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)
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 }
588 if (game_status == GAME_MODE_PLAYING)
590 /* only needed for single-step tape recording mode */
591 static boolean clear_button_2[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
592 static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
595 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);
652 for (i = 0; i < 6; i++)
653 if (key == key_info[i].key_default)
654 joy |= key_info[i].action;
659 if (key_status == KEY_PRESSED)
660 key_joystick_mapping |= joy;
662 key_joystick_mapping &= ~joy;
667 if (game_status != GAME_MODE_PLAYING)
668 key_joystick_mapping = 0;
670 if (key_status == KEY_RELEASED)
673 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
674 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
676 CloseDoor(DOOR_CLOSE_1);
677 game_status = GAME_MODE_MAIN;
683 if (game_status == GAME_MODE_MAIN &&
684 (key == setup.shortcut.toggle_pause || key == KSYM_space))
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 if (game_status == GAME_MODE_PLAYING)
703 for (i = 0; i < MAX_PLAYERS; i++)
704 if (key == KSYM_1 + i)
705 game.centered_to_player_next = i;
708 HandleKeysSpecial(key);
710 if (HandleGadgetsKeyInput(key))
712 if (key != KSYM_Escape) /* always allow ESC key to be handled */
713 key = KSYM_UNDEFINED;
718 case GAME_MODE_PSEUDO_TYPENAME:
719 HandleTypeName(0, key);
723 case GAME_MODE_LEVELS:
724 case GAME_MODE_SETUP:
731 /* !!! only use "space" key to start game from main menu !!! */
735 if (game_status == GAME_MODE_MAIN)
736 HandleMainMenu(0,0, 0,0, MB_MENU_CHOICE);
737 else if (game_status == GAME_MODE_LEVELS)
738 HandleChooseLevel(0,0, 0,0, MB_MENU_CHOICE);
739 else if (game_status == GAME_MODE_SETUP)
740 HandleSetupScreen(0,0, 0,0, MB_MENU_CHOICE);
741 else if (game_status == GAME_MODE_INFO)
742 HandleInfoScreen(0,0, 0,0, MB_MENU_CHOICE);
746 if (game_status == GAME_MODE_LEVELS)
747 HandleChooseLevel(0,0, 0,0, MB_MENU_LEAVE);
748 else if (game_status == GAME_MODE_SETUP)
749 HandleSetupScreen(0,0, 0,0, MB_MENU_LEAVE);
750 else if (game_status == GAME_MODE_INFO)
751 HandleInfoScreen(0,0, 0,0, MB_MENU_LEAVE);
755 if (game_status == GAME_MODE_LEVELS)
756 HandleChooseLevel(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
757 else if (game_status == GAME_MODE_SETUP)
758 HandleSetupScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
759 else if (game_status == GAME_MODE_INFO)
760 HandleInfoScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
764 if (game_status == GAME_MODE_LEVELS)
765 HandleChooseLevel(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
766 else if (game_status == GAME_MODE_SETUP)
767 HandleSetupScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
768 else if (game_status == GAME_MODE_INFO)
769 HandleInfoScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
774 GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
783 case GAME_MODE_SCORES:
789 game_status = GAME_MODE_MAIN;
794 HandleHallOfFame(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
798 HandleHallOfFame(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
806 case GAME_MODE_EDITOR:
807 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
808 HandleLevelEditorKeyInput(key);
811 case GAME_MODE_PLAYING:
816 RequestQuitGame(setup.ask_on_escape);
834 if (GameFrameDelay == 500)
835 GameFrameDelay = GAME_FRAME_DELAY;
837 GameFrameDelay = 500;
840 GameFrameDelay = (key - KSYM_0) * 10;
841 printf("Game speed == %d%% (%d ms delay between two frames)\n",
842 GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
848 options.debug = FALSE;
849 printf("debug mode disabled\n");
853 options.debug = TRUE;
854 printf("debug mode enabled\n");
859 if (!global.fps_slowdown)
861 global.fps_slowdown = TRUE;
862 global.fps_slowdown_factor = 2;
863 printf("fps slowdown enabled -- display only every 2nd frame\n");
865 else if (global.fps_slowdown_factor == 2)
867 global.fps_slowdown_factor = 4;
868 printf("fps slowdown enabled -- display only every 4th frame\n");
872 global.fps_slowdown = FALSE;
873 global.fps_slowdown_factor = 1;
874 printf("fps slowdown disabled\n");
879 ScrollStepSize = TILEX/8;
880 printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
884 ScrollStepSize = TILEX/4;
885 printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
889 ScrollStepSize = TILEX/2;
890 printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
894 ScrollStepSize = TILEX;
895 printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
899 printf("::: currently using game engine version %d\n",
900 game.engine_version);
911 if (key == KSYM_Escape)
913 game_status = GAME_MODE_MAIN;
923 if (button_status && game_status != GAME_MODE_PLAYING)
925 HandleButton(0, 0, -button_status);
929 #if defined(NETWORK_AVALIABLE)
937 static int HandleJoystickForAllPlayers()
942 for (i = 0; i < MAX_PLAYERS; i++)
947 if (!setup.input[i].use_joystick)
951 joy_action = Joystick(i);
952 result |= joy_action;
954 if (!setup.input[i].use_joystick)
957 stored_player[i].action = joy_action;
963 void HandleJoystick()
965 int joystick = HandleJoystickForAllPlayers();
966 int keyboard = key_joystick_mapping;
967 int joy = (joystick | keyboard);
968 int left = joy & JOY_LEFT;
969 int right = joy & JOY_RIGHT;
970 int up = joy & JOY_UP;
971 int down = joy & JOY_DOWN;
972 int button = joy & JOY_BUTTON;
973 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
974 int dx = (left ? -1 : right ? 1 : 0);
975 int dy = (up ? -1 : down ? 1 : 0);
980 case GAME_MODE_LEVELS:
981 case GAME_MODE_SETUP:
984 static unsigned long joystickmove_delay = 0;
986 if (joystick && !button &&
987 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
988 newbutton = dx = dy = 0;
990 if (game_status == GAME_MODE_MAIN)
991 HandleMainMenu(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
992 else if (game_status == GAME_MODE_LEVELS)
993 HandleChooseLevel(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
994 else if (game_status == GAME_MODE_SETUP)
995 HandleSetupScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
996 else if (game_status == GAME_MODE_INFO)
997 HandleInfoScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1001 case GAME_MODE_SCORES:
1002 HandleHallOfFame(0,0, dx,dy, !newbutton);
1005 case GAME_MODE_EDITOR:
1006 HandleLevelEditorIdle();
1009 case GAME_MODE_PLAYING:
1010 if (tape.playing || keyboard)
1011 newbutton = ((joy & JOY_BUTTON) != 0);
1013 if (AllPlayersGone && newbutton)
1015 CloseDoor(DOOR_CLOSE_1);
1016 game_status = GAME_MODE_MAIN;