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:
441 HandleInfoScreen(mx,my, 0,0, button);
444 case GAME_MODE_SETUP:
445 HandleSetupScreen(mx,my, 0,0, button);
448 case GAME_MODE_PLAYING:
450 if (button == MB_PRESSED && !motion_status && IN_GFX_SCREEN(mx, my))
451 DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
460 static boolean is_string_suffix(char *string, char *suffix)
462 int string_len = strlen(string);
463 int suffix_len = strlen(suffix);
465 if (suffix_len > string_len)
468 return (strcmp(&string[string_len - suffix_len], suffix) == 0);
471 #define MAX_CHEAT_INPUT_LEN 32
473 static void HandleKeysSpecial(Key key)
475 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
476 char letter = getCharFromKey(key);
477 int cheat_input_len = strlen(cheat_input);
483 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
485 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
486 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
488 cheat_input_len = MAX_CHEAT_INPUT_LEN;
491 cheat_input[cheat_input_len++] = letter;
492 cheat_input[cheat_input_len] = '\0';
495 printf("::: '%s' [%d]\n", cheat_input, cheat_input_len);
498 if (game_status == GAME_MODE_MAIN)
500 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
501 is_string_suffix(cheat_input, ":ist"))
503 InsertSolutionTape();
505 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
506 is_string_suffix(cheat_input, ":rg"))
508 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
511 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
512 is_string_suffix(cheat_input, ":rs"))
514 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
517 else if (is_string_suffix(cheat_input, ":reload-music") ||
518 is_string_suffix(cheat_input, ":rm"))
520 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
523 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
524 is_string_suffix(cheat_input, ":ra"))
526 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
527 1 << ARTWORK_TYPE_SOUNDS |
528 1 << ARTWORK_TYPE_MUSIC);
531 else if (is_string_suffix(cheat_input, ":dump-level") ||
532 is_string_suffix(cheat_input, ":dl"))
536 else if (is_string_suffix(cheat_input, ":dump-tape") ||
537 is_string_suffix(cheat_input, ":dt"))
542 else if (game_status == GAME_MODE_PLAYING)
545 if (is_string_suffix(cheat_input, ".q"))
546 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
547 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
548 local_player->inventory_element[local_player->inventory_size++] =
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)
569 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
570 static struct SetupKeyboardInfo custom_key;
578 { &custom_key.left, DEFAULT_KEY_LEFT, JOY_LEFT },
579 { &custom_key.right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
580 { &custom_key.up, DEFAULT_KEY_UP, JOY_UP },
581 { &custom_key.down, DEFAULT_KEY_DOWN, JOY_DOWN },
582 { &custom_key.snap, DEFAULT_KEY_SNAP, JOY_BUTTON_1 },
583 { &custom_key.drop, DEFAULT_KEY_DROP, JOY_BUTTON_2 }
586 if (game_status == GAME_MODE_PLAYING)
588 /* only needed for single-step tape recording mode */
589 static boolean clear_button_2[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
590 static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
593 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);
653 for (i = 0; i < 6; i++)
654 if (key == key_info[i].key_default)
655 joy |= key_info[i].action;
660 if (key_status == KEY_PRESSED)
661 key_joystick_mapping |= joy;
663 key_joystick_mapping &= ~joy;
668 if (game_status != GAME_MODE_PLAYING)
669 key_joystick_mapping = 0;
671 if (key_status == KEY_RELEASED)
674 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
675 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
677 CloseDoor(DOOR_CLOSE_1);
678 game_status = GAME_MODE_MAIN;
684 if (game_status == GAME_MODE_MAIN && key == setup.shortcut.toggle_pause)
686 StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
691 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
693 if (key == setup.shortcut.save_game)
695 else if (key == setup.shortcut.load_game)
697 else if (key == setup.shortcut.toggle_pause)
698 TapeTogglePause(TAPE_TOGGLE_MANUAL);
701 HandleKeysSpecial(key);
703 if (HandleGadgetsKeyInput(key))
705 if (key != KSYM_Escape) /* always allow ESC key to be handled */
706 key = KSYM_UNDEFINED;
711 case GAME_MODE_PSEUDO_TYPENAME:
712 HandleTypeName(0, key);
716 case GAME_MODE_LEVELS:
717 case GAME_MODE_SETUP:
723 if (game_status == GAME_MODE_MAIN)
724 HandleMainMenu(0,0, 0,0, MB_MENU_CHOICE);
725 else if (game_status == GAME_MODE_LEVELS)
726 HandleChooseLevel(0,0, 0,0, MB_MENU_CHOICE);
727 else if (game_status == GAME_MODE_SETUP)
728 HandleSetupScreen(0,0, 0,0, MB_MENU_CHOICE);
729 else if (game_status == GAME_MODE_INFO)
730 HandleInfoScreen(0,0, 0,0, MB_MENU_CHOICE);
734 if (game_status == GAME_MODE_LEVELS)
735 HandleChooseLevel(0,0, 0,0, MB_MENU_LEAVE);
736 else if (game_status == GAME_MODE_SETUP)
737 HandleSetupScreen(0,0, 0,0, MB_MENU_LEAVE);
738 else if (game_status == GAME_MODE_INFO)
739 HandleInfoScreen(0,0, 0,0, MB_MENU_LEAVE);
743 if (game_status == GAME_MODE_LEVELS)
744 HandleChooseLevel(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
745 else if (game_status == GAME_MODE_SETUP)
746 HandleSetupScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
747 else if (game_status == GAME_MODE_INFO)
748 HandleInfoScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
752 if (game_status == GAME_MODE_LEVELS)
753 HandleChooseLevel(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
754 else if (game_status == GAME_MODE_SETUP)
755 HandleSetupScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
756 else if (game_status == GAME_MODE_INFO)
757 HandleInfoScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
765 case GAME_MODE_SCORES:
771 game_status = GAME_MODE_MAIN;
776 HandleHallOfFame(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
780 HandleHallOfFame(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
788 case GAME_MODE_EDITOR:
789 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
790 HandleLevelEditorKeyInput(key);
793 case GAME_MODE_PLAYING:
798 RequestQuitGame(setup.ask_on_escape);
814 if (GameFrameDelay == 500)
815 GameFrameDelay = GAME_FRAME_DELAY;
817 GameFrameDelay = 500;
820 GameFrameDelay = (key - KSYM_0) * 10;
821 printf("Game speed == %d%% (%d ms delay between two frames)\n",
822 GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
828 options.debug = FALSE;
829 printf("debug mode disabled\n");
833 options.debug = TRUE;
834 printf("debug mode enabled\n");
839 if (!global.fps_slowdown)
841 global.fps_slowdown = TRUE;
842 global.fps_slowdown_factor = 2;
843 printf("fps slowdown enabled -- display only every 2nd frame\n");
845 else if (global.fps_slowdown_factor == 2)
847 global.fps_slowdown_factor = 4;
848 printf("fps slowdown enabled -- display only every 4th frame\n");
852 global.fps_slowdown = FALSE;
853 global.fps_slowdown_factor = 1;
854 printf("fps slowdown disabled\n");
859 ScrollStepSize = TILEX/8;
860 printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
864 ScrollStepSize = TILEX/4;
865 printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
869 ScrollStepSize = TILEX/2;
870 printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
874 ScrollStepSize = TILEX;
875 printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
879 printf("::: currently using game engine version %d\n",
880 game.engine_version);
891 if (key == KSYM_Escape)
893 game_status = GAME_MODE_MAIN;
903 if (button_status && game_status != GAME_MODE_PLAYING)
905 HandleButton(0, 0, -button_status);
909 #if defined(NETWORK_AVALIABLE)
917 static int HandleJoystickForAllPlayers()
922 for (i = 0; i < MAX_PLAYERS; i++)
927 if (!setup.input[i].use_joystick)
931 joy_action = Joystick(i);
932 result |= joy_action;
934 if (!setup.input[i].use_joystick)
937 stored_player[i].action = joy_action;
943 void HandleJoystick()
945 int joystick = HandleJoystickForAllPlayers();
946 int keyboard = key_joystick_mapping;
947 int joy = (joystick | keyboard);
948 int left = joy & JOY_LEFT;
949 int right = joy & JOY_RIGHT;
950 int up = joy & JOY_UP;
951 int down = joy & JOY_DOWN;
952 int button = joy & JOY_BUTTON;
953 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
954 int dx = (left ? -1 : right ? 1 : 0);
955 int dy = (up ? -1 : down ? 1 : 0);
960 case GAME_MODE_LEVELS:
961 case GAME_MODE_SETUP:
964 static unsigned long joystickmove_delay = 0;
966 if (joystick && !button &&
967 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
968 newbutton = dx = dy = 0;
970 if (game_status == GAME_MODE_MAIN)
971 HandleMainMenu(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
972 else if (game_status == GAME_MODE_LEVELS)
973 HandleChooseLevel(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
974 else if (game_status == GAME_MODE_SETUP)
975 HandleSetupScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
976 else if (game_status == GAME_MODE_INFO)
977 HandleInfoScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
981 case GAME_MODE_SCORES:
982 HandleHallOfFame(0,0, dx,dy, !newbutton);
985 case GAME_MODE_EDITOR:
986 HandleLevelEditorIdle();
989 case GAME_MODE_PLAYING:
990 if (tape.playing || keyboard)
991 newbutton = ((joy & JOY_BUTTON) != 0);
993 if (AllPlayersGone && newbutton)
995 CloseDoor(DOOR_CLOSE_1);
996 game_status = GAME_MODE_MAIN;