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