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