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