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