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