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