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"
25 #define DEBUG_EVENTS 0
27 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
28 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
31 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
32 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
35 static boolean cursor_inside_playfield = FALSE;
36 static boolean playfield_cursor_set = FALSE;
37 static unsigned int playfield_cursor_delay = 0;
40 /* event filter especially needed for SDL event filtering due to
41 delay problems with lots of mouse motion events when mouse button
42 not pressed (X11 can handle this with 'PointerMotionHintMask') */
44 /* event filter addition for SDL2: as SDL2 does not have a function to enable
45 or disable keyboard auto-repeat, filter repeated keyboard events instead */
47 static int FilterEventsExt(const Event *event)
51 #if defined(TARGET_SDL2)
52 /* skip repeated key press events if keyboard auto-repeat is disabled */
53 if (event->type == EVENT_KEYPRESS &&
59 /* non-motion events are directly passed to event handler functions */
60 if (event->type != EVENT_MOTIONNOTIFY)
63 motion = (MotionEvent *)event;
64 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
65 motion->y >= SY && motion->y < SY + SYSIZE);
67 if (game_status == GAME_MODE_PLAYING && playfield_cursor_set)
69 SetMouseCursor(CURSOR_DEFAULT);
70 playfield_cursor_set = FALSE;
71 DelayReached(&playfield_cursor_delay, 0);
74 /* skip mouse motion events without pressed button outside level editor */
75 if (button_status == MB_RELEASED &&
76 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
82 #if defined(TARGET_SDL2)
83 int FilterEvents(void *userdata, Event *event)
85 return FilterEventsExt(event);
88 int FilterEvents(const Event *event)
90 return FilterEventsExt(event);
94 /* to prevent delay problems, skip mouse motion events if the very next
95 event is also a mouse motion event (and therefore effectively only
96 handling the last of a row of mouse motion events in the event queue) */
98 boolean SkipPressedMouseMotionEvent(const Event *event)
100 /* nothing to do if the current event is not a mouse motion event */
101 if (event->type != EVENT_MOTIONNOTIFY)
104 /* only skip motion events with pressed button outside level editor */
105 if (button_status == MB_RELEASED ||
106 game_status == GAME_MODE_EDITOR || game_status == GAME_MODE_PLAYING)
113 PeekEvent(&next_event);
115 /* if next event is also a mouse motion event, skip the current one */
116 if (next_event.type == EVENT_MOTIONNOTIFY)
123 /* this is only really needed for non-SDL targets to filter unwanted events;
124 when using SDL with properly installed event filter, this function can be
125 replaced with a simple "NextEvent()" call, but it doesn't hurt either */
127 static boolean NextValidEvent(Event *event)
129 while (PendingEvent())
131 boolean handle_this_event = FALSE;
135 if (FilterEventsExt(event))
136 handle_this_event = TRUE;
138 if (SkipPressedMouseMotionEvent(event))
139 handle_this_event = FALSE;
141 if (handle_this_event)
152 if (PendingEvent()) /* got event */
156 while (NextValidEvent(&event))
160 case EVENT_BUTTONPRESS:
161 case EVENT_BUTTONRELEASE:
162 HandleButtonEvent((ButtonEvent *) &event);
165 case EVENT_MOTIONNOTIFY:
166 HandleMotionEvent((MotionEvent *) &event);
169 #if defined(TARGET_SDL2)
170 case SDL_WINDOWEVENT:
171 HandleWindowEvent((WindowEvent *) &event);
174 case EVENT_FINGERPRESS:
175 case EVENT_FINGERRELEASE:
176 case EVENT_FINGERMOTION:
177 HandleFingerEvent((FingerEvent *) &event);
180 case EVENT_TEXTINPUT:
181 HandleTextEvent((TextEvent *) &event);
184 case SDL_APP_WILLENTERBACKGROUND:
185 case SDL_APP_DIDENTERBACKGROUND:
186 case SDL_APP_WILLENTERFOREGROUND:
187 case SDL_APP_DIDENTERFOREGROUND:
188 HandlePauseResumeEvent((PauseResumeEvent *) &event);
193 case EVENT_KEYRELEASE:
194 HandleKeyEvent((KeyEvent *) &event);
198 HandleOtherEvents(&event);
205 /* when playing, display a special mouse pointer inside the playfield */
206 if (game_status == GAME_MODE_PLAYING && !tape.pausing)
208 if (!playfield_cursor_set && cursor_inside_playfield &&
209 DelayReached(&playfield_cursor_delay, 1000))
211 SetMouseCursor(CURSOR_PLAYFIELD);
212 playfield_cursor_set = TRUE;
215 else if (playfield_cursor_set)
217 SetMouseCursor(CURSOR_DEFAULT);
218 playfield_cursor_set = FALSE;
222 /* also execute after pending events have been processed before */
225 /* don't use all CPU time when idle; the main loop while playing
226 has its own synchronization and is CPU friendly, too */
228 if (game_status == GAME_MODE_PLAYING)
236 if (!PendingEvent()) /* delay only if no pending events */
240 /* refresh window contents from drawing buffer, if needed */
243 if (game_status == GAME_MODE_QUIT)
248 void HandleOtherEvents(Event *event)
253 HandleExposeEvent((ExposeEvent *) event);
256 case EVENT_UNMAPNOTIFY:
258 /* This causes the game to stop not only when iconified, but also
259 when on another virtual desktop, which might be not desired. */
260 SleepWhileUnmapped();
266 HandleFocusEvent((FocusChangeEvent *) event);
269 case EVENT_CLIENTMESSAGE:
270 HandleClientMessageEvent((ClientMessageEvent *) event);
273 #if defined(TARGET_SDL)
274 case SDL_JOYAXISMOTION:
275 case SDL_JOYBUTTONDOWN:
276 case SDL_JOYBUTTONUP:
277 HandleJoystickEvent(event);
281 HandleWindowManagerEvent(event);
290 void ClearEventQueue()
292 while (PendingEvent())
300 case EVENT_BUTTONRELEASE:
301 button_status = MB_RELEASED;
304 case EVENT_KEYRELEASE:
309 HandleOtherEvents(&event);
315 void ClearPlayerAction()
319 /* simulate key release events for still pressed keys */
320 key_joystick_mapping = 0;
321 for (i = 0; i < MAX_PLAYERS; i++)
322 stored_player[i].action = 0;
325 void SleepWhileUnmapped()
327 boolean window_unmapped = TRUE;
329 KeyboardAutoRepeatOn();
331 while (window_unmapped)
339 case EVENT_BUTTONRELEASE:
340 button_status = MB_RELEASED;
343 case EVENT_KEYRELEASE:
344 key_joystick_mapping = 0;
347 case EVENT_MAPNOTIFY:
348 window_unmapped = FALSE;
351 case EVENT_UNMAPNOTIFY:
352 /* this is only to surely prevent the 'should not happen' case
353 * of recursively looping between 'SleepWhileUnmapped()' and
354 * 'HandleOtherEvents()' which usually calls this funtion.
359 HandleOtherEvents(&event);
364 if (game_status == GAME_MODE_PLAYING)
365 KeyboardAutoRepeatOffUnlessAutoplay();
368 void HandleExposeEvent(ExposeEvent *event)
372 void HandleButtonEvent(ButtonEvent *event)
374 #if DEBUG_EVENTS_BUTTON
375 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
377 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
381 motion_status = FALSE;
383 if (event->type == EVENT_BUTTONPRESS)
384 button_status = event->button;
386 button_status = MB_RELEASED;
388 HandleButton(event->x, event->y, button_status, event->button);
391 void HandleMotionEvent(MotionEvent *event)
393 if (!PointerInWindow(window))
394 return; /* window and pointer are on different screens */
396 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
399 motion_status = TRUE;
401 #if DEBUG_EVENTS_MOTION
402 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
403 button_status, event->x, event->y);
406 HandleButton(event->x, event->y, button_status, button_status);
409 #if defined(TARGET_SDL2)
411 void HandleWindowEvent(WindowEvent *event)
413 #if DEBUG_EVENTS_WINDOW
414 int subtype = event->event;
417 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
418 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
419 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
420 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
421 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
422 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
423 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
424 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
425 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
426 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
427 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
428 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
429 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
430 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
433 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
434 event_name, event->data1, event->data2);
437 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
438 event->event == SDL_WINDOWEVENT_RESIZED ||
439 event->event == SDL_WINDOWEVENT_EXPOSED)
442 if (event->event == SDL_WINDOWEVENT_RESIZED && !video.fullscreen_enabled)
444 int new_window_width = event->data1;
445 int new_window_height = event->data2;
447 // if window size has changed after resizing, calculate new scaling factor
448 if (new_window_width != video.window_width ||
449 new_window_height != video.window_height)
451 int new_xpercent = (100 * new_window_width / video.width);
452 int new_ypercent = (100 * new_window_height / video.height);
454 setup.window_scaling_percent = video.window_scaling_percent =
455 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, MIN(new_xpercent, new_ypercent)),
456 MAX_WINDOW_SCALING_PERCENT);
458 video.window_width = new_window_width;
459 video.window_height = new_window_height;
461 if (game_status == GAME_MODE_SETUP)
462 RedrawSetupScreenAfterFullscreenToggle();
469 #define NUM_TOUCH_FINGERS 3
474 SDL_FingerID finger_id;
477 } touch_info[NUM_TOUCH_FINGERS];
479 void HandleFingerEvent(FingerEvent *event)
481 static Key motion_key_x = KSYM_UNDEFINED;
482 static Key motion_key_y = KSYM_UNDEFINED;
483 static Key button_key = KSYM_UNDEFINED;
484 static float motion_x1, motion_y1;
485 static float button_x1, button_y1;
486 static SDL_FingerID motion_id = -1;
487 static SDL_FingerID button_id = -1;
488 int move_trigger_distance_percent = 2; // percent of touchpad width/height
489 int drop_trigger_distance_percent = 5; // percent of touchpad width/height
490 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
491 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
492 float event_x = event->x;
493 float event_y = event->y;
495 #if DEBUG_EVENTS_FINGER
496 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
497 event->type == EVENT_FINGERPRESS ? "pressed" :
498 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
502 event->dx, event->dy,
506 if (game_status != GAME_MODE_PLAYING)
509 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
511 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
513 Key key = (event->x < 1.0 / 3.0 ?
514 (event->y < 1.0 / 2.0 ? setup.input[0].key.snap :
515 setup.input[0].key.drop) :
516 event->x > 2.0 / 3.0 ?
517 (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
518 event->y > 2.0 / 3.0 ? setup.input[0].key.down :
519 event->x < 5.0 / 6.0 ? setup.input[0].key.left :
520 setup.input[0].key.right) :
522 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
526 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
527 getKeyNameFromKey(key), key_status_name, event->fingerId);
529 // check if we already know this touch event's finger id
530 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
532 if (touch_info[i].touched &&
533 touch_info[i].finger_id == event->fingerId)
535 // Error(ERR_DEBUG, "MARK 1: %d", i);
541 if (i >= NUM_TOUCH_FINGERS)
543 if (key_status == KEY_PRESSED)
545 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
547 // unknown finger id -- get new, empty slot, if available
548 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
550 if (touch_info[i].counter < oldest_counter)
553 oldest_counter = touch_info[i].counter;
555 // Error(ERR_DEBUG, "MARK 2: %d", i);
558 if (!touch_info[i].touched)
560 // Error(ERR_DEBUG, "MARK 3: %d", i);
566 if (i >= NUM_TOUCH_FINGERS)
568 // all slots allocated -- use oldest slot
571 // Error(ERR_DEBUG, "MARK 4: %d", i);
576 // release of previously unknown key (should not happen)
578 if (key != KSYM_UNDEFINED)
580 HandleKey(key, KEY_RELEASED);
582 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
583 getKeyNameFromKey(key), "KEY_RELEASED", i);
588 if (i < NUM_TOUCH_FINGERS)
590 if (key_status == KEY_PRESSED)
592 if (touch_info[i].key != key)
594 if (touch_info[i].key != KSYM_UNDEFINED)
596 HandleKey(touch_info[i].key, KEY_RELEASED);
598 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
599 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
602 if (key != KSYM_UNDEFINED)
604 HandleKey(key, KEY_PRESSED);
606 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
607 getKeyNameFromKey(key), "KEY_PRESSED", i);
611 touch_info[i].touched = TRUE;
612 touch_info[i].finger_id = event->fingerId;
613 touch_info[i].counter = Counter();
614 touch_info[i].key = key;
618 if (touch_info[i].key != KSYM_UNDEFINED)
620 HandleKey(touch_info[i].key, KEY_RELEASED);
622 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
623 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
626 touch_info[i].touched = FALSE;
627 touch_info[i].finger_id = 0;
628 touch_info[i].counter = 0;
629 touch_info[i].key = 0;
636 // use touch direction control
638 if (event->type == EVENT_FINGERPRESS)
640 if (event_x > 1.0 / 3.0)
644 motion_id = event->fingerId;
649 motion_key_x = KSYM_UNDEFINED;
650 motion_key_y = KSYM_UNDEFINED;
652 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
658 button_id = event->fingerId;
663 button_key = setup.input[0].key.snap;
665 HandleKey(button_key, KEY_PRESSED);
667 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
670 else if (event->type == EVENT_FINGERRELEASE)
672 if (event->fingerId == motion_id)
676 if (motion_key_x != KSYM_UNDEFINED)
677 HandleKey(motion_key_x, KEY_RELEASED);
678 if (motion_key_y != KSYM_UNDEFINED)
679 HandleKey(motion_key_y, KEY_RELEASED);
681 motion_key_x = KSYM_UNDEFINED;
682 motion_key_y = KSYM_UNDEFINED;
684 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
686 else if (event->fingerId == button_id)
690 if (button_key != KSYM_UNDEFINED)
691 HandleKey(button_key, KEY_RELEASED);
693 button_key = KSYM_UNDEFINED;
695 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
698 else if (event->type == EVENT_FINGERMOTION)
700 if (event->fingerId == motion_id)
702 float distance_x = ABS(event_x - motion_x1);
703 float distance_y = ABS(event_y - motion_y1);
704 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
705 event_x > motion_x1 ? setup.input[0].key.right :
707 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
708 event_y > motion_y1 ? setup.input[0].key.down :
711 if (distance_x < move_trigger_distance / 2 ||
712 distance_x < distance_y)
713 new_motion_key_x = KSYM_UNDEFINED;
715 if (distance_y < move_trigger_distance / 2 ||
716 distance_y < distance_x)
717 new_motion_key_y = KSYM_UNDEFINED;
719 if (distance_x > move_trigger_distance ||
720 distance_y > move_trigger_distance)
722 if (new_motion_key_x != motion_key_x)
724 if (motion_key_x != KSYM_UNDEFINED)
725 HandleKey(motion_key_x, KEY_RELEASED);
726 if (new_motion_key_x != KSYM_UNDEFINED)
727 HandleKey(new_motion_key_x, KEY_PRESSED);
730 if (new_motion_key_y != motion_key_y)
732 if (motion_key_y != KSYM_UNDEFINED)
733 HandleKey(motion_key_y, KEY_RELEASED);
734 if (new_motion_key_y != KSYM_UNDEFINED)
735 HandleKey(new_motion_key_y, KEY_PRESSED);
741 motion_key_x = new_motion_key_x;
742 motion_key_y = new_motion_key_y;
744 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
747 else if (event->fingerId == button_id)
749 float distance_x = ABS(event_x - button_x1);
750 float distance_y = ABS(event_y - button_y1);
752 if (distance_x < drop_trigger_distance / 2 &&
753 distance_y > drop_trigger_distance)
755 if (button_key == setup.input[0].key.snap)
756 HandleKey(button_key, KEY_RELEASED);
761 button_key = setup.input[0].key.drop;
763 HandleKey(button_key, KEY_PRESSED);
765 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
771 static boolean checkTextInputKeyModState()
773 // when playing, only handle raw key events and ignore text input
774 if (game_status == GAME_MODE_PLAYING)
777 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
780 void HandleTextEvent(TextEvent *event)
782 char *text = event->text;
783 Key key = getKeyFromKeyName(text);
785 #if DEBUG_EVENTS_TEXT
786 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
789 text[0], (int)(text[0]),
791 getKeyNameFromKey(key),
795 #if defined(PLATFORM_ANDROID)
796 if (game_status == GAME_MODE_PSEUDO_TYPENAME)
798 HandleTypeName(0, key);
804 // only handle key input with text modifier keys pressed
805 if (checkTextInputKeyModState())
807 HandleKey(key, KEY_PRESSED);
808 HandleKey(key, KEY_RELEASED);
812 void HandlePauseResumeEvent(PauseResumeEvent *event)
814 if (event->type == SDL_APP_WILLENTERBACKGROUND)
818 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
826 void HandleKeyEvent(KeyEvent *event)
828 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
829 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
830 Key key = GetEventKey(event, with_modifiers);
831 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
834 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
835 event->type == EVENT_KEYPRESS ? "pressed" : "released",
836 event->keysym.scancode,
841 getKeyNameFromKey(key));
844 #if defined(PLATFORM_ANDROID)
845 // always map the "back" button to the "escape" key on Android devices
846 if (key == KSYM_Back)
850 HandleKeyModState(keymod, key_status);
852 #if defined(TARGET_SDL2)
853 // only handle raw key input without text modifier keys pressed
854 if (!checkTextInputKeyModState())
855 HandleKey(key, key_status);
857 HandleKey(key, key_status);
861 void HandleFocusEvent(FocusChangeEvent *event)
863 static int old_joystick_status = -1;
865 if (event->type == EVENT_FOCUSOUT)
867 KeyboardAutoRepeatOn();
868 old_joystick_status = joystick.status;
869 joystick.status = JOYSTICK_NOT_AVAILABLE;
873 else if (event->type == EVENT_FOCUSIN)
875 /* When there are two Rocks'n'Diamonds windows which overlap and
876 the player moves the pointer from one game window to the other,
877 a 'FocusOut' event is generated for the window the pointer is
878 leaving and a 'FocusIn' event is generated for the window the
879 pointer is entering. In some cases, it can happen that the
880 'FocusIn' event is handled by the one game process before the
881 'FocusOut' event by the other game process. In this case the
882 X11 environment would end up with activated keyboard auto repeat,
883 because unfortunately this is a global setting and not (which
884 would be far better) set for each X11 window individually.
885 The effect would be keyboard auto repeat while playing the game
886 (game_status == GAME_MODE_PLAYING), which is not desired.
887 To avoid this special case, we just wait 1/10 second before
888 processing the 'FocusIn' event.
891 if (game_status == GAME_MODE_PLAYING)
894 KeyboardAutoRepeatOffUnlessAutoplay();
897 if (old_joystick_status != -1)
898 joystick.status = old_joystick_status;
902 void HandleClientMessageEvent(ClientMessageEvent *event)
904 if (CheckCloseWindowEvent(event))
908 void HandleWindowManagerEvent(Event *event)
910 #if defined(TARGET_SDL)
911 SDLHandleWindowManagerEvent(event);
915 void HandleButton(int mx, int my, int button, int button_nr)
917 static int old_mx = 0, old_my = 0;
931 #if defined(PLATFORM_ANDROID)
932 if (game_status != GAME_MODE_PLAYING &&
933 HandleGadgets(mx, my, button))
935 /* do not handle this button event anymore */
936 mx = my = -32; /* force mouse event to be outside screen tiles */
939 if (HandleGadgets(mx, my, button))
941 /* do not handle this button event anymore */
942 mx = my = -32; /* force mouse event to be outside screen tiles */
946 /* do not use scroll wheel button events for anything other than gadgets */
947 if (IS_WHEEL_BUTTON(button_nr))
952 case GAME_MODE_TITLE:
953 HandleTitleScreen(mx, my, 0, 0, button);
957 HandleMainMenu(mx, my, 0, 0, button);
960 case GAME_MODE_PSEUDO_TYPENAME:
961 HandleTypeName(0, KSYM_Return);
964 case GAME_MODE_LEVELS:
965 HandleChooseLevelSet(mx, my, 0, 0, button);
968 case GAME_MODE_LEVELNR:
969 HandleChooseLevelNr(mx, my, 0, 0, button);
972 case GAME_MODE_SCORES:
973 HandleHallOfFame(0, 0, 0, 0, button);
976 case GAME_MODE_EDITOR:
977 HandleLevelEditorIdle();
981 HandleInfoScreen(mx, my, 0, 0, button);
984 case GAME_MODE_SETUP:
985 HandleSetupScreen(mx, my, 0, 0, button);
988 case GAME_MODE_PLAYING:
990 if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my))
991 DumpTile(LEVELX((mx - SX) / TILESIZE_VAR),
992 LEVELY((my - SY) / TILESIZE_VAR));
993 // DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
1002 static boolean is_string_suffix(char *string, char *suffix)
1004 int string_len = strlen(string);
1005 int suffix_len = strlen(suffix);
1007 if (suffix_len > string_len)
1010 return (strEqual(&string[string_len - suffix_len], suffix));
1013 #define MAX_CHEAT_INPUT_LEN 32
1015 static void HandleKeysSpecial(Key key)
1017 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1018 char letter = getCharFromKey(key);
1019 int cheat_input_len = strlen(cheat_input);
1025 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1027 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1028 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1030 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1033 cheat_input[cheat_input_len++] = letter;
1034 cheat_input[cheat_input_len] = '\0';
1036 #if DEBUG_EVENTS_KEY
1037 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1040 if (game_status == GAME_MODE_MAIN)
1042 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1043 is_string_suffix(cheat_input, ":ist"))
1045 InsertSolutionTape();
1047 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1048 is_string_suffix(cheat_input, ":rg"))
1050 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1053 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1054 is_string_suffix(cheat_input, ":rs"))
1056 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1059 else if (is_string_suffix(cheat_input, ":reload-music") ||
1060 is_string_suffix(cheat_input, ":rm"))
1062 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1065 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1066 is_string_suffix(cheat_input, ":ra"))
1068 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1069 1 << ARTWORK_TYPE_SOUNDS |
1070 1 << ARTWORK_TYPE_MUSIC);
1073 else if (is_string_suffix(cheat_input, ":dump-level") ||
1074 is_string_suffix(cheat_input, ":dl"))
1078 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1079 is_string_suffix(cheat_input, ":dt"))
1083 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1084 is_string_suffix(cheat_input, ":ft"))
1086 /* fix single-player tapes that contain player input for more than one
1087 player (due to a bug in 3.3.1.2 and earlier versions), which results
1088 in playing levels with more than one player in multi-player mode,
1089 even though the tape was originally recorded in single-player mode */
1091 /* remove player input actions for all players but the first one */
1092 for (i = 1; i < MAX_PLAYERS; i++)
1093 tape.player_participates[i] = FALSE;
1095 tape.changed = TRUE;
1097 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1098 is_string_suffix(cheat_input, ":snl"))
1100 SaveNativeLevel(&level);
1103 else if (game_status == GAME_MODE_PLAYING)
1106 if (is_string_suffix(cheat_input, ".q"))
1107 DEBUG_SetMaximumDynamite();
1110 else if (game_status == GAME_MODE_EDITOR)
1112 if (is_string_suffix(cheat_input, ":dump-brush") ||
1113 is_string_suffix(cheat_input, ":DB"))
1117 else if (is_string_suffix(cheat_input, ":DDB"))
1124 void HandleKey(Key key, int key_status)
1126 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1127 static struct SetupKeyboardInfo ski;
1128 static struct SetupShortcutInfo ssi;
1137 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1138 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1139 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1140 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1141 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1142 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1147 if (game_status == GAME_MODE_PLAYING)
1149 /* only needed for single-step tape recording mode */
1150 static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1151 static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1152 static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1153 static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1156 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1158 byte key_action = 0;
1160 if (setup.input[pnr].use_joystick)
1163 ski = setup.input[pnr].key;
1165 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1166 if (key == *key_info[i].key_custom)
1167 key_action |= key_info[i].action;
1169 /* use combined snap+direction keys for the first player only */
1172 ssi = setup.shortcut;
1174 for (i = 0; i < NUM_DIRECTIONS; i++)
1175 if (key == *key_info[i].key_snap)
1176 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1179 /* clear delayed snap and drop actions in single step mode (see below) */
1180 if (tape.single_step)
1182 if (clear_snap_button[pnr])
1184 stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1185 clear_snap_button[pnr] = FALSE;
1188 if (clear_drop_button[pnr])
1190 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1191 clear_drop_button[pnr] = FALSE;
1195 if (key_status == KEY_PRESSED)
1196 stored_player[pnr].action |= key_action;
1198 stored_player[pnr].action &= ~key_action;
1200 if (tape.single_step && tape.recording && tape.pausing)
1202 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1204 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1206 /* if snap key already pressed, don't snap when releasing (below) */
1207 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1208 element_snapped[pnr] = TRUE;
1210 /* if drop key already pressed, don't drop when releasing (below) */
1211 if (stored_player[pnr].action & KEY_BUTTON_DROP)
1212 element_dropped[pnr] = TRUE;
1214 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1216 if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1217 level.game_engine_type == GAME_ENGINE_TYPE_SP)
1220 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1221 getRedDiskReleaseFlag_SP() == 0)
1222 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1224 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1227 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1229 if (key_action & KEY_BUTTON_SNAP)
1231 /* if snap key was released without moving (see above), snap now */
1232 if (!element_snapped[pnr])
1234 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1236 stored_player[pnr].action |= KEY_BUTTON_SNAP;
1238 /* clear delayed snap button on next event */
1239 clear_snap_button[pnr] = TRUE;
1242 element_snapped[pnr] = FALSE;
1245 if (key_action & KEY_BUTTON_DROP &&
1246 level.game_engine_type == GAME_ENGINE_TYPE_RND)
1248 /* if drop key was released without moving (see above), drop now */
1249 if (!element_dropped[pnr])
1251 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1253 if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1254 getRedDiskReleaseFlag_SP() != 0)
1255 stored_player[pnr].action |= KEY_BUTTON_DROP;
1257 /* clear delayed drop button on next event */
1258 clear_drop_button[pnr] = TRUE;
1261 element_dropped[pnr] = FALSE;
1265 else if (tape.recording && tape.pausing)
1267 /* prevent key release events from un-pausing a paused game */
1268 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1269 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1275 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1276 if (key == key_info[i].key_default)
1277 joy |= key_info[i].action;
1282 if (key_status == KEY_PRESSED)
1283 key_joystick_mapping |= joy;
1285 key_joystick_mapping &= ~joy;
1290 if (game_status != GAME_MODE_PLAYING)
1291 key_joystick_mapping = 0;
1293 if (key_status == KEY_RELEASED)
1296 if ((key == KSYM_F11 ||
1297 ((key == KSYM_Return ||
1298 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1299 video.fullscreen_available)
1301 setup.fullscreen = !setup.fullscreen;
1303 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1305 if (game_status == GAME_MODE_SETUP)
1306 RedrawSetupScreenAfterFullscreenToggle();
1311 if ((key == KSYM_minus ||
1314 ((GetKeyModState() & KMOD_Control) ||
1315 (GetKeyModState() & KMOD_Alt)) &&
1316 video.window_scaling_available &&
1317 !video.fullscreen_enabled)
1320 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1322 setup.window_scaling_percent +=
1323 (key == KSYM_minus ? -1 : +1) * STEP_WINDOW_SCALING_PERCENT;
1325 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1326 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1327 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1328 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1330 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1332 if (game_status == GAME_MODE_SETUP)
1333 RedrawSetupScreenAfterFullscreenToggle();
1338 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1339 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1346 if (game_status == GAME_MODE_MAIN &&
1347 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1349 StartGameActions(options.network, setup.autorecord, level.random_seed);
1354 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1356 if (key == setup.shortcut.save_game)
1358 else if (key == setup.shortcut.load_game)
1360 else if (key == setup.shortcut.toggle_pause)
1361 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1363 HandleTapeButtonKeys(key);
1364 HandleSoundButtonKeys(key);
1367 if (game_status == GAME_MODE_PLAYING && !network_playing)
1369 int centered_player_nr_next = -999;
1371 if (key == setup.shortcut.focus_player_all)
1372 centered_player_nr_next = -1;
1374 for (i = 0; i < MAX_PLAYERS; i++)
1375 if (key == setup.shortcut.focus_player[i])
1376 centered_player_nr_next = i;
1378 if (centered_player_nr_next != -999)
1380 game.centered_player_nr_next = centered_player_nr_next;
1381 game.set_centered_player = TRUE;
1385 tape.centered_player_nr_next = game.centered_player_nr_next;
1386 tape.set_centered_player = TRUE;
1391 HandleKeysSpecial(key);
1393 if (HandleGadgetsKeyInput(key))
1395 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1396 key = KSYM_UNDEFINED;
1399 switch (game_status)
1401 case GAME_MODE_PSEUDO_TYPENAME:
1402 HandleTypeName(0, key);
1405 case GAME_MODE_TITLE:
1406 case GAME_MODE_MAIN:
1407 case GAME_MODE_LEVELS:
1408 case GAME_MODE_LEVELNR:
1409 case GAME_MODE_SETUP:
1410 case GAME_MODE_INFO:
1411 case GAME_MODE_SCORES:
1416 if (game_status == GAME_MODE_TITLE)
1417 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1418 else if (game_status == GAME_MODE_MAIN)
1419 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1420 else if (game_status == GAME_MODE_LEVELS)
1421 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1422 else if (game_status == GAME_MODE_LEVELNR)
1423 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1424 else if (game_status == GAME_MODE_SETUP)
1425 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1426 else if (game_status == GAME_MODE_INFO)
1427 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1428 else if (game_status == GAME_MODE_SCORES)
1429 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1433 if (game_status != GAME_MODE_MAIN)
1434 FadeSkipNextFadeIn();
1436 if (game_status == GAME_MODE_TITLE)
1437 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1438 else if (game_status == GAME_MODE_LEVELS)
1439 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1440 else if (game_status == GAME_MODE_LEVELNR)
1441 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1442 else if (game_status == GAME_MODE_SETUP)
1443 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1444 else if (game_status == GAME_MODE_INFO)
1445 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1446 else if (game_status == GAME_MODE_SCORES)
1447 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1451 if (game_status == GAME_MODE_LEVELS)
1452 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1453 else if (game_status == GAME_MODE_LEVELNR)
1454 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1455 else if (game_status == GAME_MODE_SETUP)
1456 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1457 else if (game_status == GAME_MODE_INFO)
1458 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1459 else if (game_status == GAME_MODE_SCORES)
1460 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1463 case KSYM_Page_Down:
1464 if (game_status == GAME_MODE_LEVELS)
1465 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1466 else if (game_status == GAME_MODE_LEVELNR)
1467 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1468 else if (game_status == GAME_MODE_SETUP)
1469 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1470 else if (game_status == GAME_MODE_INFO)
1471 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1472 else if (game_status == GAME_MODE_SCORES)
1473 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1478 GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
1482 setup.sp_show_border_elements = !setup.sp_show_border_elements;
1483 printf("Supaplex border elements %s\n",
1484 setup.sp_show_border_elements ? "enabled" : "disabled");
1493 case GAME_MODE_EDITOR:
1494 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1495 HandleLevelEditorKeyInput(key);
1498 case GAME_MODE_PLAYING:
1503 RequestQuitGame(setup.ask_on_escape);
1510 if (GameFrameDelay == 500)
1511 GameFrameDelay = GAME_FRAME_DELAY;
1513 GameFrameDelay = 500;
1516 GameFrameDelay = (key - KSYM_0) * 10;
1517 printf("Game speed == %d%% (%d ms delay between two frames)\n",
1518 GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
1524 options.debug = FALSE;
1525 printf("debug mode disabled\n");
1529 options.debug = TRUE;
1530 printf("debug mode enabled\n");
1536 if (!global.fps_slowdown)
1538 global.fps_slowdown = TRUE;
1539 global.fps_slowdown_factor = 2;
1540 printf("fps slowdown enabled -- display only every 2nd frame\n");
1542 else if (global.fps_slowdown_factor == 2)
1544 global.fps_slowdown_factor = 4;
1545 printf("fps slowdown enabled -- display only every 4th frame\n");
1549 global.fps_slowdown = FALSE;
1550 global.fps_slowdown_factor = 1;
1551 printf("fps slowdown disabled\n");
1557 printf("::: currently using game engine version %d\n",
1558 game.engine_version);
1569 if (key == KSYM_Escape)
1571 game_status = GAME_MODE_MAIN;
1579 void HandleNoEvent()
1581 if (button_status && game_status != GAME_MODE_PLAYING)
1583 HandleButton(0, 0, -button_status, button_status);
1590 #if defined(NETWORK_AVALIABLE)
1591 if (options.network)
1595 switch (game_status)
1597 case GAME_MODE_MAIN:
1598 DrawPreviewLevelAnimation();
1602 case GAME_MODE_LEVELS:
1603 case GAME_MODE_LEVELNR:
1604 case GAME_MODE_SETUP:
1605 case GAME_MODE_INFO:
1606 case GAME_MODE_SCORES:
1610 case GAME_MODE_EDITOR:
1611 HandleLevelEditorIdle();
1619 static int HandleJoystickForAllPlayers()
1624 for (i = 0; i < MAX_PLAYERS; i++)
1626 byte joy_action = 0;
1629 if (!setup.input[i].use_joystick)
1633 joy_action = Joystick(i);
1634 result |= joy_action;
1636 if (!setup.input[i].use_joystick)
1639 stored_player[i].action = joy_action;
1645 void HandleJoystick()
1647 int joystick = HandleJoystickForAllPlayers();
1648 int keyboard = key_joystick_mapping;
1649 int joy = (joystick | keyboard);
1650 int left = joy & JOY_LEFT;
1651 int right = joy & JOY_RIGHT;
1652 int up = joy & JOY_UP;
1653 int down = joy & JOY_DOWN;
1654 int button = joy & JOY_BUTTON;
1655 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1656 int dx = (left ? -1 : right ? 1 : 0);
1657 int dy = (up ? -1 : down ? 1 : 0);
1659 switch (game_status)
1661 case GAME_MODE_TITLE:
1662 case GAME_MODE_MAIN:
1663 case GAME_MODE_LEVELS:
1664 case GAME_MODE_LEVELNR:
1665 case GAME_MODE_SETUP:
1666 case GAME_MODE_INFO:
1668 static unsigned int joystickmove_delay = 0;
1670 if (joystick && !button &&
1671 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1672 newbutton = dx = dy = 0;
1674 if (game_status == GAME_MODE_TITLE)
1675 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1676 else if (game_status == GAME_MODE_MAIN)
1677 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1678 else if (game_status == GAME_MODE_LEVELS)
1679 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1680 else if (game_status == GAME_MODE_LEVELNR)
1681 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1682 else if (game_status == GAME_MODE_SETUP)
1683 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1684 else if (game_status == GAME_MODE_INFO)
1685 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1689 case GAME_MODE_SCORES:
1690 HandleHallOfFame(0, 0, dx, dy, !newbutton);
1693 case GAME_MODE_PLAYING:
1694 if (tape.playing || keyboard)
1695 newbutton = ((joy & JOY_BUTTON) != 0);
1697 if (newbutton && AllPlayersGone)