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