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