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