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