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 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
548 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
549 local_player->inventory_element[local_player->inventory_size++] =
553 else if (game_status == GAME_MODE_EDITOR)
555 if (is_string_suffix(cheat_input, ":dump-brush") ||
556 is_string_suffix(cheat_input, ":DB"))
560 else if (is_string_suffix(cheat_input, ":DDB"))
567 void HandleKey(Key key, int key_status)
570 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
571 static struct SetupKeyboardInfo custom_key;
579 { &custom_key.left, DEFAULT_KEY_LEFT, JOY_LEFT },
580 { &custom_key.right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
581 { &custom_key.up, DEFAULT_KEY_UP, JOY_UP },
582 { &custom_key.down, DEFAULT_KEY_DOWN, JOY_DOWN },
583 { &custom_key.snap, DEFAULT_KEY_SNAP, JOY_BUTTON_1 },
584 { &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++)
599 if (setup.input[pnr].use_joystick)
602 custom_key = setup.input[pnr].key;
604 for (i = 0; i < 6; i++)
605 if (key == *key_info[i].key_custom)
606 key_action |= key_info[i].action;
608 if (tape.single_step && clear_button_2[pnr])
610 stored_player[pnr].action &= ~KEY_BUTTON_2;
611 clear_button_2[pnr] = FALSE;
614 if (key_status == KEY_PRESSED)
615 stored_player[pnr].action |= key_action;
617 stored_player[pnr].action &= ~key_action;
619 if (tape.single_step && tape.recording && tape.pausing)
621 if (key_status == KEY_PRESSED &&
622 (key_action & (KEY_MOTION | KEY_BUTTON_1)))
624 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
626 if (key_action & KEY_MOTION)
628 if (stored_player[pnr].action & KEY_BUTTON_2)
629 element_dropped[pnr] = TRUE;
632 else if (key_status == KEY_RELEASED &&
633 (key_action & KEY_BUTTON_2))
635 if (!element_dropped[pnr])
637 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
639 stored_player[pnr].action |= KEY_BUTTON_2;
640 clear_button_2[pnr] = TRUE;
643 element_dropped[pnr] = FALSE;
646 else if (tape.recording && tape.pausing && (key_action & KEY_ACTION))
647 TapeTogglePause(TAPE_TOGGLE_MANUAL);
654 for (i = 0; i < 6; i++)
655 if (key == key_info[i].key_default)
656 joy |= key_info[i].action;
661 if (key_status == KEY_PRESSED)
662 key_joystick_mapping |= joy;
664 key_joystick_mapping &= ~joy;
669 if (game_status != GAME_MODE_PLAYING)
670 key_joystick_mapping = 0;
672 if (key_status == KEY_RELEASED)
675 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
676 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
678 CloseDoor(DOOR_CLOSE_1);
679 game_status = GAME_MODE_MAIN;
685 if (game_status == GAME_MODE_MAIN &&
686 (key == setup.shortcut.toggle_pause || key == KSYM_space))
688 StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
693 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
695 if (key == setup.shortcut.save_game)
697 else if (key == setup.shortcut.load_game)
699 else if (key == setup.shortcut.toggle_pause)
700 TapeTogglePause(TAPE_TOGGLE_MANUAL);
703 HandleKeysSpecial(key);
705 if (HandleGadgetsKeyInput(key))
707 if (key != KSYM_Escape) /* always allow ESC key to be handled */
708 key = KSYM_UNDEFINED;
713 case GAME_MODE_PSEUDO_TYPENAME:
714 HandleTypeName(0, key);
718 case GAME_MODE_LEVELS:
719 case GAME_MODE_SETUP:
726 /* !!! only use "space" key to start game from main menu !!! */
730 if (game_status == GAME_MODE_MAIN)
731 HandleMainMenu(0,0, 0,0, MB_MENU_CHOICE);
732 else if (game_status == GAME_MODE_LEVELS)
733 HandleChooseLevel(0,0, 0,0, MB_MENU_CHOICE);
734 else if (game_status == GAME_MODE_SETUP)
735 HandleSetupScreen(0,0, 0,0, MB_MENU_CHOICE);
736 else if (game_status == GAME_MODE_INFO)
737 HandleInfoScreen(0,0, 0,0, MB_MENU_CHOICE);
741 if (game_status == GAME_MODE_LEVELS)
742 HandleChooseLevel(0,0, 0,0, MB_MENU_LEAVE);
743 else if (game_status == GAME_MODE_SETUP)
744 HandleSetupScreen(0,0, 0,0, MB_MENU_LEAVE);
745 else if (game_status == GAME_MODE_INFO)
746 HandleInfoScreen(0,0, 0,0, MB_MENU_LEAVE);
750 if (game_status == GAME_MODE_LEVELS)
751 HandleChooseLevel(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
752 else if (game_status == GAME_MODE_SETUP)
753 HandleSetupScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
754 else if (game_status == GAME_MODE_INFO)
755 HandleInfoScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
759 if (game_status == GAME_MODE_LEVELS)
760 HandleChooseLevel(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
761 else if (game_status == GAME_MODE_SETUP)
762 HandleSetupScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
763 else if (game_status == GAME_MODE_INFO)
764 HandleInfoScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
769 GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
778 case GAME_MODE_SCORES:
784 game_status = GAME_MODE_MAIN;
789 HandleHallOfFame(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
793 HandleHallOfFame(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
801 case GAME_MODE_EDITOR:
802 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
803 HandleLevelEditorKeyInput(key);
806 case GAME_MODE_PLAYING:
811 RequestQuitGame(setup.ask_on_escape);
827 if (GameFrameDelay == 500)
828 GameFrameDelay = GAME_FRAME_DELAY;
830 GameFrameDelay = 500;
833 GameFrameDelay = (key - KSYM_0) * 10;
834 printf("Game speed == %d%% (%d ms delay between two frames)\n",
835 GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
841 options.debug = FALSE;
842 printf("debug mode disabled\n");
846 options.debug = TRUE;
847 printf("debug mode enabled\n");
852 if (!global.fps_slowdown)
854 global.fps_slowdown = TRUE;
855 global.fps_slowdown_factor = 2;
856 printf("fps slowdown enabled -- display only every 2nd frame\n");
858 else if (global.fps_slowdown_factor == 2)
860 global.fps_slowdown_factor = 4;
861 printf("fps slowdown enabled -- display only every 4th frame\n");
865 global.fps_slowdown = FALSE;
866 global.fps_slowdown_factor = 1;
867 printf("fps slowdown disabled\n");
872 ScrollStepSize = TILEX/8;
873 printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
877 ScrollStepSize = TILEX/4;
878 printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
882 ScrollStepSize = TILEX/2;
883 printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
887 ScrollStepSize = TILEX;
888 printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
892 printf("::: currently using game engine version %d\n",
893 game.engine_version);
904 if (key == KSYM_Escape)
906 game_status = GAME_MODE_MAIN;
916 if (button_status && game_status != GAME_MODE_PLAYING)
918 HandleButton(0, 0, -button_status);
922 #if defined(NETWORK_AVALIABLE)
930 static int HandleJoystickForAllPlayers()
935 for (i = 0; i < MAX_PLAYERS; i++)
940 if (!setup.input[i].use_joystick)
944 joy_action = Joystick(i);
945 result |= joy_action;
947 if (!setup.input[i].use_joystick)
950 stored_player[i].action = joy_action;
956 void HandleJoystick()
958 int joystick = HandleJoystickForAllPlayers();
959 int keyboard = key_joystick_mapping;
960 int joy = (joystick | keyboard);
961 int left = joy & JOY_LEFT;
962 int right = joy & JOY_RIGHT;
963 int up = joy & JOY_UP;
964 int down = joy & JOY_DOWN;
965 int button = joy & JOY_BUTTON;
966 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
967 int dx = (left ? -1 : right ? 1 : 0);
968 int dy = (up ? -1 : down ? 1 : 0);
973 case GAME_MODE_LEVELS:
974 case GAME_MODE_SETUP:
977 static unsigned long joystickmove_delay = 0;
979 if (joystick && !button &&
980 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
981 newbutton = dx = dy = 0;
983 if (game_status == GAME_MODE_MAIN)
984 HandleMainMenu(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
985 else if (game_status == GAME_MODE_LEVELS)
986 HandleChooseLevel(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
987 else if (game_status == GAME_MODE_SETUP)
988 HandleSetupScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
989 else if (game_status == GAME_MODE_INFO)
990 HandleInfoScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
994 case GAME_MODE_SCORES:
995 HandleHallOfFame(0,0, dx,dy, !newbutton);
998 case GAME_MODE_EDITOR:
999 HandleLevelEditorIdle();
1002 case GAME_MODE_PLAYING:
1003 if (tape.playing || keyboard)
1004 newbutton = ((joy & JOY_BUTTON) != 0);
1006 if (AllPlayersGone && newbutton)
1008 CloseDoor(DOOR_CLOSE_1);
1009 game_status = GAME_MODE_MAIN;