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;
79 // non-motion events are directly passed to event handler functions
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 // do no reset mouse cursor before all pending events have been processed
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 // skip mouse motion events without pressed button outside level editor
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 // to prevent delay problems, skip mouse motion events if the very next
110 // event is also a mouse motion event (and therefore effectively only
111 // handling the last of a row of mouse motion events in the event queue)
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 // nothing to do if the current event is not a mouse motion event
116 if (event->type != EVENT_MOTIONNOTIFY)
119 // only skip motion events with pressed button outside the game
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 // if next event is also a mouse motion event, skip the current one
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
167 static void HandleEvents(void)
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 case EVENT_WHEELMOTION:
189 HandleWheelEvent((WheelEvent *) &event);
192 case SDL_WINDOWEVENT:
193 HandleWindowEvent((WindowEvent *) &event);
196 case EVENT_FINGERPRESS:
197 case EVENT_FINGERRELEASE:
198 case EVENT_FINGERMOTION:
199 HandleFingerEvent((FingerEvent *) &event);
202 case EVENT_TEXTINPUT:
203 HandleTextEvent((TextEvent *) &event);
206 case SDL_APP_WILLENTERBACKGROUND:
207 case SDL_APP_DIDENTERBACKGROUND:
208 case SDL_APP_WILLENTERFOREGROUND:
209 case SDL_APP_DIDENTERFOREGROUND:
210 HandlePauseResumeEvent((PauseResumeEvent *) &event);
214 case EVENT_KEYRELEASE:
215 HandleKeyEvent((KeyEvent *) &event);
219 HandleOtherEvents(&event);
223 // do not handle events for longer than standard frame delay period
224 if (DelayReached(&event_frame_delay, event_frame_delay_value))
229 void HandleOtherEvents(Event *event)
234 HandleExposeEvent((ExposeEvent *) event);
237 case EVENT_UNMAPNOTIFY:
239 // This causes the game to stop not only when iconified, but also
240 // when on another virtual desktop, which might be not desired.
241 SleepWhileUnmapped();
247 HandleFocusEvent((FocusChangeEvent *) event);
250 case EVENT_CLIENTMESSAGE:
251 HandleClientMessageEvent((ClientMessageEvent *) event);
254 case SDL_CONTROLLERBUTTONDOWN:
255 case SDL_CONTROLLERBUTTONUP:
256 // for any game controller button event, disable overlay buttons
257 SetOverlayEnabled(FALSE);
259 HandleSpecialGameControllerButtons(event);
262 case SDL_CONTROLLERDEVICEADDED:
263 case SDL_CONTROLLERDEVICEREMOVED:
264 case SDL_CONTROLLERAXISMOTION:
265 case SDL_JOYAXISMOTION:
266 case SDL_JOYBUTTONDOWN:
267 case SDL_JOYBUTTONUP:
268 HandleJoystickEvent(event);
272 case SDL_DROPCOMPLETE:
275 HandleDropEvent(event);
283 static void HandleMouseCursor(void)
285 if (game_status == GAME_MODE_TITLE)
287 // when showing title screens, hide mouse pointer (if not moved)
289 if (gfx.cursor_mode != CURSOR_NONE &&
290 DelayReached(&special_cursor_delay, special_cursor_delay_value))
292 SetMouseCursor(CURSOR_NONE);
295 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
298 // when playing, display a special mouse pointer inside the playfield
300 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
301 cursor_inside_playfield &&
302 DelayReached(&special_cursor_delay, special_cursor_delay_value))
304 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
306 SetMouseCursor(CURSOR_PLAYFIELD);
309 else if (gfx.cursor_mode != CURSOR_DEFAULT)
311 SetMouseCursor(CURSOR_DEFAULT);
314 // this is set after all pending events have been processed
315 cursor_mode_last = gfx.cursor_mode;
327 // execute event related actions after pending events have been processed
328 HandleEventActions();
330 // don't use all CPU time when idle; the main loop while playing
331 // has its own synchronization and is CPU friendly, too
333 if (game_status == GAME_MODE_PLAYING)
336 // always copy backbuffer to visible screen for every video frame
339 // reset video frame delay to default (may change again while playing)
340 SetVideoFrameDelay(MenuFrameDelay);
342 if (game_status == GAME_MODE_QUIT)
347 void ClearAutoRepeatKeyEvents(void)
349 while (PendingEvent())
353 PeekEvent(&next_event);
355 // if event is repeated key press event, remove it from event queue
356 if (next_event.type == EVENT_KEYPRESS &&
357 next_event.key.repeat)
358 WaitEvent(&next_event);
364 void ClearEventQueue(void)
368 while (NextValidEvent(&event))
372 case EVENT_BUTTONRELEASE:
373 button_status = MB_RELEASED;
376 case EVENT_KEYRELEASE:
380 case SDL_CONTROLLERBUTTONUP:
381 HandleJoystickEvent(&event);
386 HandleOtherEvents(&event);
392 static void ClearPlayerMouseAction(void)
394 local_player->mouse_action.lx = 0;
395 local_player->mouse_action.ly = 0;
396 local_player->mouse_action.button = 0;
399 void ClearPlayerAction(void)
403 // simulate key release events for still pressed keys
404 key_joystick_mapping = 0;
405 for (i = 0; i < MAX_PLAYERS; i++)
406 stored_player[i].action = 0;
408 ClearJoystickState();
409 ClearPlayerMouseAction();
412 static void SetPlayerMouseAction(int mx, int my, int button)
414 int lx = getLevelFromScreenX(mx);
415 int ly = getLevelFromScreenY(my);
416 int new_button = (!local_player->mouse_action.button && button);
418 if (local_player->mouse_action.button_hint)
419 button = local_player->mouse_action.button_hint;
421 ClearPlayerMouseAction();
423 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
426 local_player->mouse_action.lx = lx;
427 local_player->mouse_action.ly = ly;
428 local_player->mouse_action.button = button;
430 if (tape.recording && tape.pausing && tape.use_mouse)
432 // un-pause a paused game only if mouse button was newly pressed down
434 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
437 SetTileCursorXY(lx, ly);
440 void SleepWhileUnmapped(void)
442 boolean window_unmapped = TRUE;
444 KeyboardAutoRepeatOn();
446 while (window_unmapped)
450 if (!WaitValidEvent(&event))
455 case EVENT_BUTTONRELEASE:
456 button_status = MB_RELEASED;
459 case EVENT_KEYRELEASE:
460 key_joystick_mapping = 0;
463 case SDL_CONTROLLERBUTTONUP:
464 HandleJoystickEvent(&event);
465 key_joystick_mapping = 0;
468 case EVENT_MAPNOTIFY:
469 window_unmapped = FALSE;
472 case EVENT_UNMAPNOTIFY:
473 // this is only to surely prevent the 'should not happen' case
474 // of recursively looping between 'SleepWhileUnmapped()' and
475 // 'HandleOtherEvents()' which usually calls this funtion.
479 HandleOtherEvents(&event);
484 if (game_status == GAME_MODE_PLAYING)
485 KeyboardAutoRepeatOffUnlessAutoplay();
488 void HandleExposeEvent(ExposeEvent *event)
492 void HandleButtonEvent(ButtonEvent *event)
494 #if DEBUG_EVENTS_BUTTON
495 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
497 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
501 // for any mouse button event, disable playfield tile cursor
502 SetTileCursorEnabled(FALSE);
504 #if defined(HAS_SCREEN_KEYBOARD)
505 if (video.shifted_up)
506 event->y += video.shifted_up_pos;
509 motion_status = FALSE;
511 if (event->type == EVENT_BUTTONPRESS)
512 button_status = event->button;
514 button_status = MB_RELEASED;
516 HandleButton(event->x, event->y, button_status, event->button);
519 void HandleMotionEvent(MotionEvent *event)
521 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
524 motion_status = TRUE;
526 #if DEBUG_EVENTS_MOTION
527 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
528 button_status, event->x, event->y);
531 HandleButton(event->x, event->y, button_status, button_status);
534 void HandleWheelEvent(WheelEvent *event)
538 #if DEBUG_EVENTS_WHEEL
540 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
541 event->which, event->x, event->y);
543 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
544 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
545 event->which, event->x, event->y,
546 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
547 "SDL_MOUSEWHEEL_FLIPPED"));
551 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
552 event->x > 0 ? MB_WHEEL_RIGHT :
553 event->y < 0 ? MB_WHEEL_DOWN :
554 event->y > 0 ? MB_WHEEL_UP : 0);
556 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
557 // accelerated mouse wheel available on Mac and Windows
558 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
560 // no accelerated mouse wheel available on Unix/Linux
561 wheel_steps = DEFAULT_WHEEL_STEPS;
564 motion_status = FALSE;
566 button_status = button_nr;
567 HandleButton(0, 0, button_status, -button_nr);
569 button_status = MB_RELEASED;
570 HandleButton(0, 0, button_status, -button_nr);
573 void HandleWindowEvent(WindowEvent *event)
575 #if DEBUG_EVENTS_WINDOW
576 int subtype = event->event;
579 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
580 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
581 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
582 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
583 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
584 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
585 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
586 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
587 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
588 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
589 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
590 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
591 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
592 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
595 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
596 event_name, event->data1, event->data2);
600 // (not needed, as the screen gets redrawn every 20 ms anyway)
601 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
602 event->event == SDL_WINDOWEVENT_RESIZED ||
603 event->event == SDL_WINDOWEVENT_EXPOSED)
607 if (event->event == SDL_WINDOWEVENT_RESIZED)
609 if (!video.fullscreen_enabled)
611 int new_window_width = event->data1;
612 int new_window_height = event->data2;
614 // if window size has changed after resizing, calculate new scaling factor
615 if (new_window_width != video.window_width ||
616 new_window_height != video.window_height)
618 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
619 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
621 // (extreme window scaling allowed, but cannot be saved permanently)
622 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
623 setup.window_scaling_percent =
624 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
625 MAX_WINDOW_SCALING_PERCENT);
627 video.window_width = new_window_width;
628 video.window_height = new_window_height;
630 if (game_status == GAME_MODE_SETUP)
631 RedrawSetupScreenAfterFullscreenToggle();
636 #if defined(PLATFORM_ANDROID)
639 int new_display_width = event->data1;
640 int new_display_height = event->data2;
642 // if fullscreen display size has changed, device has been rotated
643 if (new_display_width != video.display_width ||
644 new_display_height != video.display_height)
646 int nr = GRID_ACTIVE_NR(); // previous screen orientation
648 video.display_width = new_display_width;
649 video.display_height = new_display_height;
651 SDLSetScreenProperties();
653 // check if screen orientation has changed (should always be true here)
654 if (nr != GRID_ACTIVE_NR())
658 if (game_status == GAME_MODE_SETUP)
659 RedrawSetupScreenAfterScreenRotation(nr);
661 nr = GRID_ACTIVE_NR();
663 overlay.grid_xsize = setup.touch.grid_xsize[nr];
664 overlay.grid_ysize = setup.touch.grid_ysize[nr];
666 for (x = 0; x < MAX_GRID_XSIZE; x++)
667 for (y = 0; y < MAX_GRID_YSIZE; y++)
668 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
676 #define NUM_TOUCH_FINGERS 3
681 SDL_FingerID finger_id;
684 } touch_info[NUM_TOUCH_FINGERS];
686 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
689 int x = event->x * overlay.grid_xsize;
690 int y = event->y * overlay.grid_ysize;
691 int grid_button = overlay.grid_button[x][y];
692 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
693 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
694 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
695 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
696 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
697 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
698 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
701 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
702 float event_x = (event->x);
703 float event_y = (event->y - ypos) / (1 - ypos);
704 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
705 event_y > 2.0 / 3.0 && event_y < 1 ?
706 setup.input[0].key.snap :
707 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
708 event_y > 2.0 / 3.0 && event_y < 1 ?
709 setup.input[0].key.drop :
710 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
711 event_y > 0 && event_y < 1.0 / 3.0 ?
712 setup.input[0].key.up :
713 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
714 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
715 setup.input[0].key.left :
716 event_x > 8.0 / 9.0 && event_x < 1 &&
717 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
718 setup.input[0].key.right :
719 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
720 event_y > 2.0 / 3.0 && event_y < 1 ?
721 setup.input[0].key.down :
724 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
726 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
730 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
732 // for any touch input event, enable overlay buttons (if activated)
733 SetOverlayEnabled(TRUE);
735 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
736 getKeyNameFromKey(key), key_status_name, event->fingerId);
738 if (key_status == KEY_PRESSED)
739 overlay.grid_button_action |= grid_button_action;
741 overlay.grid_button_action &= ~grid_button_action;
743 // check if we already know this touch event's finger id
744 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
746 if (touch_info[i].touched &&
747 touch_info[i].finger_id == event->fingerId)
749 // Error(ERR_DEBUG, "MARK 1: %d", i);
755 if (i >= NUM_TOUCH_FINGERS)
757 if (key_status == KEY_PRESSED)
759 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
761 // unknown finger id -- get new, empty slot, if available
762 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
764 if (touch_info[i].counter < oldest_counter)
767 oldest_counter = touch_info[i].counter;
769 // Error(ERR_DEBUG, "MARK 2: %d", i);
772 if (!touch_info[i].touched)
774 // Error(ERR_DEBUG, "MARK 3: %d", i);
780 if (i >= NUM_TOUCH_FINGERS)
782 // all slots allocated -- use oldest slot
785 // Error(ERR_DEBUG, "MARK 4: %d", i);
790 // release of previously unknown key (should not happen)
792 if (key != KSYM_UNDEFINED)
794 HandleKey(key, KEY_RELEASED);
796 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
797 getKeyNameFromKey(key), "KEY_RELEASED", i);
802 if (i < NUM_TOUCH_FINGERS)
804 if (key_status == KEY_PRESSED)
806 if (touch_info[i].key != key)
808 if (touch_info[i].key != KSYM_UNDEFINED)
810 HandleKey(touch_info[i].key, KEY_RELEASED);
812 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
813 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
816 if (key != KSYM_UNDEFINED)
818 HandleKey(key, KEY_PRESSED);
820 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
821 getKeyNameFromKey(key), "KEY_PRESSED", i);
825 touch_info[i].touched = TRUE;
826 touch_info[i].finger_id = event->fingerId;
827 touch_info[i].counter = Counter();
828 touch_info[i].key = key;
832 if (touch_info[i].key != KSYM_UNDEFINED)
834 HandleKey(touch_info[i].key, KEY_RELEASED);
836 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
837 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
840 touch_info[i].touched = FALSE;
841 touch_info[i].finger_id = 0;
842 touch_info[i].counter = 0;
843 touch_info[i].key = 0;
848 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
850 static Key motion_key_x = KSYM_UNDEFINED;
851 static Key motion_key_y = KSYM_UNDEFINED;
852 static Key button_key = KSYM_UNDEFINED;
853 static float motion_x1, motion_y1;
854 static float button_x1, button_y1;
855 static SDL_FingerID motion_id = -1;
856 static SDL_FingerID button_id = -1;
857 int move_trigger_distance_percent = setup.touch.move_distance;
858 int drop_trigger_distance_percent = setup.touch.drop_distance;
859 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
860 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
861 float event_x = event->x;
862 float event_y = event->y;
864 if (event->type == EVENT_FINGERPRESS)
866 if (event_x > 1.0 / 3.0)
870 motion_id = event->fingerId;
875 motion_key_x = KSYM_UNDEFINED;
876 motion_key_y = KSYM_UNDEFINED;
878 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
884 button_id = event->fingerId;
889 button_key = setup.input[0].key.snap;
891 HandleKey(button_key, KEY_PRESSED);
893 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
896 else if (event->type == EVENT_FINGERRELEASE)
898 if (event->fingerId == motion_id)
902 if (motion_key_x != KSYM_UNDEFINED)
903 HandleKey(motion_key_x, KEY_RELEASED);
904 if (motion_key_y != KSYM_UNDEFINED)
905 HandleKey(motion_key_y, KEY_RELEASED);
907 motion_key_x = KSYM_UNDEFINED;
908 motion_key_y = KSYM_UNDEFINED;
910 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
912 else if (event->fingerId == button_id)
916 if (button_key != KSYM_UNDEFINED)
917 HandleKey(button_key, KEY_RELEASED);
919 button_key = KSYM_UNDEFINED;
921 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
924 else if (event->type == EVENT_FINGERMOTION)
926 if (event->fingerId == motion_id)
928 float distance_x = ABS(event_x - motion_x1);
929 float distance_y = ABS(event_y - motion_y1);
930 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
931 event_x > motion_x1 ? setup.input[0].key.right :
933 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
934 event_y > motion_y1 ? setup.input[0].key.down :
937 if (distance_x < move_trigger_distance / 2 ||
938 distance_x < distance_y)
939 new_motion_key_x = KSYM_UNDEFINED;
941 if (distance_y < move_trigger_distance / 2 ||
942 distance_y < distance_x)
943 new_motion_key_y = KSYM_UNDEFINED;
945 if (distance_x > move_trigger_distance ||
946 distance_y > move_trigger_distance)
948 if (new_motion_key_x != motion_key_x)
950 if (motion_key_x != KSYM_UNDEFINED)
951 HandleKey(motion_key_x, KEY_RELEASED);
952 if (new_motion_key_x != KSYM_UNDEFINED)
953 HandleKey(new_motion_key_x, KEY_PRESSED);
956 if (new_motion_key_y != motion_key_y)
958 if (motion_key_y != KSYM_UNDEFINED)
959 HandleKey(motion_key_y, KEY_RELEASED);
960 if (new_motion_key_y != KSYM_UNDEFINED)
961 HandleKey(new_motion_key_y, KEY_PRESSED);
967 motion_key_x = new_motion_key_x;
968 motion_key_y = new_motion_key_y;
970 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
973 else if (event->fingerId == button_id)
975 float distance_x = ABS(event_x - button_x1);
976 float distance_y = ABS(event_y - button_y1);
978 if (distance_x < drop_trigger_distance / 2 &&
979 distance_y > drop_trigger_distance)
981 if (button_key == setup.input[0].key.snap)
982 HandleKey(button_key, KEY_RELEASED);
987 button_key = setup.input[0].key.drop;
989 HandleKey(button_key, KEY_PRESSED);
991 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
997 void HandleFingerEvent(FingerEvent *event)
999 #if DEBUG_EVENTS_FINGER
1000 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1001 event->type == EVENT_FINGERPRESS ? "pressed" :
1002 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1006 event->dx, event->dy,
1010 if (game_status != GAME_MODE_PLAYING)
1013 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1015 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1016 local_player->mouse_action.button_hint =
1017 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1018 event->x < 0.5 ? MB_LEFTBUTTON :
1019 event->x > 0.5 ? MB_RIGHTBUTTON :
1025 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1026 HandleFingerEvent_VirtualButtons(event);
1027 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1028 HandleFingerEvent_WipeGestures(event);
1031 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1033 static int old_mx = 0, old_my = 0;
1034 static int last_button = MB_LEFTBUTTON;
1035 static boolean touched = FALSE;
1036 static boolean tapped = FALSE;
1038 // screen tile was tapped (but finger not touching the screen anymore)
1039 // (this point will also be reached without receiving a touch event)
1040 if (tapped && !touched)
1042 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1047 // stop here if this function was not triggered by a touch event
1051 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1053 // finger started touching the screen
1063 ClearPlayerMouseAction();
1065 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1068 else if (button == MB_RELEASED && touched)
1070 // finger stopped touching the screen
1075 SetPlayerMouseAction(old_mx, old_my, last_button);
1077 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1079 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1084 // finger moved while touching the screen
1086 int old_x = getLevelFromScreenX(old_mx);
1087 int old_y = getLevelFromScreenY(old_my);
1088 int new_x = getLevelFromScreenX(mx);
1089 int new_y = getLevelFromScreenY(my);
1091 if (new_x != old_x || new_y != old_y)
1096 // finger moved left or right from (horizontal) starting position
1098 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1100 SetPlayerMouseAction(old_mx, old_my, button_nr);
1102 last_button = button_nr;
1104 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1108 // finger stays at or returned to (horizontal) starting position
1110 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1112 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1117 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1119 static int old_mx = 0, old_my = 0;
1120 static int last_button = MB_LEFTBUTTON;
1121 static boolean touched = FALSE;
1122 static boolean tapped = FALSE;
1124 // screen tile was tapped (but finger not touching the screen anymore)
1125 // (this point will also be reached without receiving a touch event)
1126 if (tapped && !touched)
1128 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1133 // stop here if this function was not triggered by a touch event
1137 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1139 // finger started touching the screen
1149 ClearPlayerMouseAction();
1151 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1154 else if (button == MB_RELEASED && touched)
1156 // finger stopped touching the screen
1161 SetPlayerMouseAction(old_mx, old_my, last_button);
1163 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1165 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1170 // finger moved while touching the screen
1172 int old_x = getLevelFromScreenX(old_mx);
1173 int old_y = getLevelFromScreenY(old_my);
1174 int new_x = getLevelFromScreenX(mx);
1175 int new_y = getLevelFromScreenY(my);
1177 if (new_x != old_x || new_y != old_y)
1179 // finger moved away from starting position
1181 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1183 // quickly alternate between clicking and releasing for maximum speed
1184 if (FrameCounter % 2 == 0)
1185 button_nr = MB_RELEASED;
1187 SetPlayerMouseAction(old_mx, old_my, button_nr);
1190 last_button = button_nr;
1194 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1198 // finger stays at or returned to starting position
1200 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1202 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1207 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1209 static int old_mx = 0, old_my = 0;
1210 static Key motion_key_x = KSYM_UNDEFINED;
1211 static Key motion_key_y = KSYM_UNDEFINED;
1212 static boolean touched = FALSE;
1213 static boolean started_on_player = FALSE;
1214 static boolean player_is_dropping = FALSE;
1215 static int player_drop_count = 0;
1216 static int last_player_x = -1;
1217 static int last_player_y = -1;
1219 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1228 started_on_player = FALSE;
1229 player_is_dropping = FALSE;
1230 player_drop_count = 0;
1234 motion_key_x = KSYM_UNDEFINED;
1235 motion_key_y = KSYM_UNDEFINED;
1237 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1240 else if (button == MB_RELEASED && touched)
1247 if (motion_key_x != KSYM_UNDEFINED)
1248 HandleKey(motion_key_x, KEY_RELEASED);
1249 if (motion_key_y != KSYM_UNDEFINED)
1250 HandleKey(motion_key_y, KEY_RELEASED);
1252 if (started_on_player)
1254 if (player_is_dropping)
1256 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1258 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1262 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1264 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1268 motion_key_x = KSYM_UNDEFINED;
1269 motion_key_y = KSYM_UNDEFINED;
1271 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1276 int src_x = local_player->jx;
1277 int src_y = local_player->jy;
1278 int dst_x = getLevelFromScreenX(old_mx);
1279 int dst_y = getLevelFromScreenY(old_my);
1280 int dx = dst_x - src_x;
1281 int dy = dst_y - src_y;
1282 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1283 dx > 0 ? setup.input[0].key.right :
1285 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1286 dy > 0 ? setup.input[0].key.down :
1289 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1290 (last_player_x != local_player->jx ||
1291 last_player_y != local_player->jy))
1293 // in case of asymmetric diagonal movement, use "preferred" direction
1295 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1297 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1298 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1300 local_player->last_move_dir = last_move_dir;
1302 // (required to prevent accidentally forcing direction for next movement)
1303 last_player_x = local_player->jx;
1304 last_player_y = local_player->jy;
1307 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1309 started_on_player = TRUE;
1310 player_drop_count = getPlayerInventorySize(0);
1311 player_is_dropping = (player_drop_count > 0);
1313 if (player_is_dropping)
1315 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1317 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1321 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1323 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1326 else if (dx != 0 || dy != 0)
1328 if (player_is_dropping &&
1329 player_drop_count == getPlayerInventorySize(0))
1331 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1333 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1334 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1336 player_is_dropping = FALSE;
1340 if (new_motion_key_x != motion_key_x)
1342 Error(ERR_DEBUG, "---------- %s %s ----------",
1343 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1344 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1346 if (motion_key_x != KSYM_UNDEFINED)
1347 HandleKey(motion_key_x, KEY_RELEASED);
1348 if (new_motion_key_x != KSYM_UNDEFINED)
1349 HandleKey(new_motion_key_x, KEY_PRESSED);
1352 if (new_motion_key_y != motion_key_y)
1354 Error(ERR_DEBUG, "---------- %s %s ----------",
1355 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1356 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1358 if (motion_key_y != KSYM_UNDEFINED)
1359 HandleKey(motion_key_y, KEY_RELEASED);
1360 if (new_motion_key_y != KSYM_UNDEFINED)
1361 HandleKey(new_motion_key_y, KEY_PRESSED);
1364 motion_key_x = new_motion_key_x;
1365 motion_key_y = new_motion_key_y;
1369 static void HandleButtonOrFinger(int mx, int my, int button)
1371 if (game_status != GAME_MODE_PLAYING)
1374 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1376 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1377 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1378 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1379 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1380 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1381 SetPlayerMouseAction(mx, my, button); // special case
1385 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1386 HandleButtonOrFinger_FollowFinger(mx, my, button);
1390 static boolean checkTextInputKeyModState(void)
1392 // when playing, only handle raw key events and ignore text input
1393 if (game_status == GAME_MODE_PLAYING)
1396 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1399 void HandleTextEvent(TextEvent *event)
1401 char *text = event->text;
1402 Key key = getKeyFromKeyName(text);
1404 #if DEBUG_EVENTS_TEXT
1405 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1408 text[0], (int)(text[0]),
1410 getKeyNameFromKey(key),
1414 #if !defined(HAS_SCREEN_KEYBOARD)
1415 // non-mobile devices: only handle key input with modifier keys pressed here
1416 // (every other key input is handled directly as physical key input event)
1417 if (!checkTextInputKeyModState())
1421 // process text input as "classic" (with uppercase etc.) key input event
1422 HandleKey(key, KEY_PRESSED);
1423 HandleKey(key, KEY_RELEASED);
1426 void HandlePauseResumeEvent(PauseResumeEvent *event)
1428 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1432 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1438 void HandleKeyEvent(KeyEvent *event)
1440 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1441 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1442 Key key = GetEventKey(event, with_modifiers);
1443 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1445 #if DEBUG_EVENTS_KEY
1446 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1447 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1448 event->keysym.scancode,
1453 getKeyNameFromKey(key));
1456 #if defined(PLATFORM_ANDROID)
1457 if (key == KSYM_Back)
1459 // always map the "back" button to the "escape" key on Android devices
1462 else if (key == KSYM_Menu)
1464 // the "menu" button can be used to toggle displaying virtual buttons
1465 if (key_status == KEY_PRESSED)
1466 SetOverlayEnabled(!GetOverlayEnabled());
1470 // for any other "real" key event, disable virtual buttons
1471 SetOverlayEnabled(FALSE);
1475 HandleKeyModState(keymod, key_status);
1477 // only handle raw key input without text modifier keys pressed
1478 if (!checkTextInputKeyModState())
1479 HandleKey(key, key_status);
1482 void HandleFocusEvent(FocusChangeEvent *event)
1484 static int old_joystick_status = -1;
1486 if (event->type == EVENT_FOCUSOUT)
1488 KeyboardAutoRepeatOn();
1489 old_joystick_status = joystick.status;
1490 joystick.status = JOYSTICK_NOT_AVAILABLE;
1492 ClearPlayerAction();
1494 else if (event->type == EVENT_FOCUSIN)
1496 /* When there are two Rocks'n'Diamonds windows which overlap and
1497 the player moves the pointer from one game window to the other,
1498 a 'FocusOut' event is generated for the window the pointer is
1499 leaving and a 'FocusIn' event is generated for the window the
1500 pointer is entering. In some cases, it can happen that the
1501 'FocusIn' event is handled by the one game process before the
1502 'FocusOut' event by the other game process. In this case the
1503 X11 environment would end up with activated keyboard auto repeat,
1504 because unfortunately this is a global setting and not (which
1505 would be far better) set for each X11 window individually.
1506 The effect would be keyboard auto repeat while playing the game
1507 (game_status == GAME_MODE_PLAYING), which is not desired.
1508 To avoid this special case, we just wait 1/10 second before
1509 processing the 'FocusIn' event. */
1511 if (game_status == GAME_MODE_PLAYING)
1514 KeyboardAutoRepeatOffUnlessAutoplay();
1517 if (old_joystick_status != -1)
1518 joystick.status = old_joystick_status;
1522 void HandleClientMessageEvent(ClientMessageEvent *event)
1524 if (CheckCloseWindowEvent(event))
1528 static boolean HandleDropFileEvent(char *filename)
1530 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1532 // check and extract dropped zip files into correct user data directory
1533 if (!strSuffixLower(filename, ".zip"))
1535 Error(ERR_WARN, "file '%s' not supported", filename);
1540 int tree_type = GetZipFileTreeType(filename);
1541 char *directory = TREE_USERDIR(tree_type);
1543 if (directory == NULL)
1545 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1550 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1552 if (top_dir == NULL)
1554 // error message already issued by "ExtractZipFileIntoDirectory()"
1559 AddUserTreeSetToTreeInfo(top_dir, tree_type);
1561 // when adding new level set in main menu, select it as current level set
1562 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1563 game_status == GAME_MODE_MAIN &&
1564 !game.request_active)
1566 // change current level set to newly added level set from zip file
1567 leveldir_current = getTreeInfoFromIdentifier(leveldir_first, top_dir);
1569 // change current level number to first level of newly added level set
1570 level_nr = leveldir_current->first_level;
1572 // redraw screen to reflect changed level set
1575 // save this level set and level number as last selected level set
1576 SaveLevelSetup_LastSeries();
1577 SaveLevelSetup_SeriesInfo();
1583 static void HandleDropTextEvent(char *text)
1585 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1588 void HandleDropEvent(Event *event)
1590 static int files_succeeded = 0;
1591 static int files_failed = 0;
1593 switch (event->type)
1597 files_succeeded = 0;
1605 boolean success = HandleDropFileEvent(event->drop.file);
1617 HandleDropTextEvent(event->drop.file);
1622 case SDL_DROPCOMPLETE:
1624 // only show request dialog if no other request dialog already active
1625 if (!game.request_active)
1627 if (files_succeeded > 0 && files_failed > 0)
1628 Request("New level or artwork set(s) added, "
1629 "but some dropped file(s) failed!", REQ_CONFIRM);
1630 else if (files_succeeded > 0)
1631 Request("New level or artwork set(s) added!", REQ_CONFIRM);
1632 else if (files_failed > 0)
1633 Request("Failed to process dropped file(s)!", REQ_CONFIRM);
1640 if (event->drop.file != NULL)
1641 SDL_free(event->drop.file);
1644 void HandleButton(int mx, int my, int button, int button_nr)
1646 static int old_mx = 0, old_my = 0;
1647 boolean button_hold = FALSE;
1648 boolean handle_gadgets = TRUE;
1654 button_nr = -button_nr;
1663 #if defined(PLATFORM_ANDROID)
1664 // when playing, only handle gadgets when using "follow finger" controls
1665 // or when using touch controls in combination with the MM game engine
1666 // or when using gadgets that do not overlap with virtual buttons
1668 (game_status != GAME_MODE_PLAYING ||
1669 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1670 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1671 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1672 !virtual_button_pressed));
1675 if (HandleGlobalAnimClicks(mx, my, button))
1677 // do not handle this button event anymore
1678 return; // force mouse event not to be handled at all
1681 if (handle_gadgets && HandleGadgets(mx, my, button))
1683 // do not handle this button event anymore
1684 mx = my = -32; // force mouse event to be outside screen tiles
1687 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1690 // do not use scroll wheel button events for anything other than gadgets
1691 if (IS_WHEEL_BUTTON(button_nr))
1694 switch (game_status)
1696 case GAME_MODE_TITLE:
1697 HandleTitleScreen(mx, my, 0, 0, button);
1700 case GAME_MODE_MAIN:
1701 HandleMainMenu(mx, my, 0, 0, button);
1704 case GAME_MODE_PSEUDO_TYPENAME:
1705 HandleTypeName(0, KSYM_Return);
1708 case GAME_MODE_LEVELS:
1709 HandleChooseLevelSet(mx, my, 0, 0, button);
1712 case GAME_MODE_LEVELNR:
1713 HandleChooseLevelNr(mx, my, 0, 0, button);
1716 case GAME_MODE_SCORES:
1717 HandleHallOfFame(0, 0, 0, 0, button);
1720 case GAME_MODE_EDITOR:
1721 HandleLevelEditorIdle();
1724 case GAME_MODE_INFO:
1725 HandleInfoScreen(mx, my, 0, 0, button);
1728 case GAME_MODE_SETUP:
1729 HandleSetupScreen(mx, my, 0, 0, button);
1732 case GAME_MODE_PLAYING:
1733 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1734 HandleButtonOrFinger(mx, my, button);
1736 SetPlayerMouseAction(mx, my, button);
1739 if (button == MB_PRESSED && !motion_status && !button_hold &&
1740 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1741 DumpTileFromScreen(mx, my);
1751 static boolean is_string_suffix(char *string, char *suffix)
1753 int string_len = strlen(string);
1754 int suffix_len = strlen(suffix);
1756 if (suffix_len > string_len)
1759 return (strEqual(&string[string_len - suffix_len], suffix));
1762 #define MAX_CHEAT_INPUT_LEN 32
1764 static void HandleKeysSpecial(Key key)
1766 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1767 char letter = getCharFromKey(key);
1768 int cheat_input_len = strlen(cheat_input);
1774 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1776 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1777 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1779 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1782 cheat_input[cheat_input_len++] = letter;
1783 cheat_input[cheat_input_len] = '\0';
1785 #if DEBUG_EVENTS_KEY
1786 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1789 if (game_status == GAME_MODE_MAIN)
1791 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1792 is_string_suffix(cheat_input, ":ist"))
1794 InsertSolutionTape();
1796 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1797 is_string_suffix(cheat_input, ":pst"))
1801 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1802 is_string_suffix(cheat_input, ":rg"))
1804 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1807 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1808 is_string_suffix(cheat_input, ":rs"))
1810 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1813 else if (is_string_suffix(cheat_input, ":reload-music") ||
1814 is_string_suffix(cheat_input, ":rm"))
1816 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1819 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1820 is_string_suffix(cheat_input, ":ra"))
1822 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1823 1 << ARTWORK_TYPE_SOUNDS |
1824 1 << ARTWORK_TYPE_MUSIC);
1827 else if (is_string_suffix(cheat_input, ":dump-level") ||
1828 is_string_suffix(cheat_input, ":dl"))
1832 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1833 is_string_suffix(cheat_input, ":dt"))
1837 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1838 is_string_suffix(cheat_input, ":ft"))
1840 /* fix single-player tapes that contain player input for more than one
1841 player (due to a bug in 3.3.1.2 and earlier versions), which results
1842 in playing levels with more than one player in multi-player mode,
1843 even though the tape was originally recorded in single-player mode */
1845 // remove player input actions for all players but the first one
1846 for (i = 1; i < MAX_PLAYERS; i++)
1847 tape.player_participates[i] = FALSE;
1849 tape.changed = TRUE;
1851 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1852 is_string_suffix(cheat_input, ":snl"))
1854 SaveNativeLevel(&level);
1856 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1857 is_string_suffix(cheat_input, ":fps"))
1859 global.show_frames_per_second = !global.show_frames_per_second;
1862 else if (game_status == GAME_MODE_PLAYING)
1865 if (is_string_suffix(cheat_input, ".q"))
1866 DEBUG_SetMaximumDynamite();
1869 else if (game_status == GAME_MODE_EDITOR)
1871 if (is_string_suffix(cheat_input, ":dump-brush") ||
1872 is_string_suffix(cheat_input, ":DB"))
1876 else if (is_string_suffix(cheat_input, ":DDB"))
1881 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1883 if (letter == 'x') // copy brush to clipboard (small size)
1885 CopyBrushToClipboard_Small();
1887 else if (letter == 'c') // copy brush to clipboard (normal size)
1889 CopyBrushToClipboard();
1891 else if (letter == 'v') // paste brush from Clipboard
1893 CopyClipboardToBrush();
1898 // special key shortcuts for all game modes
1899 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1900 is_string_suffix(cheat_input, ":dea") ||
1901 is_string_suffix(cheat_input, ":DEA"))
1903 DumpGadgetIdentifiers();
1904 DumpScreenIdentifiers();
1908 boolean HandleKeysDebug(Key key, int key_status)
1913 if (key_status != KEY_PRESSED)
1916 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1918 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1920 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1922 if (key == setup.debug.frame_delay_key[i] &&
1923 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1925 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1926 setup.debug.frame_delay[i] : setup.game_frame_delay);
1928 if (!setup.debug.frame_delay_game_only)
1929 MenuFrameDelay = GameFrameDelay;
1931 SetVideoFrameDelay(GameFrameDelay);
1933 if (GameFrameDelay > ONE_SECOND_DELAY)
1934 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1935 else if (GameFrameDelay != 0)
1936 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1937 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1938 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1940 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1947 if (game_status == GAME_MODE_PLAYING)
1951 options.debug = !options.debug;
1953 Error(ERR_DEBUG, "debug mode %s",
1954 (options.debug ? "enabled" : "disabled"));
1958 else if (key == KSYM_v)
1960 Error(ERR_DEBUG, "currently using game engine version %d",
1961 game.engine_version);
1971 void HandleKey(Key key, int key_status)
1973 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1974 static boolean ignore_repeated_key = FALSE;
1975 static struct SetupKeyboardInfo ski;
1976 static struct SetupShortcutInfo ssi;
1985 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1986 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1987 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1988 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1989 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1990 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1995 if (HandleKeysDebug(key, key_status))
1996 return; // do not handle already processed keys again
1998 // map special keys (media keys / remote control buttons) to default keys
1999 if (key == KSYM_PlayPause)
2001 else if (key == KSYM_Select)
2004 HandleSpecialGameControllerKeys(key, key_status);
2006 if (game_status == GAME_MODE_PLAYING)
2008 // only needed for single-step tape recording mode
2009 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2012 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2014 byte key_action = 0;
2016 if (setup.input[pnr].use_joystick)
2019 ski = setup.input[pnr].key;
2021 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2022 if (key == *key_info[i].key_custom)
2023 key_action |= key_info[i].action;
2025 // use combined snap+direction keys for the first player only
2028 ssi = setup.shortcut;
2030 for (i = 0; i < NUM_DIRECTIONS; i++)
2031 if (key == *key_info[i].key_snap)
2032 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2035 if (key_status == KEY_PRESSED)
2036 stored_player[pnr].action |= key_action;
2038 stored_player[pnr].action &= ~key_action;
2040 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2042 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2044 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2046 // if snap key already pressed, keep pause mode when releasing
2047 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2048 has_snapped[pnr] = TRUE;
2050 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2052 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2054 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2055 getRedDiskReleaseFlag_SP() == 0)
2057 // add a single inactive frame before dropping starts
2058 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2059 stored_player[pnr].force_dropping = TRUE;
2062 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2064 // if snap key was pressed without direction, leave pause mode
2065 if (!has_snapped[pnr])
2066 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2068 has_snapped[pnr] = FALSE;
2071 else if (tape.recording && tape.pausing && !tape.use_mouse)
2073 // prevent key release events from un-pausing a paused game
2074 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2075 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2078 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2079 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2085 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2086 if (key == key_info[i].key_default)
2087 joy |= key_info[i].action;
2092 if (key_status == KEY_PRESSED)
2093 key_joystick_mapping |= joy;
2095 key_joystick_mapping &= ~joy;
2100 if (game_status != GAME_MODE_PLAYING)
2101 key_joystick_mapping = 0;
2103 if (key_status == KEY_RELEASED)
2105 // reset flag to ignore repeated "key pressed" events after key release
2106 ignore_repeated_key = FALSE;
2111 if ((key == KSYM_F11 ||
2112 ((key == KSYM_Return ||
2113 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2114 video.fullscreen_available &&
2115 !ignore_repeated_key)
2117 setup.fullscreen = !setup.fullscreen;
2119 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2121 if (game_status == GAME_MODE_SETUP)
2122 RedrawSetupScreenAfterFullscreenToggle();
2124 // set flag to ignore repeated "key pressed" events
2125 ignore_repeated_key = TRUE;
2130 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2131 key == KSYM_minus || key == KSYM_KP_Subtract ||
2132 key == KSYM_plus || key == KSYM_KP_Add ||
2133 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2134 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2135 video.window_scaling_available &&
2136 !video.fullscreen_enabled)
2138 if (key == KSYM_0 || key == KSYM_KP_0)
2139 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2140 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2141 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2143 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2145 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2146 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2147 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2148 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2150 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2152 if (game_status == GAME_MODE_SETUP)
2153 RedrawSetupScreenAfterFullscreenToggle();
2158 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2159 key == KSYM_Return ||
2160 key == KSYM_Escape)))
2162 // do not handle this key event anymore
2163 if (key != KSYM_Escape) // always allow ESC key to be handled
2167 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2168 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2175 if (game_status == GAME_MODE_MAIN &&
2176 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2178 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2183 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2185 if (key == setup.shortcut.save_game)
2187 else if (key == setup.shortcut.load_game)
2189 else if (key == setup.shortcut.toggle_pause)
2190 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2192 HandleTapeButtonKeys(key);
2193 HandleSoundButtonKeys(key);
2196 if (game_status == GAME_MODE_PLAYING && !network_playing)
2198 int centered_player_nr_next = -999;
2200 if (key == setup.shortcut.focus_player_all)
2201 centered_player_nr_next = -1;
2203 for (i = 0; i < MAX_PLAYERS; i++)
2204 if (key == setup.shortcut.focus_player[i])
2205 centered_player_nr_next = i;
2207 if (centered_player_nr_next != -999)
2209 game.centered_player_nr_next = centered_player_nr_next;
2210 game.set_centered_player = TRUE;
2214 tape.centered_player_nr_next = game.centered_player_nr_next;
2215 tape.set_centered_player = TRUE;
2220 HandleKeysSpecial(key);
2222 if (HandleGadgetsKeyInput(key))
2223 return; // do not handle already processed keys again
2225 switch (game_status)
2227 case GAME_MODE_PSEUDO_TYPENAME:
2228 HandleTypeName(0, key);
2231 case GAME_MODE_TITLE:
2232 case GAME_MODE_MAIN:
2233 case GAME_MODE_LEVELS:
2234 case GAME_MODE_LEVELNR:
2235 case GAME_MODE_SETUP:
2236 case GAME_MODE_INFO:
2237 case GAME_MODE_SCORES:
2239 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2246 if (game_status == GAME_MODE_TITLE)
2247 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2248 else if (game_status == GAME_MODE_MAIN)
2249 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2250 else if (game_status == GAME_MODE_LEVELS)
2251 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2252 else if (game_status == GAME_MODE_LEVELNR)
2253 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2254 else if (game_status == GAME_MODE_SETUP)
2255 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2256 else if (game_status == GAME_MODE_INFO)
2257 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2258 else if (game_status == GAME_MODE_SCORES)
2259 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2263 if (game_status != GAME_MODE_MAIN)
2264 FadeSkipNextFadeIn();
2266 if (game_status == GAME_MODE_TITLE)
2267 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2268 else if (game_status == GAME_MODE_LEVELS)
2269 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2270 else if (game_status == GAME_MODE_LEVELNR)
2271 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2272 else if (game_status == GAME_MODE_SETUP)
2273 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2274 else if (game_status == GAME_MODE_INFO)
2275 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2276 else if (game_status == GAME_MODE_SCORES)
2277 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2281 if (game_status == GAME_MODE_LEVELS)
2282 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2283 else if (game_status == GAME_MODE_LEVELNR)
2284 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2285 else if (game_status == GAME_MODE_SETUP)
2286 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2287 else if (game_status == GAME_MODE_INFO)
2288 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2289 else if (game_status == GAME_MODE_SCORES)
2290 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2293 case KSYM_Page_Down:
2294 if (game_status == GAME_MODE_LEVELS)
2295 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2296 else if (game_status == GAME_MODE_LEVELNR)
2297 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2298 else if (game_status == GAME_MODE_SETUP)
2299 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2300 else if (game_status == GAME_MODE_INFO)
2301 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2302 else if (game_status == GAME_MODE_SCORES)
2303 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2311 case GAME_MODE_EDITOR:
2312 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2313 HandleLevelEditorKeyInput(key);
2316 case GAME_MODE_PLAYING:
2321 RequestQuitGame(setup.ask_on_escape);
2331 if (key == KSYM_Escape)
2333 SetGameStatus(GAME_MODE_MAIN);
2342 void HandleNoEvent(void)
2344 HandleMouseCursor();
2346 switch (game_status)
2348 case GAME_MODE_PLAYING:
2349 HandleButtonOrFinger(-1, -1, -1);
2354 void HandleEventActions(void)
2356 // if (button_status && game_status != GAME_MODE_PLAYING)
2357 if (button_status && (game_status != GAME_MODE_PLAYING ||
2359 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2361 HandleButton(0, 0, button_status, -button_status);
2368 if (network.enabled)
2371 switch (game_status)
2373 case GAME_MODE_MAIN:
2374 DrawPreviewLevelAnimation();
2377 case GAME_MODE_EDITOR:
2378 HandleLevelEditorIdle();
2386 static void HandleTileCursor(int dx, int dy, int button)
2389 ClearPlayerMouseAction();
2396 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2397 (dx < 0 ? MB_LEFTBUTTON :
2398 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2400 else if (!tile_cursor.moving)
2402 int old_xpos = tile_cursor.xpos;
2403 int old_ypos = tile_cursor.ypos;
2404 int new_xpos = old_xpos;
2405 int new_ypos = old_ypos;
2407 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2408 new_xpos = old_xpos + dx;
2410 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2411 new_ypos = old_ypos + dy;
2413 SetTileCursorTargetXY(new_xpos, new_ypos);
2417 static int HandleJoystickForAllPlayers(void)
2421 boolean no_joysticks_configured = TRUE;
2422 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2423 static byte joy_action_last[MAX_PLAYERS];
2425 for (i = 0; i < MAX_PLAYERS; i++)
2426 if (setup.input[i].use_joystick)
2427 no_joysticks_configured = FALSE;
2429 // if no joysticks configured, map connected joysticks to players
2430 if (no_joysticks_configured)
2431 use_as_joystick_nr = TRUE;
2433 for (i = 0; i < MAX_PLAYERS; i++)
2435 byte joy_action = 0;
2437 joy_action = JoystickExt(i, use_as_joystick_nr);
2438 result |= joy_action;
2440 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2441 joy_action != joy_action_last[i])
2442 stored_player[i].action = joy_action;
2444 joy_action_last[i] = joy_action;
2450 void HandleJoystick(void)
2452 static unsigned int joytest_delay = 0;
2453 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2454 static int joytest_last = 0;
2455 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2456 int delay_value = GADGET_FRAME_DELAY;
2457 int joystick = HandleJoystickForAllPlayers();
2458 int keyboard = key_joystick_mapping;
2459 int joy = (joystick | keyboard);
2460 int joytest = joystick;
2461 int left = joy & JOY_LEFT;
2462 int right = joy & JOY_RIGHT;
2463 int up = joy & JOY_UP;
2464 int down = joy & JOY_DOWN;
2465 int button = joy & JOY_BUTTON;
2466 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2467 int dx = (left ? -1 : right ? 1 : 0);
2468 int dy = (up ? -1 : down ? 1 : 0);
2469 boolean use_delay_value_first = (joytest != joytest_last);
2471 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2473 // do not handle this button event anymore
2477 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2478 anyTextGadgetActive()))
2480 // leave name input in main menu or text input gadget
2481 HandleKey(KSYM_Escape, KEY_PRESSED);
2482 HandleKey(KSYM_Escape, KEY_RELEASED);
2487 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2489 if (game_status == GAME_MODE_PLAYING)
2491 // when playing MM style levels, also use delay for keyboard events
2492 joytest |= keyboard;
2494 // only use first delay value for new events, but not for changed events
2495 use_delay_value_first = (!joytest != !joytest_last);
2497 // only use delay after the initial keyboard event
2501 // for any joystick or keyboard event, enable playfield tile cursor
2502 if (dx || dy || button)
2503 SetTileCursorEnabled(TRUE);
2506 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2508 // delay joystick/keyboard actions if axes/keys continually pressed
2509 newbutton = dx = dy = 0;
2513 // first start with longer delay, then continue with shorter delay
2514 joytest_delay_value =
2515 (use_delay_value_first ? delay_value_first : delay_value);
2518 joytest_last = joytest;
2520 switch (game_status)
2522 case GAME_MODE_TITLE:
2523 case GAME_MODE_MAIN:
2524 case GAME_MODE_LEVELS:
2525 case GAME_MODE_LEVELNR:
2526 case GAME_MODE_SETUP:
2527 case GAME_MODE_INFO:
2528 case GAME_MODE_SCORES:
2530 if (anyTextGadgetActive())
2533 if (game_status == GAME_MODE_TITLE)
2534 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2535 else if (game_status == GAME_MODE_MAIN)
2536 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2537 else if (game_status == GAME_MODE_LEVELS)
2538 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2539 else if (game_status == GAME_MODE_LEVELNR)
2540 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2541 else if (game_status == GAME_MODE_SETUP)
2542 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2543 else if (game_status == GAME_MODE_INFO)
2544 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2545 else if (game_status == GAME_MODE_SCORES)
2546 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2551 case GAME_MODE_PLAYING:
2553 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2554 if (tape.playing || keyboard)
2555 newbutton = ((joy & JOY_BUTTON) != 0);
2558 if (newbutton && game.all_players_gone)
2565 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2567 if (joystick & JOY_ACTION)
2568 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2570 else if (tape.recording && tape.pausing && !tape.use_mouse)
2572 if (joystick & JOY_ACTION)
2573 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2576 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2577 HandleTileCursor(dx, dy, button);
2586 void HandleSpecialGameControllerButtons(Event *event)
2591 switch (event->type)
2593 case SDL_CONTROLLERBUTTONDOWN:
2594 key_status = KEY_PRESSED;
2597 case SDL_CONTROLLERBUTTONUP:
2598 key_status = KEY_RELEASED;
2605 switch (event->cbutton.button)
2607 case SDL_CONTROLLER_BUTTON_START:
2611 case SDL_CONTROLLER_BUTTON_BACK:
2619 HandleKey(key, key_status);
2622 void HandleSpecialGameControllerKeys(Key key, int key_status)
2624 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2625 int button = SDL_CONTROLLER_BUTTON_INVALID;
2627 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2628 if (key == KSYM_Rewind)
2629 button = SDL_CONTROLLER_BUTTON_A;
2630 else if (key == KSYM_FastForward || key == KSYM_Menu)
2631 button = SDL_CONTROLLER_BUTTON_B;
2633 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2637 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2638 SDL_CONTROLLERBUTTONUP);
2640 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2641 event.cbutton.button = button;
2642 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2645 HandleJoystickEvent(&event);
2650 boolean DoKeysymAction(int keysym)
2654 Key key = (Key)(-keysym);
2656 HandleKey(key, KEY_PRESSED);
2657 HandleKey(key, KEY_RELEASED);