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 && key == setup.shortcut.toggle_pause)
687 StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
692 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
694 if (key == setup.shortcut.save_game)
696 else if (key == setup.shortcut.load_game)
698 else if (key == setup.shortcut.toggle_pause)
699 TapeTogglePause(TAPE_TOGGLE_MANUAL);
702 HandleKeysSpecial(key);
704 if (HandleGadgetsKeyInput(key))
706 if (key != KSYM_Escape) /* always allow ESC key to be handled */
707 key = KSYM_UNDEFINED;
712 case GAME_MODE_PSEUDO_TYPENAME:
713 HandleTypeName(0, key);
717 case GAME_MODE_LEVELS:
718 case GAME_MODE_SETUP:
724 if (game_status == GAME_MODE_MAIN)
725 HandleMainMenu(0,0, 0,0, MB_MENU_CHOICE);
726 else if (game_status == GAME_MODE_LEVELS)
727 HandleChooseLevel(0,0, 0,0, MB_MENU_CHOICE);
728 else if (game_status == GAME_MODE_SETUP)
729 HandleSetupScreen(0,0, 0,0, MB_MENU_CHOICE);
730 else if (game_status == GAME_MODE_INFO)
731 HandleInfoScreen(0,0, 0,0, MB_MENU_CHOICE);
735 if (game_status == GAME_MODE_LEVELS)
736 HandleChooseLevel(0,0, 0,0, MB_MENU_LEAVE);
737 else if (game_status == GAME_MODE_SETUP)
738 HandleSetupScreen(0,0, 0,0, MB_MENU_LEAVE);
739 else if (game_status == GAME_MODE_INFO)
740 HandleInfoScreen(0,0, 0,0, MB_MENU_LEAVE);
744 if (game_status == GAME_MODE_LEVELS)
745 HandleChooseLevel(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
746 else if (game_status == GAME_MODE_SETUP)
747 HandleSetupScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
748 else if (game_status == GAME_MODE_INFO)
749 HandleInfoScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
753 if (game_status == GAME_MODE_LEVELS)
754 HandleChooseLevel(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
755 else if (game_status == GAME_MODE_SETUP)
756 HandleSetupScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
757 else if (game_status == GAME_MODE_INFO)
758 HandleInfoScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
766 case GAME_MODE_SCORES:
772 game_status = GAME_MODE_MAIN;
777 HandleHallOfFame(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
781 HandleHallOfFame(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
789 case GAME_MODE_EDITOR:
790 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
791 HandleLevelEditorKeyInput(key);
794 case GAME_MODE_PLAYING:
799 RequestQuitGame(setup.ask_on_escape);
815 if (GameFrameDelay == 500)
816 GameFrameDelay = GAME_FRAME_DELAY;
818 GameFrameDelay = 500;
821 GameFrameDelay = (key - KSYM_0) * 10;
822 printf("Game speed == %d%% (%d ms delay between two frames)\n",
823 GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
829 options.debug = FALSE;
830 printf("debug mode disabled\n");
834 options.debug = TRUE;
835 printf("debug mode enabled\n");
840 if (!global.fps_slowdown)
842 global.fps_slowdown = TRUE;
843 global.fps_slowdown_factor = 2;
844 printf("fps slowdown enabled -- display only every 2nd frame\n");
846 else if (global.fps_slowdown_factor == 2)
848 global.fps_slowdown_factor = 4;
849 printf("fps slowdown enabled -- display only every 4th frame\n");
853 global.fps_slowdown = FALSE;
854 global.fps_slowdown_factor = 1;
855 printf("fps slowdown disabled\n");
860 ScrollStepSize = TILEX/8;
861 printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
865 ScrollStepSize = TILEX/4;
866 printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
870 ScrollStepSize = TILEX/2;
871 printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
875 ScrollStepSize = TILEX;
876 printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
880 printf("::: currently using game engine version %d\n",
881 game.engine_version);
892 if (key == KSYM_Escape)
894 game_status = GAME_MODE_MAIN;
904 if (button_status && game_status != GAME_MODE_PLAYING)
906 HandleButton(0, 0, -button_status);
910 #if defined(NETWORK_AVALIABLE)
918 static int HandleJoystickForAllPlayers()
923 for (i = 0; i < MAX_PLAYERS; i++)
928 if (!setup.input[i].use_joystick)
932 joy_action = Joystick(i);
933 result |= joy_action;
935 if (!setup.input[i].use_joystick)
938 stored_player[i].action = joy_action;
944 void HandleJoystick()
946 int joystick = HandleJoystickForAllPlayers();
947 int keyboard = key_joystick_mapping;
948 int joy = (joystick | keyboard);
949 int left = joy & JOY_LEFT;
950 int right = joy & JOY_RIGHT;
951 int up = joy & JOY_UP;
952 int down = joy & JOY_DOWN;
953 int button = joy & JOY_BUTTON;
954 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
955 int dx = (left ? -1 : right ? 1 : 0);
956 int dy = (up ? -1 : down ? 1 : 0);
961 case GAME_MODE_LEVELS:
962 case GAME_MODE_SETUP:
965 static unsigned long joystickmove_delay = 0;
967 if (joystick && !button &&
968 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
969 newbutton = dx = dy = 0;
971 if (game_status == GAME_MODE_MAIN)
972 HandleMainMenu(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
973 else if (game_status == GAME_MODE_LEVELS)
974 HandleChooseLevel(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
975 else if (game_status == GAME_MODE_SETUP)
976 HandleSetupScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
977 else if (game_status == GAME_MODE_INFO)
978 HandleInfoScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
982 case GAME_MODE_SCORES:
983 HandleHallOfFame(0,0, dx,dy, !newbutton);
986 case GAME_MODE_EDITOR:
987 HandleLevelEditorIdle();
990 case GAME_MODE_PLAYING:
991 if (tape.playing || keyboard)
992 newbutton = ((joy & JOY_BUTTON) != 0);
994 if (AllPlayersGone && newbutton)
996 CloseDoor(DOOR_CLOSE_1);
997 game_status = GAME_MODE_MAIN;