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