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