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