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