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