added graphical panel values for time and health (mainly for MM engine)
[rocksndiamonds.git] / src / game_mm / mm_game.c
1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // mm_game.c
10 // ============================================================================
11
12 #include "main_mm.h"
13
14 #include "mm_main.h"
15 #include "mm_game.h"
16 #include "mm_tools.h"
17
18 /* graphic position values for game controls */
19 #define ENERGY_XSIZE            32
20 #define ENERGY_YSIZE            MAX_LASER_ENERGY
21 #define OVERLOAD_XSIZE          ENERGY_XSIZE
22 #define OVERLOAD_YSIZE          MAX_LASER_OVERLOAD
23
24 /* values for Explode_MM() */
25 #define EX_PHASE_START          0
26 #define EX_NORMAL               0
27 #define EX_KETTLE               1
28 #define EX_SHORT                2
29
30 /* special positions in the game control window (relative to control window) */
31 #define XX_LEVEL                36
32 #define YY_LEVEL                23
33 #define XX_KETTLES              29
34 #define YY_KETTLES              63
35 #define XX_SCORE                22
36 #define YY_SCORE                101
37 #define XX_ENERGY               8
38 #define YY_ENERGY               158
39 #define XX_OVERLOAD             60
40 #define YY_OVERLOAD             YY_ENERGY
41
42 /* special positions in the game control window (relative to main window) */
43 #define DX_LEVEL                (DX + XX_LEVEL)
44 #define DY_LEVEL                (DY + YY_LEVEL)
45 #define DX_KETTLES              (DX + XX_KETTLES)
46 #define DY_KETTLES              (DY + YY_KETTLES)
47 #define DX_SCORE                (DX + XX_SCORE)
48 #define DY_SCORE                (DY + YY_SCORE)
49 #define DX_ENERGY               (DX + XX_ENERGY)
50 #define DY_ENERGY               (DY + YY_ENERGY)
51 #define DX_OVERLOAD             (DX + XX_OVERLOAD)
52 #define DY_OVERLOAD             (DY + YY_OVERLOAD)
53
54 #define IS_LOOP_SOUND(s)        ((s) == SND_FUEL)
55 #define IS_MUSIC_SOUND(s)       ((s) == SND_TYGER || (s) == SND_VOYAGER)
56
57 /* game button identifiers */
58 #define GAME_CTRL_ID_LEFT       0
59 #define GAME_CTRL_ID_MIDDLE     1
60 #define GAME_CTRL_ID_RIGHT      2
61
62 #define NUM_GAME_BUTTONS        3
63
64 /* values for DrawLaser() */
65 #define DL_LASER_DISABLED       0
66 #define DL_LASER_ENABLED        1
67
68 /* values for 'click_delay_value' in ClickElement() */
69 #define CLICK_DELAY_SHORT       125
70 #define CLICK_DELAY_LONG        250
71 #define AUTO_ROTATE_DELAY       CLICK_DELAY_SHORT
72
73 /* forward declaration for internal use */
74 static int MovingOrBlocked2Element_MM(int, int);
75 static void Bang_MM(int, int);
76 static void RaiseScore_MM(int);
77 static void RemoveMovingField_MM(int, int);
78 static void InitMovingField_MM(int, int, int);
79 static void ContinueMoving_MM(int, int);
80 static void Moving2Blocked_MM(int, int, int *, int *);
81
82
83 static int get_element_angle(int element)
84 {
85   int element_phase = get_element_phase(element);
86
87   if (IS_MIRROR_FIXED(element) ||
88       IS_MCDUFFIN(element) ||
89       IS_LASER(element) ||
90       IS_RECEIVER(element))
91     return 4 * element_phase;
92   else
93     return element_phase;
94 }
95
96 static int get_opposite_angle(int angle)
97 {
98   int opposite_angle = angle + ANG_RAY_180;
99
100   /* make sure "opposite_angle" is in valid interval [0, 15] */
101   return (opposite_angle + 16) % 16;
102 }
103
104 static int get_mirrored_angle(int laser_angle, int mirror_angle)
105 {
106   int reflected_angle = 16 - laser_angle + mirror_angle;
107
108   /* make sure "reflected_angle" is in valid interval [0, 15] */
109   return (reflected_angle + 16) % 16;
110 }
111
112 static void InitMovDir_MM(int x, int y)
113 {
114   int element = Feld[x][y];
115   static int direction[3][4] =
116   {
117     { MV_RIGHT, MV_UP,    MV_LEFT,  MV_DOWN },
118     { MV_LEFT,  MV_DOWN,  MV_RIGHT, MV_UP   },
119     { MV_LEFT,  MV_RIGHT, MV_UP,    MV_DOWN }
120   };
121
122   switch(element)
123   {
124     case EL_PACMAN_RIGHT:
125     case EL_PACMAN_UP:
126     case EL_PACMAN_LEFT:
127     case EL_PACMAN_DOWN:
128       Feld[x][y] = EL_PACMAN;
129       MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
130       break;
131
132     default:
133       break;
134   }
135 }
136
137 static void InitField(int x, int y, boolean init_game)
138 {
139   int element = Feld[x][y];
140
141   switch (element)
142   {
143     case EL_DF_EMPTY:
144       Feld[x][y] = EL_EMPTY;
145       break;
146
147     case EL_KETTLE:
148     case EL_CELL:
149       if (native_mm_level.auto_count_kettles)
150         game_mm.kettles_still_needed++;
151       break;
152
153     case EL_LIGHTBULB_OFF:
154       game_mm.lights_still_needed++;
155       break;
156
157     default:
158       if (IS_MIRROR(element) ||
159           IS_BEAMER_OLD(element) ||
160           IS_BEAMER(element) ||
161           IS_POLAR(element) ||
162           IS_POLAR_CROSS(element) ||
163           IS_DF_MIRROR(element) ||
164           IS_DF_MIRROR_AUTO(element) ||
165           IS_GRID_STEEL_AUTO(element) ||
166           IS_GRID_WOOD_AUTO(element) ||
167           IS_FIBRE_OPTIC(element))
168       {
169         if (IS_BEAMER_OLD(element))
170         {
171           Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
172           element = Feld[x][y];
173         }
174
175         if (!IS_FIBRE_OPTIC(element))
176         {
177           static int steps_grid_auto = 0;
178
179           if (game_mm.num_cycle == 0)   /* initialize cycle steps for grids */
180             steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
181
182           if (IS_GRID_STEEL_AUTO(element) ||
183               IS_GRID_WOOD_AUTO(element))
184             game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
185           else
186             game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
187
188           game_mm.cycle[game_mm.num_cycle].x = x;
189           game_mm.cycle[game_mm.num_cycle].y = y;
190           game_mm.num_cycle++;
191         }
192
193         if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
194         {
195           int beamer_nr = BEAMER_NR(element);
196           int nr = laser.beamer[beamer_nr][0].num;
197
198           laser.beamer[beamer_nr][nr].x = x;
199           laser.beamer[beamer_nr][nr].y = y;
200           laser.beamer[beamer_nr][nr].num = 1;
201         }
202       }
203       else if (IS_PACMAN(element))
204       {
205         InitMovDir_MM(x, y);
206       }
207       else if (IS_MCDUFFIN(element) || IS_LASER(element))
208       {
209         laser.start_edge.x = x;
210         laser.start_edge.y = y;
211         laser.start_angle = get_element_angle(element);
212       }
213
214       break;
215   }
216 }
217
218 static void InitCycleElements()
219 {
220   int i, j;
221
222   if (game_mm.num_cycle == 0)   /* no elements to cycle */
223     return;
224
225   for (i = 0; i < 16; i++)
226   {
227     for (j = 0; j < game_mm.num_cycle; j++)
228     {
229       int x = game_mm.cycle[j].x;
230       int y = game_mm.cycle[j].y;
231       int step = SIGN(game_mm.cycle[j].steps);
232       int last_element = Feld[x][y];
233       int next_element = get_rotated_element(last_element, step);
234
235       if (!game_mm.cycle[j].steps)
236         continue;
237
238       Feld[x][y] = next_element;
239
240       DrawField_MM(x, y);
241       game_mm.cycle[j].steps -= step;
242     }
243
244     BackToFront();
245     ColorCycling();
246
247 #ifdef DEBUG
248     if (setup.quick_doors)
249       continue;
250 #endif
251
252     Delay(AUTO_ROTATE_DELAY);
253   }
254 }
255
256 static void InitLaser()
257 {
258   int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
259   int step = (IS_LASER(start_element) ? 4 : 0);
260
261   LX = laser.start_edge.x * TILEX;
262   if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
263     LX += 14;
264   else
265     LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
266
267   LY = laser.start_edge.y * TILEY;
268   if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
269     LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
270   else
271     LY += 14;
272
273   XS = 2 * Step[laser.start_angle].x;
274   YS = 2 * Step[laser.start_angle].y;
275
276   laser.current_angle = laser.start_angle;
277
278   laser.num_damages = 0;
279   laser.num_edges = 0;
280   laser.num_beamers = 0;
281   laser.beamer_edge[0] = 0;
282
283   AddLaserEdge(LX, LY);         /* set laser starting edge */
284
285   pen_ray = GetPixelFromRGB(window,
286                             native_mm_level.laser_red   * 0xFF,
287                             native_mm_level.laser_green * 0xFF,
288                             native_mm_level.laser_blue  * 0xFF);
289 }
290
291 void InitGameEngine_MM()
292 {
293   int i, x, y;
294
295   /* set global game control values */
296   game_mm.num_cycle = 0;
297   game_mm.num_pacman = 0;
298
299   game_mm.score = 0;
300   game_mm.energy_left = native_mm_level.time;
301   game_mm.kettles_still_needed =
302     (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
303   game_mm.lights_still_needed = 0;
304   game_mm.num_keys = 0;
305
306   game_mm.level_solved = FALSE;
307   game_mm.game_over = FALSE;
308   game_mm.game_over_cause = 0;
309
310   game_mm.laser_overload_value = 0;
311
312   /* set global laser control values (must be set before "InitLaser()") */
313   laser.start_edge.x = 0;
314   laser.start_edge.y = 0;
315   laser.start_angle = 0;
316
317   for (i = 0; i < MAX_NUM_BEAMERS; i++)
318     laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
319
320   laser.overloaded = FALSE;
321   laser.overload_value = 0;
322   laser.fuse_off = FALSE;
323   laser.fuse_x = laser.fuse_y = -1;
324
325   laser.dest_element = EL_EMPTY;
326   laser.wall_mask = 0;
327
328   CT = Ct = 0;
329
330   for (x = 0; x < lev_fieldx; x++)
331   {
332     for (y = 0; y < lev_fieldy; y++)
333     {
334       Feld[x][y] = Ur[x][y];
335       Hit[x][y] = Box[x][y] = 0;
336       Angle[x][y] = 0;
337       MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
338       Store[x][y] = Store2[x][y] = 0;
339       Frame[x][y] = 0;
340       Stop[x][y] = FALSE;
341
342       InitField(x, y, TRUE);
343     }
344   }
345
346 #if 0
347   CloseDoor(DOOR_CLOSE_1);
348 #endif
349
350   DrawLevel_MM();
351 }
352
353 void InitGameEngine_MM_AfterFadingIn()
354 {
355   InitCycleElements();
356   InitLaser();
357
358 #if 0
359   /* copy default game door content to main double buffer */
360   BlitBitmap(pix[PIX_DOOR], drawto,
361              DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
362 #endif
363
364 #if 0
365   DrawText(DX_LEVEL, DY_LEVEL,
366            int2str(level_nr, 2), FONT_TEXT_2);
367   DrawText(DX_KETTLES, DY_KETTLES,
368            int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
369   DrawText(DX_SCORE, DY_SCORE,
370            int2str(game_mm.score, 4), FONT_TEXT_2);
371 #endif
372
373 #if 0
374   UnmapGameButtons();
375   MapGameButtons();
376 #endif
377
378 #if 0
379   /* copy actual game door content to door double buffer for OpenDoor() */
380   BlitBitmap(drawto, pix[PIX_DB_DOOR],
381              DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
382 #endif
383
384 #if 0
385   OpenDoor(DOOR_OPEN_ALL);
386 #endif
387
388   if (setup.sound_loops)
389     PlaySoundExt(SND_FUEL, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
390
391 #if 0 // !!! TEMPORARILY DISABLED !!!
392   for (i = 0; i <= game_mm.energy_left; i += 2)
393   {
394     if (!setup.sound_loops)
395       PlaySoundStereo(SND_FUEL, SOUND_MAX_RIGHT);
396
397 #if 0
398     BlitBitmap(pix[PIX_DOOR], drawto,
399                DOOR_GFX_PAGEX4 + XX_ENERGY,
400                DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
401                ENERGY_XSIZE, i,
402                DX_ENERGY, DY_ENERGY + ENERGY_YSIZE - i);
403 #endif
404
405     redraw_mask |= REDRAW_DOOR_1;
406     BackToFront();
407
408     ColorCycling();
409
410 #ifdef DEBUG
411     if (setup.quick_doors)
412       continue;
413 #endif
414
415     Delay(20);
416   }
417
418   if (setup.sound_loops)
419     StopSound(SND_FUEL);
420 #endif
421
422 #if 0
423   if (setup.sound_music && num_bg_loops)
424     PlayMusic(level_nr % num_bg_loops);
425 #endif
426
427   ScanLaser();
428 }
429
430 void AddLaserEdge(int lx, int ly)
431 {
432   if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
433   {
434     Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
435
436     return;
437   }
438
439   laser.edge[laser.num_edges].x = SX + 2 + lx;
440   laser.edge[laser.num_edges].y = SY + 2 + ly;
441   laser.num_edges++;
442
443   laser.redraw = TRUE;
444 }
445
446 void AddDamagedField(int ex, int ey)
447 {
448   laser.damage[laser.num_damages].is_mirror = FALSE;
449   laser.damage[laser.num_damages].angle = laser.current_angle;
450   laser.damage[laser.num_damages].edge = laser.num_edges;
451   laser.damage[laser.num_damages].x = ex;
452   laser.damage[laser.num_damages].y = ey;
453   laser.num_damages++;
454 }
455
456 boolean StepBehind()
457 {
458   if (laser.num_edges)
459   {
460     int x = LX - XS;
461     int y = LY - YS;
462     int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
463     int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
464
465     return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
466   }
467
468   return FALSE;
469 }
470
471 static int getMaskFromElement(int element)
472 {
473   if (IS_GRID(element))
474     return IMG_MM_MASK_GRID_1 + get_element_phase(element);
475   else if (IS_MCDUFFIN(element))
476     return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
477   else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
478     return IMG_MM_MASK_RECTANGLE;
479   else
480     return IMG_MM_MASK_CIRCLE;
481 }
482
483 int ScanPixel()
484 {
485   int hit_mask = 0;
486
487 #if 0
488   printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
489          LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
490 #endif
491
492   /* follow laser beam until it hits something (at least the screen border) */
493   while (hit_mask == HIT_MASK_NO_HIT)
494   {
495     int i;
496
497 #if 0
498     /* for safety */
499     if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
500         SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
501     {
502       printf("ScanPixel: touched screen border!\n");
503
504       return HIT_MASK_ALL;
505     }
506 #endif
507
508     for (i = 0; i < 4; i++)
509     {
510       int px = LX + (i % 2) * 2;
511       int py = LY + (i / 2) * 2;
512       int dx = px % TILEX;
513       int dy = py % TILEY;
514       int lx = (px + TILEX) / TILEX - 1;  /* ...+TILEX...-1 to get correct */
515       int ly = (py + TILEY) / TILEY - 1;  /* negative values!              */
516       Pixel pixel;
517
518       if (IN_LEV_FIELD(lx, ly))
519       {
520         int element = Feld[lx][ly];
521
522         if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
523         {
524           pixel = 0;
525         }
526         else if (IS_WALL(element) || IS_WALL_CHANGING(element))
527         {
528           int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
529
530           pixel = ((element & (1 << pos)) ? 1 : 0);
531         }
532         else
533         {
534           int graphic_mask = getMaskFromElement(element);
535           Bitmap *bitmap;
536           int src_x, src_y;
537           int mask_x, mask_y;
538
539           getGraphicSource(graphic_mask, 0, &bitmap, &src_x, &src_y);
540
541           mask_x = src_x + dx;
542           mask_y = src_y + dy;
543
544           pixel = (ReadPixel(bitmap, mask_x, mask_y) ? 1 : 0);
545         }
546       }
547       else
548       {
549         pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
550                  SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
551       }
552
553       if ((Sign[laser.current_angle] & (1 << i)) && pixel)
554         hit_mask |= (1 << i);
555     }
556
557     if (hit_mask == HIT_MASK_NO_HIT)
558     {
559       /* hit nothing -- go on with another step */
560       LX += XS;
561       LY += YS;
562     }
563   }
564
565   return hit_mask;
566 }
567
568 void ScanLaser()
569 {
570   int element;
571   int end = 0, rf = laser.num_edges;
572
573   /* do not scan laser again after the game was lost for whatever reason */
574   if (game_mm.game_over)
575     return;
576
577   laser.overloaded = FALSE;
578   laser.stops_inside_element = FALSE;
579
580   DrawLaser(0, DL_LASER_ENABLED);
581
582 #if 0
583   printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
584          LX, LY, XS, YS);
585 #endif
586
587   while (1)
588   {
589     int hit_mask;
590
591     if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
592     {
593       end = 1;
594       laser.overloaded = TRUE;
595
596       break;
597     }
598
599     hit_mask = ScanPixel();
600
601 #if 0
602     printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
603            LX, LY, XS, YS);
604 #endif
605
606     /* hit something -- check out what it was */
607     ELX = (LX + XS) / TILEX;
608     ELY = (LY + YS) / TILEY;
609
610 #if 0
611     printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
612            hit_mask, LX, LY, ELX, ELY);
613 #endif
614
615     if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
616     {
617       element = EL_EMPTY;
618       laser.dest_element = element;
619
620       break;
621     }
622
623     if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
624     {
625       /* we have hit the top-right and bottom-left element --
626          choose the bottom-left one */
627       /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
628          ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
629          THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
630       ELX = (LX - 2) / TILEX;
631       ELY = (LY + 2) / TILEY;
632     }
633
634     if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
635     {
636       /* we have hit the top-left and bottom-right element --
637          choose the top-left one */
638       /* !!! SEE ABOVE !!! */
639       ELX = (LX - 2) / TILEX;
640       ELY = (LY - 2) / TILEY;
641     }
642
643 #if 0
644     printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
645            hit_mask, LX, LY, ELX, ELY);
646 #endif
647
648     element = Feld[ELX][ELY];
649     laser.dest_element = element;
650
651 #if 0
652     printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
653            element, ELX, ELY,
654            LX, LY,
655            LX % TILEX, LY % TILEY,
656            hit_mask);
657 #endif
658
659 #if 0
660     if (!IN_LEV_FIELD(ELX, ELY))
661       printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
662 #endif
663
664     if (element == EL_EMPTY)
665     {
666       if (!HitOnlyAnEdge(element, hit_mask))
667         break;
668     }
669     else if (element == EL_FUSE_ON)
670     {
671       if (HitPolarizer(element, hit_mask))
672         break;
673     }
674     else if (IS_GRID(element) || IS_DF_GRID(element))
675     {
676       if (HitPolarizer(element, hit_mask))
677         break;
678     }
679     else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
680              element == EL_GATE_STONE || element == EL_GATE_WOOD)
681     {
682       if (HitBlock(element, hit_mask))
683       {
684         rf = 1;
685
686         break;
687       }
688     }
689     else if (IS_MCDUFFIN(element))
690     {
691       if (HitLaserSource(element, hit_mask))
692         break;
693     }
694     else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
695              IS_RECEIVER(element))
696     {
697       if (HitLaserDestination(element, hit_mask))
698         break;
699     }
700     else if (IS_WALL(element))
701     {
702       if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
703       {
704         if (HitReflectingWalls(element, hit_mask))
705           break;
706       }
707       else
708       {
709         if (HitAbsorbingWalls(element, hit_mask))
710           break;
711       }
712     }
713     else
714     {
715       if (HitElement(element, hit_mask))
716         break;
717     }
718
719     if (rf)
720       DrawLaser(rf - 1, DL_LASER_ENABLED);
721     rf = laser.num_edges;
722   }
723
724 #if 0
725   if (laser.dest_element != Feld[ELX][ELY])
726   {
727     printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
728            laser.dest_element, Feld[ELX][ELY]);
729   }
730 #endif
731
732   if (!end && !laser.stops_inside_element && !StepBehind())
733   {
734 #if 0
735     printf("ScanLaser: Go one step back\n");
736 #endif
737
738     LX -= XS;
739     LY -= YS;
740
741     AddLaserEdge(LX, LY);
742   }
743
744   if (rf)
745     DrawLaser(rf - 1, DL_LASER_ENABLED);
746
747   Ct = CT = Counter();
748
749 #if 0
750     if (!IN_LEV_FIELD(ELX, ELY))
751       printf("WARNING! (2) %d, %d\n", ELX, ELY);
752 #endif
753 }
754
755 void DrawLaserExt(int start_edge, int num_edges, int mode)
756 {
757   int element;
758   int elx, ely;
759
760 #if 0
761   printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
762          start_edge, num_edges, mode);
763 #endif
764
765   if (start_edge < 0)
766   {
767     Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
768
769     return;
770   }
771
772   if (num_edges < 0)
773   {
774     Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
775
776     return;
777   }
778
779 #if 0
780   if (mode == DL_LASER_DISABLED)
781   {
782     printf("DrawLaser: Delete laser from edge %d\n", start_edge);
783   }
784 #endif
785
786   /* now draw the laser to the backbuffer and (if enabled) to the screen */
787   DrawLines(drawto, &laser.edge[start_edge], num_edges,
788             (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
789
790   redraw_mask |= REDRAW_FIELD;
791
792   if (mode == DL_LASER_ENABLED)
793     return;
794
795   /* after the laser was deleted, the "damaged" graphics must be restored */
796   if (laser.num_damages)
797   {
798     int damage_start = 0;
799     int i;
800
801     /* determine the starting edge, from which graphics need to be restored */
802     if (start_edge > 0)
803     {
804       for (i = 0; i < laser.num_damages; i++)
805       {
806         if (laser.damage[i].edge == start_edge + 1)
807         {
808           damage_start = i;
809
810           break;
811         }
812       }
813     }
814
815     /* restore graphics from this starting edge to the end of damage list */
816     for (i = damage_start; i < laser.num_damages; i++)
817     {
818       int lx = laser.damage[i].x;
819       int ly = laser.damage[i].y;
820       int element = Feld[lx][ly];
821
822       if (Hit[lx][ly] == laser.damage[i].edge)
823         if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
824                i == damage_start))
825           Hit[lx][ly] = 0;
826       if (Box[lx][ly] == laser.damage[i].edge)
827         Box[lx][ly] = 0;
828
829       if (IS_DRAWABLE(element))
830         DrawField_MM(lx, ly);
831     }
832
833     elx = laser.damage[damage_start].x;
834     ely = laser.damage[damage_start].y;
835     element = Feld[elx][ely];
836
837 #if 0
838     if (IS_BEAMER(element))
839     {
840       int i;
841
842       for (i = 0; i < laser.num_beamers; i++)
843         printf("-> %d\n", laser.beamer_edge[i]);
844       printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
845              mode, elx, ely, Hit[elx][ely], start_edge);
846       printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
847              get_element_angle(element), laser.damage[damage_start].angle);
848     }
849 #endif
850
851     if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
852         laser.num_beamers > 0 &&
853         start_edge == laser.beamer_edge[laser.num_beamers - 1])
854     {
855       /* element is outgoing beamer */
856       laser.num_damages = damage_start + 1;
857
858       if (IS_BEAMER(element))
859         laser.current_angle = get_element_angle(element);
860     }
861     else
862     {
863       /* element is incoming beamer or other element */
864       laser.num_damages = damage_start;
865       laser.current_angle = laser.damage[laser.num_damages].angle;
866     }
867   }
868   else
869   {
870     /* no damages but McDuffin himself (who needs to be redrawn anyway) */
871
872     elx = laser.start_edge.x;
873     ely = laser.start_edge.y;
874     element = Feld[elx][ely];
875   }
876
877   laser.num_edges = start_edge + 1;
878   if (start_edge == 0)
879     laser.current_angle = laser.start_angle;
880
881   LX = laser.edge[start_edge].x - (SX + 2);
882   LY = laser.edge[start_edge].y - (SY + 2);
883   XS = 2 * Step[laser.current_angle].x;
884   YS = 2 * Step[laser.current_angle].y;
885
886 #if 0
887   printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
888          LX, LY, element);
889 #endif
890
891   if (start_edge > 0)
892   {
893     if (IS_BEAMER(element) ||
894         IS_FIBRE_OPTIC(element) ||
895         IS_PACMAN(element) ||
896         IS_POLAR(element) ||
897         IS_POLAR_CROSS(element) ||
898         element == EL_FUSE_ON)
899     {
900       int step_size;
901
902 #if 0
903       printf("element == %d\n", element);
904 #endif
905
906       if (IS_22_5_ANGLE(laser.current_angle))   /* neither 90° nor 45° angle */
907         step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
908       else
909         step_size = 8;
910
911       if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
912           ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
913            (laser.num_beamers == 0 ||
914             start_edge != laser.beamer_edge[laser.num_beamers - 1])))
915       {
916         /* element is incoming beamer or other element */
917         step_size = -step_size;
918         laser.num_edges--;
919       }
920
921 #if 0
922       if (IS_BEAMER(element))
923       {
924         printf("start_edge == %d, laser.beamer_edge == %d\n",
925                start_edge, laser.beamer_edge);
926       }
927 #endif
928
929       LX += step_size * XS;
930       LY += step_size * YS;
931     }
932     else if (element != EL_EMPTY)
933     {
934       LX -= 3 * XS;
935       LY -= 3 * YS;
936       laser.num_edges--;
937     }
938   }
939
940 #if 0
941   printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
942          LX, LY, element);
943 #endif
944 }
945
946 void DrawLaser(int start_edge, int mode)
947 {
948   if (laser.num_edges - start_edge < 0)
949   {
950     Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
951
952     return;
953   }
954
955   /* check if laser is interrupted by beamer element */
956   if (laser.num_beamers > 0 &&
957       start_edge < laser.beamer_edge[laser.num_beamers - 1])
958   {
959     if (mode == DL_LASER_ENABLED)
960     {
961       int i;
962       int tmp_start_edge = start_edge;
963
964       /* draw laser segments forward from the start to the last beamer */
965       for (i = 0; i < laser.num_beamers; i++)
966       {
967         int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
968
969         if (tmp_num_edges <= 0)
970           continue;
971
972 #if 0
973         printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
974                i, laser.beamer_edge[i], tmp_start_edge);
975 #endif
976
977         DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
978
979         tmp_start_edge = laser.beamer_edge[i];
980       }
981
982       /* draw last segment from last beamer to the end */
983       DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
984                    DL_LASER_ENABLED);
985     }
986     else
987     {
988       int i;
989       int last_num_edges = laser.num_edges;
990       int num_beamers = laser.num_beamers;
991
992       /* delete laser segments backward from the end to the first beamer */
993       for (i = num_beamers-1; i >= 0; i--)
994       {
995         int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
996
997         if (laser.beamer_edge[i] - start_edge <= 0)
998           break;
999
1000         DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1001
1002         last_num_edges = laser.beamer_edge[i];
1003         laser.num_beamers--;
1004       }
1005
1006 #if 0
1007       if (last_num_edges - start_edge <= 0)
1008         printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1009                last_num_edges, start_edge);
1010 #endif
1011
1012       /* delete first segment from start to the first beamer */
1013       DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1014     }
1015   }
1016   else
1017   {
1018     DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1019   }
1020 }
1021
1022 boolean HitElement(int element, int hit_mask)
1023 {
1024   if (HitOnlyAnEdge(element, hit_mask))
1025     return FALSE;
1026
1027   if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1028     element = MovingOrBlocked2Element_MM(ELX, ELY);
1029
1030 #if 0
1031   printf("HitElement (1): element == %d\n", element);
1032 #endif
1033
1034 #if 0
1035   if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1036     printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1037   else
1038     printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1039 #endif
1040
1041   AddDamagedField(ELX, ELY);
1042
1043   /* this is more precise: check if laser would go through the center */
1044   if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1045   {
1046     /* skip the whole element before continuing the scan */
1047     do
1048     {
1049       LX += XS;
1050       LY += YS;
1051     }
1052     while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1053
1054     if (LX/TILEX > ELX || LY/TILEY > ELY)
1055     {
1056       /* skipping scan positions to the right and down skips one scan
1057          position too much, because this is only the top left scan position
1058          of totally four scan positions (plus one to the right, one to the
1059          bottom and one to the bottom right) */
1060
1061       LX -= XS;
1062       LY -= YS;
1063     }
1064
1065     return FALSE;
1066   }
1067
1068 #if 0
1069   printf("HitElement (2): element == %d\n", element);
1070 #endif
1071
1072   if (LX + 5 * XS < 0 ||
1073       LY + 5 * YS < 0)
1074   {
1075     LX += 2 * XS;
1076     LY += 2 * YS;
1077
1078     return FALSE;
1079   }
1080
1081 #if 0
1082   printf("HitElement (3): element == %d\n", element);
1083 #endif
1084
1085   if (IS_POLAR(element) &&
1086       ((element - EL_POLAR_START) % 2 ||
1087        (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1088   {
1089     PlaySoundStereo(SND_KINK, ST(ELX));
1090
1091     laser.num_damages--;
1092
1093     return TRUE;
1094   }
1095
1096   if (IS_POLAR_CROSS(element) &&
1097       (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1098   {
1099     PlaySoundStereo(SND_KINK, ST(ELX));
1100
1101     laser.num_damages--;
1102
1103     return TRUE;
1104   }
1105
1106   if (!IS_BEAMER(element) &&
1107       !IS_FIBRE_OPTIC(element) &&
1108       !IS_GRID_WOOD(element) &&
1109       element != EL_FUEL_EMPTY)
1110   {
1111 #if 0
1112     if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1113       printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1114     else
1115       printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1116 #endif
1117
1118     LX = ELX * TILEX + 14;
1119     LY = ELY * TILEY + 14;
1120
1121     AddLaserEdge(LX, LY);
1122   }
1123
1124   if (IS_MIRROR(element) ||
1125       IS_MIRROR_FIXED(element) ||
1126       IS_POLAR(element) ||
1127       IS_POLAR_CROSS(element) ||
1128       IS_DF_MIRROR(element) ||
1129       IS_DF_MIRROR_AUTO(element) ||
1130       element == EL_PRISM ||
1131       element == EL_REFRACTOR)
1132   {
1133     int current_angle = laser.current_angle;
1134     int step_size;
1135
1136     laser.num_damages--;
1137
1138     AddDamagedField(ELX, ELY);
1139
1140     laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1141
1142     if (!Hit[ELX][ELY])
1143       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1144
1145     if (IS_MIRROR(element) ||
1146         IS_MIRROR_FIXED(element) ||
1147         IS_DF_MIRROR(element) ||
1148         IS_DF_MIRROR_AUTO(element))
1149       laser.current_angle = get_mirrored_angle(laser.current_angle,
1150                                                get_element_angle(element));
1151
1152     if (element == EL_PRISM || element == EL_REFRACTOR)
1153       laser.current_angle = RND(16);
1154
1155     XS = 2 * Step[laser.current_angle].x;
1156     YS = 2 * Step[laser.current_angle].y;
1157
1158     if (!IS_22_5_ANGLE(laser.current_angle))    /* 90° or 45° angle */
1159       step_size = 8;
1160     else
1161       step_size = 4;
1162
1163     LX += step_size * XS;
1164     LY += step_size * YS;
1165
1166 #if 0
1167     /* draw sparkles on mirror */
1168     if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1169         current_angle != laser.current_angle)
1170     {
1171       MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1172     }
1173 #endif
1174
1175     if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1176         current_angle != laser.current_angle)
1177       PlaySoundStereo(SND_LASER, ST(ELX));
1178
1179     laser.overloaded =
1180       (get_opposite_angle(laser.current_angle) ==
1181        laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1182
1183     return (laser.overloaded ? TRUE : FALSE);
1184   }
1185
1186   if (element == EL_FUEL_FULL)
1187   {
1188     laser.stops_inside_element = TRUE;
1189
1190     return TRUE;
1191   }
1192
1193   if (element == EL_BOMB || element == EL_MINE)
1194   {
1195     PlaySoundStereo(SND_KINK, ST(ELX));
1196
1197     if (element == EL_MINE)
1198       laser.overloaded = TRUE;
1199   }
1200
1201   if (element == EL_KETTLE ||
1202       element == EL_CELL ||
1203       element == EL_KEY ||
1204       element == EL_LIGHTBALL ||
1205       element == EL_PACMAN ||
1206       IS_PACMAN(element))
1207   {
1208     if (!IS_PACMAN(element))
1209       Bang_MM(ELX, ELY);
1210
1211     if (element == EL_PACMAN)
1212       Bang_MM(ELX, ELY);
1213
1214     if (element == EL_KETTLE || element == EL_CELL)
1215     {
1216       if (game_mm.kettles_still_needed > 0)
1217         game_mm.kettles_still_needed--;
1218
1219       RaiseScore_MM(10);
1220
1221       if (game_mm.kettles_still_needed == 0)
1222       {
1223         int x, y;
1224         static int xy[4][2] =
1225         {
1226           { +1,  0 },
1227           {  0, -1 },
1228           { -1,  0 },
1229           {  0, +1 }
1230         };
1231
1232         PlaySoundStereo(SND_KLING, ST(ELX));
1233
1234         for (y = 0; y < lev_fieldy; y++)
1235         {
1236           for (x = 0; x < lev_fieldx; x++)
1237           {
1238             /* initiate opening animation of exit door */
1239             if (Feld[x][y] == EL_EXIT_CLOSED)
1240               Feld[x][y] = EL_EXIT_OPENING;
1241
1242             /* remove field that blocks receiver */
1243             if (IS_RECEIVER(Feld[x][y]))
1244             {
1245               int phase = Feld[x][y] - EL_RECEIVER_START;
1246               int blocking_x, blocking_y;
1247
1248               blocking_x = x + xy[phase][0];
1249               blocking_y = y + xy[phase][1];
1250
1251               if (IN_LEV_FIELD(blocking_x, blocking_y))
1252               {
1253                 Feld[blocking_x][blocking_y] = EL_EMPTY;
1254
1255                 DrawField_MM(blocking_x, blocking_y);
1256               }
1257             }
1258           }
1259         }
1260
1261         DrawLaser(0, DL_LASER_ENABLED);
1262       }
1263     }
1264     else if (element == EL_KEY)
1265     {
1266       game_mm.num_keys++;
1267     }
1268     else if (element == EL_LIGHTBALL)
1269     {
1270       RaiseScore_MM(10);
1271     }
1272     else if (IS_PACMAN(element))
1273     {
1274       DeletePacMan(ELX, ELY);
1275       RaiseScore_MM(50);
1276     }
1277
1278     return FALSE;
1279   }
1280
1281   if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1282   {
1283     PlaySoundStereo(SND_KINK, ST(ELX));
1284
1285     DrawLaser(0, DL_LASER_ENABLED);
1286
1287     if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1288     {
1289       Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1290       game_mm.lights_still_needed--;
1291     }
1292     else
1293     {
1294       Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1295       game_mm.lights_still_needed++;
1296     }
1297
1298     DrawField_MM(ELX, ELY);
1299     DrawLaser(0, DL_LASER_ENABLED);
1300
1301     /*
1302     BackToFront();
1303     */
1304     laser.stops_inside_element = TRUE;
1305
1306     return TRUE;
1307   }
1308
1309 #if 0
1310   printf("HitElement (4): element == %d\n", element);
1311 #endif
1312
1313   if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1314       laser.num_beamers < MAX_NUM_BEAMERS &&
1315       laser.beamer[BEAMER_NR(element)][1].num)
1316   {
1317     int beamer_angle = get_element_angle(element);
1318     int beamer_nr = BEAMER_NR(element);
1319     int step_size;
1320
1321 #if 0
1322   printf("HitElement (BEAMER): element == %d\n", element);
1323 #endif
1324
1325     laser.num_damages--;
1326
1327     if (IS_FIBRE_OPTIC(element) ||
1328         laser.current_angle == get_opposite_angle(beamer_angle))
1329     {
1330       int pos;
1331
1332       LX = ELX * TILEX + 14;
1333       LY = ELY * TILEY + 14;
1334
1335       AddLaserEdge(LX, LY);
1336       AddDamagedField(ELX, ELY);
1337
1338       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1339
1340       if (!Hit[ELX][ELY])
1341         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1342
1343       pos = (ELX == laser.beamer[beamer_nr][0].x &&
1344              ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1345       ELX = laser.beamer[beamer_nr][pos].x;
1346       ELY = laser.beamer[beamer_nr][pos].y;
1347       LX = ELX * TILEX + 14;
1348       LY = ELY * TILEY + 14;
1349
1350       if (IS_BEAMER(element))
1351       {
1352         laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1353         XS = 2 * Step[laser.current_angle].x;
1354         YS = 2 * Step[laser.current_angle].y;
1355       }
1356
1357       laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1358
1359       AddLaserEdge(LX, LY);
1360       AddDamagedField(ELX, ELY);
1361
1362       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1363
1364       if (!Hit[ELX][ELY])
1365         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1366
1367       if (laser.current_angle == (laser.current_angle >> 1) << 1)
1368         step_size = 8;
1369       else
1370         step_size = 4;
1371
1372       LX += step_size * XS;
1373       LY += step_size * YS;
1374
1375       laser.num_beamers++;
1376
1377       return FALSE;
1378     }
1379   }
1380
1381   return TRUE;
1382 }
1383
1384 boolean HitOnlyAnEdge(int element, int hit_mask)
1385 {
1386   /* check if the laser hit only the edge of an element and, if so, go on */
1387
1388 #if 0
1389   printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1390 #endif
1391
1392   if ((hit_mask == HIT_MASK_TOPLEFT ||
1393        hit_mask == HIT_MASK_TOPRIGHT ||
1394        hit_mask == HIT_MASK_BOTTOMLEFT ||
1395        hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1396       laser.current_angle % 4)                  /* angle is not 90° */
1397   {
1398     int dx, dy;
1399
1400     if (hit_mask == HIT_MASK_TOPLEFT)
1401     {
1402       dx = -1;
1403       dy = -1;
1404     }
1405     else if (hit_mask == HIT_MASK_TOPRIGHT)
1406     {
1407       dx = +1;
1408       dy = -1;
1409     }
1410     else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1411     {
1412       dx = -1;
1413       dy = +1;
1414     }
1415     else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1416     {
1417       dx = +1;
1418       dy = +1;
1419     }
1420
1421     AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1422
1423     LX += XS;
1424     LY += YS;
1425
1426 #if 0
1427     printf("[HitOnlyAnEdge() == TRUE]\n");
1428 #endif
1429
1430     return TRUE;
1431   }
1432
1433 #if 0
1434     printf("[HitOnlyAnEdge() == FALSE]\n");
1435 #endif
1436
1437   return FALSE;
1438 }
1439
1440 boolean HitPolarizer(int element, int hit_mask)
1441 {
1442   if (HitOnlyAnEdge(element, hit_mask))
1443     return FALSE;
1444
1445   if (IS_DF_GRID(element))
1446   {
1447     int grid_angle = get_element_angle(element);
1448
1449 #if 0
1450     printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1451            grid_angle, laser.current_angle);
1452 #endif
1453
1454     AddLaserEdge(LX, LY);
1455     AddDamagedField(ELX, ELY);
1456
1457     if (!Hit[ELX][ELY])
1458       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1459
1460     if (laser.current_angle == grid_angle ||
1461         laser.current_angle == get_opposite_angle(grid_angle))
1462     {
1463       /* skip the whole element before continuing the scan */
1464       do
1465       {
1466         LX += XS;
1467         LY += YS;
1468       }
1469       while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1470
1471       if (LX/TILEX > ELX || LY/TILEY > ELY)
1472       {
1473         /* skipping scan positions to the right and down skips one scan
1474            position too much, because this is only the top left scan position
1475            of totally four scan positions (plus one to the right, one to the
1476            bottom and one to the bottom right) */
1477
1478         LX -= XS;
1479         LY -= YS;
1480       }
1481
1482       AddLaserEdge(LX, LY);
1483
1484       LX += XS;
1485       LY += YS;
1486
1487 #if 0
1488       printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1489              LX, LY,
1490              LX / TILEX, LY / TILEY,
1491              LX % TILEX, LY % TILEY);
1492 #endif
1493
1494       return FALSE;
1495     }
1496     else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1497     {
1498       return HitReflectingWalls(element, hit_mask);
1499     }
1500     else
1501     {
1502       return HitAbsorbingWalls(element, hit_mask);
1503     }
1504   }
1505   else if (IS_GRID_STEEL(element))
1506   {
1507     return HitReflectingWalls(element, hit_mask);
1508   }
1509   else  /* IS_GRID_WOOD */
1510   {
1511     return HitAbsorbingWalls(element, hit_mask);
1512   }
1513
1514   return TRUE;
1515 }
1516
1517 boolean HitBlock(int element, int hit_mask)
1518 {
1519   boolean check = FALSE;
1520
1521   if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1522       game_mm.num_keys == 0)
1523     check = TRUE;
1524
1525   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1526   {
1527     int i, x, y;
1528     int ex = ELX * TILEX + 14;
1529     int ey = ELY * TILEY + 14;
1530
1531     check = TRUE;
1532
1533     for (i = 1; i < 32; i++)
1534     {
1535       x = LX + i * XS;
1536       y = LY + i * YS;
1537
1538       if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1539         check = FALSE;
1540     }
1541   }
1542
1543   if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1544     return HitAbsorbingWalls(element, hit_mask);
1545
1546   if (check)
1547   {
1548     AddLaserEdge(LX - XS, LY - YS);
1549     AddDamagedField(ELX, ELY);
1550
1551     if (!Box[ELX][ELY])
1552       Box[ELX][ELY] = laser.num_edges;
1553
1554     return HitReflectingWalls(element, hit_mask);
1555   }
1556
1557   if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1558   {
1559     int xs = XS / 2, ys = YS / 2;
1560     int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1561     int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1562
1563     if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1564         (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1565     {
1566       laser.overloaded = (element == EL_GATE_STONE);
1567
1568       return TRUE;
1569     }
1570
1571     if (ABS(xs) == 1 && ABS(ys) == 1 &&
1572         (hit_mask == HIT_MASK_TOP ||
1573          hit_mask == HIT_MASK_LEFT ||
1574          hit_mask == HIT_MASK_RIGHT ||
1575          hit_mask == HIT_MASK_BOTTOM))
1576       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1577                                   hit_mask == HIT_MASK_BOTTOM),
1578                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1579                                   hit_mask == HIT_MASK_RIGHT));
1580     AddLaserEdge(LX, LY);
1581
1582     Bang_MM(ELX, ELY);
1583
1584     game_mm.num_keys--;
1585
1586     if (element == EL_GATE_STONE && Box[ELX][ELY])
1587     {
1588       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1589       /*
1590       BackToFront();
1591       */
1592       ScanLaser();
1593
1594       return TRUE;
1595     }
1596
1597     return FALSE;
1598   }
1599
1600   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1601   {
1602     int xs = XS / 2, ys = YS / 2;
1603     int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1604     int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1605
1606     if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1607         (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1608     {
1609       laser.overloaded = (element == EL_BLOCK_STONE);
1610
1611       return TRUE;
1612     }
1613
1614     if (ABS(xs) == 1 && ABS(ys) == 1 &&
1615         (hit_mask == HIT_MASK_TOP ||
1616          hit_mask == HIT_MASK_LEFT ||
1617          hit_mask == HIT_MASK_RIGHT ||
1618          hit_mask == HIT_MASK_BOTTOM))
1619       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1620                                   hit_mask == HIT_MASK_BOTTOM),
1621                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1622                                   hit_mask == HIT_MASK_RIGHT));
1623     AddDamagedField(ELX, ELY);
1624
1625     LX = ELX * TILEX + 14;
1626     LY = ELY * TILEY + 14;
1627
1628     AddLaserEdge(LX, LY);
1629
1630     laser.stops_inside_element = TRUE;
1631
1632     return TRUE;
1633   }
1634
1635   return TRUE;
1636 }
1637
1638 boolean HitLaserSource(int element, int hit_mask)
1639 {
1640    if (HitOnlyAnEdge(element, hit_mask))
1641      return FALSE;
1642
1643    PlaySoundStereo(SND_AUTSCH, ST(ELX));
1644    laser.overloaded = TRUE;
1645
1646    return TRUE;
1647 }
1648
1649 boolean HitLaserDestination(int element, int hit_mask)
1650 {
1651   if (HitOnlyAnEdge(element, hit_mask))
1652     return FALSE;
1653
1654   if (element != EL_EXIT_OPEN &&
1655       !(IS_RECEIVER(element) &&
1656         game_mm.kettles_still_needed == 0 &&
1657         laser.current_angle == get_opposite_angle(get_element_angle(element))))
1658   {
1659     PlaySoundStereo(SND_HOLZ, ST(ELX));
1660     return TRUE;
1661   }
1662
1663   if (IS_RECEIVER(element) ||
1664       (IS_22_5_ANGLE(laser.current_angle) &&
1665        (ELX != (LX + 6 * XS) / TILEX ||
1666         ELY != (LY + 6 * YS) / TILEY ||
1667         LX + 6 * XS < 0 ||
1668         LY + 6 * YS < 0)))
1669   {
1670     LX -= XS;
1671     LY -= YS;
1672   }
1673   else
1674   {
1675     LX = ELX * TILEX + 14;
1676     LY = ELY * TILEY + 14;
1677
1678     laser.stops_inside_element = TRUE;
1679   }
1680
1681   AddLaserEdge(LX, LY);
1682   AddDamagedField(ELX, ELY);
1683
1684   if (game_mm.lights_still_needed == 0)
1685     game_mm.level_solved = TRUE;
1686
1687   return TRUE;
1688 }
1689
1690 boolean HitReflectingWalls(int element, int hit_mask)
1691 {
1692   /* check if laser hits side of a wall with an angle that is not 90° */
1693   if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1694                                             hit_mask == HIT_MASK_LEFT ||
1695                                             hit_mask == HIT_MASK_RIGHT ||
1696                                             hit_mask == HIT_MASK_BOTTOM))
1697   {
1698     PlaySoundStereo(SND_HUI, ST(ELX));
1699
1700     LX -= XS;
1701     LY -= YS;
1702
1703     if (!IS_DF_GRID(element))
1704       AddLaserEdge(LX, LY);
1705
1706     /* check if laser hits wall with an angle of 45° */
1707     if (!IS_22_5_ANGLE(laser.current_angle))
1708     {
1709       if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1710       {
1711         LX += 2 * XS;
1712         laser.current_angle = get_mirrored_angle(laser.current_angle,
1713                                                  ANG_MIRROR_0);
1714       }
1715       else      /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1716       {
1717         LY += 2 * YS;
1718         laser.current_angle = get_mirrored_angle(laser.current_angle,
1719                                                  ANG_MIRROR_90);
1720       }
1721
1722       AddLaserEdge(LX, LY);
1723
1724       XS = 2 * Step[laser.current_angle].x;
1725       YS = 2 * Step[laser.current_angle].y;
1726
1727       return FALSE;
1728     }
1729     else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1730     {
1731       laser.current_angle = get_mirrored_angle(laser.current_angle,
1732                                                ANG_MIRROR_0);
1733       if (ABS(XS) == 4)
1734       {
1735         LX += 2 * XS;
1736         if (!IS_DF_GRID(element))
1737           AddLaserEdge(LX, LY);
1738       }
1739       else
1740       {
1741         LX += XS;
1742         if (!IS_DF_GRID(element))
1743           AddLaserEdge(LX, LY + YS / 2);
1744
1745         LX += XS;
1746         if (!IS_DF_GRID(element))
1747           AddLaserEdge(LX, LY);
1748       }
1749
1750       YS = 2 * Step[laser.current_angle].y;
1751
1752       return FALSE;
1753     }
1754     else        /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1755     {
1756       laser.current_angle = get_mirrored_angle(laser.current_angle,
1757                                                ANG_MIRROR_90);
1758       if (ABS(YS) == 4)
1759       {
1760         LY += 2 * YS;
1761         if (!IS_DF_GRID(element))
1762           AddLaserEdge(LX, LY);
1763       }
1764       else
1765       {
1766         LY += YS;
1767         if (!IS_DF_GRID(element))
1768           AddLaserEdge(LX + XS / 2, LY);
1769
1770         LY += YS;
1771         if (!IS_DF_GRID(element))
1772           AddLaserEdge(LX, LY);
1773       }
1774
1775       XS = 2 * Step[laser.current_angle].x;
1776
1777       return FALSE;
1778     }
1779   }
1780
1781   /* reflection at the edge of reflecting DF style wall */
1782   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1783   {
1784     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1785          hit_mask == HIT_MASK_TOPRIGHT) ||
1786         ((laser.current_angle == 5 || laser.current_angle == 7) &&
1787          hit_mask == HIT_MASK_TOPLEFT) ||
1788         ((laser.current_angle == 9 || laser.current_angle == 11) &&
1789          hit_mask == HIT_MASK_BOTTOMLEFT) ||
1790         ((laser.current_angle == 13 || laser.current_angle == 15) &&
1791          hit_mask == HIT_MASK_BOTTOMRIGHT))
1792     {
1793       int mirror_angle =
1794         (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
1795          ANG_MIRROR_135 : ANG_MIRROR_45);
1796
1797       PlaySoundStereo(SND_HUI, ST(ELX));
1798
1799       AddDamagedField(ELX, ELY);
1800       AddLaserEdge(LX, LY);
1801
1802       laser.current_angle = get_mirrored_angle(laser.current_angle,
1803                                                mirror_angle);
1804       XS = 8 / -XS;
1805       YS = 8 / -YS;
1806
1807       LX += XS;
1808       LY += YS;
1809
1810       AddLaserEdge(LX, LY);
1811
1812       return FALSE;
1813     }
1814   }
1815
1816   /* reflection inside an edge of reflecting DF style wall */
1817   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1818   {
1819     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1820          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
1821         ((laser.current_angle == 5 || laser.current_angle == 7) &&
1822          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
1823         ((laser.current_angle == 9 || laser.current_angle == 11) &&
1824          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
1825         ((laser.current_angle == 13 || laser.current_angle == 15) &&
1826          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
1827     {
1828       int mirror_angle =
1829         (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
1830          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
1831          ANG_MIRROR_135 : ANG_MIRROR_45);
1832
1833       PlaySoundStereo(SND_HUI, ST(ELX));
1834
1835       /*
1836       AddDamagedField(ELX, ELY);
1837       */
1838
1839       AddLaserEdge(LX - XS, LY - YS);
1840       AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
1841                    LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
1842
1843       laser.current_angle = get_mirrored_angle(laser.current_angle,
1844                                                mirror_angle);
1845       XS = 8 / -XS;
1846       YS = 8 / -YS;
1847
1848       LX += XS;
1849       LY += YS;
1850
1851       AddLaserEdge(LX, LY);
1852
1853       return FALSE;
1854     }
1855   }
1856
1857   /* check if laser hits DF style wall with an angle of 90° */
1858   if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
1859   {
1860     if ((IS_HORIZ_ANGLE(laser.current_angle) &&
1861          (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
1862         (IS_VERT_ANGLE(laser.current_angle) &&
1863          (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
1864     {
1865       static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
1866
1867       /* laser at last step touched nothing or the same side of the wall */
1868       if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
1869       {
1870         AddDamagedField(ELX, ELY);
1871
1872         LX += 8 * XS;
1873         LY += 8 * YS;
1874
1875         last_LX = LX;
1876         last_LY = LY;
1877         last_hit_mask = hit_mask;
1878
1879         return FALSE;
1880       }
1881     }
1882   }
1883
1884   if (!HitOnlyAnEdge(element, hit_mask))
1885   {
1886     laser.overloaded = TRUE;
1887
1888     return TRUE;
1889   }
1890
1891   return FALSE;
1892 }
1893
1894 boolean HitAbsorbingWalls(int element, int hit_mask)
1895 {
1896   if (HitOnlyAnEdge(element, hit_mask))
1897     return FALSE;
1898
1899   if (ABS(XS) == 4 &&
1900       (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
1901   {
1902     AddLaserEdge(LX - XS, LY - YS);
1903
1904     LX = LX + XS / 2;
1905     LY = LY + YS;
1906   }
1907
1908   if (ABS(YS) == 4 &&
1909       (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
1910   {
1911     AddLaserEdge(LX - XS, LY - YS);
1912
1913     LX = LX + XS;
1914     LY = LY + YS / 2;
1915   }
1916
1917   if (IS_WALL_WOOD(element) ||
1918       IS_DF_WALL_WOOD(element) ||
1919       IS_GRID_WOOD(element) ||
1920       IS_GRID_WOOD_FIXED(element) ||
1921       IS_GRID_WOOD_AUTO(element) ||
1922       element == EL_FUSE_ON ||
1923       element == EL_BLOCK_WOOD ||
1924       element == EL_GATE_WOOD)
1925   {
1926     PlaySoundStereo(SND_HOLZ, ST(ELX));
1927
1928     return TRUE;
1929   }
1930
1931   if (IS_WALL_ICE(element))
1932   {
1933     int mask;
1934
1935     mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1;    /* Quadrant (horizontal) */
1936     mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2;  /* || (vertical) */
1937
1938     /* check if laser hits wall with an angle of 90° */
1939     if (IS_90_ANGLE(laser.current_angle))
1940       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1941
1942     if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
1943     {
1944       int i;
1945
1946       for (i = 0; i < 4; i++)
1947       {
1948         if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
1949           mask = 15 - (8 >> i);
1950         else if (ABS(XS) == 4 &&
1951                  mask == (1 << i) &&
1952                  (XS > 0) == (i % 2) &&
1953                  (YS < 0) == (i / 2))
1954           mask = 3 + (i / 2) * 9;
1955         else if (ABS(YS) == 4 &&
1956                  mask == (1 << i) &&
1957                  (XS < 0) == (i % 2) &&
1958                  (YS > 0) == (i / 2))
1959           mask = 5 + (i % 2) * 5;
1960       }
1961     }
1962
1963     laser.wall_mask = mask;
1964   }
1965   else if (IS_WALL_AMOEBA(element))
1966   {
1967     int elx = (LX - 2 * XS) / TILEX;
1968     int ely = (LY - 2 * YS) / TILEY;
1969     int element2 = Feld[elx][ely];
1970     int mask;
1971
1972     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
1973     {
1974       laser.dest_element = EL_EMPTY;
1975
1976       return TRUE;
1977     }
1978
1979     ELX = elx;
1980     ELY = ely;
1981
1982     mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
1983     mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
1984
1985     if (IS_90_ANGLE(laser.current_angle))
1986       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1987
1988     laser.dest_element = element2 | EL_WALL_AMOEBA;
1989
1990     laser.wall_mask = mask;
1991   }
1992
1993   return TRUE;
1994 }
1995
1996 void OpenExit(int x, int y)
1997 {
1998   int delay = 6;
1999
2000   if (!MovDelay[x][y])          /* next animation frame */
2001     MovDelay[x][y] = 4 * delay;
2002
2003   if (MovDelay[x][y])           /* wait some time before next frame */
2004   {
2005     int phase;
2006
2007     MovDelay[x][y]--;
2008     phase = MovDelay[x][y] / delay;
2009
2010     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2011       DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2012
2013     if (!MovDelay[x][y])
2014     {
2015       Feld[x][y] = EL_EXIT_OPEN;
2016       DrawField_MM(x, y);
2017     }
2018   }
2019 }
2020
2021 void OpenSurpriseBall(int x, int y)
2022 {
2023   int delay = 2;
2024
2025   if (!MovDelay[x][y])          /* next animation frame */
2026     MovDelay[x][y] = 50 * delay;
2027
2028   if (MovDelay[x][y])           /* wait some time before next frame */
2029   {
2030     MovDelay[x][y]--;
2031
2032     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2033     {
2034       Bitmap *bitmap;
2035       int graphic = el2gfx(Store[x][y]);
2036       int gx, gy;
2037       int dx = RND(26), dy = RND(26);
2038
2039       getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2040
2041       BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2042                  SX + x * TILEX + dx, SY + y * TILEY + dy);
2043
2044       MarkTileDirty(x, y);
2045     }
2046
2047     if (!MovDelay[x][y])
2048     {
2049       Feld[x][y] = Store[x][y];
2050       Store[x][y] = 0;
2051       DrawField_MM(x, y);
2052
2053       ScanLaser();
2054     }
2055   }
2056 }
2057
2058 void MeltIce(int x, int y)
2059 {
2060   int frames = 5;
2061   int delay = 5;
2062
2063   if (!MovDelay[x][y])          /* next animation frame */
2064     MovDelay[x][y] = frames * delay;
2065
2066   if (MovDelay[x][y])           /* wait some time before next frame */
2067   {
2068     int phase;
2069     int wall_mask = Store2[x][y];
2070     int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2071
2072     MovDelay[x][y]--;
2073     phase = frames - MovDelay[x][y] / delay - 1;
2074
2075     if (!MovDelay[x][y])
2076     {
2077       int i;
2078
2079       Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2080       Store[x][y] = Store2[x][y] = 0;
2081
2082       DrawWalls_MM(x, y, Feld[x][y]);
2083
2084       if (Feld[x][y] == EL_WALL_ICE)
2085         Feld[x][y] = EL_EMPTY;
2086
2087       for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2088         if (laser.damage[i].is_mirror)
2089           break;
2090
2091       if (i > 0)
2092         DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2093       else
2094         DrawLaser(0, DL_LASER_DISABLED);
2095
2096       ScanLaser();
2097     }
2098     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2099     {
2100       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2101
2102       laser.redraw = TRUE;
2103     }
2104   }
2105 }
2106
2107 void GrowAmoeba(int x, int y)
2108 {
2109   int frames = 5;
2110   int delay = 1;
2111
2112   if (!MovDelay[x][y])          /* next animation frame */
2113     MovDelay[x][y] = frames * delay;
2114
2115   if (MovDelay[x][y])           /* wait some time before next frame */
2116   {
2117     int phase;
2118     int wall_mask = Store2[x][y];
2119     int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2120
2121     MovDelay[x][y]--;
2122     phase = MovDelay[x][y] / delay;
2123
2124     if (!MovDelay[x][y])
2125     {
2126       Feld[x][y] = real_element;
2127       Store[x][y] = Store2[x][y] = 0;
2128
2129       DrawWalls_MM(x, y, Feld[x][y]);
2130       DrawLaser(0, DL_LASER_ENABLED);
2131     }
2132     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2133     {
2134       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2135     }
2136   }
2137 }
2138
2139 static void Explode_MM(int x, int y, int phase, int mode)
2140 {
2141   int num_phase = 9, delay = 2;
2142   int last_phase = num_phase * delay;
2143   int half_phase = (num_phase / 2) * delay;
2144
2145   laser.redraw = TRUE;
2146
2147   if (phase == EX_PHASE_START)          /* initialize 'Store[][]' field */
2148   {
2149     int center_element = Feld[x][y];
2150
2151     if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2152     {
2153       /* put moving element to center field (and let it explode there) */
2154       center_element = MovingOrBlocked2Element_MM(x, y);
2155       RemoveMovingField_MM(x, y);
2156
2157       Feld[x][y] = center_element;
2158     }
2159
2160     if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2161       Store[x][y] = center_element;
2162     else
2163       Store[x][y] = EL_EMPTY;
2164
2165     Store2[x][y] = mode;
2166     Feld[x][y] = EL_EXPLODING_OPAQUE;
2167     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2168     Frame[x][y] = 1;
2169
2170     return;
2171   }
2172
2173   Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2174
2175   if (phase == half_phase)
2176   {
2177     Feld[x][y] = EL_EXPLODING_TRANSP;
2178
2179     if (x == ELX && y == ELY)
2180       ScanLaser();
2181   }
2182
2183   if (phase == last_phase)
2184   {
2185     if (Store[x][y] == EL_BOMB)
2186     {
2187       laser.num_damages--;
2188       DrawLaser(0, DL_LASER_DISABLED);
2189       laser.num_edges = 0;
2190
2191       Bang_MM(laser.start_edge.x, laser.start_edge.y);
2192       Store[x][y] = EL_EMPTY;
2193     }
2194     else if (IS_MCDUFFIN(Store[x][y]))
2195     {
2196       game_mm.game_over = TRUE;
2197       game_mm.game_over_cause = GAME_OVER_BOMB;
2198       Store[x][y] = EL_EMPTY;
2199     }
2200
2201     Feld[x][y] = Store[x][y];
2202     Store[x][y] = Store2[x][y] = 0;
2203     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2204
2205     InitField(x, y, FALSE);
2206     DrawField_MM(x, y);
2207   }
2208   else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2209   {
2210     int graphic = IMG_MM_DEFAULT_EXPLODING;
2211     int graphic_phase = (phase / delay - 1);
2212     Bitmap *bitmap;
2213     int src_x, src_y;
2214
2215     if (Store2[x][y] == EX_KETTLE)
2216     {
2217       if (graphic_phase < 3)
2218       {
2219         graphic = IMG_MM_KETTLE_EXPLODING;
2220       }
2221       else if (graphic_phase < 5)
2222       {
2223         graphic_phase += 3;
2224       }
2225       else
2226       {
2227         graphic = IMG_EMPTY;
2228         graphic_phase = 0;
2229       }
2230     }
2231     else if (Store2[x][y] == EX_SHORT)
2232     {
2233       if (graphic_phase < 4)
2234       {
2235         graphic_phase += 4;
2236       }
2237       else
2238       {
2239         graphic = IMG_EMPTY;
2240         graphic_phase = 0;
2241       }
2242     }
2243
2244     getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2245
2246     BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2247                FX + x * TILEX, FY + y * TILEY);
2248
2249     MarkTileDirty(x, y);
2250   }
2251 }
2252
2253 static void Bang_MM(int x, int y)
2254 {
2255   int element = Feld[x][y];
2256   int mode = EX_NORMAL;
2257
2258 #if 0
2259   DrawLaser(0, DL_LASER_ENABLED);
2260 #endif
2261
2262   switch(element)
2263   {
2264     case EL_KETTLE:
2265       mode = EX_KETTLE;
2266       break;
2267
2268     case EL_GATE_STONE:
2269     case EL_GATE_WOOD:
2270       mode = EX_SHORT;
2271       break;
2272
2273     default:
2274       mode = EX_NORMAL;
2275       break;
2276   }
2277
2278   if (IS_PACMAN(element))
2279     PlaySoundStereo(SND_QUIEK, ST(x));
2280   else if (element == EL_BOMB || IS_MCDUFFIN(element))
2281     PlaySoundStereo(SND_ROAAAR, ST(x));
2282   else if (element == EL_KEY)
2283     PlaySoundStereo(SND_KLING, ST(x));
2284   else
2285     PlaySoundStereo((mode == EX_SHORT ? SND_WHOOSH : SND_KABUMM), ST(x));
2286
2287   Explode_MM(x, y, EX_PHASE_START, mode);
2288 }
2289
2290 void TurnRound(int x, int y)
2291 {
2292   static struct
2293   {
2294     int x, y;
2295   } move_xy[] =
2296   {
2297     { 0, 0 },
2298     {-1, 0 },
2299     {+1, 0 },
2300     { 0, 0 },
2301     { 0, -1 },
2302     { 0, 0 }, { 0, 0 }, { 0, 0 },
2303     { 0, +1 }
2304   };
2305   static struct
2306   {
2307     int left, right, back;
2308   } turn[] =
2309   {
2310     { 0,        0,              0 },
2311     { MV_DOWN,  MV_UP,          MV_RIGHT },
2312     { MV_UP,    MV_DOWN,        MV_LEFT },
2313     { 0,        0,              0 },
2314     { MV_LEFT,  MV_RIGHT,       MV_DOWN },
2315     { 0,0,0 },  { 0,0,0 },      { 0,0,0 },
2316     { MV_RIGHT, MV_LEFT,        MV_UP }
2317   };
2318
2319   int element = Feld[x][y];
2320   int old_move_dir = MovDir[x][y];
2321   int right_dir = turn[old_move_dir].right;
2322   int back_dir = turn[old_move_dir].back;
2323   int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2324   int right_x = x + right_dx, right_y = y + right_dy;
2325
2326   if (element == EL_PACMAN)
2327   {
2328     boolean can_turn_right = FALSE;
2329
2330     if (IN_LEV_FIELD(right_x, right_y) &&
2331         IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2332       can_turn_right = TRUE;
2333
2334     if (can_turn_right)
2335       MovDir[x][y] = right_dir;
2336     else
2337       MovDir[x][y] = back_dir;
2338
2339     MovDelay[x][y] = 0;
2340   }
2341 }
2342
2343 static void StartMoving_MM(int x, int y)
2344 {
2345   int element = Feld[x][y];
2346
2347   if (Stop[x][y])
2348     return;
2349
2350   if (CAN_MOVE(element))
2351   {
2352     int newx, newy;
2353
2354     if (MovDelay[x][y])         /* wait some time before next movement */
2355     {
2356       MovDelay[x][y]--;
2357
2358       if (MovDelay[x][y])
2359         return;
2360     }
2361
2362     /* now make next step */
2363
2364     Moving2Blocked_MM(x, y, &newx, &newy);      /* get next screen position */
2365
2366     if (element == EL_PACMAN &&
2367         IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2368         !ObjHit(newx, newy, HIT_POS_CENTER))
2369     {
2370       Store[newx][newy] = Feld[newx][newy];
2371       Feld[newx][newy] = EL_EMPTY;
2372
2373       DrawField_MM(newx, newy);
2374     }
2375     else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2376              ObjHit(newx, newy, HIT_POS_CENTER))
2377     {
2378       /* object was running against a wall */
2379
2380       TurnRound(x, y);
2381
2382       return;
2383     }
2384
2385     InitMovingField_MM(x, y, MovDir[x][y]);
2386   }
2387
2388   if (MovDir[x][y])
2389     ContinueMoving_MM(x, y);
2390 }
2391
2392 static void ContinueMoving_MM(int x, int y)
2393 {
2394   int element = Feld[x][y];
2395   int direction = MovDir[x][y];
2396   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2397   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
2398   int horiz_move = (dx!=0);
2399   int newx = x + dx, newy = y + dy;
2400   int step = (horiz_move ? dx : dy) * TILEX / 8;
2401
2402   MovPos[x][y] += step;
2403
2404   if (ABS(MovPos[x][y]) >= TILEX)       /* object reached its destination */
2405   {
2406     Feld[x][y] = EL_EMPTY;
2407     Feld[newx][newy] = element;
2408
2409     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2410     MovDelay[newx][newy] = 0;
2411
2412     if (!CAN_MOVE(element))
2413       MovDir[newx][newy] = 0;
2414
2415     DrawField_MM(x, y);
2416     DrawField_MM(newx, newy);
2417
2418     Stop[newx][newy] = TRUE;
2419
2420     if (element == EL_PACMAN)
2421     {
2422       if (Store[newx][newy] == EL_BOMB)
2423         Bang_MM(newx, newy);
2424
2425       if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2426           (LX + 2 * XS) / TILEX == newx &&
2427           (LY + 2 * YS) / TILEY == newy)
2428       {
2429         laser.num_edges--;
2430         ScanLaser();
2431       }
2432     }
2433   }
2434   else                          /* still moving on */
2435   {
2436     DrawField_MM(x, y);
2437   }
2438
2439   laser.redraw = TRUE;
2440 }
2441
2442 void ClickElement(int mx, int my, int button)
2443 {
2444   static unsigned int click_delay = 0;
2445   static int click_delay_value = CLICK_DELAY_SHORT;
2446   static boolean new_button = TRUE;
2447   int element;
2448   int x = (mx - SX) / TILEX, y = (my - SY) / TILEY;
2449
2450   /* do not rotate objects hit by the laser after the game was solved */
2451   if (game_mm.level_solved && Hit[x][y])
2452     return;
2453
2454   if (button == MB_RELEASED)
2455   {
2456     new_button = TRUE;
2457     click_delay_value = CLICK_DELAY_SHORT;
2458
2459     /* release eventually hold auto-rotating mirror */
2460     RotateMirror(x, y, MB_RELEASED);
2461
2462     return;
2463   }
2464
2465   if (!DelayReached(&click_delay, click_delay_value) && !new_button)
2466     return;
2467
2468   if (button == MB_MIDDLEBUTTON)        /* middle button has no function */
2469     return;
2470
2471   if (!IN_PIX_FIELD(mx - SX, my - SY))
2472     return;
2473
2474   if (Feld[x][y] == EL_EMPTY)
2475     return;
2476
2477   element = Feld[x][y];
2478
2479   if (IS_MIRROR(element) ||
2480       IS_BEAMER(element) ||
2481       IS_POLAR(element) ||
2482       IS_POLAR_CROSS(element) ||
2483       IS_DF_MIRROR(element) ||
2484       IS_DF_MIRROR_AUTO(element))
2485   {
2486     RotateMirror(x, y, button);
2487   }
2488   else if (IS_MCDUFFIN(element))
2489   {
2490     if (!laser.fuse_off)
2491     {
2492       DrawLaser(0, DL_LASER_DISABLED);
2493
2494       /*
2495       BackToFront();
2496       */
2497     }
2498
2499     element = get_rotated_element(element, BUTTON_ROTATION(button));
2500     laser.start_angle = get_element_angle(element);
2501
2502     InitLaser();
2503
2504     Feld[x][y] = element;
2505     DrawField_MM(x, y);
2506
2507     /*
2508     BackToFront();
2509     */
2510
2511     if (!laser.fuse_off)
2512       ScanLaser();
2513   }
2514   else if (element == EL_FUSE_ON && laser.fuse_off)
2515   {
2516     if (x != laser.fuse_x || y != laser.fuse_y)
2517       return;
2518
2519     laser.fuse_off = FALSE;
2520     laser.fuse_x = laser.fuse_y = -1;
2521
2522     DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2523     ScanLaser();
2524   }
2525   else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2526   {
2527     laser.fuse_off = TRUE;
2528     laser.fuse_x = x;
2529     laser.fuse_y = y;
2530     laser.overloaded = FALSE;
2531
2532     DrawLaser(0, DL_LASER_DISABLED);
2533     DrawGraphic_MM(x, y, IMG_MM_FUSE);
2534   }
2535   else if (element == EL_LIGHTBALL)
2536   {
2537     Bang_MM(x, y);
2538     RaiseScore_MM(10);
2539     DrawLaser(0, DL_LASER_ENABLED);
2540   }
2541
2542   click_delay_value = (new_button ? CLICK_DELAY_LONG : CLICK_DELAY_SHORT);
2543   new_button = FALSE;
2544 }
2545
2546 void RotateMirror(int x, int y, int button)
2547 {
2548   static int hold_x = -1, hold_y = -1;
2549
2550   if (button == MB_RELEASED)
2551   {
2552     /* release eventually hold auto-rotating mirror */
2553     hold_x = -1;
2554     hold_y = -1;
2555
2556     return;
2557   }
2558
2559   if (IS_MIRROR(Feld[x][y]) ||
2560       IS_POLAR_CROSS(Feld[x][y]) ||
2561       IS_POLAR(Feld[x][y]) ||
2562       IS_BEAMER(Feld[x][y]) ||
2563       IS_DF_MIRROR(Feld[x][y]) ||
2564       IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2565       IS_GRID_WOOD_AUTO(Feld[x][y]))
2566   {
2567     Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2568   }
2569   else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2570   {
2571     if (button == MB_LEFTBUTTON)
2572     {
2573       /* left mouse button only for manual adjustment, no auto-rotating;
2574          freeze mirror for until mouse button released */
2575       hold_x = x;
2576       hold_y = y;
2577     }
2578     else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2579     {
2580       Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2581     }
2582   }
2583
2584   if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2585   {
2586     int edge = Hit[x][y];
2587
2588     DrawField_MM(x, y);
2589
2590     if (edge > 0)
2591     {
2592       DrawLaser(edge - 1, DL_LASER_DISABLED);
2593       ScanLaser();
2594     }
2595   }
2596   else if (ObjHit(x, y, HIT_POS_CENTER))
2597   {
2598     int edge = Hit[x][y];
2599
2600     if (edge == 0)
2601     {
2602       Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2603       edge = 1;
2604     }
2605
2606     DrawLaser(edge - 1, DL_LASER_DISABLED);
2607     ScanLaser();
2608   }
2609   else
2610   {
2611     int check = 1;
2612
2613     if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2614       check = 2;
2615
2616     DrawField_MM(x, y);
2617
2618     if ((IS_BEAMER(Feld[x][y]) ||
2619          IS_POLAR(Feld[x][y]) ||
2620          IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2621     {
2622       check = 0;
2623
2624       if (IS_BEAMER(Feld[x][y]))
2625       {
2626 #if 0
2627         printf("TEST (%d, %d) [%d] [%d]\n",
2628                LX, LY,
2629                laser.beamer_edge, laser.beamer[1].num);
2630 #endif
2631
2632         laser.num_edges--;
2633       }
2634
2635       ScanLaser();
2636     }
2637
2638     if (check == 2)
2639       DrawLaser(0, DL_LASER_ENABLED);
2640   }
2641 }
2642
2643 void AutoRotateMirrors()
2644 {
2645   static unsigned int rotate_delay = 0;
2646   int x, y;
2647
2648   if (!DelayReached(&rotate_delay, AUTO_ROTATE_DELAY))
2649     return;
2650
2651   for (x = 0; x < lev_fieldx; x++)
2652   {
2653     for (y = 0; y < lev_fieldy; y++)
2654     {
2655       int element = Feld[x][y];
2656
2657       /* do not rotate objects hit by the laser after the game was solved */
2658       if (game_mm.level_solved && Hit[x][y])
2659         continue;
2660
2661       if (IS_DF_MIRROR_AUTO(element) ||
2662           IS_GRID_WOOD_AUTO(element) ||
2663           IS_GRID_STEEL_AUTO(element) ||
2664           element == EL_REFRACTOR)
2665         RotateMirror(x, y, MB_RIGHTBUTTON);
2666     }
2667   }
2668 }
2669
2670 boolean ObjHit(int obx, int oby, int bits)
2671 {
2672   int i;
2673
2674   obx *= TILEX;
2675   oby *= TILEY;
2676
2677   if (bits & HIT_POS_CENTER)
2678   {
2679     if (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
2680       return TRUE;
2681   }
2682
2683   if (bits & HIT_POS_EDGE)
2684   {
2685     for (i = 0; i < 4; i++)
2686       if (ReadPixel(drawto,
2687                     SX + obx + 31 * (i % 2),
2688                     SY + oby + 31 * (i / 2)) == pen_ray)
2689         return TRUE;
2690   }
2691
2692   if (bits & HIT_POS_BETWEEN)
2693   {
2694     for (i = 0; i < 4; i++)
2695       if (ReadPixel(drawto,
2696                     SX + 4 + obx + 22 * (i % 2),
2697                     SY + 4 + oby + 22 * (i / 2)) == pen_ray)
2698         return TRUE;
2699   }
2700
2701   return FALSE;
2702 }
2703
2704 void DeletePacMan(int px, int py)
2705 {
2706   int i, j;
2707
2708   Bang_MM(px, py);
2709
2710   if (game_mm.num_pacman <= 1)
2711   {
2712     game_mm.num_pacman = 0;
2713     return;
2714   }
2715
2716   for (i = 0; i < game_mm.num_pacman; i++)
2717     if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2718       break;
2719
2720   game_mm.num_pacman--;
2721
2722   for (j = i; j < game_mm.num_pacman; j++)
2723   {
2724     game_mm.pacman[j].x   = game_mm.pacman[j + 1].x;
2725     game_mm.pacman[j].y   = game_mm.pacman[j + 1].y;
2726     game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2727   }
2728 }
2729
2730 void ColorCycling(void)
2731 {
2732   static int CC, Cc = 0;
2733
2734   static int color, old = 0xF00, new = 0x010, mult = 1;
2735   static unsigned short red, green, blue;
2736
2737   if (color_status == STATIC_COLORS)
2738     return;
2739
2740   CC = Counter();
2741
2742   if (CC < Cc || CC > Cc + 50)
2743   {
2744     Cc = CC;
2745
2746     color = old + new * mult;
2747     if (mult > 0)
2748       mult++;
2749     else
2750       mult--;
2751
2752     if (ABS(mult) == 16)
2753     {
2754       mult =- mult / 16;
2755       old = color;
2756       new = new << 4;
2757
2758       if (new > 0x100)
2759         new = 0x001;
2760     }
2761
2762     red   = 0x0e00 * ((color & 0xF00) >> 8);
2763     green = 0x0e00 * ((color & 0x0F0) >> 4);
2764     blue  = 0x0e00 * ((color & 0x00F));
2765     SetRGB(pen_magicolor[0], red, green, blue);
2766
2767     red   = 0x1111 * ((color & 0xF00) >> 8);
2768     green = 0x1111 * ((color & 0x0F0) >> 4);
2769     blue  = 0x1111 * ((color & 0x00F));
2770     SetRGB(pen_magicolor[1], red, green, blue);
2771   }
2772 }
2773
2774 static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
2775 {
2776   static unsigned int action_delay = 0;
2777   static unsigned int pacman_delay = 0;
2778   static unsigned int energy_delay = 0;
2779   static unsigned int overload_delay = 0;
2780   int element;
2781   int x, y, i;
2782
2783   int r, d;
2784
2785   WaitUntilDelayReached(&action_delay, GameFrameDelay);
2786
2787   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
2788     Stop[x][y] = FALSE;
2789
2790   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
2791   {
2792     element = Feld[x][y];
2793
2794     if (!IS_MOVING(x, y) && CAN_MOVE(element))
2795       StartMoving_MM(x, y);
2796     else if (IS_MOVING(x, y))
2797       ContinueMoving_MM(x, y);
2798     else if (IS_EXPLODING(element))
2799       Explode_MM(x, y, Frame[x][y], EX_NORMAL);
2800     else if (element == EL_EXIT_OPENING)
2801       OpenExit(x, y);
2802     else if (element == EL_GRAY_BALL_OPENING)
2803       OpenSurpriseBall(x, y);
2804     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
2805       MeltIce(x, y);
2806     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
2807       GrowAmoeba(x, y);
2808   }
2809
2810   AutoRotateMirrors();
2811
2812 #if 1
2813   /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
2814
2815   /* redraw after Explode_MM() ... */
2816   if (laser.redraw)
2817     DrawLaser(0, DL_LASER_ENABLED);
2818   laser.redraw = FALSE;
2819 #endif
2820
2821   CT = Counter();
2822
2823   if (game_mm.num_pacman && DelayReached(&pacman_delay, 250))
2824   {
2825     MovePacMen();
2826
2827     if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
2828     {
2829       DrawLaser(0, DL_LASER_DISABLED);
2830       ScanLaser();
2831     }
2832   }
2833
2834   if (DelayReached(&energy_delay, 4000))
2835   {
2836     game_mm.energy_left--;
2837     if (game_mm.energy_left >= 0)
2838     {
2839 #if 0
2840       BlitBitmap(pix[PIX_DOOR], drawto,
2841                  DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
2842                  ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
2843                  DX_ENERGY, DY_ENERGY);
2844 #endif
2845       redraw_mask |= REDRAW_DOOR_1;
2846     }
2847     else if (setup.time_limit)
2848     {
2849       int i;
2850
2851       for (i = 15; i >= 0; i--)
2852       {
2853 #if 0
2854         SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
2855 #endif
2856         pen_ray = GetPixelFromRGB(window,
2857                                   native_mm_level.laser_red   * 0x11 * i,
2858                                   native_mm_level.laser_green * 0x11 * i,
2859                                   native_mm_level.laser_blue  * 0x11 * i);
2860
2861         DrawLaser(0, DL_LASER_ENABLED);
2862         BackToFront();
2863         Delay(50);
2864       }
2865
2866       StopSound(SND_WARNTON);
2867       FadeMusic();
2868
2869       DrawLaser(0, DL_LASER_DISABLED);
2870       game_mm.game_over = TRUE;
2871       game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
2872
2873 #if 0
2874       if (Request("Out of magic energy ! Play it again ?",
2875                   REQ_ASK | REQ_STAY_CLOSED))
2876       {
2877         InitGame();
2878       }
2879       else
2880       {
2881         game_status = MAINMENU;
2882         DrawMainMenu();
2883       }
2884 #endif
2885
2886       return;
2887     }
2888   }
2889
2890   element = laser.dest_element;
2891
2892 #if 0
2893   if (element != Feld[ELX][ELY])
2894   {
2895     printf("element == %d, Feld[ELX][ELY] == %d\n",
2896            element, Feld[ELX][ELY]);
2897   }
2898 #endif
2899
2900   if (!laser.overloaded && laser.overload_value == 0 &&
2901       element != EL_BOMB &&
2902       element != EL_MINE &&
2903       element != EL_BALL_GRAY &&
2904       element != EL_BLOCK_STONE &&
2905       element != EL_BLOCK_WOOD &&
2906       element != EL_FUSE_ON &&
2907       element != EL_FUEL_FULL &&
2908       !IS_WALL_ICE(element) &&
2909       !IS_WALL_AMOEBA(element))
2910     return;
2911
2912   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
2913        (!laser.overloaded && laser.overload_value > 0)) &&
2914       DelayReached(&overload_delay, 60 + !laser.overloaded * 120))
2915   {
2916     if (laser.overloaded)
2917       laser.overload_value++;
2918     else
2919       laser.overload_value--;
2920
2921     if (game_mm.cheat_no_overload)
2922     {
2923       laser.overloaded = FALSE;
2924       laser.overload_value = 0;
2925     }
2926
2927     game_mm.laser_overload_value = laser.overload_value;
2928
2929     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
2930     {
2931       int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
2932       int color_down = 0xFF - color_up;
2933
2934 #if 0
2935       SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
2936              (15 - (laser.overload_value / 6)) * color_scale);
2937 #endif
2938       pen_ray =
2939         GetPixelFromRGB(window,
2940                         (native_mm_level.laser_red  ? 0xFF : color_up),
2941                         (native_mm_level.laser_green ? color_down : 0x00),
2942                         (native_mm_level.laser_blue  ? color_down : 0x00));
2943
2944       DrawLaser(0, DL_LASER_ENABLED);
2945       BackToFront();
2946     }
2947
2948     if (laser.overloaded)
2949     {
2950       if (setup.sound_loops)
2951         PlaySoundExt(SND_WARNTON, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
2952                      SND_CTRL_PLAY_LOOP);
2953       else
2954         PlaySoundStereo(SND_WARNTON, SOUND_MAX_RIGHT);
2955     }
2956
2957     if (!laser.overloaded)
2958       StopSound(SND_WARNTON);
2959
2960     if (laser.overloaded)
2961     {
2962 #if 0
2963       BlitBitmap(pix[PIX_DOOR], drawto,
2964                  DOOR_GFX_PAGEX4 + XX_OVERLOAD,
2965                  DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
2966                  - laser.overload_value,
2967                  OVERLOAD_XSIZE, laser.overload_value,
2968                  DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
2969                  - laser.overload_value);
2970 #endif
2971       redraw_mask |= REDRAW_DOOR_1;
2972     }
2973     else
2974     {
2975 #if 0
2976       BlitBitmap(pix[PIX_DOOR], drawto,
2977                  DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
2978                  OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
2979                  DX_OVERLOAD, DY_OVERLOAD);
2980 #endif
2981       redraw_mask |= REDRAW_DOOR_1;
2982     }
2983
2984     if (laser.overload_value == MAX_LASER_OVERLOAD)
2985     {
2986       int i;
2987
2988       for (i = 15; i >= 0; i--)
2989       {
2990 #if 0
2991         SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
2992 #endif
2993
2994         pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
2995
2996         DrawLaser(0, DL_LASER_ENABLED);
2997         BackToFront();
2998         Delay(50);
2999       }
3000
3001       DrawLaser(0, DL_LASER_DISABLED);
3002
3003       game_mm.game_over = TRUE;
3004       game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3005
3006 #if 0
3007       if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3008                   REQ_ASK | REQ_STAY_CLOSED))
3009       {
3010         InitGame();
3011       }
3012       else
3013       {
3014         game_status = MAINMENU;
3015         DrawMainMenu();
3016       }
3017 #endif
3018
3019       return;
3020     }
3021   }
3022
3023   if (laser.fuse_off)
3024     return;
3025
3026   CT -= Ct;
3027
3028   if (element == EL_BOMB && CT > 1500)
3029   {
3030     if (game_mm.cheat_no_explosion)
3031       return;
3032
3033 #if 0
3034     laser.num_damages--;
3035     DrawLaser(0, DL_LASER_DISABLED);
3036     laser.num_edges = 0;
3037 #endif
3038
3039     Bang_MM(ELX, ELY);
3040
3041     laser.dest_element = EL_EXPLODING_OPAQUE;
3042
3043 #if 0
3044     Bang_MM(ELX, ELY);
3045     laser.num_damages--;
3046     DrawLaser(0, DL_LASER_DISABLED);
3047
3048     laser.num_edges = 0;
3049     Bang_MM(laser.start_edge.x, laser.start_edge.y);
3050
3051     if (Request("Bomb killed Mc Duffin ! Play it again ?",
3052                 REQ_ASK | REQ_STAY_CLOSED))
3053     {
3054       InitGame();
3055     }
3056     else
3057     {
3058       game_status = MAINMENU;
3059       DrawMainMenu();
3060     }
3061 #endif
3062
3063     return;
3064   }
3065
3066   if (element == EL_FUSE_ON && CT > 500)
3067   {
3068     laser.fuse_off = TRUE;
3069     laser.fuse_x = ELX;
3070     laser.fuse_y = ELY;
3071
3072     DrawLaser(0, DL_LASER_DISABLED);
3073     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3074   }
3075
3076   if (element == EL_BALL_GRAY && CT > 1500)
3077   {
3078     static int new_elements[] =
3079     {
3080       EL_MIRROR_START,
3081       EL_MIRROR_FIXED_START,
3082       EL_POLAR_START,
3083       EL_POLAR_CROSS_START,
3084       EL_PACMAN_START,
3085       EL_KETTLE,
3086       EL_BOMB,
3087       EL_PRISM
3088     };
3089     int num_new_elements = sizeof(new_elements) / sizeof(int);
3090     int new_element = new_elements[RND(num_new_elements)];
3091
3092     Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3093     Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3094
3095     /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3096     ScanLaser();
3097
3098     return;
3099
3100 #if 0
3101     int graphic;
3102
3103     switch (RND(5))
3104     {
3105       case 0:
3106         element = EL_MIRROR_START + RND(16);
3107         break;
3108       case 1:
3109         {
3110           int rnd = RND(3);
3111
3112           element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3113         }
3114         break;
3115       default:
3116         {
3117           int rnd = RND(3);
3118
3119           element = (rnd == 0 ? EL_FUSE_ON :
3120                      rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3121                      rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3122                      rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3123                      EL_MIRROR_FIXED_START + rnd - 25);
3124         }
3125         break;
3126     }
3127
3128     graphic = el2gfx(element);
3129
3130     for (i = 0; i < 50; i++)
3131     {
3132       int x = RND(26);
3133       int y = RND(26);
3134
3135 #if 0
3136       BlitBitmap(pix[PIX_BACK], drawto,
3137                  SX + (graphic % GFX_PER_LINE) * TILEX + x,
3138                  SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3139                  SX + ELX * TILEX + x,
3140                  SY + ELY * TILEY + y);
3141 #endif
3142       MarkTileDirty(ELX, ELY);
3143       BackToFront();
3144
3145       DrawLaser(0, DL_LASER_ENABLED);
3146
3147       Delay(50);
3148     }
3149
3150     Feld[ELX][ELY] = element;
3151     DrawField_MM(ELX, ELY);
3152
3153 #if 0
3154     printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3155 #endif
3156
3157     /* above stuff: GRAY BALL -> PRISM !!! */
3158 /*
3159     LX = ELX * TILEX + 14;
3160     LY = ELY * TILEY + 14;
3161     if (laser.current_angle == (laser.current_angle >> 1) << 1)
3162       OK = 8;
3163     else
3164       OK = 4;
3165     LX -= OK * XS;
3166     LY -= OK * YS;
3167
3168     laser.num_edges -= 2;
3169     laser.num_damages--;
3170 */
3171
3172 #if 0
3173     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3174       if (laser.damage[i].is_mirror)
3175         break;
3176
3177     if (i > 0)
3178       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3179     else
3180       DrawLaser(0, DL_LASER_DISABLED);
3181 #else
3182     DrawLaser(0, DL_LASER_DISABLED);
3183 #endif
3184
3185     ScanLaser();
3186
3187     /*
3188     printf("TEST ELEMENT: %d\n", Feld[0][0]);
3189     */
3190 #endif
3191
3192     return;
3193   }
3194
3195   if (IS_WALL_ICE(element) && CT > 1000)
3196   {
3197     PlaySoundStereo(SND_SLURP, ST(ELX));
3198
3199     {
3200       Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3201       Store[ELX][ELY] = EL_WALL_ICE;
3202       Store2[ELX][ELY] = laser.wall_mask;
3203
3204       laser.dest_element = Feld[ELX][ELY];
3205
3206       return;
3207     }
3208
3209     for (i = 0; i < 5; i++)
3210     {
3211       int phase = i + 1;
3212
3213       if (i == 4)
3214       {
3215         Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3216         phase = 0;
3217       }
3218
3219       DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3220       BackToFront();
3221       Delay(100);
3222     }
3223
3224     if (Feld[ELX][ELY] == EL_WALL_ICE)
3225       Feld[ELX][ELY] = EL_EMPTY;
3226
3227 /*
3228     laser.num_edges--;
3229     LX = laser.edge[laser.num_edges].x - (SX + 2);
3230     LY = laser.edge[laser.num_edges].y - (SY + 2);
3231 */
3232
3233     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3234       if (laser.damage[i].is_mirror)
3235         break;
3236
3237     if (i > 0)
3238       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3239     else
3240       DrawLaser(0, DL_LASER_DISABLED);
3241
3242     ScanLaser();
3243
3244     return;
3245   }
3246
3247   if (IS_WALL_AMOEBA(element) && CT > 1200)
3248   {
3249     int k1, k2, k3, dx, dy, de, dm;
3250     int element2 = Feld[ELX][ELY];
3251
3252     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3253       return;
3254
3255     for (i = laser.num_damages - 1; i >= 0; i--)
3256       if (laser.damage[i].is_mirror)
3257         break;
3258
3259     r = laser.num_edges;
3260     d = laser.num_damages;
3261     k1 = i;
3262
3263     if (k1 > 0)
3264     {
3265       int x, y;
3266
3267       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3268
3269       laser.num_edges++;
3270       DrawLaser(0, DL_LASER_ENABLED);
3271       laser.num_edges--;
3272
3273       x = laser.damage[k1].x;
3274       y = laser.damage[k1].y;
3275
3276       DrawField_MM(x, y);
3277     }
3278
3279     for (i = 0; i < 4; i++)
3280     {
3281       if (laser.wall_mask & (1 << i))
3282       {
3283         if (ReadPixel(drawto,
3284                       SX + ELX * TILEX + 14 + (i % 2) * 2,
3285                       SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3286           break;
3287         if (ReadPixel(drawto,
3288                       SX + ELX * TILEX + 31 * (i % 2),
3289                       SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3290           break;
3291       }
3292     }
3293
3294     k2 = i;
3295
3296     for (i = 0; i < 4; i++)
3297     {
3298       if (laser.wall_mask & (1 << i))
3299       {
3300         if (ReadPixel(drawto,
3301                       SX + ELX * TILEX + 31 * (i % 2),
3302                       SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3303           break;
3304       }
3305     }
3306
3307     k3 = i;
3308
3309     if (laser.num_beamers > 0 ||
3310         k1 < 1 || k2 < 4 || k3 < 4 ||
3311         ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3312         == pen_ray)
3313     {
3314       laser.num_edges = r;
3315       laser.num_damages = d;
3316
3317       DrawLaser(0, DL_LASER_DISABLED);
3318     }
3319
3320     Feld[ELX][ELY] = element | laser.wall_mask;
3321
3322     dx = ELX;
3323     dy = ELY;
3324     de = Feld[ELX][ELY];
3325     dm = laser.wall_mask;
3326
3327 #if 1
3328     {
3329       int x = ELX, y = ELY;
3330       int wall_mask = laser.wall_mask;
3331
3332       ScanLaser();
3333       DrawLaser(0, DL_LASER_ENABLED);
3334
3335       PlaySoundStereo(SND_AMOEBE, ST(dx));
3336
3337       Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3338       Store[x][y] = EL_WALL_AMOEBA;
3339       Store2[x][y] = wall_mask;
3340
3341       return;
3342     }
3343 #endif
3344
3345     DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3346     ScanLaser();
3347     DrawLaser(0, DL_LASER_ENABLED);
3348
3349     PlaySoundStereo(SND_AMOEBE, ST(dx));
3350
3351     for (i = 4; i >= 0; i--)
3352     {
3353       DrawWallsAnimation_MM(dx, dy, de, i, dm);
3354
3355       BackToFront();
3356       Delay(20);
3357     }
3358
3359     DrawLaser(0, DL_LASER_ENABLED);
3360
3361     return;
3362   }
3363
3364   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3365       laser.stops_inside_element && CT > 1500)
3366   {
3367     int x, y;
3368     int k;
3369
3370     if (ABS(XS) > ABS(YS))
3371       k = 0;
3372     else
3373       k = 1;
3374     if (XS < YS)
3375       k += 2;
3376
3377     for (i = 0; i < 4; i++)
3378     {
3379       if (i)
3380         k++;
3381       if (k > 3)
3382         k = 0;
3383
3384       x = ELX + Step[k * 4].x;
3385       y = ELY + Step[k * 4].y;
3386
3387       if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3388         continue;
3389
3390       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3391         continue;
3392
3393       break;
3394     }
3395
3396     if (i > 3)
3397     {
3398       laser.overloaded = (element == EL_BLOCK_STONE);
3399
3400       return;
3401     }
3402
3403     PlaySoundStereo(SND_BONG, ST(ELX));
3404
3405     Feld[ELX][ELY] = 0;
3406     Feld[x][y] = element;
3407
3408     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3409     DrawField_MM(x, y);
3410
3411     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3412     {
3413       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3414       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3415     }
3416
3417     ScanLaser();
3418
3419     return;
3420   }
3421
3422   if (element == EL_FUEL_FULL && CT > 200)
3423   {
3424     for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3425     {
3426 #if 0
3427       BlitBitmap(pix[PIX_DOOR], drawto,
3428                  DOOR_GFX_PAGEX4 + XX_ENERGY,
3429                  DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3430                  ENERGY_XSIZE, i, DX_ENERGY,
3431                  DY_ENERGY + ENERGY_YSIZE - i);
3432 #endif
3433
3434       redraw_mask |= REDRAW_DOOR_1;
3435       BackToFront();
3436
3437       Delay(20);
3438     }
3439
3440     game_mm.energy_left = MAX_LASER_ENERGY;
3441     Feld[ELX][ELY] = EL_FUEL_EMPTY;
3442     DrawField_MM(ELX, ELY);
3443
3444     DrawLaser(0, DL_LASER_ENABLED);
3445
3446     return;
3447   }
3448
3449   return;
3450 }
3451
3452 void GameActions_MM(byte action[MAX_PLAYERS], boolean warp_mode)
3453 {
3454   if (!button_status)
3455     ClickElement(0, 0, MB_NOT_PRESSED);
3456
3457   GameActions_MM_Ext(action, warp_mode);
3458 }
3459
3460 void MovePacMen()
3461 {
3462   static int p = -1;
3463   int mx, my, ox, oy, nx, ny;
3464   int element;
3465   int l;
3466
3467   if (++p >= game_mm.num_pacman)
3468     p = 0;
3469
3470   game_mm.pacman[p].dir--;
3471
3472   for (l = 1; l < 5; l++)
3473   {
3474     game_mm.pacman[p].dir++;
3475
3476     if (game_mm.pacman[p].dir > 4)
3477       game_mm.pacman[p].dir = 1;
3478
3479     if (game_mm.pacman[p].dir % 2)
3480     {
3481       mx = 0;
3482       my = game_mm.pacman[p].dir - 2;
3483     }
3484     else
3485     {
3486       my = 0;
3487       mx = 3 - game_mm.pacman[p].dir;
3488     }
3489
3490     ox = game_mm.pacman[p].x;
3491     oy = game_mm.pacman[p].y;
3492     nx = ox + mx;
3493     ny = oy + my;
3494     element = Feld[nx][ny];
3495
3496     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3497       continue;
3498
3499     if (!IS_EATABLE4PACMAN(element))
3500       continue;
3501
3502     if (ObjHit(nx, ny, HIT_POS_CENTER))
3503       continue;
3504
3505     Feld[ox][oy] = EL_EMPTY;
3506     Feld[nx][ny] =
3507       EL_PACMAN_RIGHT - 1 +
3508       (game_mm.pacman[p].dir - 1 +
3509        (game_mm.pacman[p].dir % 2) * 2);
3510
3511     game_mm.pacman[p].x = nx;
3512     game_mm.pacman[p].y = ny;
3513
3514     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3515
3516     if (element != EL_EMPTY)
3517     {
3518       int graphic = el2gfx(Feld[nx][ny]);
3519       Bitmap *bitmap;
3520       int src_x, src_y;
3521       int i;
3522
3523       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3524
3525       CT = Counter();
3526       ox = SX + ox * TILEX;
3527       oy = SY + oy * TILEY;
3528
3529       for (i = 1; i < 33; i += 2)
3530         BlitBitmap(bitmap, window,
3531                    src_x, src_y, TILEX, TILEY,
3532                    ox + i * mx, oy + i * my);
3533       Ct = Ct + Counter() - CT;
3534     }
3535
3536     DrawField_MM(nx, ny);
3537     BackToFront();
3538
3539     if (!laser.fuse_off)
3540     {
3541       DrawLaser(0, DL_LASER_ENABLED);
3542
3543       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3544       {
3545         AddDamagedField(nx, ny);
3546
3547         laser.damage[laser.num_damages - 1].edge = 0;
3548       }
3549     }
3550
3551     if (element == EL_BOMB)
3552       DeletePacMan(nx, ny);
3553
3554     if (IS_WALL_AMOEBA(element) &&
3555         (LX + 2 * XS) / TILEX == nx &&
3556         (LY + 2 * YS) / TILEY == ny)
3557     {
3558       laser.num_edges--;
3559       ScanLaser();
3560     }
3561
3562     break;
3563   }
3564 }
3565
3566 void GameWon_MM()
3567 {
3568   int hi_pos;
3569   boolean raise_level = FALSE;
3570
3571 #if 0
3572   if (local_player->MovPos)
3573     return;
3574
3575   local_player->LevelSolved = FALSE;
3576 #endif
3577
3578   if (game_mm.energy_left)
3579   {
3580     if (setup.sound_loops)
3581       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3582                    SND_CTRL_PLAY_LOOP);
3583
3584     while (game_mm.energy_left > 0)
3585     {
3586       if (!setup.sound_loops)
3587         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3588
3589       /*
3590       if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3591         RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3592       */
3593
3594       RaiseScore_MM(5);
3595
3596       game_mm.energy_left--;
3597       if (game_mm.energy_left >= 0)
3598       {
3599 #if 0
3600         BlitBitmap(pix[PIX_DOOR], drawto,
3601                    DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3602                    ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3603                    DX_ENERGY, DY_ENERGY);
3604 #endif
3605         redraw_mask |= REDRAW_DOOR_1;
3606       }
3607
3608       BackToFront();
3609       Delay(10);
3610     }
3611
3612     if (setup.sound_loops)
3613       StopSound(SND_SIRR);
3614   }
3615   else if (native_mm_level.time == 0)           /* level without time limit */
3616   {
3617     if (setup.sound_loops)
3618       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3619                    SND_CTRL_PLAY_LOOP);
3620
3621     while (TimePlayed < 999)
3622     {
3623       if (!setup.sound_loops)
3624         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3625       if (TimePlayed < 999 && !(TimePlayed % 10))
3626         RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3627       if (TimePlayed < 900 && !(TimePlayed % 10))
3628         TimePlayed += 10;
3629       else
3630         TimePlayed++;
3631
3632       /*
3633       DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3634       */
3635
3636       BackToFront();
3637       Delay(10);
3638     }
3639
3640     if (setup.sound_loops)
3641       StopSound(SND_SIRR);
3642   }
3643
3644 #if 0
3645   FadeSounds();
3646 #endif
3647
3648   CloseDoor(DOOR_CLOSE_1);
3649
3650   Request("Level solved !", REQ_CONFIRM);
3651
3652   if (level_nr == leveldir_current->handicap_level)
3653   {
3654     leveldir_current->handicap_level++;
3655     SaveLevelSetup_SeriesInfo();
3656   }
3657
3658   if (level_editor_test_game)
3659     game_mm.score = -1;         /* no highscore when playing from editor */
3660   else if (level_nr < leveldir_current->last_level)
3661     raise_level = TRUE;         /* advance to next level */
3662
3663   if ((hi_pos = NewHiScore_MM()) >= 0)
3664   {
3665     game_status = HALLOFFAME;
3666
3667     // DrawHallOfFame(hi_pos);
3668
3669     if (raise_level)
3670       level_nr++;
3671   }
3672   else
3673   {
3674     game_status = MAINMENU;
3675
3676     if (raise_level)
3677       level_nr++;
3678
3679     // DrawMainMenu();
3680   }
3681
3682   BackToFront();
3683 }
3684
3685 int NewHiScore_MM()
3686 {
3687   int k, l;
3688   int position = -1;
3689
3690   // LoadScore(level_nr);
3691
3692   if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3693       game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3694     return -1;
3695
3696   for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3697   {
3698     if (game_mm.score > highscore[k].Score)
3699     {
3700       /* player has made it to the hall of fame */
3701
3702       if (k < MAX_SCORE_ENTRIES - 1)
3703       {
3704         int m = MAX_SCORE_ENTRIES - 1;
3705
3706 #ifdef ONE_PER_NAME
3707         for (l = k; l < MAX_SCORE_ENTRIES; l++)
3708           if (!strcmp(setup.player_name, highscore[l].Name))
3709             m = l;
3710         if (m == k)     /* player's new highscore overwrites his old one */
3711           goto put_into_list;
3712 #endif
3713
3714         for (l = m; l>k; l--)
3715         {
3716           strcpy(highscore[l].Name, highscore[l - 1].Name);
3717           highscore[l].Score = highscore[l - 1].Score;
3718         }
3719       }
3720
3721 #ifdef ONE_PER_NAME
3722       put_into_list:
3723 #endif
3724       strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3725       highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3726       highscore[k].Score = game_mm.score;
3727       position = k;
3728
3729       break;
3730     }
3731
3732 #ifdef ONE_PER_NAME
3733     else if (!strncmp(setup.player_name, highscore[k].Name,
3734                       MAX_PLAYER_NAME_LEN))
3735       break;    /* player already there with a higher score */
3736 #endif
3737
3738   }
3739
3740   // if (position >= 0)
3741   //   SaveScore(level_nr);
3742
3743   return position;
3744 }
3745
3746 static void InitMovingField_MM(int x, int y, int direction)
3747 {
3748   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3749   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3750
3751   MovDir[x][y] = direction;
3752   MovDir[newx][newy] = direction;
3753
3754   if (Feld[newx][newy] == EL_EMPTY)
3755     Feld[newx][newy] = EL_BLOCKED;
3756 }
3757
3758 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3759 {
3760   int direction = MovDir[x][y];
3761   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3762   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3763
3764   *goes_to_x = newx;
3765   *goes_to_y = newy;
3766 }
3767
3768 static void Blocked2Moving_MM(int x, int y,
3769                               int *comes_from_x, int *comes_from_y)
3770 {
3771   int oldx = x, oldy = y;
3772   int direction = MovDir[x][y];
3773
3774   if (direction == MV_LEFT)
3775     oldx++;
3776   else if (direction == MV_RIGHT)
3777     oldx--;
3778   else if (direction == MV_UP)
3779     oldy++;
3780   else if (direction == MV_DOWN)
3781     oldy--;
3782
3783   *comes_from_x = oldx;
3784   *comes_from_y = oldy;
3785 }
3786
3787 static int MovingOrBlocked2Element_MM(int x, int y)
3788 {
3789   int element = Feld[x][y];
3790
3791   if (element == EL_BLOCKED)
3792   {
3793     int oldx, oldy;
3794
3795     Blocked2Moving_MM(x, y, &oldx, &oldy);
3796
3797     return Feld[oldx][oldy];
3798   }
3799
3800   return element;
3801 }
3802
3803 #if 0
3804 static void RemoveField(int x, int y)
3805 {
3806   Feld[x][y] = EL_EMPTY;
3807   MovPos[x][y] = 0;
3808   MovDir[x][y] = 0;
3809   MovDelay[x][y] = 0;
3810 }
3811 #endif
3812
3813 static void RemoveMovingField_MM(int x, int y)
3814 {
3815   int oldx = x, oldy = y, newx = x, newy = y;
3816
3817   if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3818     return;
3819
3820   if (IS_MOVING(x, y))
3821   {
3822     Moving2Blocked_MM(x, y, &newx, &newy);
3823     if (Feld[newx][newy] != EL_BLOCKED)
3824       return;
3825   }
3826   else if (Feld[x][y] == EL_BLOCKED)
3827   {
3828     Blocked2Moving_MM(x, y, &oldx, &oldy);
3829     if (!IS_MOVING(oldx, oldy))
3830       return;
3831   }
3832
3833   Feld[oldx][oldy] = EL_EMPTY;
3834   Feld[newx][newy] = EL_EMPTY;
3835   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3836   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3837
3838   DrawLevelField_MM(oldx, oldy);
3839   DrawLevelField_MM(newx, newy);
3840 }
3841
3842 void PlaySoundLevel(int x, int y, int sound_nr)
3843 {
3844   int sx = SCREENX(x), sy = SCREENY(y);
3845   int volume, stereo;
3846   int silence_distance = 8;
3847
3848   if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3849       (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3850     return;
3851
3852   if (!IN_LEV_FIELD(x, y) ||
3853       sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3854       sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3855     return;
3856
3857   volume = SOUND_MAX_VOLUME;
3858
3859 #ifndef MSDOS
3860   stereo = (sx - SCR_FIELDX/2) * 12;
3861 #else
3862   stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3863   if (stereo > SOUND_MAX_RIGHT)
3864     stereo = SOUND_MAX_RIGHT;
3865   if (stereo < SOUND_MAX_LEFT)
3866     stereo = SOUND_MAX_LEFT;
3867 #endif
3868
3869   if (!IN_SCR_FIELD(sx, sy))
3870   {
3871     int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3872     int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3873
3874     volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3875   }
3876
3877   PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3878 }
3879
3880 static void RaiseScore_MM(int value)
3881 {
3882   game_mm.score += value;
3883
3884 #if 0
3885   DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
3886            FONT_TEXT_2);
3887 #endif
3888 }
3889
3890 void RaiseScoreElement_MM(int element)
3891 {
3892   switch(element)
3893   {
3894     case EL_PACMAN:
3895       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3896       break;
3897
3898     case EL_KEY:
3899       RaiseScore_MM(native_mm_level.score[SC_KEY]);
3900       break;
3901
3902     default:
3903       break;
3904   }
3905 }