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++)
407 stored_player[i].action = 0;
408 stored_player[i].snap_action = 0;
411 ClearJoystickState();
412 ClearPlayerMouseAction();
415 static void SetPlayerMouseAction(int mx, int my, int button)
417 int lx = getLevelFromScreenX(mx);
418 int ly = getLevelFromScreenY(my);
419 int new_button = (!local_player->mouse_action.button && button);
421 if (local_player->mouse_action.button_hint)
422 button = local_player->mouse_action.button_hint;
424 ClearPlayerMouseAction();
426 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
429 local_player->mouse_action.lx = lx;
430 local_player->mouse_action.ly = ly;
431 local_player->mouse_action.button = button;
433 if (tape.recording && tape.pausing && tape.use_mouse)
435 // un-pause a paused game only if mouse button was newly pressed down
437 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
440 SetTileCursorXY(lx, ly);
443 void SleepWhileUnmapped(void)
445 boolean window_unmapped = TRUE;
447 KeyboardAutoRepeatOn();
449 while (window_unmapped)
453 if (!WaitValidEvent(&event))
458 case EVENT_BUTTONRELEASE:
459 button_status = MB_RELEASED;
462 case EVENT_KEYRELEASE:
463 key_joystick_mapping = 0;
466 case SDL_CONTROLLERBUTTONUP:
467 HandleJoystickEvent(&event);
468 key_joystick_mapping = 0;
471 case EVENT_MAPNOTIFY:
472 window_unmapped = FALSE;
475 case EVENT_UNMAPNOTIFY:
476 // this is only to surely prevent the 'should not happen' case
477 // of recursively looping between 'SleepWhileUnmapped()' and
478 // 'HandleOtherEvents()' which usually calls this funtion.
482 HandleOtherEvents(&event);
487 if (game_status == GAME_MODE_PLAYING)
488 KeyboardAutoRepeatOffUnlessAutoplay();
491 void HandleExposeEvent(ExposeEvent *event)
495 void HandleButtonEvent(ButtonEvent *event)
497 #if DEBUG_EVENTS_BUTTON
498 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
500 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
504 // for any mouse button event, disable playfield tile cursor
505 SetTileCursorEnabled(FALSE);
507 #if defined(HAS_SCREEN_KEYBOARD)
508 if (video.shifted_up)
509 event->y += video.shifted_up_pos;
512 motion_status = FALSE;
514 if (event->type == EVENT_BUTTONPRESS)
515 button_status = event->button;
517 button_status = MB_RELEASED;
519 HandleButton(event->x, event->y, button_status, event->button);
522 void HandleMotionEvent(MotionEvent *event)
524 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
527 motion_status = TRUE;
529 #if DEBUG_EVENTS_MOTION
530 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
531 button_status, event->x, event->y);
534 HandleButton(event->x, event->y, button_status, button_status);
537 void HandleWheelEvent(WheelEvent *event)
541 #if DEBUG_EVENTS_WHEEL
543 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
544 event->which, event->x, event->y);
546 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
547 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
548 event->which, event->x, event->y,
549 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
550 "SDL_MOUSEWHEEL_FLIPPED"));
554 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
555 event->x > 0 ? MB_WHEEL_RIGHT :
556 event->y < 0 ? MB_WHEEL_DOWN :
557 event->y > 0 ? MB_WHEEL_UP : 0);
559 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
560 // accelerated mouse wheel available on Mac and Windows
561 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
563 // no accelerated mouse wheel available on Unix/Linux
564 wheel_steps = DEFAULT_WHEEL_STEPS;
567 motion_status = FALSE;
569 button_status = button_nr;
570 HandleButton(0, 0, button_status, -button_nr);
572 button_status = MB_RELEASED;
573 HandleButton(0, 0, button_status, -button_nr);
576 void HandleWindowEvent(WindowEvent *event)
578 #if DEBUG_EVENTS_WINDOW
579 int subtype = event->event;
582 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
583 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
584 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
585 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
586 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
587 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
588 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
589 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
590 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
591 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
592 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
593 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
594 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
595 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
598 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
599 event_name, event->data1, event->data2);
603 // (not needed, as the screen gets redrawn every 20 ms anyway)
604 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
605 event->event == SDL_WINDOWEVENT_RESIZED ||
606 event->event == SDL_WINDOWEVENT_EXPOSED)
610 if (event->event == SDL_WINDOWEVENT_RESIZED)
612 if (!video.fullscreen_enabled)
614 int new_window_width = event->data1;
615 int new_window_height = event->data2;
617 // if window size has changed after resizing, calculate new scaling factor
618 if (new_window_width != video.window_width ||
619 new_window_height != video.window_height)
621 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
622 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
624 // (extreme window scaling allowed, but cannot be saved permanently)
625 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
626 setup.window_scaling_percent =
627 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
628 MAX_WINDOW_SCALING_PERCENT);
630 video.window_width = new_window_width;
631 video.window_height = new_window_height;
633 if (game_status == GAME_MODE_SETUP)
634 RedrawSetupScreenAfterFullscreenToggle();
639 #if defined(PLATFORM_ANDROID)
642 int new_display_width = event->data1;
643 int new_display_height = event->data2;
645 // if fullscreen display size has changed, device has been rotated
646 if (new_display_width != video.display_width ||
647 new_display_height != video.display_height)
649 int nr = GRID_ACTIVE_NR(); // previous screen orientation
651 video.display_width = new_display_width;
652 video.display_height = new_display_height;
654 SDLSetScreenProperties();
656 // check if screen orientation has changed (should always be true here)
657 if (nr != GRID_ACTIVE_NR())
661 if (game_status == GAME_MODE_SETUP)
662 RedrawSetupScreenAfterScreenRotation(nr);
664 nr = GRID_ACTIVE_NR();
666 overlay.grid_xsize = setup.touch.grid_xsize[nr];
667 overlay.grid_ysize = setup.touch.grid_ysize[nr];
669 for (x = 0; x < MAX_GRID_XSIZE; x++)
670 for (y = 0; y < MAX_GRID_YSIZE; y++)
671 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
679 #define NUM_TOUCH_FINGERS 3
684 SDL_FingerID finger_id;
688 } touch_info[NUM_TOUCH_FINGERS];
690 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
693 int x = event->x * overlay.grid_xsize;
694 int y = event->y * overlay.grid_ysize;
695 int grid_button = overlay.grid_button[x][y];
696 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
697 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
698 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
699 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
700 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
701 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
702 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
705 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
706 float event_x = (event->x);
707 float event_y = (event->y - ypos) / (1 - ypos);
708 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
709 event_y > 2.0 / 3.0 && event_y < 1 ?
710 setup.input[0].key.snap :
711 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
712 event_y > 2.0 / 3.0 && event_y < 1 ?
713 setup.input[0].key.drop :
714 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
715 event_y > 0 && event_y < 1.0 / 3.0 ?
716 setup.input[0].key.up :
717 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
718 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
719 setup.input[0].key.left :
720 event_x > 8.0 / 9.0 && event_x < 1 &&
721 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
722 setup.input[0].key.right :
723 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
724 event_y > 2.0 / 3.0 && event_y < 1 ?
725 setup.input[0].key.down :
728 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
730 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
734 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
736 // for any touch input event, enable overlay buttons (if activated)
737 SetOverlayEnabled(TRUE);
739 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
740 getKeyNameFromKey(key), key_status_name, event->fingerId);
742 if (key_status == KEY_PRESSED)
743 overlay.grid_button_action |= grid_button_action;
745 overlay.grid_button_action &= ~grid_button_action;
747 // check if we already know this touch event's finger id
748 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
750 if (touch_info[i].touched &&
751 touch_info[i].finger_id == event->fingerId)
753 // Error(ERR_DEBUG, "MARK 1: %d", i);
759 if (i >= NUM_TOUCH_FINGERS)
761 if (key_status == KEY_PRESSED)
763 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
765 // unknown finger id -- get new, empty slot, if available
766 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
768 if (touch_info[i].counter < oldest_counter)
771 oldest_counter = touch_info[i].counter;
773 // Error(ERR_DEBUG, "MARK 2: %d", i);
776 if (!touch_info[i].touched)
778 // Error(ERR_DEBUG, "MARK 3: %d", i);
784 if (i >= NUM_TOUCH_FINGERS)
786 // all slots allocated -- use oldest slot
789 // Error(ERR_DEBUG, "MARK 4: %d", i);
794 // release of previously unknown key (should not happen)
796 if (key != KSYM_UNDEFINED)
798 HandleKey(key, KEY_RELEASED);
800 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
801 getKeyNameFromKey(key), "KEY_RELEASED", i);
806 if (i < NUM_TOUCH_FINGERS)
808 if (key_status == KEY_PRESSED)
810 if (touch_info[i].key != key)
812 if (touch_info[i].key != KSYM_UNDEFINED)
814 HandleKey(touch_info[i].key, KEY_RELEASED);
816 // undraw previous grid button when moving finger away
817 overlay.grid_button_action &= ~touch_info[i].action;
819 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
820 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
823 if (key != KSYM_UNDEFINED)
825 HandleKey(key, KEY_PRESSED);
827 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
828 getKeyNameFromKey(key), "KEY_PRESSED", i);
832 touch_info[i].touched = TRUE;
833 touch_info[i].finger_id = event->fingerId;
834 touch_info[i].counter = Counter();
835 touch_info[i].key = key;
836 touch_info[i].action = grid_button_action;
840 if (touch_info[i].key != KSYM_UNDEFINED)
842 HandleKey(touch_info[i].key, KEY_RELEASED);
844 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
845 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
848 touch_info[i].touched = FALSE;
849 touch_info[i].finger_id = 0;
850 touch_info[i].counter = 0;
851 touch_info[i].key = 0;
852 touch_info[i].action = JOY_NO_ACTION;
857 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
859 static Key motion_key_x = KSYM_UNDEFINED;
860 static Key motion_key_y = KSYM_UNDEFINED;
861 static Key button_key = KSYM_UNDEFINED;
862 static float motion_x1, motion_y1;
863 static float button_x1, button_y1;
864 static SDL_FingerID motion_id = -1;
865 static SDL_FingerID button_id = -1;
866 int move_trigger_distance_percent = setup.touch.move_distance;
867 int drop_trigger_distance_percent = setup.touch.drop_distance;
868 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
869 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
870 float event_x = event->x;
871 float event_y = event->y;
873 if (event->type == EVENT_FINGERPRESS)
875 if (event_x > 1.0 / 3.0)
879 motion_id = event->fingerId;
884 motion_key_x = KSYM_UNDEFINED;
885 motion_key_y = KSYM_UNDEFINED;
887 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
893 button_id = event->fingerId;
898 button_key = setup.input[0].key.snap;
900 HandleKey(button_key, KEY_PRESSED);
902 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
905 else if (event->type == EVENT_FINGERRELEASE)
907 if (event->fingerId == motion_id)
911 if (motion_key_x != KSYM_UNDEFINED)
912 HandleKey(motion_key_x, KEY_RELEASED);
913 if (motion_key_y != KSYM_UNDEFINED)
914 HandleKey(motion_key_y, KEY_RELEASED);
916 motion_key_x = KSYM_UNDEFINED;
917 motion_key_y = KSYM_UNDEFINED;
919 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
921 else if (event->fingerId == button_id)
925 if (button_key != KSYM_UNDEFINED)
926 HandleKey(button_key, KEY_RELEASED);
928 button_key = KSYM_UNDEFINED;
930 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
933 else if (event->type == EVENT_FINGERMOTION)
935 if (event->fingerId == motion_id)
937 float distance_x = ABS(event_x - motion_x1);
938 float distance_y = ABS(event_y - motion_y1);
939 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
940 event_x > motion_x1 ? setup.input[0].key.right :
942 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
943 event_y > motion_y1 ? setup.input[0].key.down :
946 if (distance_x < move_trigger_distance / 2 ||
947 distance_x < distance_y)
948 new_motion_key_x = KSYM_UNDEFINED;
950 if (distance_y < move_trigger_distance / 2 ||
951 distance_y < distance_x)
952 new_motion_key_y = KSYM_UNDEFINED;
954 if (distance_x > move_trigger_distance ||
955 distance_y > move_trigger_distance)
957 if (new_motion_key_x != motion_key_x)
959 if (motion_key_x != KSYM_UNDEFINED)
960 HandleKey(motion_key_x, KEY_RELEASED);
961 if (new_motion_key_x != KSYM_UNDEFINED)
962 HandleKey(new_motion_key_x, KEY_PRESSED);
965 if (new_motion_key_y != motion_key_y)
967 if (motion_key_y != KSYM_UNDEFINED)
968 HandleKey(motion_key_y, KEY_RELEASED);
969 if (new_motion_key_y != KSYM_UNDEFINED)
970 HandleKey(new_motion_key_y, KEY_PRESSED);
976 motion_key_x = new_motion_key_x;
977 motion_key_y = new_motion_key_y;
979 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
982 else if (event->fingerId == button_id)
984 float distance_x = ABS(event_x - button_x1);
985 float distance_y = ABS(event_y - button_y1);
987 if (distance_x < drop_trigger_distance / 2 &&
988 distance_y > drop_trigger_distance)
990 if (button_key == setup.input[0].key.snap)
991 HandleKey(button_key, KEY_RELEASED);
996 button_key = setup.input[0].key.drop;
998 HandleKey(button_key, KEY_PRESSED);
1000 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1006 void HandleFingerEvent(FingerEvent *event)
1008 #if DEBUG_EVENTS_FINGER
1009 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1010 event->type == EVENT_FINGERPRESS ? "pressed" :
1011 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1015 event->dx, event->dy,
1019 runtime.uses_touch_device = TRUE;
1021 if (game_status != GAME_MODE_PLAYING)
1024 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1026 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1027 local_player->mouse_action.button_hint =
1028 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1029 event->x < 0.5 ? MB_LEFTBUTTON :
1030 event->x > 0.5 ? MB_RIGHTBUTTON :
1036 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1037 HandleFingerEvent_VirtualButtons(event);
1038 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1039 HandleFingerEvent_WipeGestures(event);
1042 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1044 static int old_mx = 0, old_my = 0;
1045 static int last_button = MB_LEFTBUTTON;
1046 static boolean touched = FALSE;
1047 static boolean tapped = FALSE;
1049 // screen tile was tapped (but finger not touching the screen anymore)
1050 // (this point will also be reached without receiving a touch event)
1051 if (tapped && !touched)
1053 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1058 // stop here if this function was not triggered by a touch event
1062 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1064 // finger started touching the screen
1074 ClearPlayerMouseAction();
1076 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1079 else if (button == MB_RELEASED && touched)
1081 // finger stopped touching the screen
1086 SetPlayerMouseAction(old_mx, old_my, last_button);
1088 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1090 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1095 // finger moved while touching the screen
1097 int old_x = getLevelFromScreenX(old_mx);
1098 int old_y = getLevelFromScreenY(old_my);
1099 int new_x = getLevelFromScreenX(mx);
1100 int new_y = getLevelFromScreenY(my);
1102 if (new_x != old_x || new_y != old_y)
1107 // finger moved left or right from (horizontal) starting position
1109 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1111 SetPlayerMouseAction(old_mx, old_my, button_nr);
1113 last_button = button_nr;
1115 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1119 // finger stays at or returned to (horizontal) starting position
1121 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1123 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1128 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1130 static int old_mx = 0, old_my = 0;
1131 static int last_button = MB_LEFTBUTTON;
1132 static boolean touched = FALSE;
1133 static boolean tapped = FALSE;
1135 // screen tile was tapped (but finger not touching the screen anymore)
1136 // (this point will also be reached without receiving a touch event)
1137 if (tapped && !touched)
1139 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1144 // stop here if this function was not triggered by a touch event
1148 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1150 // finger started touching the screen
1160 ClearPlayerMouseAction();
1162 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1165 else if (button == MB_RELEASED && touched)
1167 // finger stopped touching the screen
1172 SetPlayerMouseAction(old_mx, old_my, last_button);
1174 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1176 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1181 // finger moved while touching the screen
1183 int old_x = getLevelFromScreenX(old_mx);
1184 int old_y = getLevelFromScreenY(old_my);
1185 int new_x = getLevelFromScreenX(mx);
1186 int new_y = getLevelFromScreenY(my);
1188 if (new_x != old_x || new_y != old_y)
1190 // finger moved away from starting position
1192 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1194 // quickly alternate between clicking and releasing for maximum speed
1195 if (FrameCounter % 2 == 0)
1196 button_nr = MB_RELEASED;
1198 SetPlayerMouseAction(old_mx, old_my, button_nr);
1201 last_button = button_nr;
1205 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1209 // finger stays at or returned to starting position
1211 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1213 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1218 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1220 static int old_mx = 0, old_my = 0;
1221 static Key motion_key_x = KSYM_UNDEFINED;
1222 static Key motion_key_y = KSYM_UNDEFINED;
1223 static boolean touched = FALSE;
1224 static boolean started_on_player = FALSE;
1225 static boolean player_is_dropping = FALSE;
1226 static int player_drop_count = 0;
1227 static int last_player_x = -1;
1228 static int last_player_y = -1;
1230 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1239 started_on_player = FALSE;
1240 player_is_dropping = FALSE;
1241 player_drop_count = 0;
1245 motion_key_x = KSYM_UNDEFINED;
1246 motion_key_y = KSYM_UNDEFINED;
1248 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1251 else if (button == MB_RELEASED && touched)
1258 if (motion_key_x != KSYM_UNDEFINED)
1259 HandleKey(motion_key_x, KEY_RELEASED);
1260 if (motion_key_y != KSYM_UNDEFINED)
1261 HandleKey(motion_key_y, KEY_RELEASED);
1263 if (started_on_player)
1265 if (player_is_dropping)
1267 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1269 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1273 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1275 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1279 motion_key_x = KSYM_UNDEFINED;
1280 motion_key_y = KSYM_UNDEFINED;
1282 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1287 int src_x = local_player->jx;
1288 int src_y = local_player->jy;
1289 int dst_x = getLevelFromScreenX(old_mx);
1290 int dst_y = getLevelFromScreenY(old_my);
1291 int dx = dst_x - src_x;
1292 int dy = dst_y - src_y;
1293 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1294 dx > 0 ? setup.input[0].key.right :
1296 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1297 dy > 0 ? setup.input[0].key.down :
1300 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1301 (last_player_x != local_player->jx ||
1302 last_player_y != local_player->jy))
1304 // in case of asymmetric diagonal movement, use "preferred" direction
1306 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1308 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1309 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1311 local_player->last_move_dir = last_move_dir;
1313 // (required to prevent accidentally forcing direction for next movement)
1314 last_player_x = local_player->jx;
1315 last_player_y = local_player->jy;
1318 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1320 started_on_player = TRUE;
1321 player_drop_count = getPlayerInventorySize(0);
1322 player_is_dropping = (player_drop_count > 0);
1324 if (player_is_dropping)
1326 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1328 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1332 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1334 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1337 else if (dx != 0 || dy != 0)
1339 if (player_is_dropping &&
1340 player_drop_count == getPlayerInventorySize(0))
1342 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1344 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1345 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1347 player_is_dropping = FALSE;
1351 if (new_motion_key_x != motion_key_x)
1353 Error(ERR_DEBUG, "---------- %s %s ----------",
1354 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1355 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1357 if (motion_key_x != KSYM_UNDEFINED)
1358 HandleKey(motion_key_x, KEY_RELEASED);
1359 if (new_motion_key_x != KSYM_UNDEFINED)
1360 HandleKey(new_motion_key_x, KEY_PRESSED);
1363 if (new_motion_key_y != motion_key_y)
1365 Error(ERR_DEBUG, "---------- %s %s ----------",
1366 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1367 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1369 if (motion_key_y != KSYM_UNDEFINED)
1370 HandleKey(motion_key_y, KEY_RELEASED);
1371 if (new_motion_key_y != KSYM_UNDEFINED)
1372 HandleKey(new_motion_key_y, KEY_PRESSED);
1375 motion_key_x = new_motion_key_x;
1376 motion_key_y = new_motion_key_y;
1380 static void HandleButtonOrFinger(int mx, int my, int button)
1382 if (game_status != GAME_MODE_PLAYING)
1385 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1387 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1388 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1389 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1390 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1391 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1392 SetPlayerMouseAction(mx, my, button); // special case
1396 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1397 HandleButtonOrFinger_FollowFinger(mx, my, button);
1401 static boolean checkTextInputKeyModState(void)
1403 // when playing, only handle raw key events and ignore text input
1404 if (game_status == GAME_MODE_PLAYING)
1407 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1410 void HandleTextEvent(TextEvent *event)
1412 char *text = event->text;
1413 Key key = getKeyFromKeyName(text);
1415 #if DEBUG_EVENTS_TEXT
1416 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1419 text[0], (int)(text[0]),
1421 getKeyNameFromKey(key),
1425 #if !defined(HAS_SCREEN_KEYBOARD)
1426 // non-mobile devices: only handle key input with modifier keys pressed here
1427 // (every other key input is handled directly as physical key input event)
1428 if (!checkTextInputKeyModState())
1432 // process text input as "classic" (with uppercase etc.) key input event
1433 HandleKey(key, KEY_PRESSED);
1434 HandleKey(key, KEY_RELEASED);
1437 void HandlePauseResumeEvent(PauseResumeEvent *event)
1439 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1443 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1449 void HandleKeyEvent(KeyEvent *event)
1451 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1452 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1453 Key key = GetEventKey(event, with_modifiers);
1454 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1456 #if DEBUG_EVENTS_KEY
1457 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1458 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1459 event->keysym.scancode,
1464 getKeyNameFromKey(key));
1467 #if defined(PLATFORM_ANDROID)
1468 if (key == KSYM_Back)
1470 // always map the "back" button to the "escape" key on Android devices
1473 else if (key == KSYM_Menu)
1475 // the "menu" button can be used to toggle displaying virtual buttons
1476 if (key_status == KEY_PRESSED)
1477 SetOverlayEnabled(!GetOverlayEnabled());
1481 // for any other "real" key event, disable virtual buttons
1482 SetOverlayEnabled(FALSE);
1486 HandleKeyModState(keymod, key_status);
1488 // only handle raw key input without text modifier keys pressed
1489 if (!checkTextInputKeyModState())
1490 HandleKey(key, key_status);
1493 void HandleFocusEvent(FocusChangeEvent *event)
1495 static int old_joystick_status = -1;
1497 if (event->type == EVENT_FOCUSOUT)
1499 KeyboardAutoRepeatOn();
1500 old_joystick_status = joystick.status;
1501 joystick.status = JOYSTICK_NOT_AVAILABLE;
1503 ClearPlayerAction();
1505 else if (event->type == EVENT_FOCUSIN)
1507 /* When there are two Rocks'n'Diamonds windows which overlap and
1508 the player moves the pointer from one game window to the other,
1509 a 'FocusOut' event is generated for the window the pointer is
1510 leaving and a 'FocusIn' event is generated for the window the
1511 pointer is entering. In some cases, it can happen that the
1512 'FocusIn' event is handled by the one game process before the
1513 'FocusOut' event by the other game process. In this case the
1514 X11 environment would end up with activated keyboard auto repeat,
1515 because unfortunately this is a global setting and not (which
1516 would be far better) set for each X11 window individually.
1517 The effect would be keyboard auto repeat while playing the game
1518 (game_status == GAME_MODE_PLAYING), which is not desired.
1519 To avoid this special case, we just wait 1/10 second before
1520 processing the 'FocusIn' event. */
1522 if (game_status == GAME_MODE_PLAYING)
1525 KeyboardAutoRepeatOffUnlessAutoplay();
1528 if (old_joystick_status != -1)
1529 joystick.status = old_joystick_status;
1533 void HandleClientMessageEvent(ClientMessageEvent *event)
1535 if (CheckCloseWindowEvent(event))
1539 static int HandleDropFileEvent(char *filename)
1541 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1543 // check and extract dropped zip files into correct user data directory
1544 if (!strSuffixLower(filename, ".zip"))
1546 Error(ERR_WARN, "file '%s' not supported", filename);
1548 return TREE_TYPE_UNDEFINED;
1551 TreeInfo *tree_node = NULL;
1552 int tree_type = GetZipFileTreeType(filename);
1553 char *directory = TREE_USERDIR(tree_type);
1555 if (directory == NULL)
1557 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1559 return TREE_TYPE_UNDEFINED;
1562 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1563 game_status == GAME_MODE_LEVELS &&
1564 leveldir_current->node_parent != NULL)
1566 // extract new level set next to currently selected level set
1567 tree_node = leveldir_current;
1569 // get parent directory of currently selected level set directory
1570 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1572 // use private level directory instead of top-level package level directory
1573 if (strPrefix(directory, options.level_directory) &&
1574 strEqual(leveldir_current->node_parent->fullpath, "."))
1575 directory = getUserLevelDir(NULL);
1578 // extract level or artwork set from zip file to target directory
1579 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1581 if (top_dir == NULL)
1583 // error message already issued by "ExtractZipFileIntoDirectory()"
1585 return TREE_TYPE_UNDEFINED;
1588 // add extracted level or artwork set to tree info structure
1589 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1591 // update menu screen (and possibly change current level set)
1592 DrawScreenAfterAddingSet(top_dir, tree_type);
1597 static void HandleDropTextEvent(char *text)
1599 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1602 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1603 int num_artwork_sets_succeeded,
1604 int num_files_failed)
1606 // only show request dialog if no other request dialog already active
1607 if (game.request_active)
1610 // this case can happen with drag-and-drop with older SDL versions
1611 if (num_level_sets_succeeded == 0 &&
1612 num_artwork_sets_succeeded == 0 &&
1613 num_files_failed == 0)
1618 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1620 char message_part1[50];
1622 sprintf(message_part1, "New %s set%s added",
1623 (num_artwork_sets_succeeded == 0 ? "level" :
1624 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1625 (num_level_sets_succeeded +
1626 num_artwork_sets_succeeded > 1 ? "s" : ""));
1628 if (num_files_failed > 0)
1629 sprintf(message, "%s, but %d dropped file%s failed!",
1630 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1632 sprintf(message, "%s!", message_part1);
1634 else if (num_files_failed > 0)
1636 sprintf(message, "Failed to process dropped file%s!",
1637 num_files_failed > 1 ? "s" : "");
1640 Request(message, REQ_CONFIRM);
1643 void HandleDropEvent(Event *event)
1645 static boolean confirm_on_drop_complete = FALSE;
1646 static int num_level_sets_succeeded = 0;
1647 static int num_artwork_sets_succeeded = 0;
1648 static int num_files_failed = 0;
1650 switch (event->type)
1654 confirm_on_drop_complete = TRUE;
1655 num_level_sets_succeeded = 0;
1656 num_artwork_sets_succeeded = 0;
1657 num_files_failed = 0;
1664 int tree_type = HandleDropFileEvent(event->drop.file);
1666 if (tree_type == TREE_TYPE_LEVEL_DIR)
1667 num_level_sets_succeeded++;
1668 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1669 tree_type == TREE_TYPE_SOUNDS_DIR ||
1670 tree_type == TREE_TYPE_MUSIC_DIR)
1671 num_artwork_sets_succeeded++;
1675 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1676 if (!confirm_on_drop_complete)
1678 // process all remaining events, including further SDL_DROPFILE events
1681 HandleDropCompleteEvent(num_level_sets_succeeded,
1682 num_artwork_sets_succeeded,
1685 num_level_sets_succeeded = 0;
1686 num_artwork_sets_succeeded = 0;
1687 num_files_failed = 0;
1695 HandleDropTextEvent(event->drop.file);
1700 case SDL_DROPCOMPLETE:
1702 HandleDropCompleteEvent(num_level_sets_succeeded,
1703 num_artwork_sets_succeeded,
1710 if (event->drop.file != NULL)
1711 SDL_free(event->drop.file);
1714 void HandleButton(int mx, int my, int button, int button_nr)
1716 static int old_mx = 0, old_my = 0;
1717 boolean button_hold = FALSE;
1718 boolean handle_gadgets = TRUE;
1724 button_nr = -button_nr;
1733 #if defined(PLATFORM_ANDROID)
1734 // when playing, only handle gadgets when using "follow finger" controls
1735 // or when using touch controls in combination with the MM game engine
1736 // or when using gadgets that do not overlap with virtual buttons
1738 (game_status != GAME_MODE_PLAYING ||
1739 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1740 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1741 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1742 !virtual_button_pressed));
1745 if (HandleGlobalAnimClicks(mx, my, button))
1747 // do not handle this button event anymore
1748 return; // force mouse event not to be handled at all
1751 if (handle_gadgets && HandleGadgets(mx, my, button))
1753 // do not handle this button event anymore
1754 mx = my = -32; // force mouse event to be outside screen tiles
1757 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1760 // do not use scroll wheel button events for anything other than gadgets
1761 if (IS_WHEEL_BUTTON(button_nr))
1764 switch (game_status)
1766 case GAME_MODE_TITLE:
1767 HandleTitleScreen(mx, my, 0, 0, button);
1770 case GAME_MODE_MAIN:
1771 HandleMainMenu(mx, my, 0, 0, button);
1774 case GAME_MODE_PSEUDO_TYPENAME:
1775 HandleTypeName(0, KSYM_Return);
1778 case GAME_MODE_LEVELS:
1779 HandleChooseLevelSet(mx, my, 0, 0, button);
1782 case GAME_MODE_LEVELNR:
1783 HandleChooseLevelNr(mx, my, 0, 0, button);
1786 case GAME_MODE_SCORES:
1787 HandleHallOfFame(0, 0, 0, 0, button);
1790 case GAME_MODE_EDITOR:
1791 HandleLevelEditorIdle();
1794 case GAME_MODE_INFO:
1795 HandleInfoScreen(mx, my, 0, 0, button);
1798 case GAME_MODE_SETUP:
1799 HandleSetupScreen(mx, my, 0, 0, button);
1802 case GAME_MODE_PLAYING:
1803 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1804 HandleButtonOrFinger(mx, my, button);
1806 SetPlayerMouseAction(mx, my, button);
1809 if (button == MB_PRESSED && !motion_status && !button_hold &&
1810 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1811 DumpTileFromScreen(mx, my);
1821 static boolean is_string_suffix(char *string, char *suffix)
1823 int string_len = strlen(string);
1824 int suffix_len = strlen(suffix);
1826 if (suffix_len > string_len)
1829 return (strEqual(&string[string_len - suffix_len], suffix));
1832 #define MAX_CHEAT_INPUT_LEN 32
1834 static void HandleKeysSpecial(Key key)
1836 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1837 char letter = getCharFromKey(key);
1838 int cheat_input_len = strlen(cheat_input);
1844 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1846 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1847 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1849 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1852 cheat_input[cheat_input_len++] = letter;
1853 cheat_input[cheat_input_len] = '\0';
1855 #if DEBUG_EVENTS_KEY
1856 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1859 if (game_status == GAME_MODE_MAIN)
1861 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1862 is_string_suffix(cheat_input, ":ist"))
1864 InsertSolutionTape();
1866 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1867 is_string_suffix(cheat_input, ":pst"))
1871 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1872 is_string_suffix(cheat_input, ":rg"))
1874 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1877 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1878 is_string_suffix(cheat_input, ":rs"))
1880 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1883 else if (is_string_suffix(cheat_input, ":reload-music") ||
1884 is_string_suffix(cheat_input, ":rm"))
1886 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1889 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1890 is_string_suffix(cheat_input, ":ra"))
1892 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1893 1 << ARTWORK_TYPE_SOUNDS |
1894 1 << ARTWORK_TYPE_MUSIC);
1897 else if (is_string_suffix(cheat_input, ":dump-level") ||
1898 is_string_suffix(cheat_input, ":dl"))
1902 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1903 is_string_suffix(cheat_input, ":dt"))
1907 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1908 is_string_suffix(cheat_input, ":ft"))
1910 /* fix single-player tapes that contain player input for more than one
1911 player (due to a bug in 3.3.1.2 and earlier versions), which results
1912 in playing levels with more than one player in multi-player mode,
1913 even though the tape was originally recorded in single-player mode */
1915 // remove player input actions for all players but the first one
1916 for (i = 1; i < MAX_PLAYERS; i++)
1917 tape.player_participates[i] = FALSE;
1919 tape.changed = TRUE;
1921 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1922 is_string_suffix(cheat_input, ":snl"))
1924 SaveNativeLevel(&level);
1926 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1927 is_string_suffix(cheat_input, ":fps"))
1929 global.show_frames_per_second = !global.show_frames_per_second;
1932 else if (game_status == GAME_MODE_PLAYING)
1935 if (is_string_suffix(cheat_input, ".q"))
1936 DEBUG_SetMaximumDynamite();
1939 else if (game_status == GAME_MODE_EDITOR)
1941 if (is_string_suffix(cheat_input, ":dump-brush") ||
1942 is_string_suffix(cheat_input, ":DB"))
1946 else if (is_string_suffix(cheat_input, ":DDB"))
1951 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1953 if (letter == 'x') // copy brush to clipboard (small size)
1955 CopyBrushToClipboard_Small();
1957 else if (letter == 'c') // copy brush to clipboard (normal size)
1959 CopyBrushToClipboard();
1961 else if (letter == 'v') // paste brush from Clipboard
1963 CopyClipboardToBrush();
1968 // special key shortcuts for all game modes
1969 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1970 is_string_suffix(cheat_input, ":dea") ||
1971 is_string_suffix(cheat_input, ":DEA"))
1973 DumpGadgetIdentifiers();
1974 DumpScreenIdentifiers();
1978 boolean HandleKeysDebug(Key key, int key_status)
1983 if (key_status != KEY_PRESSED)
1986 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1988 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1990 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1992 if (key == setup.debug.frame_delay_key[i] &&
1993 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1995 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1996 setup.debug.frame_delay[i] : setup.game_frame_delay);
1998 if (!setup.debug.frame_delay_game_only)
1999 MenuFrameDelay = GameFrameDelay;
2001 SetVideoFrameDelay(GameFrameDelay);
2003 if (GameFrameDelay > ONE_SECOND_DELAY)
2004 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
2005 else if (GameFrameDelay != 0)
2006 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
2007 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2008 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2010 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
2017 if (game_status == GAME_MODE_PLAYING)
2021 options.debug = !options.debug;
2023 Error(ERR_INFO, "debug mode %s",
2024 (options.debug ? "enabled" : "disabled"));
2028 else if (key == KSYM_v)
2030 Error(ERR_INFO, "currently using game engine version %d",
2031 game.engine_version);
2041 void HandleKey(Key key, int key_status)
2043 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2044 static boolean ignore_repeated_key = FALSE;
2045 static struct SetupKeyboardInfo ski;
2046 static struct SetupShortcutInfo ssi;
2055 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2056 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2057 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2058 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2059 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2060 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2065 if (HandleKeysDebug(key, key_status))
2066 return; // do not handle already processed keys again
2068 // map special keys (media keys / remote control buttons) to default keys
2069 if (key == KSYM_PlayPause)
2071 else if (key == KSYM_Select)
2074 HandleSpecialGameControllerKeys(key, key_status);
2076 if (game_status == GAME_MODE_PLAYING)
2078 // only needed for single-step tape recording mode
2079 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2082 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2084 byte key_action = 0;
2085 byte key_snap_action = 0;
2087 if (setup.input[pnr].use_joystick)
2090 ski = setup.input[pnr].key;
2092 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2093 if (key == *key_info[i].key_custom)
2094 key_action |= key_info[i].action;
2096 // use combined snap+direction keys for the first player only
2099 ssi = setup.shortcut;
2101 // also remember normal snap key when handling snap+direction keys
2102 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2104 for (i = 0; i < NUM_DIRECTIONS; i++)
2106 if (key == *key_info[i].key_snap)
2108 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2109 key_snap_action |= key_info[i].action;
2114 if (key_status == KEY_PRESSED)
2116 stored_player[pnr].action |= key_action;
2117 stored_player[pnr].snap_action |= key_snap_action;
2121 stored_player[pnr].action &= ~key_action;
2122 stored_player[pnr].snap_action &= ~key_snap_action;
2125 // restore snap action if one of several pressed snap keys was released
2126 if (stored_player[pnr].snap_action)
2127 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2129 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2131 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2133 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2135 // if snap key already pressed, keep pause mode when releasing
2136 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2137 has_snapped[pnr] = TRUE;
2139 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2141 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2143 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2144 getRedDiskReleaseFlag_SP() == 0)
2146 // add a single inactive frame before dropping starts
2147 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2148 stored_player[pnr].force_dropping = TRUE;
2151 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2153 // if snap key was pressed without direction, leave pause mode
2154 if (!has_snapped[pnr])
2155 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2157 has_snapped[pnr] = FALSE;
2160 else if (tape.recording && tape.pausing && !tape.use_mouse)
2162 // prevent key release events from un-pausing a paused game
2163 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2164 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2167 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2168 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2174 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2175 if (key == key_info[i].key_default)
2176 joy |= key_info[i].action;
2181 if (key_status == KEY_PRESSED)
2182 key_joystick_mapping |= joy;
2184 key_joystick_mapping &= ~joy;
2189 if (game_status != GAME_MODE_PLAYING)
2190 key_joystick_mapping = 0;
2192 if (key_status == KEY_RELEASED)
2194 // reset flag to ignore repeated "key pressed" events after key release
2195 ignore_repeated_key = FALSE;
2200 if ((key == KSYM_F11 ||
2201 ((key == KSYM_Return ||
2202 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2203 video.fullscreen_available &&
2204 !ignore_repeated_key)
2206 setup.fullscreen = !setup.fullscreen;
2208 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2210 if (game_status == GAME_MODE_SETUP)
2211 RedrawSetupScreenAfterFullscreenToggle();
2213 // set flag to ignore repeated "key pressed" events
2214 ignore_repeated_key = TRUE;
2219 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2220 key == KSYM_minus || key == KSYM_KP_Subtract ||
2221 key == KSYM_plus || key == KSYM_KP_Add ||
2222 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2223 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2224 video.window_scaling_available &&
2225 !video.fullscreen_enabled)
2227 if (key == KSYM_0 || key == KSYM_KP_0)
2228 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2229 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2230 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2232 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2234 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2235 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2236 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2237 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2239 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2241 if (game_status == GAME_MODE_SETUP)
2242 RedrawSetupScreenAfterFullscreenToggle();
2247 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2248 key == KSYM_Return ||
2249 key == KSYM_Escape)))
2251 // do not handle this key event anymore
2252 if (key != KSYM_Escape) // always allow ESC key to be handled
2256 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2257 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2264 if (game_status == GAME_MODE_MAIN &&
2265 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2267 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2272 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2274 if (key == setup.shortcut.save_game)
2276 else if (key == setup.shortcut.load_game)
2278 else if (key == setup.shortcut.toggle_pause)
2279 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2281 HandleTapeButtonKeys(key);
2282 HandleSoundButtonKeys(key);
2285 if (game_status == GAME_MODE_PLAYING && !network_playing)
2287 int centered_player_nr_next = -999;
2289 if (key == setup.shortcut.focus_player_all)
2290 centered_player_nr_next = -1;
2292 for (i = 0; i < MAX_PLAYERS; i++)
2293 if (key == setup.shortcut.focus_player[i])
2294 centered_player_nr_next = i;
2296 if (centered_player_nr_next != -999)
2298 game.centered_player_nr_next = centered_player_nr_next;
2299 game.set_centered_player = TRUE;
2303 tape.centered_player_nr_next = game.centered_player_nr_next;
2304 tape.set_centered_player = TRUE;
2309 HandleKeysSpecial(key);
2311 if (HandleGadgetsKeyInput(key))
2312 return; // do not handle already processed keys again
2314 switch (game_status)
2316 case GAME_MODE_PSEUDO_TYPENAME:
2317 HandleTypeName(0, key);
2320 case GAME_MODE_TITLE:
2321 case GAME_MODE_MAIN:
2322 case GAME_MODE_LEVELS:
2323 case GAME_MODE_LEVELNR:
2324 case GAME_MODE_SETUP:
2325 case GAME_MODE_INFO:
2326 case GAME_MODE_SCORES:
2328 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2335 if (game_status == GAME_MODE_TITLE)
2336 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2337 else if (game_status == GAME_MODE_MAIN)
2338 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2339 else if (game_status == GAME_MODE_LEVELS)
2340 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2341 else if (game_status == GAME_MODE_LEVELNR)
2342 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2343 else if (game_status == GAME_MODE_SETUP)
2344 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2345 else if (game_status == GAME_MODE_INFO)
2346 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2347 else if (game_status == GAME_MODE_SCORES)
2348 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2352 if (game_status != GAME_MODE_MAIN)
2353 FadeSkipNextFadeIn();
2355 if (game_status == GAME_MODE_TITLE)
2356 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2357 else if (game_status == GAME_MODE_LEVELS)
2358 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2359 else if (game_status == GAME_MODE_LEVELNR)
2360 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2361 else if (game_status == GAME_MODE_SETUP)
2362 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2363 else if (game_status == GAME_MODE_INFO)
2364 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2365 else if (game_status == GAME_MODE_SCORES)
2366 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2370 if (game_status == GAME_MODE_LEVELS)
2371 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2372 else if (game_status == GAME_MODE_LEVELNR)
2373 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2374 else if (game_status == GAME_MODE_SETUP)
2375 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2376 else if (game_status == GAME_MODE_INFO)
2377 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2378 else if (game_status == GAME_MODE_SCORES)
2379 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2382 case KSYM_Page_Down:
2383 if (game_status == GAME_MODE_LEVELS)
2384 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2385 else if (game_status == GAME_MODE_LEVELNR)
2386 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2387 else if (game_status == GAME_MODE_SETUP)
2388 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2389 else if (game_status == GAME_MODE_INFO)
2390 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2391 else if (game_status == GAME_MODE_SCORES)
2392 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2400 case GAME_MODE_EDITOR:
2401 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2402 HandleLevelEditorKeyInput(key);
2405 case GAME_MODE_PLAYING:
2410 RequestQuitGame(setup.ask_on_escape);
2420 if (key == KSYM_Escape)
2422 SetGameStatus(GAME_MODE_MAIN);
2431 void HandleNoEvent(void)
2433 HandleMouseCursor();
2435 switch (game_status)
2437 case GAME_MODE_PLAYING:
2438 HandleButtonOrFinger(-1, -1, -1);
2443 void HandleEventActions(void)
2445 // if (button_status && game_status != GAME_MODE_PLAYING)
2446 if (button_status && (game_status != GAME_MODE_PLAYING ||
2448 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2450 HandleButton(0, 0, button_status, -button_status);
2457 if (network.enabled)
2460 switch (game_status)
2462 case GAME_MODE_MAIN:
2463 DrawPreviewLevelAnimation();
2466 case GAME_MODE_EDITOR:
2467 HandleLevelEditorIdle();
2475 static void HandleTileCursor(int dx, int dy, int button)
2478 ClearPlayerMouseAction();
2485 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2486 (dx < 0 ? MB_LEFTBUTTON :
2487 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2489 else if (!tile_cursor.moving)
2491 int old_xpos = tile_cursor.xpos;
2492 int old_ypos = tile_cursor.ypos;
2493 int new_xpos = old_xpos;
2494 int new_ypos = old_ypos;
2496 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2497 new_xpos = old_xpos + dx;
2499 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2500 new_ypos = old_ypos + dy;
2502 SetTileCursorTargetXY(new_xpos, new_ypos);
2506 static int HandleJoystickForAllPlayers(void)
2510 boolean no_joysticks_configured = TRUE;
2511 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2512 static byte joy_action_last[MAX_PLAYERS];
2514 for (i = 0; i < MAX_PLAYERS; i++)
2515 if (setup.input[i].use_joystick)
2516 no_joysticks_configured = FALSE;
2518 // if no joysticks configured, map connected joysticks to players
2519 if (no_joysticks_configured)
2520 use_as_joystick_nr = TRUE;
2522 for (i = 0; i < MAX_PLAYERS; i++)
2524 byte joy_action = 0;
2526 joy_action = JoystickExt(i, use_as_joystick_nr);
2527 result |= joy_action;
2529 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2530 joy_action != joy_action_last[i])
2531 stored_player[i].action = joy_action;
2533 joy_action_last[i] = joy_action;
2539 void HandleJoystick(void)
2541 static unsigned int joytest_delay = 0;
2542 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2543 static int joytest_last = 0;
2544 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2545 int delay_value = GADGET_FRAME_DELAY;
2546 int joystick = HandleJoystickForAllPlayers();
2547 int keyboard = key_joystick_mapping;
2548 int joy = (joystick | keyboard);
2549 int joytest = joystick;
2550 int left = joy & JOY_LEFT;
2551 int right = joy & JOY_RIGHT;
2552 int up = joy & JOY_UP;
2553 int down = joy & JOY_DOWN;
2554 int button = joy & JOY_BUTTON;
2555 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2556 int dx = (left ? -1 : right ? 1 : 0);
2557 int dy = (up ? -1 : down ? 1 : 0);
2558 boolean use_delay_value_first = (joytest != joytest_last);
2560 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2562 // do not handle this button event anymore
2566 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2567 anyTextGadgetActive()))
2569 // leave name input in main menu or text input gadget
2570 HandleKey(KSYM_Escape, KEY_PRESSED);
2571 HandleKey(KSYM_Escape, KEY_RELEASED);
2576 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2578 if (game_status == GAME_MODE_PLAYING)
2580 // when playing MM style levels, also use delay for keyboard events
2581 joytest |= keyboard;
2583 // only use first delay value for new events, but not for changed events
2584 use_delay_value_first = (!joytest != !joytest_last);
2586 // only use delay after the initial keyboard event
2590 // for any joystick or keyboard event, enable playfield tile cursor
2591 if (dx || dy || button)
2592 SetTileCursorEnabled(TRUE);
2595 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2597 // delay joystick/keyboard actions if axes/keys continually pressed
2598 newbutton = dx = dy = 0;
2602 // first start with longer delay, then continue with shorter delay
2603 joytest_delay_value =
2604 (use_delay_value_first ? delay_value_first : delay_value);
2607 joytest_last = joytest;
2609 switch (game_status)
2611 case GAME_MODE_TITLE:
2612 case GAME_MODE_MAIN:
2613 case GAME_MODE_LEVELS:
2614 case GAME_MODE_LEVELNR:
2615 case GAME_MODE_SETUP:
2616 case GAME_MODE_INFO:
2617 case GAME_MODE_SCORES:
2619 if (anyTextGadgetActive())
2622 if (game_status == GAME_MODE_TITLE)
2623 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2624 else if (game_status == GAME_MODE_MAIN)
2625 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2626 else if (game_status == GAME_MODE_LEVELS)
2627 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2628 else if (game_status == GAME_MODE_LEVELNR)
2629 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2630 else if (game_status == GAME_MODE_SETUP)
2631 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2632 else if (game_status == GAME_MODE_INFO)
2633 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2634 else if (game_status == GAME_MODE_SCORES)
2635 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2640 case GAME_MODE_PLAYING:
2642 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2643 if (tape.playing || keyboard)
2644 newbutton = ((joy & JOY_BUTTON) != 0);
2647 if (newbutton && game.all_players_gone)
2654 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2656 if (joystick & JOY_ACTION)
2657 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2659 else if (tape.recording && tape.pausing && !tape.use_mouse)
2661 if (joystick & JOY_ACTION)
2662 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2665 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2666 HandleTileCursor(dx, dy, button);
2675 void HandleSpecialGameControllerButtons(Event *event)
2680 switch (event->type)
2682 case SDL_CONTROLLERBUTTONDOWN:
2683 key_status = KEY_PRESSED;
2686 case SDL_CONTROLLERBUTTONUP:
2687 key_status = KEY_RELEASED;
2694 switch (event->cbutton.button)
2696 case SDL_CONTROLLER_BUTTON_START:
2700 case SDL_CONTROLLER_BUTTON_BACK:
2708 HandleKey(key, key_status);
2711 void HandleSpecialGameControllerKeys(Key key, int key_status)
2713 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2714 int button = SDL_CONTROLLER_BUTTON_INVALID;
2716 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2717 if (key == KSYM_Rewind)
2718 button = SDL_CONTROLLER_BUTTON_A;
2719 else if (key == KSYM_FastForward || key == KSYM_Menu)
2720 button = SDL_CONTROLLER_BUTTON_B;
2722 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2726 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2727 SDL_CONTROLLERBUTTONUP);
2729 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2730 event.cbutton.button = button;
2731 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2734 HandleJoystickEvent(&event);
2739 boolean DoKeysymAction(int keysym)
2743 Key key = (Key)(-keysym);
2745 HandleKey(key, KEY_PRESSED);
2746 HandleKey(key, KEY_RELEASED);