improved smooth movement animation for BD engine (complete rewrite)
[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 //                  https://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 DelayCounter special_cursor_delay = { 1000 };
40 static boolean special_cursor_enabled = FALSE;
41
42 static boolean stop_processing_events = FALSE;
43 static boolean is_global_anim_event = FALSE;
44
45
46 // forward declarations for internal use
47 static void ClearTouchInfo(void);
48 static void HandleNoEvent(void);
49 static void HandleEventActions(void);
50
51
52 void SetPlayfieldMouseCursorEnabled(boolean enabled)
53 {
54   special_cursor_enabled = enabled;
55 }
56
57 // event filter to set mouse x/y position (for pointer class global animations)
58 // (this is especially required to ensure smooth global animation mouse pointer
59 // movement when the screen is updated without handling events; this can happen
60 // when drawing door/envelope request animations, for example)
61
62 int FilterMouseMotionEvents(void *userdata, Event *event)
63 {
64   if (event->type == EVENT_MOTIONNOTIFY)
65   {
66     int mouse_x = ((MotionEvent *)event)->x;
67     int mouse_y = ((MotionEvent *)event)->y;
68
69     UpdateRawMousePosition(mouse_x, mouse_y);
70   }
71
72   return 1;
73 }
74
75 // event filter especially needed for SDL event filtering due to
76 // delay problems with lots of mouse motion events when mouse button
77 // not pressed (X11 can handle this with 'PointerMotionHintMask')
78
79 // event filter addition for SDL2: as SDL2 does not have a function to enable
80 // or disable keyboard auto-repeat, filter repeated keyboard events instead
81
82 static int FilterEvents(const Event *event)
83 {
84   MotionEvent *motion;
85
86   // skip repeated key press events if keyboard auto-repeat is disabled
87   if (event->type == EVENT_KEYPRESS &&
88       event->key.repeat &&
89       !keyrepeat_status)
90     return 0;
91
92   if (event->type == EVENT_BUTTONPRESS ||
93       event->type == EVENT_BUTTONRELEASE)
94   {
95     ((ButtonEvent *)event)->x -= video.screen_xoffset;
96     ((ButtonEvent *)event)->y -= video.screen_yoffset;
97   }
98   else if (event->type == EVENT_MOTIONNOTIFY)
99   {
100     ((MotionEvent *)event)->x -= video.screen_xoffset;
101     ((MotionEvent *)event)->y -= video.screen_yoffset;
102   }
103
104   if (event->type == EVENT_BUTTONPRESS ||
105       event->type == EVENT_BUTTONRELEASE ||
106       event->type == EVENT_MOTIONNOTIFY)
107   {
108     // do not reset mouse cursor before all pending events have been processed
109     if (gfx.cursor_mode == cursor_mode_last &&
110         ((game_status == GAME_MODE_TITLE &&
111           gfx.cursor_mode == CURSOR_NONE) ||
112          (game_status == GAME_MODE_PLAYING &&
113           gfx.cursor_mode == CURSOR_PLAYFIELD)))
114     {
115       SetMouseCursor(CURSOR_DEFAULT);
116
117       ResetDelayCounter(&special_cursor_delay);
118
119       cursor_mode_last = CURSOR_DEFAULT;
120     }
121   }
122
123   // non-motion events are directly passed to event handler functions
124   if (event->type != EVENT_MOTIONNOTIFY)
125     return 1;
126
127   motion = (MotionEvent *)event;
128   cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
129                              motion->y >= SY && motion->y < SY + SYSIZE);
130
131   // set correct mouse x/y position (for pointer class global animations)
132   // (this is required in rare cases where the mouse x/y position calculated
133   // from raw values (to apply logical screen size scaling corrections) does
134   // not match the final mouse event x/y position -- this may happen because
135   // the SDL renderer's viewport position is internally represented as float,
136   // but only accessible as integer, which may lead to rounding errors)
137   gfx.mouse_x = motion->x;
138   gfx.mouse_y = motion->y;
139
140   // skip mouse motion events without pressed button outside level editor
141   if (button_status == MB_RELEASED &&
142       game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
143     return 0;
144
145   return 1;
146 }
147
148 // to prevent delay problems, skip mouse motion events if the very next
149 // event is also a mouse motion event (and therefore effectively only
150 // handling the last of a row of mouse motion events in the event queue)
151
152 static boolean SkipPressedMouseMotionEvent(const Event *event)
153 {
154   // nothing to do if the current event is not a mouse motion event
155   if (event->type != EVENT_MOTIONNOTIFY)
156     return FALSE;
157
158   // only skip motion events with pressed button outside the game
159   if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
160     return FALSE;
161
162   if (PendingEvent())
163   {
164     Event next_event;
165
166     PeekEvent(&next_event);
167
168     // if next event is also a mouse motion event, skip the current one
169     if (next_event.type == EVENT_MOTIONNOTIFY)
170       return TRUE;
171   }
172
173   return FALSE;
174 }
175
176 static boolean WaitValidEvent(Event *event)
177 {
178   WaitEvent(event);
179
180   if (!FilterEvents(event))
181     return FALSE;
182
183   if (SkipPressedMouseMotionEvent(event))
184     return FALSE;
185
186   return TRUE;
187 }
188
189 /* this is especially needed for event modifications for the Android target:
190    if mouse coordinates should be modified in the event filter function,
191    using a properly installed SDL event filter does not work, because in
192    the event filter, mouse coordinates in the event structure are still
193    physical pixel positions, not logical (scaled) screen positions, so this
194    has to be handled at a later stage in the event processing functions
195    (when device pixel positions are already converted to screen positions) */
196
197 boolean NextValidEvent(Event *event)
198 {
199   while (PendingEvent())
200     if (WaitValidEvent(event))
201       return TRUE;
202
203   return FALSE;
204 }
205
206 void StopProcessingEvents(void)
207 {
208   stop_processing_events = TRUE;
209 }
210
211 static void HandleEvents(void)
212 {
213   Event event;
214   DelayCounter event_frame_delay = { GAME_FRAME_DELAY };
215
216   ResetDelayCounter(&event_frame_delay);
217
218   stop_processing_events = FALSE;
219
220   while (NextValidEvent(&event))
221   {
222     int game_status_last = game_status;
223
224     switch (event.type)
225     {
226       case EVENT_BUTTONPRESS:
227       case EVENT_BUTTONRELEASE:
228         HandleButtonEvent((ButtonEvent *) &event);
229         break;
230
231       case EVENT_MOTIONNOTIFY:
232         HandleMotionEvent((MotionEvent *) &event);
233         break;
234
235       case EVENT_WHEELMOTION:
236         HandleWheelEvent((WheelEvent *) &event);
237         break;
238
239       case SDL_WINDOWEVENT:
240         HandleWindowEvent((WindowEvent *) &event);
241         break;
242
243       case EVENT_FINGERPRESS:
244       case EVENT_FINGERRELEASE:
245       case EVENT_FINGERMOTION:
246         HandleFingerEvent((FingerEvent *) &event);
247         break;
248
249       case EVENT_TEXTINPUT:
250         HandleTextEvent((TextEvent *) &event);
251         break;
252
253       case SDL_APP_WILLENTERBACKGROUND:
254       case SDL_APP_DIDENTERBACKGROUND:
255       case SDL_APP_WILLENTERFOREGROUND:
256       case SDL_APP_DIDENTERFOREGROUND:
257         HandlePauseResumeEvent((PauseResumeEvent *) &event);
258         break;
259
260       case EVENT_KEYPRESS:
261       case EVENT_KEYRELEASE:
262         HandleKeyEvent((KeyEvent *) &event);
263         break;
264
265       case EVENT_USER:
266         HandleUserEvent((UserEvent *) &event);
267         break;
268
269       default:
270         HandleOtherEvents(&event);
271         break;
272     }
273
274     // always handle events within delay period if game status has changed
275     if (game_status != game_status_last)
276       ResetDelayCounter(&event_frame_delay);
277
278     // do not handle events for longer than standard frame delay period
279     if (DelayReached(&event_frame_delay))
280       break;
281
282     // do not handle any further events if triggered by a special flag
283     if (stop_processing_events)
284       break;
285   }
286 }
287
288 void HandleOtherEvents(Event *event)
289 {
290   switch (event->type)
291   {
292     case SDL_CONTROLLERBUTTONDOWN:
293     case SDL_CONTROLLERBUTTONUP:
294       // for any game controller button event, disable overlay buttons
295       SetOverlayEnabled(FALSE);
296
297       HandleSpecialGameControllerButtons(event);
298
299       // FALL THROUGH
300     case SDL_CONTROLLERDEVICEADDED:
301     case SDL_CONTROLLERDEVICEREMOVED:
302     case SDL_CONTROLLERAXISMOTION:
303     case SDL_JOYAXISMOTION:
304     case SDL_JOYBUTTONDOWN:
305     case SDL_JOYBUTTONUP:
306       HandleJoystickEvent(event);
307       break;
308
309     case SDL_DROPBEGIN:
310     case SDL_DROPCOMPLETE:
311     case SDL_DROPFILE:
312     case SDL_DROPTEXT:
313       HandleDropEvent(event);
314       break;
315
316     case EVENT_QUIT:
317       CloseAllAndExit(0);
318       break;
319
320     default:
321       break;
322   }
323 }
324
325 static void HandleMouseCursor(void)
326 {
327   if (game_status == GAME_MODE_TITLE)
328   {
329     // when showing title screens, hide mouse pointer (if not moved)
330
331     if (gfx.cursor_mode != CURSOR_NONE &&
332         DelayReached(&special_cursor_delay))
333     {
334       SetMouseCursor(CURSOR_NONE);
335     }
336   }
337   else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
338                                                 tape.single_step))
339   {
340     // when playing, display a special mouse pointer inside the playfield
341
342     // display normal pointer if mouse pressed
343     if (button_status != MB_RELEASED)
344       ResetDelayCounter(&special_cursor_delay);
345
346     if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
347         cursor_inside_playfield &&
348         special_cursor_enabled &&
349         DelayReached(&special_cursor_delay))
350     {
351       SetMouseCursor(CURSOR_PLAYFIELD);
352     }
353   }
354   else if (gfx.cursor_mode != CURSOR_DEFAULT)
355   {
356     SetMouseCursor(CURSOR_DEFAULT);
357   }
358
359   // this is set after all pending events have been processed
360   cursor_mode_last = gfx.cursor_mode;
361 }
362
363 void EventLoop(void)
364 {
365   while (1)
366   {
367     if (PendingEvent())
368       HandleEvents();
369     else
370       HandleNoEvent();
371
372     // execute event related actions after pending events have been processed
373     HandleEventActions();
374
375     // don't use all CPU time when idle; the main loop while playing
376     // has its own synchronization and is CPU friendly, too
377
378     if (game_status == GAME_MODE_PLAYING)
379       HandleGameActions();
380
381     // always copy backbuffer to visible screen for every video frame
382     BackToFront();
383
384     // reset video frame delay to default (may change again while playing)
385     SetVideoFrameDelay(MenuFrameDelay);
386
387     if (game_status == GAME_MODE_QUIT)
388       return;
389   }
390 }
391
392 void ClearAutoRepeatKeyEvents(void)
393 {
394   while (PendingEvent())
395   {
396     Event next_event;
397
398     PeekEvent(&next_event);
399
400     // if event is repeated key press event, remove it from event queue
401     if (next_event.type == EVENT_KEYPRESS &&
402         next_event.key.repeat)
403       WaitEvent(&next_event);
404     else
405       break;
406   }
407 }
408
409 void ClearEventQueue(void)
410 {
411   Event event;
412
413   while (NextValidEvent(&event))
414   {
415     switch (event.type)
416     {
417       case EVENT_BUTTONRELEASE:
418         button_status = MB_RELEASED;
419         break;
420
421       case EVENT_FINGERRELEASE:
422       case EVENT_KEYRELEASE:
423         ClearPlayerAction();
424         break;
425
426       case SDL_CONTROLLERBUTTONUP:
427         HandleJoystickEvent(&event);
428         ClearPlayerAction();
429         break;
430
431       default:
432         HandleOtherEvents(&event);
433         break;
434     }
435   }
436 }
437
438 static void ClearPlayerMouseAction(void)
439 {
440   local_player->mouse_action.lx = 0;
441   local_player->mouse_action.ly = 0;
442   local_player->mouse_action.button = 0;
443 }
444
445 void ClearPlayerAction(void)
446 {
447   int i;
448
449   // simulate key release events for still pressed keys
450   key_joystick_mapping = 0;
451   for (i = 0; i < MAX_PLAYERS; i++)
452   {
453     stored_player[i].action = 0;
454     stored_player[i].snap_action = 0;
455   }
456
457   // simulate finger release events for still pressed virtual buttons
458   overlay.grid_button_action = JOY_NO_ACTION;
459
460   ClearTouchInfo();
461   ClearJoystickState();
462   ClearPlayerMouseAction();
463 }
464
465 static void SetPlayerMouseAction(int mx, int my, int button)
466 {
467   int lx = getLevelFromScreenX(mx);
468   int ly = getLevelFromScreenY(my);
469   int new_button = (!local_player->mouse_action.button && button);
470
471   if (local_player->mouse_action.button_hint)
472     button = local_player->mouse_action.button_hint;
473
474   ClearPlayerMouseAction();
475
476   if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
477     return;
478
479   local_player->mouse_action.lx = lx;
480   local_player->mouse_action.ly = ly;
481   local_player->mouse_action.button = button;
482
483   if (tape.recording && tape.pausing && tape.use_mouse_actions)
484   {
485     // un-pause a paused game only if mouse button was newly pressed down
486     if (new_button)
487       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
488   }
489
490   SetTileCursorXY(lx, ly);
491 }
492
493 static Key GetKeyFromGridButton(int grid_button)
494 {
495   return (grid_button == CHAR_GRID_BUTTON_LEFT  ? setup.input[0].key.left :
496           grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
497           grid_button == CHAR_GRID_BUTTON_UP    ? setup.input[0].key.up :
498           grid_button == CHAR_GRID_BUTTON_DOWN  ? setup.input[0].key.down :
499           grid_button == CHAR_GRID_BUTTON_SNAP  ? setup.input[0].key.snap :
500           grid_button == CHAR_GRID_BUTTON_DROP  ? setup.input[0].key.drop :
501           KSYM_UNDEFINED);
502 }
503
504 #if defined(PLATFORM_ANDROID)
505 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
506 {
507   float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
508   float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
509   int x = touch_x * overlay.grid_xsize;
510   int y = touch_y * overlay.grid_ysize;
511   int grid_button = overlay.grid_button[x][y];
512   Key key = GetKeyFromGridButton(grid_button);
513   int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
514
515   return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
516 }
517 #endif
518
519 void HandleButtonEvent(ButtonEvent *event)
520 {
521 #if DEBUG_EVENTS_BUTTON
522   Debug("event:button", "button %d %s, x/y %d/%d\n",
523         event->button,
524         event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
525         event->x, event->y);
526 #endif
527
528   // for any mouse button event, disable playfield tile cursor
529   SetTileCursorEnabled(FALSE);
530
531   // for any mouse button event, disable playfield mouse cursor
532   if (cursor_inside_playfield)
533     SetPlayfieldMouseCursorEnabled(FALSE);
534
535 #if defined(HAS_SCREEN_KEYBOARD)
536   if (video.shifted_up)
537     event->y += video.shifted_up_pos;
538 #endif
539
540   motion_status = FALSE;
541
542   if (event->type == EVENT_BUTTONPRESS)
543     button_status = event->button;
544   else
545     button_status = MB_RELEASED;
546
547   HandleButton(event->x, event->y, button_status, event->button);
548 }
549
550 void HandleMotionEvent(MotionEvent *event)
551 {
552   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
553     return;
554
555   motion_status = TRUE;
556
557 #if DEBUG_EVENTS_MOTION
558   Debug("event:motion", "button %d moved, x/y %d/%d\n",
559         button_status, event->x, event->y);
560 #endif
561
562   HandleButton(event->x, event->y, button_status, button_status);
563 }
564
565 void HandleWheelEvent(WheelEvent *event)
566 {
567   int button_nr;
568
569 #if DEBUG_EVENTS_WHEEL
570 #if 1
571   Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
572         event->which, event->x, event->y);
573 #else
574   // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
575   Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
576         event->which, event->x, event->y,
577         (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
578          "SDL_MOUSEWHEEL_FLIPPED"));
579 #endif
580 #endif
581
582   button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
583                event->x > 0 ? MB_WHEEL_RIGHT :
584                event->y < 0 ? MB_WHEEL_DOWN :
585                event->y > 0 ? MB_WHEEL_UP : 0);
586
587 #if defined(PLATFORM_WINDOWS) || defined(PLATFORM_MAC)
588   // accelerated mouse wheel available on Mac and Windows
589   wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
590 #else
591   // no accelerated mouse wheel available on Unix/Linux
592   wheel_steps = DEFAULT_WHEEL_STEPS;
593 #endif
594
595   motion_status = FALSE;
596
597   button_status = button_nr;
598   HandleButton(0, 0, button_status, -button_nr);
599
600   button_status = MB_RELEASED;
601   HandleButton(0, 0, button_status, -button_nr);
602 }
603
604 void HandleWindowEvent(WindowEvent *event)
605 {
606 #if DEBUG_EVENTS_WINDOW
607   int subtype = event->event;
608
609   char *event_name =
610     (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
611      subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
612      subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
613      subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
614      subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
615      subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
616      subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
617      subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
618      subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
619      subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
620      subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
621      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
622      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
623      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
624      subtype == SDL_WINDOWEVENT_TAKE_FOCUS ? "SDL_WINDOWEVENT_TAKE_FOCUS" :
625      subtype == SDL_WINDOWEVENT_HIT_TEST ? "SDL_WINDOWEVENT_HIT_TEST" :
626      "(UNKNOWN)");
627
628   Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
629         event_name, event->data1, event->data2);
630 #endif
631
632 #if 0
633   // (not needed, as the screen gets redrawn every 20 ms anyway)
634   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
635       event->event == SDL_WINDOWEVENT_RESIZED ||
636       event->event == SDL_WINDOWEVENT_EXPOSED)
637     SDLRedrawWindow();
638 #endif
639
640   if (event->event == SDL_WINDOWEVENT_RESIZED)
641   {
642     if (!video.fullscreen_enabled)
643     {
644       int new_window_width  = event->data1;
645       int new_window_height = event->data2;
646
647       // if window size has changed after resizing, calculate new scaling factor
648       if (new_window_width  != video.window_width ||
649           new_window_height != video.window_height)
650       {
651         int new_xpercent = 100.0 * new_window_width  / video.screen_width  + .5;
652         int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
653
654         // (extreme window scaling allowed, but cannot be saved permanently)
655         video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
656         setup.window_scaling_percent =
657           MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
658               MAX_WINDOW_SCALING_PERCENT);
659
660         video.window_width  = new_window_width;
661         video.window_height = new_window_height;
662
663         if (game_status == GAME_MODE_SETUP)
664           RedrawSetupScreenAfterFullscreenToggle();
665
666         UpdateMousePosition();
667
668         SetWindowTitle();
669       }
670     }
671 #if defined(PLATFORM_ANDROID)
672     else
673     {
674       int new_display_width  = event->data1;
675       int new_display_height = event->data2;
676
677       // if fullscreen display size has changed, device has been rotated
678       if (new_display_width  != video.display_width ||
679           new_display_height != video.display_height)
680       {
681         int nr = GRID_ACTIVE_NR();      // previous screen orientation
682
683         video.display_width  = new_display_width;
684         video.display_height = new_display_height;
685
686         SDLSetScreenProperties();
687         SetGadgetsPosition_OverlayTouchButtons();
688
689         // check if screen orientation has changed (should always be true here)
690         if (nr != GRID_ACTIVE_NR())
691         {
692           if (game_status == GAME_MODE_SETUP)
693             RedrawSetupScreenAfterScreenRotation(nr);
694
695           SetOverlayGridSizeAndButtons();
696         }
697       }
698     }
699 #endif
700   }
701 }
702
703 #define NUM_TOUCH_FINGERS               3
704
705 static struct
706 {
707   boolean touched;
708   SDL_FingerID finger_id;
709   int counter;
710   Key key;
711   byte action;
712 } touch_info[NUM_TOUCH_FINGERS];
713
714 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
715                          Key key, byte action)
716 {
717   touch_info[pos].touched = (action != JOY_NO_ACTION);
718   touch_info[pos].finger_id = finger_id;
719   touch_info[pos].counter = counter;
720   touch_info[pos].key = key;
721   touch_info[pos].action = action;
722 }
723
724 static void ClearTouchInfo(void)
725 {
726   int i;
727
728   for (i = 0; i < NUM_TOUCH_FINGERS; i++)
729     SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
730 }
731
732 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
733 {
734   int x = event->x * overlay.grid_xsize;
735   int y = event->y * overlay.grid_ysize;
736   int grid_button = overlay.grid_button[x][y];
737   int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
738   Key key = GetKeyFromGridButton(grid_button);
739   int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
740                     KEY_PRESSED);
741   char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
742                            "KEY_PRESSED");
743   int i;
744
745   // for any touch input event, enable overlay buttons (if activated)
746   SetOverlayEnabled(TRUE);
747
748   Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
749         getKeyNameFromKey(key), key_status_name, event->fingerId);
750
751   if (key_status == KEY_PRESSED)
752     overlay.grid_button_action |= grid_button_action;
753   else
754     overlay.grid_button_action &= ~grid_button_action;
755
756   // check if we already know this touch event's finger id
757   for (i = 0; i < NUM_TOUCH_FINGERS; i++)
758   {
759     if (touch_info[i].touched &&
760         touch_info[i].finger_id == event->fingerId)
761     {
762       // Debug("event:finger", "MARK 1: %d", i);
763
764       break;
765     }
766   }
767
768   if (i >= NUM_TOUCH_FINGERS)
769   {
770     if (key_status == KEY_PRESSED)
771     {
772       int oldest_pos = 0, oldest_counter = touch_info[0].counter;
773
774       // unknown finger id -- get new, empty slot, if available
775       for (i = 0; i < NUM_TOUCH_FINGERS; i++)
776       {
777         if (touch_info[i].counter < oldest_counter)
778         {
779           oldest_pos = i;
780           oldest_counter = touch_info[i].counter;
781
782           // Debug("event:finger", "MARK 2: %d", i);
783         }
784
785         if (!touch_info[i].touched)
786         {
787           // Debug("event:finger", "MARK 3: %d", i);
788
789           break;
790         }
791       }
792
793       if (i >= NUM_TOUCH_FINGERS)
794       {
795         // all slots allocated -- use oldest slot
796         i = oldest_pos;
797
798         // Debug("event:finger", "MARK 4: %d", i);
799       }
800     }
801     else
802     {
803       // release of previously unknown key (should not happen)
804
805       if (key != KSYM_UNDEFINED)
806       {
807         HandleKey(key, KEY_RELEASED);
808
809         Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
810               getKeyNameFromKey(key), "KEY_RELEASED", i);
811       }
812     }
813   }
814
815   if (i < NUM_TOUCH_FINGERS)
816   {
817     if (key_status == KEY_PRESSED)
818     {
819       if (touch_info[i].key != key)
820       {
821         if (touch_info[i].key != KSYM_UNDEFINED)
822         {
823           HandleKey(touch_info[i].key, KEY_RELEASED);
824
825           // undraw previous grid button when moving finger away
826           overlay.grid_button_action &= ~touch_info[i].action;
827
828           Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
829                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
830         }
831
832         if (key != KSYM_UNDEFINED)
833         {
834           HandleKey(key, KEY_PRESSED);
835
836           Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
837                 getKeyNameFromKey(key), "KEY_PRESSED", i);
838         }
839       }
840
841       SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
842     }
843     else
844     {
845       if (touch_info[i].key != KSYM_UNDEFINED)
846       {
847         HandleKey(touch_info[i].key, KEY_RELEASED);
848
849         Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
850               getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
851       }
852
853       SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
854     }
855   }
856 }
857
858 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
859 {
860   static Key motion_key_x = KSYM_UNDEFINED;
861   static Key motion_key_y = KSYM_UNDEFINED;
862   static Key button_key = KSYM_UNDEFINED;
863   static float motion_x1, motion_y1;
864   static float button_x1, button_y1;
865   static SDL_FingerID motion_id = -1;
866   static SDL_FingerID button_id = -1;
867   int move_trigger_distance_percent = setup.touch.move_distance;
868   int drop_trigger_distance_percent = setup.touch.drop_distance;
869   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
870   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
871   float event_x = event->x;
872   float event_y = event->y;
873
874   if (event->type == EVENT_FINGERPRESS)
875   {
876     if (event_x > 1.0 / 3.0)
877     {
878       // motion area
879
880       motion_id = event->fingerId;
881
882       motion_x1 = event_x;
883       motion_y1 = event_y;
884
885       motion_key_x = KSYM_UNDEFINED;
886       motion_key_y = KSYM_UNDEFINED;
887
888       Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
889     }
890     else
891     {
892       // button area
893
894       button_id = event->fingerId;
895
896       button_x1 = event_x;
897       button_y1 = event_y;
898
899       button_key = setup.input[0].key.snap;
900
901       HandleKey(button_key, KEY_PRESSED);
902
903       Debug("event:finger", "---------- SNAP STARTED ----------");
904     }
905   }
906   else if (event->type == EVENT_FINGERRELEASE)
907   {
908     if (event->fingerId == motion_id)
909     {
910       motion_id = -1;
911
912       if (motion_key_x != KSYM_UNDEFINED)
913         HandleKey(motion_key_x, KEY_RELEASED);
914       if (motion_key_y != KSYM_UNDEFINED)
915         HandleKey(motion_key_y, KEY_RELEASED);
916
917       motion_key_x = KSYM_UNDEFINED;
918       motion_key_y = KSYM_UNDEFINED;
919
920       Debug("event:finger", "---------- MOVE STOPPED ----------");
921     }
922     else if (event->fingerId == button_id)
923     {
924       button_id = -1;
925
926       if (button_key != KSYM_UNDEFINED)
927         HandleKey(button_key, KEY_RELEASED);
928
929       button_key = KSYM_UNDEFINED;
930
931       Debug("event:finger", "---------- SNAP STOPPED ----------");
932     }
933   }
934   else if (event->type == EVENT_FINGERMOTION)
935   {
936     if (event->fingerId == motion_id)
937     {
938       float distance_x = ABS(event_x - motion_x1);
939       float distance_y = ABS(event_y - motion_y1);
940       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
941                               event_x > motion_x1 ? setup.input[0].key.right :
942                               KSYM_UNDEFINED);
943       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
944                               event_y > motion_y1 ? setup.input[0].key.down :
945                               KSYM_UNDEFINED);
946
947       if (distance_x < move_trigger_distance / 2 ||
948           distance_x < distance_y)
949         new_motion_key_x = KSYM_UNDEFINED;
950
951       if (distance_y < move_trigger_distance / 2 ||
952           distance_y < distance_x)
953         new_motion_key_y = KSYM_UNDEFINED;
954
955       if (distance_x > move_trigger_distance ||
956           distance_y > move_trigger_distance)
957       {
958         if (new_motion_key_x != motion_key_x)
959         {
960           if (motion_key_x != KSYM_UNDEFINED)
961             HandleKey(motion_key_x, KEY_RELEASED);
962           if (new_motion_key_x != KSYM_UNDEFINED)
963             HandleKey(new_motion_key_x, KEY_PRESSED);
964         }
965
966         if (new_motion_key_y != motion_key_y)
967         {
968           if (motion_key_y != KSYM_UNDEFINED)
969             HandleKey(motion_key_y, KEY_RELEASED);
970           if (new_motion_key_y != KSYM_UNDEFINED)
971             HandleKey(new_motion_key_y, KEY_PRESSED);
972         }
973
974         motion_x1 = event_x;
975         motion_y1 = event_y;
976
977         motion_key_x = new_motion_key_x;
978         motion_key_y = new_motion_key_y;
979
980         Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
981       }
982     }
983     else if (event->fingerId == button_id)
984     {
985       float distance_x = ABS(event_x - button_x1);
986       float distance_y = ABS(event_y - button_y1);
987
988       if (distance_x < drop_trigger_distance / 2 &&
989           distance_y > drop_trigger_distance)
990       {
991         if (button_key == setup.input[0].key.snap)
992           HandleKey(button_key, KEY_RELEASED);
993
994         button_x1 = event_x;
995         button_y1 = event_y;
996
997         button_key = setup.input[0].key.drop;
998
999         HandleKey(button_key, KEY_PRESSED);
1000
1001         Debug("event:finger", "---------- DROP STARTED ----------");
1002       }
1003     }
1004   }
1005 }
1006
1007 void HandleFingerEvent(FingerEvent *event)
1008 {
1009 #if DEBUG_EVENTS_FINGER
1010   Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1011         event->type == EVENT_FINGERPRESS ? "pressed" :
1012         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1013         event->touchId,
1014         event->fingerId,
1015         event->x, event->y,
1016         event->dx, event->dy,
1017         event->pressure);
1018 #endif
1019
1020   runtime.uses_touch_device = TRUE;
1021
1022   if (game_status != GAME_MODE_PLAYING)
1023     return;
1024
1025   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1026   {
1027     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1028       local_player->mouse_action.button_hint =
1029         (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1030          event->x < 0.5                     ? MB_LEFTBUTTON  :
1031          event->x > 0.5                     ? MB_RIGHTBUTTON :
1032          MB_NOT_PRESSED);
1033
1034     return;
1035   }
1036
1037   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1038     HandleFingerEvent_VirtualButtons(event);
1039   else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1040     HandleFingerEvent_WipeGestures(event);
1041 }
1042
1043 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1044 {
1045   static int old_mx = 0, old_my = 0;
1046   static int last_button = MB_LEFTBUTTON;
1047   static boolean touched = FALSE;
1048   static boolean tapped = FALSE;
1049
1050   // screen tile was tapped (but finger not touching the screen anymore)
1051   // (this point will also be reached without receiving a touch event)
1052   if (tapped && !touched)
1053   {
1054     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1055
1056     tapped = FALSE;
1057   }
1058
1059   // stop here if this function was not triggered by a touch event
1060   if (button == -1)
1061     return;
1062
1063   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1064   {
1065     // finger started touching the screen
1066
1067     touched = TRUE;
1068     tapped = TRUE;
1069
1070     if (!motion_status)
1071     {
1072       old_mx = mx;
1073       old_my = my;
1074
1075       ClearPlayerMouseAction();
1076
1077       Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1078     }
1079   }
1080   else if (button == MB_RELEASED && touched)
1081   {
1082     // finger stopped touching the screen
1083
1084     touched = FALSE;
1085
1086     if (tapped)
1087       SetPlayerMouseAction(old_mx, old_my, last_button);
1088     else
1089       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1090
1091     Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1092   }
1093
1094   if (touched)
1095   {
1096     // finger moved while touching the screen
1097
1098     int old_x = getLevelFromScreenX(old_mx);
1099     int old_y = getLevelFromScreenY(old_my);
1100     int new_x = getLevelFromScreenX(mx);
1101     int new_y = getLevelFromScreenY(my);
1102
1103     if (new_x != old_x || new_y != old_y)
1104       tapped = FALSE;
1105
1106     if (new_x != old_x)
1107     {
1108       // finger moved left or right from (horizontal) starting position
1109
1110       int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1111
1112       SetPlayerMouseAction(old_mx, old_my, button_nr);
1113
1114       last_button = button_nr;
1115
1116       Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1117     }
1118     else
1119     {
1120       // finger stays at or returned to (horizontal) starting position
1121
1122       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1123
1124       Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1125     }
1126   }
1127 }
1128
1129 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1130 {
1131   static int old_mx = 0, old_my = 0;
1132   static int last_button = MB_LEFTBUTTON;
1133   static boolean touched = FALSE;
1134   static boolean tapped = FALSE;
1135
1136   // screen tile was tapped (but finger not touching the screen anymore)
1137   // (this point will also be reached without receiving a touch event)
1138   if (tapped && !touched)
1139   {
1140     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1141
1142     tapped = FALSE;
1143   }
1144
1145   // stop here if this function was not triggered by a touch event
1146   if (button == -1)
1147     return;
1148
1149   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1150   {
1151     // finger started touching the screen
1152
1153     touched = TRUE;
1154     tapped = TRUE;
1155
1156     if (!motion_status)
1157     {
1158       old_mx = mx;
1159       old_my = my;
1160
1161       ClearPlayerMouseAction();
1162
1163       Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1164     }
1165   }
1166   else if (button == MB_RELEASED && touched)
1167   {
1168     // finger stopped touching the screen
1169
1170     touched = FALSE;
1171
1172     if (tapped)
1173       SetPlayerMouseAction(old_mx, old_my, last_button);
1174     else
1175       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1176
1177     Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1178   }
1179
1180   if (touched)
1181   {
1182     // finger moved while touching the screen
1183
1184     int old_x = getLevelFromScreenX(old_mx);
1185     int old_y = getLevelFromScreenY(old_my);
1186     int new_x = getLevelFromScreenX(mx);
1187     int new_y = getLevelFromScreenY(my);
1188
1189     if (new_x != old_x || new_y != old_y)
1190     {
1191       // finger moved away from starting position
1192
1193       int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1194
1195       // quickly alternate between clicking and releasing for maximum speed
1196       if (FrameCounter % 2 == 0)
1197         button_nr = MB_RELEASED;
1198
1199       SetPlayerMouseAction(old_mx, old_my, button_nr);
1200
1201       if (button_nr)
1202         last_button = button_nr;
1203
1204       tapped = FALSE;
1205
1206       Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1207     }
1208     else
1209     {
1210       // finger stays at or returned to starting position
1211
1212       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1213
1214       Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1215     }
1216   }
1217 }
1218
1219 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1220 {
1221   static int old_mx = 0, old_my = 0;
1222   static Key motion_key_x = KSYM_UNDEFINED;
1223   static Key motion_key_y = KSYM_UNDEFINED;
1224   static boolean touched = FALSE;
1225   static boolean started_on_player = FALSE;
1226   static boolean player_is_dropping = FALSE;
1227   static int player_drop_count = 0;
1228   static int last_player_x = -1;
1229   static int last_player_y = -1;
1230
1231   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1232   {
1233     touched = TRUE;
1234
1235     old_mx = mx;
1236     old_my = my;
1237
1238     if (!motion_status)
1239     {
1240       started_on_player = FALSE;
1241       player_is_dropping = FALSE;
1242       player_drop_count = 0;
1243       last_player_x = -1;
1244       last_player_y = -1;
1245
1246       motion_key_x = KSYM_UNDEFINED;
1247       motion_key_y = KSYM_UNDEFINED;
1248
1249       Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1250     }
1251   }
1252   else if (button == MB_RELEASED && touched)
1253   {
1254     touched = FALSE;
1255
1256     old_mx = 0;
1257     old_my = 0;
1258
1259     if (motion_key_x != KSYM_UNDEFINED)
1260       HandleKey(motion_key_x, KEY_RELEASED);
1261     if (motion_key_y != KSYM_UNDEFINED)
1262       HandleKey(motion_key_y, KEY_RELEASED);
1263
1264     if (started_on_player)
1265     {
1266       if (player_is_dropping)
1267       {
1268         Debug("event:finger", "---------- DROP STOPPED ----------");
1269
1270         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1271       }
1272       else
1273       {
1274         Debug("event:finger", "---------- SNAP STOPPED ----------");
1275
1276         HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1277       }
1278     }
1279
1280     motion_key_x = KSYM_UNDEFINED;
1281     motion_key_y = KSYM_UNDEFINED;
1282
1283     Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1284   }
1285
1286   if (touched)
1287   {
1288     int src_x = local_player->jx;
1289     int src_y = local_player->jy;
1290     int dst_x = getLevelFromScreenX(old_mx);
1291     int dst_y = getLevelFromScreenY(old_my);
1292     int dx = dst_x - src_x;
1293     int dy = dst_y - src_y;
1294     Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1295                             dx > 0 ? setup.input[0].key.right :
1296                             KSYM_UNDEFINED);
1297     Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1298                             dy > 0 ? setup.input[0].key.down :
1299                             KSYM_UNDEFINED);
1300
1301     if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1302         (last_player_x != local_player->jx ||
1303          last_player_y != local_player->jy))
1304     {
1305       // in case of asymmetric diagonal movement, use "preferred" direction
1306
1307       int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1308
1309       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1310         game_em.ply[0]->last_move_dir = last_move_dir;
1311       else
1312         local_player->last_move_dir = last_move_dir;
1313
1314       // (required to prevent accidentally forcing direction for next movement)
1315       last_player_x = local_player->jx;
1316       last_player_y = local_player->jy;
1317     }
1318
1319     if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1320     {
1321       started_on_player = TRUE;
1322       player_drop_count = getPlayerInventorySize(0);
1323       player_is_dropping = (player_drop_count > 0);
1324
1325       if (player_is_dropping)
1326       {
1327         Debug("event:finger", "---------- DROP STARTED ----------");
1328
1329         HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1330       }
1331       else
1332       {
1333         Debug("event:finger", "---------- SNAP STARTED ----------");
1334
1335         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1336       }
1337     }
1338     else if (dx != 0 || dy != 0)
1339     {
1340       if (player_is_dropping &&
1341           player_drop_count == getPlayerInventorySize(0))
1342       {
1343         Debug("event:finger", "---------- DROP -> SNAP ----------");
1344
1345         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1346         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1347
1348         player_is_dropping = FALSE;
1349       }
1350     }
1351
1352     if (new_motion_key_x != motion_key_x)
1353     {
1354       Debug("event:finger", "---------- %s %s ----------",
1355             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1356             dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1357
1358       if (motion_key_x != KSYM_UNDEFINED)
1359         HandleKey(motion_key_x, KEY_RELEASED);
1360       if (new_motion_key_x != KSYM_UNDEFINED)
1361         HandleKey(new_motion_key_x, KEY_PRESSED);
1362     }
1363
1364     if (new_motion_key_y != motion_key_y)
1365     {
1366       Debug("event:finger", "---------- %s %s ----------",
1367             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1368             dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1369
1370       if (motion_key_y != KSYM_UNDEFINED)
1371         HandleKey(motion_key_y, KEY_RELEASED);
1372       if (new_motion_key_y != KSYM_UNDEFINED)
1373         HandleKey(new_motion_key_y, KEY_PRESSED);
1374     }
1375
1376     motion_key_x = new_motion_key_x;
1377     motion_key_y = new_motion_key_y;
1378   }
1379 }
1380
1381 static void HandleButtonOrFinger(int mx, int my, int button)
1382 {
1383   boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1384
1385   if (game_status != GAME_MODE_PLAYING)
1386     return;
1387
1388   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1389   {
1390     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1391       HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1392     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1393       HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1394     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1395       SetPlayerMouseAction(mx, my, button);     // special case
1396   }
1397   else
1398   {
1399     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1400       HandleButtonOrFinger_FollowFinger(mx, my, button);
1401     else if (game.use_mouse_actions && valid_mouse_event)
1402       SetPlayerMouseAction(mx, my, button);
1403   }
1404 }
1405
1406 static boolean checkTextInputKey(Key key)
1407 {
1408   // when playing, only handle raw key events and ignore text input
1409   if (game_status == GAME_MODE_PLAYING)
1410     return FALSE;
1411
1412   // if Shift or right Alt key is pressed, handle key as text input
1413   if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1414     return TRUE;
1415
1416   // ignore raw keys as text input when not in text input mode
1417   if (KSYM_RAW(key) && !textinput_status)
1418     return FALSE;
1419
1420   // else handle all printable keys as text input
1421   return KSYM_PRINTABLE(key);
1422 }
1423
1424 void HandleTextEvent(TextEvent *event)
1425 {
1426   char *text = event->text;
1427   Key key = getKeyFromKeyName(text);
1428
1429 #if DEBUG_EVENTS_TEXT
1430   Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1431         text,
1432         strlen(text),
1433         text[0], (int)(text[0]),
1434         key,
1435         getKeyNameFromKey(key),
1436         GetKeyModState());
1437 #endif
1438
1439   if (checkTextInputKey(key))
1440   {
1441     // process printable keys (with uppercase etc.) in text input mode
1442     HandleKey(key, KEY_PRESSED);
1443     HandleKey(key, KEY_RELEASED);
1444   }
1445 }
1446
1447 void HandlePauseResumeEvent(PauseResumeEvent *event)
1448 {
1449   if (event->type == SDL_APP_WILLENTERBACKGROUND)
1450   {
1451     Mix_PauseMusic();
1452   }
1453   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1454   {
1455     Mix_ResumeMusic();
1456   }
1457 }
1458
1459 void HandleKeyEvent(KeyEvent *event)
1460 {
1461   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1462   Key key = GetEventKey(event);
1463
1464 #if DEBUG_EVENTS_KEY
1465   Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1466         event->type == EVENT_KEYPRESS ? "pressed" : "released",
1467         event->keysym.scancode,
1468         event->keysym.sym,
1469         GetKeyModState(),
1470         key,
1471         getKeyNameFromKey(key));
1472 #endif
1473
1474 #if defined(PLATFORM_ANDROID)
1475   if (key == KSYM_Back)
1476   {
1477     // always map the "back" button to the "escape" key on Android devices
1478     key = KSYM_Escape;
1479   }
1480   else if (key == KSYM_Menu)
1481   {
1482     // the "menu" button can be used to toggle displaying virtual buttons
1483     if (key_status == KEY_PRESSED)
1484       SetOverlayEnabled(!GetOverlayEnabled());
1485   }
1486   else if (!textinput_status)
1487   {
1488     // for any other "real" key event, disable virtual buttons
1489     SetOverlayEnabled(FALSE);
1490
1491     // for any other "real" key event, disable overlay touch buttons
1492     runtime.uses_touch_device = FALSE;
1493   }
1494 #endif
1495
1496   HandleKeyModState(key, key_status);
1497
1498   // process all keys if not in text input mode or if non-printable keys
1499   if (!checkTextInputKey(key))
1500     HandleKey(key, key_status);
1501 }
1502
1503 static int HandleDropFileEvent(char *filename)
1504 {
1505   Debug("event:dropfile", "filename == '%s'", filename);
1506
1507   // check and extract dropped zip files into correct user data directory
1508   if (!strSuffixLower(filename, ".zip") &&
1509       !strPrefixLower(filename, "fd:"))
1510   {
1511     Warn("file '%s' not supported", filename);
1512
1513     return TREE_TYPE_UNDEFINED;
1514   }
1515
1516   TreeInfo *tree_node = NULL;
1517   int tree_type = GetZipFileTreeType(filename);
1518   char *directory = TREE_USERDIR(tree_type);
1519
1520   if (directory == NULL)
1521   {
1522     Warn("zip file '%s' has invalid content!", filename);
1523
1524     return TREE_TYPE_UNDEFINED;
1525   }
1526
1527   if (tree_type == TREE_TYPE_LEVEL_DIR &&
1528       game_status == GAME_MODE_LEVELS &&
1529       leveldir_current->node_parent != NULL)
1530   {
1531     // extract new level set next to currently selected level set
1532     tree_node = leveldir_current;
1533
1534     // get parent directory of currently selected level set directory
1535     directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1536
1537     // use private level directory instead of top-level package level directory
1538     if (strPrefix(directory, options.level_directory) &&
1539         strEqual(leveldir_current->node_parent->fullpath, "."))
1540       directory = getUserLevelDir(NULL);
1541   }
1542
1543   // extract level or artwork set from zip file to target directory
1544   char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1545
1546   if (top_dir == NULL)
1547   {
1548     // error message already issued by "ExtractZipFileIntoDirectory()"
1549
1550     return TREE_TYPE_UNDEFINED;
1551   }
1552
1553   // add extracted level or artwork set to tree info structure
1554   AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1555
1556   // force restart after adding level collection
1557   if (getTreeInfoFromIdentifier(TREE_FIRST_NODE(tree_type), top_dir) == NULL)
1558   {
1559     Request("Program must be restarted after adding a new level collection!",
1560             REQ_CONFIRM);
1561
1562     CloseAllAndExit(0);
1563   }
1564
1565   // update menu screen (and possibly change current level set)
1566   DrawScreenAfterAddingSet(top_dir, tree_type);
1567
1568   return tree_type;
1569 }
1570
1571 static void HandleDropTextEvent(char *text)
1572 {
1573   Debug("event:droptext", "text == '%s'", text);
1574 }
1575
1576 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1577                                     int num_artwork_sets_succeeded,
1578                                     int num_files_failed)
1579 {
1580   // only show request dialog if no other request dialog already active
1581   if (game.request_active)
1582     return;
1583
1584   // this case can happen with drag-and-drop with older SDL versions
1585   if (num_level_sets_succeeded == 0 &&
1586       num_artwork_sets_succeeded == 0 &&
1587       num_files_failed == 0)
1588     return;
1589
1590   char message[100];
1591
1592   if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1593   {
1594     char message_part1[50];
1595
1596     sprintf(message_part1, "New %s set%s added",
1597             (num_artwork_sets_succeeded == 0 ? "level" :
1598              num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1599             (num_level_sets_succeeded +
1600              num_artwork_sets_succeeded > 1 ? "s" : ""));
1601
1602     if (num_files_failed > 0)
1603       sprintf(message, "%s, but %d dropped file%s failed!",
1604               message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1605     else
1606       sprintf(message, "%s!", message_part1);
1607   }
1608   else if (num_files_failed > 0)
1609   {
1610     sprintf(message, "Failed to process dropped file%s!",
1611             num_files_failed > 1 ? "s" : "");
1612   }
1613
1614   Request(message, REQ_CONFIRM);
1615 }
1616
1617 void HandleDropEvent(Event *event)
1618 {
1619   Debug("event:drop", (event->type == SDL_DROPBEGIN     ? "SDL_DROPBEGIN" :
1620                        event->type == SDL_DROPFILE      ? "SDL_DROPFILE" :
1621                        event->type == SDL_DROPTEXT      ? "SDL_DROPTEXT" :
1622                        event->type == SDL_DROPCOMPLETE  ? "SDL_DROPCOMPLETE" :
1623                        "(unknown drop event type)"));
1624
1625   static boolean confirm_on_drop_complete = FALSE;
1626   static int num_level_sets_succeeded = 0;
1627   static int num_artwork_sets_succeeded = 0;
1628   static int num_files_failed = 0;
1629
1630   switch (event->type)
1631   {
1632     case SDL_DROPBEGIN:
1633     {
1634       confirm_on_drop_complete = TRUE;
1635       num_level_sets_succeeded = 0;
1636       num_artwork_sets_succeeded = 0;
1637       num_files_failed = 0;
1638
1639       break;
1640     }
1641
1642     case SDL_DROPFILE:
1643     {
1644       int tree_type = HandleDropFileEvent(event->drop.file);
1645
1646       if (tree_type == TREE_TYPE_LEVEL_DIR)
1647         num_level_sets_succeeded++;
1648       else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1649                tree_type == TREE_TYPE_SOUNDS_DIR ||
1650                tree_type == TREE_TYPE_MUSIC_DIR)
1651         num_artwork_sets_succeeded++;
1652       else
1653         num_files_failed++;
1654
1655       // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1656       if (!confirm_on_drop_complete)
1657       {
1658         // process all remaining events, including further SDL_DROPFILE events
1659         ClearEventQueue();
1660
1661         HandleDropCompleteEvent(num_level_sets_succeeded,
1662                                 num_artwork_sets_succeeded,
1663                                 num_files_failed);
1664
1665         num_level_sets_succeeded = 0;
1666         num_artwork_sets_succeeded = 0;
1667         num_files_failed = 0;
1668       }
1669
1670       break;
1671     }
1672
1673     case SDL_DROPTEXT:
1674     {
1675       HandleDropTextEvent(event->drop.file);
1676
1677       break;
1678     }
1679
1680     case SDL_DROPCOMPLETE:
1681     {
1682       HandleDropCompleteEvent(num_level_sets_succeeded,
1683                               num_artwork_sets_succeeded,
1684                               num_files_failed);
1685
1686       break;
1687     }
1688   }
1689
1690   if (event->drop.file != NULL)
1691     SDL_free(event->drop.file);
1692 }
1693
1694 void HandleUserEvent(UserEvent *event)
1695 {
1696   switch (event->code)
1697   {
1698     case USEREVENT_ANIM_DELAY_ACTION:
1699     case USEREVENT_ANIM_EVENT_ACTION:
1700       // execute action functions until matching action was found
1701       if (DoKeysymAction(event->value1) ||
1702           DoGadgetAction(event->value1) ||
1703           DoScreenAction(event->value1))
1704         return;
1705       break;
1706
1707     default:
1708       break;
1709   }
1710 }
1711
1712 void HandleButton(int mx, int my, int button, int button_nr)
1713 {
1714   static int old_mx = 0, old_my = 0;
1715   boolean button_hold = FALSE;
1716   boolean handle_gadgets = TRUE;
1717   int game_status_last = game_status;
1718
1719   if (button_nr < 0)
1720   {
1721     mx = old_mx;
1722     my = old_my;
1723     button_nr = -button_nr;
1724     button_hold = TRUE;
1725   }
1726   else
1727   {
1728     old_mx = mx;
1729     old_my = my;
1730   }
1731
1732 #if defined(PLATFORM_ANDROID)
1733   // when playing, only handle gadgets when using "follow finger" controls
1734   // or when using touch controls in combination with the MM game engine
1735   // or when using gadgets that do not overlap with virtual buttons
1736   // or when touch controls are disabled (e.g., with mouse-only levels)
1737   handle_gadgets =
1738     (game_status != GAME_MODE_PLAYING ||
1739      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1740      strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) ||
1741      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1742      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1743       !CheckVirtualButtonPressed(mx, my, button)));
1744
1745   // always recognize potentially releasing already pressed gadgets
1746   if (button == MB_RELEASED)
1747     handle_gadgets = TRUE;
1748
1749   // always recognize pressing or releasing overlay touch buttons
1750   if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1751     handle_gadgets = TRUE;
1752 #endif
1753
1754   if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1755   {
1756     // do not handle this button event anymore
1757     return;             // force mouse event not to be handled at all
1758   }
1759
1760   if (handle_gadgets && HandleGadgets(mx, my, button))
1761   {
1762     // do not handle this button event anymore with position on screen
1763     mx = my = -32;      // force mouse event to be outside screen tiles
1764
1765     // do not handle this button event anymore if game status has changed
1766     if (game_status != game_status_last)
1767       return;
1768   }
1769
1770   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1771     return;
1772
1773   // do not use scroll wheel button events for anything other than gadgets
1774   if (IS_WHEEL_BUTTON(button_nr))
1775     return;
1776
1777   switch (game_status)
1778   {
1779     case GAME_MODE_TITLE:
1780       HandleTitleScreen(mx, my, 0, 0, button);
1781       break;
1782
1783     case GAME_MODE_MAIN:
1784       HandleMainMenu(mx, my, 0, 0, button);
1785       break;
1786
1787     case GAME_MODE_PSEUDO_TYPENAME:
1788     case GAME_MODE_PSEUDO_TYPENAMES:
1789       HandleTypeName(KSYM_Return);
1790       break;
1791
1792     case GAME_MODE_NAMES:
1793       HandleChoosePlayerName(mx, my, 0, 0, button);
1794       break;
1795
1796     case GAME_MODE_LEVELS:
1797       HandleChooseLevelSet(mx, my, 0, 0, button);
1798       break;
1799
1800     case GAME_MODE_LEVELNR:
1801       HandleChooseLevelNr(mx, my, 0, 0, button);
1802       break;
1803
1804     case GAME_MODE_SCORES:
1805       HandleHallOfFame(mx, my, 0, 0, button);
1806       break;
1807
1808     case GAME_MODE_SCOREINFO:
1809       HandleScoreInfo(mx, my, 0, 0, button);
1810       break;
1811
1812     case GAME_MODE_EDITOR:
1813       HandleLevelEditorIdle();
1814       break;
1815
1816     case GAME_MODE_INFO:
1817       HandleInfoScreen(mx, my, 0, 0, button);
1818       break;
1819
1820     case GAME_MODE_SETUP:
1821       HandleSetupScreen(mx, my, 0, 0, button);
1822       break;
1823
1824     case GAME_MODE_PLAYING:
1825       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1826         HandleButtonOrFinger(mx, my, button);
1827       else
1828         SetPlayerMouseAction(mx, my, button);
1829
1830 #ifdef DEBUG
1831       if (button == MB_PRESSED && !motion_status && !button_hold &&
1832           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1833         DumpTileFromScreen(mx, my);
1834 #endif
1835
1836       break;
1837
1838     default:
1839       break;
1840   }
1841 }
1842
1843 #define MAX_CHEAT_INPUT_LEN     32
1844
1845 static void HandleKeysSpecial(Key key)
1846 {
1847   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1848   char letter = getCharFromKey(key);
1849   int cheat_input_len = strlen(cheat_input);
1850   int i;
1851
1852   if (letter == 0)
1853     return;
1854
1855   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1856   {
1857     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1858       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1859
1860     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1861   }
1862
1863   cheat_input[cheat_input_len++] = letter;
1864   cheat_input[cheat_input_len] = '\0';
1865
1866 #if DEBUG_EVENTS_KEY
1867   Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1868 #endif
1869
1870   if (game_status == GAME_MODE_MAIN)
1871   {
1872     if (strSuffix(cheat_input, ":insert-solution-tape") ||
1873         strSuffix(cheat_input, ":ist"))
1874     {
1875       InsertSolutionTape();
1876     }
1877     else if (strSuffix(cheat_input, ":play-solution-tape") ||
1878              strSuffix(cheat_input, ":pst"))
1879     {
1880       PlaySolutionTape();
1881     }
1882     else if (strSuffix(cheat_input, ":reload-graphics") ||
1883              strSuffix(cheat_input, ":rg"))
1884     {
1885       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1886       DrawMainMenu();
1887     }
1888     else if (strSuffix(cheat_input, ":reload-sounds") ||
1889              strSuffix(cheat_input, ":rs"))
1890     {
1891       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1892       DrawMainMenu();
1893     }
1894     else if (strSuffix(cheat_input, ":reload-music") ||
1895              strSuffix(cheat_input, ":rm"))
1896     {
1897       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1898       DrawMainMenu();
1899     }
1900     else if (strSuffix(cheat_input, ":reload-artwork") ||
1901              strSuffix(cheat_input, ":ra"))
1902     {
1903       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1904                           1 << ARTWORK_TYPE_SOUNDS |
1905                           1 << ARTWORK_TYPE_MUSIC);
1906       DrawMainMenu();
1907     }
1908     else if (strSuffix(cheat_input, ":dump-level") ||
1909              strSuffix(cheat_input, ":dl"))
1910     {
1911       DumpLevel(&level);
1912     }
1913     else if (strSuffix(cheat_input, ":dump-tape") ||
1914              strSuffix(cheat_input, ":dt"))
1915     {
1916       DumpTape(&tape);
1917     }
1918     else if (strSuffix(cheat_input, ":undo-tape") ||
1919              strSuffix(cheat_input, ":ut"))
1920     {
1921       UndoTape();
1922     }
1923     else if (strSuffix(cheat_input, ":fix-tape") ||
1924              strSuffix(cheat_input, ":ft"))
1925     {
1926       FixTape_ForceSinglePlayer();
1927     }
1928     else if (strSuffix(cheat_input, ":save-native-level") ||
1929              strSuffix(cheat_input, ":snl"))
1930     {
1931       SaveNativeLevel(&level);
1932     }
1933     else if (strSuffix(cheat_input, ":frames-per-second") ||
1934              strSuffix(cheat_input, ":fps"))
1935     {
1936       global.show_frames_per_second = !global.show_frames_per_second;
1937     }
1938     else if (strSuffix(cheat_input, ":xsn"))
1939     {
1940       tile_cursor.xsn_debug = TRUE;
1941     }
1942   }
1943   else if (game_status == GAME_MODE_PLAYING)
1944   {
1945 #ifdef DEBUG
1946     if (strSuffix(cheat_input, ".q"))
1947       DEBUG_SetMaximumDynamite();
1948 #endif
1949   }
1950   else if (game_status == GAME_MODE_EDITOR)
1951   {
1952     if (strSuffix(cheat_input, ":dump-brush") ||
1953         strSuffix(cheat_input, ":DB"))
1954     {
1955       DumpBrush();
1956     }
1957     else if (strSuffix(cheat_input, ":DDB"))
1958     {
1959       DumpBrush_Small();
1960     }
1961
1962     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1963     {
1964       if (letter == 'x')        // copy brush to clipboard (small size)
1965       {
1966         CopyBrushToClipboard_Small();
1967       }
1968       else if (letter == 'c')   // copy brush to clipboard (normal size)
1969       {
1970         CopyBrushToClipboard();
1971       }
1972       else if (letter == 'v')   // paste brush from Clipboard
1973       {
1974         CopyClipboardToBrush();
1975       }
1976       else if (letter == 'z')   // undo or redo last operation
1977       {
1978         if (GetKeyModState() & KMOD_Shift)
1979           RedoLevelEditorOperation();
1980         else
1981           UndoLevelEditorOperation();
1982       }
1983     }
1984   }
1985
1986   // special key shortcuts for all game modes
1987   if (strSuffix(cheat_input, ":dump-event-actions") ||
1988       strSuffix(cheat_input, ":dea") ||
1989       strSuffix(cheat_input, ":DEA"))
1990   {
1991     DumpGadgetIdentifiers();
1992     DumpScreenIdentifiers();
1993   }
1994 }
1995
1996 static boolean HandleKeysSpeed(Key key, int key_status)
1997 {
1998   if (game_status == GAME_MODE_PLAYING)
1999   {
2000     if (key == setup.shortcut.speed_fast ||
2001         key == setup.shortcut.speed_slow)
2002     {
2003       int speed_factor = 4;
2004
2005       GameFrameDelay = (key_status != KEY_PRESSED ? setup.game_frame_delay :
2006                         key == setup.shortcut.speed_fast ? setup.game_frame_delay / speed_factor :
2007                         key == setup.shortcut.speed_slow ? setup.game_frame_delay * speed_factor :
2008                         setup.game_frame_delay);
2009
2010       GameFrameDelay = MIN(MAX(1, GameFrameDelay), 1000);
2011
2012       SetVideoFrameDelay(GameFrameDelay);
2013
2014       return TRUE;
2015     }
2016   }
2017
2018   return FALSE;
2019 }
2020
2021 boolean HandleKeysDebug(Key key, int key_status)
2022 {
2023 #ifdef DEBUG
2024   int i;
2025
2026   if (key_status != KEY_PRESSED)
2027     return FALSE;
2028
2029   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
2030   {
2031     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
2032
2033     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
2034     {
2035       if (key == setup.debug.frame_delay_key[i] &&
2036           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
2037       {
2038         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
2039                           setup.debug.frame_delay[i] : setup.game_frame_delay);
2040
2041         if (!setup.debug.frame_delay_game_only)
2042           MenuFrameDelay = GameFrameDelay;
2043
2044         SetVideoFrameDelay(GameFrameDelay);
2045
2046         if (GameFrameDelay > ONE_SECOND_DELAY)
2047           Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
2048         else if (GameFrameDelay != 0)
2049           Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
2050                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2051                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2052         else
2053           Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2054
2055         return TRUE;
2056       }
2057     }
2058   }
2059
2060   if (game_status == GAME_MODE_PLAYING)
2061   {
2062     if (key == KSYM_d)
2063     {
2064       options.debug = !options.debug;
2065
2066       Debug("event:key:debug", "debug mode %s",
2067             (options.debug ? "enabled" : "disabled"));
2068
2069       return TRUE;
2070     }
2071     else if (key == KSYM_v)
2072     {
2073       Debug("event:key:debug", "currently using game engine version %d",
2074             game.engine_version);
2075
2076       return TRUE;
2077     }
2078   }
2079 #endif
2080
2081   return FALSE;
2082 }
2083
2084 void HandleKey(Key key, int key_status)
2085 {
2086   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2087   static boolean ignore_repeated_key = FALSE;
2088   static struct SetupKeyboardInfo ski;
2089   static struct SetupShortcutInfo ssi;
2090   static struct
2091   {
2092     Key *key_custom;
2093     Key *key_snap;
2094     Key key_default;
2095     byte action;
2096   } key_info[] =
2097   {
2098     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
2099     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
2100     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
2101     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
2102     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
2103     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
2104   };
2105   boolean game_key_pressed = FALSE;
2106   int joy = 0;
2107   int i;
2108
2109   // check if any game key is pressed (direction/snap/drop keys)
2110   if (game_status == GAME_MODE_PLAYING)
2111   {
2112     int pnr;
2113
2114     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2115     {
2116       ski = setup.input[pnr].key;
2117
2118       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2119         if (key == *key_info[i].key_custom)
2120           game_key_pressed = TRUE;
2121     }
2122
2123     ssi = setup.shortcut;
2124
2125     for (i = 0; i < NUM_DIRECTIONS; i++)
2126       if (key == *key_info[i].key_snap)
2127         game_key_pressed = TRUE;
2128   }
2129
2130   // only handle speed or debug keys if no game key is pressed
2131   if (!game_key_pressed)
2132   {
2133     if (HandleKeysSpeed(key, key_status))
2134       return;           // do not handle already processed keys again
2135
2136     if (HandleKeysDebug(key, key_status))
2137       return;           // do not handle already processed keys again
2138   }
2139
2140   // map special keys (media keys / remote control buttons) to default keys
2141   if (key == KSYM_PlayPause)
2142     key = KSYM_space;
2143   else if (key == KSYM_Select)
2144     key = KSYM_Return;
2145
2146   HandleSpecialGameControllerKeys(key, key_status);
2147
2148   if (game_status == GAME_MODE_PLAYING)
2149   {
2150     // only needed for single-step tape recording mode
2151     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2152     int pnr;
2153
2154     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2155     {
2156       byte key_action = 0;
2157       byte key_snap_action = 0;
2158
2159       if (setup.input[pnr].use_joystick)
2160         continue;
2161
2162       ski = setup.input[pnr].key;
2163
2164       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2165         if (key == *key_info[i].key_custom)
2166           key_action |= key_info[i].action;
2167
2168       // use combined snap+direction keys for the first player only
2169       if (pnr == 0)
2170       {
2171         ssi = setup.shortcut;
2172
2173         // also remember normal snap key when handling snap+direction keys
2174         key_snap_action |= key_action & JOY_BUTTON_SNAP;
2175
2176         for (i = 0; i < NUM_DIRECTIONS; i++)
2177         {
2178           if (key == *key_info[i].key_snap)
2179           {
2180             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2181             key_snap_action |= key_info[i].action;
2182
2183             tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2184           }
2185         }
2186       }
2187
2188       if (key_status == KEY_PRESSED)
2189       {
2190         stored_player[pnr].action      |= key_action;
2191         stored_player[pnr].snap_action |= key_snap_action;
2192       }
2193       else
2194       {
2195         stored_player[pnr].action      &= ~key_action;
2196         stored_player[pnr].snap_action &= ~key_snap_action;
2197       }
2198
2199       // restore snap action if one of several pressed snap keys was released
2200       if (stored_player[pnr].snap_action)
2201         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2202
2203       if (tape.recording && tape.pausing && tape.use_key_actions)
2204       {
2205         if (tape.single_step)
2206         {
2207           if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2208           {
2209             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2210
2211             // if snap key already pressed, keep pause mode when releasing
2212             if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2213               has_snapped[pnr] = TRUE;
2214           }
2215           else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2216           {
2217             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2218
2219             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2220                 getRedDiskReleaseFlag_SP() == 0)
2221             {
2222               // add a single inactive frame before dropping starts
2223               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2224               stored_player[pnr].force_dropping = TRUE;
2225             }
2226           }
2227           else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2228           {
2229             // if snap key was pressed without direction, leave pause mode
2230             if (!has_snapped[pnr])
2231               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2232
2233             has_snapped[pnr] = FALSE;
2234           }
2235         }
2236         else
2237         {
2238           // prevent key release events from un-pausing a paused game
2239           if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2240             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2241         }
2242       }
2243
2244       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2245       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2246         joy |= key_action;
2247
2248       // for any keyboard event, enable playfield mouse cursor
2249       if (key_action && key_status == KEY_PRESSED)
2250         SetPlayfieldMouseCursorEnabled(TRUE);
2251     }
2252   }
2253   else
2254   {
2255     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2256       if (key == key_info[i].key_default)
2257         joy |= key_info[i].action;
2258   }
2259
2260   if (joy)
2261   {
2262     if (key_status == KEY_PRESSED)
2263       key_joystick_mapping |= joy;
2264     else
2265       key_joystick_mapping &= ~joy;
2266
2267     HandleJoystick();
2268   }
2269
2270   if (game_status != GAME_MODE_PLAYING)
2271     key_joystick_mapping = 0;
2272
2273   if (key_status == KEY_RELEASED)
2274   {
2275     // reset flag to ignore repeated "key pressed" events after key release
2276     ignore_repeated_key = FALSE;
2277
2278     // send key release event to global animation event handling
2279     if (!is_global_anim_event)
2280       HandleGlobalAnimClicks(-1, -1, KEY_RELEASED, FALSE);
2281
2282     return;
2283   }
2284
2285   if ((key == KSYM_F11 ||
2286        ((key == KSYM_Return ||
2287          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2288       video.fullscreen_available &&
2289       !ignore_repeated_key)
2290   {
2291     setup.fullscreen = !setup.fullscreen;
2292
2293     ToggleFullscreenIfNeeded();
2294
2295     if (game_status == GAME_MODE_SETUP)
2296       RedrawSetupScreenAfterFullscreenToggle();
2297
2298     UpdateMousePosition();
2299
2300     // set flag to ignore repeated "key pressed" events
2301     ignore_repeated_key = TRUE;
2302
2303     return;
2304   }
2305
2306   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2307        key == KSYM_minus || key == KSYM_KP_Subtract ||
2308        key == KSYM_plus  || key == KSYM_KP_Add ||
2309        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2310       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2311       video.window_scaling_available &&
2312       !video.fullscreen_enabled)
2313   {
2314     if (key == KSYM_0 || key == KSYM_KP_0)
2315       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2316     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2317       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2318     else
2319       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2320
2321     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2322       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2323     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2324       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2325
2326     ChangeWindowScalingIfNeeded();
2327
2328     if (game_status == GAME_MODE_SETUP)
2329       RedrawSetupScreenAfterFullscreenToggle();
2330
2331     UpdateMousePosition();
2332
2333     return;
2334   }
2335
2336   // some key events are handled like clicks for global animations
2337   boolean click = (!is_global_anim_event && (key == KSYM_space ||
2338                                              key == KSYM_Return ||
2339                                              key == KSYM_Escape));
2340
2341   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2342   {
2343     // do not handle this key event anymore
2344     if (key != KSYM_Escape)     // always allow ESC key to be handled
2345       return;
2346   }
2347
2348   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2349       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2350   {
2351     GameEnd();
2352
2353     return;
2354   }
2355
2356   if (game_status == GAME_MODE_MAIN &&
2357       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2358   {
2359     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2360
2361     return;
2362   }
2363
2364   if (game_status == GAME_MODE_MAIN &&
2365       (setup.internal.info_screens_from_main ||
2366        leveldir_current->info_screens_from_main) &&
2367       (key >= KSYM_KP_1 && key <= KSYM_KP_9))
2368   {
2369     DrawInfoScreen_FromMainMenu(key - KSYM_KP_1 + 1);
2370
2371     return;
2372   }
2373
2374   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2375   {
2376     if (key == setup.shortcut.save_game)
2377       TapeQuickSave();
2378     else if (key == setup.shortcut.load_game)
2379       TapeQuickLoad();
2380     else if (key == setup.shortcut.restart_game)
2381       TapeRestartGame();
2382     else if (key == setup.shortcut.pause_before_end)
2383       TapeReplayAndPauseBeforeEnd();
2384     else if (key == setup.shortcut.toggle_pause)
2385       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2386
2387     HandleTapeButtonKeys(key);
2388     HandleSoundButtonKeys(key);
2389   }
2390
2391   if (game_status == GAME_MODE_SCOREINFO)
2392   {
2393     HandleScreenGadgetKeys(key);
2394   }
2395
2396   if (game_status == GAME_MODE_PLAYING && !network_playing)
2397   {
2398     int centered_player_nr_next = -999;
2399
2400     if (key == setup.shortcut.focus_player_all)
2401       centered_player_nr_next = -1;
2402     else
2403       for (i = 0; i < MAX_PLAYERS; i++)
2404         if (key == setup.shortcut.focus_player[i])
2405           centered_player_nr_next = i;
2406
2407     if (centered_player_nr_next != -999)
2408     {
2409       game.centered_player_nr_next = centered_player_nr_next;
2410       game.set_centered_player = TRUE;
2411
2412       if (tape.recording)
2413       {
2414         tape.centered_player_nr_next = game.centered_player_nr_next;
2415         tape.set_centered_player = TRUE;
2416       }
2417     }
2418   }
2419
2420   HandleKeysSpecial(key);
2421
2422   if (HandleGadgetsKeyInput(key))
2423     return;             // do not handle already processed keys again
2424
2425   // special case: on "space" key, either continue playing or go to main menu
2426   if (game_status == GAME_MODE_SCORES && key == KSYM_space)
2427   {
2428     HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
2429
2430     return;
2431   }
2432
2433   switch (game_status)
2434   {
2435     case GAME_MODE_PSEUDO_TYPENAME:
2436     case GAME_MODE_PSEUDO_TYPENAMES:
2437       HandleTypeName(key);
2438       break;
2439
2440     case GAME_MODE_TITLE:
2441     case GAME_MODE_MAIN:
2442     case GAME_MODE_NAMES:
2443     case GAME_MODE_LEVELS:
2444     case GAME_MODE_LEVELNR:
2445     case GAME_MODE_SETUP:
2446     case GAME_MODE_INFO:
2447     case GAME_MODE_SCORES:
2448     case GAME_MODE_SCOREINFO:
2449
2450       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2451         break;
2452
2453       switch (key)
2454       {
2455         case KSYM_space:
2456         case KSYM_Return:
2457           if (game_status == GAME_MODE_TITLE)
2458             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2459           else if (game_status == GAME_MODE_MAIN)
2460             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2461           else if (game_status == GAME_MODE_NAMES)
2462             HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2463           else if (game_status == GAME_MODE_LEVELS)
2464             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2465           else if (game_status == GAME_MODE_LEVELNR)
2466             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2467           else if (game_status == GAME_MODE_SETUP)
2468             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2469           else if (game_status == GAME_MODE_INFO)
2470             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2471           else if (game_status == GAME_MODE_SCORES)
2472             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2473           else if (game_status == GAME_MODE_SCOREINFO)
2474             HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
2475           break;
2476
2477         case KSYM_Escape:
2478           if (game_status != GAME_MODE_MAIN)
2479             FadeSkipNextFadeIn();
2480
2481           if (game_status == GAME_MODE_TITLE)
2482             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2483           else if (game_status == GAME_MODE_NAMES)
2484             HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2485           else if (game_status == GAME_MODE_LEVELS)
2486             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2487           else if (game_status == GAME_MODE_LEVELNR)
2488             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2489           else if (game_status == GAME_MODE_SETUP)
2490             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2491           else if (game_status == GAME_MODE_INFO)
2492             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2493           else if (game_status == GAME_MODE_SCORES)
2494             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2495           else if (game_status == GAME_MODE_SCOREINFO)
2496             HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
2497           break;
2498
2499         case KSYM_Page_Up:
2500           if (game_status == GAME_MODE_NAMES)
2501             HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2502           else if (game_status == GAME_MODE_LEVELS)
2503             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2504           else if (game_status == GAME_MODE_LEVELNR)
2505             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2506           else if (game_status == GAME_MODE_SETUP)
2507             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2508           else if (game_status == GAME_MODE_INFO)
2509             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2510           else if (game_status == GAME_MODE_SCORES)
2511             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2512           else if (game_status == GAME_MODE_SCOREINFO)
2513             HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2514           break;
2515
2516         case KSYM_Page_Down:
2517           if (game_status == GAME_MODE_NAMES)
2518             HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2519           else if (game_status == GAME_MODE_LEVELS)
2520             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2521           else if (game_status == GAME_MODE_LEVELNR)
2522             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2523           else if (game_status == GAME_MODE_SETUP)
2524             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2525           else if (game_status == GAME_MODE_INFO)
2526             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2527           else if (game_status == GAME_MODE_SCORES)
2528             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2529           else if (game_status == GAME_MODE_SCOREINFO)
2530             HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2531           break;
2532
2533         default:
2534           break;
2535       }
2536       break;
2537
2538     case GAME_MODE_EDITOR:
2539       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2540         HandleLevelEditorKeyInput(key);
2541       break;
2542
2543     case GAME_MODE_PLAYING:
2544     {
2545       switch (key)
2546       {
2547         case KSYM_Escape:
2548           RequestQuitGame(TRUE);
2549           break;
2550
2551         default:
2552           break;
2553       }
2554       break;
2555     }
2556
2557     default:
2558       if (key == KSYM_Escape)
2559       {
2560         SetGameStatus(GAME_MODE_MAIN);
2561
2562         DrawMainMenu();
2563
2564         return;
2565       }
2566   }
2567 }
2568
2569 void HandleNoEvent(void)
2570 {
2571   HandleMouseCursor();
2572
2573   switch (game_status)
2574   {
2575     case GAME_MODE_PLAYING:
2576       HandleButtonOrFinger(-1, -1, -1);
2577       break;
2578   }
2579 }
2580
2581 void HandleEventActions(void)
2582 {
2583   // if (button_status && game_status != GAME_MODE_PLAYING)
2584   if (button_status && (game_status != GAME_MODE_PLAYING ||
2585                         tape.pausing ||
2586                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2587   {
2588     HandleButton(0, 0, button_status, -button_status);
2589   }
2590   else
2591   {
2592     HandleJoystick();
2593   }
2594
2595   if (network.enabled)
2596     HandleNetworking();
2597
2598   switch (game_status)
2599   {
2600     case GAME_MODE_MAIN:
2601       DrawPreviewLevelAnimation();
2602       break;
2603
2604     case GAME_MODE_EDITOR:
2605       HandleLevelEditorIdle();
2606       break;
2607
2608     default:
2609       break;
2610   }
2611 }
2612
2613 static void HandleTileCursor(int dx, int dy, int button)
2614 {
2615   if (!dx || !button)
2616     ClearPlayerMouseAction();
2617
2618   if (!dx && !dy)
2619     return;
2620
2621   if (button)
2622   {
2623     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2624                          (dx < 0 ? MB_LEFTBUTTON :
2625                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2626   }
2627   else if (!tile_cursor.moving)
2628   {
2629     int old_xpos = tile_cursor.xpos;
2630     int old_ypos = tile_cursor.ypos;
2631     int new_xpos = tile_cursor.xpos + dx;
2632     int new_ypos = tile_cursor.ypos + dy;
2633
2634     if (!IN_LEV_FIELD(new_xpos, old_ypos) || !IN_SCR_FIELD(new_xpos, old_ypos))
2635       new_xpos = old_xpos;
2636
2637     if (!IN_LEV_FIELD(old_xpos, new_ypos) || !IN_SCR_FIELD(old_xpos, new_ypos))
2638       new_ypos = old_ypos;
2639
2640     SetTileCursorTargetXY(new_xpos, new_ypos);
2641   }
2642 }
2643
2644 static int HandleJoystickForAllPlayers(void)
2645 {
2646   int i;
2647   int result = 0;
2648   boolean no_joysticks_configured = TRUE;
2649   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2650   static byte joy_action_last[MAX_PLAYERS];
2651
2652   for (i = 0; i < MAX_PLAYERS; i++)
2653     if (setup.input[i].use_joystick)
2654       no_joysticks_configured = FALSE;
2655
2656   // if no joysticks configured, map connected joysticks to players
2657   if (no_joysticks_configured)
2658     use_as_joystick_nr = TRUE;
2659
2660   for (i = 0; i < MAX_PLAYERS; i++)
2661   {
2662     byte joy_action = 0;
2663
2664     joy_action = JoystickExt(i, use_as_joystick_nr);
2665     result |= joy_action;
2666
2667     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2668         joy_action != joy_action_last[i])
2669       stored_player[i].action = joy_action;
2670
2671     joy_action_last[i] = joy_action;
2672   }
2673
2674   return result;
2675 }
2676
2677 void HandleJoystick(void)
2678 {
2679   static DelayCounter joytest_delay = { GADGET_FRAME_DELAY };
2680   static int joytest_last = 0;
2681   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2682   int delay_value       = GADGET_FRAME_DELAY;
2683   int joystick  = HandleJoystickForAllPlayers();
2684   int keyboard  = key_joystick_mapping;
2685   int joy       = (joystick | keyboard);
2686   int joytest   = joystick;
2687   int left      = joy & JOY_LEFT;
2688   int right     = joy & JOY_RIGHT;
2689   int up        = joy & JOY_UP;
2690   int down      = joy & JOY_DOWN;
2691   int button    = joy & JOY_BUTTON;
2692   int anybutton = AnyJoystickButton();
2693   int newbutton = (anybutton == JOY_BUTTON_NEW_PRESSED);
2694   int dx        = (left ? -1    : right ? 1     : 0);
2695   int dy        = (up   ? -1    : down  ? 1     : 0);
2696   boolean use_delay_value_first = (joytest != joytest_last);
2697   boolean new_button_event = (anybutton == JOY_BUTTON_NEW_PRESSED ||
2698                               anybutton == JOY_BUTTON_NEW_RELEASED);
2699
2700   if (new_button_event && HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2701   {
2702     // do not handle this button event anymore
2703     return;
2704   }
2705
2706   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2707                     game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2708                     anyTextGadgetActive()))
2709   {
2710     // leave name input in main menu or text input gadget
2711     HandleKey(KSYM_Escape, KEY_PRESSED);
2712     HandleKey(KSYM_Escape, KEY_RELEASED);
2713
2714     return;
2715   }
2716
2717   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2718   {
2719     if (game_status == GAME_MODE_PLAYING)
2720     {
2721       // when playing MM style levels, also use delay for keyboard events
2722       joytest |= keyboard;
2723
2724       // only use first delay value for new events, but not for changed events
2725       use_delay_value_first = (!joytest != !joytest_last);
2726
2727       // only use delay after the initial keyboard event
2728       delay_value = 0;
2729     }
2730
2731     // for any joystick or keyboard event, enable playfield tile cursor
2732     if (dx || dy || button)
2733       SetTileCursorEnabled(TRUE);
2734   }
2735
2736   // for any joystick event, enable playfield mouse cursor
2737   if (dx || dy || button)
2738     SetPlayfieldMouseCursorEnabled(TRUE);
2739
2740   if (joytest && !button && !DelayReached(&joytest_delay))
2741   {
2742     // delay joystick/keyboard actions if axes/keys continually pressed
2743     newbutton = dx = dy = 0;
2744   }
2745   else
2746   {
2747     // first start with longer delay, then continue with shorter delay
2748     joytest_delay.value =
2749       (use_delay_value_first ? delay_value_first : delay_value);
2750   }
2751
2752   joytest_last = joytest;
2753
2754   switch (game_status)
2755   {
2756     case GAME_MODE_TITLE:
2757     case GAME_MODE_MAIN:
2758     case GAME_MODE_NAMES:
2759     case GAME_MODE_LEVELS:
2760     case GAME_MODE_LEVELNR:
2761     case GAME_MODE_SETUP:
2762     case GAME_MODE_INFO:
2763     case GAME_MODE_SCORES:
2764     case GAME_MODE_SCOREINFO:
2765     {
2766       if (anyTextGadgetActive())
2767         break;
2768
2769       if (game_status == GAME_MODE_TITLE)
2770         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2771       else if (game_status == GAME_MODE_MAIN)
2772         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2773       else if (game_status == GAME_MODE_NAMES)
2774         HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2775       else if (game_status == GAME_MODE_LEVELS)
2776         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2777       else if (game_status == GAME_MODE_LEVELNR)
2778         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2779       else if (game_status == GAME_MODE_SETUP)
2780         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2781       else if (game_status == GAME_MODE_INFO)
2782         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2783       else if (game_status == GAME_MODE_SCORES)
2784         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2785       else if (game_status == GAME_MODE_SCOREINFO)
2786         HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2787
2788       break;
2789     }
2790
2791     case GAME_MODE_PLAYING:
2792 #if 0
2793       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2794       if (tape.playing || keyboard)
2795         newbutton = ((joy & JOY_BUTTON) != 0);
2796 #endif
2797
2798       if (newbutton && game.all_players_gone)
2799       {
2800         GameEnd();
2801
2802         return;
2803       }
2804
2805       if (tape.recording && tape.pausing && tape.use_key_actions)
2806       {
2807         if (tape.single_step)
2808         {
2809           if (joystick & JOY_ACTION)
2810             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2811         }
2812         else
2813         {
2814           if (joystick & JOY_ACTION)
2815             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2816         }
2817       }
2818
2819       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2820         HandleTileCursor(dx, dy, button);
2821
2822       break;
2823
2824     default:
2825       break;
2826   }
2827 }
2828
2829 void HandleSpecialGameControllerButtons(Event *event)
2830 {
2831   int key_status;
2832   Key key;
2833
2834   switch (event->type)
2835   {
2836     case SDL_CONTROLLERBUTTONDOWN:
2837       key_status = KEY_PRESSED;
2838       break;
2839
2840     case SDL_CONTROLLERBUTTONUP:
2841       key_status = KEY_RELEASED;
2842       break;
2843
2844     default:
2845       return;
2846   }
2847
2848   switch (event->cbutton.button)
2849   {
2850     case SDL_CONTROLLER_BUTTON_START:
2851       key = KSYM_space;
2852       break;
2853
2854     case SDL_CONTROLLER_BUTTON_BACK:
2855       key = KSYM_Escape;
2856       break;
2857
2858     default:
2859       return;
2860   }
2861
2862   HandleKey(key, key_status);
2863 }
2864
2865 void HandleSpecialGameControllerKeys(Key key, int key_status)
2866 {
2867 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2868   int button = SDL_CONTROLLER_BUTTON_INVALID;
2869
2870   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2871   if (key == KSYM_Rewind)
2872     button = SDL_CONTROLLER_BUTTON_A;
2873   else if (key == KSYM_FastForward || key == KSYM_Menu)
2874     button = SDL_CONTROLLER_BUTTON_B;
2875
2876   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2877   {
2878     Event event;
2879
2880     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2881                   SDL_CONTROLLERBUTTONUP);
2882
2883     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2884     event.cbutton.button = button;
2885     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2886                            SDL_RELEASED);
2887
2888     HandleJoystickEvent(&event);
2889   }
2890 #endif
2891 }
2892
2893 boolean DoKeysymAction(int keysym)
2894 {
2895   if (keysym < 0)
2896   {
2897     Key key = (Key)(-keysym);
2898
2899     is_global_anim_event = TRUE;
2900
2901     HandleKey(key, KEY_PRESSED);
2902     HandleKey(key, KEY_RELEASED);
2903
2904     is_global_anim_event = FALSE;
2905
2906     return TRUE;
2907   }
2908
2909   return FALSE;
2910 }