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