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