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