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