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)
178 if (!PendingEvent()) /* delay only if no pending events */
182 /* refresh window contents from drawing buffer, if needed */
185 if (game_status == GAME_MODE_QUIT)
190 void HandleOtherEvents(Event *event)
195 HandleExposeEvent((ExposeEvent *) event);
198 case EVENT_UNMAPNOTIFY:
200 /* This causes the game to stop not only when iconified, but also
201 when on another virtual desktop, which might be not desired. */
202 SleepWhileUnmapped();
208 HandleFocusEvent((FocusChangeEvent *) event);
211 case EVENT_CLIENTMESSAGE:
212 HandleClientMessageEvent((ClientMessageEvent *) event);
215 #if defined(TARGET_SDL)
216 case SDL_JOYAXISMOTION:
217 case SDL_JOYBUTTONDOWN:
218 case SDL_JOYBUTTONUP:
219 HandleJoystickEvent(event);
228 void ClearEventQueue()
230 while (PendingEvent())
238 case EVENT_BUTTONRELEASE:
239 button_status = MB_RELEASED;
242 case EVENT_KEYRELEASE:
243 key_joystick_mapping = 0;
247 HandleOtherEvents(&event);
253 void ClearPlayerAction()
257 /* simulate key release events for still pressed keys */
258 key_joystick_mapping = 0;
259 for (i = 0; i < MAX_PLAYERS; i++)
260 stored_player[i].action = 0;
263 void SleepWhileUnmapped()
265 boolean window_unmapped = TRUE;
267 KeyboardAutoRepeatOn();
269 while (window_unmapped)
277 case EVENT_BUTTONRELEASE:
278 button_status = MB_RELEASED;
281 case EVENT_KEYRELEASE:
282 key_joystick_mapping = 0;
285 case EVENT_MAPNOTIFY:
286 window_unmapped = FALSE;
289 case EVENT_UNMAPNOTIFY:
290 /* this is only to surely prevent the 'should not happen' case
291 * of recursively looping between 'SleepWhileUnmapped()' and
292 * 'HandleOtherEvents()' which usually calls this funtion.
297 HandleOtherEvents(&event);
302 if (game_status == GAME_MODE_PLAYING)
303 KeyboardAutoRepeatOffUnlessAutoplay();
306 void HandleExposeEvent(ExposeEvent *event)
309 RedrawPlayfield(FALSE, event->x, event->y, event->width, event->height);
314 void HandleButtonEvent(ButtonEvent *event)
316 motion_status = FALSE;
318 if (event->type == EVENT_BUTTONPRESS)
319 button_status = event->button;
321 button_status = MB_RELEASED;
323 HandleButton(event->x, event->y, button_status);
326 void HandleMotionEvent(MotionEvent *event)
328 if (!PointerInWindow(window))
329 return; /* window and pointer are on different screens */
331 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
334 motion_status = TRUE;
336 HandleButton(event->x, event->y, button_status);
339 void HandleKeyEvent(KeyEvent *event)
341 int key_status = (event->type==EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
342 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
343 Key key = GetEventKey(event, with_modifiers);
344 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
346 HandleKeyModState(keymod, key_status);
347 HandleKey(key, key_status);
350 void HandleFocusEvent(FocusChangeEvent *event)
352 static int old_joystick_status = -1;
354 if (event->type == EVENT_FOCUSOUT)
356 KeyboardAutoRepeatOn();
357 old_joystick_status = joystick.status;
358 joystick.status = JOYSTICK_NOT_AVAILABLE;
362 else if (event->type == EVENT_FOCUSIN)
364 /* When there are two Rocks'n'Diamonds windows which overlap and
365 the player moves the pointer from one game window to the other,
366 a 'FocusOut' event is generated for the window the pointer is
367 leaving and a 'FocusIn' event is generated for the window the
368 pointer is entering. In some cases, it can happen that the
369 'FocusIn' event is handled by the one game process before the
370 'FocusOut' event by the other game process. In this case the
371 X11 environment would end up with activated keyboard auto repeat,
372 because unfortunately this is a global setting and not (which
373 would be far better) set for each X11 window individually.
374 The effect would be keyboard auto repeat while playing the game
375 (game_status == GAME_MODE_PLAYING), which is not desired.
376 To avoid this special case, we just wait 1/10 second before
377 processing the 'FocusIn' event.
380 if (game_status == GAME_MODE_PLAYING)
383 KeyboardAutoRepeatOffUnlessAutoplay();
386 if (old_joystick_status != -1)
387 joystick.status = old_joystick_status;
391 void HandleClientMessageEvent(ClientMessageEvent *event)
393 if (CheckCloseWindowEvent(event))
397 void HandleButton(int mx, int my, int button)
399 static int old_mx = 0, old_my = 0;
413 if (HandleGadgets(mx, my, button))
415 /* do not handle this button event anymore */
416 mx = my = -32; /* force mouse event to be outside screen tiles */
422 HandleMainMenu(mx,my, 0,0, button);
425 case GAME_MODE_PSEUDO_TYPENAME:
426 HandleTypeName(0, KSYM_Return);
429 case GAME_MODE_LEVELS:
430 HandleChooseLevel(mx,my, 0,0, button);
433 case GAME_MODE_SCORES:
434 HandleHallOfFame(0,0, 0,0, button);
437 case GAME_MODE_EDITOR:
438 HandleLevelEditorIdle();
442 HandleInfoScreen(mx,my, 0,0, button);
445 case GAME_MODE_SETUP:
446 HandleSetupScreen(mx,my, 0,0, button);
449 case GAME_MODE_PLAYING:
451 if (button == MB_PRESSED && !motion_status && IN_GFX_SCREEN(mx, my))
452 DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
461 static boolean is_string_suffix(char *string, char *suffix)
463 int string_len = strlen(string);
464 int suffix_len = strlen(suffix);
466 if (suffix_len > string_len)
469 return (strcmp(&string[string_len - suffix_len], suffix) == 0);
472 #define MAX_CHEAT_INPUT_LEN 32
474 static void HandleKeysSpecial(Key key)
476 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
477 char letter = getCharFromKey(key);
478 int cheat_input_len = strlen(cheat_input);
484 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
486 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
487 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
489 cheat_input_len = MAX_CHEAT_INPUT_LEN;
492 cheat_input[cheat_input_len++] = letter;
493 cheat_input[cheat_input_len] = '\0';
496 printf("::: '%s' [%d]\n", cheat_input, cheat_input_len);
499 if (game_status == GAME_MODE_MAIN)
501 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
502 is_string_suffix(cheat_input, ":ist"))
504 InsertSolutionTape();
506 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
507 is_string_suffix(cheat_input, ":rg"))
509 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
512 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
513 is_string_suffix(cheat_input, ":rs"))
515 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
518 else if (is_string_suffix(cheat_input, ":reload-music") ||
519 is_string_suffix(cheat_input, ":rm"))
521 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
524 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
525 is_string_suffix(cheat_input, ":ra"))
527 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
528 1 << ARTWORK_TYPE_SOUNDS |
529 1 << ARTWORK_TYPE_MUSIC);
532 else if (is_string_suffix(cheat_input, ":dump-level") ||
533 is_string_suffix(cheat_input, ":dl"))
537 else if (is_string_suffix(cheat_input, ":dump-tape") ||
538 is_string_suffix(cheat_input, ":dt"))
543 else if (game_status == GAME_MODE_PLAYING)
546 if (is_string_suffix(cheat_input, ".q"))
547 DEBUG_SetMaximumDynamite();
550 else if (game_status == GAME_MODE_EDITOR)
552 if (is_string_suffix(cheat_input, ":dump-brush") ||
553 is_string_suffix(cheat_input, ":DB"))
557 else if (is_string_suffix(cheat_input, ":DDB"))
564 void HandleKey(Key key, int key_status)
566 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
567 static struct SetupKeyboardInfo custom_key;
575 { &custom_key.left, DEFAULT_KEY_LEFT, JOY_LEFT },
576 { &custom_key.right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
577 { &custom_key.up, DEFAULT_KEY_UP, JOY_UP },
578 { &custom_key.down, DEFAULT_KEY_DOWN, JOY_DOWN },
579 { &custom_key.snap, DEFAULT_KEY_SNAP, JOY_BUTTON_1 },
580 { &custom_key.drop, DEFAULT_KEY_DROP, JOY_BUTTON_2 }
585 if (game_status == GAME_MODE_PLAYING)
587 /* only needed for single-step tape recording mode */
588 static boolean clear_button_2[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
589 static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
592 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
596 if (setup.input[pnr].use_joystick)
599 custom_key = setup.input[pnr].key;
601 for (i = 0; i < 6; i++)
602 if (key == *key_info[i].key_custom)
603 key_action |= key_info[i].action;
605 if (tape.single_step && clear_button_2[pnr])
607 stored_player[pnr].action &= ~KEY_BUTTON_2;
608 clear_button_2[pnr] = FALSE;
611 if (key_status == KEY_PRESSED)
612 stored_player[pnr].action |= key_action;
614 stored_player[pnr].action &= ~key_action;
616 if (tape.single_step && tape.recording && tape.pausing)
618 if (key_status == KEY_PRESSED &&
619 (key_action & (KEY_MOTION | KEY_BUTTON_1)))
621 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
623 if (key_action & KEY_MOTION)
625 if (stored_player[pnr].action & KEY_BUTTON_2)
626 element_dropped[pnr] = TRUE;
629 else if (key_status == KEY_RELEASED &&
630 (key_action & KEY_BUTTON_2))
632 if (!element_dropped[pnr])
634 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
636 stored_player[pnr].action |= KEY_BUTTON_2;
637 clear_button_2[pnr] = TRUE;
640 element_dropped[pnr] = FALSE;
643 else if (tape.recording && tape.pausing && (key_action & KEY_ACTION))
644 TapeTogglePause(TAPE_TOGGLE_MANUAL);
649 for (i = 0; i < 6; i++)
650 if (key == key_info[i].key_default)
651 joy |= key_info[i].action;
656 if (key_status == KEY_PRESSED)
657 key_joystick_mapping |= joy;
659 key_joystick_mapping &= ~joy;
664 if (game_status != GAME_MODE_PLAYING)
665 key_joystick_mapping = 0;
667 if (key_status == KEY_RELEASED)
670 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
671 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
673 CloseDoor(DOOR_CLOSE_1);
674 game_status = GAME_MODE_MAIN;
680 if (game_status == GAME_MODE_MAIN &&
681 (key == setup.shortcut.toggle_pause || key == KSYM_space))
683 StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
688 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
690 if (key == setup.shortcut.save_game)
692 else if (key == setup.shortcut.load_game)
694 else if (key == setup.shortcut.toggle_pause)
695 TapeTogglePause(TAPE_TOGGLE_MANUAL);
698 if (game_status == GAME_MODE_PLAYING)
700 int centered_player_nr_next = -999;
702 if (key == setup.shortcut.focus_player_all)
703 centered_player_nr_next = -1;
705 for (i = 0; i < MAX_PLAYERS; i++)
706 if (key == setup.shortcut.focus_player[i])
707 centered_player_nr_next = i;
709 if (centered_player_nr_next != -999)
711 game.centered_player_nr_next = centered_player_nr_next;
712 game.set_centered_player = TRUE;
716 tape.centered_player_nr_next = game.centered_player_nr_next;
717 tape.set_centered_player = TRUE;
722 HandleKeysSpecial(key);
724 if (HandleGadgetsKeyInput(key))
726 if (key != KSYM_Escape) /* always allow ESC key to be handled */
727 key = KSYM_UNDEFINED;
732 case GAME_MODE_PSEUDO_TYPENAME:
733 HandleTypeName(0, key);
737 case GAME_MODE_LEVELS:
738 case GAME_MODE_SETUP:
745 /* !!! only use "space" key to start game from main menu !!! */
749 if (game_status == GAME_MODE_MAIN)
750 HandleMainMenu(0,0, 0,0, MB_MENU_CHOICE);
751 else if (game_status == GAME_MODE_LEVELS)
752 HandleChooseLevel(0,0, 0,0, MB_MENU_CHOICE);
753 else if (game_status == GAME_MODE_SETUP)
754 HandleSetupScreen(0,0, 0,0, MB_MENU_CHOICE);
755 else if (game_status == GAME_MODE_INFO)
756 HandleInfoScreen(0,0, 0,0, MB_MENU_CHOICE);
760 if (game_status == GAME_MODE_LEVELS)
761 HandleChooseLevel(0,0, 0,0, MB_MENU_LEAVE);
762 else if (game_status == GAME_MODE_SETUP)
763 HandleSetupScreen(0,0, 0,0, MB_MENU_LEAVE);
764 else if (game_status == GAME_MODE_INFO)
765 HandleInfoScreen(0,0, 0,0, MB_MENU_LEAVE);
769 if (game_status == GAME_MODE_LEVELS)
770 HandleChooseLevel(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
771 else if (game_status == GAME_MODE_SETUP)
772 HandleSetupScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
773 else if (game_status == GAME_MODE_INFO)
774 HandleInfoScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
778 if (game_status == GAME_MODE_LEVELS)
779 HandleChooseLevel(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
780 else if (game_status == GAME_MODE_SETUP)
781 HandleSetupScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
782 else if (game_status == GAME_MODE_INFO)
783 HandleInfoScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
788 GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
797 case GAME_MODE_SCORES:
803 game_status = GAME_MODE_MAIN;
808 HandleHallOfFame(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
812 HandleHallOfFame(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
820 case GAME_MODE_EDITOR:
821 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
822 HandleLevelEditorKeyInput(key);
825 case GAME_MODE_PLAYING:
830 RequestQuitGame(setup.ask_on_escape);
848 if (GameFrameDelay == 500)
849 GameFrameDelay = GAME_FRAME_DELAY;
851 GameFrameDelay = 500;
854 GameFrameDelay = (key - KSYM_0) * 10;
855 printf("Game speed == %d%% (%d ms delay between two frames)\n",
856 GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
862 options.debug = FALSE;
863 printf("debug mode disabled\n");
867 options.debug = TRUE;
868 printf("debug mode enabled\n");
873 if (!global.fps_slowdown)
875 global.fps_slowdown = TRUE;
876 global.fps_slowdown_factor = 2;
877 printf("fps slowdown enabled -- display only every 2nd frame\n");
879 else if (global.fps_slowdown_factor == 2)
881 global.fps_slowdown_factor = 4;
882 printf("fps slowdown enabled -- display only every 4th frame\n");
886 global.fps_slowdown = FALSE;
887 global.fps_slowdown_factor = 1;
888 printf("fps slowdown disabled\n");
893 ScrollStepSize = TILEX/8;
894 printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
898 ScrollStepSize = TILEX/4;
899 printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
903 ScrollStepSize = TILEX/2;
904 printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
908 ScrollStepSize = TILEX;
909 printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
913 printf("::: currently using game engine version %d\n",
914 game.engine_version);
925 if (key == KSYM_Escape)
927 game_status = GAME_MODE_MAIN;
937 if (button_status && game_status != GAME_MODE_PLAYING)
939 HandleButton(0, 0, -button_status);
943 #if defined(NETWORK_AVALIABLE)
951 static int HandleJoystickForAllPlayers()
956 for (i = 0; i < MAX_PLAYERS; i++)
961 if (!setup.input[i].use_joystick)
965 joy_action = Joystick(i);
966 result |= joy_action;
968 if (!setup.input[i].use_joystick)
971 stored_player[i].action = joy_action;
977 void HandleJoystick()
979 int joystick = HandleJoystickForAllPlayers();
980 int keyboard = key_joystick_mapping;
981 int joy = (joystick | keyboard);
982 int left = joy & JOY_LEFT;
983 int right = joy & JOY_RIGHT;
984 int up = joy & JOY_UP;
985 int down = joy & JOY_DOWN;
986 int button = joy & JOY_BUTTON;
987 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
988 int dx = (left ? -1 : right ? 1 : 0);
989 int dy = (up ? -1 : down ? 1 : 0);
994 case GAME_MODE_LEVELS:
995 case GAME_MODE_SETUP:
998 static unsigned long joystickmove_delay = 0;
1000 if (joystick && !button &&
1001 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1002 newbutton = dx = dy = 0;
1004 if (game_status == GAME_MODE_MAIN)
1005 HandleMainMenu(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1006 else if (game_status == GAME_MODE_LEVELS)
1007 HandleChooseLevel(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1008 else if (game_status == GAME_MODE_SETUP)
1009 HandleSetupScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1010 else if (game_status == GAME_MODE_INFO)
1011 HandleInfoScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1015 case GAME_MODE_SCORES:
1016 HandleHallOfFame(0,0, dx,dy, !newbutton);
1019 case GAME_MODE_EDITOR:
1020 HandleLevelEditorIdle();
1023 case GAME_MODE_PLAYING:
1024 if (tape.playing || keyboard)
1025 newbutton = ((joy & JOY_BUTTON) != 0);
1027 if (AllPlayersGone && newbutton)
1029 CloseDoor(DOOR_CLOSE_1);
1030 game_status = GAME_MODE_MAIN;