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