rnd-19990604-1-src
[rocksndiamonds.git] / src / events.c
1 /***********************************************************
2 *  Rocks'n'Diamonds -- McDuffin Strikes Back!              *
3 *----------------------------------------------------------*
4 *  (c) 1995-98 Artsoft Entertainment                       *
5 *              Holger Schemel                              *
6 *              Oststrasse 11a                              *
7 *              33604 Bielefeld                             *
8 *              phone: ++49 +521 290471                     *
9 *              email: aeglos@valinor.owl.de                *
10 *----------------------------------------------------------*
11 *  events.c                                                *
12 ***********************************************************/
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 "misc.h"
21 #include "tape.h"
22 #include "joystick.h"
23 #include "buttons.h"
24 #include "network.h"
25
26 /* values for key_status */
27 #define KEY_NOT_PRESSED         FALSE
28 #define KEY_RELEASED            FALSE
29 #define KEY_PRESSED             TRUE
30
31 void EventLoop(void)
32 {
33   while(1)
34   {
35     if (XPending(display))      /* got event from X server */
36     {
37       XEvent event;
38
39       XNextEvent(display, &event);
40
41       switch(event.type)
42       {
43         case ButtonPress:
44         case ButtonRelease:
45           HandleButtonEvent((XButtonEvent *) &event);
46           break;
47
48         case MotionNotify:
49           HandleMotionEvent((XMotionEvent *) &event);
50           break;
51
52         case KeyPress:
53         case KeyRelease:
54           HandleKeyEvent((XKeyEvent *) &event);
55           break;
56
57         default:
58           HandleOtherEvents(&event);
59           break;
60       }
61     }
62
63     HandleNoXEvent();
64
65     /* don't use all CPU time when idle; the main loop while playing
66        has its own synchronization and is CPU friendly, too */
67
68     if (game_status != PLAYING)
69     {
70       XSync(display, FALSE);
71       if (!XPending(display))   /* delay only if no pending events */
72         Delay(10);
73     }
74
75     /* refresh window contents from drawing buffer, if needed */
76     BackToFront();
77
78     if (game_status == EXITGAME)
79       return;
80   }
81 }
82
83 void HandleOtherEvents(XEvent *event)
84 {
85   switch(event->type)
86   {
87     case Expose:
88       HandleExposeEvent((XExposeEvent *) event);
89       break;
90
91     case UnmapNotify:
92       SleepWhileUnmapped();
93       break;
94
95     case FocusIn:
96     case FocusOut:
97       HandleFocusEvent((XFocusChangeEvent *) event);
98       break;
99
100     case ClientMessage:
101       HandleClientMessageEvent((XClientMessageEvent *) event);
102       break;
103
104     default:
105       break;
106   }
107 }
108
109 void ClearEventQueue()
110 {
111   while(XPending(display))
112   {
113     XEvent event;
114
115     XNextEvent(display, &event);
116
117     switch(event.type)
118     {
119       case ButtonRelease:
120         button_status = MB_RELEASED;
121         break;
122
123       case KeyRelease:
124         key_joystick_mapping = 0;
125         break;
126
127       default:
128         HandleOtherEvents(&event);
129         break;
130     }
131   }
132 }
133
134 void SleepWhileUnmapped()
135 {
136   boolean window_unmapped = TRUE;
137
138   XAutoRepeatOn(display);
139
140   while(window_unmapped)
141   {
142     XEvent event;
143
144     XNextEvent(display, &event);
145
146     switch(event.type)
147     {
148       case ButtonRelease:
149         button_status = MB_RELEASED;
150         break;
151
152       case KeyRelease:
153         key_joystick_mapping = 0;
154         break;
155
156       case MapNotify:
157         window_unmapped = FALSE;
158         break;
159
160       case UnmapNotify:
161         /* this is only to surely prevent the 'should not happen' case
162          * of recursively looping between 'SleepWhileUnmapped()' and
163          * 'HandleOtherEvents()' which usually calls this funtion.
164          */
165         break;
166
167       default:
168         HandleOtherEvents(&event);
169         break;
170     }
171   }
172
173   if (game_status == PLAYING)
174     XAutoRepeatOff(display);
175 }
176
177 void HandleExposeEvent(XExposeEvent *event)
178 {
179   int x = event->x, y = event->y;
180   int width = event->width, height = event->height;
181
182   if (setup.direct_draw && game_status==PLAYING)
183   {
184     int xx,yy;
185     int x1 = (x-SX)/TILEX, y1 = (y-SY)/TILEY;
186     int x2 = (x-SX+width)/TILEX, y2 = (y-SY+height)/TILEY;
187
188     SetDrawtoField(DRAW_BACKBUFFER);
189
190     for(xx=0; xx<SCR_FIELDX; xx++)
191       for(yy=0; yy<SCR_FIELDY; yy++)
192         if (xx>=x1 && xx<=x2 && yy>=y1 && yy<=y2)
193           DrawScreenField(xx,yy);
194     DrawAllPlayers();
195
196     SetDrawtoField(DRAW_DIRECT);
197   }
198
199   if (setup.soft_scrolling && game_status == PLAYING)
200   {
201     int fx = FX, fy = FY;
202
203     fx += (ScreenMovDir & (MV_LEFT|MV_RIGHT) ? ScreenGfxPos : 0);
204     fy += (ScreenMovDir & (MV_UP|MV_DOWN)    ? ScreenGfxPos : 0);
205
206     XCopyArea(display,fieldbuffer,backbuffer,gc,
207               fx,fy, SXSIZE,SYSIZE,
208               SX,SY);
209   }
210
211   XCopyArea(display,drawto,window,gc, x,y, width,height, x,y);
212
213   XFlush(display);
214 }
215
216 void HandleButtonEvent(XButtonEvent *event)
217 {
218   motion_status = FALSE;
219
220   if (event->type == ButtonPress)
221     button_status = event->button;
222   else
223     button_status = MB_RELEASED;
224
225   HandleButton(event->x, event->y, button_status);
226 }
227
228 void HandleMotionEvent(XMotionEvent *event)
229 {
230   Window root, child;
231   int root_x, root_y;
232   int win_x, win_y;
233   unsigned int mask;
234
235   if (!XQueryPointer(display, window, &root, &child, &root_x, &root_y,
236                      &win_x, &win_y, &mask))
237     return;
238
239   if (!button_status && game_status != LEVELED)
240     return;
241
242   motion_status = TRUE;
243
244   HandleButton(win_x, win_y, button_status);
245 }
246
247 void HandleKeyEvent(XKeyEvent *event)
248 {
249   int key_status = (event->type == KeyPress ? KEY_PRESSED : KEY_RELEASED);
250   KeySym key;
251
252   if (game_status == PLAYING)
253   {
254     /* use '0' instead of 'event->state' to get the key without modifiers */
255     key = XLookupKeysym(event, 0);
256   }
257   else
258   {
259     /* get the key with all modifiers */
260     char buffer[10];
261     int buffer_size = 10;
262     XComposeStatus compose;
263     int char_count;
264
265     char_count = XLookupString(event, buffer, buffer_size, &key, &compose);
266     buffer[char_count] = '\0';
267   }
268
269   HandleKey(key, key_status);
270 }
271
272 void HandleFocusEvent(XFocusChangeEvent *event)
273 {
274   static int old_joystick_status = -1;
275
276   if (event->type == FocusOut)
277   {
278     int i;
279
280     XAutoRepeatOn(display);
281     old_joystick_status = joystick_status;
282     joystick_status = JOYSTICK_OFF;
283
284     /* simulate key release events for still pressed keys */
285     key_joystick_mapping = 0;
286     for (i=0; i<MAX_PLAYERS; i++)
287       stored_player[i].action = 0;
288   }
289   else if (event->type == FocusIn)
290   {
291     /* When there are two Rocks'n'Diamonds windows which overlap and
292        the player moves the pointer from one game window to the other,
293        a 'FocusOut' event is generated for the window the pointer is
294        leaving and a 'FocusIn' event is generated for the window the
295        pointer is entering. In some cases, it can happen that the
296        'FocusIn' event is handled by the one game process before the
297        'FocusOut' event by the other game process. In this case the
298        X11 environment would end up with activated keyboard auto repeat,
299        because unfortunately this is a global setting and not (which
300        would be far better) set for each X11 window individually.
301        The effect would be keyboard auto repeat while playing the game
302        (game_status == PLAYING), which is not desired.
303        To avoid this special case, we just wait 1/10 second before
304        processing the 'FocusIn' event.
305     */
306
307     if (game_status == PLAYING)
308     {
309       Delay(100);
310       XAutoRepeatOff(display);
311     }
312     if (old_joystick_status != -1)
313       joystick_status = old_joystick_status;
314   }
315 }
316
317 void HandleClientMessageEvent(XClientMessageEvent *event)
318 {
319 #ifndef MSDOS
320   if ((event->window == window) &&
321       (event->data.l[0] == XInternAtom(display, "WM_DELETE_WINDOW", FALSE)))
322     CloseAllAndExit(0);
323 #endif
324 }
325
326 void HandleButton(int mx, int my, int button)
327 {
328   static int old_mx = 0, old_my = 0;
329
330   if (button < 0)
331   {
332     mx = old_mx;
333     my = old_my;
334     button = -button;
335   }
336   else
337   {
338     old_mx = mx;
339     old_my = my;
340   }
341
342   HandleGadgets(mx, my, button);
343
344   switch(game_status)
345   {
346     case MAINMENU:
347       HandleMainMenu(mx,my, 0,0, button);
348       break;
349
350     case TYPENAME:
351       HandleTypeName(0, XK_Return);
352       break;
353
354     case CHOOSELEVEL:
355       HandleChooseLevel(mx,my, 0,0, button);
356       break;
357
358     case HALLOFFAME:
359       HandleHallOfFame(button);
360       break;
361
362     case LEVELED:
363       break;
364
365     case HELPSCREEN:
366       HandleHelpScreen(button);
367       break;
368
369     case SETUP:
370       HandleSetupScreen(mx,my, 0,0, button);
371       break;
372
373     case SETUPINPUT:
374       HandleSetupInputScreen(mx,my, 0,0, button);
375       break;
376
377     case PLAYING:
378 #ifdef DEBUG
379       if (button == MB_RELEASED)
380       {
381         int sx = (mx - SX) / TILEX;
382         int sy = (my - SY) / TILEY;
383
384         if (IN_VIS_FIELD(sx,sy))
385         {
386           int x = LEVELX(sx);
387           int y = LEVELY(sy);
388
389           printf("INFO: SCREEN(%d, %d), LEVEL(%d, %d)\n", sx, sy, x, y);
390
391           if (!IN_LEV_FIELD(x, y))
392             break;
393
394           printf("      Feld[%d][%d] == %d\n", x,y, Feld[x][y]);
395           printf("      Store[%d][%d] == %d\n", x,y, Store[x][y]);
396           printf("      Store2[%d][%d] == %d\n", x,y, Store2[x][y]);
397           printf("      StorePlayer[%d][%d] == %d\n", x,y, StorePlayer[x][y]);
398           printf("      MovPos[%d][%d] == %d\n", x,y, MovPos[x][y]);
399           printf("      MovDir[%d][%d] == %d\n", x,y, MovDir[x][y]);
400           printf("      MovDelay[%d][%d] == %d\n", x,y, MovDelay[x][y]);
401           printf("\n");
402         }
403       }
404 #endif
405       break;
406
407     default:
408       break;
409   }
410 }
411
412 void HandleKey(KeySym key, int key_status)
413 {
414   int joy = 0;
415   static struct SetupKeyboardInfo custom_key;
416   static struct
417   {
418     KeySym *keysym_custom;
419     KeySym keysym_default;
420     byte action;
421   } key_info[] =
422   {
423     { &custom_key.left,  DEFAULT_KEY_LEFT,  JOY_LEFT     },
424     { &custom_key.right, DEFAULT_KEY_RIGHT, JOY_RIGHT    },
425     { &custom_key.up,    DEFAULT_KEY_UP,    JOY_UP       },
426     { &custom_key.down,  DEFAULT_KEY_DOWN,  JOY_DOWN     },
427     { &custom_key.snap,  DEFAULT_KEY_SNAP,  JOY_BUTTON_1 },
428     { &custom_key.bomb,  DEFAULT_KEY_BOMB,  JOY_BUTTON_2 }
429   };
430
431   if (game_status == PLAYING)
432   {
433     int pnr;
434
435     for (pnr=0; pnr<MAX_PLAYERS; pnr++)
436     {
437       int i;
438       byte key_action = 0;
439
440       if (setup.input[pnr].use_joystick)
441         continue;
442
443       custom_key = setup.input[pnr].key;
444
445       for (i=0; i<6; i++)
446         if (key == *key_info[i].keysym_custom)
447           key_action |= key_info[i].action;
448
449       if (key_status == KEY_PRESSED)
450         stored_player[pnr].action |= key_action;
451       else
452         stored_player[pnr].action &= ~key_action;
453     }
454   }
455   else
456   {
457     int i;
458
459     for (i=0; i<6; i++)
460       if (key == key_info[i].keysym_default)
461         joy |= key_info[i].action;
462   }
463
464   if (joy)
465   {
466     if (key_status == KEY_PRESSED)
467       key_joystick_mapping |= joy;
468     else
469       key_joystick_mapping &= ~joy;
470
471     HandleJoystick();
472   }
473
474   if (game_status != PLAYING)
475     key_joystick_mapping = 0;
476
477   if (key_status == KEY_RELEASED)
478     return;
479
480   if (key == XK_Return && game_status == PLAYING && AllPlayersGone)
481   {
482     CloseDoor(DOOR_CLOSE_1);
483     game_status = MAINMENU;
484     DrawMainMenu();
485     return;
486   }
487
488   /* allow quick escape to the main menu with the Escape key */
489   if (key == XK_Escape && game_status != MAINMENU)
490   {
491     CloseDoor(DOOR_CLOSE_1 | DOOR_OPEN_2 | DOOR_NO_DELAY);
492     game_status = MAINMENU;
493     DrawMainMenu();
494     return;
495   }
496
497
498
499 #ifndef DEBUG
500
501   if (game_status == PLAYING && (tape.playing || tape.pausing))
502     return;
503
504 #endif
505
506
507
508   HandleGadgetsKeyInput(key);
509
510   switch(game_status)
511   {
512     case TYPENAME:
513       HandleTypeName(0, key);
514       break;
515
516     case MAINMENU:
517     case CHOOSELEVEL:
518     case SETUP:
519     case SETUPINPUT:
520       switch(key)
521       {
522         case XK_Return:
523           if (game_status == MAINMENU)
524             HandleMainMenu(0,0, 0,0, MB_MENU_CHOICE);
525           else if (game_status == CHOOSELEVEL)
526             HandleChooseLevel(0,0, 0,0, MB_MENU_CHOICE);
527           else if (game_status == SETUP)
528             HandleSetupScreen(0,0, 0,0, MB_MENU_CHOICE);
529           else if (game_status == SETUPINPUT)
530             HandleSetupInputScreen(0,0, 0,0, MB_MENU_CHOICE);
531           break;
532
533         case XK_Page_Up:
534           if (game_status == CHOOSELEVEL)
535             HandleChooseLevel(0,0, 0,-SCR_FIELDY, MB_MENU_MARK);
536           break;
537
538         case XK_Page_Down:
539           if (game_status == CHOOSELEVEL)
540             HandleChooseLevel(0,0, 0,SCR_FIELDY, MB_MENU_MARK);
541           break;
542
543         default:
544           break;
545       }
546       break;
547
548     case HELPSCREEN:
549       HandleHelpScreen(MB_RELEASED);
550       break;
551
552     case HALLOFFAME:
553       switch(key)
554       {
555         case XK_Return:
556           game_status = MAINMENU;
557           DrawMainMenu();
558           BackToFront();
559           break;
560
561         default:
562           break;
563       }
564       break;
565
566     case LEVELED:
567       HandleLevelEditorKeyInput(key);
568       break;
569
570     case PLAYING:
571     {
572       switch(key)
573       {
574
575 #ifdef DEBUG
576         case XK_0:
577         case XK_1:
578         case XK_2:
579         case XK_3:
580         case XK_4:
581         case XK_5:
582         case XK_6:
583         case XK_7:
584         case XK_8:
585         case XK_9:
586           if (key == XK_0)
587           {
588             if (GameFrameDelay == 500)
589               GameFrameDelay = GAME_FRAME_DELAY;
590             else
591               GameFrameDelay = 500;
592           }
593           else
594             GameFrameDelay = (key - XK_0) * 10;
595           printf("Game speed == %d%% (%d ms delay between two frames)\n",
596                  GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
597           break;
598
599
600 #if 0
601         case XK_a:
602           if (ScrollStepSize == TILEX/8)
603             ScrollStepSize = TILEX/4;
604           else
605             ScrollStepSize = TILEX/8;
606           printf("ScrollStepSize == %d\n", ScrollStepSize);
607           break;
608 #endif
609
610 #if 0
611         case XK_m:
612           if (MoveSpeed == 8)
613           {
614             MoveSpeed = 4;
615             ScrollStepSize = TILEX/4;
616           }
617           else
618           {
619             MoveSpeed = 8;
620             ScrollStepSize = TILEX/8;
621           }
622           printf("MoveSpeed == %d\n", MoveSpeed);
623           break;
624 #endif
625
626         case XK_f:
627           ScrollStepSize = TILEX/8;
628           printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
629           break;
630
631         case XK_g:
632           ScrollStepSize = TILEX/4;
633           printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
634           break;
635
636         case XK_h:
637           ScrollStepSize = TILEX/2;
638           printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
639           break;
640
641         case XK_l:
642           ScrollStepSize = TILEX;
643           printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
644           break;
645
646 #ifndef MSDOS
647         case XK_Q:
648 #endif
649         case XK_q:
650           local_player->dynamite = 1000;
651           break;
652
653
654
655 #if 0
656
657         case XK_z:
658           {
659             int i;
660
661             for(i=0; i<MAX_PLAYERS; i++)
662             {
663               printf("Player %d:\n", i);
664               printf("  jx == %d, jy == %d\n",
665                      stored_player[i].jx, stored_player[i].jy);
666               printf("  last_jx == %d, last_jy == %d\n",
667                      stored_player[i].last_jx, stored_player[i].last_jy);
668             }
669             printf("\n");
670           }
671
672           break;
673 #endif
674 #endif
675
676         default:
677           break;
678       }
679       break;
680     }
681     default:
682       break;
683   }
684 }
685
686 void HandleNoXEvent()
687 {
688   if (button_status && game_status != PLAYING)
689   {
690     HandleButton(0, 0, -button_status);
691     return;
692   }
693
694 #ifndef MSDOS
695   if (options.network)
696     HandleNetworking();
697 #endif
698
699   HandleJoystick();
700
701   if (game_status == PLAYING)
702     HandleGameActions();
703 }
704
705 static int HandleJoystickForAllPlayers()
706 {
707   int i;
708   int result = 0;
709
710   for (i=0; i<MAX_PLAYERS; i++)
711   {
712     byte joy_action = 0;
713
714     /*
715     if (!setup.input[i].use_joystick)
716       continue;
717       */
718
719     joy_action = Joystick(i);
720     result |= joy_action;
721
722
723     if (!setup.input[i].use_joystick)
724       continue;
725
726
727     stored_player[i].action = joy_action;
728   }
729
730   return result;
731 }
732
733 void HandleJoystick()
734 {
735   int joystick  = HandleJoystickForAllPlayers();
736   int keyboard  = key_joystick_mapping;
737   int joy       = (joystick | keyboard);
738   int left      = joy & JOY_LEFT;
739   int right     = joy & JOY_RIGHT;
740   int up        = joy & JOY_UP;
741   int down      = joy & JOY_DOWN;
742   int button    = joy & JOY_BUTTON;
743   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
744   int dx        = (left ? -1    : right ? 1     : 0);
745   int dy        = (up   ? -1    : down  ? 1     : 0);
746
747   switch(game_status)
748   {
749     case MAINMENU:
750     case CHOOSELEVEL:
751     case SETUP:
752     case SETUPINPUT:
753     {
754       static unsigned long joystickmove_delay = 0;
755
756       if (joystick && !button &&
757           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
758         newbutton = dx = dy = 0;
759
760       if (game_status==MAINMENU)
761         HandleMainMenu(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
762       else if (game_status==CHOOSELEVEL)
763         HandleChooseLevel(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
764       else if (game_status==SETUP)
765         HandleSetupScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
766       else if (game_status==SETUPINPUT)
767         HandleSetupInputScreen(0,0,dx,dy,
768                                newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
769       break;
770     }
771
772     case HALLOFFAME:
773       HandleHallOfFame(!newbutton);
774       break;
775
776     case HELPSCREEN:
777       HandleHelpScreen(!newbutton);
778       break;
779
780     case PLAYING:
781       if (tape.playing || keyboard)
782         newbutton = ((joy & JOY_BUTTON) != 0);
783
784       if (AllPlayersGone && newbutton)
785       {
786         CloseDoor(DOOR_CLOSE_1);
787         game_status = MAINMENU;
788         DrawMainMenu();
789         return;
790       }
791
792       break;
793
794     default:
795       break;
796   }
797 }