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