minor whitespace change
[rocksndiamonds.git] / src / game_mm / mm_game.c
1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // mm_game.c
10 // ============================================================================
11
12 #include "main_mm.h"
13
14 #include "mm_main.h"
15 #include "mm_game.h"
16 #include "mm_tools.h"
17
18 /* graphic position values for game controls */
19 #define ENERGY_XSIZE            32
20 #define ENERGY_YSIZE            MAX_LASER_ENERGY
21 #define OVERLOAD_XSIZE          ENERGY_XSIZE
22 #define OVERLOAD_YSIZE          MAX_LASER_OVERLOAD
23
24 /* values for Explode_MM() */
25 #define EX_PHASE_START          0
26 #define EX_NORMAL               0
27 #define EX_KETTLE               1
28 #define EX_SHORT                2
29
30 /* special positions in the game control window (relative to control window) */
31 #define XX_LEVEL                36
32 #define YY_LEVEL                23
33 #define XX_KETTLES              29
34 #define YY_KETTLES              63
35 #define XX_SCORE                22
36 #define YY_SCORE                101
37 #define XX_ENERGY               8
38 #define YY_ENERGY               158
39 #define XX_OVERLOAD             60
40 #define YY_OVERLOAD             YY_ENERGY
41
42 /* special positions in the game control window (relative to main window) */
43 #define DX_LEVEL                (DX + XX_LEVEL)
44 #define DY_LEVEL                (DY + YY_LEVEL)
45 #define DX_KETTLES              (DX + XX_KETTLES)
46 #define DY_KETTLES              (DY + YY_KETTLES)
47 #define DX_SCORE                (DX + XX_SCORE)
48 #define DY_SCORE                (DY + YY_SCORE)
49 #define DX_ENERGY               (DX + XX_ENERGY)
50 #define DY_ENERGY               (DY + YY_ENERGY)
51 #define DX_OVERLOAD             (DX + XX_OVERLOAD)
52 #define DY_OVERLOAD             (DY + YY_OVERLOAD)
53
54 #define IS_LOOP_SOUND(s)        ((s) == SND_FUEL)
55 #define IS_MUSIC_SOUND(s)       ((s) == SND_TYGER || (s) == SND_VOYAGER)
56
57 /* game button identifiers */
58 #define GAME_CTRL_ID_LEFT       0
59 #define GAME_CTRL_ID_MIDDLE     1
60 #define GAME_CTRL_ID_RIGHT      2
61
62 #define NUM_GAME_BUTTONS        3
63
64 /* values for DrawLaser() */
65 #define DL_LASER_DISABLED       0
66 #define DL_LASER_ENABLED        1
67
68 /* values for 'click_delay_value' in ClickElement() */
69 #define CLICK_DELAY_FIRST       12      /* delay (frames) after first click */
70 #define CLICK_DELAY             6       /* delay (frames) for pressed butten */
71
72 #define AUTO_ROTATE_DELAY       CLICK_DELAY
73 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
74 #define NUM_INIT_CYCLE_STEPS    16
75 #define PACMAN_MOVE_DELAY       12
76 #define ENERGY_DELAY            (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
77 #define HEALTH_DEC_DELAY        3
78 #define HEALTH_INC_DELAY        9
79 #define HEALTH_DELAY(x)         ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
80
81 #define BEGIN_NO_HEADLESS                       \
82   {                                             \
83     boolean last_headless = program.headless;   \
84                                                 \
85     program.headless = FALSE;                   \
86
87 #define END_NO_HEADLESS                         \
88     program.headless = last_headless;           \
89   }                                             \
90
91 /* forward declaration for internal use */
92 static int MovingOrBlocked2Element_MM(int, int);
93 static void Bang_MM(int, int);
94 static void RaiseScore_MM(int);
95 static void RemoveMovingField_MM(int, int);
96 static void InitMovingField_MM(int, int, int);
97 static void ContinueMoving_MM(int, int);
98 static void Moving2Blocked_MM(int, int, int *, int *);
99
100 /* bitmap for laser beam detection */
101 static Bitmap *laser_bitmap = NULL;
102
103 /* variables for laser control */
104 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
105 static int hold_x = -1, hold_y = -1;
106
107 /* variables for pacman control */
108 static int pacman_nr = -1;
109
110 /* various game engine delay counters */
111 static unsigned int rotate_delay = 0;
112 static unsigned int pacman_delay = 0;
113 static unsigned int energy_delay = 0;
114 static unsigned int overload_delay = 0;
115
116 /* element masks for scanning pixels of MM elements */
117 static const char mm_masks[10][16][16 + 1] =
118 {
119   {
120     "                ",
121     "    XXXXX       ",
122     "   XXXXXXX      ",
123     "  XXXXXXXXXXX   ",
124     "  XXXXXXXXXXXXX ",
125     "  XXXXXXXXXXXXXX",
126     "  XXXXXXXXXXXXXX",
127     "  XXXXXXXXXXXXX ",
128     "  XXXXXXXXXXXXX ",
129     "  XXXXXXXXXXXXX ",
130     "  XXXXXXXXXXXXX ",
131     "  XXXXXXXXXXXXX ",
132     "  XXXXXXXXXXXXX ",
133     "  XXXXXXXXXXXXX ",
134     "  XXXXXXXXXXXX  ",
135     "  XXXXXXXXXXXX  ",
136   },
137   {
138     "                ",
139     "    XXXXXXXX    ",
140     "  XXXXXXXXXXXX  ",
141     " XXXXXXXXXXXXXX ",
142     " XXXXXXXXXXXXXX ",
143     " XXXXXXXXXXXXXX ",
144     " XXXXXXXXXXXXXX ",
145     " XXXXXXXXXXXXXX ",
146     " XXXXXXXXXXXXXX ",
147     " XXXXXXXXXXXXXX ",
148     " XXXXXXXXXXXXXX ",
149     " XXXXXXXXXXXXXX ",
150     " XXXXXXXXXXXXXX ",
151     "  XXXXXXXXXXXXX ",
152     "  XXXXXXXXXXXX  ",
153     "  XXXXXXXXXXXX  ",
154   },
155   {
156     "                ",
157     "    XXXXXX      ",
158     "  XXXXXXXXX     ",
159     " XXXXXXXXXXX    ",
160     "XXXXXXXXXXXXX   ",
161     "XXXXXXXXXXXXX   ",
162     "XXXXXXXXXXXXXX  ",
163     " XXXXXXXXXXXXX  ",
164     " XXXXXXXXXXXXX  ",
165     " XXXXXXXXXXXXX  ",
166     " XXXXXXXXXXXXX  ",
167     " XXXXXXXXXXXXX  ",
168     " XXXXXXXXXXXXX  ",
169     " XXXXXXXXXXXXX  ",
170     "  XXXXXXXXXXXX  ",
171     "  XXXXXXXXXXXX  ",
172   },
173   {
174     "                ",
175     "    XXXXXX      ",
176     "   XXXXXXXX     ",
177     "  XXXXXXXXXX    ",
178     "  XXXXXXXXXXX   ",
179     "  XXXXXXXXXXX   ",
180     "  XXXXXXXXXXXX  ",
181     "  XXXXXXXXXXXX  ",
182     "  XXXXXXXXXXXX  ",
183     "  XXXXXXXXXXXX  ",
184     "  XXXXXXXXXXXX  ",
185     "  XXXXXXXXXXXX  ",
186     "  XXXXXXXXXXXX  ",
187     "  XXXXXXXXXXXX  ",
188     "  XXXXXXXXXXXX  ",
189     "  XXXXX  XXXXX  ",
190   },
191   {
192     " XXXXXX  XXXXXX ",
193     "XXXXXXXXXXXXXXXX",
194     "XXXXXXXXXXXXXXXX",
195     "XXXXXXXXXXXXXXXX",
196     "XXXXXXXXXXXXXXXX",
197     "XXXXXXXXXXXXXXXX",
198     "XXXXXXXXXXXXXXXX",
199     "                ",
200     "                ",
201     "XXXXXXXXXXXXXXXX",
202     "XXXXXXXXXXXXXXXX",
203     "XXXXXXXXXXXXXXXX",
204     "XXXXXXXXXXXXXXXX",
205     "XXXXXXXXXXXXXXXX",
206     "XXXXXXXXXXXXXXXX",
207     " XXXXXX  XXXXXX ",
208   },
209   {
210     " XXXXXX  XXXXXX ",
211     "XXXXXXX  XXXXXXX",
212     "XXXXXXX  XXXXXXX",
213     "XXXXXXX  XXXXXXX",
214     "XXXXXXX  XXXXXXX",
215     "XXXXXXX  XXXXXXX",
216     "XXXXXXX  XXXXXXX",
217     " XXXXXX  XXXXXX ",
218     " XXXXXX  XXXXXX ",
219     "XXXXXXX  XXXXXXX",
220     "XXXXXXX  XXXXXXX",
221     "XXXXXXX  XXXXXXX",
222     "XXXXXXX  XXXXXXX",
223     "XXXXXXX  XXXXXXX",
224     "XXXXXXX  XXXXXXX",
225     " XXXXXX  XXXXXX ",
226   },
227   {
228     "     XX  XXXXX  ",
229     "    XXX  XXXX   ",
230     "   XXXX  XXX   X",
231     "  XXXXXXXXX   XX",
232     " XXXXXXXXX   XXX",
233     "XXXXXXXXX   XXXX",
234     "XXXXXXXX   XXXXX",
235     "   XXXX   XXX   ",
236     "   XXX   XXXX   ",
237     "XXXXX   XXXXXXXX",
238     "XXXX   XXXXXXXXX",
239     "XXX   XXXXXXXXX ",
240     "XX   XXXXXXXXX  ",
241     "X   XXX  XXXX   ",
242     "   XXXX  XXX    ",
243     "  XXXXX  XX     ",
244   },
245   {
246     "  XXXXX  XX     ",
247     "   XXXX  XXX    ",
248     "X   XXX  XXXX   ",
249     "XX   XXXXXXXXX  ",
250     "XXX   XXXXXXXXX ",
251     "XXXX   XXXXXXXXX",
252     "XXXXX   XXXXXXXX",
253     "   XXX   XXXX   ",
254     "   XXXX   XXX   ",
255     "XXXXXXXX   XXXXX",
256     "XXXXXXXXX   XXXX",
257     " XXXXXXXXX   XXX",
258     "  XXXXXXXXX   XX",
259     "   XXXX  XXX   X",
260     "    XXX  XXXX   ",
261     "     XX  XXXXX  ",
262   },
263   {
264     "XXXXXXXXXXXXXXXX",
265     "XXXXXXXXXXXXXXXX",
266     "XXXXXXXXXXXXXXXX",
267     "XXXXXXXXXXXXXXXX",
268     "XXXXXXXXXXXXXXXX",
269     "XXXXXXXXXXXXXXXX",
270     "XXXXXXXXXXXXXXXX",
271     "XXXXXXXXXXXXXXXX",
272     "XXXXXXXXXXXXXXXX",
273     "XXXXXXXXXXXXXXXX",
274     "XXXXXXXXXXXXXXXX",
275     "XXXXXXXXXXXXXXXX",
276     "XXXXXXXXXXXXXXXX",
277     "XXXXXXXXXXXXXXXX",
278     "XXXXXXXXXXXXXXXX",
279     "XXXXXXXXXXXXXXXX",
280   },
281   {
282     "                ",
283     "      XXXX      ",
284     "    XXXXXXXX    ",
285     "   XXXXXXXXXX   ",
286     "  XXXXXXXXXXXX  ",
287     "  XXXXXXXXXXXX  ",
288     " XXXXXXXXXXXXXX ",
289     " XXXXXXXXXXXXXX ",
290     " XXXXXXXXXXXXXX ",
291     " XXXXXXXXXXXXXX ",
292     "  XXXXXXXXXXXX  ",
293     "  XXXXXXXXXXXX  ",
294     "   XXXXXXXXXX   ",
295     "    XXXXXXXX    ",
296     "      XXXX      ",
297     "                ",
298   },
299 };
300
301 static int get_element_angle(int element)
302 {
303   int element_phase = get_element_phase(element);
304
305   if (IS_MIRROR_FIXED(element) ||
306       IS_MCDUFFIN(element) ||
307       IS_LASER(element) ||
308       IS_RECEIVER(element))
309     return 4 * element_phase;
310   else
311     return element_phase;
312 }
313
314 static int get_opposite_angle(int angle)
315 {
316   int opposite_angle = angle + ANG_RAY_180;
317
318   /* make sure "opposite_angle" is in valid interval [0, 15] */
319   return (opposite_angle + 16) % 16;
320 }
321
322 static int get_mirrored_angle(int laser_angle, int mirror_angle)
323 {
324   int reflected_angle = 16 - laser_angle + mirror_angle;
325
326   /* make sure "reflected_angle" is in valid interval [0, 15] */
327   return (reflected_angle + 16) % 16;
328 }
329
330 static void DrawLaserLines(struct XY *points, int num_points, int mode)
331 {
332   Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray     : pen_bg);
333   Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
334
335   DrawLines(drawto, points, num_points, pixel_drawto);
336
337   BEGIN_NO_HEADLESS
338   {
339     DrawLines(laser_bitmap, points, num_points, pixel_buffer);
340   }
341   END_NO_HEADLESS
342 }
343
344 static boolean CheckLaserPixel(int x, int y)
345 {
346   Pixel pixel;
347
348   BEGIN_NO_HEADLESS
349   {
350     pixel = ReadPixel(laser_bitmap, x, y);
351   }
352   END_NO_HEADLESS
353
354   return (pixel == WHITE_PIXEL);
355 }
356
357 static void 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     if (game_mm.energy_left > 0)
3093     {
3094       game_mm.energy_left--;
3095
3096 #if 0
3097       BlitBitmap(pix[PIX_DOOR], drawto,
3098                  DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3099                  ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3100                  DX_ENERGY, DY_ENERGY);
3101 #endif
3102       redraw_mask |= REDRAW_DOOR_1;
3103     }
3104     else if (setup.time_limit && !game_mm.game_over)
3105     {
3106       int i;
3107
3108       for (i = 15; i >= 0; i--)
3109       {
3110 #if 0
3111         SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3112 #endif
3113         pen_ray = GetPixelFromRGB(window,
3114                                   native_mm_level.laser_red   * 0x11 * i,
3115                                   native_mm_level.laser_green * 0x11 * i,
3116                                   native_mm_level.laser_blue  * 0x11 * i);
3117
3118         DrawLaser(0, DL_LASER_ENABLED);
3119         BackToFront();
3120         Delay(50);
3121       }
3122
3123       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3124       FadeMusic();
3125
3126       DrawLaser(0, DL_LASER_DISABLED);
3127       game_mm.game_over = TRUE;
3128       game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3129
3130 #if 0
3131       if (Request("Out of magic energy ! Play it again ?",
3132                   REQ_ASK | REQ_STAY_CLOSED))
3133       {
3134         InitGame();
3135       }
3136       else
3137       {
3138         game_status = MAINMENU;
3139         DrawMainMenu();
3140       }
3141 #endif
3142
3143       return;
3144     }
3145   }
3146
3147   element = laser.dest_element;
3148
3149 #if 0
3150   if (element != Feld[ELX][ELY])
3151   {
3152     printf("element == %d, Feld[ELX][ELY] == %d\n",
3153            element, Feld[ELX][ELY]);
3154   }
3155 #endif
3156
3157   if (!laser.overloaded && laser.overload_value == 0 &&
3158       element != EL_BOMB &&
3159       element != EL_MINE &&
3160       element != EL_BALL_GRAY &&
3161       element != EL_BLOCK_STONE &&
3162       element != EL_BLOCK_WOOD &&
3163       element != EL_FUSE_ON &&
3164       element != EL_FUEL_FULL &&
3165       !IS_WALL_ICE(element) &&
3166       !IS_WALL_AMOEBA(element))
3167     return;
3168
3169   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3170        (!laser.overloaded && laser.overload_value > 0)) &&
3171       FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3172   {
3173     if (laser.overloaded)
3174       laser.overload_value++;
3175     else
3176       laser.overload_value--;
3177
3178     if (game_mm.cheat_no_overload)
3179     {
3180       laser.overloaded = FALSE;
3181       laser.overload_value = 0;
3182     }
3183
3184     game_mm.laser_overload_value = laser.overload_value;
3185
3186     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3187     {
3188       int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3189       int color_down = 0xFF - color_up;
3190
3191 #if 0
3192       SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3193              (15 - (laser.overload_value / 6)) * color_scale);
3194 #endif
3195       pen_ray =
3196         GetPixelFromRGB(window,
3197                         (native_mm_level.laser_red  ? 0xFF : color_up),
3198                         (native_mm_level.laser_green ? color_down : 0x00),
3199                         (native_mm_level.laser_blue  ? color_down : 0x00));
3200
3201       DrawLaser(0, DL_LASER_ENABLED);
3202 #if 0
3203       BackToFront();
3204 #endif
3205     }
3206
3207     if (!laser.overloaded)
3208       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3209     else if (setup.sound_loops)
3210       PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3211     else
3212       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3213
3214     if (laser.overloaded)
3215     {
3216 #if 0
3217       BlitBitmap(pix[PIX_DOOR], drawto,
3218                  DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3219                  DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3220                  - laser.overload_value,
3221                  OVERLOAD_XSIZE, laser.overload_value,
3222                  DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3223                  - laser.overload_value);
3224 #endif
3225       redraw_mask |= REDRAW_DOOR_1;
3226     }
3227     else
3228     {
3229 #if 0
3230       BlitBitmap(pix[PIX_DOOR], drawto,
3231                  DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3232                  OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3233                  DX_OVERLOAD, DY_OVERLOAD);
3234 #endif
3235       redraw_mask |= REDRAW_DOOR_1;
3236     }
3237
3238     if (laser.overload_value == MAX_LASER_OVERLOAD)
3239     {
3240       int i;
3241
3242       for (i = 15; i >= 0; i--)
3243       {
3244 #if 0
3245         SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3246 #endif
3247
3248         pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3249
3250         DrawLaser(0, DL_LASER_ENABLED);
3251         BackToFront();
3252         Delay(50);
3253       }
3254
3255       DrawLaser(0, DL_LASER_DISABLED);
3256
3257       game_mm.game_over = TRUE;
3258       game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3259
3260 #if 0
3261       if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3262                   REQ_ASK | REQ_STAY_CLOSED))
3263       {
3264         InitGame();
3265       }
3266       else
3267       {
3268         game_status = MAINMENU;
3269         DrawMainMenu();
3270       }
3271 #endif
3272
3273       return;
3274     }
3275   }
3276
3277   if (laser.fuse_off)
3278     return;
3279
3280   CT -= Ct;
3281
3282   if (element == EL_BOMB && CT > 75)
3283   {
3284     if (game_mm.cheat_no_explosion)
3285       return;
3286
3287 #if 0
3288     laser.num_damages--;
3289     DrawLaser(0, DL_LASER_DISABLED);
3290     laser.num_edges = 0;
3291 #endif
3292
3293     Bang_MM(ELX, ELY);
3294
3295     laser.dest_element = EL_EXPLODING_OPAQUE;
3296
3297 #if 0
3298     Bang_MM(ELX, ELY);
3299     laser.num_damages--;
3300     DrawLaser(0, DL_LASER_DISABLED);
3301
3302     laser.num_edges = 0;
3303     Bang_MM(laser.start_edge.x, laser.start_edge.y);
3304
3305     if (Request("Bomb killed Mc Duffin ! Play it again ?",
3306                 REQ_ASK | REQ_STAY_CLOSED))
3307     {
3308       InitGame();
3309     }
3310     else
3311     {
3312       game_status = MAINMENU;
3313       DrawMainMenu();
3314     }
3315 #endif
3316
3317     return;
3318   }
3319
3320   if (element == EL_FUSE_ON && CT > 25)
3321   {
3322     laser.fuse_off = TRUE;
3323     laser.fuse_x = ELX;
3324     laser.fuse_y = ELY;
3325
3326     DrawLaser(0, DL_LASER_DISABLED);
3327     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3328   }
3329
3330   if (element == EL_BALL_GRAY && CT > 75)
3331   {
3332     static int new_elements[] =
3333     {
3334       EL_MIRROR_START,
3335       EL_MIRROR_FIXED_START,
3336       EL_POLAR_START,
3337       EL_POLAR_CROSS_START,
3338       EL_PACMAN_START,
3339       EL_KETTLE,
3340       EL_BOMB,
3341       EL_PRISM
3342     };
3343     int num_new_elements = sizeof(new_elements) / sizeof(int);
3344     int new_element = new_elements[RND(num_new_elements)];
3345
3346     Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3347     Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3348
3349     /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3350     ScanLaser();
3351
3352     return;
3353
3354 #if 0
3355     int graphic;
3356
3357     switch (RND(5))
3358     {
3359       case 0:
3360         element = EL_MIRROR_START + RND(16);
3361         break;
3362       case 1:
3363         {
3364           int rnd = RND(3);
3365
3366           element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3367         }
3368         break;
3369       default:
3370         {
3371           int rnd = RND(3);
3372
3373           element = (rnd == 0 ? EL_FUSE_ON :
3374                      rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3375                      rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3376                      rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3377                      EL_MIRROR_FIXED_START + rnd - 25);
3378         }
3379         break;
3380     }
3381
3382     graphic = el2gfx(element);
3383
3384     for (i = 0; i < 50; i++)
3385     {
3386       int x = RND(26);
3387       int y = RND(26);
3388
3389 #if 0
3390       BlitBitmap(pix[PIX_BACK], drawto,
3391                  SX + (graphic % GFX_PER_LINE) * TILEX + x,
3392                  SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3393                  SX + ELX * TILEX + x,
3394                  SY + ELY * TILEY + y);
3395 #endif
3396       MarkTileDirty(ELX, ELY);
3397       BackToFront();
3398
3399       DrawLaser(0, DL_LASER_ENABLED);
3400
3401       Delay(50);
3402     }
3403
3404     Feld[ELX][ELY] = element;
3405     DrawField_MM(ELX, ELY);
3406
3407 #if 0
3408     printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3409 #endif
3410
3411     /* above stuff: GRAY BALL -> PRISM !!! */
3412 /*
3413     LX = ELX * TILEX + 14;
3414     LY = ELY * TILEY + 14;
3415     if (laser.current_angle == (laser.current_angle >> 1) << 1)
3416       OK = 8;
3417     else
3418       OK = 4;
3419     LX -= OK * XS;
3420     LY -= OK * YS;
3421
3422     laser.num_edges -= 2;
3423     laser.num_damages--;
3424 */
3425
3426 #if 0
3427     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3428       if (laser.damage[i].is_mirror)
3429         break;
3430
3431     if (i > 0)
3432       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3433     else
3434       DrawLaser(0, DL_LASER_DISABLED);
3435 #else
3436     DrawLaser(0, DL_LASER_DISABLED);
3437 #endif
3438
3439     ScanLaser();
3440
3441     /*
3442     printf("TEST ELEMENT: %d\n", Feld[0][0]);
3443     */
3444 #endif
3445
3446     return;
3447   }
3448
3449   if (IS_WALL_ICE(element) && CT > 50)
3450   {
3451     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3452
3453     {
3454       Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3455       Store[ELX][ELY] = EL_WALL_ICE;
3456       Store2[ELX][ELY] = laser.wall_mask;
3457
3458       laser.dest_element = Feld[ELX][ELY];
3459
3460       return;
3461     }
3462
3463     for (i = 0; i < 5; i++)
3464     {
3465       int phase = i + 1;
3466
3467       if (i == 4)
3468       {
3469         Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3470         phase = 0;
3471       }
3472
3473       DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3474       BackToFront();
3475       Delay(100);
3476     }
3477
3478     if (Feld[ELX][ELY] == EL_WALL_ICE)
3479       Feld[ELX][ELY] = EL_EMPTY;
3480
3481 /*
3482     laser.num_edges--;
3483     LX = laser.edge[laser.num_edges].x - (SX + 2);
3484     LY = laser.edge[laser.num_edges].y - (SY + 2);
3485 */
3486
3487     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3488       if (laser.damage[i].is_mirror)
3489         break;
3490
3491     if (i > 0)
3492       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3493     else
3494       DrawLaser(0, DL_LASER_DISABLED);
3495
3496     ScanLaser();
3497
3498     return;
3499   }
3500
3501   if (IS_WALL_AMOEBA(element) && CT > 60)
3502   {
3503     int k1, k2, k3, dx, dy, de, dm;
3504     int element2 = Feld[ELX][ELY];
3505
3506     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3507       return;
3508
3509     for (i = laser.num_damages - 1; i >= 0; i--)
3510       if (laser.damage[i].is_mirror)
3511         break;
3512
3513     r = laser.num_edges;
3514     d = laser.num_damages;
3515     k1 = i;
3516
3517     if (k1 > 0)
3518     {
3519       int x, y;
3520
3521       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3522
3523       laser.num_edges++;
3524       DrawLaser(0, DL_LASER_ENABLED);
3525       laser.num_edges--;
3526
3527       x = laser.damage[k1].x;
3528       y = laser.damage[k1].y;
3529
3530       DrawField_MM(x, y);
3531     }
3532
3533     for (i = 0; i < 4; i++)
3534     {
3535       if (laser.wall_mask & (1 << i))
3536       {
3537         if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3538                             SY + ELY * TILEY + 31 * (i / 2)))
3539           break;
3540
3541         if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3542                             SY + ELY * TILEY + 14 + (i / 2) * 2))
3543           break;
3544       }
3545     }
3546
3547     k2 = i;
3548
3549     for (i = 0; i < 4; i++)
3550     {
3551       if (laser.wall_mask & (1 << i))
3552       {
3553         if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3554                             SY + ELY * TILEY + 31 * (i / 2)))
3555           break;
3556       }
3557     }
3558
3559     k3 = i;
3560
3561     if (laser.num_beamers > 0 ||
3562         k1 < 1 || k2 < 4 || k3 < 4 ||
3563         CheckLaserPixel(SX + ELX * TILEX + 14,
3564                         SY + ELY * TILEY + 14))
3565     {
3566       laser.num_edges = r;
3567       laser.num_damages = d;
3568
3569       DrawLaser(0, DL_LASER_DISABLED);
3570     }
3571
3572     Feld[ELX][ELY] = element | laser.wall_mask;
3573
3574     dx = ELX;
3575     dy = ELY;
3576     de = Feld[ELX][ELY];
3577     dm = laser.wall_mask;
3578
3579 #if 1
3580     {
3581       int x = ELX, y = ELY;
3582       int wall_mask = laser.wall_mask;
3583
3584       ScanLaser();
3585       DrawLaser(0, DL_LASER_ENABLED);
3586
3587       PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3588
3589       Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3590       Store[x][y] = EL_WALL_AMOEBA;
3591       Store2[x][y] = wall_mask;
3592
3593       return;
3594     }
3595 #endif
3596
3597     DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3598     ScanLaser();
3599     DrawLaser(0, DL_LASER_ENABLED);
3600
3601     PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3602
3603     for (i = 4; i >= 0; i--)
3604     {
3605       DrawWallsAnimation_MM(dx, dy, de, i, dm);
3606
3607       BackToFront();
3608       Delay(20);
3609     }
3610
3611     DrawLaser(0, DL_LASER_ENABLED);
3612
3613     return;
3614   }
3615
3616   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3617       laser.stops_inside_element && CT > 75)
3618   {
3619     int x, y;
3620     int k;
3621
3622     if (ABS(XS) > ABS(YS))
3623       k = 0;
3624     else
3625       k = 1;
3626     if (XS < YS)
3627       k += 2;
3628
3629     for (i = 0; i < 4; i++)
3630     {
3631       if (i)
3632         k++;
3633       if (k > 3)
3634         k = 0;
3635
3636       x = ELX + Step[k * 4].x;
3637       y = ELY + Step[k * 4].y;
3638
3639       if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3640         continue;
3641
3642       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3643         continue;
3644
3645       break;
3646     }
3647
3648     if (i > 3)
3649     {
3650       laser.overloaded = (element == EL_BLOCK_STONE);
3651
3652       return;
3653     }
3654
3655     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3656
3657     Feld[ELX][ELY] = 0;
3658     Feld[x][y] = element;
3659
3660     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3661     DrawField_MM(x, y);
3662
3663     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3664     {
3665       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3666       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3667     }
3668
3669     ScanLaser();
3670
3671     return;
3672   }
3673
3674   if (element == EL_FUEL_FULL && CT > 10)
3675   {
3676     for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3677     {
3678 #if 0
3679       BlitBitmap(pix[PIX_DOOR], drawto,
3680                  DOOR_GFX_PAGEX4 + XX_ENERGY,
3681                  DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3682                  ENERGY_XSIZE, i, DX_ENERGY,
3683                  DY_ENERGY + ENERGY_YSIZE - i);
3684 #endif
3685
3686       redraw_mask |= REDRAW_DOOR_1;
3687       BackToFront();
3688
3689       Delay(20);
3690     }
3691
3692     game_mm.energy_left = MAX_LASER_ENERGY;
3693     Feld[ELX][ELY] = EL_FUEL_EMPTY;
3694     DrawField_MM(ELX, ELY);
3695
3696     DrawLaser(0, DL_LASER_ENABLED);
3697
3698     return;
3699   }
3700
3701   return;
3702 }
3703
3704 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3705 {
3706   ClickElement(action.lx, action.ly, action.button);
3707
3708   GameActions_MM_Ext(action, warp_mode);
3709 }
3710
3711 void MovePacMen()
3712 {
3713   int mx, my, ox, oy, nx, ny;
3714   int element;
3715   int l;
3716
3717   if (++pacman_nr >= game_mm.num_pacman)
3718     pacman_nr = 0;
3719
3720   game_mm.pacman[pacman_nr].dir--;
3721
3722   for (l = 1; l < 5; l++)
3723   {
3724     game_mm.pacman[pacman_nr].dir++;
3725
3726     if (game_mm.pacman[pacman_nr].dir > 4)
3727       game_mm.pacman[pacman_nr].dir = 1;
3728
3729     if (game_mm.pacman[pacman_nr].dir % 2)
3730     {
3731       mx = 0;
3732       my = game_mm.pacman[pacman_nr].dir - 2;
3733     }
3734     else
3735     {
3736       my = 0;
3737       mx = 3 - game_mm.pacman[pacman_nr].dir;
3738     }
3739
3740     ox = game_mm.pacman[pacman_nr].x;
3741     oy = game_mm.pacman[pacman_nr].y;
3742     nx = ox + mx;
3743     ny = oy + my;
3744     element = Feld[nx][ny];
3745
3746     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3747       continue;
3748
3749     if (!IS_EATABLE4PACMAN(element))
3750       continue;
3751
3752     if (ObjHit(nx, ny, HIT_POS_CENTER))
3753       continue;
3754
3755     Feld[ox][oy] = EL_EMPTY;
3756     Feld[nx][ny] =
3757       EL_PACMAN_RIGHT - 1 +
3758       (game_mm.pacman[pacman_nr].dir - 1 +
3759        (game_mm.pacman[pacman_nr].dir % 2) * 2);
3760
3761     game_mm.pacman[pacman_nr].x = nx;
3762     game_mm.pacman[pacman_nr].y = ny;
3763
3764     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3765
3766     if (element != EL_EMPTY)
3767     {
3768       int graphic = el2gfx(Feld[nx][ny]);
3769       Bitmap *bitmap;
3770       int src_x, src_y;
3771       int i;
3772
3773       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3774
3775       CT = FrameCounter;
3776       ox = SX + ox * TILEX;
3777       oy = SY + oy * TILEY;
3778
3779       for (i = 1; i < 33; i += 2)
3780         BlitBitmap(bitmap, window,
3781                    src_x, src_y, TILEX, TILEY,
3782                    ox + i * mx, oy + i * my);
3783       Ct = Ct + FrameCounter - CT;
3784     }
3785
3786     DrawField_MM(nx, ny);
3787     BackToFront();
3788
3789     if (!laser.fuse_off)
3790     {
3791       DrawLaser(0, DL_LASER_ENABLED);
3792
3793       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3794       {
3795         AddDamagedField(nx, ny);
3796
3797         laser.damage[laser.num_damages - 1].edge = 0;
3798       }
3799     }
3800
3801     if (element == EL_BOMB)
3802       DeletePacMan(nx, ny);
3803
3804     if (IS_WALL_AMOEBA(element) &&
3805         (LX + 2 * XS) / TILEX == nx &&
3806         (LY + 2 * YS) / TILEY == ny)
3807     {
3808       laser.num_edges--;
3809       ScanLaser();
3810     }
3811
3812     break;
3813   }
3814 }
3815
3816 void GameWon_MM()
3817 {
3818   int hi_pos;
3819   boolean raise_level = FALSE;
3820
3821 #if 0
3822   if (local_player->MovPos)
3823     return;
3824
3825   local_player->LevelSolved = FALSE;
3826 #endif
3827
3828   if (game_mm.energy_left)
3829   {
3830     if (setup.sound_loops)
3831       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3832                    SND_CTRL_PLAY_LOOP);
3833
3834     while (game_mm.energy_left > 0)
3835     {
3836       if (!setup.sound_loops)
3837         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3838
3839       /*
3840       if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3841         RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3842       */
3843
3844       RaiseScore_MM(5);
3845
3846       game_mm.energy_left--;
3847       if (game_mm.energy_left >= 0)
3848       {
3849 #if 0
3850         BlitBitmap(pix[PIX_DOOR], drawto,
3851                    DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3852                    ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3853                    DX_ENERGY, DY_ENERGY);
3854 #endif
3855         redraw_mask |= REDRAW_DOOR_1;
3856       }
3857
3858       BackToFront();
3859       Delay(10);
3860     }
3861
3862     if (setup.sound_loops)
3863       StopSound(SND_SIRR);
3864   }
3865   else if (native_mm_level.time == 0)           /* level without time limit */
3866   {
3867     if (setup.sound_loops)
3868       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3869                    SND_CTRL_PLAY_LOOP);
3870
3871     while (TimePlayed < 999)
3872     {
3873       if (!setup.sound_loops)
3874         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3875       if (TimePlayed < 999 && !(TimePlayed % 10))
3876         RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3877       if (TimePlayed < 900 && !(TimePlayed % 10))
3878         TimePlayed += 10;
3879       else
3880         TimePlayed++;
3881
3882       /*
3883       DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3884       */
3885
3886       BackToFront();
3887       Delay(10);
3888     }
3889
3890     if (setup.sound_loops)
3891       StopSound(SND_SIRR);
3892   }
3893
3894 #if 0
3895   FadeSounds();
3896 #endif
3897
3898   CloseDoor(DOOR_CLOSE_1);
3899
3900   Request("Level solved !", REQ_CONFIRM);
3901
3902   if (level_nr == leveldir_current->handicap_level)
3903   {
3904     leveldir_current->handicap_level++;
3905     SaveLevelSetup_SeriesInfo();
3906   }
3907
3908   if (level_editor_test_game)
3909     game_mm.score = -1;         /* no highscore when playing from editor */
3910   else if (level_nr < leveldir_current->last_level)
3911     raise_level = TRUE;         /* advance to next level */
3912
3913   if ((hi_pos = NewHiScore_MM()) >= 0)
3914   {
3915     game_status = HALLOFFAME;
3916
3917     // DrawHallOfFame(hi_pos);
3918
3919     if (raise_level)
3920       level_nr++;
3921   }
3922   else
3923   {
3924     game_status = MAINMENU;
3925
3926     if (raise_level)
3927       level_nr++;
3928
3929     // DrawMainMenu();
3930   }
3931
3932   BackToFront();
3933 }
3934
3935 int NewHiScore_MM()
3936 {
3937   int k, l;
3938   int position = -1;
3939
3940   // LoadScore(level_nr);
3941
3942   if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3943       game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3944     return -1;
3945
3946   for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3947   {
3948     if (game_mm.score > highscore[k].Score)
3949     {
3950       /* player has made it to the hall of fame */
3951
3952       if (k < MAX_SCORE_ENTRIES - 1)
3953       {
3954         int m = MAX_SCORE_ENTRIES - 1;
3955
3956 #ifdef ONE_PER_NAME
3957         for (l = k; l < MAX_SCORE_ENTRIES; l++)
3958           if (!strcmp(setup.player_name, highscore[l].Name))
3959             m = l;
3960         if (m == k)     /* player's new highscore overwrites his old one */
3961           goto put_into_list;
3962 #endif
3963
3964         for (l = m; l>k; l--)
3965         {
3966           strcpy(highscore[l].Name, highscore[l - 1].Name);
3967           highscore[l].Score = highscore[l - 1].Score;
3968         }
3969       }
3970
3971 #ifdef ONE_PER_NAME
3972       put_into_list:
3973 #endif
3974       strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3975       highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3976       highscore[k].Score = game_mm.score;
3977       position = k;
3978
3979       break;
3980     }
3981
3982 #ifdef ONE_PER_NAME
3983     else if (!strncmp(setup.player_name, highscore[k].Name,
3984                       MAX_PLAYER_NAME_LEN))
3985       break;    /* player already there with a higher score */
3986 #endif
3987
3988   }
3989
3990   // if (position >= 0)
3991   //   SaveScore(level_nr);
3992
3993   return position;
3994 }
3995
3996 static void InitMovingField_MM(int x, int y, int direction)
3997 {
3998   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3999   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
4000
4001   MovDir[x][y] = direction;
4002   MovDir[newx][newy] = direction;
4003
4004   if (Feld[newx][newy] == EL_EMPTY)
4005     Feld[newx][newy] = EL_BLOCKED;
4006 }
4007
4008 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4009 {
4010   int direction = MovDir[x][y];
4011   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4012   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
4013
4014   *goes_to_x = newx;
4015   *goes_to_y = newy;
4016 }
4017
4018 static void Blocked2Moving_MM(int x, int y,
4019                               int *comes_from_x, int *comes_from_y)
4020 {
4021   int oldx = x, oldy = y;
4022   int direction = MovDir[x][y];
4023
4024   if (direction == MV_LEFT)
4025     oldx++;
4026   else if (direction == MV_RIGHT)
4027     oldx--;
4028   else if (direction == MV_UP)
4029     oldy++;
4030   else if (direction == MV_DOWN)
4031     oldy--;
4032
4033   *comes_from_x = oldx;
4034   *comes_from_y = oldy;
4035 }
4036
4037 static int MovingOrBlocked2Element_MM(int x, int y)
4038 {
4039   int element = Feld[x][y];
4040
4041   if (element == EL_BLOCKED)
4042   {
4043     int oldx, oldy;
4044
4045     Blocked2Moving_MM(x, y, &oldx, &oldy);
4046
4047     return Feld[oldx][oldy];
4048   }
4049
4050   return element;
4051 }
4052
4053 #if 0
4054 static void RemoveField(int x, int y)
4055 {
4056   Feld[x][y] = EL_EMPTY;
4057   MovPos[x][y] = 0;
4058   MovDir[x][y] = 0;
4059   MovDelay[x][y] = 0;
4060 }
4061 #endif
4062
4063 static void RemoveMovingField_MM(int x, int y)
4064 {
4065   int oldx = x, oldy = y, newx = x, newy = y;
4066
4067   if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4068     return;
4069
4070   if (IS_MOVING(x, y))
4071   {
4072     Moving2Blocked_MM(x, y, &newx, &newy);
4073     if (Feld[newx][newy] != EL_BLOCKED)
4074       return;
4075   }
4076   else if (Feld[x][y] == EL_BLOCKED)
4077   {
4078     Blocked2Moving_MM(x, y, &oldx, &oldy);
4079     if (!IS_MOVING(oldx, oldy))
4080       return;
4081   }
4082
4083   Feld[oldx][oldy] = EL_EMPTY;
4084   Feld[newx][newy] = EL_EMPTY;
4085   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4086   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4087
4088   DrawLevelField_MM(oldx, oldy);
4089   DrawLevelField_MM(newx, newy);
4090 }
4091
4092 void PlaySoundLevel(int x, int y, int sound_nr)
4093 {
4094   int sx = SCREENX(x), sy = SCREENY(y);
4095   int volume, stereo;
4096   int silence_distance = 8;
4097
4098   if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4099       (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4100     return;
4101
4102   if (!IN_LEV_FIELD(x, y) ||
4103       sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4104       sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4105     return;
4106
4107   volume = SOUND_MAX_VOLUME;
4108
4109 #ifndef MSDOS
4110   stereo = (sx - SCR_FIELDX/2) * 12;
4111 #else
4112   stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4113   if (stereo > SOUND_MAX_RIGHT)
4114     stereo = SOUND_MAX_RIGHT;
4115   if (stereo < SOUND_MAX_LEFT)
4116     stereo = SOUND_MAX_LEFT;
4117 #endif
4118
4119   if (!IN_SCR_FIELD(sx, sy))
4120   {
4121     int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4122     int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4123
4124     volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4125   }
4126
4127   PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4128 }
4129
4130 static void RaiseScore_MM(int value)
4131 {
4132   game_mm.score += value;
4133
4134 #if 0
4135   DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4136            FONT_TEXT_2);
4137 #endif
4138 }
4139
4140 void RaiseScoreElement_MM(int element)
4141 {
4142   switch(element)
4143   {
4144     case EL_PACMAN:
4145       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4146       break;
4147
4148     case EL_KEY:
4149       RaiseScore_MM(native_mm_level.score[SC_KEY]);
4150       break;
4151
4152     default:
4153       break;
4154   }
4155 }