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 int 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);
1546 return TREE_TYPE_UNDEFINED;
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);
1557 return TREE_TYPE_UNDEFINED;
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()"
1583 return TREE_TYPE_UNDEFINED;
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 num_level_sets_succeeded,
1601 int num_artwork_sets_succeeded,
1602 int num_files_failed)
1604 // only show request dialog if no other request dialog already active
1605 if (game.request_active)
1608 // this case can happen with drag-and-drop with older SDL versions
1609 if (num_level_sets_succeeded == 0 &&
1610 num_artwork_sets_succeeded == 0 &&
1611 num_files_failed == 0)
1616 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1618 char message_part1[50];
1620 sprintf(message_part1, "New %s set%s added",
1621 (num_artwork_sets_succeeded == 0 ? "level" :
1622 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1623 (num_level_sets_succeeded +
1624 num_artwork_sets_succeeded > 1 ? "s" : ""));
1626 if (num_files_failed > 0)
1627 sprintf(message, "%s, but %d dropped file%s failed!",
1628 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1630 sprintf(message, "%s!", message_part1);
1632 else if (num_files_failed > 0)
1634 sprintf(message, "Failed to process dropped file%s!",
1635 num_files_failed > 1 ? "s" : "");
1638 Request(message, REQ_CONFIRM);
1641 void HandleDropEvent(Event *event)
1643 static boolean confirm_on_drop_complete = FALSE;
1644 static int num_level_sets_succeeded = 0;
1645 static int num_artwork_sets_succeeded = 0;
1646 static int num_files_failed = 0;
1648 switch (event->type)
1652 confirm_on_drop_complete = TRUE;
1653 num_level_sets_succeeded = 0;
1654 num_artwork_sets_succeeded = 0;
1655 num_files_failed = 0;
1662 int tree_type = HandleDropFileEvent(event->drop.file);
1664 if (tree_type == TREE_TYPE_LEVEL_DIR)
1665 num_level_sets_succeeded++;
1666 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1667 tree_type == TREE_TYPE_SOUNDS_DIR ||
1668 tree_type == TREE_TYPE_MUSIC_DIR)
1669 num_artwork_sets_succeeded++;
1673 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1674 if (!confirm_on_drop_complete)
1676 // process all remaining events, including further SDL_DROPFILE events
1679 HandleDropCompleteEvent(num_level_sets_succeeded,
1680 num_artwork_sets_succeeded,
1683 num_level_sets_succeeded = 0;
1684 num_artwork_sets_succeeded = 0;
1685 num_files_failed = 0;
1693 HandleDropTextEvent(event->drop.file);
1698 case SDL_DROPCOMPLETE:
1700 HandleDropCompleteEvent(num_level_sets_succeeded,
1701 num_artwork_sets_succeeded,
1708 if (event->drop.file != NULL)
1709 SDL_free(event->drop.file);
1712 void HandleButton(int mx, int my, int button, int button_nr)
1714 static int old_mx = 0, old_my = 0;
1715 boolean button_hold = FALSE;
1716 boolean handle_gadgets = TRUE;
1722 button_nr = -button_nr;
1731 #if defined(PLATFORM_ANDROID)
1732 // when playing, only handle gadgets when using "follow finger" controls
1733 // or when using touch controls in combination with the MM game engine
1734 // or when using gadgets that do not overlap with virtual buttons
1736 (game_status != GAME_MODE_PLAYING ||
1737 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1738 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1739 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1740 !virtual_button_pressed));
1743 if (HandleGlobalAnimClicks(mx, my, button))
1745 // do not handle this button event anymore
1746 return; // force mouse event not to be handled at all
1749 if (handle_gadgets && HandleGadgets(mx, my, button))
1751 // do not handle this button event anymore
1752 mx = my = -32; // force mouse event to be outside screen tiles
1755 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1758 // do not use scroll wheel button events for anything other than gadgets
1759 if (IS_WHEEL_BUTTON(button_nr))
1762 switch (game_status)
1764 case GAME_MODE_TITLE:
1765 HandleTitleScreen(mx, my, 0, 0, button);
1768 case GAME_MODE_MAIN:
1769 HandleMainMenu(mx, my, 0, 0, button);
1772 case GAME_MODE_PSEUDO_TYPENAME:
1773 HandleTypeName(0, KSYM_Return);
1776 case GAME_MODE_LEVELS:
1777 HandleChooseLevelSet(mx, my, 0, 0, button);
1780 case GAME_MODE_LEVELNR:
1781 HandleChooseLevelNr(mx, my, 0, 0, button);
1784 case GAME_MODE_SCORES:
1785 HandleHallOfFame(0, 0, 0, 0, button);
1788 case GAME_MODE_EDITOR:
1789 HandleLevelEditorIdle();
1792 case GAME_MODE_INFO:
1793 HandleInfoScreen(mx, my, 0, 0, button);
1796 case GAME_MODE_SETUP:
1797 HandleSetupScreen(mx, my, 0, 0, button);
1800 case GAME_MODE_PLAYING:
1801 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1802 HandleButtonOrFinger(mx, my, button);
1804 SetPlayerMouseAction(mx, my, button);
1807 if (button == MB_PRESSED && !motion_status && !button_hold &&
1808 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1809 DumpTileFromScreen(mx, my);
1819 static boolean is_string_suffix(char *string, char *suffix)
1821 int string_len = strlen(string);
1822 int suffix_len = strlen(suffix);
1824 if (suffix_len > string_len)
1827 return (strEqual(&string[string_len - suffix_len], suffix));
1830 #define MAX_CHEAT_INPUT_LEN 32
1832 static void HandleKeysSpecial(Key key)
1834 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1835 char letter = getCharFromKey(key);
1836 int cheat_input_len = strlen(cheat_input);
1842 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1844 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1845 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1847 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1850 cheat_input[cheat_input_len++] = letter;
1851 cheat_input[cheat_input_len] = '\0';
1853 #if DEBUG_EVENTS_KEY
1854 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1857 if (game_status == GAME_MODE_MAIN)
1859 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1860 is_string_suffix(cheat_input, ":ist"))
1862 InsertSolutionTape();
1864 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1865 is_string_suffix(cheat_input, ":pst"))
1869 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1870 is_string_suffix(cheat_input, ":rg"))
1872 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1875 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1876 is_string_suffix(cheat_input, ":rs"))
1878 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1881 else if (is_string_suffix(cheat_input, ":reload-music") ||
1882 is_string_suffix(cheat_input, ":rm"))
1884 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1887 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1888 is_string_suffix(cheat_input, ":ra"))
1890 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1891 1 << ARTWORK_TYPE_SOUNDS |
1892 1 << ARTWORK_TYPE_MUSIC);
1895 else if (is_string_suffix(cheat_input, ":dump-level") ||
1896 is_string_suffix(cheat_input, ":dl"))
1900 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1901 is_string_suffix(cheat_input, ":dt"))
1905 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1906 is_string_suffix(cheat_input, ":ft"))
1908 /* fix single-player tapes that contain player input for more than one
1909 player (due to a bug in 3.3.1.2 and earlier versions), which results
1910 in playing levels with more than one player in multi-player mode,
1911 even though the tape was originally recorded in single-player mode */
1913 // remove player input actions for all players but the first one
1914 for (i = 1; i < MAX_PLAYERS; i++)
1915 tape.player_participates[i] = FALSE;
1917 tape.changed = TRUE;
1919 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1920 is_string_suffix(cheat_input, ":snl"))
1922 SaveNativeLevel(&level);
1924 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1925 is_string_suffix(cheat_input, ":fps"))
1927 global.show_frames_per_second = !global.show_frames_per_second;
1930 else if (game_status == GAME_MODE_PLAYING)
1933 if (is_string_suffix(cheat_input, ".q"))
1934 DEBUG_SetMaximumDynamite();
1937 else if (game_status == GAME_MODE_EDITOR)
1939 if (is_string_suffix(cheat_input, ":dump-brush") ||
1940 is_string_suffix(cheat_input, ":DB"))
1944 else if (is_string_suffix(cheat_input, ":DDB"))
1949 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1951 if (letter == 'x') // copy brush to clipboard (small size)
1953 CopyBrushToClipboard_Small();
1955 else if (letter == 'c') // copy brush to clipboard (normal size)
1957 CopyBrushToClipboard();
1959 else if (letter == 'v') // paste brush from Clipboard
1961 CopyClipboardToBrush();
1966 // special key shortcuts for all game modes
1967 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1968 is_string_suffix(cheat_input, ":dea") ||
1969 is_string_suffix(cheat_input, ":DEA"))
1971 DumpGadgetIdentifiers();
1972 DumpScreenIdentifiers();
1976 boolean HandleKeysDebug(Key key, int key_status)
1981 if (key_status != KEY_PRESSED)
1984 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1986 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1988 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1990 if (key == setup.debug.frame_delay_key[i] &&
1991 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1993 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1994 setup.debug.frame_delay[i] : setup.game_frame_delay);
1996 if (!setup.debug.frame_delay_game_only)
1997 MenuFrameDelay = GameFrameDelay;
1999 SetVideoFrameDelay(GameFrameDelay);
2001 if (GameFrameDelay > ONE_SECOND_DELAY)
2002 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
2003 else if (GameFrameDelay != 0)
2004 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
2005 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2006 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2008 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
2015 if (game_status == GAME_MODE_PLAYING)
2019 options.debug = !options.debug;
2021 Error(ERR_INFO, "debug mode %s",
2022 (options.debug ? "enabled" : "disabled"));
2026 else if (key == KSYM_v)
2028 Error(ERR_INFO, "currently using game engine version %d",
2029 game.engine_version);
2039 void HandleKey(Key key, int key_status)
2041 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2042 static boolean ignore_repeated_key = FALSE;
2043 static struct SetupKeyboardInfo ski;
2044 static struct SetupShortcutInfo ssi;
2053 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2054 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2055 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2056 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2057 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2058 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2063 if (HandleKeysDebug(key, key_status))
2064 return; // do not handle already processed keys again
2066 // map special keys (media keys / remote control buttons) to default keys
2067 if (key == KSYM_PlayPause)
2069 else if (key == KSYM_Select)
2072 HandleSpecialGameControllerKeys(key, key_status);
2074 if (game_status == GAME_MODE_PLAYING)
2076 // only needed for single-step tape recording mode
2077 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2080 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2082 byte key_action = 0;
2083 byte key_snap_action = 0;
2085 if (setup.input[pnr].use_joystick)
2088 ski = setup.input[pnr].key;
2090 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2091 if (key == *key_info[i].key_custom)
2092 key_action |= key_info[i].action;
2094 // use combined snap+direction keys for the first player only
2097 ssi = setup.shortcut;
2099 // also remember normal snap key when handling snap+direction keys
2100 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2102 for (i = 0; i < NUM_DIRECTIONS; i++)
2104 if (key == *key_info[i].key_snap)
2106 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2107 key_snap_action |= key_info[i].action;
2112 if (key_status == KEY_PRESSED)
2114 stored_player[pnr].action |= key_action;
2115 stored_player[pnr].snap_action |= key_snap_action;
2119 stored_player[pnr].action &= ~key_action;
2120 stored_player[pnr].snap_action &= ~key_snap_action;
2123 // restore snap action if one of several pressed snap keys was released
2124 if (stored_player[pnr].snap_action)
2125 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2127 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2129 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2131 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2133 // if snap key already pressed, keep pause mode when releasing
2134 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2135 has_snapped[pnr] = TRUE;
2137 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2139 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2141 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2142 getRedDiskReleaseFlag_SP() == 0)
2144 // add a single inactive frame before dropping starts
2145 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2146 stored_player[pnr].force_dropping = TRUE;
2149 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2151 // if snap key was pressed without direction, leave pause mode
2152 if (!has_snapped[pnr])
2153 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2155 has_snapped[pnr] = FALSE;
2158 else if (tape.recording && tape.pausing && !tape.use_mouse)
2160 // prevent key release events from un-pausing a paused game
2161 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2162 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2165 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2166 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2172 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2173 if (key == key_info[i].key_default)
2174 joy |= key_info[i].action;
2179 if (key_status == KEY_PRESSED)
2180 key_joystick_mapping |= joy;
2182 key_joystick_mapping &= ~joy;
2187 if (game_status != GAME_MODE_PLAYING)
2188 key_joystick_mapping = 0;
2190 if (key_status == KEY_RELEASED)
2192 // reset flag to ignore repeated "key pressed" events after key release
2193 ignore_repeated_key = FALSE;
2198 if ((key == KSYM_F11 ||
2199 ((key == KSYM_Return ||
2200 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2201 video.fullscreen_available &&
2202 !ignore_repeated_key)
2204 setup.fullscreen = !setup.fullscreen;
2206 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2208 if (game_status == GAME_MODE_SETUP)
2209 RedrawSetupScreenAfterFullscreenToggle();
2211 // set flag to ignore repeated "key pressed" events
2212 ignore_repeated_key = TRUE;
2217 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2218 key == KSYM_minus || key == KSYM_KP_Subtract ||
2219 key == KSYM_plus || key == KSYM_KP_Add ||
2220 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2221 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2222 video.window_scaling_available &&
2223 !video.fullscreen_enabled)
2225 if (key == KSYM_0 || key == KSYM_KP_0)
2226 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2227 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2228 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2230 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2232 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2233 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2234 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2235 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2237 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2239 if (game_status == GAME_MODE_SETUP)
2240 RedrawSetupScreenAfterFullscreenToggle();
2245 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2246 key == KSYM_Return ||
2247 key == KSYM_Escape)))
2249 // do not handle this key event anymore
2250 if (key != KSYM_Escape) // always allow ESC key to be handled
2254 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2255 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2262 if (game_status == GAME_MODE_MAIN &&
2263 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2265 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2270 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2272 if (key == setup.shortcut.save_game)
2274 else if (key == setup.shortcut.load_game)
2276 else if (key == setup.shortcut.toggle_pause)
2277 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2279 HandleTapeButtonKeys(key);
2280 HandleSoundButtonKeys(key);
2283 if (game_status == GAME_MODE_PLAYING && !network_playing)
2285 int centered_player_nr_next = -999;
2287 if (key == setup.shortcut.focus_player_all)
2288 centered_player_nr_next = -1;
2290 for (i = 0; i < MAX_PLAYERS; i++)
2291 if (key == setup.shortcut.focus_player[i])
2292 centered_player_nr_next = i;
2294 if (centered_player_nr_next != -999)
2296 game.centered_player_nr_next = centered_player_nr_next;
2297 game.set_centered_player = TRUE;
2301 tape.centered_player_nr_next = game.centered_player_nr_next;
2302 tape.set_centered_player = TRUE;
2307 HandleKeysSpecial(key);
2309 if (HandleGadgetsKeyInput(key))
2310 return; // do not handle already processed keys again
2312 switch (game_status)
2314 case GAME_MODE_PSEUDO_TYPENAME:
2315 HandleTypeName(0, key);
2318 case GAME_MODE_TITLE:
2319 case GAME_MODE_MAIN:
2320 case GAME_MODE_LEVELS:
2321 case GAME_MODE_LEVELNR:
2322 case GAME_MODE_SETUP:
2323 case GAME_MODE_INFO:
2324 case GAME_MODE_SCORES:
2326 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2333 if (game_status == GAME_MODE_TITLE)
2334 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2335 else if (game_status == GAME_MODE_MAIN)
2336 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2337 else if (game_status == GAME_MODE_LEVELS)
2338 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2339 else if (game_status == GAME_MODE_LEVELNR)
2340 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2341 else if (game_status == GAME_MODE_SETUP)
2342 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2343 else if (game_status == GAME_MODE_INFO)
2344 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2345 else if (game_status == GAME_MODE_SCORES)
2346 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2350 if (game_status != GAME_MODE_MAIN)
2351 FadeSkipNextFadeIn();
2353 if (game_status == GAME_MODE_TITLE)
2354 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2355 else if (game_status == GAME_MODE_LEVELS)
2356 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2357 else if (game_status == GAME_MODE_LEVELNR)
2358 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2359 else if (game_status == GAME_MODE_SETUP)
2360 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2361 else if (game_status == GAME_MODE_INFO)
2362 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2363 else if (game_status == GAME_MODE_SCORES)
2364 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2368 if (game_status == GAME_MODE_LEVELS)
2369 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2370 else if (game_status == GAME_MODE_LEVELNR)
2371 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2372 else if (game_status == GAME_MODE_SETUP)
2373 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2374 else if (game_status == GAME_MODE_INFO)
2375 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2376 else if (game_status == GAME_MODE_SCORES)
2377 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2380 case KSYM_Page_Down:
2381 if (game_status == GAME_MODE_LEVELS)
2382 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2383 else if (game_status == GAME_MODE_LEVELNR)
2384 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2385 else if (game_status == GAME_MODE_SETUP)
2386 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2387 else if (game_status == GAME_MODE_INFO)
2388 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2389 else if (game_status == GAME_MODE_SCORES)
2390 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2398 case GAME_MODE_EDITOR:
2399 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2400 HandleLevelEditorKeyInput(key);
2403 case GAME_MODE_PLAYING:
2408 RequestQuitGame(setup.ask_on_escape);
2418 if (key == KSYM_Escape)
2420 SetGameStatus(GAME_MODE_MAIN);
2429 void HandleNoEvent(void)
2431 HandleMouseCursor();
2433 switch (game_status)
2435 case GAME_MODE_PLAYING:
2436 HandleButtonOrFinger(-1, -1, -1);
2441 void HandleEventActions(void)
2443 // if (button_status && game_status != GAME_MODE_PLAYING)
2444 if (button_status && (game_status != GAME_MODE_PLAYING ||
2446 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2448 HandleButton(0, 0, button_status, -button_status);
2455 if (network.enabled)
2458 switch (game_status)
2460 case GAME_MODE_MAIN:
2461 DrawPreviewLevelAnimation();
2464 case GAME_MODE_EDITOR:
2465 HandleLevelEditorIdle();
2473 static void HandleTileCursor(int dx, int dy, int button)
2476 ClearPlayerMouseAction();
2483 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2484 (dx < 0 ? MB_LEFTBUTTON :
2485 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2487 else if (!tile_cursor.moving)
2489 int old_xpos = tile_cursor.xpos;
2490 int old_ypos = tile_cursor.ypos;
2491 int new_xpos = old_xpos;
2492 int new_ypos = old_ypos;
2494 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2495 new_xpos = old_xpos + dx;
2497 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2498 new_ypos = old_ypos + dy;
2500 SetTileCursorTargetXY(new_xpos, new_ypos);
2504 static int HandleJoystickForAllPlayers(void)
2508 boolean no_joysticks_configured = TRUE;
2509 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2510 static byte joy_action_last[MAX_PLAYERS];
2512 for (i = 0; i < MAX_PLAYERS; i++)
2513 if (setup.input[i].use_joystick)
2514 no_joysticks_configured = FALSE;
2516 // if no joysticks configured, map connected joysticks to players
2517 if (no_joysticks_configured)
2518 use_as_joystick_nr = TRUE;
2520 for (i = 0; i < MAX_PLAYERS; i++)
2522 byte joy_action = 0;
2524 joy_action = JoystickExt(i, use_as_joystick_nr);
2525 result |= joy_action;
2527 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2528 joy_action != joy_action_last[i])
2529 stored_player[i].action = joy_action;
2531 joy_action_last[i] = joy_action;
2537 void HandleJoystick(void)
2539 static unsigned int joytest_delay = 0;
2540 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2541 static int joytest_last = 0;
2542 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2543 int delay_value = GADGET_FRAME_DELAY;
2544 int joystick = HandleJoystickForAllPlayers();
2545 int keyboard = key_joystick_mapping;
2546 int joy = (joystick | keyboard);
2547 int joytest = joystick;
2548 int left = joy & JOY_LEFT;
2549 int right = joy & JOY_RIGHT;
2550 int up = joy & JOY_UP;
2551 int down = joy & JOY_DOWN;
2552 int button = joy & JOY_BUTTON;
2553 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2554 int dx = (left ? -1 : right ? 1 : 0);
2555 int dy = (up ? -1 : down ? 1 : 0);
2556 boolean use_delay_value_first = (joytest != joytest_last);
2558 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2560 // do not handle this button event anymore
2564 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2565 anyTextGadgetActive()))
2567 // leave name input in main menu or text input gadget
2568 HandleKey(KSYM_Escape, KEY_PRESSED);
2569 HandleKey(KSYM_Escape, KEY_RELEASED);
2574 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2576 if (game_status == GAME_MODE_PLAYING)
2578 // when playing MM style levels, also use delay for keyboard events
2579 joytest |= keyboard;
2581 // only use first delay value for new events, but not for changed events
2582 use_delay_value_first = (!joytest != !joytest_last);
2584 // only use delay after the initial keyboard event
2588 // for any joystick or keyboard event, enable playfield tile cursor
2589 if (dx || dy || button)
2590 SetTileCursorEnabled(TRUE);
2593 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2595 // delay joystick/keyboard actions if axes/keys continually pressed
2596 newbutton = dx = dy = 0;
2600 // first start with longer delay, then continue with shorter delay
2601 joytest_delay_value =
2602 (use_delay_value_first ? delay_value_first : delay_value);
2605 joytest_last = joytest;
2607 switch (game_status)
2609 case GAME_MODE_TITLE:
2610 case GAME_MODE_MAIN:
2611 case GAME_MODE_LEVELS:
2612 case GAME_MODE_LEVELNR:
2613 case GAME_MODE_SETUP:
2614 case GAME_MODE_INFO:
2615 case GAME_MODE_SCORES:
2617 if (anyTextGadgetActive())
2620 if (game_status == GAME_MODE_TITLE)
2621 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2622 else if (game_status == GAME_MODE_MAIN)
2623 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2624 else if (game_status == GAME_MODE_LEVELS)
2625 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2626 else if (game_status == GAME_MODE_LEVELNR)
2627 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2628 else if (game_status == GAME_MODE_SETUP)
2629 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2630 else if (game_status == GAME_MODE_INFO)
2631 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2632 else if (game_status == GAME_MODE_SCORES)
2633 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2638 case GAME_MODE_PLAYING:
2640 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2641 if (tape.playing || keyboard)
2642 newbutton = ((joy & JOY_BUTTON) != 0);
2645 if (newbutton && game.all_players_gone)
2652 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2654 if (joystick & JOY_ACTION)
2655 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2657 else if (tape.recording && tape.pausing && !tape.use_mouse)
2659 if (joystick & JOY_ACTION)
2660 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2663 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2664 HandleTileCursor(dx, dy, button);
2673 void HandleSpecialGameControllerButtons(Event *event)
2678 switch (event->type)
2680 case SDL_CONTROLLERBUTTONDOWN:
2681 key_status = KEY_PRESSED;
2684 case SDL_CONTROLLERBUTTONUP:
2685 key_status = KEY_RELEASED;
2692 switch (event->cbutton.button)
2694 case SDL_CONTROLLER_BUTTON_START:
2698 case SDL_CONTROLLER_BUTTON_BACK:
2706 HandleKey(key, key_status);
2709 void HandleSpecialGameControllerKeys(Key key, int key_status)
2711 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2712 int button = SDL_CONTROLLER_BUTTON_INVALID;
2714 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2715 if (key == KSYM_Rewind)
2716 button = SDL_CONTROLLER_BUTTON_A;
2717 else if (key == KSYM_FastForward || key == KSYM_Menu)
2718 button = SDL_CONTROLLER_BUTTON_B;
2720 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2724 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2725 SDL_CONTROLLERBUTTONUP);
2727 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2728 event.cbutton.button = button;
2729 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2732 HandleJoystickEvent(&event);
2737 boolean DoKeysymAction(int keysym)
2741 Key key = (Key)(-keysym);
2743 HandleKey(key, KEY_PRESSED);
2744 HandleKey(key, KEY_RELEASED);