1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://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 Debug("event:button", "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 Debug("event:motion", "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 Debug("event:wheel", "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 Debug("event:wheel", "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 Debug("event:window", "name: '%s', data1: %ld, data2: %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 SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
702 Key key, byte action)
704 touch_info[pos].touched = (action != JOY_NO_ACTION);
705 touch_info[pos].finger_id = finger_id;
706 touch_info[pos].counter = counter;
707 touch_info[pos].key = key;
708 touch_info[pos].action = action;
711 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
713 int x = event->x * overlay.grid_xsize;
714 int y = event->y * overlay.grid_ysize;
715 int grid_button = overlay.grid_button[x][y];
716 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
717 Key key = GetKeyFromGridButton(grid_button);
718 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
720 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
724 // for any touch input event, enable overlay buttons (if activated)
725 SetOverlayEnabled(TRUE);
727 Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
728 getKeyNameFromKey(key), key_status_name, event->fingerId);
730 if (key_status == KEY_PRESSED)
731 overlay.grid_button_action |= grid_button_action;
733 overlay.grid_button_action &= ~grid_button_action;
735 // check if we already know this touch event's finger id
736 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
738 if (touch_info[i].touched &&
739 touch_info[i].finger_id == event->fingerId)
741 // Debug("event:finger", "MARK 1: %d", i);
747 if (i >= NUM_TOUCH_FINGERS)
749 if (key_status == KEY_PRESSED)
751 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
753 // unknown finger id -- get new, empty slot, if available
754 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
756 if (touch_info[i].counter < oldest_counter)
759 oldest_counter = touch_info[i].counter;
761 // Debug("event:finger", "MARK 2: %d", i);
764 if (!touch_info[i].touched)
766 // Debug("event:finger", "MARK 3: %d", i);
772 if (i >= NUM_TOUCH_FINGERS)
774 // all slots allocated -- use oldest slot
777 // Debug("event:finger", "MARK 4: %d", i);
782 // release of previously unknown key (should not happen)
784 if (key != KSYM_UNDEFINED)
786 HandleKey(key, KEY_RELEASED);
788 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
789 getKeyNameFromKey(key), "KEY_RELEASED", i);
794 if (i < NUM_TOUCH_FINGERS)
796 if (key_status == KEY_PRESSED)
798 if (touch_info[i].key != key)
800 if (touch_info[i].key != KSYM_UNDEFINED)
802 HandleKey(touch_info[i].key, KEY_RELEASED);
804 // undraw previous grid button when moving finger away
805 overlay.grid_button_action &= ~touch_info[i].action;
807 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
808 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
811 if (key != KSYM_UNDEFINED)
813 HandleKey(key, KEY_PRESSED);
815 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
816 getKeyNameFromKey(key), "KEY_PRESSED", i);
820 SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
824 if (touch_info[i].key != KSYM_UNDEFINED)
826 HandleKey(touch_info[i].key, KEY_RELEASED);
828 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
829 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
832 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
837 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
839 static Key motion_key_x = KSYM_UNDEFINED;
840 static Key motion_key_y = KSYM_UNDEFINED;
841 static Key button_key = KSYM_UNDEFINED;
842 static float motion_x1, motion_y1;
843 static float button_x1, button_y1;
844 static SDL_FingerID motion_id = -1;
845 static SDL_FingerID button_id = -1;
846 int move_trigger_distance_percent = setup.touch.move_distance;
847 int drop_trigger_distance_percent = setup.touch.drop_distance;
848 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
849 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
850 float event_x = event->x;
851 float event_y = event->y;
853 if (event->type == EVENT_FINGERPRESS)
855 if (event_x > 1.0 / 3.0)
859 motion_id = event->fingerId;
864 motion_key_x = KSYM_UNDEFINED;
865 motion_key_y = KSYM_UNDEFINED;
867 Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
873 button_id = event->fingerId;
878 button_key = setup.input[0].key.snap;
880 HandleKey(button_key, KEY_PRESSED);
882 Debug("event:finger", "---------- SNAP STARTED ----------");
885 else if (event->type == EVENT_FINGERRELEASE)
887 if (event->fingerId == motion_id)
891 if (motion_key_x != KSYM_UNDEFINED)
892 HandleKey(motion_key_x, KEY_RELEASED);
893 if (motion_key_y != KSYM_UNDEFINED)
894 HandleKey(motion_key_y, KEY_RELEASED);
896 motion_key_x = KSYM_UNDEFINED;
897 motion_key_y = KSYM_UNDEFINED;
899 Debug("event:finger", "---------- MOVE STOPPED ----------");
901 else if (event->fingerId == button_id)
905 if (button_key != KSYM_UNDEFINED)
906 HandleKey(button_key, KEY_RELEASED);
908 button_key = KSYM_UNDEFINED;
910 Debug("event:finger", "---------- SNAP STOPPED ----------");
913 else if (event->type == EVENT_FINGERMOTION)
915 if (event->fingerId == motion_id)
917 float distance_x = ABS(event_x - motion_x1);
918 float distance_y = ABS(event_y - motion_y1);
919 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
920 event_x > motion_x1 ? setup.input[0].key.right :
922 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
923 event_y > motion_y1 ? setup.input[0].key.down :
926 if (distance_x < move_trigger_distance / 2 ||
927 distance_x < distance_y)
928 new_motion_key_x = KSYM_UNDEFINED;
930 if (distance_y < move_trigger_distance / 2 ||
931 distance_y < distance_x)
932 new_motion_key_y = KSYM_UNDEFINED;
934 if (distance_x > move_trigger_distance ||
935 distance_y > move_trigger_distance)
937 if (new_motion_key_x != motion_key_x)
939 if (motion_key_x != KSYM_UNDEFINED)
940 HandleKey(motion_key_x, KEY_RELEASED);
941 if (new_motion_key_x != KSYM_UNDEFINED)
942 HandleKey(new_motion_key_x, KEY_PRESSED);
945 if (new_motion_key_y != motion_key_y)
947 if (motion_key_y != KSYM_UNDEFINED)
948 HandleKey(motion_key_y, KEY_RELEASED);
949 if (new_motion_key_y != KSYM_UNDEFINED)
950 HandleKey(new_motion_key_y, KEY_PRESSED);
956 motion_key_x = new_motion_key_x;
957 motion_key_y = new_motion_key_y;
959 Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
962 else if (event->fingerId == button_id)
964 float distance_x = ABS(event_x - button_x1);
965 float distance_y = ABS(event_y - button_y1);
967 if (distance_x < drop_trigger_distance / 2 &&
968 distance_y > drop_trigger_distance)
970 if (button_key == setup.input[0].key.snap)
971 HandleKey(button_key, KEY_RELEASED);
976 button_key = setup.input[0].key.drop;
978 HandleKey(button_key, KEY_PRESSED);
980 Debug("event:finger", "---------- DROP STARTED ----------");
986 void HandleFingerEvent(FingerEvent *event)
988 #if DEBUG_EVENTS_FINGER
989 Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
990 event->type == EVENT_FINGERPRESS ? "pressed" :
991 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
995 event->dx, event->dy,
999 runtime.uses_touch_device = TRUE;
1001 if (game_status != GAME_MODE_PLAYING)
1004 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1006 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1007 local_player->mouse_action.button_hint =
1008 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1009 event->x < 0.5 ? MB_LEFTBUTTON :
1010 event->x > 0.5 ? MB_RIGHTBUTTON :
1016 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1017 HandleFingerEvent_VirtualButtons(event);
1018 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1019 HandleFingerEvent_WipeGestures(event);
1022 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1024 static int old_mx = 0, old_my = 0;
1025 static int last_button = MB_LEFTBUTTON;
1026 static boolean touched = FALSE;
1027 static boolean tapped = FALSE;
1029 // screen tile was tapped (but finger not touching the screen anymore)
1030 // (this point will also be reached without receiving a touch event)
1031 if (tapped && !touched)
1033 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1038 // stop here if this function was not triggered by a touch event
1042 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1044 // finger started touching the screen
1054 ClearPlayerMouseAction();
1056 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1059 else if (button == MB_RELEASED && touched)
1061 // finger stopped touching the screen
1066 SetPlayerMouseAction(old_mx, old_my, last_button);
1068 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1070 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1075 // finger moved while touching the screen
1077 int old_x = getLevelFromScreenX(old_mx);
1078 int old_y = getLevelFromScreenY(old_my);
1079 int new_x = getLevelFromScreenX(mx);
1080 int new_y = getLevelFromScreenY(my);
1082 if (new_x != old_x || new_y != old_y)
1087 // finger moved left or right from (horizontal) starting position
1089 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1091 SetPlayerMouseAction(old_mx, old_my, button_nr);
1093 last_button = button_nr;
1095 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1099 // finger stays at or returned to (horizontal) starting position
1101 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1103 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1108 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1110 static int old_mx = 0, old_my = 0;
1111 static int last_button = MB_LEFTBUTTON;
1112 static boolean touched = FALSE;
1113 static boolean tapped = FALSE;
1115 // screen tile was tapped (but finger not touching the screen anymore)
1116 // (this point will also be reached without receiving a touch event)
1117 if (tapped && !touched)
1119 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1124 // stop here if this function was not triggered by a touch event
1128 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1130 // finger started touching the screen
1140 ClearPlayerMouseAction();
1142 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1145 else if (button == MB_RELEASED && touched)
1147 // finger stopped touching the screen
1152 SetPlayerMouseAction(old_mx, old_my, last_button);
1154 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1156 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1161 // finger moved while touching the screen
1163 int old_x = getLevelFromScreenX(old_mx);
1164 int old_y = getLevelFromScreenY(old_my);
1165 int new_x = getLevelFromScreenX(mx);
1166 int new_y = getLevelFromScreenY(my);
1168 if (new_x != old_x || new_y != old_y)
1170 // finger moved away from starting position
1172 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1174 // quickly alternate between clicking and releasing for maximum speed
1175 if (FrameCounter % 2 == 0)
1176 button_nr = MB_RELEASED;
1178 SetPlayerMouseAction(old_mx, old_my, button_nr);
1181 last_button = button_nr;
1185 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1189 // finger stays at or returned to starting position
1191 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1193 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1198 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1200 static int old_mx = 0, old_my = 0;
1201 static Key motion_key_x = KSYM_UNDEFINED;
1202 static Key motion_key_y = KSYM_UNDEFINED;
1203 static boolean touched = FALSE;
1204 static boolean started_on_player = FALSE;
1205 static boolean player_is_dropping = FALSE;
1206 static int player_drop_count = 0;
1207 static int last_player_x = -1;
1208 static int last_player_y = -1;
1210 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1219 started_on_player = FALSE;
1220 player_is_dropping = FALSE;
1221 player_drop_count = 0;
1225 motion_key_x = KSYM_UNDEFINED;
1226 motion_key_y = KSYM_UNDEFINED;
1228 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1231 else if (button == MB_RELEASED && touched)
1238 if (motion_key_x != KSYM_UNDEFINED)
1239 HandleKey(motion_key_x, KEY_RELEASED);
1240 if (motion_key_y != KSYM_UNDEFINED)
1241 HandleKey(motion_key_y, KEY_RELEASED);
1243 if (started_on_player)
1245 if (player_is_dropping)
1247 Debug("event:finger", "---------- DROP STOPPED ----------");
1249 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1253 Debug("event:finger", "---------- SNAP STOPPED ----------");
1255 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1259 motion_key_x = KSYM_UNDEFINED;
1260 motion_key_y = KSYM_UNDEFINED;
1262 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1267 int src_x = local_player->jx;
1268 int src_y = local_player->jy;
1269 int dst_x = getLevelFromScreenX(old_mx);
1270 int dst_y = getLevelFromScreenY(old_my);
1271 int dx = dst_x - src_x;
1272 int dy = dst_y - src_y;
1273 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1274 dx > 0 ? setup.input[0].key.right :
1276 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1277 dy > 0 ? setup.input[0].key.down :
1280 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1281 (last_player_x != local_player->jx ||
1282 last_player_y != local_player->jy))
1284 // in case of asymmetric diagonal movement, use "preferred" direction
1286 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1288 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1289 game_em.ply[0]->last_move_dir = last_move_dir;
1291 local_player->last_move_dir = last_move_dir;
1293 // (required to prevent accidentally forcing direction for next movement)
1294 last_player_x = local_player->jx;
1295 last_player_y = local_player->jy;
1298 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1300 started_on_player = TRUE;
1301 player_drop_count = getPlayerInventorySize(0);
1302 player_is_dropping = (player_drop_count > 0);
1304 if (player_is_dropping)
1306 Debug("event:finger", "---------- DROP STARTED ----------");
1308 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1312 Debug("event:finger", "---------- SNAP STARTED ----------");
1314 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1317 else if (dx != 0 || dy != 0)
1319 if (player_is_dropping &&
1320 player_drop_count == getPlayerInventorySize(0))
1322 Debug("event:finger", "---------- DROP -> SNAP ----------");
1324 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1325 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1327 player_is_dropping = FALSE;
1331 if (new_motion_key_x != motion_key_x)
1333 Debug("event:finger", "---------- %s %s ----------",
1334 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1335 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1337 if (motion_key_x != KSYM_UNDEFINED)
1338 HandleKey(motion_key_x, KEY_RELEASED);
1339 if (new_motion_key_x != KSYM_UNDEFINED)
1340 HandleKey(new_motion_key_x, KEY_PRESSED);
1343 if (new_motion_key_y != motion_key_y)
1345 Debug("event:finger", "---------- %s %s ----------",
1346 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1347 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1349 if (motion_key_y != KSYM_UNDEFINED)
1350 HandleKey(motion_key_y, KEY_RELEASED);
1351 if (new_motion_key_y != KSYM_UNDEFINED)
1352 HandleKey(new_motion_key_y, KEY_PRESSED);
1355 motion_key_x = new_motion_key_x;
1356 motion_key_y = new_motion_key_y;
1360 static void HandleButtonOrFinger(int mx, int my, int button)
1362 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1364 if (game_status != GAME_MODE_PLAYING)
1367 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1369 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1370 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1371 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1372 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1373 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1374 SetPlayerMouseAction(mx, my, button); // special case
1378 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1379 HandleButtonOrFinger_FollowFinger(mx, my, button);
1380 else if (game.use_mouse_actions && valid_mouse_event)
1381 SetPlayerMouseAction(mx, my, button);
1385 static boolean checkTextInputKey(Key key)
1387 // when playing, only handle raw key events and ignore text input
1388 if (game_status == GAME_MODE_PLAYING)
1391 // if Shift or right Alt key is pressed, handle key as text input
1392 if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1395 // ignore raw keys as text input when not in text input mode
1396 if (KSYM_RAW(key) && !textinput_status)
1399 // else handle all printable keys as text input
1400 return KSYM_PRINTABLE(key);
1403 void HandleTextEvent(TextEvent *event)
1405 char *text = event->text;
1406 Key key = getKeyFromKeyName(text);
1408 #if DEBUG_EVENTS_TEXT
1409 Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1412 text[0], (int)(text[0]),
1414 getKeyNameFromKey(key),
1418 if (checkTextInputKey(key))
1420 // process printable keys (with uppercase etc.) in text input mode
1421 HandleKey(key, KEY_PRESSED);
1422 HandleKey(key, KEY_RELEASED);
1426 void HandlePauseResumeEvent(PauseResumeEvent *event)
1428 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1432 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1438 void HandleKeyEvent(KeyEvent *event)
1440 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1441 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1442 Key key = GetEventKey(event, with_modifiers);
1443 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1445 #if DEBUG_EVENTS_KEY
1446 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1447 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1448 event->keysym.scancode,
1453 getKeyNameFromKey(key));
1456 #if defined(PLATFORM_ANDROID)
1457 if (key == KSYM_Back)
1459 // always map the "back" button to the "escape" key on Android devices
1462 else if (key == KSYM_Menu)
1464 // the "menu" button can be used to toggle displaying virtual buttons
1465 if (key_status == KEY_PRESSED)
1466 SetOverlayEnabled(!GetOverlayEnabled());
1470 // for any other "real" key event, disable virtual buttons
1471 SetOverlayEnabled(FALSE);
1473 // for any other "real" key event, disable overlay touch buttons
1474 runtime.uses_touch_device = FALSE;
1478 HandleKeyModState(keymod, key_status);
1480 // process all keys if not in text input mode or if non-printable keys
1481 if (!checkTextInputKey(key))
1482 HandleKey(key, key_status);
1485 static int HandleDropFileEvent(char *filename)
1487 Debug("event:dropfile", "filename == '%s'", filename);
1489 // check and extract dropped zip files into correct user data directory
1490 if (!strSuffixLower(filename, ".zip"))
1492 Warn("file '%s' not supported", filename);
1494 return TREE_TYPE_UNDEFINED;
1497 TreeInfo *tree_node = NULL;
1498 int tree_type = GetZipFileTreeType(filename);
1499 char *directory = TREE_USERDIR(tree_type);
1501 if (directory == NULL)
1503 Warn("zip file '%s' has invalid content!", filename);
1505 return TREE_TYPE_UNDEFINED;
1508 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1509 game_status == GAME_MODE_LEVELS &&
1510 leveldir_current->node_parent != NULL)
1512 // extract new level set next to currently selected level set
1513 tree_node = leveldir_current;
1515 // get parent directory of currently selected level set directory
1516 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1518 // use private level directory instead of top-level package level directory
1519 if (strPrefix(directory, options.level_directory) &&
1520 strEqual(leveldir_current->node_parent->fullpath, "."))
1521 directory = getUserLevelDir(NULL);
1524 // extract level or artwork set from zip file to target directory
1525 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1527 if (top_dir == NULL)
1529 // error message already issued by "ExtractZipFileIntoDirectory()"
1531 return TREE_TYPE_UNDEFINED;
1534 // add extracted level or artwork set to tree info structure
1535 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1537 // update menu screen (and possibly change current level set)
1538 DrawScreenAfterAddingSet(top_dir, tree_type);
1543 static void HandleDropTextEvent(char *text)
1545 Debug("event:droptext", "text == '%s'", text);
1548 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1549 int num_artwork_sets_succeeded,
1550 int num_files_failed)
1552 // only show request dialog if no other request dialog already active
1553 if (game.request_active)
1556 // this case can happen with drag-and-drop with older SDL versions
1557 if (num_level_sets_succeeded == 0 &&
1558 num_artwork_sets_succeeded == 0 &&
1559 num_files_failed == 0)
1564 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1566 char message_part1[50];
1568 sprintf(message_part1, "New %s set%s added",
1569 (num_artwork_sets_succeeded == 0 ? "level" :
1570 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1571 (num_level_sets_succeeded +
1572 num_artwork_sets_succeeded > 1 ? "s" : ""));
1574 if (num_files_failed > 0)
1575 sprintf(message, "%s, but %d dropped file%s failed!",
1576 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1578 sprintf(message, "%s!", message_part1);
1580 else if (num_files_failed > 0)
1582 sprintf(message, "Failed to process dropped file%s!",
1583 num_files_failed > 1 ? "s" : "");
1586 Request(message, REQ_CONFIRM);
1589 void HandleDropEvent(Event *event)
1591 static boolean confirm_on_drop_complete = FALSE;
1592 static int num_level_sets_succeeded = 0;
1593 static int num_artwork_sets_succeeded = 0;
1594 static int num_files_failed = 0;
1596 switch (event->type)
1600 confirm_on_drop_complete = TRUE;
1601 num_level_sets_succeeded = 0;
1602 num_artwork_sets_succeeded = 0;
1603 num_files_failed = 0;
1610 int tree_type = HandleDropFileEvent(event->drop.file);
1612 if (tree_type == TREE_TYPE_LEVEL_DIR)
1613 num_level_sets_succeeded++;
1614 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1615 tree_type == TREE_TYPE_SOUNDS_DIR ||
1616 tree_type == TREE_TYPE_MUSIC_DIR)
1617 num_artwork_sets_succeeded++;
1621 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1622 if (!confirm_on_drop_complete)
1624 // process all remaining events, including further SDL_DROPFILE events
1627 HandleDropCompleteEvent(num_level_sets_succeeded,
1628 num_artwork_sets_succeeded,
1631 num_level_sets_succeeded = 0;
1632 num_artwork_sets_succeeded = 0;
1633 num_files_failed = 0;
1641 HandleDropTextEvent(event->drop.file);
1646 case SDL_DROPCOMPLETE:
1648 HandleDropCompleteEvent(num_level_sets_succeeded,
1649 num_artwork_sets_succeeded,
1656 if (event->drop.file != NULL)
1657 SDL_free(event->drop.file);
1660 void HandleUserEvent(UserEvent *event)
1662 switch (event->code)
1664 case USEREVENT_ANIM_DELAY_ACTION:
1665 case USEREVENT_ANIM_EVENT_ACTION:
1666 // execute action functions until matching action was found
1667 if (DoKeysymAction(event->value1) ||
1668 DoGadgetAction(event->value1) ||
1669 DoScreenAction(event->value1))
1678 void HandleButton(int mx, int my, int button, int button_nr)
1680 static int old_mx = 0, old_my = 0;
1681 boolean button_hold = FALSE;
1682 boolean handle_gadgets = TRUE;
1688 button_nr = -button_nr;
1697 #if defined(PLATFORM_ANDROID)
1698 // when playing, only handle gadgets when using "follow finger" controls
1699 // or when using touch controls in combination with the MM game engine
1700 // or when using gadgets that do not overlap with virtual buttons
1702 (game_status != GAME_MODE_PLAYING ||
1703 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1704 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1705 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1706 !CheckVirtualButtonPressed(mx, my, button)));
1708 // always recognize potentially releasing already pressed gadgets
1709 if (button == MB_RELEASED)
1710 handle_gadgets = TRUE;
1712 // always recognize pressing or releasing overlay touch buttons
1713 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1714 handle_gadgets = TRUE;
1717 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1719 // do not handle this button event anymore
1720 return; // force mouse event not to be handled at all
1723 if (handle_gadgets && HandleGadgets(mx, my, button))
1725 // do not handle this button event anymore
1726 mx = my = -32; // force mouse event to be outside screen tiles
1729 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1732 // do not use scroll wheel button events for anything other than gadgets
1733 if (IS_WHEEL_BUTTON(button_nr))
1736 switch (game_status)
1738 case GAME_MODE_TITLE:
1739 HandleTitleScreen(mx, my, 0, 0, button);
1742 case GAME_MODE_MAIN:
1743 HandleMainMenu(mx, my, 0, 0, button);
1746 case GAME_MODE_PSEUDO_TYPENAME:
1747 HandleTypeName(0, KSYM_Return);
1750 case GAME_MODE_LEVELS:
1751 HandleChooseLevelSet(mx, my, 0, 0, button);
1754 case GAME_MODE_LEVELNR:
1755 HandleChooseLevelNr(mx, my, 0, 0, button);
1758 case GAME_MODE_SCORES:
1759 HandleHallOfFame(0, 0, 0, 0, button);
1762 case GAME_MODE_EDITOR:
1763 HandleLevelEditorIdle();
1766 case GAME_MODE_INFO:
1767 HandleInfoScreen(mx, my, 0, 0, button);
1770 case GAME_MODE_SETUP:
1771 HandleSetupScreen(mx, my, 0, 0, button);
1774 case GAME_MODE_PLAYING:
1775 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1776 HandleButtonOrFinger(mx, my, button);
1778 SetPlayerMouseAction(mx, my, button);
1781 if (button == MB_PRESSED && !motion_status && !button_hold &&
1782 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1783 DumpTileFromScreen(mx, my);
1793 #define MAX_CHEAT_INPUT_LEN 32
1795 static void HandleKeysSpecial(Key key)
1797 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1798 char letter = getCharFromKey(key);
1799 int cheat_input_len = strlen(cheat_input);
1805 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1807 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1808 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1810 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1813 cheat_input[cheat_input_len++] = letter;
1814 cheat_input[cheat_input_len] = '\0';
1816 #if DEBUG_EVENTS_KEY
1817 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1820 if (game_status == GAME_MODE_MAIN)
1822 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1823 strSuffix(cheat_input, ":ist"))
1825 InsertSolutionTape();
1827 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1828 strSuffix(cheat_input, ":pst"))
1832 else if (strSuffix(cheat_input, ":reload-graphics") ||
1833 strSuffix(cheat_input, ":rg"))
1835 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1838 else if (strSuffix(cheat_input, ":reload-sounds") ||
1839 strSuffix(cheat_input, ":rs"))
1841 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1844 else if (strSuffix(cheat_input, ":reload-music") ||
1845 strSuffix(cheat_input, ":rm"))
1847 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1850 else if (strSuffix(cheat_input, ":reload-artwork") ||
1851 strSuffix(cheat_input, ":ra"))
1853 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1854 1 << ARTWORK_TYPE_SOUNDS |
1855 1 << ARTWORK_TYPE_MUSIC);
1858 else if (strSuffix(cheat_input, ":dump-level") ||
1859 strSuffix(cheat_input, ":dl"))
1863 else if (strSuffix(cheat_input, ":dump-tape") ||
1864 strSuffix(cheat_input, ":dt"))
1868 else if (strSuffix(cheat_input, ":undo-tape") ||
1869 strSuffix(cheat_input, ":ut"))
1873 else if (strSuffix(cheat_input, ":fix-tape") ||
1874 strSuffix(cheat_input, ":ft"))
1876 FixTape_ForceSinglePlayer();
1878 else if (strSuffix(cheat_input, ":save-native-level") ||
1879 strSuffix(cheat_input, ":snl"))
1881 SaveNativeLevel(&level);
1883 else if (strSuffix(cheat_input, ":frames-per-second") ||
1884 strSuffix(cheat_input, ":fps"))
1886 global.show_frames_per_second = !global.show_frames_per_second;
1889 else if (game_status == GAME_MODE_PLAYING)
1892 if (strSuffix(cheat_input, ".q"))
1893 DEBUG_SetMaximumDynamite();
1896 else if (game_status == GAME_MODE_EDITOR)
1898 if (strSuffix(cheat_input, ":dump-brush") ||
1899 strSuffix(cheat_input, ":DB"))
1903 else if (strSuffix(cheat_input, ":DDB"))
1908 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1910 if (letter == 'x') // copy brush to clipboard (small size)
1912 CopyBrushToClipboard_Small();
1914 else if (letter == 'c') // copy brush to clipboard (normal size)
1916 CopyBrushToClipboard();
1918 else if (letter == 'v') // paste brush from Clipboard
1920 CopyClipboardToBrush();
1922 else if (letter == 'z') // undo or redo last operation
1924 if (GetKeyModState() & KMOD_Shift)
1925 RedoLevelEditorOperation();
1927 UndoLevelEditorOperation();
1932 // special key shortcuts for all game modes
1933 if (strSuffix(cheat_input, ":dump-event-actions") ||
1934 strSuffix(cheat_input, ":dea") ||
1935 strSuffix(cheat_input, ":DEA"))
1937 DumpGadgetIdentifiers();
1938 DumpScreenIdentifiers();
1942 boolean HandleKeysDebug(Key key, int key_status)
1947 if (key_status != KEY_PRESSED)
1950 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1952 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1954 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1956 if (key == setup.debug.frame_delay_key[i] &&
1957 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1959 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1960 setup.debug.frame_delay[i] : setup.game_frame_delay);
1962 if (!setup.debug.frame_delay_game_only)
1963 MenuFrameDelay = GameFrameDelay;
1965 SetVideoFrameDelay(GameFrameDelay);
1967 if (GameFrameDelay > ONE_SECOND_DELAY)
1968 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
1969 else if (GameFrameDelay != 0)
1970 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
1971 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1972 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1974 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
1981 if (game_status == GAME_MODE_PLAYING)
1985 options.debug = !options.debug;
1987 Debug("event:key:debug", "debug mode %s",
1988 (options.debug ? "enabled" : "disabled"));
1992 else if (key == KSYM_v)
1994 Debug("event:key:debug", "currently using game engine version %d",
1995 game.engine_version);
2005 void HandleKey(Key key, int key_status)
2007 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2008 static boolean ignore_repeated_key = FALSE;
2009 static struct SetupKeyboardInfo ski;
2010 static struct SetupShortcutInfo ssi;
2019 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2020 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2021 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2022 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2023 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2024 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2029 if (HandleKeysDebug(key, key_status))
2030 return; // do not handle already processed keys again
2032 // map special keys (media keys / remote control buttons) to default keys
2033 if (key == KSYM_PlayPause)
2035 else if (key == KSYM_Select)
2038 HandleSpecialGameControllerKeys(key, key_status);
2040 if (game_status == GAME_MODE_PLAYING)
2042 // only needed for single-step tape recording mode
2043 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2046 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2048 byte key_action = 0;
2049 byte key_snap_action = 0;
2051 if (setup.input[pnr].use_joystick)
2054 ski = setup.input[pnr].key;
2056 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2057 if (key == *key_info[i].key_custom)
2058 key_action |= key_info[i].action;
2060 // use combined snap+direction keys for the first player only
2063 ssi = setup.shortcut;
2065 // also remember normal snap key when handling snap+direction keys
2066 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2068 for (i = 0; i < NUM_DIRECTIONS; i++)
2070 if (key == *key_info[i].key_snap)
2072 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2073 key_snap_action |= key_info[i].action;
2078 if (key_status == KEY_PRESSED)
2080 stored_player[pnr].action |= key_action;
2081 stored_player[pnr].snap_action |= key_snap_action;
2085 stored_player[pnr].action &= ~key_action;
2086 stored_player[pnr].snap_action &= ~key_snap_action;
2089 // restore snap action if one of several pressed snap keys was released
2090 if (stored_player[pnr].snap_action)
2091 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2093 if (tape.recording && tape.pausing && tape.use_key_actions)
2095 if (tape.single_step)
2097 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2099 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2101 // if snap key already pressed, keep pause mode when releasing
2102 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2103 has_snapped[pnr] = TRUE;
2105 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2107 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2109 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2110 getRedDiskReleaseFlag_SP() == 0)
2112 // add a single inactive frame before dropping starts
2113 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2114 stored_player[pnr].force_dropping = TRUE;
2117 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2119 // if snap key was pressed without direction, leave pause mode
2120 if (!has_snapped[pnr])
2121 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2123 has_snapped[pnr] = FALSE;
2128 // prevent key release events from un-pausing a paused game
2129 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2130 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2134 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2135 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2141 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2142 if (key == key_info[i].key_default)
2143 joy |= key_info[i].action;
2148 if (key_status == KEY_PRESSED)
2149 key_joystick_mapping |= joy;
2151 key_joystick_mapping &= ~joy;
2156 if (game_status != GAME_MODE_PLAYING)
2157 key_joystick_mapping = 0;
2159 if (key_status == KEY_RELEASED)
2161 // reset flag to ignore repeated "key pressed" events after key release
2162 ignore_repeated_key = FALSE;
2167 if ((key == KSYM_F11 ||
2168 ((key == KSYM_Return ||
2169 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2170 video.fullscreen_available &&
2171 !ignore_repeated_key)
2173 setup.fullscreen = !setup.fullscreen;
2175 ToggleFullscreenIfNeeded();
2177 if (game_status == GAME_MODE_SETUP)
2178 RedrawSetupScreenAfterFullscreenToggle();
2180 UpdateMousePosition();
2182 // set flag to ignore repeated "key pressed" events
2183 ignore_repeated_key = TRUE;
2188 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2189 key == KSYM_minus || key == KSYM_KP_Subtract ||
2190 key == KSYM_plus || key == KSYM_KP_Add ||
2191 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2192 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2193 video.window_scaling_available &&
2194 !video.fullscreen_enabled)
2196 if (key == KSYM_0 || key == KSYM_KP_0)
2197 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2198 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2199 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2201 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2203 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2204 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2205 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2206 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2208 ChangeWindowScalingIfNeeded();
2210 if (game_status == GAME_MODE_SETUP)
2211 RedrawSetupScreenAfterFullscreenToggle();
2213 UpdateMousePosition();
2218 // some key events are handled like clicks for global animations
2219 boolean click = (key == KSYM_space ||
2220 key == KSYM_Return ||
2221 key == KSYM_Escape);
2223 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2225 // do not handle this key event anymore
2226 if (key != KSYM_Escape) // always allow ESC key to be handled
2230 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2231 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2238 if (game_status == GAME_MODE_MAIN &&
2239 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2241 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2246 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2248 if (key == setup.shortcut.save_game)
2250 else if (key == setup.shortcut.load_game)
2252 else if (key == setup.shortcut.toggle_pause)
2253 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2255 HandleTapeButtonKeys(key);
2256 HandleSoundButtonKeys(key);
2259 if (game_status == GAME_MODE_PLAYING && !network_playing)
2261 int centered_player_nr_next = -999;
2263 if (key == setup.shortcut.focus_player_all)
2264 centered_player_nr_next = -1;
2266 for (i = 0; i < MAX_PLAYERS; i++)
2267 if (key == setup.shortcut.focus_player[i])
2268 centered_player_nr_next = i;
2270 if (centered_player_nr_next != -999)
2272 game.centered_player_nr_next = centered_player_nr_next;
2273 game.set_centered_player = TRUE;
2277 tape.centered_player_nr_next = game.centered_player_nr_next;
2278 tape.set_centered_player = TRUE;
2283 HandleKeysSpecial(key);
2285 if (HandleGadgetsKeyInput(key))
2286 return; // do not handle already processed keys again
2288 switch (game_status)
2290 case GAME_MODE_PSEUDO_TYPENAME:
2291 HandleTypeName(0, key);
2294 case GAME_MODE_TITLE:
2295 case GAME_MODE_MAIN:
2296 case GAME_MODE_LEVELS:
2297 case GAME_MODE_LEVELNR:
2298 case GAME_MODE_SETUP:
2299 case GAME_MODE_INFO:
2300 case GAME_MODE_SCORES:
2302 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2309 if (game_status == GAME_MODE_TITLE)
2310 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2311 else if (game_status == GAME_MODE_MAIN)
2312 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2313 else if (game_status == GAME_MODE_LEVELS)
2314 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2315 else if (game_status == GAME_MODE_LEVELNR)
2316 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2317 else if (game_status == GAME_MODE_SETUP)
2318 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2319 else if (game_status == GAME_MODE_INFO)
2320 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2321 else if (game_status == GAME_MODE_SCORES)
2322 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2326 if (game_status != GAME_MODE_MAIN)
2327 FadeSkipNextFadeIn();
2329 if (game_status == GAME_MODE_TITLE)
2330 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2331 else if (game_status == GAME_MODE_LEVELS)
2332 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2333 else if (game_status == GAME_MODE_LEVELNR)
2334 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2335 else if (game_status == GAME_MODE_SETUP)
2336 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2337 else if (game_status == GAME_MODE_INFO)
2338 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2339 else if (game_status == GAME_MODE_SCORES)
2340 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2344 if (game_status == GAME_MODE_LEVELS)
2345 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2346 else if (game_status == GAME_MODE_LEVELNR)
2347 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2348 else if (game_status == GAME_MODE_SETUP)
2349 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2350 else if (game_status == GAME_MODE_INFO)
2351 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2352 else if (game_status == GAME_MODE_SCORES)
2353 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2356 case KSYM_Page_Down:
2357 if (game_status == GAME_MODE_LEVELS)
2358 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2359 else if (game_status == GAME_MODE_LEVELNR)
2360 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2361 else if (game_status == GAME_MODE_SETUP)
2362 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2363 else if (game_status == GAME_MODE_INFO)
2364 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2365 else if (game_status == GAME_MODE_SCORES)
2366 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2374 case GAME_MODE_EDITOR:
2375 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2376 HandleLevelEditorKeyInput(key);
2379 case GAME_MODE_PLAYING:
2384 RequestQuitGame(setup.ask_on_escape);
2394 if (key == KSYM_Escape)
2396 SetGameStatus(GAME_MODE_MAIN);
2405 void HandleNoEvent(void)
2407 HandleMouseCursor();
2409 switch (game_status)
2411 case GAME_MODE_PLAYING:
2412 HandleButtonOrFinger(-1, -1, -1);
2417 void HandleEventActions(void)
2419 // if (button_status && game_status != GAME_MODE_PLAYING)
2420 if (button_status && (game_status != GAME_MODE_PLAYING ||
2422 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2424 HandleButton(0, 0, button_status, -button_status);
2431 if (network.enabled)
2434 switch (game_status)
2436 case GAME_MODE_MAIN:
2437 DrawPreviewLevelAnimation();
2440 case GAME_MODE_EDITOR:
2441 HandleLevelEditorIdle();
2449 static void HandleTileCursor(int dx, int dy, int button)
2452 ClearPlayerMouseAction();
2459 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2460 (dx < 0 ? MB_LEFTBUTTON :
2461 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2463 else if (!tile_cursor.moving)
2465 int old_xpos = tile_cursor.xpos;
2466 int old_ypos = tile_cursor.ypos;
2467 int new_xpos = old_xpos;
2468 int new_ypos = old_ypos;
2470 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2471 new_xpos = old_xpos + dx;
2473 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2474 new_ypos = old_ypos + dy;
2476 SetTileCursorTargetXY(new_xpos, new_ypos);
2480 static int HandleJoystickForAllPlayers(void)
2484 boolean no_joysticks_configured = TRUE;
2485 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2486 static byte joy_action_last[MAX_PLAYERS];
2488 for (i = 0; i < MAX_PLAYERS; i++)
2489 if (setup.input[i].use_joystick)
2490 no_joysticks_configured = FALSE;
2492 // if no joysticks configured, map connected joysticks to players
2493 if (no_joysticks_configured)
2494 use_as_joystick_nr = TRUE;
2496 for (i = 0; i < MAX_PLAYERS; i++)
2498 byte joy_action = 0;
2500 joy_action = JoystickExt(i, use_as_joystick_nr);
2501 result |= joy_action;
2503 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2504 joy_action != joy_action_last[i])
2505 stored_player[i].action = joy_action;
2507 joy_action_last[i] = joy_action;
2513 void HandleJoystick(void)
2515 static unsigned int joytest_delay = 0;
2516 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2517 static int joytest_last = 0;
2518 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2519 int delay_value = GADGET_FRAME_DELAY;
2520 int joystick = HandleJoystickForAllPlayers();
2521 int keyboard = key_joystick_mapping;
2522 int joy = (joystick | keyboard);
2523 int joytest = joystick;
2524 int left = joy & JOY_LEFT;
2525 int right = joy & JOY_RIGHT;
2526 int up = joy & JOY_UP;
2527 int down = joy & JOY_DOWN;
2528 int button = joy & JOY_BUTTON;
2529 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2530 int dx = (left ? -1 : right ? 1 : 0);
2531 int dy = (up ? -1 : down ? 1 : 0);
2532 boolean use_delay_value_first = (joytest != joytest_last);
2534 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2536 // do not handle this button event anymore
2540 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2541 anyTextGadgetActive()))
2543 // leave name input in main menu or text input gadget
2544 HandleKey(KSYM_Escape, KEY_PRESSED);
2545 HandleKey(KSYM_Escape, KEY_RELEASED);
2550 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2552 if (game_status == GAME_MODE_PLAYING)
2554 // when playing MM style levels, also use delay for keyboard events
2555 joytest |= keyboard;
2557 // only use first delay value for new events, but not for changed events
2558 use_delay_value_first = (!joytest != !joytest_last);
2560 // only use delay after the initial keyboard event
2564 // for any joystick or keyboard event, enable playfield tile cursor
2565 if (dx || dy || button)
2566 SetTileCursorEnabled(TRUE);
2569 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2571 // delay joystick/keyboard actions if axes/keys continually pressed
2572 newbutton = dx = dy = 0;
2576 // first start with longer delay, then continue with shorter delay
2577 joytest_delay_value =
2578 (use_delay_value_first ? delay_value_first : delay_value);
2581 joytest_last = joytest;
2583 switch (game_status)
2585 case GAME_MODE_TITLE:
2586 case GAME_MODE_MAIN:
2587 case GAME_MODE_LEVELS:
2588 case GAME_MODE_LEVELNR:
2589 case GAME_MODE_SETUP:
2590 case GAME_MODE_INFO:
2591 case GAME_MODE_SCORES:
2593 if (anyTextGadgetActive())
2596 if (game_status == GAME_MODE_TITLE)
2597 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2598 else if (game_status == GAME_MODE_MAIN)
2599 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2600 else if (game_status == GAME_MODE_LEVELS)
2601 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2602 else if (game_status == GAME_MODE_LEVELNR)
2603 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2604 else if (game_status == GAME_MODE_SETUP)
2605 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2606 else if (game_status == GAME_MODE_INFO)
2607 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2608 else if (game_status == GAME_MODE_SCORES)
2609 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2614 case GAME_MODE_PLAYING:
2616 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2617 if (tape.playing || keyboard)
2618 newbutton = ((joy & JOY_BUTTON) != 0);
2621 if (newbutton && game.all_players_gone)
2628 if (tape.recording && tape.pausing && tape.use_key_actions)
2630 if (tape.single_step)
2632 if (joystick & JOY_ACTION)
2633 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2637 if (joystick & JOY_ACTION)
2638 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2642 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2643 HandleTileCursor(dx, dy, button);
2652 void HandleSpecialGameControllerButtons(Event *event)
2657 switch (event->type)
2659 case SDL_CONTROLLERBUTTONDOWN:
2660 key_status = KEY_PRESSED;
2663 case SDL_CONTROLLERBUTTONUP:
2664 key_status = KEY_RELEASED;
2671 switch (event->cbutton.button)
2673 case SDL_CONTROLLER_BUTTON_START:
2677 case SDL_CONTROLLER_BUTTON_BACK:
2685 HandleKey(key, key_status);
2688 void HandleSpecialGameControllerKeys(Key key, int key_status)
2690 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2691 int button = SDL_CONTROLLER_BUTTON_INVALID;
2693 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2694 if (key == KSYM_Rewind)
2695 button = SDL_CONTROLLER_BUTTON_A;
2696 else if (key == KSYM_FastForward || key == KSYM_Menu)
2697 button = SDL_CONTROLLER_BUTTON_B;
2699 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2703 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2704 SDL_CONTROLLERBUTTONUP);
2706 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2707 event.cbutton.button = button;
2708 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2711 HandleJoystickEvent(&event);
2716 boolean DoKeysymAction(int keysym)
2720 Key key = (Key)(-keysym);
2722 HandleKey(key, KEY_PRESSED);
2723 HandleKey(key, KEY_RELEASED);