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