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