added step-based engine snapshots to undo/redo game steps
[rocksndiamonds.git] / src / events.c
1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // events.c
10 // ============================================================================
11
12 #include "libgame/libgame.h"
13
14 #include "events.h"
15 #include "init.h"
16 #include "screens.h"
17 #include "tools.h"
18 #include "game.h"
19 #include "editor.h"
20 #include "files.h"
21 #include "tape.h"
22 #include "network.h"
23
24
25 #define DEBUG_EVENTS            0
26
27 #define DEBUG_EVENTS_BUTTON     (DEBUG_EVENTS   * 0)
28 #define DEBUG_EVENTS_MOTION     (DEBUG_EVENTS   * 0)
29 #define DEBUG_EVENTS_WINDOW     (DEBUG_EVENTS   * 0)
30 #define DEBUG_EVENTS_FINGER     (DEBUG_EVENTS   * 0)
31 #define DEBUG_EVENTS_TEXT       (DEBUG_EVENTS   * 1)
32 #define DEBUG_EVENTS_KEY        (DEBUG_EVENTS   * 1)
33
34
35 static boolean cursor_inside_playfield = FALSE;
36 static int cursor_mode_last = CURSOR_DEFAULT;
37 static unsigned int special_cursor_delay = 0;
38 static unsigned int special_cursor_delay_value = 1000;
39
40 /* event filter especially needed for SDL event filtering due to
41    delay problems with lots of mouse motion events when mouse button
42    not pressed (X11 can handle this with 'PointerMotionHintMask') */
43
44 /* event filter addition for SDL2: as SDL2 does not have a function to enable
45    or disable keyboard auto-repeat, filter repeated keyboard events instead */
46
47 static int FilterEventsExt(const Event *event)
48 {
49   MotionEvent *motion;
50
51 #if defined(TARGET_SDL2)
52   /* skip repeated key press events if keyboard auto-repeat is disabled */
53   if (event->type == EVENT_KEYPRESS &&
54       event->key.repeat &&
55       !keyrepeat_status)
56     return 0;
57 #endif
58
59   /* non-motion events are directly passed to event handler functions */
60   if (event->type != EVENT_MOTIONNOTIFY)
61     return 1;
62
63   motion = (MotionEvent *)event;
64   cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
65                              motion->y >= SY && motion->y < SY + SYSIZE);
66
67   /* do no reset mouse cursor before all pending events have been processed */
68   if (gfx.cursor_mode == cursor_mode_last &&
69       ((effectiveGameStatus() == GAME_MODE_TITLE &&
70         gfx.cursor_mode == CURSOR_NONE) ||
71        (game_status == GAME_MODE_PLAYING &&
72         gfx.cursor_mode == CURSOR_PLAYFIELD)))
73   {
74     SetMouseCursor(CURSOR_DEFAULT);
75
76     DelayReached(&special_cursor_delay, 0);
77
78     cursor_mode_last = CURSOR_DEFAULT;
79   }
80
81   /* skip mouse motion events without pressed button outside level editor */
82   if (button_status == MB_RELEASED &&
83       game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
84     return 0;
85
86   return 1;
87 }
88
89 #if defined(TARGET_SDL2)
90 int FilterEvents(void *userdata, Event *event)
91 {
92   return FilterEventsExt(event);
93 }
94 #else
95 int FilterEvents(const Event *event)
96 {
97   return FilterEventsExt(event);
98 }
99 #endif
100
101 /* to prevent delay problems, skip mouse motion events if the very next
102    event is also a mouse motion event (and therefore effectively only
103    handling the last of a row of mouse motion events in the event queue) */
104
105 boolean SkipPressedMouseMotionEvent(const Event *event)
106 {
107   /* nothing to do if the current event is not a mouse motion event */
108   if (event->type != EVENT_MOTIONNOTIFY)
109     return FALSE;
110
111   /* only skip motion events with pressed button outside the game */
112   if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
113     return FALSE;
114
115   if (PendingEvent())
116   {
117     Event next_event;
118
119     PeekEvent(&next_event);
120
121     /* if next event is also a mouse motion event, skip the current one */
122     if (next_event.type == EVENT_MOTIONNOTIFY)
123       return TRUE;
124   }
125
126   return FALSE;
127 }
128
129 /* this is only really needed for non-SDL targets to filter unwanted events;
130    when using SDL with properly installed event filter, this function can be
131    replaced with a simple "NextEvent()" call, but it doesn't hurt either */
132
133 boolean NextValidEvent(Event *event)
134 {
135   while (PendingEvent())
136   {
137     boolean handle_this_event = FALSE;
138
139     NextEvent(event);
140
141     if (FilterEventsExt(event))
142       handle_this_event = TRUE;
143
144     if (SkipPressedMouseMotionEvent(event))
145       handle_this_event = FALSE;
146
147     if (handle_this_event)
148       return TRUE;
149   }
150
151   return FALSE;
152 }
153
154 void EventLoop(void)
155 {
156   while (1)
157   {
158     if (PendingEvent())         /* got event */
159     {
160       Event event;
161
162       while (NextValidEvent(&event))
163       {
164         switch (event.type)
165         {
166           case EVENT_BUTTONPRESS:
167           case EVENT_BUTTONRELEASE:
168             HandleButtonEvent((ButtonEvent *) &event);
169             break;
170   
171           case EVENT_MOTIONNOTIFY:
172             HandleMotionEvent((MotionEvent *) &event);
173             break;
174
175 #if defined(TARGET_SDL2)
176           case SDL_WINDOWEVENT:
177             HandleWindowEvent((WindowEvent *) &event);
178             break;
179
180           case EVENT_FINGERPRESS:
181           case EVENT_FINGERRELEASE:
182           case EVENT_FINGERMOTION:
183             HandleFingerEvent((FingerEvent *) &event);
184             break;
185
186           case EVENT_TEXTINPUT:
187             HandleTextEvent((TextEvent *) &event);
188             break;
189
190           case SDL_APP_WILLENTERBACKGROUND:
191           case SDL_APP_DIDENTERBACKGROUND:
192           case SDL_APP_WILLENTERFOREGROUND:
193           case SDL_APP_DIDENTERFOREGROUND:
194             HandlePauseResumeEvent((PauseResumeEvent *) &event);
195             break;
196 #endif
197
198           case EVENT_KEYPRESS:
199           case EVENT_KEYRELEASE:
200             HandleKeyEvent((KeyEvent *) &event);
201             break;
202
203           default:
204             HandleOtherEvents(&event);
205             break;
206         }
207       }
208     }
209     else
210     {
211       if (effectiveGameStatus() == GAME_MODE_TITLE)
212       {
213         /* when showing title screens, hide mouse pointer (if not moved) */
214
215         if (gfx.cursor_mode != CURSOR_NONE &&
216             DelayReached(&special_cursor_delay, special_cursor_delay_value))
217         {
218           SetMouseCursor(CURSOR_NONE);
219         }
220       }
221       else if (game_status == GAME_MODE_PLAYING && !tape.pausing)
222       {
223         /* when playing, display a special mouse pointer inside the playfield */
224
225         if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
226             cursor_inside_playfield &&
227             DelayReached(&special_cursor_delay, special_cursor_delay_value))
228         {
229           SetMouseCursor(CURSOR_PLAYFIELD);
230         }
231       }
232       else if (gfx.cursor_mode != CURSOR_DEFAULT)
233       {
234         SetMouseCursor(CURSOR_DEFAULT);
235       }
236
237       /* this is set after all pending events have been processed */
238       cursor_mode_last = gfx.cursor_mode;
239     }
240
241     /* also execute after pending events have been processed before */
242     HandleNoEvent();
243
244     /* don't use all CPU time when idle; the main loop while playing
245        has its own synchronization and is CPU friendly, too */
246
247     if (game_status == GAME_MODE_PLAYING)
248     {
249       HandleGameActions();
250     }
251     else
252     {
253       if (!PendingEvent())      /* delay only if no pending events */
254         Delay(10);
255     }
256
257     /* refresh window contents from drawing buffer, if needed */
258     BackToFront();
259
260     if (game_status == GAME_MODE_QUIT)
261       return;
262   }
263 }
264
265 void HandleOtherEvents(Event *event)
266 {
267   switch (event->type)
268   {
269     case EVENT_EXPOSE:
270       HandleExposeEvent((ExposeEvent *) event);
271       break;
272
273     case EVENT_UNMAPNOTIFY:
274 #if 0
275       /* This causes the game to stop not only when iconified, but also
276          when on another virtual desktop, which might be not desired. */
277       SleepWhileUnmapped();
278 #endif
279       break;
280
281     case EVENT_FOCUSIN:
282     case EVENT_FOCUSOUT:
283       HandleFocusEvent((FocusChangeEvent *) event);
284       break;
285
286     case EVENT_CLIENTMESSAGE:
287       HandleClientMessageEvent((ClientMessageEvent *) event);
288       break;
289
290 #if defined(TARGET_SDL)
291     case SDL_JOYAXISMOTION:
292     case SDL_JOYBUTTONDOWN:
293     case SDL_JOYBUTTONUP:
294       HandleJoystickEvent(event);
295       break;
296
297     case SDL_SYSWMEVENT:
298       HandleWindowManagerEvent(event);
299       break;
300 #endif
301
302     default:
303       break;
304   }
305 }
306
307 void ClearEventQueue()
308 {
309   while (PendingEvent())
310   {
311     Event event;
312
313     NextEvent(&event);
314
315     switch (event.type)
316     {
317       case EVENT_BUTTONRELEASE:
318         button_status = MB_RELEASED;
319         break;
320
321       case EVENT_KEYRELEASE:
322         ClearPlayerAction();
323         break;
324
325       default:
326         HandleOtherEvents(&event);
327         break;
328     }
329   }
330 }
331
332 void ClearPlayerAction()
333 {
334   int i;
335
336   /* simulate key release events for still pressed keys */
337   key_joystick_mapping = 0;
338   for (i = 0; i < MAX_PLAYERS; i++)
339     stored_player[i].action = 0;
340 }
341
342 void SleepWhileUnmapped()
343 {
344   boolean window_unmapped = TRUE;
345
346   KeyboardAutoRepeatOn();
347
348   while (window_unmapped)
349   {
350     Event event;
351
352     NextEvent(&event);
353
354     switch (event.type)
355     {
356       case EVENT_BUTTONRELEASE:
357         button_status = MB_RELEASED;
358         break;
359
360       case EVENT_KEYRELEASE:
361         key_joystick_mapping = 0;
362         break;
363
364       case EVENT_MAPNOTIFY:
365         window_unmapped = FALSE;
366         break;
367
368       case EVENT_UNMAPNOTIFY:
369         /* this is only to surely prevent the 'should not happen' case
370          * of recursively looping between 'SleepWhileUnmapped()' and
371          * 'HandleOtherEvents()' which usually calls this funtion.
372          */
373         break;
374
375       default:
376         HandleOtherEvents(&event);
377         break;
378     }
379   }
380
381   if (game_status == GAME_MODE_PLAYING)
382     KeyboardAutoRepeatOffUnlessAutoplay();
383 }
384
385 void HandleExposeEvent(ExposeEvent *event)
386 {
387 }
388
389 void HandleButtonEvent(ButtonEvent *event)
390 {
391 #if DEBUG_EVENTS_BUTTON
392   Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
393         event->button,
394         event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
395         event->x, event->y);
396 #endif
397
398   motion_status = FALSE;
399
400   if (event->type == EVENT_BUTTONPRESS)
401     button_status = event->button;
402   else
403     button_status = MB_RELEASED;
404
405   HandleButton(event->x, event->y, button_status, event->button);
406 }
407
408 void HandleMotionEvent(MotionEvent *event)
409 {
410   if (!PointerInWindow(window))
411     return;     /* window and pointer are on different screens */
412
413   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
414     return;
415
416   motion_status = TRUE;
417
418 #if DEBUG_EVENTS_MOTION
419   Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
420         button_status, event->x, event->y);
421 #endif
422
423   HandleButton(event->x, event->y, button_status, button_status);
424 }
425
426 #if defined(TARGET_SDL2)
427
428 void HandleWindowEvent(WindowEvent *event)
429 {
430 #if DEBUG_EVENTS_WINDOW
431   int subtype = event->event;
432
433   char *event_name =
434     (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
435      subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
436      subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
437      subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
438      subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
439      subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
440      subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
441      subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
442      subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
443      subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
444      subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
445      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
446      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
447      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
448      "(UNKNOWN)");
449
450   Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
451         event_name, event->data1, event->data2);
452 #endif
453
454   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
455       event->event == SDL_WINDOWEVENT_RESIZED ||
456       event->event == SDL_WINDOWEVENT_EXPOSED)
457     SDLRedrawWindow();
458
459   if (event->event == SDL_WINDOWEVENT_RESIZED && !video.fullscreen_enabled)
460   {
461     int new_window_width  = event->data1;
462     int new_window_height = event->data2;
463
464     // if window size has changed after resizing, calculate new scaling factor
465     if (new_window_width  != video.window_width ||
466         new_window_height != video.window_height)
467     {
468       int new_xpercent = (100 * new_window_width  / video.width);
469       int new_ypercent = (100 * new_window_height / video.height);
470
471       setup.window_scaling_percent = video.window_scaling_percent =
472         MIN(MAX(MIN_WINDOW_SCALING_PERCENT, MIN(new_xpercent, new_ypercent)),
473             MAX_WINDOW_SCALING_PERCENT);
474
475       video.window_width  = new_window_width;
476       video.window_height = new_window_height;
477
478       if (game_status == GAME_MODE_SETUP)
479         RedrawSetupScreenAfterFullscreenToggle();
480
481       SetWindowTitle();
482     }
483   }
484 }
485
486 #define NUM_TOUCH_FINGERS               3
487
488 static struct
489 {
490   boolean touched;
491   SDL_FingerID finger_id;
492   int counter;
493   Key key;
494 } touch_info[NUM_TOUCH_FINGERS];
495
496 void HandleFingerEvent(FingerEvent *event)
497 {
498   static Key motion_key_x = KSYM_UNDEFINED;
499   static Key motion_key_y = KSYM_UNDEFINED;
500   static Key button_key = KSYM_UNDEFINED;
501   static float motion_x1, motion_y1;
502   static float button_x1, button_y1;
503   static SDL_FingerID motion_id = -1;
504   static SDL_FingerID button_id = -1;
505   int move_trigger_distance_percent = 2;   // percent of touchpad width/height
506   int drop_trigger_distance_percent = 5;   // percent of touchpad width/height
507   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
508   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
509   float event_x = event->x;
510   float event_y = event->y;
511
512 #if DEBUG_EVENTS_FINGER
513   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
514         event->type == EVENT_FINGERPRESS ? "pressed" :
515         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
516         event->touchId,
517         event->fingerId,
518         event->x, event->y,
519         event->dx, event->dy,
520         event->pressure);
521 #endif
522
523   if (game_status != GAME_MODE_PLAYING)
524     return;
525
526   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
527   {
528     int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
529                       KEY_PRESSED);
530     Key key = (event->x < 1.0 / 3.0 ?
531                (event->y < 1.0 / 2.0 ? setup.input[0].key.snap :
532                 setup.input[0].key.drop) :
533                event->x > 2.0 / 3.0 ?
534                (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
535                 event->y > 2.0 / 3.0 ? setup.input[0].key.down :
536                 event->x < 5.0 / 6.0 ? setup.input[0].key.left :
537                 setup.input[0].key.right) :
538                KSYM_UNDEFINED);
539     char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
540                              "KEY_PRESSED");
541     int i;
542
543     Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
544           getKeyNameFromKey(key), key_status_name, event->fingerId);
545
546     // check if we already know this touch event's finger id
547     for (i = 0; i < NUM_TOUCH_FINGERS; i++)
548     {
549       if (touch_info[i].touched &&
550           touch_info[i].finger_id == event->fingerId)
551       {
552         // Error(ERR_DEBUG, "MARK 1: %d", i);
553
554         break;
555       }
556     }
557
558     if (i >= NUM_TOUCH_FINGERS)
559     {
560       if (key_status == KEY_PRESSED)
561       {
562         int oldest_pos = 0, oldest_counter = touch_info[0].counter;
563
564         // unknown finger id -- get new, empty slot, if available
565         for (i = 0; i < NUM_TOUCH_FINGERS; i++)
566         {
567           if (touch_info[i].counter < oldest_counter)
568           {
569             oldest_pos = i;
570             oldest_counter = touch_info[i].counter;
571
572             // Error(ERR_DEBUG, "MARK 2: %d", i);
573           }
574
575           if (!touch_info[i].touched)
576           {
577             // Error(ERR_DEBUG, "MARK 3: %d", i);
578
579             break;
580           }
581         }
582
583         if (i >= NUM_TOUCH_FINGERS)
584         {
585           // all slots allocated -- use oldest slot
586           i = oldest_pos;
587
588           // Error(ERR_DEBUG, "MARK 4: %d", i);
589         }
590       }
591       else
592       {
593         // release of previously unknown key (should not happen)
594
595         if (key != KSYM_UNDEFINED)
596         {
597           HandleKey(key, KEY_RELEASED);
598
599           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
600                 getKeyNameFromKey(key), "KEY_RELEASED", i);
601         }
602       }
603     }
604
605     if (i < NUM_TOUCH_FINGERS)
606     {
607       if (key_status == KEY_PRESSED)
608       {
609         if (touch_info[i].key != key)
610         {
611           if (touch_info[i].key != KSYM_UNDEFINED)
612           {
613             HandleKey(touch_info[i].key, KEY_RELEASED);
614
615             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
616                   getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
617           }
618
619           if (key != KSYM_UNDEFINED)
620           {
621             HandleKey(key, KEY_PRESSED);
622
623             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
624                   getKeyNameFromKey(key), "KEY_PRESSED", i);
625           }
626         }
627
628         touch_info[i].touched = TRUE;
629         touch_info[i].finger_id = event->fingerId;
630         touch_info[i].counter = Counter();
631         touch_info[i].key = key;
632       }
633       else
634       {
635         if (touch_info[i].key != KSYM_UNDEFINED)
636         {
637           HandleKey(touch_info[i].key, KEY_RELEASED);
638
639           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
640                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
641         }
642
643         touch_info[i].touched = FALSE;
644         touch_info[i].finger_id = 0;
645         touch_info[i].counter = 0;
646         touch_info[i].key = 0;
647       }
648     }
649
650     return;
651   }
652
653   // use touch direction control
654
655   if (event->type == EVENT_FINGERPRESS)
656   {
657     if (event_x > 1.0 / 3.0)
658     {
659       // motion area
660
661       motion_id = event->fingerId;
662
663       motion_x1 = event_x;
664       motion_y1 = event_y;
665
666       motion_key_x = KSYM_UNDEFINED;
667       motion_key_y = KSYM_UNDEFINED;
668
669       Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
670     }
671     else
672     {
673       // button area
674
675       button_id = event->fingerId;
676
677       button_x1 = event_x;
678       button_y1 = event_y;
679
680       button_key = setup.input[0].key.snap;
681
682       HandleKey(button_key, KEY_PRESSED);
683
684       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
685     }
686   }
687   else if (event->type == EVENT_FINGERRELEASE)
688   {
689     if (event->fingerId == motion_id)
690     {
691       motion_id = -1;
692
693       if (motion_key_x != KSYM_UNDEFINED)
694         HandleKey(motion_key_x, KEY_RELEASED);
695       if (motion_key_y != KSYM_UNDEFINED)
696         HandleKey(motion_key_y, KEY_RELEASED);
697
698       motion_key_x = KSYM_UNDEFINED;
699       motion_key_y = KSYM_UNDEFINED;
700
701       Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
702     }
703     else if (event->fingerId == button_id)
704     {
705       button_id = -1;
706
707       if (button_key != KSYM_UNDEFINED)
708         HandleKey(button_key, KEY_RELEASED);
709
710       button_key = KSYM_UNDEFINED;
711
712       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
713     }
714   }
715   else if (event->type == EVENT_FINGERMOTION)
716   {
717     if (event->fingerId == motion_id)
718     {
719       float distance_x = ABS(event_x - motion_x1);
720       float distance_y = ABS(event_y - motion_y1);
721       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
722                               event_x > motion_x1 ? setup.input[0].key.right :
723                               KSYM_UNDEFINED);
724       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
725                               event_y > motion_y1 ? setup.input[0].key.down :
726                               KSYM_UNDEFINED);
727
728       if (distance_x < move_trigger_distance / 2 ||
729           distance_x < distance_y)
730         new_motion_key_x = KSYM_UNDEFINED;
731
732       if (distance_y < move_trigger_distance / 2 ||
733           distance_y < distance_x)
734         new_motion_key_y = KSYM_UNDEFINED;
735
736       if (distance_x > move_trigger_distance ||
737           distance_y > move_trigger_distance)
738       {
739         if (new_motion_key_x != motion_key_x)
740         {
741           if (motion_key_x != KSYM_UNDEFINED)
742             HandleKey(motion_key_x, KEY_RELEASED);
743           if (new_motion_key_x != KSYM_UNDEFINED)
744             HandleKey(new_motion_key_x, KEY_PRESSED);
745         }
746
747         if (new_motion_key_y != motion_key_y)
748         {
749           if (motion_key_y != KSYM_UNDEFINED)
750             HandleKey(motion_key_y, KEY_RELEASED);
751           if (new_motion_key_y != KSYM_UNDEFINED)
752             HandleKey(new_motion_key_y, KEY_PRESSED);
753         }
754
755         motion_x1 = event_x;
756         motion_y1 = event_y;
757
758         motion_key_x = new_motion_key_x;
759         motion_key_y = new_motion_key_y;
760
761         Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
762       }
763     }
764     else if (event->fingerId == button_id)
765     {
766       float distance_x = ABS(event_x - button_x1);
767       float distance_y = ABS(event_y - button_y1);
768
769       if (distance_x < drop_trigger_distance / 2 &&
770           distance_y > drop_trigger_distance)
771       {
772         if (button_key == setup.input[0].key.snap)
773           HandleKey(button_key, KEY_RELEASED);
774
775         button_x1 = event_x;
776         button_y1 = event_y;
777
778         button_key = setup.input[0].key.drop;
779
780         HandleKey(button_key, KEY_PRESSED);
781
782         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
783       }
784     }
785   }
786 }
787
788 static boolean checkTextInputKeyModState()
789 {
790   // when playing, only handle raw key events and ignore text input
791   if (game_status == GAME_MODE_PLAYING)
792     return FALSE;
793
794   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
795 }
796
797 void HandleTextEvent(TextEvent *event)
798 {
799   char *text = event->text;
800   Key key = getKeyFromKeyName(text);
801
802 #if DEBUG_EVENTS_TEXT
803   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
804         text,
805         strlen(text),
806         text[0], (int)(text[0]),
807         key,
808         getKeyNameFromKey(key),
809         GetKeyModState());
810 #endif
811
812 #if defined(PLATFORM_ANDROID)
813   if (game_status == GAME_MODE_PSEUDO_TYPENAME)
814   {
815     HandleTypeName(0, key);
816
817     return;
818   }
819 #endif
820
821   // only handle key input with text modifier keys pressed
822   if (checkTextInputKeyModState())
823   {
824     HandleKey(key, KEY_PRESSED);
825     HandleKey(key, KEY_RELEASED);
826   }
827 }
828
829 void HandlePauseResumeEvent(PauseResumeEvent *event)
830 {
831   if (event->type == SDL_APP_WILLENTERBACKGROUND)
832   {
833     Mix_PauseMusic();
834   }
835   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
836   {
837     Mix_ResumeMusic();
838   }
839 }
840
841 #endif
842
843 void HandleKeyEvent(KeyEvent *event)
844 {
845   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
846   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
847   Key key = GetEventKey(event, with_modifiers);
848   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
849
850 #if DEBUG_EVENTS_KEY
851   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
852         event->type == EVENT_KEYPRESS ? "pressed" : "released",
853         event->keysym.scancode,
854         event->keysym.sym,
855         keymod,
856         GetKeyModState(),
857         key,
858         getKeyNameFromKey(key));
859 #endif
860
861 #if defined(PLATFORM_ANDROID)
862   // always map the "back" button to the "escape" key on Android devices
863   if (key == KSYM_Back)
864     key = KSYM_Escape;
865 #endif
866
867   HandleKeyModState(keymod, key_status);
868
869 #if defined(TARGET_SDL2)
870   // only handle raw key input without text modifier keys pressed
871   if (!checkTextInputKeyModState())
872     HandleKey(key, key_status);
873 #else
874   HandleKey(key, key_status);
875 #endif
876 }
877
878 void HandleFocusEvent(FocusChangeEvent *event)
879 {
880   static int old_joystick_status = -1;
881
882   if (event->type == EVENT_FOCUSOUT)
883   {
884     KeyboardAutoRepeatOn();
885     old_joystick_status = joystick.status;
886     joystick.status = JOYSTICK_NOT_AVAILABLE;
887
888     ClearPlayerAction();
889   }
890   else if (event->type == EVENT_FOCUSIN)
891   {
892     /* When there are two Rocks'n'Diamonds windows which overlap and
893        the player moves the pointer from one game window to the other,
894        a 'FocusOut' event is generated for the window the pointer is
895        leaving and a 'FocusIn' event is generated for the window the
896        pointer is entering. In some cases, it can happen that the
897        'FocusIn' event is handled by the one game process before the
898        'FocusOut' event by the other game process. In this case the
899        X11 environment would end up with activated keyboard auto repeat,
900        because unfortunately this is a global setting and not (which
901        would be far better) set for each X11 window individually.
902        The effect would be keyboard auto repeat while playing the game
903        (game_status == GAME_MODE_PLAYING), which is not desired.
904        To avoid this special case, we just wait 1/10 second before
905        processing the 'FocusIn' event.
906     */
907
908     if (game_status == GAME_MODE_PLAYING)
909     {
910       Delay(100);
911       KeyboardAutoRepeatOffUnlessAutoplay();
912     }
913
914     if (old_joystick_status != -1)
915       joystick.status = old_joystick_status;
916   }
917 }
918
919 void HandleClientMessageEvent(ClientMessageEvent *event)
920 {
921   if (CheckCloseWindowEvent(event))
922     CloseAllAndExit(0);
923 }
924
925 void HandleWindowManagerEvent(Event *event)
926 {
927 #if defined(TARGET_SDL)
928   SDLHandleWindowManagerEvent(event);
929 #endif
930 }
931
932 void HandleButton(int mx, int my, int button, int button_nr)
933 {
934   static int old_mx = 0, old_my = 0;
935   boolean button_hold = FALSE;
936
937   if (button < 0)
938   {
939     mx = old_mx;
940     my = old_my;
941     button = -button;
942     button_hold = TRUE;
943   }
944   else
945   {
946     old_mx = mx;
947     old_my = my;
948   }
949
950 #if defined(PLATFORM_ANDROID)
951   // !!! for now, do not handle gadgets when playing -- maybe fix this !!!
952   if (game_status != GAME_MODE_PLAYING &&
953       HandleGadgets(mx, my, button))
954   {
955     /* do not handle this button event anymore */
956     mx = my = -32;      /* force mouse event to be outside screen tiles */
957   }
958 #else
959   if (HandleGadgets(mx, my, button))
960   {
961     /* do not handle this button event anymore */
962     mx = my = -32;      /* force mouse event to be outside screen tiles */
963   }
964 #endif
965
966   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
967     return;
968
969   /* do not use scroll wheel button events for anything other than gadgets */
970   if (IS_WHEEL_BUTTON(button_nr))
971     return;
972
973   switch (game_status)
974   {
975     case GAME_MODE_TITLE:
976       HandleTitleScreen(mx, my, 0, 0, button);
977       break;
978
979     case GAME_MODE_MAIN:
980       HandleMainMenu(mx, my, 0, 0, button);
981       break;
982
983     case GAME_MODE_PSEUDO_TYPENAME:
984       HandleTypeName(0, KSYM_Return);
985       break;
986
987     case GAME_MODE_LEVELS:
988       HandleChooseLevelSet(mx, my, 0, 0, button);
989       break;
990
991     case GAME_MODE_LEVELNR:
992       HandleChooseLevelNr(mx, my, 0, 0, button);
993       break;
994
995     case GAME_MODE_SCORES:
996       HandleHallOfFame(0, 0, 0, 0, button);
997       break;
998
999     case GAME_MODE_EDITOR:
1000       HandleLevelEditorIdle();
1001       break;
1002
1003     case GAME_MODE_INFO:
1004       HandleInfoScreen(mx, my, 0, 0, button);
1005       break;
1006
1007     case GAME_MODE_SETUP:
1008       HandleSetupScreen(mx, my, 0, 0, button);
1009       break;
1010
1011     case GAME_MODE_PLAYING:
1012 #ifdef DEBUG
1013       if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my))
1014         DumpTile(LEVELX((mx - SX) / TILESIZE_VAR),
1015                  LEVELY((my - SY) / TILESIZE_VAR));
1016         // DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
1017 #endif
1018       break;
1019
1020     default:
1021       break;
1022   }
1023 }
1024
1025 static boolean is_string_suffix(char *string, char *suffix)
1026 {
1027   int string_len = strlen(string);
1028   int suffix_len = strlen(suffix);
1029
1030   if (suffix_len > string_len)
1031     return FALSE;
1032
1033   return (strEqual(&string[string_len - suffix_len], suffix));
1034 }
1035
1036 #define MAX_CHEAT_INPUT_LEN     32
1037
1038 static void HandleKeysSpecial(Key key)
1039 {
1040   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1041   char letter = getCharFromKey(key);
1042   int cheat_input_len = strlen(cheat_input);
1043   int i;
1044
1045   if (letter == 0)
1046     return;
1047
1048   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1049   {
1050     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1051       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1052
1053     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1054   }
1055
1056   cheat_input[cheat_input_len++] = letter;
1057   cheat_input[cheat_input_len] = '\0';
1058
1059 #if DEBUG_EVENTS_KEY
1060   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1061 #endif
1062
1063   if (game_status == GAME_MODE_MAIN)
1064   {
1065     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1066         is_string_suffix(cheat_input, ":ist"))
1067     {
1068       InsertSolutionTape();
1069     }
1070     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1071              is_string_suffix(cheat_input, ":rg"))
1072     {
1073       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1074       DrawMainMenu();
1075     }
1076     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1077              is_string_suffix(cheat_input, ":rs"))
1078     {
1079       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1080       DrawMainMenu();
1081     }
1082     else if (is_string_suffix(cheat_input, ":reload-music") ||
1083              is_string_suffix(cheat_input, ":rm"))
1084     {
1085       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1086       DrawMainMenu();
1087     }
1088     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1089              is_string_suffix(cheat_input, ":ra"))
1090     {
1091       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1092                           1 << ARTWORK_TYPE_SOUNDS |
1093                           1 << ARTWORK_TYPE_MUSIC);
1094       DrawMainMenu();
1095     }
1096     else if (is_string_suffix(cheat_input, ":dump-level") ||
1097              is_string_suffix(cheat_input, ":dl"))
1098     {
1099       DumpLevel(&level);
1100     }
1101     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1102              is_string_suffix(cheat_input, ":dt"))
1103     {
1104       DumpTape(&tape);
1105     }
1106     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1107              is_string_suffix(cheat_input, ":ft"))
1108     {
1109       /* fix single-player tapes that contain player input for more than one
1110          player (due to a bug in 3.3.1.2 and earlier versions), which results
1111          in playing levels with more than one player in multi-player mode,
1112          even though the tape was originally recorded in single-player mode */
1113
1114       /* remove player input actions for all players but the first one */
1115       for (i = 1; i < MAX_PLAYERS; i++)
1116         tape.player_participates[i] = FALSE;
1117
1118       tape.changed = TRUE;
1119     }
1120     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1121              is_string_suffix(cheat_input, ":snl"))
1122     {
1123       SaveNativeLevel(&level);
1124     }
1125   }
1126   else if (game_status == GAME_MODE_PLAYING)
1127   {
1128 #ifdef DEBUG
1129     if (is_string_suffix(cheat_input, ".q"))
1130       DEBUG_SetMaximumDynamite();
1131 #endif
1132   }
1133   else if (game_status == GAME_MODE_EDITOR)
1134   {
1135     if (is_string_suffix(cheat_input, ":dump-brush") ||
1136         is_string_suffix(cheat_input, ":DB"))
1137     {
1138       DumpBrush();
1139     }
1140     else if (is_string_suffix(cheat_input, ":DDB"))
1141     {
1142       DumpBrush_Small();
1143     }
1144   }
1145 }
1146
1147 void HandleKey(Key key, int key_status)
1148 {
1149   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1150   static struct SetupKeyboardInfo ski;
1151   static struct SetupShortcutInfo ssi;
1152   static struct
1153   {
1154     Key *key_custom;
1155     Key *key_snap;
1156     Key key_default;
1157     byte action;
1158   } key_info[] =
1159   {
1160     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1161     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1162     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1163     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1164     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1165     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1166   };
1167   int joy = 0;
1168   int i;
1169
1170   if (game_status == GAME_MODE_PLAYING)
1171   {
1172     /* only needed for single-step tape recording mode */
1173     static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1174     static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1175     static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1176     static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1177     int pnr;
1178
1179     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1180     {
1181       byte key_action = 0;
1182
1183       if (setup.input[pnr].use_joystick)
1184         continue;
1185
1186       ski = setup.input[pnr].key;
1187
1188       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1189         if (key == *key_info[i].key_custom)
1190           key_action |= key_info[i].action;
1191
1192       /* use combined snap+direction keys for the first player only */
1193       if (pnr == 0)
1194       {
1195         ssi = setup.shortcut;
1196
1197         for (i = 0; i < NUM_DIRECTIONS; i++)
1198           if (key == *key_info[i].key_snap)
1199             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1200       }
1201
1202       /* clear delayed snap and drop actions in single step mode (see below) */
1203       if (tape.single_step)
1204       {
1205         if (clear_snap_button[pnr])
1206         {
1207           stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1208           clear_snap_button[pnr] = FALSE;
1209         }
1210
1211         if (clear_drop_button[pnr])
1212         {
1213           stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1214           clear_drop_button[pnr] = FALSE;
1215         }
1216       }
1217
1218       if (key_status == KEY_PRESSED)
1219         stored_player[pnr].action |= key_action;
1220       else
1221         stored_player[pnr].action &= ~key_action;
1222
1223       if (tape.single_step && tape.recording && tape.pausing)
1224       {
1225         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1226         {
1227           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1228
1229           /* if snap key already pressed, don't snap when releasing (below) */
1230           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1231             element_snapped[pnr] = TRUE;
1232
1233           /* if drop key already pressed, don't drop when releasing (below) */
1234           if (stored_player[pnr].action & KEY_BUTTON_DROP)
1235             element_dropped[pnr] = TRUE;
1236         }
1237         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1238         {
1239           if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1240               level.game_engine_type == GAME_ENGINE_TYPE_SP)
1241           {
1242
1243             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1244                 getRedDiskReleaseFlag_SP() == 0)
1245               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1246
1247             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1248           }
1249         }
1250         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1251         {
1252           if (key_action & KEY_BUTTON_SNAP)
1253           {
1254             /* if snap key was released without moving (see above), snap now */
1255             if (!element_snapped[pnr])
1256             {
1257               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1258
1259               stored_player[pnr].action |= KEY_BUTTON_SNAP;
1260
1261               /* clear delayed snap button on next event */
1262               clear_snap_button[pnr] = TRUE;
1263             }
1264
1265             element_snapped[pnr] = FALSE;
1266           }
1267
1268           if (key_action & KEY_BUTTON_DROP &&
1269               level.game_engine_type == GAME_ENGINE_TYPE_RND)
1270           {
1271             /* if drop key was released without moving (see above), drop now */
1272             if (!element_dropped[pnr])
1273             {
1274               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1275
1276               if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1277                   getRedDiskReleaseFlag_SP() != 0)
1278                 stored_player[pnr].action |= KEY_BUTTON_DROP;
1279
1280               /* clear delayed drop button on next event */
1281               clear_drop_button[pnr] = TRUE;
1282             }
1283
1284             element_dropped[pnr] = FALSE;
1285           }
1286         }
1287       }
1288       else if (tape.recording && tape.pausing)
1289       {
1290         /* prevent key release events from un-pausing a paused game */
1291         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1292           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1293       }
1294     }
1295   }
1296   else
1297   {
1298     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1299       if (key == key_info[i].key_default)
1300         joy |= key_info[i].action;
1301   }
1302
1303   if (joy)
1304   {
1305     if (key_status == KEY_PRESSED)
1306       key_joystick_mapping |= joy;
1307     else
1308       key_joystick_mapping &= ~joy;
1309
1310     HandleJoystick();
1311   }
1312
1313   if (game_status != GAME_MODE_PLAYING)
1314     key_joystick_mapping = 0;
1315
1316   if (key_status == KEY_RELEASED)
1317     return;
1318
1319   if ((key == KSYM_F11 ||
1320        ((key == KSYM_Return ||
1321          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1322       video.fullscreen_available)
1323   {
1324     setup.fullscreen = !setup.fullscreen;
1325
1326     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1327
1328     if (game_status == GAME_MODE_SETUP)
1329       RedrawSetupScreenAfterFullscreenToggle();
1330
1331     return;
1332   }
1333
1334   if ((key == KSYM_minus ||
1335        key == KSYM_plus ||
1336        key == KSYM_0) &&
1337       ((GetKeyModState() & KMOD_Control) ||
1338        (GetKeyModState() & KMOD_Alt)) &&
1339       video.window_scaling_available &&
1340       !video.fullscreen_enabled)
1341   {
1342     if (key == KSYM_0)
1343       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1344     else
1345       setup.window_scaling_percent +=
1346         (key == KSYM_minus ? -1 : +1) * STEP_WINDOW_SCALING_PERCENT;
1347
1348     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1349       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1350     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1351       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1352
1353     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1354
1355     if (game_status == GAME_MODE_SETUP)
1356       RedrawSetupScreenAfterFullscreenToggle();
1357
1358     return;
1359   }
1360
1361   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1362       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1363   {
1364     GameEnd();
1365
1366     return;
1367   }
1368
1369   if (game_status == GAME_MODE_MAIN &&
1370       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1371   {
1372     StartGameActions(options.network, setup.autorecord, level.random_seed);
1373
1374     return;
1375   }
1376
1377   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1378   {
1379     if (key == setup.shortcut.save_game)
1380       TapeQuickSave();
1381     else if (key == setup.shortcut.load_game)
1382       TapeQuickLoad();
1383     else if (key == setup.shortcut.toggle_pause)
1384       TapeTogglePause(TAPE_TOGGLE_MANUAL);
1385
1386     HandleTapeButtonKeys(key);
1387     HandleSoundButtonKeys(key);
1388   }
1389
1390   if (game_status == GAME_MODE_PLAYING && !network_playing)
1391   {
1392     int centered_player_nr_next = -999;
1393
1394     if (key == setup.shortcut.focus_player_all)
1395       centered_player_nr_next = -1;
1396     else
1397       for (i = 0; i < MAX_PLAYERS; i++)
1398         if (key == setup.shortcut.focus_player[i])
1399           centered_player_nr_next = i;
1400
1401     if (centered_player_nr_next != -999)
1402     {
1403       game.centered_player_nr_next = centered_player_nr_next;
1404       game.set_centered_player = TRUE;
1405
1406       if (tape.recording)
1407       {
1408         tape.centered_player_nr_next = game.centered_player_nr_next;
1409         tape.set_centered_player = TRUE;
1410       }
1411     }
1412   }
1413
1414   HandleKeysSpecial(key);
1415
1416   if (HandleGadgetsKeyInput(key))
1417   {
1418     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1419       key = KSYM_UNDEFINED;
1420   }
1421
1422   switch (game_status)
1423   {
1424     case GAME_MODE_PSEUDO_TYPENAME:
1425       HandleTypeName(0, key);
1426       break;
1427
1428     case GAME_MODE_TITLE:
1429     case GAME_MODE_MAIN:
1430     case GAME_MODE_LEVELS:
1431     case GAME_MODE_LEVELNR:
1432     case GAME_MODE_SETUP:
1433     case GAME_MODE_INFO:
1434     case GAME_MODE_SCORES:
1435       switch (key)
1436       {
1437         case KSYM_space:
1438         case KSYM_Return:
1439           if (game_status == GAME_MODE_TITLE)
1440             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1441           else if (game_status == GAME_MODE_MAIN)
1442             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1443           else if (game_status == GAME_MODE_LEVELS)
1444             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1445           else if (game_status == GAME_MODE_LEVELNR)
1446             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1447           else if (game_status == GAME_MODE_SETUP)
1448             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1449           else if (game_status == GAME_MODE_INFO)
1450             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1451           else if (game_status == GAME_MODE_SCORES)
1452             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1453           break;
1454
1455         case KSYM_Escape:
1456           if (game_status != GAME_MODE_MAIN)
1457             FadeSkipNextFadeIn();
1458
1459           if (game_status == GAME_MODE_TITLE)
1460             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1461           else if (game_status == GAME_MODE_LEVELS)
1462             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1463           else if (game_status == GAME_MODE_LEVELNR)
1464             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1465           else if (game_status == GAME_MODE_SETUP)
1466             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1467           else if (game_status == GAME_MODE_INFO)
1468             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1469           else if (game_status == GAME_MODE_SCORES)
1470             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1471           break;
1472
1473         case KSYM_Page_Up:
1474           if (game_status == GAME_MODE_LEVELS)
1475             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1476           else if (game_status == GAME_MODE_LEVELNR)
1477             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1478           else if (game_status == GAME_MODE_SETUP)
1479             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1480           else if (game_status == GAME_MODE_INFO)
1481             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1482           else if (game_status == GAME_MODE_SCORES)
1483             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1484           break;
1485
1486         case KSYM_Page_Down:
1487           if (game_status == GAME_MODE_LEVELS)
1488             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1489           else if (game_status == GAME_MODE_LEVELNR)
1490             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1491           else if (game_status == GAME_MODE_SETUP)
1492             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1493           else if (game_status == GAME_MODE_INFO)
1494             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1495           else if (game_status == GAME_MODE_SCORES)
1496             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1497           break;
1498
1499 #ifdef DEBUG
1500         case KSYM_0:
1501           GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
1502           break;
1503
1504         case KSYM_b:
1505           setup.sp_show_border_elements = !setup.sp_show_border_elements;
1506           printf("Supaplex border elements %s\n",
1507                  setup.sp_show_border_elements ? "enabled" : "disabled");
1508           break;
1509 #endif
1510
1511         default:
1512           break;
1513       }
1514       break;
1515
1516     case GAME_MODE_EDITOR:
1517       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1518         HandleLevelEditorKeyInput(key);
1519       break;
1520
1521     case GAME_MODE_PLAYING:
1522     {
1523       switch (key)
1524       {
1525         case KSYM_Escape:
1526           RequestQuitGame(setup.ask_on_escape);
1527           break;
1528
1529 #ifdef DEBUG
1530         case KSYM_0:
1531           if (key == KSYM_0)
1532           {
1533             if (GameFrameDelay == 500)
1534               GameFrameDelay = GAME_FRAME_DELAY;
1535             else
1536               GameFrameDelay = 500;
1537           }
1538           else
1539             GameFrameDelay = (key - KSYM_0) * 10;
1540           printf("Game speed == %d%% (%d ms delay between two frames)\n",
1541                  GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
1542           break;
1543
1544         case KSYM_d:
1545           if (options.debug)
1546           {
1547             options.debug = FALSE;
1548             printf("debug mode disabled\n");
1549           }
1550           else
1551           {
1552             options.debug = TRUE;
1553             printf("debug mode enabled\n");
1554           }
1555           break;
1556
1557 #if 0
1558         case KSYM_s:
1559           if (!global.fps_slowdown)
1560           {
1561             global.fps_slowdown = TRUE;
1562             global.fps_slowdown_factor = 2;
1563             printf("fps slowdown enabled -- display only every 2nd frame\n");
1564           }
1565           else if (global.fps_slowdown_factor == 2)
1566           {
1567             global.fps_slowdown_factor = 4;
1568             printf("fps slowdown enabled -- display only every 4th frame\n");
1569           }
1570           else
1571           {
1572             global.fps_slowdown = FALSE;
1573             global.fps_slowdown_factor = 1;
1574             printf("fps slowdown disabled\n");
1575           }
1576           break;
1577 #endif
1578
1579         case KSYM_v:
1580           printf("::: currently using game engine version %d\n",
1581                  game.engine_version);
1582           break;
1583 #endif
1584
1585         default:
1586           break;
1587       }
1588       break;
1589     }
1590
1591     default:
1592       if (key == KSYM_Escape)
1593       {
1594         game_status = GAME_MODE_MAIN;
1595         DrawMainMenu();
1596
1597         return;
1598       }
1599   }
1600 }
1601
1602 void HandleNoEvent()
1603 {
1604   // if (button_status && game_status != GAME_MODE_PLAYING)
1605   if (button_status && (game_status != GAME_MODE_PLAYING || tape.pausing))
1606   {
1607     HandleButton(0, 0, -button_status, button_status);
1608   }
1609   else
1610   {
1611     HandleJoystick();
1612   }
1613
1614 #if defined(NETWORK_AVALIABLE)
1615   if (options.network)
1616     HandleNetworking();
1617 #endif
1618
1619   switch (game_status)
1620   {
1621     case GAME_MODE_MAIN:
1622       DrawPreviewLevelAnimation();
1623       DoAnimation();
1624       break;
1625
1626     case GAME_MODE_LEVELS:
1627     case GAME_MODE_LEVELNR:
1628     case GAME_MODE_SETUP:
1629     case GAME_MODE_INFO:
1630     case GAME_MODE_SCORES:
1631       DoAnimation();
1632       break;
1633
1634     case GAME_MODE_EDITOR:
1635       HandleLevelEditorIdle();
1636       break;
1637
1638     default:
1639       break;
1640   }
1641 }
1642
1643 static int HandleJoystickForAllPlayers()
1644 {
1645   int i;
1646   int result = 0;
1647
1648   for (i = 0; i < MAX_PLAYERS; i++)
1649   {
1650     byte joy_action = 0;
1651
1652     /*
1653     if (!setup.input[i].use_joystick)
1654       continue;
1655       */
1656
1657     joy_action = Joystick(i);
1658     result |= joy_action;
1659
1660     if (!setup.input[i].use_joystick)
1661       continue;
1662
1663     stored_player[i].action = joy_action;
1664   }
1665
1666   return result;
1667 }
1668
1669 void HandleJoystick()
1670 {
1671   int joystick  = HandleJoystickForAllPlayers();
1672   int keyboard  = key_joystick_mapping;
1673   int joy       = (joystick | keyboard);
1674   int left      = joy & JOY_LEFT;
1675   int right     = joy & JOY_RIGHT;
1676   int up        = joy & JOY_UP;
1677   int down      = joy & JOY_DOWN;
1678   int button    = joy & JOY_BUTTON;
1679   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1680   int dx        = (left ? -1    : right ? 1     : 0);
1681   int dy        = (up   ? -1    : down  ? 1     : 0);
1682
1683   switch (game_status)
1684   {
1685     case GAME_MODE_TITLE:
1686     case GAME_MODE_MAIN:
1687     case GAME_MODE_LEVELS:
1688     case GAME_MODE_LEVELNR:
1689     case GAME_MODE_SETUP:
1690     case GAME_MODE_INFO:
1691     {
1692       static unsigned int joystickmove_delay = 0;
1693
1694       if (joystick && !button &&
1695           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1696         newbutton = dx = dy = 0;
1697
1698       if (game_status == GAME_MODE_TITLE)
1699         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1700       else if (game_status == GAME_MODE_MAIN)
1701         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1702       else if (game_status == GAME_MODE_LEVELS)
1703         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1704       else if (game_status == GAME_MODE_LEVELNR)
1705         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1706       else if (game_status == GAME_MODE_SETUP)
1707         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1708       else if (game_status == GAME_MODE_INFO)
1709         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1710       break;
1711     }
1712
1713     case GAME_MODE_SCORES:
1714       HandleHallOfFame(0, 0, dx, dy, !newbutton);
1715       break;
1716
1717     case GAME_MODE_PLAYING:
1718       if (tape.playing || keyboard)
1719         newbutton = ((joy & JOY_BUTTON) != 0);
1720
1721       if (newbutton && AllPlayersGone)
1722       {
1723         GameEnd();
1724
1725         return;
1726       }
1727
1728       break;
1729
1730     default:
1731       break;
1732   }
1733 }