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
28 static boolean cursor_inside_playfield = FALSE;
29 static boolean playfield_cursor_set = FALSE;
30 static unsigned int playfield_cursor_delay = 0;
33 /* event filter especially needed for SDL event filtering due to
34 delay problems with lots of mouse motion events when mouse button
35 not pressed (X11 can handle this with 'PointerMotionHintMask') */
37 /* event filter addition for SDL2: as SDL2 does not have a function to enable
38 or disable keyboard auto-repeat, filter repeated keyboard events instead */
40 static int FilterEventsExt(const Event *event)
44 #if defined(TARGET_SDL2)
45 /* skip repeated key press events if keyboard auto-repeat is disabled */
46 if (event->type == EVENT_KEYPRESS &&
52 /* non-motion events are directly passed to event handler functions */
53 if (event->type != EVENT_MOTIONNOTIFY)
56 motion = (MotionEvent *)event;
57 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
58 motion->y >= SY && motion->y < SY + SYSIZE);
60 if (game_status == GAME_MODE_PLAYING && playfield_cursor_set)
62 SetMouseCursor(CURSOR_DEFAULT);
63 playfield_cursor_set = FALSE;
64 DelayReached(&playfield_cursor_delay, 0);
67 /* skip mouse motion events without pressed button outside level editor */
68 if (button_status == MB_RELEASED &&
69 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
75 #if defined(TARGET_SDL2)
76 int FilterEvents(void *userdata, Event *event)
78 return FilterEventsExt(event);
81 int FilterEvents(const Event *event)
83 return FilterEventsExt(event);
87 /* to prevent delay problems, skip mouse motion events if the very next
88 event is also a mouse motion event (and therefore effectively only
89 handling the last of a row of mouse motion events in the event queue) */
91 boolean SkipPressedMouseMotionEvent(const Event *event)
93 /* nothing to do if the current event is not a mouse motion event */
94 if (event->type != EVENT_MOTIONNOTIFY)
97 /* only skip motion events with pressed button outside level editor */
98 if (button_status == MB_RELEASED ||
99 game_status == GAME_MODE_EDITOR || game_status == GAME_MODE_PLAYING)
106 PeekEvent(&next_event);
108 /* if next event is also a mouse motion event, skip the current one */
109 if (next_event.type == EVENT_MOTIONNOTIFY)
116 /* this is only really needed for non-SDL targets to filter unwanted events;
117 when using SDL with properly installed event filter, this function can be
118 replaced with a simple "NextEvent()" call, but it doesn't hurt either */
120 static boolean NextValidEvent(Event *event)
122 while (PendingEvent())
124 boolean handle_this_event = FALSE;
128 if (FilterEventsExt(event))
129 handle_this_event = TRUE;
131 if (SkipPressedMouseMotionEvent(event))
132 handle_this_event = FALSE;
134 if (handle_this_event)
145 if (PendingEvent()) /* got event */
149 while (NextValidEvent(&event))
153 case EVENT_BUTTONPRESS:
154 case EVENT_BUTTONRELEASE:
155 HandleButtonEvent((ButtonEvent *) &event);
158 case EVENT_MOTIONNOTIFY:
159 HandleMotionEvent((MotionEvent *) &event);
162 #if defined(TARGET_SDL2)
163 case SDL_WINDOWEVENT:
164 HandleWindowEvent((WindowEvent *) &event);
167 case EVENT_FINGERPRESS:
168 case EVENT_FINGERRELEASE:
169 case EVENT_FINGERMOTION:
170 HandleFingerEvent((FingerEvent *) &event);
173 case EVENT_TEXTINPUT:
174 HandleTextEvent((TextEvent *) &event);
177 case SDL_APP_WILLENTERBACKGROUND:
178 case SDL_APP_DIDENTERBACKGROUND:
179 case SDL_APP_WILLENTERFOREGROUND:
180 case SDL_APP_DIDENTERFOREGROUND:
181 HandlePauseResumeEvent((PauseResumeEvent *) &event);
186 case EVENT_KEYRELEASE:
187 HandleKeyEvent((KeyEvent *) &event);
191 HandleOtherEvents(&event);
198 /* when playing, display a special mouse pointer inside the playfield */
199 if (game_status == GAME_MODE_PLAYING && !tape.pausing)
201 if (!playfield_cursor_set && cursor_inside_playfield &&
202 DelayReached(&playfield_cursor_delay, 1000))
204 SetMouseCursor(CURSOR_PLAYFIELD);
205 playfield_cursor_set = TRUE;
208 else if (playfield_cursor_set)
210 SetMouseCursor(CURSOR_DEFAULT);
211 playfield_cursor_set = FALSE;
215 /* also execute after pending events have been processed before */
218 /* don't use all CPU time when idle; the main loop while playing
219 has its own synchronization and is CPU friendly, too */
221 if (game_status == GAME_MODE_PLAYING)
229 if (!PendingEvent()) /* delay only if no pending events */
233 /* refresh window contents from drawing buffer, if needed */
236 if (game_status == GAME_MODE_QUIT)
241 void HandleOtherEvents(Event *event)
246 HandleExposeEvent((ExposeEvent *) event);
249 case EVENT_UNMAPNOTIFY:
251 /* This causes the game to stop not only when iconified, but also
252 when on another virtual desktop, which might be not desired. */
253 SleepWhileUnmapped();
259 HandleFocusEvent((FocusChangeEvent *) event);
262 case EVENT_CLIENTMESSAGE:
263 HandleClientMessageEvent((ClientMessageEvent *) event);
266 #if defined(TARGET_SDL)
267 case SDL_JOYAXISMOTION:
268 case SDL_JOYBUTTONDOWN:
269 case SDL_JOYBUTTONUP:
270 HandleJoystickEvent(event);
274 HandleWindowManagerEvent(event);
283 void ClearEventQueue()
285 while (PendingEvent())
293 case EVENT_BUTTONRELEASE:
294 button_status = MB_RELEASED;
297 case EVENT_KEYRELEASE:
302 HandleOtherEvents(&event);
308 void ClearPlayerAction()
312 /* simulate key release events for still pressed keys */
313 key_joystick_mapping = 0;
314 for (i = 0; i < MAX_PLAYERS; i++)
315 stored_player[i].action = 0;
318 void SleepWhileUnmapped()
320 boolean window_unmapped = TRUE;
322 KeyboardAutoRepeatOn();
324 while (window_unmapped)
332 case EVENT_BUTTONRELEASE:
333 button_status = MB_RELEASED;
336 case EVENT_KEYRELEASE:
337 key_joystick_mapping = 0;
340 case EVENT_MAPNOTIFY:
341 window_unmapped = FALSE;
344 case EVENT_UNMAPNOTIFY:
345 /* this is only to surely prevent the 'should not happen' case
346 * of recursively looping between 'SleepWhileUnmapped()' and
347 * 'HandleOtherEvents()' which usually calls this funtion.
352 HandleOtherEvents(&event);
357 if (game_status == GAME_MODE_PLAYING)
358 KeyboardAutoRepeatOffUnlessAutoplay();
361 void HandleExposeEvent(ExposeEvent *event)
365 void HandleButtonEvent(ButtonEvent *event)
368 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
370 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
374 motion_status = FALSE;
376 if (event->type == EVENT_BUTTONPRESS)
377 button_status = event->button;
379 button_status = MB_RELEASED;
381 HandleButton(event->x, event->y, button_status, event->button);
384 void HandleMotionEvent(MotionEvent *event)
386 if (!PointerInWindow(window))
387 return; /* window and pointer are on different screens */
389 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
392 motion_status = TRUE;
395 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
396 button_status, event->x, event->y);
399 HandleButton(event->x, event->y, button_status, button_status);
402 #if defined(TARGET_SDL2)
404 void HandleWindowEvent(WindowEvent *event)
407 int subtype = event->event;
410 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
411 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
412 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
413 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
414 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
415 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
416 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
417 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
418 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
419 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
420 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
421 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
422 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
423 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
426 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
427 event_name, event->data1, event->data2);
430 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
431 event->event == SDL_WINDOWEVENT_RESIZED ||
432 event->event == SDL_WINDOWEVENT_EXPOSED)
435 if (event->event == SDL_WINDOWEVENT_RESIZED && !video.fullscreen_enabled)
437 int new_window_width = event->data1;
438 int new_window_height = event->data2;
440 // if window size has changed after resizing, calculate new scaling factor
441 if (new_window_width != video.window_width ||
442 new_window_height != video.window_height)
444 int new_xpercent = (100 * new_window_width / video.width);
445 int new_ypercent = (100 * new_window_height / video.height);
447 setup.window_scaling_percent = video.window_scaling_percent =
448 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, MIN(new_xpercent, new_ypercent)),
449 MAX_WINDOW_SCALING_PERCENT);
451 video.window_width = new_window_width;
452 video.window_height = new_window_height;
454 if (game_status == GAME_MODE_SETUP)
455 RedrawSetupScreenAfterFullscreenToggle();
462 #define NUM_TOUCH_FINGERS 3
467 SDL_FingerID finger_id;
470 } touch_info[NUM_TOUCH_FINGERS];
472 void HandleFingerEvent(FingerEvent *event)
474 static Key motion_key_x = KSYM_UNDEFINED;
475 static Key motion_key_y = KSYM_UNDEFINED;
476 static Key button_key = KSYM_UNDEFINED;
477 static float motion_x1, motion_y1;
478 static float button_x1, button_y1;
479 static SDL_FingerID motion_id = -1;
480 static SDL_FingerID button_id = -1;
481 int move_trigger_distance_percent = 2; // percent of touchpad width/height
482 int drop_trigger_distance_percent = 5; // percent of touchpad width/height
483 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
484 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
485 float event_x = event->x;
486 float event_y = event->y;
489 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
490 event->type == EVENT_FINGERPRESS ? "pressed" :
491 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
495 event->dx, event->dy,
499 if (game_status != GAME_MODE_PLAYING)
502 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
504 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
506 Key key = (event->x < 1.0 / 3.0 ?
507 (event->y < 1.0 / 2.0 ? setup.input[0].key.snap :
508 setup.input[0].key.drop) :
509 event->x > 2.0 / 3.0 ?
510 (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
511 event->y > 2.0 / 3.0 ? setup.input[0].key.down :
512 event->x < 5.0 / 6.0 ? setup.input[0].key.left :
513 setup.input[0].key.right) :
515 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
519 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
520 getKeyNameFromKey(key), key_status_name, event->fingerId);
522 // check if we already know this touch event's finger id
523 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
525 if (touch_info[i].touched &&
526 touch_info[i].finger_id == event->fingerId)
528 // Error(ERR_DEBUG, "MARK 1: %d", i);
534 if (i >= NUM_TOUCH_FINGERS)
536 if (key_status == KEY_PRESSED)
538 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
540 // unknown finger id -- get new, empty slot, if available
541 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
543 if (touch_info[i].counter < oldest_counter)
546 oldest_counter = touch_info[i].counter;
548 // Error(ERR_DEBUG, "MARK 2: %d", i);
551 if (!touch_info[i].touched)
553 // Error(ERR_DEBUG, "MARK 3: %d", i);
559 if (i >= NUM_TOUCH_FINGERS)
561 // all slots allocated -- use oldest slot
564 // Error(ERR_DEBUG, "MARK 4: %d", i);
569 // release of previously unknown key (should not happen)
571 if (key != KSYM_UNDEFINED)
573 HandleKey(key, KEY_RELEASED);
575 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
576 getKeyNameFromKey(key), "KEY_RELEASED", i);
581 if (i < NUM_TOUCH_FINGERS)
583 if (key_status == KEY_PRESSED)
585 if (touch_info[i].key != key)
587 if (touch_info[i].key != KSYM_UNDEFINED)
589 HandleKey(touch_info[i].key, KEY_RELEASED);
591 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
592 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
595 if (key != KSYM_UNDEFINED)
597 HandleKey(key, KEY_PRESSED);
599 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
600 getKeyNameFromKey(key), "KEY_PRESSED", i);
604 touch_info[i].touched = TRUE;
605 touch_info[i].finger_id = event->fingerId;
606 touch_info[i].counter = Counter();
607 touch_info[i].key = key;
611 if (touch_info[i].key != KSYM_UNDEFINED)
613 HandleKey(touch_info[i].key, KEY_RELEASED);
615 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
616 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
619 touch_info[i].touched = FALSE;
620 touch_info[i].finger_id = 0;
621 touch_info[i].counter = 0;
622 touch_info[i].key = 0;
629 // use touch direction control
631 if (event->type == EVENT_FINGERPRESS)
633 if (event_x > 1.0 / 3.0)
637 motion_id = event->fingerId;
642 motion_key_x = KSYM_UNDEFINED;
643 motion_key_y = KSYM_UNDEFINED;
645 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
651 button_id = event->fingerId;
656 button_key = setup.input[0].key.snap;
658 HandleKey(button_key, KEY_PRESSED);
660 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
663 else if (event->type == EVENT_FINGERRELEASE)
665 if (event->fingerId == motion_id)
669 if (motion_key_x != KSYM_UNDEFINED)
670 HandleKey(motion_key_x, KEY_RELEASED);
671 if (motion_key_y != KSYM_UNDEFINED)
672 HandleKey(motion_key_y, KEY_RELEASED);
674 motion_key_x = KSYM_UNDEFINED;
675 motion_key_y = KSYM_UNDEFINED;
677 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
679 else if (event->fingerId == button_id)
683 if (button_key != KSYM_UNDEFINED)
684 HandleKey(button_key, KEY_RELEASED);
686 button_key = KSYM_UNDEFINED;
688 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
691 else if (event->type == EVENT_FINGERMOTION)
693 if (event->fingerId == motion_id)
695 float distance_x = ABS(event_x - motion_x1);
696 float distance_y = ABS(event_y - motion_y1);
697 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
698 event_x > motion_x1 ? setup.input[0].key.right :
700 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
701 event_y > motion_y1 ? setup.input[0].key.down :
704 if (distance_x < move_trigger_distance / 2 ||
705 distance_x < distance_y)
706 new_motion_key_x = KSYM_UNDEFINED;
708 if (distance_y < move_trigger_distance / 2 ||
709 distance_y < distance_x)
710 new_motion_key_y = KSYM_UNDEFINED;
712 if (distance_x > move_trigger_distance ||
713 distance_y > move_trigger_distance)
715 if (new_motion_key_x != motion_key_x)
717 if (motion_key_x != KSYM_UNDEFINED)
718 HandleKey(motion_key_x, KEY_RELEASED);
719 if (new_motion_key_x != KSYM_UNDEFINED)
720 HandleKey(new_motion_key_x, KEY_PRESSED);
723 if (new_motion_key_y != motion_key_y)
725 if (motion_key_y != KSYM_UNDEFINED)
726 HandleKey(motion_key_y, KEY_RELEASED);
727 if (new_motion_key_y != KSYM_UNDEFINED)
728 HandleKey(new_motion_key_y, KEY_PRESSED);
734 motion_key_x = new_motion_key_x;
735 motion_key_y = new_motion_key_y;
737 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
740 else if (event->fingerId == button_id)
742 float distance_x = ABS(event_x - button_x1);
743 float distance_y = ABS(event_y - button_y1);
745 if (distance_x < drop_trigger_distance / 2 &&
746 distance_y > drop_trigger_distance)
748 if (button_key == setup.input[0].key.snap)
749 HandleKey(button_key, KEY_RELEASED);
754 button_key = setup.input[0].key.drop;
756 HandleKey(button_key, KEY_PRESSED);
758 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
764 static boolean checkTextInputKeyModState()
766 // when playing, only handle raw key events and ignore text input
767 if (game_status == GAME_MODE_PLAYING)
770 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
773 void HandleTextEvent(TextEvent *event)
775 char *text = event->text;
776 Key key = getKeyFromKeyName(text);
779 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
782 text[0], (int)(text[0]),
784 getKeyNameFromKey(key),
788 // if (game_status != GAME_MODE_PLAYING && GetKeyModState() != KMOD_None)
790 if (game_status != GAME_MODE_PLAYING &&
791 (GetKeyModState() & KMOD_TextInput) != KMOD_None)
793 if (checkTextInputKeyModState())
795 HandleKey(key, KEY_PRESSED);
796 HandleKey(key, KEY_RELEASED);
800 void HandlePauseResumeEvent(PauseResumeEvent *event)
802 if (event->type == SDL_APP_WILLENTERBACKGROUND)
806 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
814 void HandleKeyEvent(KeyEvent *event)
816 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
817 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
818 Key key = GetEventKey(event, with_modifiers);
819 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
822 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
823 event->type == EVENT_KEYPRESS ? "pressed" : "released",
824 event->keysym.scancode,
829 getKeyNameFromKey(key));
832 #if defined(PLATFORM_ANDROID)
833 // always map the "back" button to the "escape" key on Android devices
834 if (key == KSYM_Back)
838 HandleKeyModState(keymod, key_status);
840 #if defined(TARGET_SDL2)
841 if (!checkTextInputKeyModState())
842 HandleKey(key, key_status);
844 HandleKey(key, key_status);
848 void HandleFocusEvent(FocusChangeEvent *event)
850 static int old_joystick_status = -1;
852 if (event->type == EVENT_FOCUSOUT)
854 KeyboardAutoRepeatOn();
855 old_joystick_status = joystick.status;
856 joystick.status = JOYSTICK_NOT_AVAILABLE;
860 else if (event->type == EVENT_FOCUSIN)
862 /* When there are two Rocks'n'Diamonds windows which overlap and
863 the player moves the pointer from one game window to the other,
864 a 'FocusOut' event is generated for the window the pointer is
865 leaving and a 'FocusIn' event is generated for the window the
866 pointer is entering. In some cases, it can happen that the
867 'FocusIn' event is handled by the one game process before the
868 'FocusOut' event by the other game process. In this case the
869 X11 environment would end up with activated keyboard auto repeat,
870 because unfortunately this is a global setting and not (which
871 would be far better) set for each X11 window individually.
872 The effect would be keyboard auto repeat while playing the game
873 (game_status == GAME_MODE_PLAYING), which is not desired.
874 To avoid this special case, we just wait 1/10 second before
875 processing the 'FocusIn' event.
878 if (game_status == GAME_MODE_PLAYING)
881 KeyboardAutoRepeatOffUnlessAutoplay();
884 if (old_joystick_status != -1)
885 joystick.status = old_joystick_status;
889 void HandleClientMessageEvent(ClientMessageEvent *event)
891 if (CheckCloseWindowEvent(event))
895 void HandleWindowManagerEvent(Event *event)
897 #if defined(TARGET_SDL)
898 SDLHandleWindowManagerEvent(event);
902 void HandleButton(int mx, int my, int button, int button_nr)
904 static int old_mx = 0, old_my = 0;
918 #if defined(PLATFORM_ANDROID)
919 if (game_status != GAME_MODE_PLAYING &&
920 HandleGadgets(mx, my, button))
922 /* do not handle this button event anymore */
923 mx = my = -32; /* force mouse event to be outside screen tiles */
926 if (HandleGadgets(mx, my, button))
928 /* do not handle this button event anymore */
929 mx = my = -32; /* force mouse event to be outside screen tiles */
933 /* do not use scroll wheel button events for anything other than gadgets */
934 if (IS_WHEEL_BUTTON(button_nr))
939 case GAME_MODE_TITLE:
940 HandleTitleScreen(mx, my, 0, 0, button);
944 HandleMainMenu(mx, my, 0, 0, button);
947 case GAME_MODE_PSEUDO_TYPENAME:
948 HandleTypeName(0, KSYM_Return);
951 case GAME_MODE_LEVELS:
952 HandleChooseLevelSet(mx, my, 0, 0, button);
955 case GAME_MODE_LEVELNR:
956 HandleChooseLevelNr(mx, my, 0, 0, button);
959 case GAME_MODE_SCORES:
960 HandleHallOfFame(0, 0, 0, 0, button);
963 case GAME_MODE_EDITOR:
964 HandleLevelEditorIdle();
968 HandleInfoScreen(mx, my, 0, 0, button);
971 case GAME_MODE_SETUP:
972 HandleSetupScreen(mx, my, 0, 0, button);
975 case GAME_MODE_PLAYING:
977 if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my))
978 DumpTile(LEVELX((mx - SX) / TILESIZE_VAR),
979 LEVELY((my - SY) / TILESIZE_VAR));
980 // DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
989 static boolean is_string_suffix(char *string, char *suffix)
991 int string_len = strlen(string);
992 int suffix_len = strlen(suffix);
994 if (suffix_len > string_len)
997 return (strEqual(&string[string_len - suffix_len], suffix));
1000 #define MAX_CHEAT_INPUT_LEN 32
1002 static void HandleKeysSpecial(Key key)
1004 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1005 char letter = getCharFromKey(key);
1006 int cheat_input_len = strlen(cheat_input);
1012 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1014 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1015 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1017 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1020 cheat_input[cheat_input_len++] = letter;
1021 cheat_input[cheat_input_len] = '\0';
1024 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1027 if (game_status == GAME_MODE_MAIN)
1029 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1030 is_string_suffix(cheat_input, ":ist"))
1032 InsertSolutionTape();
1034 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1035 is_string_suffix(cheat_input, ":rg"))
1037 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1040 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1041 is_string_suffix(cheat_input, ":rs"))
1043 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1046 else if (is_string_suffix(cheat_input, ":reload-music") ||
1047 is_string_suffix(cheat_input, ":rm"))
1049 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1052 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1053 is_string_suffix(cheat_input, ":ra"))
1055 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1056 1 << ARTWORK_TYPE_SOUNDS |
1057 1 << ARTWORK_TYPE_MUSIC);
1060 else if (is_string_suffix(cheat_input, ":dump-level") ||
1061 is_string_suffix(cheat_input, ":dl"))
1065 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1066 is_string_suffix(cheat_input, ":dt"))
1070 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1071 is_string_suffix(cheat_input, ":ft"))
1073 /* fix single-player tapes that contain player input for more than one
1074 player (due to a bug in 3.3.1.2 and earlier versions), which results
1075 in playing levels with more than one player in multi-player mode,
1076 even though the tape was originally recorded in single-player mode */
1078 /* remove player input actions for all players but the first one */
1079 for (i = 1; i < MAX_PLAYERS; i++)
1080 tape.player_participates[i] = FALSE;
1082 tape.changed = TRUE;
1084 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1085 is_string_suffix(cheat_input, ":snl"))
1087 SaveNativeLevel(&level);
1090 else if (game_status == GAME_MODE_PLAYING)
1093 if (is_string_suffix(cheat_input, ".q"))
1094 DEBUG_SetMaximumDynamite();
1097 else if (game_status == GAME_MODE_EDITOR)
1099 if (is_string_suffix(cheat_input, ":dump-brush") ||
1100 is_string_suffix(cheat_input, ":DB"))
1104 else if (is_string_suffix(cheat_input, ":DDB"))
1111 void HandleKey(Key key, int key_status)
1113 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1114 static struct SetupKeyboardInfo ski;
1115 static struct SetupShortcutInfo ssi;
1124 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1125 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1126 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1127 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1128 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1129 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1134 if (game_status == GAME_MODE_PLAYING)
1136 /* only needed for single-step tape recording mode */
1137 static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1138 static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1139 static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1140 static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1143 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1145 byte key_action = 0;
1147 if (setup.input[pnr].use_joystick)
1150 ski = setup.input[pnr].key;
1152 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1153 if (key == *key_info[i].key_custom)
1154 key_action |= key_info[i].action;
1156 /* use combined snap+direction keys for the first player only */
1159 ssi = setup.shortcut;
1161 for (i = 0; i < NUM_DIRECTIONS; i++)
1162 if (key == *key_info[i].key_snap)
1163 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1166 /* clear delayed snap and drop actions in single step mode (see below) */
1167 if (tape.single_step)
1169 if (clear_snap_button[pnr])
1171 stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1172 clear_snap_button[pnr] = FALSE;
1175 if (clear_drop_button[pnr])
1177 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1178 clear_drop_button[pnr] = FALSE;
1182 if (key_status == KEY_PRESSED)
1183 stored_player[pnr].action |= key_action;
1185 stored_player[pnr].action &= ~key_action;
1187 if (tape.single_step && tape.recording && tape.pausing)
1189 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1191 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1193 /* if snap key already pressed, don't snap when releasing (below) */
1194 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1195 element_snapped[pnr] = TRUE;
1197 /* if drop key already pressed, don't drop when releasing (below) */
1198 if (stored_player[pnr].action & KEY_BUTTON_DROP)
1199 element_dropped[pnr] = TRUE;
1201 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1203 if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1204 level.game_engine_type == GAME_ENGINE_TYPE_SP)
1207 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1208 getRedDiskReleaseFlag_SP() == 0)
1209 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1211 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1214 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1216 if (key_action & KEY_BUTTON_SNAP)
1218 /* if snap key was released without moving (see above), snap now */
1219 if (!element_snapped[pnr])
1221 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1223 stored_player[pnr].action |= KEY_BUTTON_SNAP;
1225 /* clear delayed snap button on next event */
1226 clear_snap_button[pnr] = TRUE;
1229 element_snapped[pnr] = FALSE;
1232 if (key_action & KEY_BUTTON_DROP &&
1233 level.game_engine_type == GAME_ENGINE_TYPE_RND)
1235 /* if drop key was released without moving (see above), drop now */
1236 if (!element_dropped[pnr])
1238 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1240 if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1241 getRedDiskReleaseFlag_SP() != 0)
1242 stored_player[pnr].action |= KEY_BUTTON_DROP;
1244 /* clear delayed drop button on next event */
1245 clear_drop_button[pnr] = TRUE;
1248 element_dropped[pnr] = FALSE;
1252 else if (tape.recording && tape.pausing)
1254 /* prevent key release events from un-pausing a paused game */
1255 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1256 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1262 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1263 if (key == key_info[i].key_default)
1264 joy |= key_info[i].action;
1269 if (key_status == KEY_PRESSED)
1270 key_joystick_mapping |= joy;
1272 key_joystick_mapping &= ~joy;
1277 if (game_status != GAME_MODE_PLAYING)
1278 key_joystick_mapping = 0;
1280 if (key_status == KEY_RELEASED)
1283 if ((key == KSYM_F11 ||
1284 ((key == KSYM_Return ||
1285 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1286 video.fullscreen_available)
1288 setup.fullscreen = !setup.fullscreen;
1290 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1292 if (game_status == GAME_MODE_SETUP)
1293 RedrawSetupScreenAfterFullscreenToggle();
1298 if ((key == KSYM_minus ||
1301 ((GetKeyModState() & KMOD_Control) ||
1302 (GetKeyModState() & KMOD_Alt)) &&
1303 video.window_scaling_available &&
1304 !video.fullscreen_enabled)
1307 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1309 setup.window_scaling_percent +=
1310 (key == KSYM_minus ? -1 : +1) * STEP_WINDOW_SCALING_PERCENT;
1312 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1313 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1314 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1315 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1317 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1319 if (game_status == GAME_MODE_SETUP)
1320 RedrawSetupScreenAfterFullscreenToggle();
1325 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1326 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1333 if (game_status == GAME_MODE_MAIN &&
1334 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1336 StartGameActions(options.network, setup.autorecord, level.random_seed);
1341 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1343 if (key == setup.shortcut.save_game)
1345 else if (key == setup.shortcut.load_game)
1347 else if (key == setup.shortcut.toggle_pause)
1348 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1350 HandleTapeButtonKeys(key);
1351 HandleSoundButtonKeys(key);
1354 if (game_status == GAME_MODE_PLAYING && !network_playing)
1356 int centered_player_nr_next = -999;
1358 if (key == setup.shortcut.focus_player_all)
1359 centered_player_nr_next = -1;
1361 for (i = 0; i < MAX_PLAYERS; i++)
1362 if (key == setup.shortcut.focus_player[i])
1363 centered_player_nr_next = i;
1365 if (centered_player_nr_next != -999)
1367 game.centered_player_nr_next = centered_player_nr_next;
1368 game.set_centered_player = TRUE;
1372 tape.centered_player_nr_next = game.centered_player_nr_next;
1373 tape.set_centered_player = TRUE;
1378 HandleKeysSpecial(key);
1380 if (HandleGadgetsKeyInput(key))
1382 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1383 key = KSYM_UNDEFINED;
1386 switch (game_status)
1388 case GAME_MODE_PSEUDO_TYPENAME:
1389 HandleTypeName(0, key);
1392 case GAME_MODE_TITLE:
1393 case GAME_MODE_MAIN:
1394 case GAME_MODE_LEVELS:
1395 case GAME_MODE_LEVELNR:
1396 case GAME_MODE_SETUP:
1397 case GAME_MODE_INFO:
1398 case GAME_MODE_SCORES:
1403 if (game_status == GAME_MODE_TITLE)
1404 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1405 else if (game_status == GAME_MODE_MAIN)
1406 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1407 else if (game_status == GAME_MODE_LEVELS)
1408 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1409 else if (game_status == GAME_MODE_LEVELNR)
1410 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1411 else if (game_status == GAME_MODE_SETUP)
1412 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1413 else if (game_status == GAME_MODE_INFO)
1414 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1415 else if (game_status == GAME_MODE_SCORES)
1416 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1420 if (game_status != GAME_MODE_MAIN)
1421 FadeSkipNextFadeIn();
1423 if (game_status == GAME_MODE_TITLE)
1424 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1425 else if (game_status == GAME_MODE_LEVELS)
1426 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1427 else if (game_status == GAME_MODE_LEVELNR)
1428 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1429 else if (game_status == GAME_MODE_SETUP)
1430 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1431 else if (game_status == GAME_MODE_INFO)
1432 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1433 else if (game_status == GAME_MODE_SCORES)
1434 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1438 if (game_status == GAME_MODE_LEVELS)
1439 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1440 else if (game_status == GAME_MODE_LEVELNR)
1441 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1442 else if (game_status == GAME_MODE_SETUP)
1443 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1444 else if (game_status == GAME_MODE_INFO)
1445 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1446 else if (game_status == GAME_MODE_SCORES)
1447 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1450 case KSYM_Page_Down:
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);
1465 GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
1469 setup.sp_show_border_elements = !setup.sp_show_border_elements;
1470 printf("Supaplex border elements %s\n",
1471 setup.sp_show_border_elements ? "enabled" : "disabled");
1480 case GAME_MODE_EDITOR:
1481 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1482 HandleLevelEditorKeyInput(key);
1485 case GAME_MODE_PLAYING:
1490 RequestQuitGame(setup.ask_on_escape);
1497 if (GameFrameDelay == 500)
1498 GameFrameDelay = GAME_FRAME_DELAY;
1500 GameFrameDelay = 500;
1503 GameFrameDelay = (key - KSYM_0) * 10;
1504 printf("Game speed == %d%% (%d ms delay between two frames)\n",
1505 GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
1511 options.debug = FALSE;
1512 printf("debug mode disabled\n");
1516 options.debug = TRUE;
1517 printf("debug mode enabled\n");
1523 if (!global.fps_slowdown)
1525 global.fps_slowdown = TRUE;
1526 global.fps_slowdown_factor = 2;
1527 printf("fps slowdown enabled -- display only every 2nd frame\n");
1529 else if (global.fps_slowdown_factor == 2)
1531 global.fps_slowdown_factor = 4;
1532 printf("fps slowdown enabled -- display only every 4th frame\n");
1536 global.fps_slowdown = FALSE;
1537 global.fps_slowdown_factor = 1;
1538 printf("fps slowdown disabled\n");
1544 printf("::: currently using game engine version %d\n",
1545 game.engine_version);
1556 if (key == KSYM_Escape)
1558 game_status = GAME_MODE_MAIN;
1566 void HandleNoEvent()
1568 if (button_status && game_status != GAME_MODE_PLAYING)
1570 HandleButton(0, 0, -button_status, button_status);
1577 #if defined(NETWORK_AVALIABLE)
1578 if (options.network)
1582 switch (game_status)
1584 case GAME_MODE_MAIN:
1585 DrawPreviewLevelAnimation();
1589 case GAME_MODE_LEVELS:
1590 case GAME_MODE_LEVELNR:
1591 case GAME_MODE_SETUP:
1592 case GAME_MODE_INFO:
1593 case GAME_MODE_SCORES:
1597 case GAME_MODE_EDITOR:
1598 HandleLevelEditorIdle();
1606 static int HandleJoystickForAllPlayers()
1611 for (i = 0; i < MAX_PLAYERS; i++)
1613 byte joy_action = 0;
1616 if (!setup.input[i].use_joystick)
1620 joy_action = Joystick(i);
1621 result |= joy_action;
1623 if (!setup.input[i].use_joystick)
1626 stored_player[i].action = joy_action;
1632 void HandleJoystick()
1634 int joystick = HandleJoystickForAllPlayers();
1635 int keyboard = key_joystick_mapping;
1636 int joy = (joystick | keyboard);
1637 int left = joy & JOY_LEFT;
1638 int right = joy & JOY_RIGHT;
1639 int up = joy & JOY_UP;
1640 int down = joy & JOY_DOWN;
1641 int button = joy & JOY_BUTTON;
1642 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1643 int dx = (left ? -1 : right ? 1 : 0);
1644 int dy = (up ? -1 : down ? 1 : 0);
1646 switch (game_status)
1648 case GAME_MODE_TITLE:
1649 case GAME_MODE_MAIN:
1650 case GAME_MODE_LEVELS:
1651 case GAME_MODE_LEVELNR:
1652 case GAME_MODE_SETUP:
1653 case GAME_MODE_INFO:
1655 static unsigned int joystickmove_delay = 0;
1657 if (joystick && !button &&
1658 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1659 newbutton = dx = dy = 0;
1661 if (game_status == GAME_MODE_TITLE)
1662 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1663 else if (game_status == GAME_MODE_MAIN)
1664 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1665 else if (game_status == GAME_MODE_LEVELS)
1666 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1667 else if (game_status == GAME_MODE_LEVELNR)
1668 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1669 else if (game_status == GAME_MODE_SETUP)
1670 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1671 else if (game_status == GAME_MODE_INFO)
1672 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1676 case GAME_MODE_SCORES:
1677 HandleHallOfFame(0, 0, dx, dy, !newbutton);
1680 case GAME_MODE_PLAYING:
1681 if (tape.playing || keyboard)
1682 newbutton = ((joy & JOY_BUTTON) != 0);
1684 if (newbutton && AllPlayersGone)