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 TreeInfo *tree_node = NULL;
1541 int tree_type = GetZipFileTreeType(filename);
1542 char *directory = TREE_USERDIR(tree_type);
1544 if (directory == NULL)
1546 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1551 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1552 game_status == GAME_MODE_LEVELS &&
1553 leveldir_current->node_parent != NULL)
1555 // extract new level set next to currently selected level set
1556 tree_node = leveldir_current;
1558 // get parent directory of currently selected level set directory
1559 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1561 // use private level directory instead of top-level package level directory
1562 if (strPrefix(directory, options.level_directory) &&
1563 strEqual(leveldir_current->node_parent->fullpath, "."))
1564 directory = getUserLevelDir(NULL);
1567 // extract level or artwork set from zip file to target directory
1568 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1570 if (top_dir == NULL)
1572 // error message already issued by "ExtractZipFileIntoDirectory()"
1577 // add extracted level or artwork set to tree info structure
1578 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1580 // update menu screen (and possibly change current level set)
1581 DrawScreenAfterAddingSet(top_dir, tree_type);
1586 static void HandleDropTextEvent(char *text)
1588 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1591 void HandleDropEvent(Event *event)
1593 static int files_succeeded = 0;
1594 static int files_failed = 0;
1596 switch (event->type)
1600 files_succeeded = 0;
1608 boolean success = HandleDropFileEvent(event->drop.file);
1620 HandleDropTextEvent(event->drop.file);
1625 case SDL_DROPCOMPLETE:
1627 // only show request dialog if no other request dialog already active
1628 if (!game.request_active)
1630 if (files_succeeded > 0 && files_failed > 0)
1631 Request("New level or artwork set(s) added, "
1632 "but some dropped file(s) failed!", REQ_CONFIRM);
1633 else if (files_succeeded > 0)
1634 Request("New level or artwork set(s) added!", REQ_CONFIRM);
1635 else if (files_failed > 0)
1636 Request("Failed to process dropped file(s)!", REQ_CONFIRM);
1643 if (event->drop.file != NULL)
1644 SDL_free(event->drop.file);
1647 void HandleButton(int mx, int my, int button, int button_nr)
1649 static int old_mx = 0, old_my = 0;
1650 boolean button_hold = FALSE;
1651 boolean handle_gadgets = TRUE;
1657 button_nr = -button_nr;
1666 #if defined(PLATFORM_ANDROID)
1667 // when playing, only handle gadgets when using "follow finger" controls
1668 // or when using touch controls in combination with the MM game engine
1669 // or when using gadgets that do not overlap with virtual buttons
1671 (game_status != GAME_MODE_PLAYING ||
1672 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1673 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1674 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1675 !virtual_button_pressed));
1678 if (HandleGlobalAnimClicks(mx, my, button))
1680 // do not handle this button event anymore
1681 return; // force mouse event not to be handled at all
1684 if (handle_gadgets && HandleGadgets(mx, my, button))
1686 // do not handle this button event anymore
1687 mx = my = -32; // force mouse event to be outside screen tiles
1690 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1693 // do not use scroll wheel button events for anything other than gadgets
1694 if (IS_WHEEL_BUTTON(button_nr))
1697 switch (game_status)
1699 case GAME_MODE_TITLE:
1700 HandleTitleScreen(mx, my, 0, 0, button);
1703 case GAME_MODE_MAIN:
1704 HandleMainMenu(mx, my, 0, 0, button);
1707 case GAME_MODE_PSEUDO_TYPENAME:
1708 HandleTypeName(0, KSYM_Return);
1711 case GAME_MODE_LEVELS:
1712 HandleChooseLevelSet(mx, my, 0, 0, button);
1715 case GAME_MODE_LEVELNR:
1716 HandleChooseLevelNr(mx, my, 0, 0, button);
1719 case GAME_MODE_SCORES:
1720 HandleHallOfFame(0, 0, 0, 0, button);
1723 case GAME_MODE_EDITOR:
1724 HandleLevelEditorIdle();
1727 case GAME_MODE_INFO:
1728 HandleInfoScreen(mx, my, 0, 0, button);
1731 case GAME_MODE_SETUP:
1732 HandleSetupScreen(mx, my, 0, 0, button);
1735 case GAME_MODE_PLAYING:
1736 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1737 HandleButtonOrFinger(mx, my, button);
1739 SetPlayerMouseAction(mx, my, button);
1742 if (button == MB_PRESSED && !motion_status && !button_hold &&
1743 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1744 DumpTileFromScreen(mx, my);
1754 static boolean is_string_suffix(char *string, char *suffix)
1756 int string_len = strlen(string);
1757 int suffix_len = strlen(suffix);
1759 if (suffix_len > string_len)
1762 return (strEqual(&string[string_len - suffix_len], suffix));
1765 #define MAX_CHEAT_INPUT_LEN 32
1767 static void HandleKeysSpecial(Key key)
1769 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1770 char letter = getCharFromKey(key);
1771 int cheat_input_len = strlen(cheat_input);
1777 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1779 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1780 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1782 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1785 cheat_input[cheat_input_len++] = letter;
1786 cheat_input[cheat_input_len] = '\0';
1788 #if DEBUG_EVENTS_KEY
1789 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1792 if (game_status == GAME_MODE_MAIN)
1794 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1795 is_string_suffix(cheat_input, ":ist"))
1797 InsertSolutionTape();
1799 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1800 is_string_suffix(cheat_input, ":pst"))
1804 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1805 is_string_suffix(cheat_input, ":rg"))
1807 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1810 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1811 is_string_suffix(cheat_input, ":rs"))
1813 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1816 else if (is_string_suffix(cheat_input, ":reload-music") ||
1817 is_string_suffix(cheat_input, ":rm"))
1819 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1822 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1823 is_string_suffix(cheat_input, ":ra"))
1825 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1826 1 << ARTWORK_TYPE_SOUNDS |
1827 1 << ARTWORK_TYPE_MUSIC);
1830 else if (is_string_suffix(cheat_input, ":dump-level") ||
1831 is_string_suffix(cheat_input, ":dl"))
1835 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1836 is_string_suffix(cheat_input, ":dt"))
1840 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1841 is_string_suffix(cheat_input, ":ft"))
1843 /* fix single-player tapes that contain player input for more than one
1844 player (due to a bug in 3.3.1.2 and earlier versions), which results
1845 in playing levels with more than one player in multi-player mode,
1846 even though the tape was originally recorded in single-player mode */
1848 // remove player input actions for all players but the first one
1849 for (i = 1; i < MAX_PLAYERS; i++)
1850 tape.player_participates[i] = FALSE;
1852 tape.changed = TRUE;
1854 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1855 is_string_suffix(cheat_input, ":snl"))
1857 SaveNativeLevel(&level);
1859 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1860 is_string_suffix(cheat_input, ":fps"))
1862 global.show_frames_per_second = !global.show_frames_per_second;
1865 else if (game_status == GAME_MODE_PLAYING)
1868 if (is_string_suffix(cheat_input, ".q"))
1869 DEBUG_SetMaximumDynamite();
1872 else if (game_status == GAME_MODE_EDITOR)
1874 if (is_string_suffix(cheat_input, ":dump-brush") ||
1875 is_string_suffix(cheat_input, ":DB"))
1879 else if (is_string_suffix(cheat_input, ":DDB"))
1884 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1886 if (letter == 'x') // copy brush to clipboard (small size)
1888 CopyBrushToClipboard_Small();
1890 else if (letter == 'c') // copy brush to clipboard (normal size)
1892 CopyBrushToClipboard();
1894 else if (letter == 'v') // paste brush from Clipboard
1896 CopyClipboardToBrush();
1901 // special key shortcuts for all game modes
1902 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1903 is_string_suffix(cheat_input, ":dea") ||
1904 is_string_suffix(cheat_input, ":DEA"))
1906 DumpGadgetIdentifiers();
1907 DumpScreenIdentifiers();
1911 boolean HandleKeysDebug(Key key, int key_status)
1916 if (key_status != KEY_PRESSED)
1919 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1921 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1923 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1925 if (key == setup.debug.frame_delay_key[i] &&
1926 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1928 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1929 setup.debug.frame_delay[i] : setup.game_frame_delay);
1931 if (!setup.debug.frame_delay_game_only)
1932 MenuFrameDelay = GameFrameDelay;
1934 SetVideoFrameDelay(GameFrameDelay);
1936 if (GameFrameDelay > ONE_SECOND_DELAY)
1937 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1938 else if (GameFrameDelay != 0)
1939 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1940 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1941 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1943 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1950 if (game_status == GAME_MODE_PLAYING)
1954 options.debug = !options.debug;
1956 Error(ERR_DEBUG, "debug mode %s",
1957 (options.debug ? "enabled" : "disabled"));
1961 else if (key == KSYM_v)
1963 Error(ERR_DEBUG, "currently using game engine version %d",
1964 game.engine_version);
1974 void HandleKey(Key key, int key_status)
1976 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1977 static boolean ignore_repeated_key = FALSE;
1978 static struct SetupKeyboardInfo ski;
1979 static struct SetupShortcutInfo ssi;
1988 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1989 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1990 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1991 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1992 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1993 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1998 if (HandleKeysDebug(key, key_status))
1999 return; // do not handle already processed keys again
2001 // map special keys (media keys / remote control buttons) to default keys
2002 if (key == KSYM_PlayPause)
2004 else if (key == KSYM_Select)
2007 HandleSpecialGameControllerKeys(key, key_status);
2009 if (game_status == GAME_MODE_PLAYING)
2011 // only needed for single-step tape recording mode
2012 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2015 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2017 byte key_action = 0;
2018 byte key_snap_action = 0;
2020 if (setup.input[pnr].use_joystick)
2023 ski = setup.input[pnr].key;
2025 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2026 if (key == *key_info[i].key_custom)
2027 key_action |= key_info[i].action;
2029 // use combined snap+direction keys for the first player only
2032 ssi = setup.shortcut;
2034 // also remember normal snap key when handling snap+direction keys
2035 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2037 for (i = 0; i < NUM_DIRECTIONS; i++)
2039 if (key == *key_info[i].key_snap)
2041 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2042 key_snap_action |= key_info[i].action;
2047 if (key_status == KEY_PRESSED)
2049 stored_player[pnr].action |= key_action;
2050 stored_player[pnr].snap_action |= key_snap_action;
2054 stored_player[pnr].action &= ~key_action;
2055 stored_player[pnr].snap_action &= ~key_snap_action;
2058 // restore snap action if one of several pressed snap keys was released
2059 if (stored_player[pnr].snap_action)
2060 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2062 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2064 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2066 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2068 // if snap key already pressed, keep pause mode when releasing
2069 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2070 has_snapped[pnr] = TRUE;
2072 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2074 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2076 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2077 getRedDiskReleaseFlag_SP() == 0)
2079 // add a single inactive frame before dropping starts
2080 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2081 stored_player[pnr].force_dropping = TRUE;
2084 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2086 // if snap key was pressed without direction, leave pause mode
2087 if (!has_snapped[pnr])
2088 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2090 has_snapped[pnr] = FALSE;
2093 else if (tape.recording && tape.pausing && !tape.use_mouse)
2095 // prevent key release events from un-pausing a paused game
2096 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2097 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2100 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2101 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2107 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2108 if (key == key_info[i].key_default)
2109 joy |= key_info[i].action;
2114 if (key_status == KEY_PRESSED)
2115 key_joystick_mapping |= joy;
2117 key_joystick_mapping &= ~joy;
2122 if (game_status != GAME_MODE_PLAYING)
2123 key_joystick_mapping = 0;
2125 if (key_status == KEY_RELEASED)
2127 // reset flag to ignore repeated "key pressed" events after key release
2128 ignore_repeated_key = FALSE;
2133 if ((key == KSYM_F11 ||
2134 ((key == KSYM_Return ||
2135 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2136 video.fullscreen_available &&
2137 !ignore_repeated_key)
2139 setup.fullscreen = !setup.fullscreen;
2141 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2143 if (game_status == GAME_MODE_SETUP)
2144 RedrawSetupScreenAfterFullscreenToggle();
2146 // set flag to ignore repeated "key pressed" events
2147 ignore_repeated_key = TRUE;
2152 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2153 key == KSYM_minus || key == KSYM_KP_Subtract ||
2154 key == KSYM_plus || key == KSYM_KP_Add ||
2155 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2156 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2157 video.window_scaling_available &&
2158 !video.fullscreen_enabled)
2160 if (key == KSYM_0 || key == KSYM_KP_0)
2161 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2162 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2163 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2165 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2167 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2168 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2169 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2170 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2172 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2174 if (game_status == GAME_MODE_SETUP)
2175 RedrawSetupScreenAfterFullscreenToggle();
2180 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2181 key == KSYM_Return ||
2182 key == KSYM_Escape)))
2184 // do not handle this key event anymore
2185 if (key != KSYM_Escape) // always allow ESC key to be handled
2189 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2190 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2197 if (game_status == GAME_MODE_MAIN &&
2198 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2200 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2205 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2207 if (key == setup.shortcut.save_game)
2209 else if (key == setup.shortcut.load_game)
2211 else if (key == setup.shortcut.toggle_pause)
2212 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2214 HandleTapeButtonKeys(key);
2215 HandleSoundButtonKeys(key);
2218 if (game_status == GAME_MODE_PLAYING && !network_playing)
2220 int centered_player_nr_next = -999;
2222 if (key == setup.shortcut.focus_player_all)
2223 centered_player_nr_next = -1;
2225 for (i = 0; i < MAX_PLAYERS; i++)
2226 if (key == setup.shortcut.focus_player[i])
2227 centered_player_nr_next = i;
2229 if (centered_player_nr_next != -999)
2231 game.centered_player_nr_next = centered_player_nr_next;
2232 game.set_centered_player = TRUE;
2236 tape.centered_player_nr_next = game.centered_player_nr_next;
2237 tape.set_centered_player = TRUE;
2242 HandleKeysSpecial(key);
2244 if (HandleGadgetsKeyInput(key))
2245 return; // do not handle already processed keys again
2247 switch (game_status)
2249 case GAME_MODE_PSEUDO_TYPENAME:
2250 HandleTypeName(0, key);
2253 case GAME_MODE_TITLE:
2254 case GAME_MODE_MAIN:
2255 case GAME_MODE_LEVELS:
2256 case GAME_MODE_LEVELNR:
2257 case GAME_MODE_SETUP:
2258 case GAME_MODE_INFO:
2259 case GAME_MODE_SCORES:
2261 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2268 if (game_status == GAME_MODE_TITLE)
2269 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2270 else if (game_status == GAME_MODE_MAIN)
2271 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2272 else if (game_status == GAME_MODE_LEVELS)
2273 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2274 else if (game_status == GAME_MODE_LEVELNR)
2275 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2276 else if (game_status == GAME_MODE_SETUP)
2277 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2278 else if (game_status == GAME_MODE_INFO)
2279 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2280 else if (game_status == GAME_MODE_SCORES)
2281 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2285 if (game_status != GAME_MODE_MAIN)
2286 FadeSkipNextFadeIn();
2288 if (game_status == GAME_MODE_TITLE)
2289 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2290 else if (game_status == GAME_MODE_LEVELS)
2291 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2292 else if (game_status == GAME_MODE_LEVELNR)
2293 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2294 else if (game_status == GAME_MODE_SETUP)
2295 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2296 else if (game_status == GAME_MODE_INFO)
2297 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2298 else if (game_status == GAME_MODE_SCORES)
2299 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2303 if (game_status == GAME_MODE_LEVELS)
2304 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2305 else if (game_status == GAME_MODE_LEVELNR)
2306 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2307 else if (game_status == GAME_MODE_SETUP)
2308 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2309 else if (game_status == GAME_MODE_INFO)
2310 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2311 else if (game_status == GAME_MODE_SCORES)
2312 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2315 case KSYM_Page_Down:
2316 if (game_status == GAME_MODE_LEVELS)
2317 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2318 else if (game_status == GAME_MODE_LEVELNR)
2319 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2320 else if (game_status == GAME_MODE_SETUP)
2321 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2322 else if (game_status == GAME_MODE_INFO)
2323 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2324 else if (game_status == GAME_MODE_SCORES)
2325 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2333 case GAME_MODE_EDITOR:
2334 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2335 HandleLevelEditorKeyInput(key);
2338 case GAME_MODE_PLAYING:
2343 RequestQuitGame(setup.ask_on_escape);
2353 if (key == KSYM_Escape)
2355 SetGameStatus(GAME_MODE_MAIN);
2364 void HandleNoEvent(void)
2366 HandleMouseCursor();
2368 switch (game_status)
2370 case GAME_MODE_PLAYING:
2371 HandleButtonOrFinger(-1, -1, -1);
2376 void HandleEventActions(void)
2378 // if (button_status && game_status != GAME_MODE_PLAYING)
2379 if (button_status && (game_status != GAME_MODE_PLAYING ||
2381 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2383 HandleButton(0, 0, button_status, -button_status);
2390 if (network.enabled)
2393 switch (game_status)
2395 case GAME_MODE_MAIN:
2396 DrawPreviewLevelAnimation();
2399 case GAME_MODE_EDITOR:
2400 HandleLevelEditorIdle();
2408 static void HandleTileCursor(int dx, int dy, int button)
2411 ClearPlayerMouseAction();
2418 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2419 (dx < 0 ? MB_LEFTBUTTON :
2420 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2422 else if (!tile_cursor.moving)
2424 int old_xpos = tile_cursor.xpos;
2425 int old_ypos = tile_cursor.ypos;
2426 int new_xpos = old_xpos;
2427 int new_ypos = old_ypos;
2429 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2430 new_xpos = old_xpos + dx;
2432 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2433 new_ypos = old_ypos + dy;
2435 SetTileCursorTargetXY(new_xpos, new_ypos);
2439 static int HandleJoystickForAllPlayers(void)
2443 boolean no_joysticks_configured = TRUE;
2444 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2445 static byte joy_action_last[MAX_PLAYERS];
2447 for (i = 0; i < MAX_PLAYERS; i++)
2448 if (setup.input[i].use_joystick)
2449 no_joysticks_configured = FALSE;
2451 // if no joysticks configured, map connected joysticks to players
2452 if (no_joysticks_configured)
2453 use_as_joystick_nr = TRUE;
2455 for (i = 0; i < MAX_PLAYERS; i++)
2457 byte joy_action = 0;
2459 joy_action = JoystickExt(i, use_as_joystick_nr);
2460 result |= joy_action;
2462 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2463 joy_action != joy_action_last[i])
2464 stored_player[i].action = joy_action;
2466 joy_action_last[i] = joy_action;
2472 void HandleJoystick(void)
2474 static unsigned int joytest_delay = 0;
2475 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2476 static int joytest_last = 0;
2477 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2478 int delay_value = GADGET_FRAME_DELAY;
2479 int joystick = HandleJoystickForAllPlayers();
2480 int keyboard = key_joystick_mapping;
2481 int joy = (joystick | keyboard);
2482 int joytest = joystick;
2483 int left = joy & JOY_LEFT;
2484 int right = joy & JOY_RIGHT;
2485 int up = joy & JOY_UP;
2486 int down = joy & JOY_DOWN;
2487 int button = joy & JOY_BUTTON;
2488 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2489 int dx = (left ? -1 : right ? 1 : 0);
2490 int dy = (up ? -1 : down ? 1 : 0);
2491 boolean use_delay_value_first = (joytest != joytest_last);
2493 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2495 // do not handle this button event anymore
2499 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2500 anyTextGadgetActive()))
2502 // leave name input in main menu or text input gadget
2503 HandleKey(KSYM_Escape, KEY_PRESSED);
2504 HandleKey(KSYM_Escape, KEY_RELEASED);
2509 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2511 if (game_status == GAME_MODE_PLAYING)
2513 // when playing MM style levels, also use delay for keyboard events
2514 joytest |= keyboard;
2516 // only use first delay value for new events, but not for changed events
2517 use_delay_value_first = (!joytest != !joytest_last);
2519 // only use delay after the initial keyboard event
2523 // for any joystick or keyboard event, enable playfield tile cursor
2524 if (dx || dy || button)
2525 SetTileCursorEnabled(TRUE);
2528 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2530 // delay joystick/keyboard actions if axes/keys continually pressed
2531 newbutton = dx = dy = 0;
2535 // first start with longer delay, then continue with shorter delay
2536 joytest_delay_value =
2537 (use_delay_value_first ? delay_value_first : delay_value);
2540 joytest_last = joytest;
2542 switch (game_status)
2544 case GAME_MODE_TITLE:
2545 case GAME_MODE_MAIN:
2546 case GAME_MODE_LEVELS:
2547 case GAME_MODE_LEVELNR:
2548 case GAME_MODE_SETUP:
2549 case GAME_MODE_INFO:
2550 case GAME_MODE_SCORES:
2552 if (anyTextGadgetActive())
2555 if (game_status == GAME_MODE_TITLE)
2556 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2557 else if (game_status == GAME_MODE_MAIN)
2558 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2559 else if (game_status == GAME_MODE_LEVELS)
2560 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2561 else if (game_status == GAME_MODE_LEVELNR)
2562 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2563 else if (game_status == GAME_MODE_SETUP)
2564 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2565 else if (game_status == GAME_MODE_INFO)
2566 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2567 else if (game_status == GAME_MODE_SCORES)
2568 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2573 case GAME_MODE_PLAYING:
2575 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2576 if (tape.playing || keyboard)
2577 newbutton = ((joy & JOY_BUTTON) != 0);
2580 if (newbutton && game.all_players_gone)
2587 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2589 if (joystick & JOY_ACTION)
2590 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2592 else if (tape.recording && tape.pausing && !tape.use_mouse)
2594 if (joystick & JOY_ACTION)
2595 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2598 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2599 HandleTileCursor(dx, dy, button);
2608 void HandleSpecialGameControllerButtons(Event *event)
2613 switch (event->type)
2615 case SDL_CONTROLLERBUTTONDOWN:
2616 key_status = KEY_PRESSED;
2619 case SDL_CONTROLLERBUTTONUP:
2620 key_status = KEY_RELEASED;
2627 switch (event->cbutton.button)
2629 case SDL_CONTROLLER_BUTTON_START:
2633 case SDL_CONTROLLER_BUTTON_BACK:
2641 HandleKey(key, key_status);
2644 void HandleSpecialGameControllerKeys(Key key, int key_status)
2646 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2647 int button = SDL_CONTROLLER_BUTTON_INVALID;
2649 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2650 if (key == KSYM_Rewind)
2651 button = SDL_CONTROLLER_BUTTON_A;
2652 else if (key == KSYM_FastForward || key == KSYM_Menu)
2653 button = SDL_CONTROLLER_BUTTON_B;
2655 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2659 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2660 SDL_CONTROLLERBUTTONUP);
2662 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2663 event.cbutton.button = button;
2664 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2667 HandleJoystickEvent(&event);
2672 boolean DoKeysymAction(int keysym)
2676 Key key = (Key)(-keysym);
2678 HandleKey(key, KEY_PRESSED);
2679 HandleKey(key, KEY_RELEASED);