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 if (key == setup.shortcut.focus_player_all)
704 game.centered_player_nr_next = -1;
706 for (i = 0; i < MAX_PLAYERS; i++)
707 if (key == setup.shortcut.focus_player[i])
708 game.centered_player_nr_next = i;
711 HandleKeysSpecial(key);
713 if (HandleGadgetsKeyInput(key))
715 if (key != KSYM_Escape) /* always allow ESC key to be handled */
716 key = KSYM_UNDEFINED;
721 case GAME_MODE_PSEUDO_TYPENAME:
722 HandleTypeName(0, key);
726 case GAME_MODE_LEVELS:
727 case GAME_MODE_SETUP:
734 /* !!! only use "space" key to start game from main menu !!! */
738 if (game_status == GAME_MODE_MAIN)
739 HandleMainMenu(0,0, 0,0, MB_MENU_CHOICE);
740 else if (game_status == GAME_MODE_LEVELS)
741 HandleChooseLevel(0,0, 0,0, MB_MENU_CHOICE);
742 else if (game_status == GAME_MODE_SETUP)
743 HandleSetupScreen(0,0, 0,0, MB_MENU_CHOICE);
744 else if (game_status == GAME_MODE_INFO)
745 HandleInfoScreen(0,0, 0,0, MB_MENU_CHOICE);
749 if (game_status == GAME_MODE_LEVELS)
750 HandleChooseLevel(0,0, 0,0, MB_MENU_LEAVE);
751 else if (game_status == GAME_MODE_SETUP)
752 HandleSetupScreen(0,0, 0,0, MB_MENU_LEAVE);
753 else if (game_status == GAME_MODE_INFO)
754 HandleInfoScreen(0,0, 0,0, MB_MENU_LEAVE);
758 if (game_status == GAME_MODE_LEVELS)
759 HandleChooseLevel(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
760 else if (game_status == GAME_MODE_SETUP)
761 HandleSetupScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
762 else if (game_status == GAME_MODE_INFO)
763 HandleInfoScreen(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
767 if (game_status == GAME_MODE_LEVELS)
768 HandleChooseLevel(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
769 else if (game_status == GAME_MODE_SETUP)
770 HandleSetupScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
771 else if (game_status == GAME_MODE_INFO)
772 HandleInfoScreen(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
777 GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
786 case GAME_MODE_SCORES:
792 game_status = GAME_MODE_MAIN;
797 HandleHallOfFame(0,0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
801 HandleHallOfFame(0,0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
809 case GAME_MODE_EDITOR:
810 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
811 HandleLevelEditorKeyInput(key);
814 case GAME_MODE_PLAYING:
819 RequestQuitGame(setup.ask_on_escape);
837 if (GameFrameDelay == 500)
838 GameFrameDelay = GAME_FRAME_DELAY;
840 GameFrameDelay = 500;
843 GameFrameDelay = (key - KSYM_0) * 10;
844 printf("Game speed == %d%% (%d ms delay between two frames)\n",
845 GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
851 options.debug = FALSE;
852 printf("debug mode disabled\n");
856 options.debug = TRUE;
857 printf("debug mode enabled\n");
862 if (!global.fps_slowdown)
864 global.fps_slowdown = TRUE;
865 global.fps_slowdown_factor = 2;
866 printf("fps slowdown enabled -- display only every 2nd frame\n");
868 else if (global.fps_slowdown_factor == 2)
870 global.fps_slowdown_factor = 4;
871 printf("fps slowdown enabled -- display only every 4th frame\n");
875 global.fps_slowdown = FALSE;
876 global.fps_slowdown_factor = 1;
877 printf("fps slowdown disabled\n");
882 ScrollStepSize = TILEX/8;
883 printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
887 ScrollStepSize = TILEX/4;
888 printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
892 ScrollStepSize = TILEX/2;
893 printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
897 ScrollStepSize = TILEX;
898 printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
902 printf("::: currently using game engine version %d\n",
903 game.engine_version);
914 if (key == KSYM_Escape)
916 game_status = GAME_MODE_MAIN;
926 if (button_status && game_status != GAME_MODE_PLAYING)
928 HandleButton(0, 0, -button_status);
932 #if defined(NETWORK_AVALIABLE)
940 static int HandleJoystickForAllPlayers()
945 for (i = 0; i < MAX_PLAYERS; i++)
950 if (!setup.input[i].use_joystick)
954 joy_action = Joystick(i);
955 result |= joy_action;
957 if (!setup.input[i].use_joystick)
960 stored_player[i].action = joy_action;
966 void HandleJoystick()
968 int joystick = HandleJoystickForAllPlayers();
969 int keyboard = key_joystick_mapping;
970 int joy = (joystick | keyboard);
971 int left = joy & JOY_LEFT;
972 int right = joy & JOY_RIGHT;
973 int up = joy & JOY_UP;
974 int down = joy & JOY_DOWN;
975 int button = joy & JOY_BUTTON;
976 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
977 int dx = (left ? -1 : right ? 1 : 0);
978 int dy = (up ? -1 : down ? 1 : 0);
983 case GAME_MODE_LEVELS:
984 case GAME_MODE_SETUP:
987 static unsigned long joystickmove_delay = 0;
989 if (joystick && !button &&
990 !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
991 newbutton = dx = dy = 0;
993 if (game_status == GAME_MODE_MAIN)
994 HandleMainMenu(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
995 else if (game_status == GAME_MODE_LEVELS)
996 HandleChooseLevel(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
997 else if (game_status == GAME_MODE_SETUP)
998 HandleSetupScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
999 else if (game_status == GAME_MODE_INFO)
1000 HandleInfoScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1004 case GAME_MODE_SCORES:
1005 HandleHallOfFame(0,0, dx,dy, !newbutton);
1008 case GAME_MODE_EDITOR:
1009 HandleLevelEditorIdle();
1012 case GAME_MODE_PLAYING:
1013 if (tape.playing || keyboard)
1014 newbutton = ((joy & JOY_BUTTON) != 0);
1016 if (AllPlayersGone && newbutton)
1018 CloseDoor(DOOR_CLOSE_1);
1019 game_status = GAME_MODE_MAIN;