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 stop_processing_events = FALSE;
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
50 // event filter to set mouse x/y position (for pointer class global animations)
51 // (this is especially required to ensure smooth global animation mouse pointer
52 // movement when the screen is updated without handling events; this can happen
53 // when drawing door/envelope request animations, for example)
55 int FilterMouseMotionEvents(void *userdata, Event *event)
57 if (event->type == EVENT_MOTIONNOTIFY)
59 int mouse_x = ((MotionEvent *)event)->x;
60 int mouse_y = ((MotionEvent *)event)->y;
62 UpdateRawMousePosition(mouse_x, mouse_y);
68 // event filter especially needed for SDL event filtering due to
69 // delay problems with lots of mouse motion events when mouse button
70 // not pressed (X11 can handle this with 'PointerMotionHintMask')
72 // event filter addition for SDL2: as SDL2 does not have a function to enable
73 // or disable keyboard auto-repeat, filter repeated keyboard events instead
75 static int FilterEvents(const Event *event)
79 // skip repeated key press events if keyboard auto-repeat is disabled
80 if (event->type == EVENT_KEYPRESS &&
85 if (event->type == EVENT_BUTTONPRESS ||
86 event->type == EVENT_BUTTONRELEASE)
88 ((ButtonEvent *)event)->x -= video.screen_xoffset;
89 ((ButtonEvent *)event)->y -= video.screen_yoffset;
91 else if (event->type == EVENT_MOTIONNOTIFY)
93 ((MotionEvent *)event)->x -= video.screen_xoffset;
94 ((MotionEvent *)event)->y -= video.screen_yoffset;
97 if (event->type == EVENT_BUTTONPRESS ||
98 event->type == EVENT_BUTTONRELEASE ||
99 event->type == EVENT_MOTIONNOTIFY)
101 // do not reset mouse cursor before all pending events have been processed
102 if (gfx.cursor_mode == cursor_mode_last &&
103 ((game_status == GAME_MODE_TITLE &&
104 gfx.cursor_mode == CURSOR_NONE) ||
105 (game_status == GAME_MODE_PLAYING &&
106 gfx.cursor_mode == CURSOR_PLAYFIELD)))
108 SetMouseCursor(CURSOR_DEFAULT);
110 DelayReached(&special_cursor_delay, 0);
112 cursor_mode_last = CURSOR_DEFAULT;
116 // non-motion events are directly passed to event handler functions
117 if (event->type != EVENT_MOTIONNOTIFY)
120 motion = (MotionEvent *)event;
121 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
122 motion->y >= SY && motion->y < SY + SYSIZE);
124 // set correct mouse x/y position (for pointer class global animations)
125 // (this is required in rare cases where the mouse x/y position calculated
126 // from raw values (to apply logical screen size scaling corrections) does
127 // not match the final mouse event x/y position -- this may happen because
128 // the SDL renderer's viewport position is internally represented as float,
129 // but only accessible as integer, which may lead to rounding errors)
130 gfx.mouse_x = motion->x;
131 gfx.mouse_y = motion->y;
133 // skip mouse motion events without pressed button outside level editor
134 if (button_status == MB_RELEASED &&
135 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
141 // to prevent delay problems, skip mouse motion events if the very next
142 // event is also a mouse motion event (and therefore effectively only
143 // handling the last of a row of mouse motion events in the event queue)
145 static boolean SkipPressedMouseMotionEvent(const Event *event)
147 // nothing to do if the current event is not a mouse motion event
148 if (event->type != EVENT_MOTIONNOTIFY)
151 // only skip motion events with pressed button outside the game
152 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
159 PeekEvent(&next_event);
161 // if next event is also a mouse motion event, skip the current one
162 if (next_event.type == EVENT_MOTIONNOTIFY)
169 static boolean WaitValidEvent(Event *event)
173 if (!FilterEvents(event))
176 if (SkipPressedMouseMotionEvent(event))
182 /* this is especially needed for event modifications for the Android target:
183 if mouse coordinates should be modified in the event filter function,
184 using a properly installed SDL event filter does not work, because in
185 the event filter, mouse coordinates in the event structure are still
186 physical pixel positions, not logical (scaled) screen positions, so this
187 has to be handled at a later stage in the event processing functions
188 (when device pixel positions are already converted to screen positions) */
190 boolean NextValidEvent(Event *event)
192 while (PendingEvent())
193 if (WaitValidEvent(event))
199 void StopProcessingEvents(void)
201 stop_processing_events = TRUE;
204 static void HandleEvents(void)
207 unsigned int event_frame_delay = 0;
208 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
210 ResetDelayCounter(&event_frame_delay);
212 stop_processing_events = FALSE;
214 while (NextValidEvent(&event))
218 case EVENT_BUTTONPRESS:
219 case EVENT_BUTTONRELEASE:
220 HandleButtonEvent((ButtonEvent *) &event);
223 case EVENT_MOTIONNOTIFY:
224 HandleMotionEvent((MotionEvent *) &event);
227 case EVENT_WHEELMOTION:
228 HandleWheelEvent((WheelEvent *) &event);
231 case SDL_WINDOWEVENT:
232 HandleWindowEvent((WindowEvent *) &event);
235 case EVENT_FINGERPRESS:
236 case EVENT_FINGERRELEASE:
237 case EVENT_FINGERMOTION:
238 HandleFingerEvent((FingerEvent *) &event);
241 case EVENT_TEXTINPUT:
242 HandleTextEvent((TextEvent *) &event);
245 case SDL_APP_WILLENTERBACKGROUND:
246 case SDL_APP_DIDENTERBACKGROUND:
247 case SDL_APP_WILLENTERFOREGROUND:
248 case SDL_APP_DIDENTERFOREGROUND:
249 HandlePauseResumeEvent((PauseResumeEvent *) &event);
253 case EVENT_KEYRELEASE:
254 HandleKeyEvent((KeyEvent *) &event);
258 HandleUserEvent((UserEvent *) &event);
262 HandleOtherEvents(&event);
266 // do not handle events for longer than standard frame delay period
267 if (DelayReached(&event_frame_delay, event_frame_delay_value))
270 // do not handle any further events if triggered by a special flag
271 if (stop_processing_events)
276 void HandleOtherEvents(Event *event)
280 case SDL_CONTROLLERBUTTONDOWN:
281 case SDL_CONTROLLERBUTTONUP:
282 // for any game controller button event, disable overlay buttons
283 SetOverlayEnabled(FALSE);
285 HandleSpecialGameControllerButtons(event);
288 case SDL_CONTROLLERDEVICEADDED:
289 case SDL_CONTROLLERDEVICEREMOVED:
290 case SDL_CONTROLLERAXISMOTION:
291 case SDL_JOYAXISMOTION:
292 case SDL_JOYBUTTONDOWN:
293 case SDL_JOYBUTTONUP:
294 HandleJoystickEvent(event);
298 case SDL_DROPCOMPLETE:
301 HandleDropEvent(event);
313 static void HandleMouseCursor(void)
315 if (game_status == GAME_MODE_TITLE)
317 // when showing title screens, hide mouse pointer (if not moved)
319 if (gfx.cursor_mode != CURSOR_NONE &&
320 DelayReached(&special_cursor_delay, special_cursor_delay_value))
322 SetMouseCursor(CURSOR_NONE);
325 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
328 // when playing, display a special mouse pointer inside the playfield
330 // display normal pointer if mouse pressed
331 if (button_status != MB_RELEASED)
332 DelayReached(&special_cursor_delay, 0);
334 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
335 cursor_inside_playfield &&
336 DelayReached(&special_cursor_delay, special_cursor_delay_value))
338 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
340 SetMouseCursor(CURSOR_PLAYFIELD);
343 else if (gfx.cursor_mode != CURSOR_DEFAULT)
345 SetMouseCursor(CURSOR_DEFAULT);
348 // this is set after all pending events have been processed
349 cursor_mode_last = gfx.cursor_mode;
361 // execute event related actions after pending events have been processed
362 HandleEventActions();
364 // don't use all CPU time when idle; the main loop while playing
365 // has its own synchronization and is CPU friendly, too
367 if (game_status == GAME_MODE_PLAYING)
370 // always copy backbuffer to visible screen for every video frame
373 // reset video frame delay to default (may change again while playing)
374 SetVideoFrameDelay(MenuFrameDelay);
376 if (game_status == GAME_MODE_QUIT)
381 void ClearAutoRepeatKeyEvents(void)
383 while (PendingEvent())
387 PeekEvent(&next_event);
389 // if event is repeated key press event, remove it from event queue
390 if (next_event.type == EVENT_KEYPRESS &&
391 next_event.key.repeat)
392 WaitEvent(&next_event);
398 void ClearEventQueue(void)
402 while (NextValidEvent(&event))
406 case EVENT_BUTTONRELEASE:
407 button_status = MB_RELEASED;
410 case EVENT_KEYRELEASE:
414 case SDL_CONTROLLERBUTTONUP:
415 HandleJoystickEvent(&event);
420 HandleOtherEvents(&event);
426 static void ClearPlayerMouseAction(void)
428 local_player->mouse_action.lx = 0;
429 local_player->mouse_action.ly = 0;
430 local_player->mouse_action.button = 0;
433 void ClearPlayerAction(void)
437 // simulate key release events for still pressed keys
438 key_joystick_mapping = 0;
439 for (i = 0; i < MAX_PLAYERS; i++)
441 stored_player[i].action = 0;
442 stored_player[i].snap_action = 0;
445 ClearJoystickState();
446 ClearPlayerMouseAction();
449 static void SetPlayerMouseAction(int mx, int my, int button)
451 int lx = getLevelFromScreenX(mx);
452 int ly = getLevelFromScreenY(my);
453 int new_button = (!local_player->mouse_action.button && button);
455 if (local_player->mouse_action.button_hint)
456 button = local_player->mouse_action.button_hint;
458 ClearPlayerMouseAction();
460 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
463 local_player->mouse_action.lx = lx;
464 local_player->mouse_action.ly = ly;
465 local_player->mouse_action.button = button;
467 if (tape.recording && tape.pausing && tape.use_mouse_actions)
469 // un-pause a paused game only if mouse button was newly pressed down
471 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
474 SetTileCursorXY(lx, ly);
477 static Key GetKeyFromGridButton(int grid_button)
479 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
480 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
481 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
482 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
483 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
484 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
488 #if defined(PLATFORM_ANDROID)
489 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
491 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
492 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
493 int x = touch_x * overlay.grid_xsize;
494 int y = touch_y * overlay.grid_ysize;
495 int grid_button = overlay.grid_button[x][y];
496 Key key = GetKeyFromGridButton(grid_button);
497 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
499 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
503 void HandleButtonEvent(ButtonEvent *event)
505 #if DEBUG_EVENTS_BUTTON
506 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
508 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
512 // for any mouse button event, disable playfield tile cursor
513 SetTileCursorEnabled(FALSE);
515 #if defined(HAS_SCREEN_KEYBOARD)
516 if (video.shifted_up)
517 event->y += video.shifted_up_pos;
520 motion_status = FALSE;
522 if (event->type == EVENT_BUTTONPRESS)
523 button_status = event->button;
525 button_status = MB_RELEASED;
527 HandleButton(event->x, event->y, button_status, event->button);
530 void HandleMotionEvent(MotionEvent *event)
532 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
535 motion_status = TRUE;
537 #if DEBUG_EVENTS_MOTION
538 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
539 button_status, event->x, event->y);
542 HandleButton(event->x, event->y, button_status, button_status);
545 void HandleWheelEvent(WheelEvent *event)
549 #if DEBUG_EVENTS_WHEEL
551 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
552 event->which, event->x, event->y);
554 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
555 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
556 event->which, event->x, event->y,
557 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
558 "SDL_MOUSEWHEEL_FLIPPED"));
562 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
563 event->x > 0 ? MB_WHEEL_RIGHT :
564 event->y < 0 ? MB_WHEEL_DOWN :
565 event->y > 0 ? MB_WHEEL_UP : 0);
567 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
568 // accelerated mouse wheel available on Mac and Windows
569 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
571 // no accelerated mouse wheel available on Unix/Linux
572 wheel_steps = DEFAULT_WHEEL_STEPS;
575 motion_status = FALSE;
577 button_status = button_nr;
578 HandleButton(0, 0, button_status, -button_nr);
580 button_status = MB_RELEASED;
581 HandleButton(0, 0, button_status, -button_nr);
584 void HandleWindowEvent(WindowEvent *event)
586 #if DEBUG_EVENTS_WINDOW
587 int subtype = event->event;
590 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
591 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
592 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
593 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
594 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
595 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
596 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
597 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
598 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
599 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
600 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
601 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
602 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
603 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
606 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
607 event_name, event->data1, event->data2);
611 // (not needed, as the screen gets redrawn every 20 ms anyway)
612 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
613 event->event == SDL_WINDOWEVENT_RESIZED ||
614 event->event == SDL_WINDOWEVENT_EXPOSED)
618 if (event->event == SDL_WINDOWEVENT_RESIZED)
620 if (!video.fullscreen_enabled)
622 int new_window_width = event->data1;
623 int new_window_height = event->data2;
625 // if window size has changed after resizing, calculate new scaling factor
626 if (new_window_width != video.window_width ||
627 new_window_height != video.window_height)
629 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
630 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
632 // (extreme window scaling allowed, but cannot be saved permanently)
633 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
634 setup.window_scaling_percent =
635 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
636 MAX_WINDOW_SCALING_PERCENT);
638 video.window_width = new_window_width;
639 video.window_height = new_window_height;
641 if (game_status == GAME_MODE_SETUP)
642 RedrawSetupScreenAfterFullscreenToggle();
644 UpdateMousePosition();
649 #if defined(PLATFORM_ANDROID)
652 int new_display_width = event->data1;
653 int new_display_height = event->data2;
655 // if fullscreen display size has changed, device has been rotated
656 if (new_display_width != video.display_width ||
657 new_display_height != video.display_height)
659 int nr = GRID_ACTIVE_NR(); // previous screen orientation
661 video.display_width = new_display_width;
662 video.display_height = new_display_height;
664 SDLSetScreenProperties();
665 SetGadgetsPosition_OverlayTouchButtons();
667 // check if screen orientation has changed (should always be true here)
668 if (nr != GRID_ACTIVE_NR())
672 if (game_status == GAME_MODE_SETUP)
673 RedrawSetupScreenAfterScreenRotation(nr);
675 nr = GRID_ACTIVE_NR();
677 overlay.grid_xsize = setup.touch.grid_xsize[nr];
678 overlay.grid_ysize = setup.touch.grid_ysize[nr];
680 for (x = 0; x < MAX_GRID_XSIZE; x++)
681 for (y = 0; y < MAX_GRID_YSIZE; y++)
682 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
690 #define NUM_TOUCH_FINGERS 3
695 SDL_FingerID finger_id;
699 } touch_info[NUM_TOUCH_FINGERS];
701 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
703 int x = event->x * overlay.grid_xsize;
704 int y = event->y * overlay.grid_ysize;
705 int grid_button = overlay.grid_button[x][y];
706 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
707 Key key = GetKeyFromGridButton(grid_button);
708 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
710 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
714 // for any touch input event, enable overlay buttons (if activated)
715 SetOverlayEnabled(TRUE);
717 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
718 getKeyNameFromKey(key), key_status_name, event->fingerId);
720 if (key_status == KEY_PRESSED)
721 overlay.grid_button_action |= grid_button_action;
723 overlay.grid_button_action &= ~grid_button_action;
725 // check if we already know this touch event's finger id
726 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
728 if (touch_info[i].touched &&
729 touch_info[i].finger_id == event->fingerId)
731 // Error(ERR_DEBUG, "MARK 1: %d", i);
737 if (i >= NUM_TOUCH_FINGERS)
739 if (key_status == KEY_PRESSED)
741 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
743 // unknown finger id -- get new, empty slot, if available
744 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
746 if (touch_info[i].counter < oldest_counter)
749 oldest_counter = touch_info[i].counter;
751 // Error(ERR_DEBUG, "MARK 2: %d", i);
754 if (!touch_info[i].touched)
756 // Error(ERR_DEBUG, "MARK 3: %d", i);
762 if (i >= NUM_TOUCH_FINGERS)
764 // all slots allocated -- use oldest slot
767 // Error(ERR_DEBUG, "MARK 4: %d", i);
772 // release of previously unknown key (should not happen)
774 if (key != KSYM_UNDEFINED)
776 HandleKey(key, KEY_RELEASED);
778 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
779 getKeyNameFromKey(key), "KEY_RELEASED", i);
784 if (i < NUM_TOUCH_FINGERS)
786 if (key_status == KEY_PRESSED)
788 if (touch_info[i].key != key)
790 if (touch_info[i].key != KSYM_UNDEFINED)
792 HandleKey(touch_info[i].key, KEY_RELEASED);
794 // undraw previous grid button when moving finger away
795 overlay.grid_button_action &= ~touch_info[i].action;
797 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
798 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
801 if (key != KSYM_UNDEFINED)
803 HandleKey(key, KEY_PRESSED);
805 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
806 getKeyNameFromKey(key), "KEY_PRESSED", i);
810 touch_info[i].touched = TRUE;
811 touch_info[i].finger_id = event->fingerId;
812 touch_info[i].counter = Counter();
813 touch_info[i].key = key;
814 touch_info[i].action = grid_button_action;
818 if (touch_info[i].key != KSYM_UNDEFINED)
820 HandleKey(touch_info[i].key, KEY_RELEASED);
822 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
823 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
826 touch_info[i].touched = FALSE;
827 touch_info[i].finger_id = 0;
828 touch_info[i].counter = 0;
829 touch_info[i].key = 0;
830 touch_info[i].action = JOY_NO_ACTION;
835 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
837 static Key motion_key_x = KSYM_UNDEFINED;
838 static Key motion_key_y = KSYM_UNDEFINED;
839 static Key button_key = KSYM_UNDEFINED;
840 static float motion_x1, motion_y1;
841 static float button_x1, button_y1;
842 static SDL_FingerID motion_id = -1;
843 static SDL_FingerID button_id = -1;
844 int move_trigger_distance_percent = setup.touch.move_distance;
845 int drop_trigger_distance_percent = setup.touch.drop_distance;
846 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
847 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
848 float event_x = event->x;
849 float event_y = event->y;
851 if (event->type == EVENT_FINGERPRESS)
853 if (event_x > 1.0 / 3.0)
857 motion_id = event->fingerId;
862 motion_key_x = KSYM_UNDEFINED;
863 motion_key_y = KSYM_UNDEFINED;
865 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
871 button_id = event->fingerId;
876 button_key = setup.input[0].key.snap;
878 HandleKey(button_key, KEY_PRESSED);
880 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
883 else if (event->type == EVENT_FINGERRELEASE)
885 if (event->fingerId == motion_id)
889 if (motion_key_x != KSYM_UNDEFINED)
890 HandleKey(motion_key_x, KEY_RELEASED);
891 if (motion_key_y != KSYM_UNDEFINED)
892 HandleKey(motion_key_y, KEY_RELEASED);
894 motion_key_x = KSYM_UNDEFINED;
895 motion_key_y = KSYM_UNDEFINED;
897 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
899 else if (event->fingerId == button_id)
903 if (button_key != KSYM_UNDEFINED)
904 HandleKey(button_key, KEY_RELEASED);
906 button_key = KSYM_UNDEFINED;
908 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
911 else if (event->type == EVENT_FINGERMOTION)
913 if (event->fingerId == motion_id)
915 float distance_x = ABS(event_x - motion_x1);
916 float distance_y = ABS(event_y - motion_y1);
917 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
918 event_x > motion_x1 ? setup.input[0].key.right :
920 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
921 event_y > motion_y1 ? setup.input[0].key.down :
924 if (distance_x < move_trigger_distance / 2 ||
925 distance_x < distance_y)
926 new_motion_key_x = KSYM_UNDEFINED;
928 if (distance_y < move_trigger_distance / 2 ||
929 distance_y < distance_x)
930 new_motion_key_y = KSYM_UNDEFINED;
932 if (distance_x > move_trigger_distance ||
933 distance_y > move_trigger_distance)
935 if (new_motion_key_x != motion_key_x)
937 if (motion_key_x != KSYM_UNDEFINED)
938 HandleKey(motion_key_x, KEY_RELEASED);
939 if (new_motion_key_x != KSYM_UNDEFINED)
940 HandleKey(new_motion_key_x, KEY_PRESSED);
943 if (new_motion_key_y != motion_key_y)
945 if (motion_key_y != KSYM_UNDEFINED)
946 HandleKey(motion_key_y, KEY_RELEASED);
947 if (new_motion_key_y != KSYM_UNDEFINED)
948 HandleKey(new_motion_key_y, KEY_PRESSED);
954 motion_key_x = new_motion_key_x;
955 motion_key_y = new_motion_key_y;
957 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
960 else if (event->fingerId == button_id)
962 float distance_x = ABS(event_x - button_x1);
963 float distance_y = ABS(event_y - button_y1);
965 if (distance_x < drop_trigger_distance / 2 &&
966 distance_y > drop_trigger_distance)
968 if (button_key == setup.input[0].key.snap)
969 HandleKey(button_key, KEY_RELEASED);
974 button_key = setup.input[0].key.drop;
976 HandleKey(button_key, KEY_PRESSED);
978 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
984 void HandleFingerEvent(FingerEvent *event)
986 #if DEBUG_EVENTS_FINGER
987 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
988 event->type == EVENT_FINGERPRESS ? "pressed" :
989 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
993 event->dx, event->dy,
997 runtime.uses_touch_device = TRUE;
999 if (game_status != GAME_MODE_PLAYING)
1002 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1004 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1005 local_player->mouse_action.button_hint =
1006 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1007 event->x < 0.5 ? MB_LEFTBUTTON :
1008 event->x > 0.5 ? MB_RIGHTBUTTON :
1014 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1015 HandleFingerEvent_VirtualButtons(event);
1016 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1017 HandleFingerEvent_WipeGestures(event);
1020 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1022 static int old_mx = 0, old_my = 0;
1023 static int last_button = MB_LEFTBUTTON;
1024 static boolean touched = FALSE;
1025 static boolean tapped = FALSE;
1027 // screen tile was tapped (but finger not touching the screen anymore)
1028 // (this point will also be reached without receiving a touch event)
1029 if (tapped && !touched)
1031 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1036 // stop here if this function was not triggered by a touch event
1040 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1042 // finger started touching the screen
1052 ClearPlayerMouseAction();
1054 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1057 else if (button == MB_RELEASED && touched)
1059 // finger stopped touching the screen
1064 SetPlayerMouseAction(old_mx, old_my, last_button);
1066 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1068 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1073 // finger moved while touching the screen
1075 int old_x = getLevelFromScreenX(old_mx);
1076 int old_y = getLevelFromScreenY(old_my);
1077 int new_x = getLevelFromScreenX(mx);
1078 int new_y = getLevelFromScreenY(my);
1080 if (new_x != old_x || new_y != old_y)
1085 // finger moved left or right from (horizontal) starting position
1087 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1089 SetPlayerMouseAction(old_mx, old_my, button_nr);
1091 last_button = button_nr;
1093 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1097 // finger stays at or returned to (horizontal) starting position
1099 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1101 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1106 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1108 static int old_mx = 0, old_my = 0;
1109 static int last_button = MB_LEFTBUTTON;
1110 static boolean touched = FALSE;
1111 static boolean tapped = FALSE;
1113 // screen tile was tapped (but finger not touching the screen anymore)
1114 // (this point will also be reached without receiving a touch event)
1115 if (tapped && !touched)
1117 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1122 // stop here if this function was not triggered by a touch event
1126 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1128 // finger started touching the screen
1138 ClearPlayerMouseAction();
1140 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1143 else if (button == MB_RELEASED && touched)
1145 // finger stopped touching the screen
1150 SetPlayerMouseAction(old_mx, old_my, last_button);
1152 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1154 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1159 // finger moved while touching the screen
1161 int old_x = getLevelFromScreenX(old_mx);
1162 int old_y = getLevelFromScreenY(old_my);
1163 int new_x = getLevelFromScreenX(mx);
1164 int new_y = getLevelFromScreenY(my);
1166 if (new_x != old_x || new_y != old_y)
1168 // finger moved away from starting position
1170 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1172 // quickly alternate between clicking and releasing for maximum speed
1173 if (FrameCounter % 2 == 0)
1174 button_nr = MB_RELEASED;
1176 SetPlayerMouseAction(old_mx, old_my, button_nr);
1179 last_button = button_nr;
1183 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1187 // finger stays at or returned to starting position
1189 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1191 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1196 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1198 static int old_mx = 0, old_my = 0;
1199 static Key motion_key_x = KSYM_UNDEFINED;
1200 static Key motion_key_y = KSYM_UNDEFINED;
1201 static boolean touched = FALSE;
1202 static boolean started_on_player = FALSE;
1203 static boolean player_is_dropping = FALSE;
1204 static int player_drop_count = 0;
1205 static int last_player_x = -1;
1206 static int last_player_y = -1;
1208 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1217 started_on_player = FALSE;
1218 player_is_dropping = FALSE;
1219 player_drop_count = 0;
1223 motion_key_x = KSYM_UNDEFINED;
1224 motion_key_y = KSYM_UNDEFINED;
1226 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1229 else if (button == MB_RELEASED && touched)
1236 if (motion_key_x != KSYM_UNDEFINED)
1237 HandleKey(motion_key_x, KEY_RELEASED);
1238 if (motion_key_y != KSYM_UNDEFINED)
1239 HandleKey(motion_key_y, KEY_RELEASED);
1241 if (started_on_player)
1243 if (player_is_dropping)
1245 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1247 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1251 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1253 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1257 motion_key_x = KSYM_UNDEFINED;
1258 motion_key_y = KSYM_UNDEFINED;
1260 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1265 int src_x = local_player->jx;
1266 int src_y = local_player->jy;
1267 int dst_x = getLevelFromScreenX(old_mx);
1268 int dst_y = getLevelFromScreenY(old_my);
1269 int dx = dst_x - src_x;
1270 int dy = dst_y - src_y;
1271 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1272 dx > 0 ? setup.input[0].key.right :
1274 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1275 dy > 0 ? setup.input[0].key.down :
1278 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1279 (last_player_x != local_player->jx ||
1280 last_player_y != local_player->jy))
1282 // in case of asymmetric diagonal movement, use "preferred" direction
1284 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1286 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1287 game_em.ply[0]->last_move_dir = last_move_dir;
1289 local_player->last_move_dir = last_move_dir;
1291 // (required to prevent accidentally forcing direction for next movement)
1292 last_player_x = local_player->jx;
1293 last_player_y = local_player->jy;
1296 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1298 started_on_player = TRUE;
1299 player_drop_count = getPlayerInventorySize(0);
1300 player_is_dropping = (player_drop_count > 0);
1302 if (player_is_dropping)
1304 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1306 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1310 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1312 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1315 else if (dx != 0 || dy != 0)
1317 if (player_is_dropping &&
1318 player_drop_count == getPlayerInventorySize(0))
1320 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1322 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1323 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1325 player_is_dropping = FALSE;
1329 if (new_motion_key_x != motion_key_x)
1331 Error(ERR_DEBUG, "---------- %s %s ----------",
1332 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1333 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1335 if (motion_key_x != KSYM_UNDEFINED)
1336 HandleKey(motion_key_x, KEY_RELEASED);
1337 if (new_motion_key_x != KSYM_UNDEFINED)
1338 HandleKey(new_motion_key_x, KEY_PRESSED);
1341 if (new_motion_key_y != motion_key_y)
1343 Error(ERR_DEBUG, "---------- %s %s ----------",
1344 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1345 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1347 if (motion_key_y != KSYM_UNDEFINED)
1348 HandleKey(motion_key_y, KEY_RELEASED);
1349 if (new_motion_key_y != KSYM_UNDEFINED)
1350 HandleKey(new_motion_key_y, KEY_PRESSED);
1353 motion_key_x = new_motion_key_x;
1354 motion_key_y = new_motion_key_y;
1358 static void HandleButtonOrFinger(int mx, int my, int button)
1360 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1362 if (game_status != GAME_MODE_PLAYING)
1365 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1367 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1368 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1369 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1370 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1371 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1372 SetPlayerMouseAction(mx, my, button); // special case
1376 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1377 HandleButtonOrFinger_FollowFinger(mx, my, button);
1378 else if (game.use_mouse_actions && valid_mouse_event)
1379 SetPlayerMouseAction(mx, my, button);
1383 static boolean checkTextInputKeyModState(void)
1385 // when playing, only handle raw key events and ignore text input
1386 if (game_status == GAME_MODE_PLAYING)
1389 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1392 void HandleTextEvent(TextEvent *event)
1394 char *text = event->text;
1395 Key key = getKeyFromKeyName(text);
1397 #if DEBUG_EVENTS_TEXT
1398 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1401 text[0], (int)(text[0]),
1403 getKeyNameFromKey(key),
1407 #if !defined(HAS_SCREEN_KEYBOARD)
1408 // non-mobile devices: only handle key input with modifier keys pressed here
1409 // (every other key input is handled directly as physical key input event)
1410 if (!checkTextInputKeyModState())
1414 // process text input as "classic" (with uppercase etc.) key input event
1415 HandleKey(key, KEY_PRESSED);
1416 HandleKey(key, KEY_RELEASED);
1419 void HandlePauseResumeEvent(PauseResumeEvent *event)
1421 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1425 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1431 void HandleKeyEvent(KeyEvent *event)
1433 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1434 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1435 Key key = GetEventKey(event, with_modifiers);
1436 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1438 #if DEBUG_EVENTS_KEY
1439 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1440 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1441 event->keysym.scancode,
1446 getKeyNameFromKey(key));
1449 #if defined(PLATFORM_ANDROID)
1450 if (key == KSYM_Back)
1452 // always map the "back" button to the "escape" key on Android devices
1455 else if (key == KSYM_Menu)
1457 // the "menu" button can be used to toggle displaying virtual buttons
1458 if (key_status == KEY_PRESSED)
1459 SetOverlayEnabled(!GetOverlayEnabled());
1463 // for any other "real" key event, disable virtual buttons
1464 SetOverlayEnabled(FALSE);
1466 // for any other "real" key event, disable overlay touch buttons
1467 runtime.uses_touch_device = FALSE;
1471 HandleKeyModState(keymod, key_status);
1473 // only handle raw key input without text modifier keys pressed
1474 if (!checkTextInputKeyModState())
1475 HandleKey(key, key_status);
1478 static int HandleDropFileEvent(char *filename)
1480 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1482 // check and extract dropped zip files into correct user data directory
1483 if (!strSuffixLower(filename, ".zip"))
1485 Error(ERR_WARN, "file '%s' not supported", filename);
1487 return TREE_TYPE_UNDEFINED;
1490 TreeInfo *tree_node = NULL;
1491 int tree_type = GetZipFileTreeType(filename);
1492 char *directory = TREE_USERDIR(tree_type);
1494 if (directory == NULL)
1496 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1498 return TREE_TYPE_UNDEFINED;
1501 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1502 game_status == GAME_MODE_LEVELS &&
1503 leveldir_current->node_parent != NULL)
1505 // extract new level set next to currently selected level set
1506 tree_node = leveldir_current;
1508 // get parent directory of currently selected level set directory
1509 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1511 // use private level directory instead of top-level package level directory
1512 if (strPrefix(directory, options.level_directory) &&
1513 strEqual(leveldir_current->node_parent->fullpath, "."))
1514 directory = getUserLevelDir(NULL);
1517 // extract level or artwork set from zip file to target directory
1518 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1520 if (top_dir == NULL)
1522 // error message already issued by "ExtractZipFileIntoDirectory()"
1524 return TREE_TYPE_UNDEFINED;
1527 // add extracted level or artwork set to tree info structure
1528 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1530 // update menu screen (and possibly change current level set)
1531 DrawScreenAfterAddingSet(top_dir, tree_type);
1536 static void HandleDropTextEvent(char *text)
1538 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1541 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1542 int num_artwork_sets_succeeded,
1543 int num_files_failed)
1545 // only show request dialog if no other request dialog already active
1546 if (game.request_active)
1549 // this case can happen with drag-and-drop with older SDL versions
1550 if (num_level_sets_succeeded == 0 &&
1551 num_artwork_sets_succeeded == 0 &&
1552 num_files_failed == 0)
1557 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1559 char message_part1[50];
1561 sprintf(message_part1, "New %s set%s added",
1562 (num_artwork_sets_succeeded == 0 ? "level" :
1563 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1564 (num_level_sets_succeeded +
1565 num_artwork_sets_succeeded > 1 ? "s" : ""));
1567 if (num_files_failed > 0)
1568 sprintf(message, "%s, but %d dropped file%s failed!",
1569 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1571 sprintf(message, "%s!", message_part1);
1573 else if (num_files_failed > 0)
1575 sprintf(message, "Failed to process dropped file%s!",
1576 num_files_failed > 1 ? "s" : "");
1579 Request(message, REQ_CONFIRM);
1582 void HandleDropEvent(Event *event)
1584 static boolean confirm_on_drop_complete = FALSE;
1585 static int num_level_sets_succeeded = 0;
1586 static int num_artwork_sets_succeeded = 0;
1587 static int num_files_failed = 0;
1589 switch (event->type)
1593 confirm_on_drop_complete = TRUE;
1594 num_level_sets_succeeded = 0;
1595 num_artwork_sets_succeeded = 0;
1596 num_files_failed = 0;
1603 int tree_type = HandleDropFileEvent(event->drop.file);
1605 if (tree_type == TREE_TYPE_LEVEL_DIR)
1606 num_level_sets_succeeded++;
1607 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1608 tree_type == TREE_TYPE_SOUNDS_DIR ||
1609 tree_type == TREE_TYPE_MUSIC_DIR)
1610 num_artwork_sets_succeeded++;
1614 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1615 if (!confirm_on_drop_complete)
1617 // process all remaining events, including further SDL_DROPFILE events
1620 HandleDropCompleteEvent(num_level_sets_succeeded,
1621 num_artwork_sets_succeeded,
1624 num_level_sets_succeeded = 0;
1625 num_artwork_sets_succeeded = 0;
1626 num_files_failed = 0;
1634 HandleDropTextEvent(event->drop.file);
1639 case SDL_DROPCOMPLETE:
1641 HandleDropCompleteEvent(num_level_sets_succeeded,
1642 num_artwork_sets_succeeded,
1649 if (event->drop.file != NULL)
1650 SDL_free(event->drop.file);
1653 void HandleUserEvent(UserEvent *event)
1655 switch (event->code)
1657 case USEREVENT_ANIM_DELAY_ACTION:
1658 case USEREVENT_ANIM_EVENT_ACTION:
1659 // execute action functions until matching action was found
1660 if (DoKeysymAction(event->value1) ||
1661 DoGadgetAction(event->value1) ||
1662 DoScreenAction(event->value1))
1671 void HandleButton(int mx, int my, int button, int button_nr)
1673 static int old_mx = 0, old_my = 0;
1674 boolean button_hold = FALSE;
1675 boolean handle_gadgets = TRUE;
1681 button_nr = -button_nr;
1690 #if defined(PLATFORM_ANDROID)
1691 // when playing, only handle gadgets when using "follow finger" controls
1692 // or when using touch controls in combination with the MM game engine
1693 // or when using gadgets that do not overlap with virtual buttons
1695 (game_status != GAME_MODE_PLAYING ||
1696 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1697 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1698 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1699 !CheckVirtualButtonPressed(mx, my, button)));
1701 // always recognize potentially releasing already pressed gadgets
1702 if (button == MB_RELEASED)
1703 handle_gadgets = TRUE;
1705 // always recognize pressing or releasing overlay touch buttons
1706 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1707 handle_gadgets = TRUE;
1710 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1712 // do not handle this button event anymore
1713 return; // force mouse event not to be handled at all
1716 if (handle_gadgets && HandleGadgets(mx, my, button))
1718 // do not handle this button event anymore
1719 mx = my = -32; // force mouse event to be outside screen tiles
1722 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1725 // do not use scroll wheel button events for anything other than gadgets
1726 if (IS_WHEEL_BUTTON(button_nr))
1729 switch (game_status)
1731 case GAME_MODE_TITLE:
1732 HandleTitleScreen(mx, my, 0, 0, button);
1735 case GAME_MODE_MAIN:
1736 HandleMainMenu(mx, my, 0, 0, button);
1739 case GAME_MODE_PSEUDO_TYPENAME:
1740 HandleTypeName(0, KSYM_Return);
1743 case GAME_MODE_LEVELS:
1744 HandleChooseLevelSet(mx, my, 0, 0, button);
1747 case GAME_MODE_LEVELNR:
1748 HandleChooseLevelNr(mx, my, 0, 0, button);
1751 case GAME_MODE_SCORES:
1752 HandleHallOfFame(0, 0, 0, 0, button);
1755 case GAME_MODE_EDITOR:
1756 HandleLevelEditorIdle();
1759 case GAME_MODE_INFO:
1760 HandleInfoScreen(mx, my, 0, 0, button);
1763 case GAME_MODE_SETUP:
1764 HandleSetupScreen(mx, my, 0, 0, button);
1767 case GAME_MODE_PLAYING:
1768 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1769 HandleButtonOrFinger(mx, my, button);
1771 SetPlayerMouseAction(mx, my, button);
1774 if (button == MB_PRESSED && !motion_status && !button_hold &&
1775 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1776 DumpTileFromScreen(mx, my);
1786 static boolean is_string_suffix(char *string, char *suffix)
1788 int string_len = strlen(string);
1789 int suffix_len = strlen(suffix);
1791 if (suffix_len > string_len)
1794 return (strEqual(&string[string_len - suffix_len], suffix));
1797 #define MAX_CHEAT_INPUT_LEN 32
1799 static void HandleKeysSpecial(Key key)
1801 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1802 char letter = getCharFromKey(key);
1803 int cheat_input_len = strlen(cheat_input);
1809 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1811 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1812 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1814 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1817 cheat_input[cheat_input_len++] = letter;
1818 cheat_input[cheat_input_len] = '\0';
1820 #if DEBUG_EVENTS_KEY
1821 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1824 if (game_status == GAME_MODE_MAIN)
1826 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1827 is_string_suffix(cheat_input, ":ist"))
1829 InsertSolutionTape();
1831 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1832 is_string_suffix(cheat_input, ":pst"))
1836 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1837 is_string_suffix(cheat_input, ":rg"))
1839 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1842 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1843 is_string_suffix(cheat_input, ":rs"))
1845 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1848 else if (is_string_suffix(cheat_input, ":reload-music") ||
1849 is_string_suffix(cheat_input, ":rm"))
1851 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1854 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1855 is_string_suffix(cheat_input, ":ra"))
1857 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1858 1 << ARTWORK_TYPE_SOUNDS |
1859 1 << ARTWORK_TYPE_MUSIC);
1862 else if (is_string_suffix(cheat_input, ":dump-level") ||
1863 is_string_suffix(cheat_input, ":dl"))
1867 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1868 is_string_suffix(cheat_input, ":dt"))
1872 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1873 is_string_suffix(cheat_input, ":ft"))
1875 /* fix single-player tapes that contain player input for more than one
1876 player (due to a bug in 3.3.1.2 and earlier versions), which results
1877 in playing levels with more than one player in multi-player mode,
1878 even though the tape was originally recorded in single-player mode */
1880 // remove player input actions for all players but the first one
1881 for (i = 1; i < MAX_PLAYERS; i++)
1882 tape.player_participates[i] = FALSE;
1884 tape.changed = TRUE;
1886 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1887 is_string_suffix(cheat_input, ":snl"))
1889 SaveNativeLevel(&level);
1891 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1892 is_string_suffix(cheat_input, ":fps"))
1894 global.show_frames_per_second = !global.show_frames_per_second;
1897 else if (game_status == GAME_MODE_PLAYING)
1900 if (is_string_suffix(cheat_input, ".q"))
1901 DEBUG_SetMaximumDynamite();
1904 else if (game_status == GAME_MODE_EDITOR)
1906 if (is_string_suffix(cheat_input, ":dump-brush") ||
1907 is_string_suffix(cheat_input, ":DB"))
1911 else if (is_string_suffix(cheat_input, ":DDB"))
1916 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1918 if (letter == 'x') // copy brush to clipboard (small size)
1920 CopyBrushToClipboard_Small();
1922 else if (letter == 'c') // copy brush to clipboard (normal size)
1924 CopyBrushToClipboard();
1926 else if (letter == 'v') // paste brush from Clipboard
1928 CopyClipboardToBrush();
1930 else if (letter == 'z') // undo or redo last operation
1932 if (GetKeyModState() & KMOD_Shift)
1933 RedoLevelEditorOperation();
1935 UndoLevelEditorOperation();
1940 // special key shortcuts for all game modes
1941 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1942 is_string_suffix(cheat_input, ":dea") ||
1943 is_string_suffix(cheat_input, ":DEA"))
1945 DumpGadgetIdentifiers();
1946 DumpScreenIdentifiers();
1950 boolean HandleKeysDebug(Key key, int key_status)
1955 if (key_status != KEY_PRESSED)
1958 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1960 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1962 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1964 if (key == setup.debug.frame_delay_key[i] &&
1965 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1967 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1968 setup.debug.frame_delay[i] : setup.game_frame_delay);
1970 if (!setup.debug.frame_delay_game_only)
1971 MenuFrameDelay = GameFrameDelay;
1973 SetVideoFrameDelay(GameFrameDelay);
1975 if (GameFrameDelay > ONE_SECOND_DELAY)
1976 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1977 else if (GameFrameDelay != 0)
1978 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1979 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1980 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1982 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1989 if (game_status == GAME_MODE_PLAYING)
1993 options.debug = !options.debug;
1995 Error(ERR_INFO, "debug mode %s",
1996 (options.debug ? "enabled" : "disabled"));
2000 else if (key == KSYM_v)
2002 Error(ERR_INFO, "currently using game engine version %d",
2003 game.engine_version);
2013 void HandleKey(Key key, int key_status)
2015 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2016 static boolean ignore_repeated_key = FALSE;
2017 static struct SetupKeyboardInfo ski;
2018 static struct SetupShortcutInfo ssi;
2027 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2028 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2029 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2030 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2031 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2032 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2037 if (HandleKeysDebug(key, key_status))
2038 return; // do not handle already processed keys again
2040 // map special keys (media keys / remote control buttons) to default keys
2041 if (key == KSYM_PlayPause)
2043 else if (key == KSYM_Select)
2046 HandleSpecialGameControllerKeys(key, key_status);
2048 if (game_status == GAME_MODE_PLAYING)
2050 // only needed for single-step tape recording mode
2051 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2054 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2056 byte key_action = 0;
2057 byte key_snap_action = 0;
2059 if (setup.input[pnr].use_joystick)
2062 ski = setup.input[pnr].key;
2064 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2065 if (key == *key_info[i].key_custom)
2066 key_action |= key_info[i].action;
2068 // use combined snap+direction keys for the first player only
2071 ssi = setup.shortcut;
2073 // also remember normal snap key when handling snap+direction keys
2074 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2076 for (i = 0; i < NUM_DIRECTIONS; i++)
2078 if (key == *key_info[i].key_snap)
2080 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2081 key_snap_action |= key_info[i].action;
2086 if (key_status == KEY_PRESSED)
2088 stored_player[pnr].action |= key_action;
2089 stored_player[pnr].snap_action |= key_snap_action;
2093 stored_player[pnr].action &= ~key_action;
2094 stored_player[pnr].snap_action &= ~key_snap_action;
2097 // restore snap action if one of several pressed snap keys was released
2098 if (stored_player[pnr].snap_action)
2099 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2101 if (tape.recording && tape.pausing && tape.use_key_actions)
2103 if (tape.single_step)
2105 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2107 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2109 // if snap key already pressed, keep pause mode when releasing
2110 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2111 has_snapped[pnr] = TRUE;
2113 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2115 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2117 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2118 getRedDiskReleaseFlag_SP() == 0)
2120 // add a single inactive frame before dropping starts
2121 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2122 stored_player[pnr].force_dropping = TRUE;
2125 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2127 // if snap key was pressed without direction, leave pause mode
2128 if (!has_snapped[pnr])
2129 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2131 has_snapped[pnr] = FALSE;
2136 // prevent key release events from un-pausing a paused game
2137 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2138 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2142 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2143 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2149 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2150 if (key == key_info[i].key_default)
2151 joy |= key_info[i].action;
2156 if (key_status == KEY_PRESSED)
2157 key_joystick_mapping |= joy;
2159 key_joystick_mapping &= ~joy;
2164 if (game_status != GAME_MODE_PLAYING)
2165 key_joystick_mapping = 0;
2167 if (key_status == KEY_RELEASED)
2169 // reset flag to ignore repeated "key pressed" events after key release
2170 ignore_repeated_key = FALSE;
2175 if ((key == KSYM_F11 ||
2176 ((key == KSYM_Return ||
2177 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2178 video.fullscreen_available &&
2179 !ignore_repeated_key)
2181 setup.fullscreen = !setup.fullscreen;
2183 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2185 if (game_status == GAME_MODE_SETUP)
2186 RedrawSetupScreenAfterFullscreenToggle();
2188 UpdateMousePosition();
2190 // set flag to ignore repeated "key pressed" events
2191 ignore_repeated_key = TRUE;
2196 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2197 key == KSYM_minus || key == KSYM_KP_Subtract ||
2198 key == KSYM_plus || key == KSYM_KP_Add ||
2199 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2200 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2201 video.window_scaling_available &&
2202 !video.fullscreen_enabled)
2204 if (key == KSYM_0 || key == KSYM_KP_0)
2205 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2206 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2207 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2209 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2211 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2212 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2213 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2214 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2216 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2218 if (game_status == GAME_MODE_SETUP)
2219 RedrawSetupScreenAfterFullscreenToggle();
2221 UpdateMousePosition();
2226 // some key events are handled like clicks for global animations
2227 boolean click = (key == KSYM_space ||
2228 key == KSYM_Return ||
2229 key == KSYM_Escape);
2231 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2233 // do not handle this key event anymore
2234 if (key != KSYM_Escape) // always allow ESC key to be handled
2238 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2239 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2246 if (game_status == GAME_MODE_MAIN &&
2247 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2249 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2254 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2256 if (key == setup.shortcut.save_game)
2258 else if (key == setup.shortcut.load_game)
2260 else if (key == setup.shortcut.toggle_pause)
2261 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2263 HandleTapeButtonKeys(key);
2264 HandleSoundButtonKeys(key);
2267 if (game_status == GAME_MODE_PLAYING && !network_playing)
2269 int centered_player_nr_next = -999;
2271 if (key == setup.shortcut.focus_player_all)
2272 centered_player_nr_next = -1;
2274 for (i = 0; i < MAX_PLAYERS; i++)
2275 if (key == setup.shortcut.focus_player[i])
2276 centered_player_nr_next = i;
2278 if (centered_player_nr_next != -999)
2280 game.centered_player_nr_next = centered_player_nr_next;
2281 game.set_centered_player = TRUE;
2285 tape.centered_player_nr_next = game.centered_player_nr_next;
2286 tape.set_centered_player = TRUE;
2291 HandleKeysSpecial(key);
2293 if (HandleGadgetsKeyInput(key))
2294 return; // do not handle already processed keys again
2296 switch (game_status)
2298 case GAME_MODE_PSEUDO_TYPENAME:
2299 HandleTypeName(0, key);
2302 case GAME_MODE_TITLE:
2303 case GAME_MODE_MAIN:
2304 case GAME_MODE_LEVELS:
2305 case GAME_MODE_LEVELNR:
2306 case GAME_MODE_SETUP:
2307 case GAME_MODE_INFO:
2308 case GAME_MODE_SCORES:
2310 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2317 if (game_status == GAME_MODE_TITLE)
2318 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2319 else if (game_status == GAME_MODE_MAIN)
2320 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2321 else if (game_status == GAME_MODE_LEVELS)
2322 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2323 else if (game_status == GAME_MODE_LEVELNR)
2324 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2325 else if (game_status == GAME_MODE_SETUP)
2326 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2327 else if (game_status == GAME_MODE_INFO)
2328 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2329 else if (game_status == GAME_MODE_SCORES)
2330 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2334 if (game_status != GAME_MODE_MAIN)
2335 FadeSkipNextFadeIn();
2337 if (game_status == GAME_MODE_TITLE)
2338 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2339 else if (game_status == GAME_MODE_LEVELS)
2340 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2341 else if (game_status == GAME_MODE_LEVELNR)
2342 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2343 else if (game_status == GAME_MODE_SETUP)
2344 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2345 else if (game_status == GAME_MODE_INFO)
2346 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2347 else if (game_status == GAME_MODE_SCORES)
2348 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2352 if (game_status == GAME_MODE_LEVELS)
2353 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2354 else if (game_status == GAME_MODE_LEVELNR)
2355 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2356 else if (game_status == GAME_MODE_SETUP)
2357 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2358 else if (game_status == GAME_MODE_INFO)
2359 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2360 else if (game_status == GAME_MODE_SCORES)
2361 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2364 case KSYM_Page_Down:
2365 if (game_status == GAME_MODE_LEVELS)
2366 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2367 else if (game_status == GAME_MODE_LEVELNR)
2368 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2369 else if (game_status == GAME_MODE_SETUP)
2370 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2371 else if (game_status == GAME_MODE_INFO)
2372 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2373 else if (game_status == GAME_MODE_SCORES)
2374 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2382 case GAME_MODE_EDITOR:
2383 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2384 HandleLevelEditorKeyInput(key);
2387 case GAME_MODE_PLAYING:
2392 RequestQuitGame(setup.ask_on_escape);
2402 if (key == KSYM_Escape)
2404 SetGameStatus(GAME_MODE_MAIN);
2413 void HandleNoEvent(void)
2415 HandleMouseCursor();
2417 switch (game_status)
2419 case GAME_MODE_PLAYING:
2420 HandleButtonOrFinger(-1, -1, -1);
2425 void HandleEventActions(void)
2427 // if (button_status && game_status != GAME_MODE_PLAYING)
2428 if (button_status && (game_status != GAME_MODE_PLAYING ||
2430 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2432 HandleButton(0, 0, button_status, -button_status);
2439 if (network.enabled)
2442 switch (game_status)
2444 case GAME_MODE_MAIN:
2445 DrawPreviewLevelAnimation();
2448 case GAME_MODE_EDITOR:
2449 HandleLevelEditorIdle();
2457 static void HandleTileCursor(int dx, int dy, int button)
2460 ClearPlayerMouseAction();
2467 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2468 (dx < 0 ? MB_LEFTBUTTON :
2469 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2471 else if (!tile_cursor.moving)
2473 int old_xpos = tile_cursor.xpos;
2474 int old_ypos = tile_cursor.ypos;
2475 int new_xpos = old_xpos;
2476 int new_ypos = old_ypos;
2478 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2479 new_xpos = old_xpos + dx;
2481 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2482 new_ypos = old_ypos + dy;
2484 SetTileCursorTargetXY(new_xpos, new_ypos);
2488 static int HandleJoystickForAllPlayers(void)
2492 boolean no_joysticks_configured = TRUE;
2493 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2494 static byte joy_action_last[MAX_PLAYERS];
2496 for (i = 0; i < MAX_PLAYERS; i++)
2497 if (setup.input[i].use_joystick)
2498 no_joysticks_configured = FALSE;
2500 // if no joysticks configured, map connected joysticks to players
2501 if (no_joysticks_configured)
2502 use_as_joystick_nr = TRUE;
2504 for (i = 0; i < MAX_PLAYERS; i++)
2506 byte joy_action = 0;
2508 joy_action = JoystickExt(i, use_as_joystick_nr);
2509 result |= joy_action;
2511 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2512 joy_action != joy_action_last[i])
2513 stored_player[i].action = joy_action;
2515 joy_action_last[i] = joy_action;
2521 void HandleJoystick(void)
2523 static unsigned int joytest_delay = 0;
2524 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2525 static int joytest_last = 0;
2526 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2527 int delay_value = GADGET_FRAME_DELAY;
2528 int joystick = HandleJoystickForAllPlayers();
2529 int keyboard = key_joystick_mapping;
2530 int joy = (joystick | keyboard);
2531 int joytest = joystick;
2532 int left = joy & JOY_LEFT;
2533 int right = joy & JOY_RIGHT;
2534 int up = joy & JOY_UP;
2535 int down = joy & JOY_DOWN;
2536 int button = joy & JOY_BUTTON;
2537 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2538 int dx = (left ? -1 : right ? 1 : 0);
2539 int dy = (up ? -1 : down ? 1 : 0);
2540 boolean use_delay_value_first = (joytest != joytest_last);
2542 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2544 // do not handle this button event anymore
2548 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2549 anyTextGadgetActive()))
2551 // leave name input in main menu or text input gadget
2552 HandleKey(KSYM_Escape, KEY_PRESSED);
2553 HandleKey(KSYM_Escape, KEY_RELEASED);
2558 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2560 if (game_status == GAME_MODE_PLAYING)
2562 // when playing MM style levels, also use delay for keyboard events
2563 joytest |= keyboard;
2565 // only use first delay value for new events, but not for changed events
2566 use_delay_value_first = (!joytest != !joytest_last);
2568 // only use delay after the initial keyboard event
2572 // for any joystick or keyboard event, enable playfield tile cursor
2573 if (dx || dy || button)
2574 SetTileCursorEnabled(TRUE);
2577 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2579 // delay joystick/keyboard actions if axes/keys continually pressed
2580 newbutton = dx = dy = 0;
2584 // first start with longer delay, then continue with shorter delay
2585 joytest_delay_value =
2586 (use_delay_value_first ? delay_value_first : delay_value);
2589 joytest_last = joytest;
2591 switch (game_status)
2593 case GAME_MODE_TITLE:
2594 case GAME_MODE_MAIN:
2595 case GAME_MODE_LEVELS:
2596 case GAME_MODE_LEVELNR:
2597 case GAME_MODE_SETUP:
2598 case GAME_MODE_INFO:
2599 case GAME_MODE_SCORES:
2601 if (anyTextGadgetActive())
2604 if (game_status == GAME_MODE_TITLE)
2605 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2606 else if (game_status == GAME_MODE_MAIN)
2607 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2608 else if (game_status == GAME_MODE_LEVELS)
2609 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2610 else if (game_status == GAME_MODE_LEVELNR)
2611 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2612 else if (game_status == GAME_MODE_SETUP)
2613 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2614 else if (game_status == GAME_MODE_INFO)
2615 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2616 else if (game_status == GAME_MODE_SCORES)
2617 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2622 case GAME_MODE_PLAYING:
2624 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2625 if (tape.playing || keyboard)
2626 newbutton = ((joy & JOY_BUTTON) != 0);
2629 if (newbutton && game.all_players_gone)
2636 if (tape.recording && tape.pausing && tape.use_key_actions)
2638 if (tape.single_step)
2640 if (joystick & JOY_ACTION)
2641 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2645 if (joystick & JOY_ACTION)
2646 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2650 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2651 HandleTileCursor(dx, dy, button);
2660 void HandleSpecialGameControllerButtons(Event *event)
2665 switch (event->type)
2667 case SDL_CONTROLLERBUTTONDOWN:
2668 key_status = KEY_PRESSED;
2671 case SDL_CONTROLLERBUTTONUP:
2672 key_status = KEY_RELEASED;
2679 switch (event->cbutton.button)
2681 case SDL_CONTROLLER_BUTTON_START:
2685 case SDL_CONTROLLER_BUTTON_BACK:
2693 HandleKey(key, key_status);
2696 void HandleSpecialGameControllerKeys(Key key, int key_status)
2698 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2699 int button = SDL_CONTROLLER_BUTTON_INVALID;
2701 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2702 if (key == KSYM_Rewind)
2703 button = SDL_CONTROLLER_BUTTON_A;
2704 else if (key == KSYM_FastForward || key == KSYM_Menu)
2705 button = SDL_CONTROLLER_BUTTON_B;
2707 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2711 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2712 SDL_CONTROLLERBUTTONUP);
2714 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2715 event.cbutton.button = button;
2716 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2719 HandleJoystickEvent(&event);
2724 boolean DoKeysymAction(int keysym)
2728 Key key = (Key)(-keysym);
2730 HandleKey(key, KEY_PRESSED);
2731 HandleKey(key, KEY_RELEASED);