b0022c351eb70247a9683d6d4ca48080ad513b9b
[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     if (game_status == LEVELED)
491     {
492       /* draw smaller door */
493       XCopyArea(display, pix[PIX_DOOR], drawto, gc,
494                 DOOR_GFX_PAGEX7, 64,
495                 108, 64,
496                 EX - 4, EY - 12);
497       redraw_mask |= REDRAW_ALL;
498     }
499
500     CloseDoor(DOOR_CLOSE_1 | DOOR_OPEN_2 | DOOR_NO_DELAY);
501     game_status = MAINMENU;
502     DrawMainMenu();
503     return;
504   }
505
506
507
508 #ifndef DEBUG
509
510   if (game_status == PLAYING && (tape.playing || tape.pausing))
511     return;
512
513 #endif
514
515
516
517   HandleGadgetsKeyInput(key);
518
519   switch(game_status)
520   {
521     case TYPENAME:
522       HandleTypeName(0, key);
523       break;
524
525     case MAINMENU:
526     case CHOOSELEVEL:
527     case SETUP:
528     case SETUPINPUT:
529       switch(key)
530       {
531         case XK_Return:
532           if (game_status == MAINMENU)
533             HandleMainMenu(0,0, 0,0, MB_MENU_CHOICE);
534           else if (game_status == CHOOSELEVEL)
535             HandleChooseLevel(0,0, 0,0, MB_MENU_CHOICE);
536           else if (game_status == SETUP)
537             HandleSetupScreen(0,0, 0,0, MB_MENU_CHOICE);
538           else if (game_status == SETUPINPUT)
539             HandleSetupInputScreen(0,0, 0,0, MB_MENU_CHOICE);
540           break;
541
542         default:
543           break;
544       }
545       break;
546
547     case HELPSCREEN:
548       HandleHelpScreen(MB_RELEASED);
549       break;
550
551     case HALLOFFAME:
552       switch(key)
553       {
554         case XK_Return:
555           game_status = MAINMENU;
556           DrawMainMenu();
557           BackToFront();
558           break;
559
560         default:
561           break;
562       }
563       break;
564
565     case LEVELED:
566       HandleLevelEditorKeyInput(key);
567       break;
568
569     case PLAYING:
570     {
571       switch(key)
572       {
573
574 #ifdef DEBUG
575         case XK_0:
576         case XK_1:
577         case XK_2:
578         case XK_3:
579         case XK_4:
580         case XK_5:
581         case XK_6:
582         case XK_7:
583         case XK_8:
584         case XK_9:
585           if (key == XK_0)
586           {
587             if (GameFrameDelay == 500)
588               GameFrameDelay = GAME_FRAME_DELAY;
589             else
590               GameFrameDelay = 500;
591           }
592           else
593             GameFrameDelay = (key - XK_0) * 10;
594           printf("Game speed == %d%% (%d ms delay between two frames)\n",
595                  GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
596           break;
597
598
599 #if 0
600         case XK_a:
601           if (ScrollStepSize == TILEX/8)
602             ScrollStepSize = TILEX/4;
603           else
604             ScrollStepSize = TILEX/8;
605           printf("ScrollStepSize == %d\n", ScrollStepSize);
606           break;
607 #endif
608
609 #if 0
610         case XK_m:
611           if (MoveSpeed == 8)
612           {
613             MoveSpeed = 4;
614             ScrollStepSize = TILEX/4;
615           }
616           else
617           {
618             MoveSpeed = 8;
619             ScrollStepSize = TILEX/8;
620           }
621           printf("MoveSpeed == %d\n", MoveSpeed);
622           break;
623 #endif
624
625         case XK_f:
626           ScrollStepSize = TILEX/8;
627           printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
628           break;
629
630         case XK_g:
631           ScrollStepSize = TILEX/4;
632           printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
633           break;
634
635         case XK_h:
636           ScrollStepSize = TILEX/2;
637           printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
638           break;
639
640         case XK_l:
641           ScrollStepSize = TILEX;
642           printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
643           break;
644
645 #ifndef MSDOS
646         case XK_Q:
647 #endif
648         case XK_q:
649           local_player->dynamite = 1000;
650           break;
651
652
653
654 #if 0
655
656         case XK_z:
657           {
658             int i;
659
660             for(i=0; i<MAX_PLAYERS; i++)
661             {
662               printf("Player %d:\n", i);
663               printf("  jx == %d, jy == %d\n",
664                      stored_player[i].jx, stored_player[i].jy);
665               printf("  last_jx == %d, last_jy == %d\n",
666                      stored_player[i].last_jx, stored_player[i].last_jy);
667             }
668             printf("\n");
669           }
670
671           break;
672 #endif
673 #endif
674
675         default:
676           break;
677       }
678       break;
679     }
680     default:
681       break;
682   }
683 }
684
685 void HandleNoXEvent()
686 {
687   if (button_status && game_status != PLAYING)
688   {
689     HandleButton(0, 0, -button_status);
690     return;
691   }
692
693 #ifndef MSDOS
694   if (options.network)
695     HandleNetworking();
696 #endif
697
698   HandleJoystick();
699
700   if (game_status == PLAYING)
701     HandleGameActions();
702 }
703
704 static int HandleJoystickForAllPlayers()
705 {
706   int i;
707   int result = 0;
708
709   for (i=0; i<MAX_PLAYERS; i++)
710   {
711     byte joy_action = 0;
712
713     /*
714     if (!setup.input[i].use_joystick)
715       continue;
716       */
717
718     joy_action = Joystick(i);
719     result |= joy_action;
720
721
722     if (!setup.input[i].use_joystick)
723       continue;
724
725
726     stored_player[i].action = joy_action;
727   }
728
729   return result;
730 }
731
732 void HandleJoystick()
733 {
734   int joystick  = HandleJoystickForAllPlayers();
735   int keyboard  = key_joystick_mapping;
736   int joy       = (joystick | keyboard);
737   int left      = joy & JOY_LEFT;
738   int right     = joy & JOY_RIGHT;
739   int up        = joy & JOY_UP;
740   int down      = joy & JOY_DOWN;
741   int button    = joy & JOY_BUTTON;
742   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
743   int dx        = (left ? -1    : right ? 1     : 0);
744   int dy        = (up   ? -1    : down  ? 1     : 0);
745
746   switch(game_status)
747   {
748     case MAINMENU:
749     case CHOOSELEVEL:
750     case SETUP:
751     case SETUPINPUT:
752     {
753       static unsigned long joystickmove_delay = 0;
754
755       if (joystick && !button &&
756           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
757         newbutton = dx = dy = 0;
758
759       if (game_status==MAINMENU)
760         HandleMainMenu(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
761       else if (game_status==CHOOSELEVEL)
762         HandleChooseLevel(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
763       else if (game_status==SETUP)
764         HandleSetupScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
765       else if (game_status==SETUPINPUT)
766         HandleSetupInputScreen(0,0,dx,dy,
767                                newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
768       break;
769     }
770
771     case HALLOFFAME:
772       HandleHallOfFame(!newbutton);
773       break;
774
775     case HELPSCREEN:
776       HandleHelpScreen(!newbutton);
777       break;
778
779     case PLAYING:
780       if (tape.playing || keyboard)
781         newbutton = ((joy & JOY_BUTTON) != 0);
782
783       if (AllPlayersGone && newbutton)
784       {
785         CloseDoor(DOOR_CLOSE_1);
786         game_status = MAINMENU;
787         DrawMainMenu();
788         return;
789       }
790
791       break;
792
793     default:
794       break;
795   }
796 }