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