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