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