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