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