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