1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
28 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_WHEEL (DEBUG_EVENTS * 1)
31 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
32 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
33 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
34 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
42 static boolean virtual_button_pressed = FALSE;
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
50 // event filter especially needed for SDL event filtering due to
51 // delay problems with lots of mouse motion events when mouse button
52 // not pressed (X11 can handle this with 'PointerMotionHintMask')
54 // event filter addition for SDL2: as SDL2 does not have a function to enable
55 // or disable keyboard auto-repeat, filter repeated keyboard events instead
57 static int FilterEvents(const Event *event)
61 // skip repeated key press events if keyboard auto-repeat is disabled
62 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
79 // non-motion events are directly passed to event handler functions
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 // do no reset mouse cursor before all pending events have been processed
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 // skip mouse motion events without pressed button outside level editor
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 // to prevent delay problems, skip mouse motion events if the very next
110 // event is also a mouse motion event (and therefore effectively only
111 // handling the last of a row of mouse motion events in the event queue)
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 // nothing to do if the current event is not a mouse motion event
116 if (event->type != EVENT_MOTIONNOTIFY)
119 // only skip motion events with pressed button outside the game
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 // if next event is also a mouse motion event, skip the current one
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
167 static void HandleEvents(void)
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 case EVENT_WHEELMOTION:
189 HandleWheelEvent((WheelEvent *) &event);
192 case SDL_WINDOWEVENT:
193 HandleWindowEvent((WindowEvent *) &event);
196 case EVENT_FINGERPRESS:
197 case EVENT_FINGERRELEASE:
198 case EVENT_FINGERMOTION:
199 HandleFingerEvent((FingerEvent *) &event);
202 case EVENT_TEXTINPUT:
203 HandleTextEvent((TextEvent *) &event);
206 case SDL_APP_WILLENTERBACKGROUND:
207 case SDL_APP_DIDENTERBACKGROUND:
208 case SDL_APP_WILLENTERFOREGROUND:
209 case SDL_APP_DIDENTERFOREGROUND:
210 HandlePauseResumeEvent((PauseResumeEvent *) &event);
214 case EVENT_KEYRELEASE:
215 HandleKeyEvent((KeyEvent *) &event);
219 HandleOtherEvents(&event);
223 // do not handle events for longer than standard frame delay period
224 if (DelayReached(&event_frame_delay, event_frame_delay_value))
229 void HandleOtherEvents(Event *event)
234 HandleExposeEvent((ExposeEvent *) event);
237 case EVENT_UNMAPNOTIFY:
239 // This causes the game to stop not only when iconified, but also
240 // when on another virtual desktop, which might be not desired.
241 SleepWhileUnmapped();
247 HandleFocusEvent((FocusChangeEvent *) event);
250 case EVENT_CLIENTMESSAGE:
251 HandleClientMessageEvent((ClientMessageEvent *) event);
254 case SDL_CONTROLLERBUTTONDOWN:
255 case SDL_CONTROLLERBUTTONUP:
256 // for any game controller button event, disable overlay buttons
257 SetOverlayEnabled(FALSE);
259 HandleSpecialGameControllerButtons(event);
262 case SDL_CONTROLLERDEVICEADDED:
263 case SDL_CONTROLLERDEVICEREMOVED:
264 case SDL_CONTROLLERAXISMOTION:
265 case SDL_JOYAXISMOTION:
266 case SDL_JOYBUTTONDOWN:
267 case SDL_JOYBUTTONUP:
268 HandleJoystickEvent(event);
272 case SDL_DROPCOMPLETE:
275 HandleDropEvent(event);
283 static void HandleMouseCursor(void)
285 if (game_status == GAME_MODE_TITLE)
287 // when showing title screens, hide mouse pointer (if not moved)
289 if (gfx.cursor_mode != CURSOR_NONE &&
290 DelayReached(&special_cursor_delay, special_cursor_delay_value))
292 SetMouseCursor(CURSOR_NONE);
295 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
298 // when playing, display a special mouse pointer inside the playfield
300 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
301 cursor_inside_playfield &&
302 DelayReached(&special_cursor_delay, special_cursor_delay_value))
304 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
306 SetMouseCursor(CURSOR_PLAYFIELD);
309 else if (gfx.cursor_mode != CURSOR_DEFAULT)
311 SetMouseCursor(CURSOR_DEFAULT);
314 // this is set after all pending events have been processed
315 cursor_mode_last = gfx.cursor_mode;
327 // execute event related actions after pending events have been processed
328 HandleEventActions();
330 // don't use all CPU time when idle; the main loop while playing
331 // has its own synchronization and is CPU friendly, too
333 if (game_status == GAME_MODE_PLAYING)
336 // always copy backbuffer to visible screen for every video frame
339 // reset video frame delay to default (may change again while playing)
340 SetVideoFrameDelay(MenuFrameDelay);
342 if (game_status == GAME_MODE_QUIT)
347 void ClearAutoRepeatKeyEvents(void)
349 while (PendingEvent())
353 PeekEvent(&next_event);
355 // if event is repeated key press event, remove it from event queue
356 if (next_event.type == EVENT_KEYPRESS &&
357 next_event.key.repeat)
358 WaitEvent(&next_event);
364 void ClearEventQueue(void)
368 while (NextValidEvent(&event))
372 case EVENT_BUTTONRELEASE:
373 button_status = MB_RELEASED;
376 case EVENT_KEYRELEASE:
380 case SDL_CONTROLLERBUTTONUP:
381 HandleJoystickEvent(&event);
386 HandleOtherEvents(&event);
392 static void ClearPlayerMouseAction(void)
394 local_player->mouse_action.lx = 0;
395 local_player->mouse_action.ly = 0;
396 local_player->mouse_action.button = 0;
399 void ClearPlayerAction(void)
403 // simulate key release events for still pressed keys
404 key_joystick_mapping = 0;
405 for (i = 0; i < MAX_PLAYERS; i++)
407 stored_player[i].action = 0;
408 stored_player[i].snap_action = 0;
411 ClearJoystickState();
412 ClearPlayerMouseAction();
415 static void SetPlayerMouseAction(int mx, int my, int button)
417 int lx = getLevelFromScreenX(mx);
418 int ly = getLevelFromScreenY(my);
419 int new_button = (!local_player->mouse_action.button && button);
421 if (local_player->mouse_action.button_hint)
422 button = local_player->mouse_action.button_hint;
424 ClearPlayerMouseAction();
426 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
429 local_player->mouse_action.lx = lx;
430 local_player->mouse_action.ly = ly;
431 local_player->mouse_action.button = button;
433 if (tape.recording && tape.pausing && tape.use_mouse)
435 // un-pause a paused game only if mouse button was newly pressed down
437 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
440 SetTileCursorXY(lx, ly);
443 void SleepWhileUnmapped(void)
445 boolean window_unmapped = TRUE;
447 KeyboardAutoRepeatOn();
449 while (window_unmapped)
453 if (!WaitValidEvent(&event))
458 case EVENT_BUTTONRELEASE:
459 button_status = MB_RELEASED;
462 case EVENT_KEYRELEASE:
463 key_joystick_mapping = 0;
466 case SDL_CONTROLLERBUTTONUP:
467 HandleJoystickEvent(&event);
468 key_joystick_mapping = 0;
471 case EVENT_MAPNOTIFY:
472 window_unmapped = FALSE;
475 case EVENT_UNMAPNOTIFY:
476 // this is only to surely prevent the 'should not happen' case
477 // of recursively looping between 'SleepWhileUnmapped()' and
478 // 'HandleOtherEvents()' which usually calls this funtion.
482 HandleOtherEvents(&event);
487 if (game_status == GAME_MODE_PLAYING)
488 KeyboardAutoRepeatOffUnlessAutoplay();
491 void HandleExposeEvent(ExposeEvent *event)
495 void HandleButtonEvent(ButtonEvent *event)
497 #if DEBUG_EVENTS_BUTTON
498 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
500 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
504 // for any mouse button event, disable playfield tile cursor
505 SetTileCursorEnabled(FALSE);
507 #if defined(HAS_SCREEN_KEYBOARD)
508 if (video.shifted_up)
509 event->y += video.shifted_up_pos;
512 motion_status = FALSE;
514 if (event->type == EVENT_BUTTONPRESS)
515 button_status = event->button;
517 button_status = MB_RELEASED;
519 HandleButton(event->x, event->y, button_status, event->button);
522 void HandleMotionEvent(MotionEvent *event)
524 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
527 motion_status = TRUE;
529 #if DEBUG_EVENTS_MOTION
530 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
531 button_status, event->x, event->y);
534 HandleButton(event->x, event->y, button_status, button_status);
537 void HandleWheelEvent(WheelEvent *event)
541 #if DEBUG_EVENTS_WHEEL
543 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
544 event->which, event->x, event->y);
546 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
547 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
548 event->which, event->x, event->y,
549 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
550 "SDL_MOUSEWHEEL_FLIPPED"));
554 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
555 event->x > 0 ? MB_WHEEL_RIGHT :
556 event->y < 0 ? MB_WHEEL_DOWN :
557 event->y > 0 ? MB_WHEEL_UP : 0);
559 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
560 // accelerated mouse wheel available on Mac and Windows
561 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
563 // no accelerated mouse wheel available on Unix/Linux
564 wheel_steps = DEFAULT_WHEEL_STEPS;
567 motion_status = FALSE;
569 button_status = button_nr;
570 HandleButton(0, 0, button_status, -button_nr);
572 button_status = MB_RELEASED;
573 HandleButton(0, 0, button_status, -button_nr);
576 void HandleWindowEvent(WindowEvent *event)
578 #if DEBUG_EVENTS_WINDOW
579 int subtype = event->event;
582 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
583 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
584 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
585 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
586 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
587 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
588 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
589 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
590 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
591 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
592 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
593 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
594 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
595 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
598 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
599 event_name, event->data1, event->data2);
603 // (not needed, as the screen gets redrawn every 20 ms anyway)
604 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
605 event->event == SDL_WINDOWEVENT_RESIZED ||
606 event->event == SDL_WINDOWEVENT_EXPOSED)
610 if (event->event == SDL_WINDOWEVENT_RESIZED)
612 if (!video.fullscreen_enabled)
614 int new_window_width = event->data1;
615 int new_window_height = event->data2;
617 // if window size has changed after resizing, calculate new scaling factor
618 if (new_window_width != video.window_width ||
619 new_window_height != video.window_height)
621 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
622 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
624 // (extreme window scaling allowed, but cannot be saved permanently)
625 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
626 setup.window_scaling_percent =
627 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
628 MAX_WINDOW_SCALING_PERCENT);
630 video.window_width = new_window_width;
631 video.window_height = new_window_height;
633 if (game_status == GAME_MODE_SETUP)
634 RedrawSetupScreenAfterFullscreenToggle();
639 #if defined(PLATFORM_ANDROID)
642 int new_display_width = event->data1;
643 int new_display_height = event->data2;
645 // if fullscreen display size has changed, device has been rotated
646 if (new_display_width != video.display_width ||
647 new_display_height != video.display_height)
649 int nr = GRID_ACTIVE_NR(); // previous screen orientation
651 video.display_width = new_display_width;
652 video.display_height = new_display_height;
654 SDLSetScreenProperties();
656 // check if screen orientation has changed (should always be true here)
657 if (nr != GRID_ACTIVE_NR())
661 if (game_status == GAME_MODE_SETUP)
662 RedrawSetupScreenAfterScreenRotation(nr);
664 nr = GRID_ACTIVE_NR();
666 overlay.grid_xsize = setup.touch.grid_xsize[nr];
667 overlay.grid_ysize = setup.touch.grid_ysize[nr];
669 for (x = 0; x < MAX_GRID_XSIZE; x++)
670 for (y = 0; y < MAX_GRID_YSIZE; y++)
671 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
679 #define NUM_TOUCH_FINGERS 3
684 SDL_FingerID finger_id;
688 } touch_info[NUM_TOUCH_FINGERS];
690 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
693 int x = event->x * overlay.grid_xsize;
694 int y = event->y * overlay.grid_ysize;
695 int grid_button = overlay.grid_button[x][y];
696 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
697 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
698 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
699 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
700 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
701 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
702 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
705 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
706 float event_x = (event->x);
707 float event_y = (event->y - ypos) / (1 - ypos);
708 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
709 event_y > 2.0 / 3.0 && event_y < 1 ?
710 setup.input[0].key.snap :
711 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
712 event_y > 2.0 / 3.0 && event_y < 1 ?
713 setup.input[0].key.drop :
714 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
715 event_y > 0 && event_y < 1.0 / 3.0 ?
716 setup.input[0].key.up :
717 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
718 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
719 setup.input[0].key.left :
720 event_x > 8.0 / 9.0 && event_x < 1 &&
721 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
722 setup.input[0].key.right :
723 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
724 event_y > 2.0 / 3.0 && event_y < 1 ?
725 setup.input[0].key.down :
728 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
730 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
734 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
736 // for any touch input event, enable overlay buttons (if activated)
737 SetOverlayEnabled(TRUE);
739 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
740 getKeyNameFromKey(key), key_status_name, event->fingerId);
742 if (key_status == KEY_PRESSED)
743 overlay.grid_button_action |= grid_button_action;
745 overlay.grid_button_action &= ~grid_button_action;
747 // check if we already know this touch event's finger id
748 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
750 if (touch_info[i].touched &&
751 touch_info[i].finger_id == event->fingerId)
753 // Error(ERR_DEBUG, "MARK 1: %d", i);
759 if (i >= NUM_TOUCH_FINGERS)
761 if (key_status == KEY_PRESSED)
763 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
765 // unknown finger id -- get new, empty slot, if available
766 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
768 if (touch_info[i].counter < oldest_counter)
771 oldest_counter = touch_info[i].counter;
773 // Error(ERR_DEBUG, "MARK 2: %d", i);
776 if (!touch_info[i].touched)
778 // Error(ERR_DEBUG, "MARK 3: %d", i);
784 if (i >= NUM_TOUCH_FINGERS)
786 // all slots allocated -- use oldest slot
789 // Error(ERR_DEBUG, "MARK 4: %d", i);
794 // release of previously unknown key (should not happen)
796 if (key != KSYM_UNDEFINED)
798 HandleKey(key, KEY_RELEASED);
800 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
801 getKeyNameFromKey(key), "KEY_RELEASED", i);
806 if (i < NUM_TOUCH_FINGERS)
808 if (key_status == KEY_PRESSED)
810 if (touch_info[i].key != key)
812 if (touch_info[i].key != KSYM_UNDEFINED)
814 HandleKey(touch_info[i].key, KEY_RELEASED);
816 // undraw previous grid button when moving finger away
817 overlay.grid_button_action &= ~touch_info[i].action;
819 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
820 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
823 if (key != KSYM_UNDEFINED)
825 HandleKey(key, KEY_PRESSED);
827 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
828 getKeyNameFromKey(key), "KEY_PRESSED", i);
832 touch_info[i].touched = TRUE;
833 touch_info[i].finger_id = event->fingerId;
834 touch_info[i].counter = Counter();
835 touch_info[i].key = key;
836 touch_info[i].action = grid_button_action;
840 if (touch_info[i].key != KSYM_UNDEFINED)
842 HandleKey(touch_info[i].key, KEY_RELEASED);
844 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
845 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
848 touch_info[i].touched = FALSE;
849 touch_info[i].finger_id = 0;
850 touch_info[i].counter = 0;
851 touch_info[i].key = 0;
852 touch_info[i].action = JOY_NO_ACTION;
857 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
859 static Key motion_key_x = KSYM_UNDEFINED;
860 static Key motion_key_y = KSYM_UNDEFINED;
861 static Key button_key = KSYM_UNDEFINED;
862 static float motion_x1, motion_y1;
863 static float button_x1, button_y1;
864 static SDL_FingerID motion_id = -1;
865 static SDL_FingerID button_id = -1;
866 int move_trigger_distance_percent = setup.touch.move_distance;
867 int drop_trigger_distance_percent = setup.touch.drop_distance;
868 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
869 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
870 float event_x = event->x;
871 float event_y = event->y;
873 if (event->type == EVENT_FINGERPRESS)
875 if (event_x > 1.0 / 3.0)
879 motion_id = event->fingerId;
884 motion_key_x = KSYM_UNDEFINED;
885 motion_key_y = KSYM_UNDEFINED;
887 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
893 button_id = event->fingerId;
898 button_key = setup.input[0].key.snap;
900 HandleKey(button_key, KEY_PRESSED);
902 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
905 else if (event->type == EVENT_FINGERRELEASE)
907 if (event->fingerId == motion_id)
911 if (motion_key_x != KSYM_UNDEFINED)
912 HandleKey(motion_key_x, KEY_RELEASED);
913 if (motion_key_y != KSYM_UNDEFINED)
914 HandleKey(motion_key_y, KEY_RELEASED);
916 motion_key_x = KSYM_UNDEFINED;
917 motion_key_y = KSYM_UNDEFINED;
919 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
921 else if (event->fingerId == button_id)
925 if (button_key != KSYM_UNDEFINED)
926 HandleKey(button_key, KEY_RELEASED);
928 button_key = KSYM_UNDEFINED;
930 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
933 else if (event->type == EVENT_FINGERMOTION)
935 if (event->fingerId == motion_id)
937 float distance_x = ABS(event_x - motion_x1);
938 float distance_y = ABS(event_y - motion_y1);
939 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
940 event_x > motion_x1 ? setup.input[0].key.right :
942 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
943 event_y > motion_y1 ? setup.input[0].key.down :
946 if (distance_x < move_trigger_distance / 2 ||
947 distance_x < distance_y)
948 new_motion_key_x = KSYM_UNDEFINED;
950 if (distance_y < move_trigger_distance / 2 ||
951 distance_y < distance_x)
952 new_motion_key_y = KSYM_UNDEFINED;
954 if (distance_x > move_trigger_distance ||
955 distance_y > move_trigger_distance)
957 if (new_motion_key_x != motion_key_x)
959 if (motion_key_x != KSYM_UNDEFINED)
960 HandleKey(motion_key_x, KEY_RELEASED);
961 if (new_motion_key_x != KSYM_UNDEFINED)
962 HandleKey(new_motion_key_x, KEY_PRESSED);
965 if (new_motion_key_y != motion_key_y)
967 if (motion_key_y != KSYM_UNDEFINED)
968 HandleKey(motion_key_y, KEY_RELEASED);
969 if (new_motion_key_y != KSYM_UNDEFINED)
970 HandleKey(new_motion_key_y, KEY_PRESSED);
976 motion_key_x = new_motion_key_x;
977 motion_key_y = new_motion_key_y;
979 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
982 else if (event->fingerId == button_id)
984 float distance_x = ABS(event_x - button_x1);
985 float distance_y = ABS(event_y - button_y1);
987 if (distance_x < drop_trigger_distance / 2 &&
988 distance_y > drop_trigger_distance)
990 if (button_key == setup.input[0].key.snap)
991 HandleKey(button_key, KEY_RELEASED);
996 button_key = setup.input[0].key.drop;
998 HandleKey(button_key, KEY_PRESSED);
1000 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1006 void HandleFingerEvent(FingerEvent *event)
1008 #if DEBUG_EVENTS_FINGER
1009 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1010 event->type == EVENT_FINGERPRESS ? "pressed" :
1011 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1015 event->dx, event->dy,
1019 if (game_status != GAME_MODE_PLAYING)
1022 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1024 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1025 local_player->mouse_action.button_hint =
1026 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1027 event->x < 0.5 ? MB_LEFTBUTTON :
1028 event->x > 0.5 ? MB_RIGHTBUTTON :
1034 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1035 HandleFingerEvent_VirtualButtons(event);
1036 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1037 HandleFingerEvent_WipeGestures(event);
1040 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1042 static int old_mx = 0, old_my = 0;
1043 static int last_button = MB_LEFTBUTTON;
1044 static boolean touched = FALSE;
1045 static boolean tapped = FALSE;
1047 // screen tile was tapped (but finger not touching the screen anymore)
1048 // (this point will also be reached without receiving a touch event)
1049 if (tapped && !touched)
1051 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1056 // stop here if this function was not triggered by a touch event
1060 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1062 // finger started touching the screen
1072 ClearPlayerMouseAction();
1074 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1077 else if (button == MB_RELEASED && touched)
1079 // finger stopped touching the screen
1084 SetPlayerMouseAction(old_mx, old_my, last_button);
1086 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1088 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1093 // finger moved while touching the screen
1095 int old_x = getLevelFromScreenX(old_mx);
1096 int old_y = getLevelFromScreenY(old_my);
1097 int new_x = getLevelFromScreenX(mx);
1098 int new_y = getLevelFromScreenY(my);
1100 if (new_x != old_x || new_y != old_y)
1105 // finger moved left or right from (horizontal) starting position
1107 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1109 SetPlayerMouseAction(old_mx, old_my, button_nr);
1111 last_button = button_nr;
1113 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1117 // finger stays at or returned to (horizontal) starting position
1119 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1121 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1126 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1128 static int old_mx = 0, old_my = 0;
1129 static int last_button = MB_LEFTBUTTON;
1130 static boolean touched = FALSE;
1131 static boolean tapped = FALSE;
1133 // screen tile was tapped (but finger not touching the screen anymore)
1134 // (this point will also be reached without receiving a touch event)
1135 if (tapped && !touched)
1137 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1142 // stop here if this function was not triggered by a touch event
1146 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1148 // finger started touching the screen
1158 ClearPlayerMouseAction();
1160 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1163 else if (button == MB_RELEASED && touched)
1165 // finger stopped touching the screen
1170 SetPlayerMouseAction(old_mx, old_my, last_button);
1172 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1174 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1179 // finger moved while touching the screen
1181 int old_x = getLevelFromScreenX(old_mx);
1182 int old_y = getLevelFromScreenY(old_my);
1183 int new_x = getLevelFromScreenX(mx);
1184 int new_y = getLevelFromScreenY(my);
1186 if (new_x != old_x || new_y != old_y)
1188 // finger moved away from starting position
1190 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1192 // quickly alternate between clicking and releasing for maximum speed
1193 if (FrameCounter % 2 == 0)
1194 button_nr = MB_RELEASED;
1196 SetPlayerMouseAction(old_mx, old_my, button_nr);
1199 last_button = button_nr;
1203 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1207 // finger stays at or returned to starting position
1209 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1211 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1216 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1218 static int old_mx = 0, old_my = 0;
1219 static Key motion_key_x = KSYM_UNDEFINED;
1220 static Key motion_key_y = KSYM_UNDEFINED;
1221 static boolean touched = FALSE;
1222 static boolean started_on_player = FALSE;
1223 static boolean player_is_dropping = FALSE;
1224 static int player_drop_count = 0;
1225 static int last_player_x = -1;
1226 static int last_player_y = -1;
1228 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1237 started_on_player = FALSE;
1238 player_is_dropping = FALSE;
1239 player_drop_count = 0;
1243 motion_key_x = KSYM_UNDEFINED;
1244 motion_key_y = KSYM_UNDEFINED;
1246 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1249 else if (button == MB_RELEASED && touched)
1256 if (motion_key_x != KSYM_UNDEFINED)
1257 HandleKey(motion_key_x, KEY_RELEASED);
1258 if (motion_key_y != KSYM_UNDEFINED)
1259 HandleKey(motion_key_y, KEY_RELEASED);
1261 if (started_on_player)
1263 if (player_is_dropping)
1265 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1267 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1271 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1273 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1277 motion_key_x = KSYM_UNDEFINED;
1278 motion_key_y = KSYM_UNDEFINED;
1280 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1285 int src_x = local_player->jx;
1286 int src_y = local_player->jy;
1287 int dst_x = getLevelFromScreenX(old_mx);
1288 int dst_y = getLevelFromScreenY(old_my);
1289 int dx = dst_x - src_x;
1290 int dy = dst_y - src_y;
1291 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1292 dx > 0 ? setup.input[0].key.right :
1294 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1295 dy > 0 ? setup.input[0].key.down :
1298 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1299 (last_player_x != local_player->jx ||
1300 last_player_y != local_player->jy))
1302 // in case of asymmetric diagonal movement, use "preferred" direction
1304 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1306 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1307 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1309 local_player->last_move_dir = last_move_dir;
1311 // (required to prevent accidentally forcing direction for next movement)
1312 last_player_x = local_player->jx;
1313 last_player_y = local_player->jy;
1316 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1318 started_on_player = TRUE;
1319 player_drop_count = getPlayerInventorySize(0);
1320 player_is_dropping = (player_drop_count > 0);
1322 if (player_is_dropping)
1324 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1326 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1330 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1332 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1335 else if (dx != 0 || dy != 0)
1337 if (player_is_dropping &&
1338 player_drop_count == getPlayerInventorySize(0))
1340 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1342 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1343 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1345 player_is_dropping = FALSE;
1349 if (new_motion_key_x != motion_key_x)
1351 Error(ERR_DEBUG, "---------- %s %s ----------",
1352 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1353 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1355 if (motion_key_x != KSYM_UNDEFINED)
1356 HandleKey(motion_key_x, KEY_RELEASED);
1357 if (new_motion_key_x != KSYM_UNDEFINED)
1358 HandleKey(new_motion_key_x, KEY_PRESSED);
1361 if (new_motion_key_y != motion_key_y)
1363 Error(ERR_DEBUG, "---------- %s %s ----------",
1364 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1365 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1367 if (motion_key_y != KSYM_UNDEFINED)
1368 HandleKey(motion_key_y, KEY_RELEASED);
1369 if (new_motion_key_y != KSYM_UNDEFINED)
1370 HandleKey(new_motion_key_y, KEY_PRESSED);
1373 motion_key_x = new_motion_key_x;
1374 motion_key_y = new_motion_key_y;
1378 static void HandleButtonOrFinger(int mx, int my, int button)
1380 if (game_status != GAME_MODE_PLAYING)
1383 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1385 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1386 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1387 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1388 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1389 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1390 SetPlayerMouseAction(mx, my, button); // special case
1394 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1395 HandleButtonOrFinger_FollowFinger(mx, my, button);
1399 static boolean checkTextInputKeyModState(void)
1401 // when playing, only handle raw key events and ignore text input
1402 if (game_status == GAME_MODE_PLAYING)
1405 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1408 void HandleTextEvent(TextEvent *event)
1410 char *text = event->text;
1411 Key key = getKeyFromKeyName(text);
1413 #if DEBUG_EVENTS_TEXT
1414 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1417 text[0], (int)(text[0]),
1419 getKeyNameFromKey(key),
1423 #if !defined(HAS_SCREEN_KEYBOARD)
1424 // non-mobile devices: only handle key input with modifier keys pressed here
1425 // (every other key input is handled directly as physical key input event)
1426 if (!checkTextInputKeyModState())
1430 // process text input as "classic" (with uppercase etc.) key input event
1431 HandleKey(key, KEY_PRESSED);
1432 HandleKey(key, KEY_RELEASED);
1435 void HandlePauseResumeEvent(PauseResumeEvent *event)
1437 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1441 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1447 void HandleKeyEvent(KeyEvent *event)
1449 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1450 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1451 Key key = GetEventKey(event, with_modifiers);
1452 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1454 #if DEBUG_EVENTS_KEY
1455 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1456 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1457 event->keysym.scancode,
1462 getKeyNameFromKey(key));
1465 #if defined(PLATFORM_ANDROID)
1466 if (key == KSYM_Back)
1468 // always map the "back" button to the "escape" key on Android devices
1471 else if (key == KSYM_Menu)
1473 // the "menu" button can be used to toggle displaying virtual buttons
1474 if (key_status == KEY_PRESSED)
1475 SetOverlayEnabled(!GetOverlayEnabled());
1479 // for any other "real" key event, disable virtual buttons
1480 SetOverlayEnabled(FALSE);
1484 HandleKeyModState(keymod, key_status);
1486 // only handle raw key input without text modifier keys pressed
1487 if (!checkTextInputKeyModState())
1488 HandleKey(key, key_status);
1491 void HandleFocusEvent(FocusChangeEvent *event)
1493 static int old_joystick_status = -1;
1495 if (event->type == EVENT_FOCUSOUT)
1497 KeyboardAutoRepeatOn();
1498 old_joystick_status = joystick.status;
1499 joystick.status = JOYSTICK_NOT_AVAILABLE;
1501 ClearPlayerAction();
1503 else if (event->type == EVENT_FOCUSIN)
1505 /* When there are two Rocks'n'Diamonds windows which overlap and
1506 the player moves the pointer from one game window to the other,
1507 a 'FocusOut' event is generated for the window the pointer is
1508 leaving and a 'FocusIn' event is generated for the window the
1509 pointer is entering. In some cases, it can happen that the
1510 'FocusIn' event is handled by the one game process before the
1511 'FocusOut' event by the other game process. In this case the
1512 X11 environment would end up with activated keyboard auto repeat,
1513 because unfortunately this is a global setting and not (which
1514 would be far better) set for each X11 window individually.
1515 The effect would be keyboard auto repeat while playing the game
1516 (game_status == GAME_MODE_PLAYING), which is not desired.
1517 To avoid this special case, we just wait 1/10 second before
1518 processing the 'FocusIn' event. */
1520 if (game_status == GAME_MODE_PLAYING)
1523 KeyboardAutoRepeatOffUnlessAutoplay();
1526 if (old_joystick_status != -1)
1527 joystick.status = old_joystick_status;
1531 void HandleClientMessageEvent(ClientMessageEvent *event)
1533 if (CheckCloseWindowEvent(event))
1537 static boolean HandleDropFileEvent(char *filename)
1539 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1541 // check and extract dropped zip files into correct user data directory
1542 if (!strSuffixLower(filename, ".zip"))
1544 Error(ERR_WARN, "file '%s' not supported", filename);
1549 TreeInfo *tree_node = NULL;
1550 int tree_type = GetZipFileTreeType(filename);
1551 char *directory = TREE_USERDIR(tree_type);
1553 if (directory == NULL)
1555 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1560 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1561 game_status == GAME_MODE_LEVELS &&
1562 leveldir_current->node_parent != NULL)
1564 // extract new level set next to currently selected level set
1565 tree_node = leveldir_current;
1567 // get parent directory of currently selected level set directory
1568 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1570 // use private level directory instead of top-level package level directory
1571 if (strPrefix(directory, options.level_directory) &&
1572 strEqual(leveldir_current->node_parent->fullpath, "."))
1573 directory = getUserLevelDir(NULL);
1576 // extract level or artwork set from zip file to target directory
1577 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1579 if (top_dir == NULL)
1581 // error message already issued by "ExtractZipFileIntoDirectory()"
1586 // add extracted level or artwork set to tree info structure
1587 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1589 // update menu screen (and possibly change current level set)
1590 DrawScreenAfterAddingSet(top_dir, tree_type);
1595 static void HandleDropTextEvent(char *text)
1597 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1600 static void HandleDropCompleteEvent(int files_succeeded, int files_failed)
1602 // only show request dialog if no other request dialog already active
1603 if (game.request_active)
1606 if (files_succeeded > 0 && files_failed > 0)
1607 Request("New level or artwork set(s) added, "
1608 "but some dropped file(s) failed!", REQ_CONFIRM);
1609 else if (files_succeeded > 0)
1610 Request("New level or artwork set(s) added!", REQ_CONFIRM);
1611 else if (files_failed > 0)
1612 Request("Failed to process dropped file(s)!", REQ_CONFIRM);
1615 void HandleDropEvent(Event *event)
1617 static boolean confirm_on_drop_complete = FALSE;
1618 static int files_succeeded = 0;
1619 static int files_failed = 0;
1621 switch (event->type)
1625 confirm_on_drop_complete = TRUE;
1626 files_succeeded = 0;
1634 boolean success = HandleDropFileEvent(event->drop.file);
1641 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1642 if (!confirm_on_drop_complete)
1644 // process all remaining events, including further SDL_DROPFILE events
1647 HandleDropCompleteEvent(files_succeeded, files_failed);
1649 files_succeeded = 0;
1658 HandleDropTextEvent(event->drop.file);
1663 case SDL_DROPCOMPLETE:
1665 HandleDropCompleteEvent(files_succeeded, files_failed);
1671 if (event->drop.file != NULL)
1672 SDL_free(event->drop.file);
1675 void HandleButton(int mx, int my, int button, int button_nr)
1677 static int old_mx = 0, old_my = 0;
1678 boolean button_hold = FALSE;
1679 boolean handle_gadgets = TRUE;
1685 button_nr = -button_nr;
1694 #if defined(PLATFORM_ANDROID)
1695 // when playing, only handle gadgets when using "follow finger" controls
1696 // or when using touch controls in combination with the MM game engine
1697 // or when using gadgets that do not overlap with virtual buttons
1699 (game_status != GAME_MODE_PLAYING ||
1700 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1701 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1702 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1703 !virtual_button_pressed));
1706 if (HandleGlobalAnimClicks(mx, my, button))
1708 // do not handle this button event anymore
1709 return; // force mouse event not to be handled at all
1712 if (handle_gadgets && HandleGadgets(mx, my, button))
1714 // do not handle this button event anymore
1715 mx = my = -32; // force mouse event to be outside screen tiles
1718 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1721 // do not use scroll wheel button events for anything other than gadgets
1722 if (IS_WHEEL_BUTTON(button_nr))
1725 switch (game_status)
1727 case GAME_MODE_TITLE:
1728 HandleTitleScreen(mx, my, 0, 0, button);
1731 case GAME_MODE_MAIN:
1732 HandleMainMenu(mx, my, 0, 0, button);
1735 case GAME_MODE_PSEUDO_TYPENAME:
1736 HandleTypeName(0, KSYM_Return);
1739 case GAME_MODE_LEVELS:
1740 HandleChooseLevelSet(mx, my, 0, 0, button);
1743 case GAME_MODE_LEVELNR:
1744 HandleChooseLevelNr(mx, my, 0, 0, button);
1747 case GAME_MODE_SCORES:
1748 HandleHallOfFame(0, 0, 0, 0, button);
1751 case GAME_MODE_EDITOR:
1752 HandleLevelEditorIdle();
1755 case GAME_MODE_INFO:
1756 HandleInfoScreen(mx, my, 0, 0, button);
1759 case GAME_MODE_SETUP:
1760 HandleSetupScreen(mx, my, 0, 0, button);
1763 case GAME_MODE_PLAYING:
1764 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1765 HandleButtonOrFinger(mx, my, button);
1767 SetPlayerMouseAction(mx, my, button);
1770 if (button == MB_PRESSED && !motion_status && !button_hold &&
1771 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1772 DumpTileFromScreen(mx, my);
1782 static boolean is_string_suffix(char *string, char *suffix)
1784 int string_len = strlen(string);
1785 int suffix_len = strlen(suffix);
1787 if (suffix_len > string_len)
1790 return (strEqual(&string[string_len - suffix_len], suffix));
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 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1820 if (game_status == GAME_MODE_MAIN)
1822 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1823 is_string_suffix(cheat_input, ":ist"))
1825 InsertSolutionTape();
1827 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1828 is_string_suffix(cheat_input, ":pst"))
1832 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1833 is_string_suffix(cheat_input, ":rg"))
1835 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1838 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1839 is_string_suffix(cheat_input, ":rs"))
1841 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1844 else if (is_string_suffix(cheat_input, ":reload-music") ||
1845 is_string_suffix(cheat_input, ":rm"))
1847 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1850 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1851 is_string_suffix(cheat_input, ":ra"))
1853 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1854 1 << ARTWORK_TYPE_SOUNDS |
1855 1 << ARTWORK_TYPE_MUSIC);
1858 else if (is_string_suffix(cheat_input, ":dump-level") ||
1859 is_string_suffix(cheat_input, ":dl"))
1863 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1864 is_string_suffix(cheat_input, ":dt"))
1868 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1869 is_string_suffix(cheat_input, ":ft"))
1871 /* fix single-player tapes that contain player input for more than one
1872 player (due to a bug in 3.3.1.2 and earlier versions), which results
1873 in playing levels with more than one player in multi-player mode,
1874 even though the tape was originally recorded in single-player mode */
1876 // remove player input actions for all players but the first one
1877 for (i = 1; i < MAX_PLAYERS; i++)
1878 tape.player_participates[i] = FALSE;
1880 tape.changed = TRUE;
1882 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1883 is_string_suffix(cheat_input, ":snl"))
1885 SaveNativeLevel(&level);
1887 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1888 is_string_suffix(cheat_input, ":fps"))
1890 global.show_frames_per_second = !global.show_frames_per_second;
1893 else if (game_status == GAME_MODE_PLAYING)
1896 if (is_string_suffix(cheat_input, ".q"))
1897 DEBUG_SetMaximumDynamite();
1900 else if (game_status == GAME_MODE_EDITOR)
1902 if (is_string_suffix(cheat_input, ":dump-brush") ||
1903 is_string_suffix(cheat_input, ":DB"))
1907 else if (is_string_suffix(cheat_input, ":DDB"))
1912 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1914 if (letter == 'x') // copy brush to clipboard (small size)
1916 CopyBrushToClipboard_Small();
1918 else if (letter == 'c') // copy brush to clipboard (normal size)
1920 CopyBrushToClipboard();
1922 else if (letter == 'v') // paste brush from Clipboard
1924 CopyClipboardToBrush();
1929 // special key shortcuts for all game modes
1930 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1931 is_string_suffix(cheat_input, ":dea") ||
1932 is_string_suffix(cheat_input, ":DEA"))
1934 DumpGadgetIdentifiers();
1935 DumpScreenIdentifiers();
1939 boolean HandleKeysDebug(Key key, int key_status)
1944 if (key_status != KEY_PRESSED)
1947 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1949 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1951 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1953 if (key == setup.debug.frame_delay_key[i] &&
1954 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1956 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1957 setup.debug.frame_delay[i] : setup.game_frame_delay);
1959 if (!setup.debug.frame_delay_game_only)
1960 MenuFrameDelay = GameFrameDelay;
1962 SetVideoFrameDelay(GameFrameDelay);
1964 if (GameFrameDelay > ONE_SECOND_DELAY)
1965 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1966 else if (GameFrameDelay != 0)
1967 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1968 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1969 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1971 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1978 if (game_status == GAME_MODE_PLAYING)
1982 options.debug = !options.debug;
1984 Error(ERR_INFO, "debug mode %s",
1985 (options.debug ? "enabled" : "disabled"));
1989 else if (key == KSYM_v)
1991 Error(ERR_INFO, "currently using game engine version %d",
1992 game.engine_version);
2002 void HandleKey(Key key, int key_status)
2004 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2005 static boolean ignore_repeated_key = FALSE;
2006 static struct SetupKeyboardInfo ski;
2007 static struct SetupShortcutInfo ssi;
2016 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2017 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2018 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2019 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2020 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2021 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2026 if (HandleKeysDebug(key, key_status))
2027 return; // do not handle already processed keys again
2029 // map special keys (media keys / remote control buttons) to default keys
2030 if (key == KSYM_PlayPause)
2032 else if (key == KSYM_Select)
2035 HandleSpecialGameControllerKeys(key, key_status);
2037 if (game_status == GAME_MODE_PLAYING)
2039 // only needed for single-step tape recording mode
2040 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2043 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2045 byte key_action = 0;
2046 byte key_snap_action = 0;
2048 if (setup.input[pnr].use_joystick)
2051 ski = setup.input[pnr].key;
2053 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2054 if (key == *key_info[i].key_custom)
2055 key_action |= key_info[i].action;
2057 // use combined snap+direction keys for the first player only
2060 ssi = setup.shortcut;
2062 // also remember normal snap key when handling snap+direction keys
2063 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2065 for (i = 0; i < NUM_DIRECTIONS; i++)
2067 if (key == *key_info[i].key_snap)
2069 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2070 key_snap_action |= key_info[i].action;
2075 if (key_status == KEY_PRESSED)
2077 stored_player[pnr].action |= key_action;
2078 stored_player[pnr].snap_action |= key_snap_action;
2082 stored_player[pnr].action &= ~key_action;
2083 stored_player[pnr].snap_action &= ~key_snap_action;
2086 // restore snap action if one of several pressed snap keys was released
2087 if (stored_player[pnr].snap_action)
2088 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2090 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2092 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2094 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2096 // if snap key already pressed, keep pause mode when releasing
2097 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2098 has_snapped[pnr] = TRUE;
2100 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2102 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2104 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2105 getRedDiskReleaseFlag_SP() == 0)
2107 // add a single inactive frame before dropping starts
2108 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2109 stored_player[pnr].force_dropping = TRUE;
2112 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2114 // if snap key was pressed without direction, leave pause mode
2115 if (!has_snapped[pnr])
2116 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2118 has_snapped[pnr] = FALSE;
2121 else if (tape.recording && tape.pausing && !tape.use_mouse)
2123 // prevent key release events from un-pausing a paused game
2124 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2125 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2128 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2129 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2135 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2136 if (key == key_info[i].key_default)
2137 joy |= key_info[i].action;
2142 if (key_status == KEY_PRESSED)
2143 key_joystick_mapping |= joy;
2145 key_joystick_mapping &= ~joy;
2150 if (game_status != GAME_MODE_PLAYING)
2151 key_joystick_mapping = 0;
2153 if (key_status == KEY_RELEASED)
2155 // reset flag to ignore repeated "key pressed" events after key release
2156 ignore_repeated_key = FALSE;
2161 if ((key == KSYM_F11 ||
2162 ((key == KSYM_Return ||
2163 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2164 video.fullscreen_available &&
2165 !ignore_repeated_key)
2167 setup.fullscreen = !setup.fullscreen;
2169 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2171 if (game_status == GAME_MODE_SETUP)
2172 RedrawSetupScreenAfterFullscreenToggle();
2174 // set flag to ignore repeated "key pressed" events
2175 ignore_repeated_key = TRUE;
2180 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2181 key == KSYM_minus || key == KSYM_KP_Subtract ||
2182 key == KSYM_plus || key == KSYM_KP_Add ||
2183 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2184 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2185 video.window_scaling_available &&
2186 !video.fullscreen_enabled)
2188 if (key == KSYM_0 || key == KSYM_KP_0)
2189 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2190 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2191 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2193 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2195 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2196 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2197 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2198 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2200 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2202 if (game_status == GAME_MODE_SETUP)
2203 RedrawSetupScreenAfterFullscreenToggle();
2208 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2209 key == KSYM_Return ||
2210 key == KSYM_Escape)))
2212 // do not handle this key event anymore
2213 if (key != KSYM_Escape) // always allow ESC key to be handled
2217 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2218 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2225 if (game_status == GAME_MODE_MAIN &&
2226 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2228 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2233 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2235 if (key == setup.shortcut.save_game)
2237 else if (key == setup.shortcut.load_game)
2239 else if (key == setup.shortcut.toggle_pause)
2240 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2242 HandleTapeButtonKeys(key);
2243 HandleSoundButtonKeys(key);
2246 if (game_status == GAME_MODE_PLAYING && !network_playing)
2248 int centered_player_nr_next = -999;
2250 if (key == setup.shortcut.focus_player_all)
2251 centered_player_nr_next = -1;
2253 for (i = 0; i < MAX_PLAYERS; i++)
2254 if (key == setup.shortcut.focus_player[i])
2255 centered_player_nr_next = i;
2257 if (centered_player_nr_next != -999)
2259 game.centered_player_nr_next = centered_player_nr_next;
2260 game.set_centered_player = TRUE;
2264 tape.centered_player_nr_next = game.centered_player_nr_next;
2265 tape.set_centered_player = TRUE;
2270 HandleKeysSpecial(key);
2272 if (HandleGadgetsKeyInput(key))
2273 return; // do not handle already processed keys again
2275 switch (game_status)
2277 case GAME_MODE_PSEUDO_TYPENAME:
2278 HandleTypeName(0, key);
2281 case GAME_MODE_TITLE:
2282 case GAME_MODE_MAIN:
2283 case GAME_MODE_LEVELS:
2284 case GAME_MODE_LEVELNR:
2285 case GAME_MODE_SETUP:
2286 case GAME_MODE_INFO:
2287 case GAME_MODE_SCORES:
2289 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2296 if (game_status == GAME_MODE_TITLE)
2297 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2298 else if (game_status == GAME_MODE_MAIN)
2299 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2300 else if (game_status == GAME_MODE_LEVELS)
2301 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2302 else if (game_status == GAME_MODE_LEVELNR)
2303 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2304 else if (game_status == GAME_MODE_SETUP)
2305 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2306 else if (game_status == GAME_MODE_INFO)
2307 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2308 else if (game_status == GAME_MODE_SCORES)
2309 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2313 if (game_status != GAME_MODE_MAIN)
2314 FadeSkipNextFadeIn();
2316 if (game_status == GAME_MODE_TITLE)
2317 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2318 else if (game_status == GAME_MODE_LEVELS)
2319 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2320 else if (game_status == GAME_MODE_LEVELNR)
2321 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2322 else if (game_status == GAME_MODE_SETUP)
2323 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2324 else if (game_status == GAME_MODE_INFO)
2325 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2326 else if (game_status == GAME_MODE_SCORES)
2327 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2331 if (game_status == GAME_MODE_LEVELS)
2332 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2333 else if (game_status == GAME_MODE_LEVELNR)
2334 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2335 else if (game_status == GAME_MODE_SETUP)
2336 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2337 else if (game_status == GAME_MODE_INFO)
2338 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2339 else if (game_status == GAME_MODE_SCORES)
2340 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2343 case KSYM_Page_Down:
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);
2361 case GAME_MODE_EDITOR:
2362 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2363 HandleLevelEditorKeyInput(key);
2366 case GAME_MODE_PLAYING:
2371 RequestQuitGame(setup.ask_on_escape);
2381 if (key == KSYM_Escape)
2383 SetGameStatus(GAME_MODE_MAIN);
2392 void HandleNoEvent(void)
2394 HandleMouseCursor();
2396 switch (game_status)
2398 case GAME_MODE_PLAYING:
2399 HandleButtonOrFinger(-1, -1, -1);
2404 void HandleEventActions(void)
2406 // if (button_status && game_status != GAME_MODE_PLAYING)
2407 if (button_status && (game_status != GAME_MODE_PLAYING ||
2409 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2411 HandleButton(0, 0, button_status, -button_status);
2418 if (network.enabled)
2421 switch (game_status)
2423 case GAME_MODE_MAIN:
2424 DrawPreviewLevelAnimation();
2427 case GAME_MODE_EDITOR:
2428 HandleLevelEditorIdle();
2436 static void HandleTileCursor(int dx, int dy, int button)
2439 ClearPlayerMouseAction();
2446 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2447 (dx < 0 ? MB_LEFTBUTTON :
2448 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2450 else if (!tile_cursor.moving)
2452 int old_xpos = tile_cursor.xpos;
2453 int old_ypos = tile_cursor.ypos;
2454 int new_xpos = old_xpos;
2455 int new_ypos = old_ypos;
2457 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2458 new_xpos = old_xpos + dx;
2460 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2461 new_ypos = old_ypos + dy;
2463 SetTileCursorTargetXY(new_xpos, new_ypos);
2467 static int HandleJoystickForAllPlayers(void)
2471 boolean no_joysticks_configured = TRUE;
2472 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2473 static byte joy_action_last[MAX_PLAYERS];
2475 for (i = 0; i < MAX_PLAYERS; i++)
2476 if (setup.input[i].use_joystick)
2477 no_joysticks_configured = FALSE;
2479 // if no joysticks configured, map connected joysticks to players
2480 if (no_joysticks_configured)
2481 use_as_joystick_nr = TRUE;
2483 for (i = 0; i < MAX_PLAYERS; i++)
2485 byte joy_action = 0;
2487 joy_action = JoystickExt(i, use_as_joystick_nr);
2488 result |= joy_action;
2490 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2491 joy_action != joy_action_last[i])
2492 stored_player[i].action = joy_action;
2494 joy_action_last[i] = joy_action;
2500 void HandleJoystick(void)
2502 static unsigned int joytest_delay = 0;
2503 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2504 static int joytest_last = 0;
2505 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2506 int delay_value = GADGET_FRAME_DELAY;
2507 int joystick = HandleJoystickForAllPlayers();
2508 int keyboard = key_joystick_mapping;
2509 int joy = (joystick | keyboard);
2510 int joytest = joystick;
2511 int left = joy & JOY_LEFT;
2512 int right = joy & JOY_RIGHT;
2513 int up = joy & JOY_UP;
2514 int down = joy & JOY_DOWN;
2515 int button = joy & JOY_BUTTON;
2516 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2517 int dx = (left ? -1 : right ? 1 : 0);
2518 int dy = (up ? -1 : down ? 1 : 0);
2519 boolean use_delay_value_first = (joytest != joytest_last);
2521 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2523 // do not handle this button event anymore
2527 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2528 anyTextGadgetActive()))
2530 // leave name input in main menu or text input gadget
2531 HandleKey(KSYM_Escape, KEY_PRESSED);
2532 HandleKey(KSYM_Escape, KEY_RELEASED);
2537 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2539 if (game_status == GAME_MODE_PLAYING)
2541 // when playing MM style levels, also use delay for keyboard events
2542 joytest |= keyboard;
2544 // only use first delay value for new events, but not for changed events
2545 use_delay_value_first = (!joytest != !joytest_last);
2547 // only use delay after the initial keyboard event
2551 // for any joystick or keyboard event, enable playfield tile cursor
2552 if (dx || dy || button)
2553 SetTileCursorEnabled(TRUE);
2556 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2558 // delay joystick/keyboard actions if axes/keys continually pressed
2559 newbutton = dx = dy = 0;
2563 // first start with longer delay, then continue with shorter delay
2564 joytest_delay_value =
2565 (use_delay_value_first ? delay_value_first : delay_value);
2568 joytest_last = joytest;
2570 switch (game_status)
2572 case GAME_MODE_TITLE:
2573 case GAME_MODE_MAIN:
2574 case GAME_MODE_LEVELS:
2575 case GAME_MODE_LEVELNR:
2576 case GAME_MODE_SETUP:
2577 case GAME_MODE_INFO:
2578 case GAME_MODE_SCORES:
2580 if (anyTextGadgetActive())
2583 if (game_status == GAME_MODE_TITLE)
2584 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2585 else if (game_status == GAME_MODE_MAIN)
2586 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2587 else if (game_status == GAME_MODE_LEVELS)
2588 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2589 else if (game_status == GAME_MODE_LEVELNR)
2590 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2591 else if (game_status == GAME_MODE_SETUP)
2592 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2593 else if (game_status == GAME_MODE_INFO)
2594 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2595 else if (game_status == GAME_MODE_SCORES)
2596 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2601 case GAME_MODE_PLAYING:
2603 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2604 if (tape.playing || keyboard)
2605 newbutton = ((joy & JOY_BUTTON) != 0);
2608 if (newbutton && game.all_players_gone)
2615 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2617 if (joystick & JOY_ACTION)
2618 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2620 else if (tape.recording && tape.pausing && !tape.use_mouse)
2622 if (joystick & JOY_ACTION)
2623 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2626 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2627 HandleTileCursor(dx, dy, button);
2636 void HandleSpecialGameControllerButtons(Event *event)
2641 switch (event->type)
2643 case SDL_CONTROLLERBUTTONDOWN:
2644 key_status = KEY_PRESSED;
2647 case SDL_CONTROLLERBUTTONUP:
2648 key_status = KEY_RELEASED;
2655 switch (event->cbutton.button)
2657 case SDL_CONTROLLER_BUTTON_START:
2661 case SDL_CONTROLLER_BUTTON_BACK:
2669 HandleKey(key, key_status);
2672 void HandleSpecialGameControllerKeys(Key key, int key_status)
2674 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2675 int button = SDL_CONTROLLER_BUTTON_INVALID;
2677 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2678 if (key == KSYM_Rewind)
2679 button = SDL_CONTROLLER_BUTTON_A;
2680 else if (key == KSYM_FastForward || key == KSYM_Menu)
2681 button = SDL_CONTROLLER_BUTTON_B;
2683 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2687 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2688 SDL_CONTROLLERBUTTONUP);
2690 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2691 event.cbutton.button = button;
2692 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2695 HandleJoystickEvent(&event);
2700 boolean DoKeysymAction(int keysym)
2704 Key key = (Key)(-keysym);
2706 HandleKey(key, KEY_PRESSED);
2707 HandleKey(key, KEY_RELEASED);