rnd-20140106-1-src
[rocksndiamonds.git] / src / events.c
1 /***********************************************************
2 * Rocks'n'Diamonds -- McDuffin Strikes Back!               *
3 *----------------------------------------------------------*
4 * (c) 1995-2006 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * events.c                                                 *
12 ***********************************************************/
13
14 #include "libgame/libgame.h"
15
16 #include "events.h"
17 #include "init.h"
18 #include "screens.h"
19 #include "tools.h"
20 #include "game.h"
21 #include "editor.h"
22 #include "files.h"
23 #include "tape.h"
24 #include "network.h"
25
26
27 #define DEBUG_EVENTS            0
28
29
30 static boolean cursor_inside_playfield = FALSE;
31 static boolean playfield_cursor_set = FALSE;
32 static unsigned int playfield_cursor_delay = 0;
33
34
35 /* event filter especially needed for SDL event filtering due to
36    delay problems with lots of mouse motion events when mouse button
37    not pressed (X11 can handle this with 'PointerMotionHintMask') */
38
39 /* event filter addition for SDL2: as SDL2 does not have a function to enable
40    or disable keyboard auto-repeat, filter repeated keyboard events instead */
41
42 static int FilterEventsExt(const Event *event)
43 {
44   MotionEvent *motion;
45
46 #if defined(TARGET_SDL2)
47   /* skip repeated key press events if keyboard auto-repeat is disabled */
48   if (event->type == EVENT_KEYPRESS &&
49       event->key.repeat &&
50       !keyrepeat_status)
51     return 0;
52 #endif
53
54   /* non-motion events are directly passed to event handler functions */
55   if (event->type != EVENT_MOTIONNOTIFY)
56     return 1;
57
58   motion = (MotionEvent *)event;
59   cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
60                              motion->y >= SY && motion->y < SY + SYSIZE);
61
62   if (game_status == GAME_MODE_PLAYING && playfield_cursor_set)
63   {
64     SetMouseCursor(CURSOR_DEFAULT);
65     playfield_cursor_set = FALSE;
66     DelayReached(&playfield_cursor_delay, 0);
67   }
68
69   /* skip mouse motion events without pressed button outside level editor */
70   if (button_status == MB_RELEASED &&
71       game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
72     return 0;
73
74   return 1;
75 }
76
77 #if defined(TARGET_SDL2)
78 int FilterEvents(void *userdata, Event *event)
79 {
80   return FilterEventsExt(event);
81 }
82 #else
83 int FilterEvents(const Event *event)
84 {
85   return FilterEventsExt(event);
86 }
87 #endif
88
89 /* to prevent delay problems, skip mouse motion events if the very next
90    event is also a mouse motion event (and therefore effectively only
91    handling the last of a row of mouse motion events in the event queue) */
92
93 boolean SkipPressedMouseMotionEvent(const Event *event)
94 {
95   /* nothing to do if the current event is not a mouse motion event */
96   if (event->type != EVENT_MOTIONNOTIFY)
97     return FALSE;
98
99   /* only skip motion events with pressed button outside level editor */
100   if (button_status == MB_RELEASED ||
101       game_status == GAME_MODE_EDITOR || game_status == GAME_MODE_PLAYING)
102     return FALSE;
103
104   if (PendingEvent())
105   {
106     Event next_event;
107
108     PeekEvent(&next_event);
109
110     /* if next event is also a mouse motion event, skip the current one */
111     if (next_event.type == EVENT_MOTIONNOTIFY)
112       return TRUE;
113   }
114
115   return FALSE;
116 }
117
118 /* this is only really needed for non-SDL targets to filter unwanted events;
119    when using SDL with properly installed event filter, this function can be
120    replaced with a simple "NextEvent()" call, but it doesn't hurt either */
121
122 static boolean NextValidEvent(Event *event)
123 {
124   while (PendingEvent())
125   {
126     boolean handle_this_event = FALSE;
127
128     NextEvent(event);
129
130     if (FilterEventsExt(event))
131       handle_this_event = TRUE;
132
133     if (SkipPressedMouseMotionEvent(event))
134       handle_this_event = FALSE;
135
136     if (handle_this_event)
137       return TRUE;
138   }
139
140   return FALSE;
141 }
142
143 void EventLoop(void)
144 {
145   while (1)
146   {
147     if (PendingEvent())         /* got event */
148     {
149       Event event;
150
151       while (NextValidEvent(&event))
152       {
153         switch (event.type)
154         {
155           case EVENT_BUTTONPRESS:
156           case EVENT_BUTTONRELEASE:
157             HandleButtonEvent((ButtonEvent *) &event);
158             break;
159   
160           case EVENT_MOTIONNOTIFY:
161             HandleMotionEvent((MotionEvent *) &event);
162             break;
163
164 #if defined(TARGET_SDL2)
165           case EVENT_FINGERPRESS:
166           case EVENT_FINGERRELEASE:
167           case EVENT_FINGERMOTION:
168             HandleFingerEvent((FingerEvent *) &event);
169             break;
170
171           case EVENT_TEXTINPUT:
172             HandleTextEvent((TextEvent *) &event);
173             break;
174 #endif
175
176           case EVENT_KEYPRESS:
177           case EVENT_KEYRELEASE:
178             HandleKeyEvent((KeyEvent *) &event);
179             break;
180
181           default:
182             HandleOtherEvents(&event);
183             break;
184         }
185       }
186     }
187     else
188     {
189       /* when playing, display a special mouse pointer inside the playfield */
190       if (game_status == GAME_MODE_PLAYING && !tape.pausing)
191       {
192         if (!playfield_cursor_set && cursor_inside_playfield &&
193             DelayReached(&playfield_cursor_delay, 1000))
194         {
195           SetMouseCursor(CURSOR_PLAYFIELD);
196           playfield_cursor_set = TRUE;
197         }
198       }
199       else if (playfield_cursor_set)
200       {
201         SetMouseCursor(CURSOR_DEFAULT);
202         playfield_cursor_set = FALSE;
203       }
204
205       HandleNoEvent();
206     }
207
208     /* don't use all CPU time when idle; the main loop while playing
209        has its own synchronization and is CPU friendly, too */
210
211     if (game_status == GAME_MODE_PLAYING)
212     {
213       HandleGameActions();
214     }
215     else
216     {
217       SyncDisplay();
218       if (!PendingEvent())      /* delay only if no pending events */
219         Delay(10);
220     }
221
222     /* refresh window contents from drawing buffer, if needed */
223     BackToFront();
224
225     if (game_status == GAME_MODE_QUIT)
226       return;
227   }
228 }
229
230 void HandleOtherEvents(Event *event)
231 {
232   switch (event->type)
233   {
234     case EVENT_EXPOSE:
235       HandleExposeEvent((ExposeEvent *) event);
236       break;
237
238     case EVENT_UNMAPNOTIFY:
239 #if 0
240       /* This causes the game to stop not only when iconified, but also
241          when on another virtual desktop, which might be not desired. */
242       SleepWhileUnmapped();
243 #endif
244       break;
245
246     case EVENT_FOCUSIN:
247     case EVENT_FOCUSOUT:
248       HandleFocusEvent((FocusChangeEvent *) event);
249       break;
250
251     case EVENT_CLIENTMESSAGE:
252       HandleClientMessageEvent((ClientMessageEvent *) event);
253       break;
254
255 #if defined(TARGET_SDL)
256     case SDL_JOYAXISMOTION:
257     case SDL_JOYBUTTONDOWN:
258     case SDL_JOYBUTTONUP:
259       HandleJoystickEvent(event);
260       break;
261
262     case SDL_SYSWMEVENT:
263       HandleWindowManagerEvent(event);
264       break;
265 #endif
266
267     default:
268       break;
269   }
270 }
271
272 void ClearEventQueue()
273 {
274   while (PendingEvent())
275   {
276     Event event;
277
278     NextEvent(&event);
279
280     switch (event.type)
281     {
282       case EVENT_BUTTONRELEASE:
283         button_status = MB_RELEASED;
284         break;
285
286       case EVENT_KEYRELEASE:
287 #if 1
288         ClearPlayerAction();
289 #else
290         key_joystick_mapping = 0;
291 #endif
292         break;
293
294       default:
295         HandleOtherEvents(&event);
296         break;
297     }
298   }
299 }
300
301 void ClearPlayerAction()
302 {
303   int i;
304
305   /* simulate key release events for still pressed keys */
306   key_joystick_mapping = 0;
307   for (i = 0; i < MAX_PLAYERS; i++)
308     stored_player[i].action = 0;
309 }
310
311 void SleepWhileUnmapped()
312 {
313   boolean window_unmapped = TRUE;
314
315   KeyboardAutoRepeatOn();
316
317   while (window_unmapped)
318   {
319     Event event;
320
321     NextEvent(&event);
322
323     switch (event.type)
324     {
325       case EVENT_BUTTONRELEASE:
326         button_status = MB_RELEASED;
327         break;
328
329       case EVENT_KEYRELEASE:
330         key_joystick_mapping = 0;
331         break;
332
333       case EVENT_MAPNOTIFY:
334         window_unmapped = FALSE;
335         break;
336
337       case EVENT_UNMAPNOTIFY:
338         /* this is only to surely prevent the 'should not happen' case
339          * of recursively looping between 'SleepWhileUnmapped()' and
340          * 'HandleOtherEvents()' which usually calls this funtion.
341          */
342         break;
343
344       default:
345         HandleOtherEvents(&event);
346         break;
347     }
348   }
349
350   if (game_status == GAME_MODE_PLAYING)
351     KeyboardAutoRepeatOffUnlessAutoplay();
352 }
353
354 void HandleExposeEvent(ExposeEvent *event)
355 {
356 #if !defined(TARGET_SDL)
357   RedrawPlayfield(FALSE, event->x, event->y, event->width, event->height);
358   FlushDisplay();
359 #endif
360 }
361
362 void HandleButtonEvent(ButtonEvent *event)
363 {
364 #if DEBUG_EVENTS
365   Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
366         event->button,
367         event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
368         event->x, event->y);
369 #endif
370
371   motion_status = FALSE;
372
373   if (event->type == EVENT_BUTTONPRESS)
374     button_status = event->button;
375   else
376     button_status = MB_RELEASED;
377
378   HandleButton(event->x, event->y, button_status, event->button);
379 }
380
381 void HandleMotionEvent(MotionEvent *event)
382 {
383   if (!PointerInWindow(window))
384     return;     /* window and pointer are on different screens */
385
386   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
387     return;
388
389   motion_status = TRUE;
390
391   Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
392         button_status, event->x, event->y);
393
394   HandleButton(event->x, event->y, button_status, button_status);
395 }
396
397 #if defined(TARGET_SDL2)
398 void HandleFingerEvent(FingerEvent *event)
399 {
400 #if 0
401   static int num_events = 0;
402   int max_events = 10;
403 #endif
404
405 #if DEBUG_EVENTS
406   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
407         event->type == EVENT_FINGERPRESS ? "pressed" :
408         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
409         event->touchId,
410         event->fingerId,
411         event->x, event->y,
412         event->dx, event->dy,
413         event->pressure);
414 #endif
415
416 #if 0
417   int x = (int)(event->x * video.width);
418   int y = (int)(event->y * video.height);
419   int button = MB_LEFTBUTTON;
420
421   Error(ERR_DEBUG, "=> screen x/y %d/%d", x, y);
422 #endif
423
424 #if 0
425   if (++num_events >= max_events)
426     CloseAllAndExit(0);
427 #endif
428
429 #if 1
430 #if 0
431   if (event->type == EVENT_FINGERPRESS ||
432       event->type == EVENT_FINGERMOTION)
433     button_status = button;
434   else
435     button_status = MB_RELEASED;
436
437   int max_x = SX + SXSIZE;
438   int max_y = SY + SYSIZE;
439 #endif
440
441 #if 1
442   if (game_status == GAME_MODE_PLAYING)
443 #else
444   if (game_status == GAME_MODE_PLAYING &&
445       x < max_x)
446 #endif
447   {
448     int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
449                       KEY_PRESSED);
450 #if 1
451     Key key = (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
452                event->y > 2.0 / 3.0 ? setup.input[0].key.down :
453                event->x < 1.0 / 3.0 ? setup.input[0].key.left :
454                event->x > 2.0 / 3.0 ? setup.input[0].key.right :
455                setup.input[0].key.drop);
456 #else
457     Key key = (y <     max_y / 3 ? setup.input[0].key.up :
458                y > 2 * max_y / 3 ? setup.input[0].key.down :
459                x <     max_x / 3 ? setup.input[0].key.left :
460                x > 2 * max_x / 3 ? setup.input[0].key.right :
461                setup.input[0].key.drop);
462 #endif
463
464     Error(ERR_DEBUG, "=> key == %d, key_status == %d", key, key_status);
465
466     HandleKey(key, key_status);
467   }
468   else
469   {
470 #if 0
471     Error(ERR_DEBUG, "::: button_status == %d, button == %d\n",
472           button_status, button);
473
474     HandleButton(x, y, button_status, button);
475 #endif
476   }
477 #endif
478 }
479
480 static boolean checkTextInputKeyModState()
481 {
482   // when playing, only handle raw key events and ignore text input
483   if (game_status == GAME_MODE_PLAYING)
484     return FALSE;
485
486   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
487 }
488
489 void HandleTextEvent(TextEvent *event)
490 {
491   char *text = event->text;
492   Key key = getKeyFromKeyName(text);
493
494 #if DEBUG_EVENTS
495   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s)",
496         text,
497         strlen(text),
498         text[0], (int)(text[0]),
499         key,
500         getKeyNameFromKey(key));
501 #endif
502
503   // if (game_status != GAME_MODE_PLAYING && GetKeyModState() != KMOD_None)
504   /*
505   if (game_status != GAME_MODE_PLAYING &&
506       (GetKeyModState() & KMOD_TextInput) != KMOD_None)
507   */
508   if (checkTextInputKeyModState())
509   {
510     HandleKey(key, KEY_PRESSED);
511     HandleKey(key, KEY_RELEASED);
512   }
513 }
514 #endif
515
516 void HandleKeyEvent(KeyEvent *event)
517 {
518   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
519   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
520   Key key = GetEventKey(event, with_modifiers);
521   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
522
523 #if DEBUG_EVENTS
524   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
525         event->type == EVENT_KEYPRESS ? "pressed" : "released",
526         event->keysym.scancode,
527         event->keysym.sym,
528         keymod,
529         GetKeyModState(),
530         key,
531         getKeyNameFromKey(key));
532 #endif
533
534 #if 0
535   if (key == KSYM_Menu)
536     Error(ERR_DEBUG, "menu key pressed");
537   else if (key == KSYM_Back)
538     Error(ERR_DEBUG, "back key pressed");
539 #endif
540
541 #if defined(PLATFORM_ANDROID)
542   // always map the "back" button to the "escape" key on Android devices
543   if (key == KSYM_Back)
544     key = KSYM_Escape;
545 #endif
546
547   HandleKeyModState(keymod, key_status);
548
549 #if defined(TARGET_SDL2)
550
551   // if (game_status == GAME_MODE_PLAYING || GetKeyModState() == KMOD_None)
552   /*
553   if (game_status == GAME_MODE_PLAYING ||
554       (GetKeyModState() & KMOD_TextInput) == KMOD_None)
555   */
556   if (!checkTextInputKeyModState())
557     HandleKey(key, key_status);
558 #else
559   HandleKey(key, key_status);
560 #endif
561 }
562
563 void HandleFocusEvent(FocusChangeEvent *event)
564 {
565   static int old_joystick_status = -1;
566
567   if (event->type == EVENT_FOCUSOUT)
568   {
569     KeyboardAutoRepeatOn();
570     old_joystick_status = joystick.status;
571     joystick.status = JOYSTICK_NOT_AVAILABLE;
572
573     ClearPlayerAction();
574   }
575   else if (event->type == EVENT_FOCUSIN)
576   {
577     /* When there are two Rocks'n'Diamonds windows which overlap and
578        the player moves the pointer from one game window to the other,
579        a 'FocusOut' event is generated for the window the pointer is
580        leaving and a 'FocusIn' event is generated for the window the
581        pointer is entering. In some cases, it can happen that the
582        'FocusIn' event is handled by the one game process before the
583        'FocusOut' event by the other game process. In this case the
584        X11 environment would end up with activated keyboard auto repeat,
585        because unfortunately this is a global setting and not (which
586        would be far better) set for each X11 window individually.
587        The effect would be keyboard auto repeat while playing the game
588        (game_status == GAME_MODE_PLAYING), which is not desired.
589        To avoid this special case, we just wait 1/10 second before
590        processing the 'FocusIn' event.
591     */
592
593     if (game_status == GAME_MODE_PLAYING)
594     {
595       Delay(100);
596       KeyboardAutoRepeatOffUnlessAutoplay();
597     }
598
599     if (old_joystick_status != -1)
600       joystick.status = old_joystick_status;
601   }
602 }
603
604 void HandleClientMessageEvent(ClientMessageEvent *event)
605 {
606   if (CheckCloseWindowEvent(event))
607     CloseAllAndExit(0);
608 }
609
610 void HandleWindowManagerEvent(Event *event)
611 {
612 #if defined(TARGET_SDL)
613   SDLHandleWindowManagerEvent(event);
614 #endif
615 }
616
617 void HandleButton(int mx, int my, int button, int button_nr)
618 {
619   static int old_mx = 0, old_my = 0;
620
621   if (button < 0)
622   {
623     mx = old_mx;
624     my = old_my;
625     button = -button;
626   }
627   else
628   {
629     old_mx = mx;
630     old_my = my;
631   }
632
633   if (HandleGadgets(mx, my, button))
634   {
635     /* do not handle this button event anymore */
636     mx = my = -32;      /* force mouse event to be outside screen tiles */
637   }
638
639   /* do not use scroll wheel button events for anything other than gadgets */
640   if (IS_WHEEL_BUTTON(button_nr))
641     return;
642
643   Error(ERR_DEBUG, "::: game_status == %d", game_status);
644
645   switch (game_status)
646   {
647     case GAME_MODE_TITLE:
648       HandleTitleScreen(mx, my, 0, 0, button);
649       break;
650
651     case GAME_MODE_MAIN:
652       HandleMainMenu(mx, my, 0, 0, button);
653       break;
654
655     case GAME_MODE_PSEUDO_TYPENAME:
656       HandleTypeName(0, KSYM_Return);
657       break;
658
659     case GAME_MODE_LEVELS:
660       HandleChooseLevelSet(mx, my, 0, 0, button);
661       break;
662
663     case GAME_MODE_LEVELNR:
664       HandleChooseLevelNr(mx, my, 0, 0, button);
665       break;
666
667     case GAME_MODE_SCORES:
668       HandleHallOfFame(0, 0, 0, 0, button);
669       break;
670
671     case GAME_MODE_EDITOR:
672       HandleLevelEditorIdle();
673       break;
674
675     case GAME_MODE_INFO:
676       HandleInfoScreen(mx, my, 0, 0, button);
677       break;
678
679     case GAME_MODE_SETUP:
680       HandleSetupScreen(mx, my, 0, 0, button);
681       break;
682
683     case GAME_MODE_PLAYING:
684 #ifdef DEBUG
685       if (button == MB_PRESSED && !motion_status && IN_GFX_SCREEN(mx, my))
686         DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
687 #endif
688       break;
689
690     default:
691       break;
692   }
693 }
694
695 static boolean is_string_suffix(char *string, char *suffix)
696 {
697   int string_len = strlen(string);
698   int suffix_len = strlen(suffix);
699
700   if (suffix_len > string_len)
701     return FALSE;
702
703   return (strEqual(&string[string_len - suffix_len], suffix));
704 }
705
706 #define MAX_CHEAT_INPUT_LEN     32
707
708 static void HandleKeysSpecial(Key key)
709 {
710   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
711   char letter = getCharFromKey(key);
712   int cheat_input_len = strlen(cheat_input);
713   int i;
714
715   if (letter == 0)
716     return;
717
718   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
719   {
720     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
721       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
722
723     cheat_input_len = MAX_CHEAT_INPUT_LEN;
724   }
725
726   cheat_input[cheat_input_len++] = letter;
727   cheat_input[cheat_input_len] = '\0';
728
729 #if DEBUG_EVENTS
730   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
731 #endif
732
733   if (game_status == GAME_MODE_MAIN)
734   {
735     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
736         is_string_suffix(cheat_input, ":ist"))
737     {
738       InsertSolutionTape();
739     }
740     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
741              is_string_suffix(cheat_input, ":rg"))
742     {
743       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
744       DrawMainMenu();
745     }
746     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
747              is_string_suffix(cheat_input, ":rs"))
748     {
749       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
750       DrawMainMenu();
751     }
752     else if (is_string_suffix(cheat_input, ":reload-music") ||
753              is_string_suffix(cheat_input, ":rm"))
754     {
755       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
756       DrawMainMenu();
757     }
758     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
759              is_string_suffix(cheat_input, ":ra"))
760     {
761       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
762                           1 << ARTWORK_TYPE_SOUNDS |
763                           1 << ARTWORK_TYPE_MUSIC);
764       DrawMainMenu();
765     }
766     else if (is_string_suffix(cheat_input, ":dump-level") ||
767              is_string_suffix(cheat_input, ":dl"))
768     {
769       DumpLevel(&level);
770     }
771     else if (is_string_suffix(cheat_input, ":dump-tape") ||
772              is_string_suffix(cheat_input, ":dt"))
773     {
774       DumpTape(&tape);
775     }
776     else if (is_string_suffix(cheat_input, ":save-native-level") ||
777              is_string_suffix(cheat_input, ":snl"))
778     {
779       SaveNativeLevel(&level);
780     }
781   }
782   else if (game_status == GAME_MODE_PLAYING)
783   {
784 #ifdef DEBUG
785     if (is_string_suffix(cheat_input, ".q"))
786       DEBUG_SetMaximumDynamite();
787 #endif
788   }
789   else if (game_status == GAME_MODE_EDITOR)
790   {
791     if (is_string_suffix(cheat_input, ":dump-brush") ||
792         is_string_suffix(cheat_input, ":DB"))
793     {
794       DumpBrush();
795     }
796     else if (is_string_suffix(cheat_input, ":DDB"))
797     {
798       DumpBrush_Small();
799     }
800   }
801 }
802
803 void HandleKey(Key key, int key_status)
804 {
805   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
806   static struct SetupKeyboardInfo ski;
807   static struct SetupShortcutInfo ssi;
808   static struct
809   {
810     Key *key_custom;
811     Key *key_snap;
812     Key key_default;
813     byte action;
814   } key_info[] =
815   {
816     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
817     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
818     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
819     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
820     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
821     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
822   };
823   int joy = 0;
824   int i;
825
826   if (game_status == GAME_MODE_PLAYING)
827   {
828     /* only needed for single-step tape recording mode */
829     static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
830     static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
831     static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
832     static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
833     int pnr;
834
835     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
836     {
837       byte key_action = 0;
838
839       if (setup.input[pnr].use_joystick)
840         continue;
841
842       ski = setup.input[pnr].key;
843
844       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
845         if (key == *key_info[i].key_custom)
846           key_action |= key_info[i].action;
847
848       /* use combined snap+direction keys for the first player only */
849       if (pnr == 0)
850       {
851         ssi = setup.shortcut;
852
853         for (i = 0; i < NUM_DIRECTIONS; i++)
854           if (key == *key_info[i].key_snap)
855             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
856       }
857
858       /* clear delayed snap and drop actions in single step mode (see below) */
859       if (tape.single_step)
860       {
861         if (clear_snap_button[pnr])
862         {
863           stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
864           clear_snap_button[pnr] = FALSE;
865         }
866
867         if (clear_drop_button[pnr])
868         {
869           stored_player[pnr].action &= ~KEY_BUTTON_DROP;
870           clear_drop_button[pnr] = FALSE;
871         }
872       }
873
874       if (key_status == KEY_PRESSED)
875         stored_player[pnr].action |= key_action;
876       else
877         stored_player[pnr].action &= ~key_action;
878
879       if (tape.single_step && tape.recording && tape.pausing)
880       {
881         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
882         {
883           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
884
885           /* if snap key already pressed, don't snap when releasing (below) */
886           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
887             element_snapped[pnr] = TRUE;
888
889           /* if drop key already pressed, don't drop when releasing (below) */
890           if (stored_player[pnr].action & KEY_BUTTON_DROP)
891             element_dropped[pnr] = TRUE;
892         }
893 #if 1
894         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
895         {
896           if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
897               level.game_engine_type == GAME_ENGINE_TYPE_SP)
898           {
899 #if 0
900             printf("::: drop key pressed\n");
901 #endif
902
903             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
904                 getRedDiskReleaseFlag_SP() == 0)
905               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
906
907             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
908           }
909         }
910 #endif
911         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
912         {
913           if (key_action & KEY_BUTTON_SNAP)
914           {
915             /* if snap key was released without moving (see above), snap now */
916             if (!element_snapped[pnr])
917             {
918               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
919
920               stored_player[pnr].action |= KEY_BUTTON_SNAP;
921
922               /* clear delayed snap button on next event */
923               clear_snap_button[pnr] = TRUE;
924             }
925
926             element_snapped[pnr] = FALSE;
927           }
928
929 #if 1
930           if (key_action & KEY_BUTTON_DROP &&
931               level.game_engine_type == GAME_ENGINE_TYPE_RND)
932           {
933             /* if drop key was released without moving (see above), drop now */
934             if (!element_dropped[pnr])
935             {
936               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
937
938               if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
939                   getRedDiskReleaseFlag_SP() != 0)
940                 stored_player[pnr].action |= KEY_BUTTON_DROP;
941
942               /* clear delayed drop button on next event */
943               clear_drop_button[pnr] = TRUE;
944             }
945
946             element_dropped[pnr] = FALSE;
947           }
948 #endif
949         }
950       }
951       else if (tape.recording && tape.pausing)
952       {
953         /* prevent key release events from un-pausing a paused game */
954         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
955           TapeTogglePause(TAPE_TOGGLE_MANUAL);
956       }
957     }
958   }
959   else
960   {
961     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
962       if (key == key_info[i].key_default)
963         joy |= key_info[i].action;
964   }
965
966   if (joy)
967   {
968     if (key_status == KEY_PRESSED)
969       key_joystick_mapping |= joy;
970     else
971       key_joystick_mapping &= ~joy;
972
973     HandleJoystick();
974   }
975
976   if (game_status != GAME_MODE_PLAYING)
977     key_joystick_mapping = 0;
978
979   if (key_status == KEY_RELEASED)
980     return;
981
982   if ((key == KSYM_Return || key == KSYM_KP_Enter) &&
983       (GetKeyModState() & KMOD_Alt) && video.fullscreen_available)
984   {
985     setup.fullscreen = !setup.fullscreen;
986
987     ToggleFullscreenIfNeeded();
988
989     if (game_status == GAME_MODE_SETUP)
990       RedrawSetupScreenAfterFullscreenToggle();
991
992     return;
993   }
994
995 #if 0
996   if (game_status == GAME_MODE_PLAYING && local_player->LevelSolved_GameEnd &&
997       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
998 #else
999   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1000       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1001 #endif
1002   {
1003     GameEnd();
1004
1005     return;
1006   }
1007
1008   if (game_status == GAME_MODE_MAIN &&
1009       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1010   {
1011     StartGameActions(options.network, setup.autorecord, level.random_seed);
1012
1013     return;
1014   }
1015
1016   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1017   {
1018     if (key == setup.shortcut.save_game)
1019       TapeQuickSave();
1020     else if (key == setup.shortcut.load_game)
1021       TapeQuickLoad();
1022     else if (key == setup.shortcut.toggle_pause)
1023       TapeTogglePause(TAPE_TOGGLE_MANUAL);
1024
1025     HandleTapeButtonKeys(key);
1026     HandleSoundButtonKeys(key);
1027   }
1028
1029   if (game_status == GAME_MODE_PLAYING && !network_playing)
1030   {
1031     int centered_player_nr_next = -999;
1032
1033     if (key == setup.shortcut.focus_player_all)
1034       centered_player_nr_next = -1;
1035     else
1036       for (i = 0; i < MAX_PLAYERS; i++)
1037         if (key == setup.shortcut.focus_player[i])
1038           centered_player_nr_next = i;
1039
1040     if (centered_player_nr_next != -999)
1041     {
1042       game.centered_player_nr_next = centered_player_nr_next;
1043       game.set_centered_player = TRUE;
1044
1045       if (tape.recording)
1046       {
1047         tape.centered_player_nr_next = game.centered_player_nr_next;
1048         tape.set_centered_player = TRUE;
1049       }
1050     }
1051   }
1052
1053   HandleKeysSpecial(key);
1054
1055   if (HandleGadgetsKeyInput(key))
1056   {
1057     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1058       key = KSYM_UNDEFINED;
1059   }
1060
1061   switch (game_status)
1062   {
1063     case GAME_MODE_PSEUDO_TYPENAME:
1064       HandleTypeName(0, key);
1065       break;
1066
1067     case GAME_MODE_TITLE:
1068     case GAME_MODE_MAIN:
1069     case GAME_MODE_LEVELS:
1070     case GAME_MODE_LEVELNR:
1071     case GAME_MODE_SETUP:
1072     case GAME_MODE_INFO:
1073     case GAME_MODE_SCORES:
1074       switch (key)
1075       {
1076         case KSYM_space:
1077         case KSYM_Return:
1078           if (game_status == GAME_MODE_TITLE)
1079             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1080           else if (game_status == GAME_MODE_MAIN)
1081             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1082           else if (game_status == GAME_MODE_LEVELS)
1083             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1084           else if (game_status == GAME_MODE_LEVELNR)
1085             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1086           else if (game_status == GAME_MODE_SETUP)
1087             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1088           else if (game_status == GAME_MODE_INFO)
1089             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1090           else if (game_status == GAME_MODE_SCORES)
1091             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1092           break;
1093
1094         case KSYM_Escape:
1095           if (game_status != GAME_MODE_MAIN)
1096             FadeSkipNextFadeIn();
1097
1098           if (game_status == GAME_MODE_TITLE)
1099             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1100           else if (game_status == GAME_MODE_LEVELS)
1101             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1102           else if (game_status == GAME_MODE_LEVELNR)
1103             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1104           else if (game_status == GAME_MODE_SETUP)
1105             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1106           else if (game_status == GAME_MODE_INFO)
1107             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1108           else if (game_status == GAME_MODE_SCORES)
1109             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1110           break;
1111
1112         case KSYM_Page_Up:
1113           if (game_status == GAME_MODE_LEVELS)
1114             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1115           else if (game_status == GAME_MODE_LEVELNR)
1116             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1117           else if (game_status == GAME_MODE_SETUP)
1118             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1119           else if (game_status == GAME_MODE_INFO)
1120             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1121           else if (game_status == GAME_MODE_SCORES)
1122             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1123           break;
1124
1125         case KSYM_Page_Down:
1126           if (game_status == GAME_MODE_LEVELS)
1127             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1128           else if (game_status == GAME_MODE_LEVELNR)
1129             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1130           else if (game_status == GAME_MODE_SETUP)
1131             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1132           else if (game_status == GAME_MODE_INFO)
1133             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1134           else if (game_status == GAME_MODE_SCORES)
1135             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1136           break;
1137
1138 #ifdef DEBUG
1139         case KSYM_0:
1140           GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
1141           break;
1142
1143         case KSYM_b:
1144           setup.sp_show_border_elements = !setup.sp_show_border_elements;
1145           printf("Supaplex border elements %s\n",
1146                  setup.sp_show_border_elements ? "enabled" : "disabled");
1147           break;
1148 #endif
1149
1150         default:
1151           break;
1152       }
1153       break;
1154
1155     case GAME_MODE_EDITOR:
1156       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1157         HandleLevelEditorKeyInput(key);
1158       break;
1159
1160     case GAME_MODE_PLAYING:
1161     {
1162       switch (key)
1163       {
1164         case KSYM_Escape:
1165           RequestQuitGame(setup.ask_on_escape);
1166           break;
1167
1168 #ifdef DEBUG
1169         case KSYM_0:
1170 #if 0
1171         case KSYM_1:
1172         case KSYM_2:
1173         case KSYM_3:
1174         case KSYM_4:
1175         case KSYM_5:
1176         case KSYM_6:
1177         case KSYM_7:
1178         case KSYM_8:
1179         case KSYM_9:
1180 #endif
1181           if (key == KSYM_0)
1182           {
1183             if (GameFrameDelay == 500)
1184               GameFrameDelay = GAME_FRAME_DELAY;
1185             else
1186               GameFrameDelay = 500;
1187           }
1188           else
1189             GameFrameDelay = (key - KSYM_0) * 10;
1190           printf("Game speed == %d%% (%d ms delay between two frames)\n",
1191                  GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
1192           break;
1193
1194         case KSYM_d:
1195           if (options.debug)
1196           {
1197             options.debug = FALSE;
1198             printf("debug mode disabled\n");
1199           }
1200           else
1201           {
1202             options.debug = TRUE;
1203             printf("debug mode enabled\n");
1204           }
1205           break;
1206
1207         case KSYM_s:
1208           if (!global.fps_slowdown)
1209           {
1210             global.fps_slowdown = TRUE;
1211             global.fps_slowdown_factor = 2;
1212             printf("fps slowdown enabled -- display only every 2nd frame\n");
1213           }
1214           else if (global.fps_slowdown_factor == 2)
1215           {
1216             global.fps_slowdown_factor = 4;
1217             printf("fps slowdown enabled -- display only every 4th frame\n");
1218           }
1219           else
1220           {
1221             global.fps_slowdown = FALSE;
1222             global.fps_slowdown_factor = 1;
1223             printf("fps slowdown disabled\n");
1224           }
1225           break;
1226
1227         case KSYM_f:
1228           ScrollStepSize = TILEX / 8;
1229           printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
1230           break;
1231
1232         case KSYM_g:
1233           ScrollStepSize = TILEX / 4;
1234           printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
1235           break;
1236
1237         case KSYM_h:
1238           ScrollStepSize = TILEX / 2;
1239           printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
1240           break;
1241
1242         case KSYM_l:
1243           ScrollStepSize = TILEX;
1244           printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
1245           break;
1246
1247         case KSYM_v:
1248           printf("::: currently using game engine version %d\n",
1249                  game.engine_version);
1250           break;
1251 #endif
1252
1253         default:
1254           break;
1255       }
1256       break;
1257     }
1258
1259     default:
1260       if (key == KSYM_Escape)
1261       {
1262         game_status = GAME_MODE_MAIN;
1263         DrawMainMenu();
1264
1265         return;
1266       }
1267   }
1268 }
1269
1270 void HandleNoEvent()
1271 {
1272   if (button_status && game_status != GAME_MODE_PLAYING)
1273   {
1274     HandleButton(0, 0, -button_status, button_status);
1275
1276     return;
1277   }
1278
1279 #if defined(NETWORK_AVALIABLE)
1280   if (options.network)
1281     HandleNetworking();
1282 #endif
1283
1284   HandleJoystick();
1285 }
1286
1287 static int HandleJoystickForAllPlayers()
1288 {
1289   int i;
1290   int result = 0;
1291
1292   for (i = 0; i < MAX_PLAYERS; i++)
1293   {
1294     byte joy_action = 0;
1295
1296     /*
1297     if (!setup.input[i].use_joystick)
1298       continue;
1299       */
1300
1301     joy_action = Joystick(i);
1302     result |= joy_action;
1303
1304     if (!setup.input[i].use_joystick)
1305       continue;
1306
1307     stored_player[i].action = joy_action;
1308   }
1309
1310   return result;
1311 }
1312
1313 void HandleJoystick()
1314 {
1315   int joystick  = HandleJoystickForAllPlayers();
1316   int keyboard  = key_joystick_mapping;
1317   int joy       = (joystick | keyboard);
1318   int left      = joy & JOY_LEFT;
1319   int right     = joy & JOY_RIGHT;
1320   int up        = joy & JOY_UP;
1321   int down      = joy & JOY_DOWN;
1322   int button    = joy & JOY_BUTTON;
1323   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1324   int dx        = (left ? -1    : right ? 1     : 0);
1325   int dy        = (up   ? -1    : down  ? 1     : 0);
1326
1327   switch (game_status)
1328   {
1329     case GAME_MODE_TITLE:
1330     case GAME_MODE_MAIN:
1331     case GAME_MODE_LEVELS:
1332     case GAME_MODE_LEVELNR:
1333     case GAME_MODE_SETUP:
1334     case GAME_MODE_INFO:
1335     {
1336       static unsigned int joystickmove_delay = 0;
1337
1338       if (joystick && !button &&
1339           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1340         newbutton = dx = dy = 0;
1341
1342       if (game_status == GAME_MODE_TITLE)
1343         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1344       else if (game_status == GAME_MODE_MAIN)
1345         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1346       else if (game_status == GAME_MODE_LEVELS)
1347         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1348       else if (game_status == GAME_MODE_LEVELNR)
1349         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1350       else if (game_status == GAME_MODE_SETUP)
1351         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1352       else if (game_status == GAME_MODE_INFO)
1353         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1354       break;
1355     }
1356
1357     case GAME_MODE_SCORES:
1358       HandleHallOfFame(0, 0, dx, dy, !newbutton);
1359       break;
1360
1361     case GAME_MODE_EDITOR:
1362       HandleLevelEditorIdle();
1363       break;
1364
1365     case GAME_MODE_PLAYING:
1366       if (tape.playing || keyboard)
1367         newbutton = ((joy & JOY_BUTTON) != 0);
1368
1369 #if 0
1370       if (local_player->LevelSolved_GameEnd && newbutton)
1371 #else
1372       if (AllPlayersGone && newbutton)
1373 #endif
1374       {
1375         GameEnd();
1376
1377         return;
1378       }
1379
1380       break;
1381
1382     default:
1383       break;
1384   }
1385 }