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