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