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