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