rnd-19990822-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 || 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         default:
565           break;
566       }
567       break;
568
569     case LEVELED:
570       HandleLevelEditorKeyInput(key);
571       break;
572
573     case PLAYING:
574     {
575       switch(key)
576       {
577
578 #ifdef DEBUG
579         case XK_0:
580         case XK_1:
581         case XK_2:
582         case XK_3:
583         case XK_4:
584         case XK_5:
585         case XK_6:
586         case XK_7:
587         case XK_8:
588         case XK_9:
589           if (key == XK_0)
590           {
591             if (GameFrameDelay == 500)
592               GameFrameDelay = GAME_FRAME_DELAY;
593             else
594               GameFrameDelay = 500;
595           }
596           else
597             GameFrameDelay = (key - XK_0) * 10;
598           printf("Game speed == %d%% (%d ms delay between two frames)\n",
599                  GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
600           break;
601
602
603 #if 0
604         case XK_a:
605           if (ScrollStepSize == TILEX/8)
606             ScrollStepSize = TILEX/4;
607           else
608             ScrollStepSize = TILEX/8;
609           printf("ScrollStepSize == %d\n", ScrollStepSize);
610           break;
611 #endif
612
613 #if 0
614         case XK_m:
615           if (MoveSpeed == 8)
616           {
617             MoveSpeed = 4;
618             ScrollStepSize = TILEX/4;
619           }
620           else
621           {
622             MoveSpeed = 8;
623             ScrollStepSize = TILEX/8;
624           }
625           printf("MoveSpeed == %d\n", MoveSpeed);
626           break;
627 #endif
628
629         case XK_f:
630           ScrollStepSize = TILEX/8;
631           printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
632           break;
633
634         case XK_g:
635           ScrollStepSize = TILEX/4;
636           printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
637           break;
638
639         case XK_h:
640           ScrollStepSize = TILEX/2;
641           printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
642           break;
643
644         case XK_l:
645           ScrollStepSize = TILEX;
646           printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
647           break;
648
649 #ifndef MSDOS
650         case XK_Q:
651 #endif
652         case XK_q:
653           local_player->dynamite = 1000;
654           break;
655
656
657
658 #if 0
659
660         case XK_z:
661           {
662             int i;
663
664             for(i=0; i<MAX_PLAYERS; i++)
665             {
666               printf("Player %d:\n", i);
667               printf("  jx == %d, jy == %d\n",
668                      stored_player[i].jx, stored_player[i].jy);
669               printf("  last_jx == %d, last_jy == %d\n",
670                      stored_player[i].last_jx, stored_player[i].last_jy);
671             }
672             printf("\n");
673           }
674
675           break;
676 #endif
677 #endif
678
679         default:
680           break;
681       }
682       break;
683     }
684     default:
685       break;
686   }
687 }
688
689 void HandleNoXEvent()
690 {
691   if (button_status && game_status != PLAYING)
692   {
693     HandleButton(0, 0, -button_status);
694     return;
695   }
696
697 #ifndef MSDOS
698   if (options.network)
699     HandleNetworking();
700 #endif
701
702   HandleJoystick();
703
704   if (game_status == PLAYING)
705     HandleGameActions();
706 }
707
708 static int HandleJoystickForAllPlayers()
709 {
710   int i;
711   int result = 0;
712
713   for (i=0; i<MAX_PLAYERS; i++)
714   {
715     byte joy_action = 0;
716
717     /*
718     if (!setup.input[i].use_joystick)
719       continue;
720       */
721
722     joy_action = Joystick(i);
723     result |= joy_action;
724
725
726     if (!setup.input[i].use_joystick)
727       continue;
728
729
730     stored_player[i].action = joy_action;
731   }
732
733   return result;
734 }
735
736 void HandleJoystick()
737 {
738   int joystick  = HandleJoystickForAllPlayers();
739   int keyboard  = key_joystick_mapping;
740   int joy       = (joystick | keyboard);
741   int left      = joy & JOY_LEFT;
742   int right     = joy & JOY_RIGHT;
743   int up        = joy & JOY_UP;
744   int down      = joy & JOY_DOWN;
745   int button    = joy & JOY_BUTTON;
746   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
747   int dx        = (left ? -1    : right ? 1     : 0);
748   int dy        = (up   ? -1    : down  ? 1     : 0);
749
750   switch(game_status)
751   {
752     case MAINMENU:
753     case CHOOSELEVEL:
754     case SETUP:
755     case SETUPINPUT:
756     {
757       static unsigned long joystickmove_delay = 0;
758
759       if (joystick && !button &&
760           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
761         newbutton = dx = dy = 0;
762
763       if (game_status==MAINMENU)
764         HandleMainMenu(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
765       else if (game_status==CHOOSELEVEL)
766         HandleChooseLevel(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
767       else if (game_status==SETUP)
768         HandleSetupScreen(0,0,dx,dy,newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
769       else if (game_status==SETUPINPUT)
770         HandleSetupInputScreen(0,0,dx,dy,
771                                newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
772       break;
773     }
774
775     case HALLOFFAME:
776       HandleHallOfFame(!newbutton);
777       break;
778
779     case HELPSCREEN:
780       HandleHelpScreen(!newbutton);
781       break;
782
783     case PLAYING:
784       if (tape.playing || keyboard)
785         newbutton = ((joy & JOY_BUTTON) != 0);
786
787       if (AllPlayersGone && newbutton)
788       {
789         CloseDoor(DOOR_CLOSE_1);
790         game_status = MAINMENU;
791         DrawMainMenu();
792         return;
793       }
794
795       break;
796
797     default:
798       break;
799   }
800 }