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