1 /***********************************************************
2 * Rocks'n'Diamonds -- McDuffin Strikes Back! *
3 *----------------------------------------------------------*
4 * (c) 1995-2002 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include "libgame/libgame.h"
27 static boolean cursor_inside_playfield = FALSE;
28 static boolean playfield_cursor_set = FALSE;
29 static unsigned long playfield_cursor_delay = 0;
32 /* event filter especially needed for SDL event filtering due to
33 delay problems with lots of mouse motion events when mouse button
34 not pressed (X11 can handle this with 'PointerMotionHintMask') */
36 int FilterMouseMotionEvents(const Event *event)
40 /* non-motion events are directly passed to event handler functions */
41 if (event->type != EVENT_MOTIONNOTIFY)
44 motion = (MotionEvent *)event;
45 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
46 motion->y >= SY && motion->y < SY + SYSIZE);
48 if (game_status == GAME_MODE_PLAYING && playfield_cursor_set)
50 SetMouseCursor(CURSOR_DEFAULT);
51 playfield_cursor_set = FALSE;
52 DelayReached(&playfield_cursor_delay, 0);
55 /* skip mouse motion events without pressed button outside level editor */
56 if (button_status == MB_RELEASED &&
57 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
63 /* to prevent delay problems, skip mouse motion events if the very next
64 event is also a mouse motion event (and therefore effectively only
65 handling the last of a row of mouse motion events in the event queue) */
67 boolean SkipPressedMouseMotionEvent(const Event *event)
69 /* nothing to do if the current event is not a mouse motion event */
70 if (event->type != EVENT_MOTIONNOTIFY)
73 /* only skip motion events with pressed button outside level editor */
74 if (button_status == MB_RELEASED ||
75 game_status == GAME_MODE_EDITOR || game_status == GAME_MODE_PLAYING)
82 PeekEvent(&next_event);
84 /* if next event is also a mouse motion event, skip the current one */
85 if (next_event.type == EVENT_MOTIONNOTIFY)
92 /* this is only really needed for non-SDL targets to filter unwanted events;
93 when using SDL with properly installed event filter, this function can be
94 replaced with a simple "NextEvent()" call, but it doesn't hurt either */
96 static boolean NextValidEvent(Event *event)
98 while (PendingEvent())
100 boolean handle_this_event = FALSE;
104 if (FilterMouseMotionEvents(event))
105 handle_this_event = TRUE;
107 if (SkipPressedMouseMotionEvent(event))
108 handle_this_event = FALSE;
110 if (handle_this_event)
121 if (PendingEvent()) /* got event */
125 while (NextValidEvent(&event))
129 case EVENT_BUTTONPRESS:
130 case EVENT_BUTTONRELEASE:
131 HandleButtonEvent((ButtonEvent *) &event);
134 case EVENT_MOTIONNOTIFY:
135 HandleMotionEvent((MotionEvent *) &event);
139 case EVENT_KEYRELEASE:
140 HandleKeyEvent((KeyEvent *) &event);
144 HandleOtherEvents(&event);
151 /* when playing, display a special mouse pointer inside the playfield */
152 if (game_status == GAME_MODE_PLAYING && !tape.pausing)
154 if (!playfield_cursor_set && cursor_inside_playfield &&
155 DelayReached(&playfield_cursor_delay, 1000))
157 SetMouseCursor(CURSOR_PLAYFIELD);
158 playfield_cursor_set = TRUE;
161 else if (playfield_cursor_set)
163 SetMouseCursor(CURSOR_DEFAULT);
164 playfield_cursor_set = FALSE;
170 /* don't use all CPU time when idle; the main loop while playing
171 has its own synchronization and is CPU friendly, too */
173 if (game_status == GAME_MODE_PLAYING)
180 if (!PendingEvent()) /* delay only if no pending events */
184 /* refresh window contents from drawing buffer, if needed */
187 if (game_status == GAME_MODE_QUIT)
192 void HandleOtherEvents(Event *event)
197 HandleExposeEvent((ExposeEvent *) event);
200 case EVENT_UNMAPNOTIFY:
202 /* This causes the game to stop not only when iconified, but also
203 when on another virtual desktop, which might be not desired. */
204 SleepWhileUnmapped();
210 HandleFocusEvent((FocusChangeEvent *) event);
213 case EVENT_CLIENTMESSAGE:
214 HandleClientMessageEvent((ClientMessageEvent *) event);
217 #if defined(TARGET_SDL)
218 case SDL_JOYAXISMOTION:
219 case SDL_JOYBUTTONDOWN:
220 case SDL_JOYBUTTONUP:
221 HandleJoystickEvent(event);
230 void ClearEventQueue()
232 while (PendingEvent())
240 case EVENT_BUTTONRELEASE:
241 button_status = MB_RELEASED;
244 case EVENT_KEYRELEASE:
245 key_joystick_mapping = 0;
249 HandleOtherEvents(&event);
255 void ClearPlayerAction()
259 /* simulate key release events for still pressed keys */
260 key_joystick_mapping = 0;
261 for (i = 0; i < MAX_PLAYERS; i++)
262 stored_player[i].action = 0;
265 void SleepWhileUnmapped()
267 boolean window_unmapped = TRUE;
269 KeyboardAutoRepeatOn();
271 while (window_unmapped)
279 case EVENT_BUTTONRELEASE:
280 button_status = MB_RELEASED;
283 case EVENT_KEYRELEASE:
284 key_joystick_mapping = 0;
287 case EVENT_MAPNOTIFY:
288 window_unmapped = FALSE;
291 case EVENT_UNMAPNOTIFY:
292 /* this is only to surely prevent the 'should not happen' case
293 * of recursively looping between 'SleepWhileUnmapped()' and
294 * 'HandleOtherEvents()' which usually calls this funtion.
299 HandleOtherEvents(&event);
304 if (game_status == GAME_MODE_PLAYING)
305 KeyboardAutoRepeatOffUnlessAutoplay();
308 void HandleExposeEvent(ExposeEvent *event)
311 RedrawPlayfield(FALSE, event->x, event->y, event->width, event->height);
316 void HandleButtonEvent(ButtonEvent *event)
318 motion_status = FALSE;
320 if (event->type == EVENT_BUTTONPRESS)
321 button_status = event->button;
323 button_status = MB_RELEASED;
325 HandleButton(event->x, event->y, button_status);
328 void HandleMotionEvent(MotionEvent *event)
330 if (!PointerInWindow(window))
331 return; /* window and pointer are on different screens */
333 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
336 motion_status = TRUE;
338 HandleButton(event->x, event->y, button_status);
341 void HandleKeyEvent(KeyEvent *event)
343 int key_status = (event->type==EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
344 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
345 Key key = GetEventKey(event, with_modifiers);
346 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
348 HandleKeyModState(keymod, key_status);
349 HandleKey(key, key_status);
352 void HandleFocusEvent(FocusChangeEvent *event)
354 static int old_joystick_status = -1;
356 if (event->type == EVENT_FOCUSOUT)
358 KeyboardAutoRepeatOn();
359 old_joystick_status = joystick.status;
360 joystick.status = JOYSTICK_NOT_AVAILABLE;
364 else if (event->type == EVENT_FOCUSIN)
366 /* When there are two Rocks'n'Diamonds windows which overlap and
367 the player moves the pointer from one game window to the other,
368 a 'FocusOut' event is generated for the window the pointer is
369 leaving and a 'FocusIn' event is generated for the window the
370 pointer is entering. In some cases, it can happen that the
371 'FocusIn' event is handled by the one game process before the
372 'FocusOut' event by the other game process. In this case the
373 X11 environment would end up with activated keyboard auto repeat,
374 because unfortunately this is a global setting and not (which
375 would be far better) set for each X11 window individually.
376 The effect would be keyboard auto repeat while playing the game
377 (game_status == GAME_MODE_PLAYING), which is not desired.
378 To avoid this special case, we just wait 1/10 second before
379 processing the 'FocusIn' event.
382 if (game_status == GAME_MODE_PLAYING)
385 KeyboardAutoRepeatOffUnlessAutoplay();
388 if (old_joystick_status != -1)
389 joystick.status = old_joystick_status;
393 void HandleClientMessageEvent(ClientMessageEvent *event)
395 if (CheckCloseWindowEvent(event))
399 void HandleButton(int mx, int my, int button)
401 static int old_mx = 0, old_my = 0;
415 if (HandleGadgets(mx, my, button))
417 /* do not handle this button event anymore */
418 mx = my = -32; /* force mouse event to be outside screen tiles */
424 HandleMainMenu(mx,my, 0,0, button);
427 case GAME_MODE_PSEUDO_TYPENAME:
428 HandleTypeName(0, KSYM_Return);
431 case GAME_MODE_LEVELS:
432 HandleChooseLevel(mx,my, 0,0, button);
435 case GAME_MODE_SCORES:
436 HandleHallOfFame(0,0, 0,0, button);
439 case GAME_MODE_EDITOR:
440 HandleLevelEditorIdle();
444 HandleInfoScreen(mx,my, 0,0, button);
447 case GAME_MODE_SETUP:
448 HandleSetupScreen(mx,my, 0,0, button);
451 case GAME_MODE_PLAYING:
453 if (button == MB_PRESSED && !motion_status && IN_GFX_SCREEN(mx, my))
454 DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
463 static boolean is_string_suffix(char *string, char *suffix)
465 int string_len = strlen(string);
466 int suffix_len = strlen(suffix);
468 if (suffix_len > string_len)
471 return (strcmp(&string[string_len - suffix_len], suffix) == 0);
474 #define MAX_CHEAT_INPUT_LEN 32
476 static void HandleKeysSpecial(Key key)
478 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
479 char letter = getCharFromKey(key);
480 int cheat_input_len = strlen(cheat_input);
486 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
488 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
489 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
491 cheat_input_len = MAX_CHEAT_INPUT_LEN;
494 cheat_input[cheat_input_len++] = letter;
495 cheat_input[cheat_input_len] = '\0';
498 printf("::: '%s' [%d]\n", cheat_input, cheat_input_len);
501 if (game_status == GAME_MODE_MAIN)
503 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
504 is_string_suffix(cheat_input, ":ist"))
506 InsertSolutionTape();
508 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
509 is_string_suffix(cheat_input, ":rg"))
511 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
514 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
515 is_string_suffix(cheat_input, ":rs"))
517 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
520 else if (is_string_suffix(cheat_input, ":reload-music") ||
521 is_string_suffix(cheat_input, ":rm"))
523 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
526 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
527 is_string_suffix(cheat_input, ":ra"))
529 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
530 1 << ARTWORK_TYPE_SOUNDS |
531 1 << ARTWORK_TYPE_MUSIC);
534 else if (is_string_suffix(cheat_input, ":dump-level") ||
535 is_string_suffix(cheat_input, ":dl"))
539 else if (is_string_suffix(cheat_input, ":dump-tape") ||
540 is_string_suffix(cheat_input, ":dt"))
545 else if (game_status == GAME_MODE_PLAYING)
548 if (is_string_suffix(cheat_input, ".q"))
549 DEBUG_SetMaximumDynamite();
552 else if (game_status == GAME_MODE_EDITOR)
554 if (is_string_suffix(cheat_input, ":dump-brush") ||
555 is_string_suffix(cheat_input, ":DB"))
559 else if (is_string_suffix(cheat_input, ":DDB"))
566 void HandleKey(Key key, int key_status)
568 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
569 static struct SetupKeyboardInfo custom_key;
577 { &custom_key.left, DEFAULT_KEY_LEFT, JOY_LEFT },
578 { &custom_key.right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
579 { &custom_key.up, DEFAULT_KEY_UP, JOY_UP },
580 { &custom_key.down, DEFAULT_KEY_DOWN, JOY_DOWN },
581 { &custom_key.snap, DEFAULT_KEY_SNAP, JOY_BUTTON_1 },
582 { &custom_key.drop, DEFAULT_KEY_DROP, JOY_BUTTON_2 }
587 if (game_status == GAME_MODE_PLAYING)
589 /* only needed for single-step tape recording mode */
590 static boolean clear_button_2[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
591 static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
594 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
598 if (setup.input[pnr].use_joystick)
601 custom_key = setup.input[pnr].key;
603 for (i = 0; i < 6; i++)
604 if (key == *key_info[i].key_custom)
605 key_action |= key_info[i].action;
607 if (tape.single_step && clear_button_2[pnr])
609 stored_player[pnr].action &= ~KEY_BUTTON_2;
610 clear_button_2[pnr] = FALSE;
613 if (key_status == KEY_PRESSED)
614 stored_player[pnr].action |= key_action;
616 stored_player[pnr].action &= ~key_action;
618 if (tape.single_step && tape.recording && tape.pausing)
620 if (key_status == KEY_PRESSED &&
621 (key_action & (KEY_MOTION | KEY_BUTTON_1)))
623 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
625 if (key_action & KEY_MOTION)
627 if (stored_player[pnr].action & KEY_BUTTON_2)
628 element_dropped[pnr] = TRUE;
631 else if (key_status == KEY_RELEASED &&
632 (key_action & KEY_BUTTON_2))
634 if (!element_dropped[pnr])
636 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
638 stored_player[pnr].action |= KEY_BUTTON_2;
639 clear_button_2[pnr] = TRUE;
642 element_dropped[pnr] = FALSE;
645 else if (tape.recording && tape.pausing && (key_action & KEY_ACTION))
646 TapeTogglePause(TAPE_TOGGLE_MANUAL);
651 for (i = 0; i < 6; i++)
652 if (key == key_info[i].key_default)
653 joy |= key_info[i].action;
658 if (key_status == KEY_PRESSED)
659 key_joystick_mapping |= joy;
661 key_joystick_mapping &= ~joy;
666 if (game_status != GAME_MODE_PLAYING)
667 key_joystick_mapping = 0;
669 if (key_status == KEY_RELEASED)
672 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
673 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
675 CloseDoor(DOOR_CLOSE_1);
676 game_status = GAME_MODE_MAIN;
682 if (game_status == GAME_MODE_MAIN &&
683 (key == setup.shortcut.toggle_pause || key == KSYM_space))
685 StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
690 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
692 if (key == setup.shortcut.save_game)
694 else if (key == setup.shortcut.load_game)
696 else if (key == setup.shortcut.toggle_pause)
697 TapeTogglePause(TAPE_TOGGLE_MANUAL);
700 if (game_status == GAME_MODE_PLAYING && !network_playing)
702 int centered_player_nr_next = -999;
704 if (key == setup.shortcut.focus_player_all)
705 centered_player_nr_next = -1;
707 for (i = 0; i < MAX_PLAYERS; i++)
708 if (key == setup.shortcut.focus_player[i])
709 centered_player_nr_next = i;
711 if (centered_player_nr_next != -999)
713 game.centered_player_nr_next = centered_player_nr_next;
714 game.set_centered_player = TRUE;
718 tape.centered_player_nr_next = game.centered_player_nr_next;
719 tape.set_centered_player = TRUE;
724 HandleKeysSpecial(key);
726 if (HandleGadgetsKeyInput(key))
728 if (key != KSYM_Escape) /* always allow ESC key to be handled */
729 key = KSYM_UNDEFINED;
734 case GAME_MODE_PSEUDO_TYPENAME:
735 HandleTypeName(0, key);
739 case GAME_MODE_LEVELS:
740 case GAME_MODE_SETUP:
747 /* !!! only use "space" key to start game from main menu !!! */
751 if (game_status == GAME_MODE_MAIN)
752 HandleMainMenu(0,0, 0,0, MB_MENU_CHOICE);
753 else if (game_status == GAME_MODE_LEVELS)
754 HandleChooseLevel(0,0, 0,0, MB_MENU_CHOICE);
755 else if (game_status == GAME_MODE_SETUP)
756 HandleSetupScreen(0,0, 0,0, MB_MENU_CHOICE);
757 else if (game_status == GAME_MODE_INFO)
758 HandleInfoScreen(0,0, 0,0, MB_MENU_CHOICE);
762 if (game_status == GAME_MODE_LEVELS)
763 HandleChooseLevel(0,0, 0,0, MB_MENU_LEAVE);
764 else if (game_status == GAME_MODE_SETUP)
765 HandleSetupScreen(0,0, 0,0, MB_MENU_LEAVE);
766 else if (game_status == GAME_MODE_INFO)
767 HandleInfoScreen(0,0, 0,0, MB_MENU_LEAVE);
771 if (game_status == GAME_MODE_LEVELS)
772 HandleChooseLevel(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
773 else if (game_status == GAME_MODE_SETUP)
774 HandleSetupScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
775 else if (game_status == GAME_MODE_INFO)
776 HandleInfoScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
780 if (game_status == GAME_MODE_LEVELS)
781 HandleChooseLevel(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
782 else if (game_status == GAME_MODE_SETUP)
783 HandleSetupScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
784 else if (game_status == GAME_MODE_INFO)
785 HandleInfoScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
790 GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
799 case GAME_MODE_SCORES:
805 game_status = GAME_MODE_MAIN;
810 HandleHallOfFame(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
814 HandleHallOfFame(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
822 case GAME_MODE_EDITOR:
823 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
824 HandleLevelEditorKeyInput(key);
827 case GAME_MODE_PLAYING:
832 RequestQuitGame(setup.ask_on_escape);
850 if (GameFrameDelay == 500)
851 GameFrameDelay = GAME_FRAME_DELAY;
853 GameFrameDelay = 500;
856 GameFrameDelay = (key - KSYM_0) * 10;
857 printf("Game speed == %d%% (%d ms delay between two frames)\n",
858 GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
864 options.debug = FALSE;
865 printf("debug mode disabled\n");
869 options.debug = TRUE;
870 printf("debug mode enabled\n");
875 if (!global.fps_slowdown)
877 global.fps_slowdown = TRUE;
878 global.fps_slowdown_factor = 2;
879 printf("fps slowdown enabled -- display only every 2nd frame\n");
881 else if (global.fps_slowdown_factor == 2)
883 global.fps_slowdown_factor = 4;
884 printf("fps slowdown enabled -- display only every 4th frame\n");
888 global.fps_slowdown = FALSE;
889 global.fps_slowdown_factor = 1;
890 printf("fps slowdown disabled\n");
895 ScrollStepSize = TILEX/8;
896 printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
900 ScrollStepSize = TILEX/4;
901 printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
905 ScrollStepSize = TILEX/2;
906 printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
910 ScrollStepSize = TILEX;
911 printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
915 printf("::: currently using game engine version %d\n",
916 game.engine_version);
927 if (key == KSYM_Escape)
929 game_status = GAME_MODE_MAIN;
939 if (button_status && game_status != GAME_MODE_PLAYING)
941 HandleButton(0, 0, -button_status);
945 #if defined(NETWORK_AVALIABLE)
953 static int HandleJoystickForAllPlayers()
958 for (i = 0; i < MAX_PLAYERS; i++)
963 if (!setup.input[i].use_joystick)
967 joy_action = Joystick(i);
968 result |= joy_action;
970 if (!setup.input[i].use_joystick)
973 stored_player[i].action = joy_action;
979 void HandleJoystick()
981 int joystick = HandleJoystickForAllPlayers();
982 int keyboard = key_joystick_mapping;
983 int joy = (joystick | keyboard);
984 int left = joy & JOY_LEFT;
985 int right = joy & JOY_RIGHT;
986 int up = joy & JOY_UP;
987 int down = joy & JOY_DOWN;
988 int button = joy & JOY_BUTTON;
989 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
990 int dx = (left ? -1 : right ? 1 : 0);
991 int dy = (up ? -1 : down ? 1 : 0);
996 case GAME_MODE_LEVELS:
997 case GAME_MODE_SETUP:
1000 static unsigned long joystickmove_delay = 0;
1002 if (joystick && !button &&
1003 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1004 newbutton = dx = dy = 0;
1006 if (game_status == GAME_MODE_MAIN)
1007 HandleMainMenu(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1008 else if (game_status == GAME_MODE_LEVELS)
1009 HandleChooseLevel(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1010 else if (game_status == GAME_MODE_SETUP)
1011 HandleSetupScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1012 else if (game_status == GAME_MODE_INFO)
1013 HandleInfoScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1017 case GAME_MODE_SCORES:
1018 HandleHallOfFame(0,0, dx,dy, !newbutton);
1021 case GAME_MODE_EDITOR:
1022 HandleLevelEditorIdle();
1025 case GAME_MODE_PLAYING:
1026 if (tape.playing || keyboard)
1027 newbutton = ((joy & JOY_BUTTON) != 0);
1029 if (AllPlayersGone && newbutton)
1031 CloseDoor(DOOR_CLOSE_1);
1032 game_status = GAME_MODE_MAIN;