0a9d2da6fe061ecab630719923aba7ca3cf565ce
[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       game.restart_game_message = "Bomb killed Mc Duffin ! Play it again ?";
2495     }
2496
2497     Feld[x][y] = Store[x][y];
2498     Store[x][y] = Store2[x][y] = 0;
2499     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2500
2501     InitField(x, y, FALSE);
2502     DrawField_MM(x, y);
2503   }
2504   else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2505   {
2506     int graphic = IMG_MM_DEFAULT_EXPLODING;
2507     int graphic_phase = (phase / delay - 1);
2508     Bitmap *bitmap;
2509     int src_x, src_y;
2510
2511     if (Store2[x][y] == EX_KETTLE)
2512     {
2513       if (graphic_phase < 3)
2514       {
2515         graphic = IMG_MM_KETTLE_EXPLODING;
2516       }
2517       else if (graphic_phase < 5)
2518       {
2519         graphic_phase += 3;
2520       }
2521       else
2522       {
2523         graphic = IMG_EMPTY;
2524         graphic_phase = 0;
2525       }
2526     }
2527     else if (Store2[x][y] == EX_SHORT)
2528     {
2529       if (graphic_phase < 4)
2530       {
2531         graphic_phase += 4;
2532       }
2533       else
2534       {
2535         graphic = IMG_EMPTY;
2536         graphic_phase = 0;
2537       }
2538     }
2539
2540     getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2541
2542     BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2543                FX + x * TILEX, FY + y * TILEY);
2544
2545     MarkTileDirty(x, y);
2546   }
2547 }
2548
2549 static void Bang_MM(int x, int y)
2550 {
2551   int element = Feld[x][y];
2552   int mode = EX_NORMAL;
2553
2554 #if 0
2555   DrawLaser(0, DL_LASER_ENABLED);
2556 #endif
2557
2558   switch(element)
2559   {
2560     case EL_KETTLE:
2561       mode = EX_KETTLE;
2562       break;
2563
2564     case EL_GATE_STONE:
2565     case EL_GATE_WOOD:
2566       mode = EX_SHORT;
2567       break;
2568
2569     default:
2570       mode = EX_NORMAL;
2571       break;
2572   }
2573
2574   if (IS_PACMAN(element))
2575     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2576   else if (element == EL_BOMB || IS_MCDUFFIN(element))
2577     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2578   else if (element == EL_KEY)
2579     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2580   else
2581     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2582
2583   Explode_MM(x, y, EX_PHASE_START, mode);
2584 }
2585
2586 void TurnRound(int x, int y)
2587 {
2588   static struct
2589   {
2590     int x, y;
2591   } move_xy[] =
2592   {
2593     { 0, 0 },
2594     {-1, 0 },
2595     {+1, 0 },
2596     { 0, 0 },
2597     { 0, -1 },
2598     { 0, 0 }, { 0, 0 }, { 0, 0 },
2599     { 0, +1 }
2600   };
2601   static struct
2602   {
2603     int left, right, back;
2604   } turn[] =
2605   {
2606     { 0,        0,              0 },
2607     { MV_DOWN,  MV_UP,          MV_RIGHT },
2608     { MV_UP,    MV_DOWN,        MV_LEFT },
2609     { 0,        0,              0 },
2610     { MV_LEFT,  MV_RIGHT,       MV_DOWN },
2611     { 0,0,0 },  { 0,0,0 },      { 0,0,0 },
2612     { MV_RIGHT, MV_LEFT,        MV_UP }
2613   };
2614
2615   int element = Feld[x][y];
2616   int old_move_dir = MovDir[x][y];
2617   int right_dir = turn[old_move_dir].right;
2618   int back_dir = turn[old_move_dir].back;
2619   int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2620   int right_x = x + right_dx, right_y = y + right_dy;
2621
2622   if (element == EL_PACMAN)
2623   {
2624     boolean can_turn_right = FALSE;
2625
2626     if (IN_LEV_FIELD(right_x, right_y) &&
2627         IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2628       can_turn_right = TRUE;
2629
2630     if (can_turn_right)
2631       MovDir[x][y] = right_dir;
2632     else
2633       MovDir[x][y] = back_dir;
2634
2635     MovDelay[x][y] = 0;
2636   }
2637 }
2638
2639 static void StartMoving_MM(int x, int y)
2640 {
2641   int element = Feld[x][y];
2642
2643   if (Stop[x][y])
2644     return;
2645
2646   if (CAN_MOVE(element))
2647   {
2648     int newx, newy;
2649
2650     if (MovDelay[x][y])         /* wait some time before next movement */
2651     {
2652       MovDelay[x][y]--;
2653
2654       if (MovDelay[x][y])
2655         return;
2656     }
2657
2658     /* now make next step */
2659
2660     Moving2Blocked_MM(x, y, &newx, &newy);      /* get next screen position */
2661
2662     if (element == EL_PACMAN &&
2663         IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2664         !ObjHit(newx, newy, HIT_POS_CENTER))
2665     {
2666       Store[newx][newy] = Feld[newx][newy];
2667       Feld[newx][newy] = EL_EMPTY;
2668
2669       DrawField_MM(newx, newy);
2670     }
2671     else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2672              ObjHit(newx, newy, HIT_POS_CENTER))
2673     {
2674       /* object was running against a wall */
2675
2676       TurnRound(x, y);
2677
2678       return;
2679     }
2680
2681     InitMovingField_MM(x, y, MovDir[x][y]);
2682   }
2683
2684   if (MovDir[x][y])
2685     ContinueMoving_MM(x, y);
2686 }
2687
2688 static void ContinueMoving_MM(int x, int y)
2689 {
2690   int element = Feld[x][y];
2691   int direction = MovDir[x][y];
2692   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2693   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
2694   int horiz_move = (dx!=0);
2695   int newx = x + dx, newy = y + dy;
2696   int step = (horiz_move ? dx : dy) * TILEX / 8;
2697
2698   MovPos[x][y] += step;
2699
2700   if (ABS(MovPos[x][y]) >= TILEX)       /* object reached its destination */
2701   {
2702     Feld[x][y] = EL_EMPTY;
2703     Feld[newx][newy] = element;
2704
2705     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2706     MovDelay[newx][newy] = 0;
2707
2708     if (!CAN_MOVE(element))
2709       MovDir[newx][newy] = 0;
2710
2711     DrawField_MM(x, y);
2712     DrawField_MM(newx, newy);
2713
2714     Stop[newx][newy] = TRUE;
2715
2716     if (element == EL_PACMAN)
2717     {
2718       if (Store[newx][newy] == EL_BOMB)
2719         Bang_MM(newx, newy);
2720
2721       if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2722           (LX + 2 * XS) / TILEX == newx &&
2723           (LY + 2 * YS) / TILEY == newy)
2724       {
2725         laser.num_edges--;
2726         ScanLaser();
2727       }
2728     }
2729   }
2730   else                          /* still moving on */
2731   {
2732     DrawField_MM(x, y);
2733   }
2734
2735   laser.redraw = TRUE;
2736 }
2737
2738 boolean ClickElement(int x, int y, int button)
2739 {
2740   static unsigned int click_delay = 0;
2741   static int click_delay_value = CLICK_DELAY;
2742   static boolean new_button = TRUE;
2743   boolean element_clicked = FALSE;
2744   int element;
2745
2746   if (button == -1)
2747   {
2748     /* initialize static variables */
2749     click_delay = 0;
2750     click_delay_value = CLICK_DELAY;
2751     new_button = TRUE;
2752
2753     return FALSE;
2754   }
2755
2756   /* do not rotate objects hit by the laser after the game was solved */
2757   if (game_mm.level_solved && Hit[x][y])
2758     return FALSE;
2759
2760   if (button == MB_RELEASED)
2761   {
2762     new_button = TRUE;
2763     click_delay_value = CLICK_DELAY;
2764
2765     /* release eventually hold auto-rotating mirror */
2766     RotateMirror(x, y, MB_RELEASED);
2767
2768     return FALSE;
2769   }
2770
2771   if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2772     return FALSE;
2773
2774   if (button == MB_MIDDLEBUTTON)        /* middle button has no function */
2775     return FALSE;
2776
2777   if (!IN_LEV_FIELD(x, y))
2778     return FALSE;
2779
2780   if (Feld[x][y] == EL_EMPTY)
2781     return FALSE;
2782
2783   element = Feld[x][y];
2784
2785   if (IS_MIRROR(element) ||
2786       IS_BEAMER(element) ||
2787       IS_POLAR(element) ||
2788       IS_POLAR_CROSS(element) ||
2789       IS_DF_MIRROR(element) ||
2790       IS_DF_MIRROR_AUTO(element))
2791   {
2792     RotateMirror(x, y, button);
2793
2794     element_clicked = TRUE;
2795   }
2796   else if (IS_MCDUFFIN(element))
2797   {
2798     if (!laser.fuse_off)
2799     {
2800       DrawLaser(0, DL_LASER_DISABLED);
2801
2802       /*
2803       BackToFront();
2804       */
2805     }
2806
2807     element = get_rotated_element(element, BUTTON_ROTATION(button));
2808     laser.start_angle = get_element_angle(element);
2809
2810     InitLaser();
2811
2812     Feld[x][y] = element;
2813     DrawField_MM(x, y);
2814
2815     /*
2816     BackToFront();
2817     */
2818
2819     if (!laser.fuse_off)
2820       ScanLaser();
2821
2822     element_clicked = TRUE;
2823   }
2824   else if (element == EL_FUSE_ON && laser.fuse_off)
2825   {
2826     if (x != laser.fuse_x || y != laser.fuse_y)
2827       return FALSE;
2828
2829     laser.fuse_off = FALSE;
2830     laser.fuse_x = laser.fuse_y = -1;
2831
2832     DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2833     ScanLaser();
2834
2835     element_clicked = TRUE;
2836   }
2837   else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2838   {
2839     laser.fuse_off = TRUE;
2840     laser.fuse_x = x;
2841     laser.fuse_y = y;
2842     laser.overloaded = FALSE;
2843
2844     DrawLaser(0, DL_LASER_DISABLED);
2845     DrawGraphic_MM(x, y, IMG_MM_FUSE);
2846
2847     element_clicked = TRUE;
2848   }
2849   else if (element == EL_LIGHTBALL)
2850   {
2851     Bang_MM(x, y);
2852     RaiseScoreElement_MM(element);
2853     DrawLaser(0, DL_LASER_ENABLED);
2854
2855     element_clicked = TRUE;
2856   }
2857
2858   click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2859   new_button = FALSE;
2860
2861   return element_clicked;
2862 }
2863
2864 void RotateMirror(int x, int y, int button)
2865 {
2866   if (button == MB_RELEASED)
2867   {
2868     /* release eventually hold auto-rotating mirror */
2869     hold_x = -1;
2870     hold_y = -1;
2871
2872     return;
2873   }
2874
2875   if (IS_MIRROR(Feld[x][y]) ||
2876       IS_POLAR_CROSS(Feld[x][y]) ||
2877       IS_POLAR(Feld[x][y]) ||
2878       IS_BEAMER(Feld[x][y]) ||
2879       IS_DF_MIRROR(Feld[x][y]) ||
2880       IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2881       IS_GRID_WOOD_AUTO(Feld[x][y]))
2882   {
2883     Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2884   }
2885   else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2886   {
2887     if (button == MB_LEFTBUTTON)
2888     {
2889       /* left mouse button only for manual adjustment, no auto-rotating;
2890          freeze mirror for until mouse button released */
2891       hold_x = x;
2892       hold_y = y;
2893     }
2894     else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2895     {
2896       Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2897     }
2898   }
2899
2900   if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2901   {
2902     int edge = Hit[x][y];
2903
2904     DrawField_MM(x, y);
2905
2906     if (edge > 0)
2907     {
2908       DrawLaser(edge - 1, DL_LASER_DISABLED);
2909       ScanLaser();
2910     }
2911   }
2912   else if (ObjHit(x, y, HIT_POS_CENTER))
2913   {
2914     int edge = Hit[x][y];
2915
2916     if (edge == 0)
2917     {
2918       Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2919       edge = 1;
2920     }
2921
2922     DrawLaser(edge - 1, DL_LASER_DISABLED);
2923     ScanLaser();
2924   }
2925   else
2926   {
2927     int check = 1;
2928
2929     if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2930       check = 2;
2931
2932     DrawField_MM(x, y);
2933
2934     if ((IS_BEAMER(Feld[x][y]) ||
2935          IS_POLAR(Feld[x][y]) ||
2936          IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2937     {
2938       check = 0;
2939
2940       if (IS_BEAMER(Feld[x][y]))
2941       {
2942 #if 0
2943         printf("TEST (%d, %d) [%d] [%d]\n",
2944                LX, LY,
2945                laser.beamer_edge, laser.beamer[1].num);
2946 #endif
2947
2948         laser.num_edges--;
2949       }
2950
2951       ScanLaser();
2952     }
2953
2954     if (check == 2)
2955       DrawLaser(0, DL_LASER_ENABLED);
2956   }
2957 }
2958
2959 void AutoRotateMirrors()
2960 {
2961   int x, y;
2962
2963   if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2964     return;
2965
2966   for (x = 0; x < lev_fieldx; x++)
2967   {
2968     for (y = 0; y < lev_fieldy; y++)
2969     {
2970       int element = Feld[x][y];
2971
2972       /* do not rotate objects hit by the laser after the game was solved */
2973       if (game_mm.level_solved && Hit[x][y])
2974         continue;
2975
2976       if (IS_DF_MIRROR_AUTO(element) ||
2977           IS_GRID_WOOD_AUTO(element) ||
2978           IS_GRID_STEEL_AUTO(element) ||
2979           element == EL_REFRACTOR)
2980         RotateMirror(x, y, MB_RIGHTBUTTON);
2981     }
2982   }
2983 }
2984
2985 boolean ObjHit(int obx, int oby, int bits)
2986 {
2987   int i;
2988
2989   obx *= TILEX;
2990   oby *= TILEY;
2991
2992   if (bits & HIT_POS_CENTER)
2993   {
2994     if (CheckLaserPixel(SX + obx + 15,
2995                         SY + oby + 15))
2996       return TRUE;
2997   }
2998
2999   if (bits & HIT_POS_EDGE)
3000   {
3001     for (i = 0; i < 4; i++)
3002       if (CheckLaserPixel(SX + obx + 31 * (i % 2),
3003                           SY + oby + 31 * (i / 2)))
3004         return TRUE;
3005   }
3006
3007   if (bits & HIT_POS_BETWEEN)
3008   {
3009     for (i = 0; i < 4; i++)
3010       if (CheckLaserPixel(SX + 4 + obx + 22 * (i % 2),
3011                           SY + 4 + oby + 22 * (i / 2)))
3012         return TRUE;
3013   }
3014
3015   return FALSE;
3016 }
3017
3018 void DeletePacMan(int px, int py)
3019 {
3020   int i, j;
3021
3022   Bang_MM(px, py);
3023
3024   if (game_mm.num_pacman <= 1)
3025   {
3026     game_mm.num_pacman = 0;
3027     return;
3028   }
3029
3030   for (i = 0; i < game_mm.num_pacman; i++)
3031     if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3032       break;
3033
3034   game_mm.num_pacman--;
3035
3036   for (j = i; j < game_mm.num_pacman; j++)
3037   {
3038     game_mm.pacman[j].x   = game_mm.pacman[j + 1].x;
3039     game_mm.pacman[j].y   = game_mm.pacman[j + 1].y;
3040     game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3041   }
3042 }
3043
3044 void ColorCycling(void)
3045 {
3046   static int CC, Cc = 0;
3047
3048   static int color, old = 0xF00, new = 0x010, mult = 1;
3049   static unsigned short red, green, blue;
3050
3051   if (color_status == STATIC_COLORS)
3052     return;
3053
3054   CC = FrameCounter;
3055
3056   if (CC < Cc || CC > Cc + 2)
3057   {
3058     Cc = CC;
3059
3060     color = old + new * mult;
3061     if (mult > 0)
3062       mult++;
3063     else
3064       mult--;
3065
3066     if (ABS(mult) == 16)
3067     {
3068       mult =- mult / 16;
3069       old = color;
3070       new = new << 4;
3071
3072       if (new > 0x100)
3073         new = 0x001;
3074     }
3075
3076     red   = 0x0e00 * ((color & 0xF00) >> 8);
3077     green = 0x0e00 * ((color & 0x0F0) >> 4);
3078     blue  = 0x0e00 * ((color & 0x00F));
3079     SetRGB(pen_magicolor[0], red, green, blue);
3080
3081     red   = 0x1111 * ((color & 0xF00) >> 8);
3082     green = 0x1111 * ((color & 0x0F0) >> 4);
3083     blue  = 0x1111 * ((color & 0x00F));
3084     SetRGB(pen_magicolor[1], red, green, blue);
3085   }
3086 }
3087
3088 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3089 {
3090   int element;
3091   int x, y, i;
3092
3093   int r, d;
3094
3095   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3096     Stop[x][y] = FALSE;
3097
3098   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3099   {
3100     element = Feld[x][y];
3101
3102     if (!IS_MOVING(x, y) && CAN_MOVE(element))
3103       StartMoving_MM(x, y);
3104     else if (IS_MOVING(x, y))
3105       ContinueMoving_MM(x, y);
3106     else if (IS_EXPLODING(element))
3107       Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3108     else if (element == EL_EXIT_OPENING)
3109       OpenExit(x, y);
3110     else if (element == EL_GRAY_BALL_OPENING)
3111       OpenSurpriseBall(x, y);
3112     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3113       MeltIce(x, y);
3114     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3115       GrowAmoeba(x, y);
3116   }
3117
3118   AutoRotateMirrors();
3119
3120 #if 1
3121   /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3122
3123   /* redraw after Explode_MM() ... */
3124   if (laser.redraw)
3125     DrawLaser(0, DL_LASER_ENABLED);
3126   laser.redraw = FALSE;
3127 #endif
3128
3129   CT = FrameCounter;
3130
3131   if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3132   {
3133     MovePacMen();
3134
3135     if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3136     {
3137       DrawLaser(0, DL_LASER_DISABLED);
3138       ScanLaser();
3139     }
3140   }
3141
3142   if (FrameReached(&energy_delay, ENERGY_DELAY))
3143   {
3144     if (game_mm.energy_left > 0)
3145     {
3146       game_mm.energy_left--;
3147
3148 #if 0
3149       BlitBitmap(pix[PIX_DOOR], drawto,
3150                  DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3151                  ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3152                  DX_ENERGY, DY_ENERGY);
3153 #endif
3154       redraw_mask |= REDRAW_DOOR_1;
3155     }
3156     else if (setup.time_limit && !game_mm.game_over)
3157     {
3158       int i;
3159
3160       for (i = 15; i >= 0; i--)
3161       {
3162 #if 0
3163         SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3164 #endif
3165         pen_ray = GetPixelFromRGB(window,
3166                                   native_mm_level.laser_red   * 0x11 * i,
3167                                   native_mm_level.laser_green * 0x11 * i,
3168                                   native_mm_level.laser_blue  * 0x11 * i);
3169
3170         DrawLaser(0, DL_LASER_ENABLED);
3171         BackToFront();
3172         Delay(50);
3173       }
3174
3175       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3176 #if 0
3177       FadeMusic();
3178 #endif
3179
3180       DrawLaser(0, DL_LASER_DISABLED);
3181       game_mm.game_over = TRUE;
3182       game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3183
3184       SetTileCursorActive(FALSE);
3185
3186       game.restart_game_message = "Out of magic energy ! Play it again ?";
3187
3188 #if 0
3189       if (Request("Out of magic energy ! Play it again ?",
3190                   REQ_ASK | REQ_STAY_CLOSED))
3191       {
3192         InitGame();
3193       }
3194       else
3195       {
3196         game_status = MAINMENU;
3197         DrawMainMenu();
3198       }
3199 #endif
3200
3201       return;
3202     }
3203   }
3204
3205   element = laser.dest_element;
3206
3207 #if 0
3208   if (element != Feld[ELX][ELY])
3209   {
3210     printf("element == %d, Feld[ELX][ELY] == %d\n",
3211            element, Feld[ELX][ELY]);
3212   }
3213 #endif
3214
3215   if (!laser.overloaded && laser.overload_value == 0 &&
3216       element != EL_BOMB &&
3217       element != EL_MINE &&
3218       element != EL_BALL_GRAY &&
3219       element != EL_BLOCK_STONE &&
3220       element != EL_BLOCK_WOOD &&
3221       element != EL_FUSE_ON &&
3222       element != EL_FUEL_FULL &&
3223       !IS_WALL_ICE(element) &&
3224       !IS_WALL_AMOEBA(element))
3225     return;
3226
3227   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3228        (!laser.overloaded && laser.overload_value > 0)) &&
3229       FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3230   {
3231     if (laser.overloaded)
3232       laser.overload_value++;
3233     else
3234       laser.overload_value--;
3235
3236     if (game_mm.cheat_no_overload)
3237     {
3238       laser.overloaded = FALSE;
3239       laser.overload_value = 0;
3240     }
3241
3242     game_mm.laser_overload_value = laser.overload_value;
3243
3244     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3245     {
3246       int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3247       int color_down = 0xFF - color_up;
3248
3249 #if 0
3250       SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3251              (15 - (laser.overload_value / 6)) * color_scale);
3252 #endif
3253       pen_ray =
3254         GetPixelFromRGB(window,
3255                         (native_mm_level.laser_red  ? 0xFF : color_up),
3256                         (native_mm_level.laser_green ? color_down : 0x00),
3257                         (native_mm_level.laser_blue  ? color_down : 0x00));
3258
3259       DrawLaser(0, DL_LASER_ENABLED);
3260 #if 0
3261       BackToFront();
3262 #endif
3263     }
3264
3265     if (!laser.overloaded)
3266       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3267     else if (setup.sound_loops)
3268       PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3269     else
3270       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3271
3272     if (laser.overloaded)
3273     {
3274 #if 0
3275       BlitBitmap(pix[PIX_DOOR], drawto,
3276                  DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3277                  DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3278                  - laser.overload_value,
3279                  OVERLOAD_XSIZE, laser.overload_value,
3280                  DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3281                  - laser.overload_value);
3282 #endif
3283       redraw_mask |= REDRAW_DOOR_1;
3284     }
3285     else
3286     {
3287 #if 0
3288       BlitBitmap(pix[PIX_DOOR], drawto,
3289                  DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3290                  OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3291                  DX_OVERLOAD, DY_OVERLOAD);
3292 #endif
3293       redraw_mask |= REDRAW_DOOR_1;
3294     }
3295
3296     if (laser.overload_value == MAX_LASER_OVERLOAD)
3297     {
3298       int i;
3299
3300       for (i = 15; i >= 0; i--)
3301       {
3302 #if 0
3303         SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3304 #endif
3305
3306         pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3307
3308         DrawLaser(0, DL_LASER_ENABLED);
3309         BackToFront();
3310         Delay(50);
3311       }
3312
3313       DrawLaser(0, DL_LASER_DISABLED);
3314
3315       game_mm.game_over = TRUE;
3316       game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3317
3318       SetTileCursorActive(FALSE);
3319
3320       game.restart_game_message = "Magic spell hit Mc Duffin ! Play it again ?";
3321
3322 #if 0
3323       if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3324                   REQ_ASK | REQ_STAY_CLOSED))
3325       {
3326         InitGame();
3327       }
3328       else
3329       {
3330         game_status = MAINMENU;
3331         DrawMainMenu();
3332       }
3333 #endif
3334
3335       return;
3336     }
3337   }
3338
3339   if (laser.fuse_off)
3340     return;
3341
3342   CT -= Ct;
3343
3344   if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3345   {
3346     if (game_mm.cheat_no_explosion)
3347       return;
3348
3349 #if 0
3350     laser.num_damages--;
3351     DrawLaser(0, DL_LASER_DISABLED);
3352     laser.num_edges = 0;
3353 #endif
3354
3355     Bang_MM(ELX, ELY);
3356
3357     laser.dest_element = EL_EXPLODING_OPAQUE;
3358
3359 #if 0
3360     Bang_MM(ELX, ELY);
3361     laser.num_damages--;
3362     DrawLaser(0, DL_LASER_DISABLED);
3363
3364     laser.num_edges = 0;
3365     Bang_MM(laser.start_edge.x, laser.start_edge.y);
3366
3367     if (Request("Bomb killed Mc Duffin ! Play it again ?",
3368                 REQ_ASK | REQ_STAY_CLOSED))
3369     {
3370       InitGame();
3371     }
3372     else
3373     {
3374       game_status = MAINMENU;
3375       DrawMainMenu();
3376     }
3377 #endif
3378
3379     return;
3380   }
3381
3382   if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3383   {
3384     laser.fuse_off = TRUE;
3385     laser.fuse_x = ELX;
3386     laser.fuse_y = ELY;
3387
3388     DrawLaser(0, DL_LASER_DISABLED);
3389     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3390   }
3391
3392   if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3393   {
3394     static int new_elements[] =
3395     {
3396       EL_MIRROR_START,
3397       EL_MIRROR_FIXED_START,
3398       EL_POLAR_START,
3399       EL_POLAR_CROSS_START,
3400       EL_PACMAN_START,
3401       EL_KETTLE,
3402       EL_BOMB,
3403       EL_PRISM
3404     };
3405     int num_new_elements = sizeof(new_elements) / sizeof(int);
3406     int new_element = new_elements[RND(num_new_elements)];
3407
3408     Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3409     Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3410
3411     /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3412     ScanLaser();
3413
3414     return;
3415
3416 #if 0
3417     int graphic;
3418
3419     switch (RND(5))
3420     {
3421       case 0:
3422         element = EL_MIRROR_START + RND(16);
3423         break;
3424       case 1:
3425         {
3426           int rnd = RND(3);
3427
3428           element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3429         }
3430         break;
3431       default:
3432         {
3433           int rnd = RND(3);
3434
3435           element = (rnd == 0 ? EL_FUSE_ON :
3436                      rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3437                      rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3438                      rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3439                      EL_MIRROR_FIXED_START + rnd - 25);
3440         }
3441         break;
3442     }
3443
3444     graphic = el2gfx(element);
3445
3446     for (i = 0; i < 50; i++)
3447     {
3448       int x = RND(26);
3449       int y = RND(26);
3450
3451 #if 0
3452       BlitBitmap(pix[PIX_BACK], drawto,
3453                  SX + (graphic % GFX_PER_LINE) * TILEX + x,
3454                  SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3455                  SX + ELX * TILEX + x,
3456                  SY + ELY * TILEY + y);
3457 #endif
3458       MarkTileDirty(ELX, ELY);
3459       BackToFront();
3460
3461       DrawLaser(0, DL_LASER_ENABLED);
3462
3463       Delay(50);
3464     }
3465
3466     Feld[ELX][ELY] = element;
3467     DrawField_MM(ELX, ELY);
3468
3469 #if 0
3470     printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3471 #endif
3472
3473     /* above stuff: GRAY BALL -> PRISM !!! */
3474 /*
3475     LX = ELX * TILEX + 14;
3476     LY = ELY * TILEY + 14;
3477     if (laser.current_angle == (laser.current_angle >> 1) << 1)
3478       OK = 8;
3479     else
3480       OK = 4;
3481     LX -= OK * XS;
3482     LY -= OK * YS;
3483
3484     laser.num_edges -= 2;
3485     laser.num_damages--;
3486 */
3487
3488 #if 0
3489     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3490       if (laser.damage[i].is_mirror)
3491         break;
3492
3493     if (i > 0)
3494       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3495     else
3496       DrawLaser(0, DL_LASER_DISABLED);
3497 #else
3498     DrawLaser(0, DL_LASER_DISABLED);
3499 #endif
3500
3501     ScanLaser();
3502
3503     /*
3504     printf("TEST ELEMENT: %d\n", Feld[0][0]);
3505     */
3506 #endif
3507
3508     return;
3509   }
3510
3511   if (IS_WALL_ICE(element) && CT > 50)
3512   {
3513     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3514
3515     {
3516       Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3517       Store[ELX][ELY] = EL_WALL_ICE;
3518       Store2[ELX][ELY] = laser.wall_mask;
3519
3520       laser.dest_element = Feld[ELX][ELY];
3521
3522       return;
3523     }
3524
3525     for (i = 0; i < 5; i++)
3526     {
3527       int phase = i + 1;
3528
3529       if (i == 4)
3530       {
3531         Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3532         phase = 0;
3533       }
3534
3535       DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3536       BackToFront();
3537       Delay(100);
3538     }
3539
3540     if (Feld[ELX][ELY] == EL_WALL_ICE)
3541       Feld[ELX][ELY] = EL_EMPTY;
3542
3543 /*
3544     laser.num_edges--;
3545     LX = laser.edge[laser.num_edges].x - (SX + 2);
3546     LY = laser.edge[laser.num_edges].y - (SY + 2);
3547 */
3548
3549     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3550       if (laser.damage[i].is_mirror)
3551         break;
3552
3553     if (i > 0)
3554       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3555     else
3556       DrawLaser(0, DL_LASER_DISABLED);
3557
3558     ScanLaser();
3559
3560     return;
3561   }
3562
3563   if (IS_WALL_AMOEBA(element) && CT > 60)
3564   {
3565     int k1, k2, k3, dx, dy, de, dm;
3566     int element2 = Feld[ELX][ELY];
3567
3568     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3569       return;
3570
3571     for (i = laser.num_damages - 1; i >= 0; i--)
3572       if (laser.damage[i].is_mirror)
3573         break;
3574
3575     r = laser.num_edges;
3576     d = laser.num_damages;
3577     k1 = i;
3578
3579     if (k1 > 0)
3580     {
3581       int x, y;
3582
3583       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3584
3585       laser.num_edges++;
3586       DrawLaser(0, DL_LASER_ENABLED);
3587       laser.num_edges--;
3588
3589       x = laser.damage[k1].x;
3590       y = laser.damage[k1].y;
3591
3592       DrawField_MM(x, y);
3593     }
3594
3595     for (i = 0; i < 4; i++)
3596     {
3597       if (laser.wall_mask & (1 << i))
3598       {
3599         if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3600                             SY + ELY * TILEY + 31 * (i / 2)))
3601           break;
3602
3603         if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3604                             SY + ELY * TILEY + 14 + (i / 2) * 2))
3605           break;
3606       }
3607     }
3608
3609     k2 = i;
3610
3611     for (i = 0; i < 4; i++)
3612     {
3613       if (laser.wall_mask & (1 << i))
3614       {
3615         if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3616                             SY + ELY * TILEY + 31 * (i / 2)))
3617           break;
3618       }
3619     }
3620
3621     k3 = i;
3622
3623     if (laser.num_beamers > 0 ||
3624         k1 < 1 || k2 < 4 || k3 < 4 ||
3625         CheckLaserPixel(SX + ELX * TILEX + 14,
3626                         SY + ELY * TILEY + 14))
3627     {
3628       laser.num_edges = r;
3629       laser.num_damages = d;
3630
3631       DrawLaser(0, DL_LASER_DISABLED);
3632     }
3633
3634     Feld[ELX][ELY] = element | laser.wall_mask;
3635
3636     dx = ELX;
3637     dy = ELY;
3638     de = Feld[ELX][ELY];
3639     dm = laser.wall_mask;
3640
3641 #if 1
3642     {
3643       int x = ELX, y = ELY;
3644       int wall_mask = laser.wall_mask;
3645
3646       ScanLaser();
3647       DrawLaser(0, DL_LASER_ENABLED);
3648
3649       PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3650
3651       Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3652       Store[x][y] = EL_WALL_AMOEBA;
3653       Store2[x][y] = wall_mask;
3654
3655       return;
3656     }
3657 #endif
3658
3659     DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3660     ScanLaser();
3661     DrawLaser(0, DL_LASER_ENABLED);
3662
3663     PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3664
3665     for (i = 4; i >= 0; i--)
3666     {
3667       DrawWallsAnimation_MM(dx, dy, de, i, dm);
3668
3669       BackToFront();
3670       Delay(20);
3671     }
3672
3673     DrawLaser(0, DL_LASER_ENABLED);
3674
3675     return;
3676   }
3677
3678   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3679       laser.stops_inside_element && CT > native_mm_level.time_block)
3680   {
3681     int x, y;
3682     int k;
3683
3684     if (ABS(XS) > ABS(YS))
3685       k = 0;
3686     else
3687       k = 1;
3688     if (XS < YS)
3689       k += 2;
3690
3691     for (i = 0; i < 4; i++)
3692     {
3693       if (i)
3694         k++;
3695       if (k > 3)
3696         k = 0;
3697
3698       x = ELX + Step[k * 4].x;
3699       y = ELY + Step[k * 4].y;
3700
3701       if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3702         continue;
3703
3704       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3705         continue;
3706
3707       break;
3708     }
3709
3710     if (i > 3)
3711     {
3712       laser.overloaded = (element == EL_BLOCK_STONE);
3713
3714       return;
3715     }
3716
3717     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3718
3719     Feld[ELX][ELY] = 0;
3720     Feld[x][y] = element;
3721
3722     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3723     DrawField_MM(x, y);
3724
3725     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3726     {
3727       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3728       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3729     }
3730
3731     ScanLaser();
3732
3733     return;
3734   }
3735
3736   if (element == EL_FUEL_FULL && CT > 10)
3737   {
3738     for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3739     {
3740 #if 0
3741       BlitBitmap(pix[PIX_DOOR], drawto,
3742                  DOOR_GFX_PAGEX4 + XX_ENERGY,
3743                  DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3744                  ENERGY_XSIZE, i, DX_ENERGY,
3745                  DY_ENERGY + ENERGY_YSIZE - i);
3746 #endif
3747
3748       redraw_mask |= REDRAW_DOOR_1;
3749       BackToFront();
3750
3751       Delay(20);
3752     }
3753
3754     game_mm.energy_left = MAX_LASER_ENERGY;
3755     Feld[ELX][ELY] = EL_FUEL_EMPTY;
3756     DrawField_MM(ELX, ELY);
3757
3758     DrawLaser(0, DL_LASER_ENABLED);
3759
3760     return;
3761   }
3762
3763   return;
3764 }
3765
3766 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3767 {
3768   boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3769   boolean button_released = (action.button == MB_RELEASED);
3770
3771   GameActions_MM_Ext(action, warp_mode);
3772
3773   CheckSingleStepMode_MM(element_clicked, button_released);
3774 }
3775
3776 void MovePacMen()
3777 {
3778   int mx, my, ox, oy, nx, ny;
3779   int element;
3780   int l;
3781
3782   if (++pacman_nr >= game_mm.num_pacman)
3783     pacman_nr = 0;
3784
3785   game_mm.pacman[pacman_nr].dir--;
3786
3787   for (l = 1; l < 5; l++)
3788   {
3789     game_mm.pacman[pacman_nr].dir++;
3790
3791     if (game_mm.pacman[pacman_nr].dir > 4)
3792       game_mm.pacman[pacman_nr].dir = 1;
3793
3794     if (game_mm.pacman[pacman_nr].dir % 2)
3795     {
3796       mx = 0;
3797       my = game_mm.pacman[pacman_nr].dir - 2;
3798     }
3799     else
3800     {
3801       my = 0;
3802       mx = 3 - game_mm.pacman[pacman_nr].dir;
3803     }
3804
3805     ox = game_mm.pacman[pacman_nr].x;
3806     oy = game_mm.pacman[pacman_nr].y;
3807     nx = ox + mx;
3808     ny = oy + my;
3809     element = Feld[nx][ny];
3810
3811     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3812       continue;
3813
3814     if (!IS_EATABLE4PACMAN(element))
3815       continue;
3816
3817     if (ObjHit(nx, ny, HIT_POS_CENTER))
3818       continue;
3819
3820     Feld[ox][oy] = EL_EMPTY;
3821     Feld[nx][ny] =
3822       EL_PACMAN_RIGHT - 1 +
3823       (game_mm.pacman[pacman_nr].dir - 1 +
3824        (game_mm.pacman[pacman_nr].dir % 2) * 2);
3825
3826     game_mm.pacman[pacman_nr].x = nx;
3827     game_mm.pacman[pacman_nr].y = ny;
3828
3829     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3830
3831     if (element != EL_EMPTY)
3832     {
3833       int graphic = el2gfx(Feld[nx][ny]);
3834       Bitmap *bitmap;
3835       int src_x, src_y;
3836       int i;
3837
3838       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3839
3840       CT = FrameCounter;
3841       ox = SX + ox * TILEX;
3842       oy = SY + oy * TILEY;
3843
3844       for (i = 1; i < 33; i += 2)
3845         BlitBitmap(bitmap, window,
3846                    src_x, src_y, TILEX, TILEY,
3847                    ox + i * mx, oy + i * my);
3848       Ct = Ct + FrameCounter - CT;
3849     }
3850
3851     DrawField_MM(nx, ny);
3852     BackToFront();
3853
3854     if (!laser.fuse_off)
3855     {
3856       DrawLaser(0, DL_LASER_ENABLED);
3857
3858       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3859       {
3860         AddDamagedField(nx, ny);
3861
3862         laser.damage[laser.num_damages - 1].edge = 0;
3863       }
3864     }
3865
3866     if (element == EL_BOMB)
3867       DeletePacMan(nx, ny);
3868
3869     if (IS_WALL_AMOEBA(element) &&
3870         (LX + 2 * XS) / TILEX == nx &&
3871         (LY + 2 * YS) / TILEY == ny)
3872     {
3873       laser.num_edges--;
3874       ScanLaser();
3875     }
3876
3877     break;
3878   }
3879 }
3880
3881 void GameWon_MM()
3882 {
3883   int hi_pos;
3884   boolean raise_level = FALSE;
3885
3886 #if 0
3887   if (local_player->MovPos)
3888     return;
3889
3890   local_player->LevelSolved = FALSE;
3891 #endif
3892
3893   if (game_mm.energy_left)
3894   {
3895     if (setup.sound_loops)
3896       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3897                    SND_CTRL_PLAY_LOOP);
3898
3899     while (game_mm.energy_left > 0)
3900     {
3901       if (!setup.sound_loops)
3902         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3903
3904       /*
3905       if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3906         RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3907       */
3908
3909       RaiseScore_MM(5);
3910
3911       game_mm.energy_left--;
3912       if (game_mm.energy_left >= 0)
3913       {
3914 #if 0
3915         BlitBitmap(pix[PIX_DOOR], drawto,
3916                    DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3917                    ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3918                    DX_ENERGY, DY_ENERGY);
3919 #endif
3920         redraw_mask |= REDRAW_DOOR_1;
3921       }
3922
3923       BackToFront();
3924       Delay(10);
3925     }
3926
3927     if (setup.sound_loops)
3928       StopSound(SND_SIRR);
3929   }
3930   else if (native_mm_level.time == 0)           /* level without time limit */
3931   {
3932     if (setup.sound_loops)
3933       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3934                    SND_CTRL_PLAY_LOOP);
3935
3936     while (TimePlayed < 999)
3937     {
3938       if (!setup.sound_loops)
3939         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3940       if (TimePlayed < 999 && !(TimePlayed % 10))
3941         RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3942       if (TimePlayed < 900 && !(TimePlayed % 10))
3943         TimePlayed += 10;
3944       else
3945         TimePlayed++;
3946
3947       /*
3948       DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3949       */
3950
3951       BackToFront();
3952       Delay(10);
3953     }
3954
3955     if (setup.sound_loops)
3956       StopSound(SND_SIRR);
3957   }
3958
3959 #if 0
3960   FadeSounds();
3961 #endif
3962
3963   CloseDoor(DOOR_CLOSE_1);
3964
3965   Request("Level solved !", REQ_CONFIRM);
3966
3967   if (level_nr == leveldir_current->handicap_level)
3968   {
3969     leveldir_current->handicap_level++;
3970     SaveLevelSetup_SeriesInfo();
3971   }
3972
3973   if (level_editor_test_game)
3974     game_mm.score = -1;         /* no highscore when playing from editor */
3975   else if (level_nr < leveldir_current->last_level)
3976     raise_level = TRUE;         /* advance to next level */
3977
3978   if ((hi_pos = NewHiScore_MM()) >= 0)
3979   {
3980     game_status = HALLOFFAME;
3981
3982     // DrawHallOfFame(hi_pos);
3983
3984     if (raise_level)
3985       level_nr++;
3986   }
3987   else
3988   {
3989     game_status = MAINMENU;
3990
3991     if (raise_level)
3992       level_nr++;
3993
3994     // DrawMainMenu();
3995   }
3996
3997   BackToFront();
3998 }
3999
4000 int NewHiScore_MM()
4001 {
4002   int k, l;
4003   int position = -1;
4004
4005   // LoadScore(level_nr);
4006
4007   if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
4008       game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
4009     return -1;
4010
4011   for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4012   {
4013     if (game_mm.score > highscore[k].Score)
4014     {
4015       /* player has made it to the hall of fame */
4016
4017       if (k < MAX_SCORE_ENTRIES - 1)
4018       {
4019         int m = MAX_SCORE_ENTRIES - 1;
4020
4021 #ifdef ONE_PER_NAME
4022         for (l = k; l < MAX_SCORE_ENTRIES; l++)
4023           if (!strcmp(setup.player_name, highscore[l].Name))
4024             m = l;
4025         if (m == k)     /* player's new highscore overwrites his old one */
4026           goto put_into_list;
4027 #endif
4028
4029         for (l = m; l>k; l--)
4030         {
4031           strcpy(highscore[l].Name, highscore[l - 1].Name);
4032           highscore[l].Score = highscore[l - 1].Score;
4033         }
4034       }
4035
4036 #ifdef ONE_PER_NAME
4037       put_into_list:
4038 #endif
4039       strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4040       highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4041       highscore[k].Score = game_mm.score;
4042       position = k;
4043
4044       break;
4045     }
4046
4047 #ifdef ONE_PER_NAME
4048     else if (!strncmp(setup.player_name, highscore[k].Name,
4049                       MAX_PLAYER_NAME_LEN))
4050       break;    /* player already there with a higher score */
4051 #endif
4052
4053   }
4054
4055   // if (position >= 0)
4056   //   SaveScore(level_nr);
4057
4058   return position;
4059 }
4060
4061 static void InitMovingField_MM(int x, int y, int direction)
4062 {
4063   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4064   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
4065
4066   MovDir[x][y] = direction;
4067   MovDir[newx][newy] = direction;
4068
4069   if (Feld[newx][newy] == EL_EMPTY)
4070     Feld[newx][newy] = EL_BLOCKED;
4071 }
4072
4073 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4074 {
4075   int direction = MovDir[x][y];
4076   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4077   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
4078
4079   *goes_to_x = newx;
4080   *goes_to_y = newy;
4081 }
4082
4083 static void Blocked2Moving_MM(int x, int y,
4084                               int *comes_from_x, int *comes_from_y)
4085 {
4086   int oldx = x, oldy = y;
4087   int direction = MovDir[x][y];
4088
4089   if (direction == MV_LEFT)
4090     oldx++;
4091   else if (direction == MV_RIGHT)
4092     oldx--;
4093   else if (direction == MV_UP)
4094     oldy++;
4095   else if (direction == MV_DOWN)
4096     oldy--;
4097
4098   *comes_from_x = oldx;
4099   *comes_from_y = oldy;
4100 }
4101
4102 static int MovingOrBlocked2Element_MM(int x, int y)
4103 {
4104   int element = Feld[x][y];
4105
4106   if (element == EL_BLOCKED)
4107   {
4108     int oldx, oldy;
4109
4110     Blocked2Moving_MM(x, y, &oldx, &oldy);
4111
4112     return Feld[oldx][oldy];
4113   }
4114
4115   return element;
4116 }
4117
4118 #if 0
4119 static void RemoveField(int x, int y)
4120 {
4121   Feld[x][y] = EL_EMPTY;
4122   MovPos[x][y] = 0;
4123   MovDir[x][y] = 0;
4124   MovDelay[x][y] = 0;
4125 }
4126 #endif
4127
4128 static void RemoveMovingField_MM(int x, int y)
4129 {
4130   int oldx = x, oldy = y, newx = x, newy = y;
4131
4132   if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4133     return;
4134
4135   if (IS_MOVING(x, y))
4136   {
4137     Moving2Blocked_MM(x, y, &newx, &newy);
4138     if (Feld[newx][newy] != EL_BLOCKED)
4139       return;
4140   }
4141   else if (Feld[x][y] == EL_BLOCKED)
4142   {
4143     Blocked2Moving_MM(x, y, &oldx, &oldy);
4144     if (!IS_MOVING(oldx, oldy))
4145       return;
4146   }
4147
4148   Feld[oldx][oldy] = EL_EMPTY;
4149   Feld[newx][newy] = EL_EMPTY;
4150   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4151   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4152
4153   DrawLevelField_MM(oldx, oldy);
4154   DrawLevelField_MM(newx, newy);
4155 }
4156
4157 void PlaySoundLevel(int x, int y, int sound_nr)
4158 {
4159   int sx = SCREENX(x), sy = SCREENY(y);
4160   int volume, stereo;
4161   int silence_distance = 8;
4162
4163   if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4164       (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4165     return;
4166
4167   if (!IN_LEV_FIELD(x, y) ||
4168       sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4169       sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4170     return;
4171
4172   volume = SOUND_MAX_VOLUME;
4173
4174 #ifndef MSDOS
4175   stereo = (sx - SCR_FIELDX/2) * 12;
4176 #else
4177   stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4178   if (stereo > SOUND_MAX_RIGHT)
4179     stereo = SOUND_MAX_RIGHT;
4180   if (stereo < SOUND_MAX_LEFT)
4181     stereo = SOUND_MAX_LEFT;
4182 #endif
4183
4184   if (!IN_SCR_FIELD(sx, sy))
4185   {
4186     int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4187     int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4188
4189     volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4190   }
4191
4192   PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4193 }
4194
4195 static void RaiseScore_MM(int value)
4196 {
4197   game_mm.score += value;
4198
4199 #if 0
4200   DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4201            FONT_TEXT_2);
4202 #endif
4203 }
4204
4205 void RaiseScoreElement_MM(int element)
4206 {
4207   switch(element)
4208   {
4209     case EL_PACMAN:
4210     case EL_PACMAN_RIGHT:
4211     case EL_PACMAN_UP:
4212     case EL_PACMAN_LEFT:
4213     case EL_PACMAN_DOWN:
4214       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4215       break;
4216
4217     case EL_KEY:
4218       RaiseScore_MM(native_mm_level.score[SC_KEY]);
4219       break;
4220
4221     case EL_KETTLE:
4222     case EL_CELL:
4223       RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4224       break;
4225
4226     case EL_LIGHTBALL:
4227       RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4228       break;
4229
4230     default:
4231       break;
4232   }
4233 }
4234
4235
4236 /* ------------------------------------------------------------------------- */
4237 /* Mirror Magic game engine snapshot handling functions                      */
4238 /* ------------------------------------------------------------------------- */
4239
4240 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4241 {
4242   int x, y;
4243
4244   engine_snapshot_mm.game_mm = game_mm;
4245   engine_snapshot_mm.laser = laser;
4246
4247   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4248   {
4249     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4250     {
4251       engine_snapshot_mm.Ur[x][y]    = Ur[x][y];
4252       engine_snapshot_mm.Hit[x][y]   = Hit[x][y];
4253       engine_snapshot_mm.Box[x][y]   = Box[x][y];
4254       engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4255       engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4256     }
4257   }
4258
4259   engine_snapshot_mm.LX = LX;
4260   engine_snapshot_mm.LY = LY;
4261   engine_snapshot_mm.XS = XS;
4262   engine_snapshot_mm.YS = YS;
4263   engine_snapshot_mm.ELX = ELX;
4264   engine_snapshot_mm.ELY = ELY;
4265   engine_snapshot_mm.CT = CT;
4266   engine_snapshot_mm.Ct = Ct;
4267
4268   engine_snapshot_mm.last_LX = last_LX;
4269   engine_snapshot_mm.last_LY = last_LY;
4270   engine_snapshot_mm.last_hit_mask = last_hit_mask;
4271   engine_snapshot_mm.hold_x = hold_x;
4272   engine_snapshot_mm.hold_y = hold_y;
4273   engine_snapshot_mm.pacman_nr = pacman_nr;
4274
4275   engine_snapshot_mm.rotate_delay = rotate_delay;
4276   engine_snapshot_mm.pacman_delay = pacman_delay;
4277   engine_snapshot_mm.energy_delay = energy_delay;
4278   engine_snapshot_mm.overload_delay = overload_delay;
4279 }
4280
4281 void LoadEngineSnapshotValues_MM()
4282 {
4283   int x, y;
4284
4285   /* stored engine snapshot buffers already restored at this point */
4286
4287   game_mm = engine_snapshot_mm.game_mm;
4288   laser   = engine_snapshot_mm.laser;
4289
4290   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4291   {
4292     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4293     {
4294       Ur[x][y]    = engine_snapshot_mm.Ur[x][y];
4295       Hit[x][y]   = engine_snapshot_mm.Hit[x][y];
4296       Box[x][y]   = engine_snapshot_mm.Box[x][y];
4297       Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4298       Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4299     }
4300   }
4301
4302   LX  = engine_snapshot_mm.LX;
4303   LY  = engine_snapshot_mm.LY;
4304   XS  = engine_snapshot_mm.XS;
4305   YS  = engine_snapshot_mm.YS;
4306   ELX = engine_snapshot_mm.ELX;
4307   ELY = engine_snapshot_mm.ELY;
4308   CT  = engine_snapshot_mm.CT;
4309   Ct  = engine_snapshot_mm.Ct;
4310
4311   last_LX       = engine_snapshot_mm.last_LX;
4312   last_LY       = engine_snapshot_mm.last_LY;
4313   last_hit_mask = engine_snapshot_mm.last_hit_mask;
4314   hold_x        = engine_snapshot_mm.hold_x;
4315   hold_y        = engine_snapshot_mm.hold_y;
4316   pacman_nr     = engine_snapshot_mm.pacman_nr;
4317
4318   rotate_delay   = engine_snapshot_mm.rotate_delay;
4319   pacman_delay   = engine_snapshot_mm.pacman_delay;
4320   energy_delay   = engine_snapshot_mm.energy_delay;
4321   overload_delay = engine_snapshot_mm.overload_delay;
4322
4323   RedrawPlayfield_MM(TRUE);
4324 }
4325
4326 static int getAngleFromTouchDelta(int dx, int dy,  int base)
4327 {
4328   double pi = 3.141592653;
4329   double rad = atan2((double)-dy, (double)dx);
4330   double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4331   double deg = rad2 * 180.0 / pi;
4332
4333   return (int)(deg * base / 360.0 + 0.5) % base;
4334 }
4335
4336 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4337 {
4338   // calculate start (source) position to be at the middle of the tile
4339   int src_mx = SX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4340   int src_my = SY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4341   int dx = dst_mx - src_mx;
4342   int dy = dst_my - src_my;
4343   int element;
4344   int base = 16;
4345   int phases = 16;
4346   int angle_old = -1;
4347   int angle_new = -1;
4348   int button = 0;
4349   int i;
4350
4351   if (!IN_LEV_FIELD(x, y))
4352     return 0;
4353
4354   element = Feld[x][y];
4355
4356   if (!IS_MCDUFFIN(element) &&
4357       !IS_MIRROR(element) &&
4358       !IS_BEAMER(element) &&
4359       !IS_POLAR(element) &&
4360       !IS_POLAR_CROSS(element) &&
4361       !IS_DF_MIRROR(element))
4362     return 0;
4363
4364   angle_old = get_element_angle(element);
4365
4366   if (IS_MCDUFFIN(element))
4367   {
4368     angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4369                  dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4370                  dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4371                  dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4372                  -1);
4373   }
4374   else if (IS_MIRROR(element) ||
4375            IS_DF_MIRROR(element))
4376   {
4377     for (i = 0; i < laser.num_damages; i++)
4378     {
4379       if (laser.damage[i].x == x &&
4380           laser.damage[i].y == y &&
4381           ObjHit(x, y, HIT_POS_CENTER))
4382       {
4383         angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4384         angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4385
4386         break;
4387       }
4388     }
4389   }
4390
4391   if (angle_new == -1)
4392   {
4393     if (IS_MIRROR(element) ||
4394         IS_DF_MIRROR(element) ||
4395         IS_POLAR(element))
4396       base = 32;
4397
4398     if (IS_POLAR_CROSS(element))
4399       phases = 4;
4400
4401     angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4402   }
4403
4404   button = (angle_new == angle_old ? 0 :
4405             (angle_new - angle_old + phases) % phases < (phases / 2) ?
4406             MB_LEFTBUTTON : MB_RIGHTBUTTON);
4407
4408   return button;
4409 }