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