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