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