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)
234 if (!PendingEvent()) /* delay only if no pending events */
238 /* refresh window contents from drawing buffer, if needed */
241 if (game_status == GAME_MODE_QUIT)
246 void HandleOtherEvents(Event *event)
251 HandleExposeEvent((ExposeEvent *) event);
254 case EVENT_UNMAPNOTIFY:
256 /* This causes the game to stop not only when iconified, but also
257 when on another virtual desktop, which might be not desired. */
258 SleepWhileUnmapped();
264 HandleFocusEvent((FocusChangeEvent *) event);
267 case EVENT_CLIENTMESSAGE:
268 HandleClientMessageEvent((ClientMessageEvent *) event);
271 #if defined(TARGET_SDL)
272 case SDL_JOYAXISMOTION:
273 case SDL_JOYBUTTONDOWN:
274 case SDL_JOYBUTTONUP:
275 HandleJoystickEvent(event);
279 HandleWindowManagerEvent(event);
288 void ClearEventQueue()
290 while (PendingEvent())
298 case EVENT_BUTTONRELEASE:
299 button_status = MB_RELEASED;
302 case EVENT_KEYRELEASE:
307 HandleOtherEvents(&event);
313 void ClearPlayerAction()
317 /* simulate key release events for still pressed keys */
318 key_joystick_mapping = 0;
319 for (i = 0; i < MAX_PLAYERS; i++)
320 stored_player[i].action = 0;
323 void SleepWhileUnmapped()
325 boolean window_unmapped = TRUE;
327 KeyboardAutoRepeatOn();
329 while (window_unmapped)
337 case EVENT_BUTTONRELEASE:
338 button_status = MB_RELEASED;
341 case EVENT_KEYRELEASE:
342 key_joystick_mapping = 0;
345 case EVENT_MAPNOTIFY:
346 window_unmapped = FALSE;
349 case EVENT_UNMAPNOTIFY:
350 /* this is only to surely prevent the 'should not happen' case
351 * of recursively looping between 'SleepWhileUnmapped()' and
352 * 'HandleOtherEvents()' which usually calls this funtion.
357 HandleOtherEvents(&event);
362 if (game_status == GAME_MODE_PLAYING)
363 KeyboardAutoRepeatOffUnlessAutoplay();
366 void HandleExposeEvent(ExposeEvent *event)
370 void HandleButtonEvent(ButtonEvent *event)
372 #if DEBUG_EVENTS_BUTTON
373 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
375 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
379 motion_status = FALSE;
381 if (event->type == EVENT_BUTTONPRESS)
382 button_status = event->button;
384 button_status = MB_RELEASED;
386 HandleButton(event->x, event->y, button_status, event->button);
389 void HandleMotionEvent(MotionEvent *event)
391 if (!PointerInWindow(window))
392 return; /* window and pointer are on different screens */
394 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
397 motion_status = TRUE;
399 #if DEBUG_EVENTS_MOTION
400 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
401 button_status, event->x, event->y);
404 HandleButton(event->x, event->y, button_status, button_status);
407 #if defined(TARGET_SDL2)
409 void HandleWindowEvent(WindowEvent *event)
411 #if DEBUG_EVENTS_WINDOW
412 int subtype = event->event;
415 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
416 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
417 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
418 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
419 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
420 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
421 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
422 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
423 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
424 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
425 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
426 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
427 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
428 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
431 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
432 event_name, event->data1, event->data2);
435 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
436 event->event == SDL_WINDOWEVENT_RESIZED ||
437 event->event == SDL_WINDOWEVENT_EXPOSED)
440 if (event->event == SDL_WINDOWEVENT_RESIZED && !video.fullscreen_enabled)
442 int new_window_width = event->data1;
443 int new_window_height = event->data2;
445 // if window size has changed after resizing, calculate new scaling factor
446 if (new_window_width != video.window_width ||
447 new_window_height != video.window_height)
449 int new_xpercent = (100 * new_window_width / video.width);
450 int new_ypercent = (100 * new_window_height / video.height);
452 setup.window_scaling_percent = video.window_scaling_percent =
453 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, MIN(new_xpercent, new_ypercent)),
454 MAX_WINDOW_SCALING_PERCENT);
456 video.window_width = new_window_width;
457 video.window_height = new_window_height;
459 if (game_status == GAME_MODE_SETUP)
460 RedrawSetupScreenAfterFullscreenToggle();
467 #define NUM_TOUCH_FINGERS 3
472 SDL_FingerID finger_id;
475 } touch_info[NUM_TOUCH_FINGERS];
477 void HandleFingerEvent(FingerEvent *event)
479 static Key motion_key_x = KSYM_UNDEFINED;
480 static Key motion_key_y = KSYM_UNDEFINED;
481 static Key button_key = KSYM_UNDEFINED;
482 static float motion_x1, motion_y1;
483 static float button_x1, button_y1;
484 static SDL_FingerID motion_id = -1;
485 static SDL_FingerID button_id = -1;
486 int move_trigger_distance_percent = 2; // percent of touchpad width/height
487 int drop_trigger_distance_percent = 5; // percent of touchpad width/height
488 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
489 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
490 float event_x = event->x;
491 float event_y = event->y;
493 #if DEBUG_EVENTS_FINGER
494 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
495 event->type == EVENT_FINGERPRESS ? "pressed" :
496 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
500 event->dx, event->dy,
504 if (game_status != GAME_MODE_PLAYING)
507 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
509 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
511 Key key = (event->x < 1.0 / 3.0 ?
512 (event->y < 1.0 / 2.0 ? setup.input[0].key.snap :
513 setup.input[0].key.drop) :
514 event->x > 2.0 / 3.0 ?
515 (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
516 event->y > 2.0 / 3.0 ? setup.input[0].key.down :
517 event->x < 5.0 / 6.0 ? setup.input[0].key.left :
518 setup.input[0].key.right) :
520 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
524 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
525 getKeyNameFromKey(key), key_status_name, event->fingerId);
527 // check if we already know this touch event's finger id
528 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
530 if (touch_info[i].touched &&
531 touch_info[i].finger_id == event->fingerId)
533 // Error(ERR_DEBUG, "MARK 1: %d", i);
539 if (i >= NUM_TOUCH_FINGERS)
541 if (key_status == KEY_PRESSED)
543 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
545 // unknown finger id -- get new, empty slot, if available
546 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
548 if (touch_info[i].counter < oldest_counter)
551 oldest_counter = touch_info[i].counter;
553 // Error(ERR_DEBUG, "MARK 2: %d", i);
556 if (!touch_info[i].touched)
558 // Error(ERR_DEBUG, "MARK 3: %d", i);
564 if (i >= NUM_TOUCH_FINGERS)
566 // all slots allocated -- use oldest slot
569 // Error(ERR_DEBUG, "MARK 4: %d", i);
574 // release of previously unknown key (should not happen)
576 if (key != KSYM_UNDEFINED)
578 HandleKey(key, KEY_RELEASED);
580 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
581 getKeyNameFromKey(key), "KEY_RELEASED", i);
586 if (i < NUM_TOUCH_FINGERS)
588 if (key_status == KEY_PRESSED)
590 if (touch_info[i].key != key)
592 if (touch_info[i].key != KSYM_UNDEFINED)
594 HandleKey(touch_info[i].key, KEY_RELEASED);
596 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
597 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
600 if (key != KSYM_UNDEFINED)
602 HandleKey(key, KEY_PRESSED);
604 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
605 getKeyNameFromKey(key), "KEY_PRESSED", i);
609 touch_info[i].touched = TRUE;
610 touch_info[i].finger_id = event->fingerId;
611 touch_info[i].counter = Counter();
612 touch_info[i].key = key;
616 if (touch_info[i].key != KSYM_UNDEFINED)
618 HandleKey(touch_info[i].key, KEY_RELEASED);
620 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
621 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
624 touch_info[i].touched = FALSE;
625 touch_info[i].finger_id = 0;
626 touch_info[i].counter = 0;
627 touch_info[i].key = 0;
634 // use touch direction control
636 if (event->type == EVENT_FINGERPRESS)
638 if (event_x > 1.0 / 3.0)
642 motion_id = event->fingerId;
647 motion_key_x = KSYM_UNDEFINED;
648 motion_key_y = KSYM_UNDEFINED;
650 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
656 button_id = event->fingerId;
661 button_key = setup.input[0].key.snap;
663 HandleKey(button_key, KEY_PRESSED);
665 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
668 else if (event->type == EVENT_FINGERRELEASE)
670 if (event->fingerId == motion_id)
674 if (motion_key_x != KSYM_UNDEFINED)
675 HandleKey(motion_key_x, KEY_RELEASED);
676 if (motion_key_y != KSYM_UNDEFINED)
677 HandleKey(motion_key_y, KEY_RELEASED);
679 motion_key_x = KSYM_UNDEFINED;
680 motion_key_y = KSYM_UNDEFINED;
682 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
684 else if (event->fingerId == button_id)
688 if (button_key != KSYM_UNDEFINED)
689 HandleKey(button_key, KEY_RELEASED);
691 button_key = KSYM_UNDEFINED;
693 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
696 else if (event->type == EVENT_FINGERMOTION)
698 if (event->fingerId == motion_id)
700 float distance_x = ABS(event_x - motion_x1);
701 float distance_y = ABS(event_y - motion_y1);
702 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
703 event_x > motion_x1 ? setup.input[0].key.right :
705 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
706 event_y > motion_y1 ? setup.input[0].key.down :
709 if (distance_x < move_trigger_distance / 2 ||
710 distance_x < distance_y)
711 new_motion_key_x = KSYM_UNDEFINED;
713 if (distance_y < move_trigger_distance / 2 ||
714 distance_y < distance_x)
715 new_motion_key_y = KSYM_UNDEFINED;
717 if (distance_x > move_trigger_distance ||
718 distance_y > move_trigger_distance)
720 if (new_motion_key_x != motion_key_x)
722 if (motion_key_x != KSYM_UNDEFINED)
723 HandleKey(motion_key_x, KEY_RELEASED);
724 if (new_motion_key_x != KSYM_UNDEFINED)
725 HandleKey(new_motion_key_x, KEY_PRESSED);
728 if (new_motion_key_y != motion_key_y)
730 if (motion_key_y != KSYM_UNDEFINED)
731 HandleKey(motion_key_y, KEY_RELEASED);
732 if (new_motion_key_y != KSYM_UNDEFINED)
733 HandleKey(new_motion_key_y, KEY_PRESSED);
739 motion_key_x = new_motion_key_x;
740 motion_key_y = new_motion_key_y;
742 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
745 else if (event->fingerId == button_id)
747 float distance_x = ABS(event_x - button_x1);
748 float distance_y = ABS(event_y - button_y1);
750 if (distance_x < drop_trigger_distance / 2 &&
751 distance_y > drop_trigger_distance)
753 if (button_key == setup.input[0].key.snap)
754 HandleKey(button_key, KEY_RELEASED);
759 button_key = setup.input[0].key.drop;
761 HandleKey(button_key, KEY_PRESSED);
763 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
769 static boolean checkTextInputKeyModState()
771 // when playing, only handle raw key events and ignore text input
772 if (game_status == GAME_MODE_PLAYING)
775 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
778 void HandleTextEvent(TextEvent *event)
780 char *text = event->text;
781 Key key = getKeyFromKeyName(text);
783 #if DEBUG_EVENTS_TEXT
784 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
787 text[0], (int)(text[0]),
789 getKeyNameFromKey(key),
793 #if defined(PLATFORM_ANDROID)
794 if (game_status == GAME_MODE_PSEUDO_TYPENAME)
796 HandleTypeName(0, key);
802 // only handle key input with text modifier keys pressed
803 if (checkTextInputKeyModState())
805 HandleKey(key, KEY_PRESSED);
806 HandleKey(key, KEY_RELEASED);
810 void HandlePauseResumeEvent(PauseResumeEvent *event)
812 if (event->type == SDL_APP_WILLENTERBACKGROUND)
816 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
824 void HandleKeyEvent(KeyEvent *event)
826 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
827 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
828 Key key = GetEventKey(event, with_modifiers);
829 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
832 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
833 event->type == EVENT_KEYPRESS ? "pressed" : "released",
834 event->keysym.scancode,
839 getKeyNameFromKey(key));
842 #if defined(PLATFORM_ANDROID)
843 // always map the "back" button to the "escape" key on Android devices
844 if (key == KSYM_Back)
848 HandleKeyModState(keymod, key_status);
850 #if defined(TARGET_SDL2)
851 // only handle raw key input without text modifier keys pressed
852 if (!checkTextInputKeyModState())
853 HandleKey(key, key_status);
855 HandleKey(key, key_status);
859 void HandleFocusEvent(FocusChangeEvent *event)
861 static int old_joystick_status = -1;
863 if (event->type == EVENT_FOCUSOUT)
865 KeyboardAutoRepeatOn();
866 old_joystick_status = joystick.status;
867 joystick.status = JOYSTICK_NOT_AVAILABLE;
871 else if (event->type == EVENT_FOCUSIN)
873 /* When there are two Rocks'n'Diamonds windows which overlap and
874 the player moves the pointer from one game window to the other,
875 a 'FocusOut' event is generated for the window the pointer is
876 leaving and a 'FocusIn' event is generated for the window the
877 pointer is entering. In some cases, it can happen that the
878 'FocusIn' event is handled by the one game process before the
879 'FocusOut' event by the other game process. In this case the
880 X11 environment would end up with activated keyboard auto repeat,
881 because unfortunately this is a global setting and not (which
882 would be far better) set for each X11 window individually.
883 The effect would be keyboard auto repeat while playing the game
884 (game_status == GAME_MODE_PLAYING), which is not desired.
885 To avoid this special case, we just wait 1/10 second before
886 processing the 'FocusIn' event.
889 if (game_status == GAME_MODE_PLAYING)
892 KeyboardAutoRepeatOffUnlessAutoplay();
895 if (old_joystick_status != -1)
896 joystick.status = old_joystick_status;
900 void HandleClientMessageEvent(ClientMessageEvent *event)
902 if (CheckCloseWindowEvent(event))
906 void HandleWindowManagerEvent(Event *event)
908 #if defined(TARGET_SDL)
909 SDLHandleWindowManagerEvent(event);
913 void HandleButton(int mx, int my, int button, int button_nr)
915 static int old_mx = 0, old_my = 0;
929 #if defined(PLATFORM_ANDROID)
930 if (game_status != GAME_MODE_PLAYING &&
931 HandleGadgets(mx, my, button))
933 /* do not handle this button event anymore */
934 mx = my = -32; /* force mouse event to be outside screen tiles */
937 if (HandleGadgets(mx, my, button))
939 /* do not handle this button event anymore */
940 mx = my = -32; /* force mouse event to be outside screen tiles */
944 /* do not use scroll wheel button events for anything other than gadgets */
945 if (IS_WHEEL_BUTTON(button_nr))
950 case GAME_MODE_TITLE:
951 HandleTitleScreen(mx, my, 0, 0, button);
955 HandleMainMenu(mx, my, 0, 0, button);
958 case GAME_MODE_PSEUDO_TYPENAME:
959 HandleTypeName(0, KSYM_Return);
962 case GAME_MODE_LEVELS:
963 HandleChooseLevelSet(mx, my, 0, 0, button);
966 case GAME_MODE_LEVELNR:
967 HandleChooseLevelNr(mx, my, 0, 0, button);
970 case GAME_MODE_SCORES:
971 HandleHallOfFame(0, 0, 0, 0, button);
974 case GAME_MODE_EDITOR:
975 HandleLevelEditorIdle();
979 HandleInfoScreen(mx, my, 0, 0, button);
982 case GAME_MODE_SETUP:
983 HandleSetupScreen(mx, my, 0, 0, button);
986 case GAME_MODE_PLAYING:
988 if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my))
989 DumpTile(LEVELX((mx - SX) / TILESIZE_VAR),
990 LEVELY((my - SY) / TILESIZE_VAR));
991 // DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
1000 static boolean is_string_suffix(char *string, char *suffix)
1002 int string_len = strlen(string);
1003 int suffix_len = strlen(suffix);
1005 if (suffix_len > string_len)
1008 return (strEqual(&string[string_len - suffix_len], suffix));
1011 #define MAX_CHEAT_INPUT_LEN 32
1013 static void HandleKeysSpecial(Key key)
1015 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1016 char letter = getCharFromKey(key);
1017 int cheat_input_len = strlen(cheat_input);
1023 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1025 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1026 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1028 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1031 cheat_input[cheat_input_len++] = letter;
1032 cheat_input[cheat_input_len] = '\0';
1034 #if DEBUG_EVENTS_KEY
1035 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1038 if (game_status == GAME_MODE_MAIN)
1040 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1041 is_string_suffix(cheat_input, ":ist"))
1043 InsertSolutionTape();
1045 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1046 is_string_suffix(cheat_input, ":rg"))
1048 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1051 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1052 is_string_suffix(cheat_input, ":rs"))
1054 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1057 else if (is_string_suffix(cheat_input, ":reload-music") ||
1058 is_string_suffix(cheat_input, ":rm"))
1060 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1063 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1064 is_string_suffix(cheat_input, ":ra"))
1066 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1067 1 << ARTWORK_TYPE_SOUNDS |
1068 1 << ARTWORK_TYPE_MUSIC);
1071 else if (is_string_suffix(cheat_input, ":dump-level") ||
1072 is_string_suffix(cheat_input, ":dl"))
1076 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1077 is_string_suffix(cheat_input, ":dt"))
1081 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1082 is_string_suffix(cheat_input, ":ft"))
1084 /* fix single-player tapes that contain player input for more than one
1085 player (due to a bug in 3.3.1.2 and earlier versions), which results
1086 in playing levels with more than one player in multi-player mode,
1087 even though the tape was originally recorded in single-player mode */
1089 /* remove player input actions for all players but the first one */
1090 for (i = 1; i < MAX_PLAYERS; i++)
1091 tape.player_participates[i] = FALSE;
1093 tape.changed = TRUE;
1095 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1096 is_string_suffix(cheat_input, ":snl"))
1098 SaveNativeLevel(&level);
1101 else if (game_status == GAME_MODE_PLAYING)
1104 if (is_string_suffix(cheat_input, ".q"))
1105 DEBUG_SetMaximumDynamite();
1108 else if (game_status == GAME_MODE_EDITOR)
1110 if (is_string_suffix(cheat_input, ":dump-brush") ||
1111 is_string_suffix(cheat_input, ":DB"))
1115 else if (is_string_suffix(cheat_input, ":DDB"))
1122 void HandleKey(Key key, int key_status)
1124 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1125 static struct SetupKeyboardInfo ski;
1126 static struct SetupShortcutInfo ssi;
1135 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1136 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1137 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1138 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1139 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1140 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1145 if (game_status == GAME_MODE_PLAYING)
1147 /* only needed for single-step tape recording mode */
1148 static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1149 static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1150 static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1151 static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1154 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1156 byte key_action = 0;
1158 if (setup.input[pnr].use_joystick)
1161 ski = setup.input[pnr].key;
1163 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1164 if (key == *key_info[i].key_custom)
1165 key_action |= key_info[i].action;
1167 /* use combined snap+direction keys for the first player only */
1170 ssi = setup.shortcut;
1172 for (i = 0; i < NUM_DIRECTIONS; i++)
1173 if (key == *key_info[i].key_snap)
1174 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1177 /* clear delayed snap and drop actions in single step mode (see below) */
1178 if (tape.single_step)
1180 if (clear_snap_button[pnr])
1182 stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1183 clear_snap_button[pnr] = FALSE;
1186 if (clear_drop_button[pnr])
1188 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1189 clear_drop_button[pnr] = FALSE;
1193 if (key_status == KEY_PRESSED)
1194 stored_player[pnr].action |= key_action;
1196 stored_player[pnr].action &= ~key_action;
1198 if (tape.single_step && tape.recording && tape.pausing)
1200 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1202 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1204 /* if snap key already pressed, don't snap when releasing (below) */
1205 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1206 element_snapped[pnr] = TRUE;
1208 /* if drop key already pressed, don't drop when releasing (below) */
1209 if (stored_player[pnr].action & KEY_BUTTON_DROP)
1210 element_dropped[pnr] = TRUE;
1212 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1214 if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1215 level.game_engine_type == GAME_ENGINE_TYPE_SP)
1218 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1219 getRedDiskReleaseFlag_SP() == 0)
1220 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1222 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1225 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1227 if (key_action & KEY_BUTTON_SNAP)
1229 /* if snap key was released without moving (see above), snap now */
1230 if (!element_snapped[pnr])
1232 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1234 stored_player[pnr].action |= KEY_BUTTON_SNAP;
1236 /* clear delayed snap button on next event */
1237 clear_snap_button[pnr] = TRUE;
1240 element_snapped[pnr] = FALSE;
1243 if (key_action & KEY_BUTTON_DROP &&
1244 level.game_engine_type == GAME_ENGINE_TYPE_RND)
1246 /* if drop key was released without moving (see above), drop now */
1247 if (!element_dropped[pnr])
1249 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1251 if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1252 getRedDiskReleaseFlag_SP() != 0)
1253 stored_player[pnr].action |= KEY_BUTTON_DROP;
1255 /* clear delayed drop button on next event */
1256 clear_drop_button[pnr] = TRUE;
1259 element_dropped[pnr] = FALSE;
1263 else if (tape.recording && tape.pausing)
1265 /* prevent key release events from un-pausing a paused game */
1266 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1267 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1273 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1274 if (key == key_info[i].key_default)
1275 joy |= key_info[i].action;
1280 if (key_status == KEY_PRESSED)
1281 key_joystick_mapping |= joy;
1283 key_joystick_mapping &= ~joy;
1288 if (game_status != GAME_MODE_PLAYING)
1289 key_joystick_mapping = 0;
1291 if (key_status == KEY_RELEASED)
1294 if ((key == KSYM_F11 ||
1295 ((key == KSYM_Return ||
1296 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1297 video.fullscreen_available)
1299 setup.fullscreen = !setup.fullscreen;
1301 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1303 if (game_status == GAME_MODE_SETUP)
1304 RedrawSetupScreenAfterFullscreenToggle();
1309 if ((key == KSYM_minus ||
1312 ((GetKeyModState() & KMOD_Control) ||
1313 (GetKeyModState() & KMOD_Alt)) &&
1314 video.window_scaling_available &&
1315 !video.fullscreen_enabled)
1318 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1320 setup.window_scaling_percent +=
1321 (key == KSYM_minus ? -1 : +1) * STEP_WINDOW_SCALING_PERCENT;
1323 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1324 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1325 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1326 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1328 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1330 if (game_status == GAME_MODE_SETUP)
1331 RedrawSetupScreenAfterFullscreenToggle();
1336 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1337 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1344 if (game_status == GAME_MODE_MAIN &&
1345 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1347 StartGameActions(options.network, setup.autorecord, level.random_seed);
1352 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1354 if (key == setup.shortcut.save_game)
1356 else if (key == setup.shortcut.load_game)
1358 else if (key == setup.shortcut.toggle_pause)
1359 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1361 HandleTapeButtonKeys(key);
1362 HandleSoundButtonKeys(key);
1365 if (game_status == GAME_MODE_PLAYING && !network_playing)
1367 int centered_player_nr_next = -999;
1369 if (key == setup.shortcut.focus_player_all)
1370 centered_player_nr_next = -1;
1372 for (i = 0; i < MAX_PLAYERS; i++)
1373 if (key == setup.shortcut.focus_player[i])
1374 centered_player_nr_next = i;
1376 if (centered_player_nr_next != -999)
1378 game.centered_player_nr_next = centered_player_nr_next;
1379 game.set_centered_player = TRUE;
1383 tape.centered_player_nr_next = game.centered_player_nr_next;
1384 tape.set_centered_player = TRUE;
1389 HandleKeysSpecial(key);
1391 if (HandleGadgetsKeyInput(key))
1393 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1394 key = KSYM_UNDEFINED;
1397 switch (game_status)
1399 case GAME_MODE_PSEUDO_TYPENAME:
1400 HandleTypeName(0, key);
1403 case GAME_MODE_TITLE:
1404 case GAME_MODE_MAIN:
1405 case GAME_MODE_LEVELS:
1406 case GAME_MODE_LEVELNR:
1407 case GAME_MODE_SETUP:
1408 case GAME_MODE_INFO:
1409 case GAME_MODE_SCORES:
1414 if (game_status == GAME_MODE_TITLE)
1415 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1416 else if (game_status == GAME_MODE_MAIN)
1417 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1418 else if (game_status == GAME_MODE_LEVELS)
1419 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1420 else if (game_status == GAME_MODE_LEVELNR)
1421 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1422 else if (game_status == GAME_MODE_SETUP)
1423 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1424 else if (game_status == GAME_MODE_INFO)
1425 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1426 else if (game_status == GAME_MODE_SCORES)
1427 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1431 if (game_status != GAME_MODE_MAIN)
1432 FadeSkipNextFadeIn();
1434 if (game_status == GAME_MODE_TITLE)
1435 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1436 else if (game_status == GAME_MODE_LEVELS)
1437 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1438 else if (game_status == GAME_MODE_LEVELNR)
1439 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1440 else if (game_status == GAME_MODE_SETUP)
1441 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1442 else if (game_status == GAME_MODE_INFO)
1443 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1444 else if (game_status == GAME_MODE_SCORES)
1445 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1449 if (game_status == GAME_MODE_LEVELS)
1450 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1451 else if (game_status == GAME_MODE_LEVELNR)
1452 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1453 else if (game_status == GAME_MODE_SETUP)
1454 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1455 else if (game_status == GAME_MODE_INFO)
1456 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1457 else if (game_status == GAME_MODE_SCORES)
1458 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1461 case KSYM_Page_Down:
1462 if (game_status == GAME_MODE_LEVELS)
1463 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1464 else if (game_status == GAME_MODE_LEVELNR)
1465 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1466 else if (game_status == GAME_MODE_SETUP)
1467 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1468 else if (game_status == GAME_MODE_INFO)
1469 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1470 else if (game_status == GAME_MODE_SCORES)
1471 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1476 GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
1480 setup.sp_show_border_elements = !setup.sp_show_border_elements;
1481 printf("Supaplex border elements %s\n",
1482 setup.sp_show_border_elements ? "enabled" : "disabled");
1491 case GAME_MODE_EDITOR:
1492 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1493 HandleLevelEditorKeyInput(key);
1496 case GAME_MODE_PLAYING:
1501 RequestQuitGame(setup.ask_on_escape);
1508 if (GameFrameDelay == 500)
1509 GameFrameDelay = GAME_FRAME_DELAY;
1511 GameFrameDelay = 500;
1514 GameFrameDelay = (key - KSYM_0) * 10;
1515 printf("Game speed == %d%% (%d ms delay between two frames)\n",
1516 GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
1522 options.debug = FALSE;
1523 printf("debug mode disabled\n");
1527 options.debug = TRUE;
1528 printf("debug mode enabled\n");
1534 if (!global.fps_slowdown)
1536 global.fps_slowdown = TRUE;
1537 global.fps_slowdown_factor = 2;
1538 printf("fps slowdown enabled -- display only every 2nd frame\n");
1540 else if (global.fps_slowdown_factor == 2)
1542 global.fps_slowdown_factor = 4;
1543 printf("fps slowdown enabled -- display only every 4th frame\n");
1547 global.fps_slowdown = FALSE;
1548 global.fps_slowdown_factor = 1;
1549 printf("fps slowdown disabled\n");
1555 printf("::: currently using game engine version %d\n",
1556 game.engine_version);
1567 if (key == KSYM_Escape)
1569 game_status = GAME_MODE_MAIN;
1577 void HandleNoEvent()
1579 if (button_status && game_status != GAME_MODE_PLAYING)
1581 HandleButton(0, 0, -button_status, button_status);
1588 #if defined(NETWORK_AVALIABLE)
1589 if (options.network)
1593 switch (game_status)
1595 case GAME_MODE_MAIN:
1596 DrawPreviewLevelAnimation();
1600 case GAME_MODE_LEVELS:
1601 case GAME_MODE_LEVELNR:
1602 case GAME_MODE_SETUP:
1603 case GAME_MODE_INFO:
1604 case GAME_MODE_SCORES:
1608 case GAME_MODE_EDITOR:
1609 HandleLevelEditorIdle();
1617 static int HandleJoystickForAllPlayers()
1622 for (i = 0; i < MAX_PLAYERS; i++)
1624 byte joy_action = 0;
1627 if (!setup.input[i].use_joystick)
1631 joy_action = Joystick(i);
1632 result |= joy_action;
1634 if (!setup.input[i].use_joystick)
1637 stored_player[i].action = joy_action;
1643 void HandleJoystick()
1645 int joystick = HandleJoystickForAllPlayers();
1646 int keyboard = key_joystick_mapping;
1647 int joy = (joystick | keyboard);
1648 int left = joy & JOY_LEFT;
1649 int right = joy & JOY_RIGHT;
1650 int up = joy & JOY_UP;
1651 int down = joy & JOY_DOWN;
1652 int button = joy & JOY_BUTTON;
1653 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1654 int dx = (left ? -1 : right ? 1 : 0);
1655 int dy = (up ? -1 : down ? 1 : 0);
1657 switch (game_status)
1659 case GAME_MODE_TITLE:
1660 case GAME_MODE_MAIN:
1661 case GAME_MODE_LEVELS:
1662 case GAME_MODE_LEVELNR:
1663 case GAME_MODE_SETUP:
1664 case GAME_MODE_INFO:
1666 static unsigned int joystickmove_delay = 0;
1668 if (joystick && !button &&
1669 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1670 newbutton = dx = dy = 0;
1672 if (game_status == GAME_MODE_TITLE)
1673 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1674 else if (game_status == GAME_MODE_MAIN)
1675 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1676 else if (game_status == GAME_MODE_LEVELS)
1677 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1678 else if (game_status == GAME_MODE_LEVELNR)
1679 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1680 else if (game_status == GAME_MODE_SETUP)
1681 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1682 else if (game_status == GAME_MODE_INFO)
1683 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1687 case GAME_MODE_SCORES:
1688 HandleHallOfFame(0, 0, dx, dy, !newbutton);
1691 case GAME_MODE_PLAYING:
1692 if (tape.playing || keyboard)
1693 newbutton = ((joy & JOY_BUTTON) != 0);
1695 if (newbutton && AllPlayersGone)