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