added handling global animations during delays in Mirror Magic game engine
[rocksndiamonds.git] / src / game_mm / mm_game.c
1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  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_WithScreenUpdates(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       UpdateAndDisplayGameControlValues();
3256
3257       for (i = 15; i >= 0; i--)
3258       {
3259 #if 0
3260         SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3261 #endif
3262
3263         pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3264
3265         DrawLaser(0, DL_LASER_ENABLED);
3266         BackToFront();
3267         Delay_WithScreenUpdates(50);
3268       }
3269
3270       DrawLaser(0, DL_LASER_DISABLED);
3271
3272       game_mm.game_over = TRUE;
3273       game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3274
3275       SetTileCursorActive(FALSE);
3276
3277       game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?";
3278
3279 #if 0
3280       if (Request("Magic spell hit Mc Duffin! Play it again?",
3281                   REQ_ASK | REQ_STAY_CLOSED))
3282       {
3283         InitGame();
3284       }
3285       else
3286       {
3287         game_status = MAINMENU;
3288         DrawMainMenu();
3289       }
3290 #endif
3291
3292       return;
3293     }
3294   }
3295
3296   if (laser.fuse_off)
3297     return;
3298
3299   CT -= Ct;
3300
3301   if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3302   {
3303     if (game_mm.cheat_no_explosion)
3304       return;
3305
3306 #if 0
3307     laser.num_damages--;
3308     DrawLaser(0, DL_LASER_DISABLED);
3309     laser.num_edges = 0;
3310 #endif
3311
3312     Bang_MM(ELX, ELY);
3313
3314     laser.dest_element = EL_EXPLODING_OPAQUE;
3315
3316 #if 0
3317     Bang_MM(ELX, ELY);
3318     laser.num_damages--;
3319     DrawLaser(0, DL_LASER_DISABLED);
3320
3321     laser.num_edges = 0;
3322     Bang_MM(laser.start_edge.x, laser.start_edge.y);
3323
3324     if (Request("Bomb killed Mc Duffin! Play it again?",
3325                 REQ_ASK | REQ_STAY_CLOSED))
3326     {
3327       InitGame();
3328     }
3329     else
3330     {
3331       game_status = MAINMENU;
3332       DrawMainMenu();
3333     }
3334 #endif
3335
3336     return;
3337   }
3338
3339   if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3340   {
3341     laser.fuse_off = TRUE;
3342     laser.fuse_x = ELX;
3343     laser.fuse_y = ELY;
3344
3345     DrawLaser(0, DL_LASER_DISABLED);
3346     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3347   }
3348
3349   if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3350   {
3351     static int new_elements[] =
3352     {
3353       EL_MIRROR_START,
3354       EL_MIRROR_FIXED_START,
3355       EL_POLAR_START,
3356       EL_POLAR_CROSS_START,
3357       EL_PACMAN_START,
3358       EL_KETTLE,
3359       EL_BOMB,
3360       EL_PRISM
3361     };
3362     int num_new_elements = sizeof(new_elements) / sizeof(int);
3363     int new_element = new_elements[RND(num_new_elements)];
3364
3365     Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3366     Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3367
3368     // !!! CHECK AGAIN: Laser on Polarizer !!!
3369     ScanLaser();
3370
3371     return;
3372
3373 #if 0
3374     int graphic;
3375
3376     switch (RND(5))
3377     {
3378       case 0:
3379         element = EL_MIRROR_START + RND(16);
3380         break;
3381       case 1:
3382         {
3383           int rnd = RND(3);
3384
3385           element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3386         }
3387         break;
3388       default:
3389         {
3390           int rnd = RND(3);
3391
3392           element = (rnd == 0 ? EL_FUSE_ON :
3393                      rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3394                      rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3395                      rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3396                      EL_MIRROR_FIXED_START + rnd - 25);
3397         }
3398         break;
3399     }
3400
3401     graphic = el2gfx(element);
3402
3403     for (i = 0; i < 50; i++)
3404     {
3405       int x = RND(26);
3406       int y = RND(26);
3407
3408 #if 0
3409       BlitBitmap(pix[PIX_BACK], drawto,
3410                  SX + (graphic % GFX_PER_LINE) * TILEX + x,
3411                  SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3412                  SX + ELX * TILEX + x,
3413                  SY + ELY * TILEY + y);
3414 #endif
3415       MarkTileDirty(ELX, ELY);
3416       BackToFront();
3417
3418       DrawLaser(0, DL_LASER_ENABLED);
3419
3420       Delay_WithScreenUpdates(50);
3421     }
3422
3423     Feld[ELX][ELY] = element;
3424     DrawField_MM(ELX, ELY);
3425
3426 #if 0
3427     printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3428 #endif
3429
3430     // above stuff: GRAY BALL -> PRISM !!!
3431 /*
3432     LX = ELX * TILEX + 14;
3433     LY = ELY * TILEY + 14;
3434     if (laser.current_angle == (laser.current_angle >> 1) << 1)
3435       OK = 8;
3436     else
3437       OK = 4;
3438     LX -= OK * XS;
3439     LY -= OK * YS;
3440
3441     laser.num_edges -= 2;
3442     laser.num_damages--;
3443 */
3444
3445 #if 0
3446     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3447       if (laser.damage[i].is_mirror)
3448         break;
3449
3450     if (i > 0)
3451       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3452     else
3453       DrawLaser(0, DL_LASER_DISABLED);
3454 #else
3455     DrawLaser(0, DL_LASER_DISABLED);
3456 #endif
3457
3458     ScanLaser();
3459
3460     /*
3461     printf("TEST ELEMENT: %d\n", Feld[0][0]);
3462     */
3463 #endif
3464
3465     return;
3466   }
3467
3468   if (IS_WALL_ICE(element) && CT > 50)
3469   {
3470     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3471
3472     {
3473       Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3474       Store[ELX][ELY] = EL_WALL_ICE;
3475       Store2[ELX][ELY] = laser.wall_mask;
3476
3477       laser.dest_element = Feld[ELX][ELY];
3478
3479       return;
3480     }
3481
3482     for (i = 0; i < 5; i++)
3483     {
3484       int phase = i + 1;
3485
3486       if (i == 4)
3487       {
3488         Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3489         phase = 0;
3490       }
3491
3492       DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3493       BackToFront();
3494       Delay_WithScreenUpdates(100);
3495     }
3496
3497     if (Feld[ELX][ELY] == EL_WALL_ICE)
3498       Feld[ELX][ELY] = EL_EMPTY;
3499
3500 /*
3501     laser.num_edges--;
3502     LX = laser.edge[laser.num_edges].x - cSX2;
3503     LY = laser.edge[laser.num_edges].y - cSY2;
3504 */
3505
3506     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3507       if (laser.damage[i].is_mirror)
3508         break;
3509
3510     if (i > 0)
3511       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3512     else
3513       DrawLaser(0, DL_LASER_DISABLED);
3514
3515     ScanLaser();
3516
3517     return;
3518   }
3519
3520   if (IS_WALL_AMOEBA(element) && CT > 60)
3521   {
3522     int k1, k2, k3, dx, dy, de, dm;
3523     int element2 = Feld[ELX][ELY];
3524
3525     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3526       return;
3527
3528     for (i = laser.num_damages - 1; i >= 0; i--)
3529       if (laser.damage[i].is_mirror)
3530         break;
3531
3532     r = laser.num_edges;
3533     d = laser.num_damages;
3534     k1 = i;
3535
3536     if (k1 > 0)
3537     {
3538       int x, y;
3539
3540       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3541
3542       laser.num_edges++;
3543       DrawLaser(0, DL_LASER_ENABLED);
3544       laser.num_edges--;
3545
3546       x = laser.damage[k1].x;
3547       y = laser.damage[k1].y;
3548
3549       DrawField_MM(x, y);
3550     }
3551
3552     for (i = 0; i < 4; i++)
3553     {
3554       if (laser.wall_mask & (1 << i))
3555       {
3556         if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3557                             cSY + ELY * TILEY + 31 * (i / 2)))
3558           break;
3559
3560         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3561                             cSY + ELY * TILEY + 14 + (i / 2) * 2))
3562           break;
3563       }
3564     }
3565
3566     k2 = i;
3567
3568     for (i = 0; i < 4; i++)
3569     {
3570       if (laser.wall_mask & (1 << i))
3571       {
3572         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3573                             cSY + ELY * TILEY + 31 * (i / 2)))
3574           break;
3575       }
3576     }
3577
3578     k3 = i;
3579
3580     if (laser.num_beamers > 0 ||
3581         k1 < 1 || k2 < 4 || k3 < 4 ||
3582         CheckLaserPixel(cSX + ELX * TILEX + 14,
3583                         cSY + ELY * TILEY + 14))
3584     {
3585       laser.num_edges = r;
3586       laser.num_damages = d;
3587
3588       DrawLaser(0, DL_LASER_DISABLED);
3589     }
3590
3591     Feld[ELX][ELY] = element | laser.wall_mask;
3592
3593     dx = ELX;
3594     dy = ELY;
3595     de = Feld[ELX][ELY];
3596     dm = laser.wall_mask;
3597
3598 #if 1
3599     {
3600       int x = ELX, y = ELY;
3601       int wall_mask = laser.wall_mask;
3602
3603       ScanLaser();
3604       DrawLaser(0, DL_LASER_ENABLED);
3605
3606       PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3607
3608       Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3609       Store[x][y] = EL_WALL_AMOEBA;
3610       Store2[x][y] = wall_mask;
3611
3612       return;
3613     }
3614 #endif
3615
3616     DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3617     ScanLaser();
3618     DrawLaser(0, DL_LASER_ENABLED);
3619
3620     PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3621
3622     for (i = 4; i >= 0; i--)
3623     {
3624       DrawWallsAnimation_MM(dx, dy, de, i, dm);
3625
3626       BackToFront();
3627       Delay_WithScreenUpdates(20);
3628     }
3629
3630     DrawLaser(0, DL_LASER_ENABLED);
3631
3632     return;
3633   }
3634
3635   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3636       laser.stops_inside_element && CT > native_mm_level.time_block)
3637   {
3638     int x, y;
3639     int k;
3640
3641     if (ABS(XS) > ABS(YS))
3642       k = 0;
3643     else
3644       k = 1;
3645     if (XS < YS)
3646       k += 2;
3647
3648     for (i = 0; i < 4; i++)
3649     {
3650       if (i)
3651         k++;
3652       if (k > 3)
3653         k = 0;
3654
3655       x = ELX + Step[k * 4].x;
3656       y = ELY + Step[k * 4].y;
3657
3658       if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3659         continue;
3660
3661       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3662         continue;
3663
3664       break;
3665     }
3666
3667     if (i > 3)
3668     {
3669       laser.overloaded = (element == EL_BLOCK_STONE);
3670
3671       return;
3672     }
3673
3674     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3675
3676     Feld[ELX][ELY] = 0;
3677     Feld[x][y] = element;
3678
3679     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3680     DrawField_MM(x, y);
3681
3682     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3683     {
3684       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3685       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3686     }
3687
3688     ScanLaser();
3689
3690     return;
3691   }
3692
3693   if (element == EL_FUEL_FULL && CT > 10)
3694   {
3695     for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3696     {
3697 #if 0
3698       BlitBitmap(pix[PIX_DOOR], drawto,
3699                  DOOR_GFX_PAGEX4 + XX_ENERGY,
3700                  DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3701                  ENERGY_XSIZE, i, DX_ENERGY,
3702                  DY_ENERGY + ENERGY_YSIZE - i);
3703 #endif
3704
3705       redraw_mask |= REDRAW_DOOR_1;
3706       BackToFront();
3707
3708       Delay_WithScreenUpdates(20);
3709     }
3710
3711     game_mm.energy_left = MAX_LASER_ENERGY;
3712     Feld[ELX][ELY] = EL_FUEL_EMPTY;
3713     DrawField_MM(ELX, ELY);
3714
3715     DrawLaser(0, DL_LASER_ENABLED);
3716
3717     return;
3718   }
3719
3720   return;
3721 }
3722
3723 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3724 {
3725   boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3726   boolean button_released = (action.button == MB_RELEASED);
3727
3728   GameActions_MM_Ext(action, warp_mode);
3729
3730   CheckSingleStepMode_MM(element_clicked, button_released);
3731 }
3732
3733 void MovePacMen(void)
3734 {
3735   int mx, my, ox, oy, nx, ny;
3736   int element;
3737   int l;
3738
3739   if (++pacman_nr >= game_mm.num_pacman)
3740     pacman_nr = 0;
3741
3742   game_mm.pacman[pacman_nr].dir--;
3743
3744   for (l = 1; l < 5; l++)
3745   {
3746     game_mm.pacman[pacman_nr].dir++;
3747
3748     if (game_mm.pacman[pacman_nr].dir > 4)
3749       game_mm.pacman[pacman_nr].dir = 1;
3750
3751     if (game_mm.pacman[pacman_nr].dir % 2)
3752     {
3753       mx = 0;
3754       my = game_mm.pacman[pacman_nr].dir - 2;
3755     }
3756     else
3757     {
3758       my = 0;
3759       mx = 3 - game_mm.pacman[pacman_nr].dir;
3760     }
3761
3762     ox = game_mm.pacman[pacman_nr].x;
3763     oy = game_mm.pacman[pacman_nr].y;
3764     nx = ox + mx;
3765     ny = oy + my;
3766     element = Feld[nx][ny];
3767
3768     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3769       continue;
3770
3771     if (!IS_EATABLE4PACMAN(element))
3772       continue;
3773
3774     if (ObjHit(nx, ny, HIT_POS_CENTER))
3775       continue;
3776
3777     Feld[ox][oy] = EL_EMPTY;
3778     Feld[nx][ny] =
3779       EL_PACMAN_RIGHT - 1 +
3780       (game_mm.pacman[pacman_nr].dir - 1 +
3781        (game_mm.pacman[pacman_nr].dir % 2) * 2);
3782
3783     game_mm.pacman[pacman_nr].x = nx;
3784     game_mm.pacman[pacman_nr].y = ny;
3785
3786     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3787
3788     if (element != EL_EMPTY)
3789     {
3790       int graphic = el2gfx(Feld[nx][ny]);
3791       Bitmap *bitmap;
3792       int src_x, src_y;
3793       int i;
3794
3795       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3796
3797       CT = FrameCounter;
3798       ox = cSX + ox * TILEX;
3799       oy = cSY + oy * TILEY;
3800
3801       for (i = 1; i < 33; i += 2)
3802         BlitBitmap(bitmap, window,
3803                    src_x, src_y, TILEX, TILEY,
3804                    ox + i * mx, oy + i * my);
3805       Ct = Ct + FrameCounter - CT;
3806     }
3807
3808     DrawField_MM(nx, ny);
3809     BackToFront();
3810
3811     if (!laser.fuse_off)
3812     {
3813       DrawLaser(0, DL_LASER_ENABLED);
3814
3815       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3816       {
3817         AddDamagedField(nx, ny);
3818
3819         laser.damage[laser.num_damages - 1].edge = 0;
3820       }
3821     }
3822
3823     if (element == EL_BOMB)
3824       DeletePacMan(nx, ny);
3825
3826     if (IS_WALL_AMOEBA(element) &&
3827         (LX + 2 * XS) / TILEX == nx &&
3828         (LY + 2 * YS) / TILEY == ny)
3829     {
3830       laser.num_edges--;
3831       ScanLaser();
3832     }
3833
3834     break;
3835   }
3836 }
3837
3838 void GameWon_MM(void)
3839 {
3840   int hi_pos;
3841   boolean raise_level = FALSE;
3842
3843 #if 0
3844   if (local_player->MovPos)
3845     return;
3846
3847   local_player->LevelSolved = FALSE;
3848 #endif
3849
3850   if (game_mm.energy_left)
3851   {
3852     if (setup.sound_loops)
3853       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3854                    SND_CTRL_PLAY_LOOP);
3855
3856     while (game_mm.energy_left > 0)
3857     {
3858       if (!setup.sound_loops)
3859         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3860
3861       /*
3862       if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3863         RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3864       */
3865
3866       RaiseScore_MM(5);
3867
3868       game_mm.energy_left--;
3869       if (game_mm.energy_left >= 0)
3870       {
3871 #if 0
3872         BlitBitmap(pix[PIX_DOOR], drawto,
3873                    DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3874                    ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3875                    DX_ENERGY, DY_ENERGY);
3876 #endif
3877         redraw_mask |= REDRAW_DOOR_1;
3878       }
3879
3880       BackToFront();
3881       Delay_WithScreenUpdates(10);
3882     }
3883
3884     if (setup.sound_loops)
3885       StopSound(SND_SIRR);
3886   }
3887   else if (native_mm_level.time == 0)           // level without time limit
3888   {
3889     if (setup.sound_loops)
3890       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3891                    SND_CTRL_PLAY_LOOP);
3892
3893     while (TimePlayed < 999)
3894     {
3895       if (!setup.sound_loops)
3896         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3897       if (TimePlayed < 999 && !(TimePlayed % 10))
3898         RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3899       if (TimePlayed < 900 && !(TimePlayed % 10))
3900         TimePlayed += 10;
3901       else
3902         TimePlayed++;
3903
3904       /*
3905       DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3906       */
3907
3908       BackToFront();
3909       Delay_WithScreenUpdates(10);
3910     }
3911
3912     if (setup.sound_loops)
3913       StopSound(SND_SIRR);
3914   }
3915
3916   CloseDoor(DOOR_CLOSE_1);
3917
3918   Request("Level solved!", REQ_CONFIRM);
3919
3920   if (level_nr == leveldir_current->handicap_level)
3921   {
3922     leveldir_current->handicap_level++;
3923     SaveLevelSetup_SeriesInfo();
3924   }
3925
3926   if (level_editor_test_game)
3927     game_mm.score = -1;         // no highscore when playing from editor
3928   else if (level_nr < leveldir_current->last_level)
3929     raise_level = TRUE;         // advance to next level
3930
3931   if ((hi_pos = NewHiScore_MM()) >= 0)
3932   {
3933     game_status = HALLOFFAME;
3934
3935     // DrawHallOfFame(hi_pos);
3936
3937     if (raise_level)
3938       level_nr++;
3939   }
3940   else
3941   {
3942     game_status = MAINMENU;
3943
3944     if (raise_level)
3945       level_nr++;
3946
3947     // DrawMainMenu();
3948   }
3949
3950   BackToFront();
3951 }
3952
3953 int NewHiScore_MM(void)
3954 {
3955   int k, l;
3956   int position = -1;
3957
3958   // LoadScore(level_nr);
3959
3960   if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3961       game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3962     return -1;
3963
3964   for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3965   {
3966     if (game_mm.score > highscore[k].Score)
3967     {
3968       // player has made it to the hall of fame
3969
3970       if (k < MAX_SCORE_ENTRIES - 1)
3971       {
3972         int m = MAX_SCORE_ENTRIES - 1;
3973
3974 #ifdef ONE_PER_NAME
3975         for (l = k; l < MAX_SCORE_ENTRIES; l++)
3976           if (!strcmp(setup.player_name, highscore[l].Name))
3977             m = l;
3978         if (m == k)     // player's new highscore overwrites his old one
3979           goto put_into_list;
3980 #endif
3981
3982         for (l = m; l>k; l--)
3983         {
3984           strcpy(highscore[l].Name, highscore[l - 1].Name);
3985           highscore[l].Score = highscore[l - 1].Score;
3986         }
3987       }
3988
3989 #ifdef ONE_PER_NAME
3990       put_into_list:
3991 #endif
3992       strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3993       highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3994       highscore[k].Score = game_mm.score;
3995       position = k;
3996
3997       break;
3998     }
3999
4000 #ifdef ONE_PER_NAME
4001     else if (!strncmp(setup.player_name, highscore[k].Name,
4002                       MAX_PLAYER_NAME_LEN))
4003       break;    // player already there with a higher score
4004 #endif
4005
4006   }
4007
4008   // if (position >= 0)
4009   //   SaveScore(level_nr);
4010
4011   return position;
4012 }
4013
4014 static void InitMovingField_MM(int x, int y, int direction)
4015 {
4016   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4017   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
4018
4019   MovDir[x][y] = direction;
4020   MovDir[newx][newy] = direction;
4021
4022   if (Feld[newx][newy] == EL_EMPTY)
4023     Feld[newx][newy] = EL_BLOCKED;
4024 }
4025
4026 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4027 {
4028   int direction = MovDir[x][y];
4029   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4030   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
4031
4032   *goes_to_x = newx;
4033   *goes_to_y = newy;
4034 }
4035
4036 static void Blocked2Moving_MM(int x, int y,
4037                               int *comes_from_x, int *comes_from_y)
4038 {
4039   int oldx = x, oldy = y;
4040   int direction = MovDir[x][y];
4041
4042   if (direction == MV_LEFT)
4043     oldx++;
4044   else if (direction == MV_RIGHT)
4045     oldx--;
4046   else if (direction == MV_UP)
4047     oldy++;
4048   else if (direction == MV_DOWN)
4049     oldy--;
4050
4051   *comes_from_x = oldx;
4052   *comes_from_y = oldy;
4053 }
4054
4055 static int MovingOrBlocked2Element_MM(int x, int y)
4056 {
4057   int element = Feld[x][y];
4058
4059   if (element == EL_BLOCKED)
4060   {
4061     int oldx, oldy;
4062
4063     Blocked2Moving_MM(x, y, &oldx, &oldy);
4064
4065     return Feld[oldx][oldy];
4066   }
4067
4068   return element;
4069 }
4070
4071 #if 0
4072 static void RemoveField(int x, int y)
4073 {
4074   Feld[x][y] = EL_EMPTY;
4075   MovPos[x][y] = 0;
4076   MovDir[x][y] = 0;
4077   MovDelay[x][y] = 0;
4078 }
4079 #endif
4080
4081 static void RemoveMovingField_MM(int x, int y)
4082 {
4083   int oldx = x, oldy = y, newx = x, newy = y;
4084
4085   if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4086     return;
4087
4088   if (IS_MOVING(x, y))
4089   {
4090     Moving2Blocked_MM(x, y, &newx, &newy);
4091     if (Feld[newx][newy] != EL_BLOCKED)
4092       return;
4093   }
4094   else if (Feld[x][y] == EL_BLOCKED)
4095   {
4096     Blocked2Moving_MM(x, y, &oldx, &oldy);
4097     if (!IS_MOVING(oldx, oldy))
4098       return;
4099   }
4100
4101   Feld[oldx][oldy] = EL_EMPTY;
4102   Feld[newx][newy] = EL_EMPTY;
4103   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4104   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4105
4106   DrawLevelField_MM(oldx, oldy);
4107   DrawLevelField_MM(newx, newy);
4108 }
4109
4110 void PlaySoundLevel(int x, int y, int sound_nr)
4111 {
4112   int sx = SCREENX(x), sy = SCREENY(y);
4113   int volume, stereo;
4114   int silence_distance = 8;
4115
4116   if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4117       (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4118     return;
4119
4120   if (!IN_LEV_FIELD(x, y) ||
4121       sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4122       sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4123     return;
4124
4125   volume = SOUND_MAX_VOLUME;
4126
4127 #ifndef MSDOS
4128   stereo = (sx - SCR_FIELDX/2) * 12;
4129 #else
4130   stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4131   if (stereo > SOUND_MAX_RIGHT)
4132     stereo = SOUND_MAX_RIGHT;
4133   if (stereo < SOUND_MAX_LEFT)
4134     stereo = SOUND_MAX_LEFT;
4135 #endif
4136
4137   if (!IN_SCR_FIELD(sx, sy))
4138   {
4139     int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4140     int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4141
4142     volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4143   }
4144
4145   PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4146 }
4147
4148 static void RaiseScore_MM(int value)
4149 {
4150   game_mm.score += value;
4151 }
4152
4153 void RaiseScoreElement_MM(int element)
4154 {
4155   switch(element)
4156   {
4157     case EL_PACMAN:
4158     case EL_PACMAN_RIGHT:
4159     case EL_PACMAN_UP:
4160     case EL_PACMAN_LEFT:
4161     case EL_PACMAN_DOWN:
4162       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4163       break;
4164
4165     case EL_KEY:
4166       RaiseScore_MM(native_mm_level.score[SC_KEY]);
4167       break;
4168
4169     case EL_KETTLE:
4170     case EL_CELL:
4171       RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4172       break;
4173
4174     case EL_LIGHTBALL:
4175       RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4176       break;
4177
4178     default:
4179       break;
4180   }
4181 }
4182
4183
4184 // ----------------------------------------------------------------------------
4185 // Mirror Magic game engine snapshot handling functions
4186 // ----------------------------------------------------------------------------
4187
4188 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4189 {
4190   int x, y;
4191
4192   engine_snapshot_mm.game_mm = game_mm;
4193   engine_snapshot_mm.laser = laser;
4194
4195   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4196   {
4197     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4198     {
4199       engine_snapshot_mm.Ur[x][y]    = Ur[x][y];
4200       engine_snapshot_mm.Hit[x][y]   = Hit[x][y];
4201       engine_snapshot_mm.Box[x][y]   = Box[x][y];
4202       engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4203       engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4204     }
4205   }
4206
4207   engine_snapshot_mm.LX = LX;
4208   engine_snapshot_mm.LY = LY;
4209   engine_snapshot_mm.XS = XS;
4210   engine_snapshot_mm.YS = YS;
4211   engine_snapshot_mm.ELX = ELX;
4212   engine_snapshot_mm.ELY = ELY;
4213   engine_snapshot_mm.CT = CT;
4214   engine_snapshot_mm.Ct = Ct;
4215
4216   engine_snapshot_mm.last_LX = last_LX;
4217   engine_snapshot_mm.last_LY = last_LY;
4218   engine_snapshot_mm.last_hit_mask = last_hit_mask;
4219   engine_snapshot_mm.hold_x = hold_x;
4220   engine_snapshot_mm.hold_y = hold_y;
4221   engine_snapshot_mm.pacman_nr = pacman_nr;
4222
4223   engine_snapshot_mm.rotate_delay = rotate_delay;
4224   engine_snapshot_mm.pacman_delay = pacman_delay;
4225   engine_snapshot_mm.energy_delay = energy_delay;
4226   engine_snapshot_mm.overload_delay = overload_delay;
4227 }
4228
4229 void LoadEngineSnapshotValues_MM(void)
4230 {
4231   int x, y;
4232
4233   // stored engine snapshot buffers already restored at this point
4234
4235   game_mm = engine_snapshot_mm.game_mm;
4236   laser   = engine_snapshot_mm.laser;
4237
4238   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4239   {
4240     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4241     {
4242       Ur[x][y]    = engine_snapshot_mm.Ur[x][y];
4243       Hit[x][y]   = engine_snapshot_mm.Hit[x][y];
4244       Box[x][y]   = engine_snapshot_mm.Box[x][y];
4245       Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4246       Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4247     }
4248   }
4249
4250   LX  = engine_snapshot_mm.LX;
4251   LY  = engine_snapshot_mm.LY;
4252   XS  = engine_snapshot_mm.XS;
4253   YS  = engine_snapshot_mm.YS;
4254   ELX = engine_snapshot_mm.ELX;
4255   ELY = engine_snapshot_mm.ELY;
4256   CT  = engine_snapshot_mm.CT;
4257   Ct  = engine_snapshot_mm.Ct;
4258
4259   last_LX       = engine_snapshot_mm.last_LX;
4260   last_LY       = engine_snapshot_mm.last_LY;
4261   last_hit_mask = engine_snapshot_mm.last_hit_mask;
4262   hold_x        = engine_snapshot_mm.hold_x;
4263   hold_y        = engine_snapshot_mm.hold_y;
4264   pacman_nr     = engine_snapshot_mm.pacman_nr;
4265
4266   rotate_delay   = engine_snapshot_mm.rotate_delay;
4267   pacman_delay   = engine_snapshot_mm.pacman_delay;
4268   energy_delay   = engine_snapshot_mm.energy_delay;
4269   overload_delay = engine_snapshot_mm.overload_delay;
4270
4271   RedrawPlayfield_MM();
4272 }
4273
4274 static int getAngleFromTouchDelta(int dx, int dy,  int base)
4275 {
4276   double pi = 3.141592653;
4277   double rad = atan2((double)-dy, (double)dx);
4278   double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4279   double deg = rad2 * 180.0 / pi;
4280
4281   return (int)(deg * base / 360.0 + 0.5) % base;
4282 }
4283
4284 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4285 {
4286   // calculate start (source) position to be at the middle of the tile
4287   int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4288   int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4289   int dx = dst_mx - src_mx;
4290   int dy = dst_my - src_my;
4291   int element;
4292   int base = 16;
4293   int phases = 16;
4294   int angle_old = -1;
4295   int angle_new = -1;
4296   int button = 0;
4297   int i;
4298
4299   if (!IN_LEV_FIELD(x, y))
4300     return 0;
4301
4302   element = Feld[x][y];
4303
4304   if (!IS_MCDUFFIN(element) &&
4305       !IS_MIRROR(element) &&
4306       !IS_BEAMER(element) &&
4307       !IS_POLAR(element) &&
4308       !IS_POLAR_CROSS(element) &&
4309       !IS_DF_MIRROR(element))
4310     return 0;
4311
4312   angle_old = get_element_angle(element);
4313
4314   if (IS_MCDUFFIN(element))
4315   {
4316     angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4317                  dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4318                  dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4319                  dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4320                  -1);
4321   }
4322   else if (IS_MIRROR(element) ||
4323            IS_DF_MIRROR(element))
4324   {
4325     for (i = 0; i < laser.num_damages; i++)
4326     {
4327       if (laser.damage[i].x == x &&
4328           laser.damage[i].y == y &&
4329           ObjHit(x, y, HIT_POS_CENTER))
4330       {
4331         angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4332         angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4333
4334         break;
4335       }
4336     }
4337   }
4338
4339   if (angle_new == -1)
4340   {
4341     if (IS_MIRROR(element) ||
4342         IS_DF_MIRROR(element) ||
4343         IS_POLAR(element))
4344       base = 32;
4345
4346     if (IS_POLAR_CROSS(element))
4347       phases = 4;
4348
4349     angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4350   }
4351
4352   button = (angle_new == angle_old ? 0 :
4353             (angle_new - angle_old + phases) % phases < (phases / 2) ?
4354             MB_LEFTBUTTON : MB_RIGHTBUTTON);
4355
4356   return button;
4357 }