replaced graphics based element masks with pre-calculated array (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 /* forward declaration for internal use */
82 static int MovingOrBlocked2Element_MM(int, int);
83 static void Bang_MM(int, int);
84 static void RaiseScore_MM(int);
85 static void RemoveMovingField_MM(int, int);
86 static void InitMovingField_MM(int, int, int);
87 static void ContinueMoving_MM(int, int);
88 static void Moving2Blocked_MM(int, int, int *, int *);
89
90
91 #if 0
92 // =============================================================================
93 // TMP_dump_masks_MM (code)
94 // =============================================================================
95
96 void TMP_dump_masks_MM()
97 {
98   int i;
99
100   printf("static const char mm_masks[10][16][16] =\n");
101   printf("{\n");
102
103   for (i = EL_MM_MASK_MCDUFFIN_RIGHT; i <= EL_MM_MASK_CIRCLE; i++)
104   {
105     int graphic_mask = el2gfx(i);
106     Bitmap *bitmap;
107     int src_x, src_y;
108     int x, y;
109
110     getGraphicSource(graphic_mask, 0, &bitmap, &src_x, &src_y);
111
112     printf("  {\n");
113
114     for (y = 0; y < TILEY; y += 2)
115     {
116       printf("    \"");
117
118       for (x = 0; x < TILEX; x += 2)
119       {
120         Pixel pixel = GetPixel(bitmap, src_x + x, src_y + y);
121
122         printf("%c", (pixel ? 'X' : ' '));
123       }
124
125       printf("\",\n");
126     }
127
128     printf("  },\n");
129   }
130
131   printf("};\n");
132 }
133 #endif
134
135 // =============================================================================
136 // TMP_dump_masks_MM (result)
137 // =============================================================================
138
139 static const char mm_masks[10][16][16 + 1] =
140 {
141   {
142     "                ",
143     "    XXXXX       ",
144     "   XXXXXXX      ",
145     "  XXXXXXXXXXX   ",
146     "  XXXXXXXXXXXXX ",
147     "  XXXXXXXXXXXXXX",
148     "  XXXXXXXXXXXXXX",
149     "  XXXXXXXXXXXXX ",
150     "  XXXXXXXXXXXXX ",
151     "  XXXXXXXXXXXXX ",
152     "  XXXXXXXXXXXXX ",
153     "  XXXXXXXXXXXXX ",
154     "  XXXXXXXXXXXXX ",
155     "  XXXXXXXXXXXXX ",
156     "  XXXXXXXXXXXX  ",
157     "  XXXXXXXXXXXX  ",
158   },
159   {
160     "                ",
161     "    XXXXXXXX    ",
162     "  XXXXXXXXXXXX  ",
163     " XXXXXXXXXXXXXX ",
164     " XXXXXXXXXXXXXX ",
165     " XXXXXXXXXXXXXX ",
166     " XXXXXXXXXXXXXX ",
167     " XXXXXXXXXXXXXX ",
168     " XXXXXXXXXXXXXX ",
169     " XXXXXXXXXXXXXX ",
170     " XXXXXXXXXXXXXX ",
171     " XXXXXXXXXXXXXX ",
172     " XXXXXXXXXXXXXX ",
173     "  XXXXXXXXXXXXX ",
174     "  XXXXXXXXXXXX  ",
175     "  XXXXXXXXXXXX  ",
176   },
177   {
178     "                ",
179     "    XXXXXX      ",
180     "  XXXXXXXXX     ",
181     " XXXXXXXXXXX    ",
182     "XXXXXXXXXXXXX   ",
183     "XXXXXXXXXXXXX   ",
184     "XXXXXXXXXXXXXX  ",
185     " XXXXXXXXXXXXX  ",
186     " XXXXXXXXXXXXX  ",
187     " XXXXXXXXXXXXX  ",
188     " XXXXXXXXXXXXX  ",
189     " XXXXXXXXXXXXX  ",
190     " XXXXXXXXXXXXX  ",
191     " XXXXXXXXXXXXX  ",
192     "  XXXXXXXXXXXX  ",
193     "  XXXXXXXXXXXX  ",
194   },
195   {
196     "                ",
197     "    XXXXXX      ",
198     "   XXXXXXXX     ",
199     "  XXXXXXXXXX    ",
200     "  XXXXXXXXXXX   ",
201     "  XXXXXXXXXXX   ",
202     "  XXXXXXXXXXXX  ",
203     "  XXXXXXXXXXXX  ",
204     "  XXXXXXXXXXXX  ",
205     "  XXXXXXXXXXXX  ",
206     "  XXXXXXXXXXXX  ",
207     "  XXXXXXXXXXXX  ",
208     "  XXXXXXXXXXXX  ",
209     "  XXXXXXXXXXXX  ",
210     "  XXXXXXXXXXXX  ",
211     "  XXXXX  XXXXX  ",
212   },
213   {
214     " XXXXXX  XXXXXX ",
215     "XXXXXXXXXXXXXXXX",
216     "XXXXXXXXXXXXXXXX",
217     "XXXXXXXXXXXXXXXX",
218     "XXXXXXXXXXXXXXXX",
219     "XXXXXXXXXXXXXXXX",
220     "XXXXXXXXXXXXXXXX",
221     "                ",
222     "                ",
223     "XXXXXXXXXXXXXXXX",
224     "XXXXXXXXXXXXXXXX",
225     "XXXXXXXXXXXXXXXX",
226     "XXXXXXXXXXXXXXXX",
227     "XXXXXXXXXXXXXXXX",
228     "XXXXXXXXXXXXXXXX",
229     " XXXXXX  XXXXXX ",
230   },
231   {
232     " XXXXXX  XXXXXX ",
233     "XXXXXXX  XXXXXXX",
234     "XXXXXXX  XXXXXXX",
235     "XXXXXXX  XXXXXXX",
236     "XXXXXXX  XXXXXXX",
237     "XXXXXXX  XXXXXXX",
238     "XXXXXXX  XXXXXXX",
239     " XXXXXX  XXXXXX ",
240     " XXXXXX  XXXXXX ",
241     "XXXXXXX  XXXXXXX",
242     "XXXXXXX  XXXXXXX",
243     "XXXXXXX  XXXXXXX",
244     "XXXXXXX  XXXXXXX",
245     "XXXXXXX  XXXXXXX",
246     "XXXXXXX  XXXXXXX",
247     " XXXXXX  XXXXXX ",
248   },
249   {
250     "     XX  XXXXX  ",
251     "    XXX  XXXX   ",
252     "   XXXX  XXX   X",
253     "  XXXXXXXXX   XX",
254     " XXXXXXXXX   XXX",
255     "XXXXXXXXX   XXXX",
256     "XXXXXXXX   XXXXX",
257     "   XXXX   XXX   ",
258     "   XXX   XXXX   ",
259     "XXXXX   XXXXXXXX",
260     "XXXX   XXXXXXXXX",
261     "XXX   XXXXXXXXX ",
262     "XX   XXXXXXXXX  ",
263     "X   XXX  XXXX   ",
264     "   XXXX  XXX    ",
265     "  XXXXX  XX     ",
266   },
267   {
268     "  XXXXX  XX     ",
269     "   XXXX  XXX    ",
270     "X   XXX  XXXX   ",
271     "XX   XXXXXXXXX  ",
272     "XXX   XXXXXXXXX ",
273     "XXXX   XXXXXXXXX",
274     "XXXXX   XXXXXXXX",
275     "   XXX   XXXX   ",
276     "   XXXX   XXX   ",
277     "XXXXXXXX   XXXXX",
278     "XXXXXXXXX   XXXX",
279     " XXXXXXXXX   XXX",
280     "  XXXXXXXXX   XX",
281     "   XXXX  XXX   X",
282     "    XXX  XXXX   ",
283     "     XX  XXXXX  ",
284   },
285   {
286     "XXXXXXXXXXXXXXXX",
287     "XXXXXXXXXXXXXXXX",
288     "XXXXXXXXXXXXXXXX",
289     "XXXXXXXXXXXXXXXX",
290     "XXXXXXXXXXXXXXXX",
291     "XXXXXXXXXXXXXXXX",
292     "XXXXXXXXXXXXXXXX",
293     "XXXXXXXXXXXXXXXX",
294     "XXXXXXXXXXXXXXXX",
295     "XXXXXXXXXXXXXXXX",
296     "XXXXXXXXXXXXXXXX",
297     "XXXXXXXXXXXXXXXX",
298     "XXXXXXXXXXXXXXXX",
299     "XXXXXXXXXXXXXXXX",
300     "XXXXXXXXXXXXXXXX",
301     "XXXXXXXXXXXXXXXX",
302   },
303   {
304     "                ",
305     "      XXXX      ",
306     "    XXXXXXXX    ",
307     "   XXXXXXXXXX   ",
308     "  XXXXXXXXXXXX  ",
309     "  XXXXXXXXXXXX  ",
310     " XXXXXXXXXXXXXX ",
311     " XXXXXXXXXXXXXX ",
312     " XXXXXXXXXXXXXX ",
313     " XXXXXXXXXXXXXX ",
314     "  XXXXXXXXXXXX  ",
315     "  XXXXXXXXXXXX  ",
316     "   XXXXXXXXXX   ",
317     "    XXXXXXXX    ",
318     "      XXXX      ",
319     "                ",
320   },
321 };
322
323 static int get_element_angle(int element)
324 {
325   int element_phase = get_element_phase(element);
326
327   if (IS_MIRROR_FIXED(element) ||
328       IS_MCDUFFIN(element) ||
329       IS_LASER(element) ||
330       IS_RECEIVER(element))
331     return 4 * element_phase;
332   else
333     return element_phase;
334 }
335
336 static int get_opposite_angle(int angle)
337 {
338   int opposite_angle = angle + ANG_RAY_180;
339
340   /* make sure "opposite_angle" is in valid interval [0, 15] */
341   return (opposite_angle + 16) % 16;
342 }
343
344 static int get_mirrored_angle(int laser_angle, int mirror_angle)
345 {
346   int reflected_angle = 16 - laser_angle + mirror_angle;
347
348   /* make sure "reflected_angle" is in valid interval [0, 15] */
349   return (reflected_angle + 16) % 16;
350 }
351
352 static void InitMovDir_MM(int x, int y)
353 {
354   int element = Feld[x][y];
355   static int direction[3][4] =
356   {
357     { MV_RIGHT, MV_UP,    MV_LEFT,  MV_DOWN },
358     { MV_LEFT,  MV_DOWN,  MV_RIGHT, MV_UP   },
359     { MV_LEFT,  MV_RIGHT, MV_UP,    MV_DOWN }
360   };
361
362   switch(element)
363   {
364     case EL_PACMAN_RIGHT:
365     case EL_PACMAN_UP:
366     case EL_PACMAN_LEFT:
367     case EL_PACMAN_DOWN:
368       Feld[x][y] = EL_PACMAN;
369       MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
370       break;
371
372     default:
373       break;
374   }
375 }
376
377 static void InitField(int x, int y, boolean init_game)
378 {
379   int element = Feld[x][y];
380
381   switch (element)
382   {
383     case EL_DF_EMPTY:
384       Feld[x][y] = EL_EMPTY;
385       break;
386
387     case EL_KETTLE:
388     case EL_CELL:
389       if (native_mm_level.auto_count_kettles)
390         game_mm.kettles_still_needed++;
391       break;
392
393     case EL_LIGHTBULB_OFF:
394       game_mm.lights_still_needed++;
395       break;
396
397     default:
398       if (IS_MIRROR(element) ||
399           IS_BEAMER_OLD(element) ||
400           IS_BEAMER(element) ||
401           IS_POLAR(element) ||
402           IS_POLAR_CROSS(element) ||
403           IS_DF_MIRROR(element) ||
404           IS_DF_MIRROR_AUTO(element) ||
405           IS_GRID_STEEL_AUTO(element) ||
406           IS_GRID_WOOD_AUTO(element) ||
407           IS_FIBRE_OPTIC(element))
408       {
409         if (IS_BEAMER_OLD(element))
410         {
411           Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
412           element = Feld[x][y];
413         }
414
415         if (!IS_FIBRE_OPTIC(element))
416         {
417           static int steps_grid_auto = 0;
418
419           if (game_mm.num_cycle == 0)   /* initialize cycle steps for grids */
420             steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
421
422           if (IS_GRID_STEEL_AUTO(element) ||
423               IS_GRID_WOOD_AUTO(element))
424             game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
425           else
426             game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
427
428           game_mm.cycle[game_mm.num_cycle].x = x;
429           game_mm.cycle[game_mm.num_cycle].y = y;
430           game_mm.num_cycle++;
431         }
432
433         if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
434         {
435           int beamer_nr = BEAMER_NR(element);
436           int nr = laser.beamer[beamer_nr][0].num;
437
438           laser.beamer[beamer_nr][nr].x = x;
439           laser.beamer[beamer_nr][nr].y = y;
440           laser.beamer[beamer_nr][nr].num = 1;
441         }
442       }
443       else if (IS_PACMAN(element))
444       {
445         InitMovDir_MM(x, y);
446       }
447       else if (IS_MCDUFFIN(element) || IS_LASER(element))
448       {
449         laser.start_edge.x = x;
450         laser.start_edge.y = y;
451         laser.start_angle = get_element_angle(element);
452       }
453
454       break;
455   }
456 }
457
458 static void InitCycleElements_RotateSingleStep()
459 {
460   int i;
461
462   if (game_mm.num_cycle == 0)   /* no elements to cycle */
463     return;
464
465   for (i = 0; i < game_mm.num_cycle; i++)
466   {
467     int x = game_mm.cycle[i].x;
468     int y = game_mm.cycle[i].y;
469     int step = SIGN(game_mm.cycle[i].steps);
470     int last_element = Feld[x][y];
471     int next_element = get_rotated_element(last_element, step);
472
473     if (!game_mm.cycle[i].steps)
474       continue;
475
476     Feld[x][y] = next_element;
477
478     DrawField_MM(x, y);
479     game_mm.cycle[i].steps -= step;
480   }
481 }
482
483 static void InitLaser()
484 {
485   int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
486   int step = (IS_LASER(start_element) ? 4 : 0);
487
488   LX = laser.start_edge.x * TILEX;
489   if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
490     LX += 14;
491   else
492     LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
493
494   LY = laser.start_edge.y * TILEY;
495   if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
496     LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
497   else
498     LY += 14;
499
500   XS = 2 * Step[laser.start_angle].x;
501   YS = 2 * Step[laser.start_angle].y;
502
503   laser.current_angle = laser.start_angle;
504
505   laser.num_damages = 0;
506   laser.num_edges = 0;
507   laser.num_beamers = 0;
508   laser.beamer_edge[0] = 0;
509
510   laser.dest_element = EL_EMPTY;
511   laser.wall_mask = 0;
512
513   AddLaserEdge(LX, LY);         /* set laser starting edge */
514
515   pen_ray = GetPixelFromRGB(window,
516                             native_mm_level.laser_red   * 0xFF,
517                             native_mm_level.laser_green * 0xFF,
518                             native_mm_level.laser_blue  * 0xFF);
519 }
520
521 void InitGameEngine_MM()
522 {
523   int i, x, y;
524
525 #if 0
526   TMP_dump_masks_MM();
527 #endif
528
529   /* set global game control values */
530   game_mm.num_cycle = 0;
531   game_mm.num_pacman = 0;
532
533   game_mm.score = 0;
534   game_mm.energy_left = 0;      // later set to "native_mm_level.time"
535   game_mm.kettles_still_needed =
536     (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
537   game_mm.lights_still_needed = 0;
538   game_mm.num_keys = 0;
539
540   game_mm.level_solved = FALSE;
541   game_mm.game_over = FALSE;
542   game_mm.game_over_cause = 0;
543
544   game_mm.laser_overload_value = 0;
545
546   /* set global laser control values (must be set before "InitLaser()") */
547   laser.start_edge.x = 0;
548   laser.start_edge.y = 0;
549   laser.start_angle = 0;
550
551   for (i = 0; i < MAX_NUM_BEAMERS; i++)
552     laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
553
554   laser.overloaded = FALSE;
555   laser.overload_value = 0;
556   laser.fuse_off = FALSE;
557   laser.fuse_x = laser.fuse_y = -1;
558
559   laser.dest_element = EL_EMPTY;
560   laser.wall_mask = 0;
561
562   CT = Ct = 0;
563
564   for (x = 0; x < lev_fieldx; x++)
565   {
566     for (y = 0; y < lev_fieldy; y++)
567     {
568       Feld[x][y] = Ur[x][y];
569       Hit[x][y] = Box[x][y] = 0;
570       Angle[x][y] = 0;
571       MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
572       Store[x][y] = Store2[x][y] = 0;
573       Frame[x][y] = 0;
574       Stop[x][y] = FALSE;
575
576       InitField(x, y, TRUE);
577     }
578   }
579
580 #if 0
581   CloseDoor(DOOR_CLOSE_1);
582 #endif
583
584   DrawLevel_MM();
585 }
586
587 void InitGameActions_MM()
588 {
589   int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
590   int cycle_steps_done = 0;
591   int i;
592
593   InitLaser();
594
595 #if 0
596   /* copy default game door content to main double buffer */
597   BlitBitmap(pix[PIX_DOOR], drawto,
598              DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
599 #endif
600
601 #if 0
602   DrawText(DX_LEVEL, DY_LEVEL,
603            int2str(level_nr, 2), FONT_TEXT_2);
604   DrawText(DX_KETTLES, DY_KETTLES,
605            int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
606   DrawText(DX_SCORE, DY_SCORE,
607            int2str(game_mm.score, 4), FONT_TEXT_2);
608 #endif
609
610 #if 0
611   UnmapGameButtons();
612   MapGameButtons();
613 #endif
614
615 #if 0
616   /* copy actual game door content to door double buffer for OpenDoor() */
617   BlitBitmap(drawto, pix[PIX_DB_DOOR],
618              DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
619 #endif
620
621 #if 0
622   OpenDoor(DOOR_OPEN_ALL);
623 #endif
624
625   for (i = 0; i <= num_init_game_frames; i++)
626   {
627     if (i == num_init_game_frames)
628       StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
629     else if (setup.sound_loops)
630       PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
631     else
632       PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
633
634     game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
635
636     UpdateAndDisplayGameControlValues();
637
638     while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
639     {
640       InitCycleElements_RotateSingleStep();
641
642       cycle_steps_done++;
643     }
644
645     BackToFront();
646
647     ColorCycling();
648
649 #ifdef DEBUG
650     if (setup.quick_doors)
651       continue;
652 #endif
653   }
654
655 #if 0
656   if (setup.sound_music && num_bg_loops)
657     PlayMusic(level_nr % num_bg_loops);
658 #endif
659
660   ScanLaser();
661 }
662
663 void AddLaserEdge(int lx, int ly)
664 {
665   if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
666   {
667     Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
668
669     return;
670   }
671
672   laser.edge[laser.num_edges].x = SX + 2 + lx;
673   laser.edge[laser.num_edges].y = SY + 2 + ly;
674   laser.num_edges++;
675
676   laser.redraw = TRUE;
677 }
678
679 void AddDamagedField(int ex, int ey)
680 {
681   laser.damage[laser.num_damages].is_mirror = FALSE;
682   laser.damage[laser.num_damages].angle = laser.current_angle;
683   laser.damage[laser.num_damages].edge = laser.num_edges;
684   laser.damage[laser.num_damages].x = ex;
685   laser.damage[laser.num_damages].y = ey;
686   laser.num_damages++;
687 }
688
689 boolean StepBehind()
690 {
691   if (laser.num_edges)
692   {
693     int x = LX - XS;
694     int y = LY - YS;
695     int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
696     int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
697
698     return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
699   }
700
701   return FALSE;
702 }
703
704 static int getMaskFromElement(int element)
705 {
706   if (IS_GRID(element))
707     return IMG_MM_MASK_GRID_1 + get_element_phase(element);
708   else if (IS_MCDUFFIN(element))
709     return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
710   else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
711     return IMG_MM_MASK_RECTANGLE;
712   else
713     return IMG_MM_MASK_CIRCLE;
714 }
715
716 int ScanPixel()
717 {
718   int hit_mask = 0;
719
720 #if 0
721   printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
722          LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
723 #endif
724
725   /* follow laser beam until it hits something (at least the screen border) */
726   while (hit_mask == HIT_MASK_NO_HIT)
727   {
728     int i;
729
730 #if 0
731     /* for safety */
732     if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
733         SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
734     {
735       printf("ScanPixel: touched screen border!\n");
736
737       return HIT_MASK_ALL;
738     }
739 #endif
740
741     for (i = 0; i < 4; i++)
742     {
743       int px = LX + (i % 2) * 2;
744       int py = LY + (i / 2) * 2;
745       int dx = px % TILEX;
746       int dy = py % TILEY;
747       int lx = (px + TILEX) / TILEX - 1;  /* ...+TILEX...-1 to get correct */
748       int ly = (py + TILEY) / TILEY - 1;  /* negative values!              */
749       Pixel pixel;
750
751       if (IN_LEV_FIELD(lx, ly))
752       {
753         int element = Feld[lx][ly];
754
755         if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
756         {
757           pixel = 0;
758         }
759         else if (IS_WALL(element) || IS_WALL_CHANGING(element))
760         {
761           int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
762
763           pixel = ((element & (1 << pos)) ? 1 : 0);
764         }
765         else
766         {
767           int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
768
769           pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
770         }
771       }
772       else
773       {
774         pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
775                  SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
776       }
777
778       if ((Sign[laser.current_angle] & (1 << i)) && pixel)
779         hit_mask |= (1 << i);
780     }
781
782     if (hit_mask == HIT_MASK_NO_HIT)
783     {
784       /* hit nothing -- go on with another step */
785       LX += XS;
786       LY += YS;
787     }
788   }
789
790   return hit_mask;
791 }
792
793 void ScanLaser()
794 {
795   int element;
796   int end = 0, rf = laser.num_edges;
797
798   /* do not scan laser again after the game was lost for whatever reason */
799   if (game_mm.game_over)
800     return;
801
802   laser.overloaded = FALSE;
803   laser.stops_inside_element = FALSE;
804
805   DrawLaser(0, DL_LASER_ENABLED);
806
807 #if 0
808   printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
809          LX, LY, XS, YS);
810 #endif
811
812   while (1)
813   {
814     int hit_mask;
815
816     if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
817     {
818       end = 1;
819       laser.overloaded = TRUE;
820
821       break;
822     }
823
824     hit_mask = ScanPixel();
825
826 #if 0
827     printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
828            LX, LY, XS, YS);
829 #endif
830
831     /* hit something -- check out what it was */
832     ELX = (LX + XS) / TILEX;
833     ELY = (LY + YS) / TILEY;
834
835 #if 0
836     printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
837            hit_mask, LX, LY, ELX, ELY);
838 #endif
839
840     if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
841     {
842       element = EL_EMPTY;
843       laser.dest_element = element;
844
845       break;
846     }
847
848     if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
849     {
850       /* we have hit the top-right and bottom-left element --
851          choose the bottom-left one */
852       /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
853          ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
854          THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
855       ELX = (LX - 2) / TILEX;
856       ELY = (LY + 2) / TILEY;
857     }
858
859     if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
860     {
861       /* we have hit the top-left and bottom-right element --
862          choose the top-left one */
863       /* !!! SEE ABOVE !!! */
864       ELX = (LX - 2) / TILEX;
865       ELY = (LY - 2) / TILEY;
866     }
867
868 #if 0
869     printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
870            hit_mask, LX, LY, ELX, ELY);
871 #endif
872
873     element = Feld[ELX][ELY];
874     laser.dest_element = element;
875
876 #if 0
877     printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
878            element, ELX, ELY,
879            LX, LY,
880            LX % TILEX, LY % TILEY,
881            hit_mask);
882 #endif
883
884 #if 0
885     if (!IN_LEV_FIELD(ELX, ELY))
886       printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
887 #endif
888
889     if (element == EL_EMPTY)
890     {
891       if (!HitOnlyAnEdge(element, hit_mask))
892         break;
893     }
894     else if (element == EL_FUSE_ON)
895     {
896       if (HitPolarizer(element, hit_mask))
897         break;
898     }
899     else if (IS_GRID(element) || IS_DF_GRID(element))
900     {
901       if (HitPolarizer(element, hit_mask))
902         break;
903     }
904     else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
905              element == EL_GATE_STONE || element == EL_GATE_WOOD)
906     {
907       if (HitBlock(element, hit_mask))
908       {
909         rf = 1;
910
911         break;
912       }
913     }
914     else if (IS_MCDUFFIN(element))
915     {
916       if (HitLaserSource(element, hit_mask))
917         break;
918     }
919     else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
920              IS_RECEIVER(element))
921     {
922       if (HitLaserDestination(element, hit_mask))
923         break;
924     }
925     else if (IS_WALL(element))
926     {
927       if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
928       {
929         if (HitReflectingWalls(element, hit_mask))
930           break;
931       }
932       else
933       {
934         if (HitAbsorbingWalls(element, hit_mask))
935           break;
936       }
937     }
938     else
939     {
940       if (HitElement(element, hit_mask))
941         break;
942     }
943
944     if (rf)
945       DrawLaser(rf - 1, DL_LASER_ENABLED);
946     rf = laser.num_edges;
947   }
948
949 #if 0
950   if (laser.dest_element != Feld[ELX][ELY])
951   {
952     printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
953            laser.dest_element, Feld[ELX][ELY]);
954   }
955 #endif
956
957   if (!end && !laser.stops_inside_element && !StepBehind())
958   {
959 #if 0
960     printf("ScanLaser: Go one step back\n");
961 #endif
962
963     LX -= XS;
964     LY -= YS;
965
966     AddLaserEdge(LX, LY);
967   }
968
969   if (rf)
970     DrawLaser(rf - 1, DL_LASER_ENABLED);
971
972   Ct = CT = FrameCounter;
973
974 #if 0
975     if (!IN_LEV_FIELD(ELX, ELY))
976       printf("WARNING! (2) %d, %d\n", ELX, ELY);
977 #endif
978 }
979
980 void DrawLaserExt(int start_edge, int num_edges, int mode)
981 {
982   int element;
983   int elx, ely;
984
985 #if 0
986   printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
987          start_edge, num_edges, mode);
988 #endif
989
990   if (start_edge < 0)
991   {
992     Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
993
994     return;
995   }
996
997   if (num_edges < 0)
998   {
999     Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
1000
1001     return;
1002   }
1003
1004 #if 0
1005   if (mode == DL_LASER_DISABLED)
1006   {
1007     printf("DrawLaser: Delete laser from edge %d\n", start_edge);
1008   }
1009 #endif
1010
1011   /* now draw the laser to the backbuffer and (if enabled) to the screen */
1012   DrawLines(drawto, &laser.edge[start_edge], num_edges,
1013             (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
1014
1015   redraw_mask |= REDRAW_FIELD;
1016
1017   if (mode == DL_LASER_ENABLED)
1018     return;
1019
1020   /* after the laser was deleted, the "damaged" graphics must be restored */
1021   if (laser.num_damages)
1022   {
1023     int damage_start = 0;
1024     int i;
1025
1026     /* determine the starting edge, from which graphics need to be restored */
1027     if (start_edge > 0)
1028     {
1029       for (i = 0; i < laser.num_damages; i++)
1030       {
1031         if (laser.damage[i].edge == start_edge + 1)
1032         {
1033           damage_start = i;
1034
1035           break;
1036         }
1037       }
1038     }
1039
1040     /* restore graphics from this starting edge to the end of damage list */
1041     for (i = damage_start; i < laser.num_damages; i++)
1042     {
1043       int lx = laser.damage[i].x;
1044       int ly = laser.damage[i].y;
1045       int element = Feld[lx][ly];
1046
1047       if (Hit[lx][ly] == laser.damage[i].edge)
1048         if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1049                i == damage_start))
1050           Hit[lx][ly] = 0;
1051       if (Box[lx][ly] == laser.damage[i].edge)
1052         Box[lx][ly] = 0;
1053
1054       if (IS_DRAWABLE(element))
1055         DrawField_MM(lx, ly);
1056     }
1057
1058     elx = laser.damage[damage_start].x;
1059     ely = laser.damage[damage_start].y;
1060     element = Feld[elx][ely];
1061
1062 #if 0
1063     if (IS_BEAMER(element))
1064     {
1065       int i;
1066
1067       for (i = 0; i < laser.num_beamers; i++)
1068         printf("-> %d\n", laser.beamer_edge[i]);
1069       printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
1070              mode, elx, ely, Hit[elx][ely], start_edge);
1071       printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
1072              get_element_angle(element), laser.damage[damage_start].angle);
1073     }
1074 #endif
1075
1076     if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1077         laser.num_beamers > 0 &&
1078         start_edge == laser.beamer_edge[laser.num_beamers - 1])
1079     {
1080       /* element is outgoing beamer */
1081       laser.num_damages = damage_start + 1;
1082
1083       if (IS_BEAMER(element))
1084         laser.current_angle = get_element_angle(element);
1085     }
1086     else
1087     {
1088       /* element is incoming beamer or other element */
1089       laser.num_damages = damage_start;
1090       laser.current_angle = laser.damage[laser.num_damages].angle;
1091     }
1092   }
1093   else
1094   {
1095     /* no damages but McDuffin himself (who needs to be redrawn anyway) */
1096
1097     elx = laser.start_edge.x;
1098     ely = laser.start_edge.y;
1099     element = Feld[elx][ely];
1100   }
1101
1102   laser.num_edges = start_edge + 1;
1103   if (start_edge == 0)
1104     laser.current_angle = laser.start_angle;
1105
1106   LX = laser.edge[start_edge].x - (SX + 2);
1107   LY = laser.edge[start_edge].y - (SY + 2);
1108   XS = 2 * Step[laser.current_angle].x;
1109   YS = 2 * Step[laser.current_angle].y;
1110
1111 #if 0
1112   printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
1113          LX, LY, element);
1114 #endif
1115
1116   if (start_edge > 0)
1117   {
1118     if (IS_BEAMER(element) ||
1119         IS_FIBRE_OPTIC(element) ||
1120         IS_PACMAN(element) ||
1121         IS_POLAR(element) ||
1122         IS_POLAR_CROSS(element) ||
1123         element == EL_FUSE_ON)
1124     {
1125       int step_size;
1126
1127 #if 0
1128       printf("element == %d\n", element);
1129 #endif
1130
1131       if (IS_22_5_ANGLE(laser.current_angle))   /* neither 90° nor 45° angle */
1132         step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1133       else
1134         step_size = 8;
1135
1136       if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1137           ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1138            (laser.num_beamers == 0 ||
1139             start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1140       {
1141         /* element is incoming beamer or other element */
1142         step_size = -step_size;
1143         laser.num_edges--;
1144       }
1145
1146 #if 0
1147       if (IS_BEAMER(element))
1148       {
1149         printf("start_edge == %d, laser.beamer_edge == %d\n",
1150                start_edge, laser.beamer_edge);
1151       }
1152 #endif
1153
1154       LX += step_size * XS;
1155       LY += step_size * YS;
1156     }
1157     else if (element != EL_EMPTY)
1158     {
1159       LX -= 3 * XS;
1160       LY -= 3 * YS;
1161       laser.num_edges--;
1162     }
1163   }
1164
1165 #if 0
1166   printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
1167          LX, LY, element);
1168 #endif
1169 }
1170
1171 void DrawLaser(int start_edge, int mode)
1172 {
1173   if (laser.num_edges - start_edge < 0)
1174   {
1175     Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
1176
1177     return;
1178   }
1179
1180   /* check if laser is interrupted by beamer element */
1181   if (laser.num_beamers > 0 &&
1182       start_edge < laser.beamer_edge[laser.num_beamers - 1])
1183   {
1184     if (mode == DL_LASER_ENABLED)
1185     {
1186       int i;
1187       int tmp_start_edge = start_edge;
1188
1189       /* draw laser segments forward from the start to the last beamer */
1190       for (i = 0; i < laser.num_beamers; i++)
1191       {
1192         int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1193
1194         if (tmp_num_edges <= 0)
1195           continue;
1196
1197 #if 0
1198         printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
1199                i, laser.beamer_edge[i], tmp_start_edge);
1200 #endif
1201
1202         DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1203
1204         tmp_start_edge = laser.beamer_edge[i];
1205       }
1206
1207       /* draw last segment from last beamer to the end */
1208       DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1209                    DL_LASER_ENABLED);
1210     }
1211     else
1212     {
1213       int i;
1214       int last_num_edges = laser.num_edges;
1215       int num_beamers = laser.num_beamers;
1216
1217       /* delete laser segments backward from the end to the first beamer */
1218       for (i = num_beamers-1; i >= 0; i--)
1219       {
1220         int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1221
1222         if (laser.beamer_edge[i] - start_edge <= 0)
1223           break;
1224
1225         DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1226
1227         last_num_edges = laser.beamer_edge[i];
1228         laser.num_beamers--;
1229       }
1230
1231 #if 0
1232       if (last_num_edges - start_edge <= 0)
1233         printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1234                last_num_edges, start_edge);
1235 #endif
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 (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
2909       return TRUE;
2910   }
2911
2912   if (bits & HIT_POS_EDGE)
2913   {
2914     for (i = 0; i < 4; i++)
2915       if (ReadPixel(drawto,
2916                     SX + obx + 31 * (i % 2),
2917                     SY + oby + 31 * (i / 2)) == pen_ray)
2918         return TRUE;
2919   }
2920
2921   if (bits & HIT_POS_BETWEEN)
2922   {
2923     for (i = 0; i < 4; i++)
2924       if (ReadPixel(drawto,
2925                     SX + 4 + obx + 22 * (i % 2),
2926                     SY + 4 + oby + 22 * (i / 2)) == pen_ray)
2927         return TRUE;
2928   }
2929
2930   return FALSE;
2931 }
2932
2933 void DeletePacMan(int px, int py)
2934 {
2935   int i, j;
2936
2937   Bang_MM(px, py);
2938
2939   if (game_mm.num_pacman <= 1)
2940   {
2941     game_mm.num_pacman = 0;
2942     return;
2943   }
2944
2945   for (i = 0; i < game_mm.num_pacman; i++)
2946     if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2947       break;
2948
2949   game_mm.num_pacman--;
2950
2951   for (j = i; j < game_mm.num_pacman; j++)
2952   {
2953     game_mm.pacman[j].x   = game_mm.pacman[j + 1].x;
2954     game_mm.pacman[j].y   = game_mm.pacman[j + 1].y;
2955     game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2956   }
2957 }
2958
2959 void ColorCycling(void)
2960 {
2961   static int CC, Cc = 0;
2962
2963   static int color, old = 0xF00, new = 0x010, mult = 1;
2964   static unsigned short red, green, blue;
2965
2966   if (color_status == STATIC_COLORS)
2967     return;
2968
2969   CC = FrameCounter;
2970
2971   if (CC < Cc || CC > Cc + 2)
2972   {
2973     Cc = CC;
2974
2975     color = old + new * mult;
2976     if (mult > 0)
2977       mult++;
2978     else
2979       mult--;
2980
2981     if (ABS(mult) == 16)
2982     {
2983       mult =- mult / 16;
2984       old = color;
2985       new = new << 4;
2986
2987       if (new > 0x100)
2988         new = 0x001;
2989     }
2990
2991     red   = 0x0e00 * ((color & 0xF00) >> 8);
2992     green = 0x0e00 * ((color & 0x0F0) >> 4);
2993     blue  = 0x0e00 * ((color & 0x00F));
2994     SetRGB(pen_magicolor[0], red, green, blue);
2995
2996     red   = 0x1111 * ((color & 0xF00) >> 8);
2997     green = 0x1111 * ((color & 0x0F0) >> 4);
2998     blue  = 0x1111 * ((color & 0x00F));
2999     SetRGB(pen_magicolor[1], red, green, blue);
3000   }
3001 }
3002
3003 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3004 {
3005   static unsigned int pacman_delay = 0;
3006   static unsigned int energy_delay = 0;
3007   static unsigned int overload_delay = 0;
3008   int element;
3009   int x, y, i;
3010
3011   int r, d;
3012
3013   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3014     Stop[x][y] = FALSE;
3015
3016   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3017   {
3018     element = Feld[x][y];
3019
3020     if (!IS_MOVING(x, y) && CAN_MOVE(element))
3021       StartMoving_MM(x, y);
3022     else if (IS_MOVING(x, y))
3023       ContinueMoving_MM(x, y);
3024     else if (IS_EXPLODING(element))
3025       Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3026     else if (element == EL_EXIT_OPENING)
3027       OpenExit(x, y);
3028     else if (element == EL_GRAY_BALL_OPENING)
3029       OpenSurpriseBall(x, y);
3030     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3031       MeltIce(x, y);
3032     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3033       GrowAmoeba(x, y);
3034   }
3035
3036   AutoRotateMirrors();
3037
3038 #if 1
3039   /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3040
3041   /* redraw after Explode_MM() ... */
3042   if (laser.redraw)
3043     DrawLaser(0, DL_LASER_ENABLED);
3044   laser.redraw = FALSE;
3045 #endif
3046
3047   CT = FrameCounter;
3048
3049   if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3050   {
3051     MovePacMen();
3052
3053     if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3054     {
3055       DrawLaser(0, DL_LASER_DISABLED);
3056       ScanLaser();
3057     }
3058   }
3059
3060   if (FrameReached(&energy_delay, ENERGY_DELAY))
3061   {
3062     game_mm.energy_left--;
3063     if (game_mm.energy_left >= 0)
3064     {
3065 #if 0
3066       BlitBitmap(pix[PIX_DOOR], drawto,
3067                  DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3068                  ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3069                  DX_ENERGY, DY_ENERGY);
3070 #endif
3071       redraw_mask |= REDRAW_DOOR_1;
3072     }
3073     else if (setup.time_limit)
3074     {
3075       int i;
3076
3077       for (i = 15; i >= 0; i--)
3078       {
3079 #if 0
3080         SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3081 #endif
3082         pen_ray = GetPixelFromRGB(window,
3083                                   native_mm_level.laser_red   * 0x11 * i,
3084                                   native_mm_level.laser_green * 0x11 * i,
3085                                   native_mm_level.laser_blue  * 0x11 * i);
3086
3087         DrawLaser(0, DL_LASER_ENABLED);
3088         BackToFront();
3089         Delay(50);
3090       }
3091
3092       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3093       FadeMusic();
3094
3095       DrawLaser(0, DL_LASER_DISABLED);
3096       game_mm.game_over = TRUE;
3097       game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3098
3099 #if 0
3100       if (Request("Out of magic energy ! Play it again ?",
3101                   REQ_ASK | REQ_STAY_CLOSED))
3102       {
3103         InitGame();
3104       }
3105       else
3106       {
3107         game_status = MAINMENU;
3108         DrawMainMenu();
3109       }
3110 #endif
3111
3112       return;
3113     }
3114   }
3115
3116   element = laser.dest_element;
3117
3118 #if 0
3119   if (element != Feld[ELX][ELY])
3120   {
3121     printf("element == %d, Feld[ELX][ELY] == %d\n",
3122            element, Feld[ELX][ELY]);
3123   }
3124 #endif
3125
3126   if (!laser.overloaded && laser.overload_value == 0 &&
3127       element != EL_BOMB &&
3128       element != EL_MINE &&
3129       element != EL_BALL_GRAY &&
3130       element != EL_BLOCK_STONE &&
3131       element != EL_BLOCK_WOOD &&
3132       element != EL_FUSE_ON &&
3133       element != EL_FUEL_FULL &&
3134       !IS_WALL_ICE(element) &&
3135       !IS_WALL_AMOEBA(element))
3136     return;
3137
3138   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3139        (!laser.overloaded && laser.overload_value > 0)) &&
3140       FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3141   {
3142     if (laser.overloaded)
3143       laser.overload_value++;
3144     else
3145       laser.overload_value--;
3146
3147     if (game_mm.cheat_no_overload)
3148     {
3149       laser.overloaded = FALSE;
3150       laser.overload_value = 0;
3151     }
3152
3153     game_mm.laser_overload_value = laser.overload_value;
3154
3155     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3156     {
3157       int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3158       int color_down = 0xFF - color_up;
3159
3160 #if 0
3161       SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3162              (15 - (laser.overload_value / 6)) * color_scale);
3163 #endif
3164       pen_ray =
3165         GetPixelFromRGB(window,
3166                         (native_mm_level.laser_red  ? 0xFF : color_up),
3167                         (native_mm_level.laser_green ? color_down : 0x00),
3168                         (native_mm_level.laser_blue  ? color_down : 0x00));
3169
3170       DrawLaser(0, DL_LASER_ENABLED);
3171 #if 0
3172       BackToFront();
3173 #endif
3174     }
3175
3176     if (!laser.overloaded)
3177       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3178     else if (setup.sound_loops)
3179       PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3180     else
3181       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3182
3183     if (laser.overloaded)
3184     {
3185 #if 0
3186       BlitBitmap(pix[PIX_DOOR], drawto,
3187                  DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3188                  DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3189                  - laser.overload_value,
3190                  OVERLOAD_XSIZE, laser.overload_value,
3191                  DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3192                  - laser.overload_value);
3193 #endif
3194       redraw_mask |= REDRAW_DOOR_1;
3195     }
3196     else
3197     {
3198 #if 0
3199       BlitBitmap(pix[PIX_DOOR], drawto,
3200                  DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3201                  OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3202                  DX_OVERLOAD, DY_OVERLOAD);
3203 #endif
3204       redraw_mask |= REDRAW_DOOR_1;
3205     }
3206
3207     if (laser.overload_value == MAX_LASER_OVERLOAD)
3208     {
3209       int i;
3210
3211       for (i = 15; i >= 0; i--)
3212       {
3213 #if 0
3214         SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3215 #endif
3216
3217         pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3218
3219         DrawLaser(0, DL_LASER_ENABLED);
3220         BackToFront();
3221         Delay(50);
3222       }
3223
3224       DrawLaser(0, DL_LASER_DISABLED);
3225
3226       game_mm.game_over = TRUE;
3227       game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3228
3229 #if 0
3230       if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3231                   REQ_ASK | REQ_STAY_CLOSED))
3232       {
3233         InitGame();
3234       }
3235       else
3236       {
3237         game_status = MAINMENU;
3238         DrawMainMenu();
3239       }
3240 #endif
3241
3242       return;
3243     }
3244   }
3245
3246   if (laser.fuse_off)
3247     return;
3248
3249   CT -= Ct;
3250
3251   if (element == EL_BOMB && CT > 75)
3252   {
3253     if (game_mm.cheat_no_explosion)
3254       return;
3255
3256 #if 0
3257     laser.num_damages--;
3258     DrawLaser(0, DL_LASER_DISABLED);
3259     laser.num_edges = 0;
3260 #endif
3261
3262     Bang_MM(ELX, ELY);
3263
3264     laser.dest_element = EL_EXPLODING_OPAQUE;
3265
3266 #if 0
3267     Bang_MM(ELX, ELY);
3268     laser.num_damages--;
3269     DrawLaser(0, DL_LASER_DISABLED);
3270
3271     laser.num_edges = 0;
3272     Bang_MM(laser.start_edge.x, laser.start_edge.y);
3273
3274     if (Request("Bomb killed Mc Duffin ! Play it again ?",
3275                 REQ_ASK | REQ_STAY_CLOSED))
3276     {
3277       InitGame();
3278     }
3279     else
3280     {
3281       game_status = MAINMENU;
3282       DrawMainMenu();
3283     }
3284 #endif
3285
3286     return;
3287   }
3288
3289   if (element == EL_FUSE_ON && CT > 25)
3290   {
3291     laser.fuse_off = TRUE;
3292     laser.fuse_x = ELX;
3293     laser.fuse_y = ELY;
3294
3295     DrawLaser(0, DL_LASER_DISABLED);
3296     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3297   }
3298
3299   if (element == EL_BALL_GRAY && CT > 75)
3300   {
3301     static int new_elements[] =
3302     {
3303       EL_MIRROR_START,
3304       EL_MIRROR_FIXED_START,
3305       EL_POLAR_START,
3306       EL_POLAR_CROSS_START,
3307       EL_PACMAN_START,
3308       EL_KETTLE,
3309       EL_BOMB,
3310       EL_PRISM
3311     };
3312     int num_new_elements = sizeof(new_elements) / sizeof(int);
3313     int new_element = new_elements[RND(num_new_elements)];
3314
3315     Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3316     Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3317
3318     /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3319     ScanLaser();
3320
3321     return;
3322
3323 #if 0
3324     int graphic;
3325
3326     switch (RND(5))
3327     {
3328       case 0:
3329         element = EL_MIRROR_START + RND(16);
3330         break;
3331       case 1:
3332         {
3333           int rnd = RND(3);
3334
3335           element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3336         }
3337         break;
3338       default:
3339         {
3340           int rnd = RND(3);
3341
3342           element = (rnd == 0 ? EL_FUSE_ON :
3343                      rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3344                      rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3345                      rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3346                      EL_MIRROR_FIXED_START + rnd - 25);
3347         }
3348         break;
3349     }
3350
3351     graphic = el2gfx(element);
3352
3353     for (i = 0; i < 50; i++)
3354     {
3355       int x = RND(26);
3356       int y = RND(26);
3357
3358 #if 0
3359       BlitBitmap(pix[PIX_BACK], drawto,
3360                  SX + (graphic % GFX_PER_LINE) * TILEX + x,
3361                  SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3362                  SX + ELX * TILEX + x,
3363                  SY + ELY * TILEY + y);
3364 #endif
3365       MarkTileDirty(ELX, ELY);
3366       BackToFront();
3367
3368       DrawLaser(0, DL_LASER_ENABLED);
3369
3370       Delay(50);
3371     }
3372
3373     Feld[ELX][ELY] = element;
3374     DrawField_MM(ELX, ELY);
3375
3376 #if 0
3377     printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3378 #endif
3379
3380     /* above stuff: GRAY BALL -> PRISM !!! */
3381 /*
3382     LX = ELX * TILEX + 14;
3383     LY = ELY * TILEY + 14;
3384     if (laser.current_angle == (laser.current_angle >> 1) << 1)
3385       OK = 8;
3386     else
3387       OK = 4;
3388     LX -= OK * XS;
3389     LY -= OK * YS;
3390
3391     laser.num_edges -= 2;
3392     laser.num_damages--;
3393 */
3394
3395 #if 0
3396     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3397       if (laser.damage[i].is_mirror)
3398         break;
3399
3400     if (i > 0)
3401       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3402     else
3403       DrawLaser(0, DL_LASER_DISABLED);
3404 #else
3405     DrawLaser(0, DL_LASER_DISABLED);
3406 #endif
3407
3408     ScanLaser();
3409
3410     /*
3411     printf("TEST ELEMENT: %d\n", Feld[0][0]);
3412     */
3413 #endif
3414
3415     return;
3416   }
3417
3418   if (IS_WALL_ICE(element) && CT > 50)
3419   {
3420     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3421
3422     {
3423       Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3424       Store[ELX][ELY] = EL_WALL_ICE;
3425       Store2[ELX][ELY] = laser.wall_mask;
3426
3427       laser.dest_element = Feld[ELX][ELY];
3428
3429       return;
3430     }
3431
3432     for (i = 0; i < 5; i++)
3433     {
3434       int phase = i + 1;
3435
3436       if (i == 4)
3437       {
3438         Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3439         phase = 0;
3440       }
3441
3442       DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3443       BackToFront();
3444       Delay(100);
3445     }
3446
3447     if (Feld[ELX][ELY] == EL_WALL_ICE)
3448       Feld[ELX][ELY] = EL_EMPTY;
3449
3450 /*
3451     laser.num_edges--;
3452     LX = laser.edge[laser.num_edges].x - (SX + 2);
3453     LY = laser.edge[laser.num_edges].y - (SY + 2);
3454 */
3455
3456     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3457       if (laser.damage[i].is_mirror)
3458         break;
3459
3460     if (i > 0)
3461       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3462     else
3463       DrawLaser(0, DL_LASER_DISABLED);
3464
3465     ScanLaser();
3466
3467     return;
3468   }
3469
3470   if (IS_WALL_AMOEBA(element) && CT > 60)
3471   {
3472     int k1, k2, k3, dx, dy, de, dm;
3473     int element2 = Feld[ELX][ELY];
3474
3475     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3476       return;
3477
3478     for (i = laser.num_damages - 1; i >= 0; i--)
3479       if (laser.damage[i].is_mirror)
3480         break;
3481
3482     r = laser.num_edges;
3483     d = laser.num_damages;
3484     k1 = i;
3485
3486     if (k1 > 0)
3487     {
3488       int x, y;
3489
3490       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3491
3492       laser.num_edges++;
3493       DrawLaser(0, DL_LASER_ENABLED);
3494       laser.num_edges--;
3495
3496       x = laser.damage[k1].x;
3497       y = laser.damage[k1].y;
3498
3499       DrawField_MM(x, y);
3500     }
3501
3502     for (i = 0; i < 4; i++)
3503     {
3504       if (laser.wall_mask & (1 << i))
3505       {
3506         if (ReadPixel(drawto,
3507                       SX + ELX * TILEX + 14 + (i % 2) * 2,
3508                       SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3509           break;
3510         if (ReadPixel(drawto,
3511                       SX + ELX * TILEX + 31 * (i % 2),
3512                       SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3513           break;
3514       }
3515     }
3516
3517     k2 = i;
3518
3519     for (i = 0; i < 4; i++)
3520     {
3521       if (laser.wall_mask & (1 << i))
3522       {
3523         if (ReadPixel(drawto,
3524                       SX + ELX * TILEX + 31 * (i % 2),
3525                       SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3526           break;
3527       }
3528     }
3529
3530     k3 = i;
3531
3532     if (laser.num_beamers > 0 ||
3533         k1 < 1 || k2 < 4 || k3 < 4 ||
3534         ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3535         == pen_ray)
3536     {
3537       laser.num_edges = r;
3538       laser.num_damages = d;
3539
3540       DrawLaser(0, DL_LASER_DISABLED);
3541     }
3542
3543     Feld[ELX][ELY] = element | laser.wall_mask;
3544
3545     dx = ELX;
3546     dy = ELY;
3547     de = Feld[ELX][ELY];
3548     dm = laser.wall_mask;
3549
3550 #if 1
3551     {
3552       int x = ELX, y = ELY;
3553       int wall_mask = laser.wall_mask;
3554
3555       ScanLaser();
3556       DrawLaser(0, DL_LASER_ENABLED);
3557
3558       PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3559
3560       Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3561       Store[x][y] = EL_WALL_AMOEBA;
3562       Store2[x][y] = wall_mask;
3563
3564       return;
3565     }
3566 #endif
3567
3568     DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3569     ScanLaser();
3570     DrawLaser(0, DL_LASER_ENABLED);
3571
3572     PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3573
3574     for (i = 4; i >= 0; i--)
3575     {
3576       DrawWallsAnimation_MM(dx, dy, de, i, dm);
3577
3578       BackToFront();
3579       Delay(20);
3580     }
3581
3582     DrawLaser(0, DL_LASER_ENABLED);
3583
3584     return;
3585   }
3586
3587   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3588       laser.stops_inside_element && CT > 75)
3589   {
3590     int x, y;
3591     int k;
3592
3593     if (ABS(XS) > ABS(YS))
3594       k = 0;
3595     else
3596       k = 1;
3597     if (XS < YS)
3598       k += 2;
3599
3600     for (i = 0; i < 4; i++)
3601     {
3602       if (i)
3603         k++;
3604       if (k > 3)
3605         k = 0;
3606
3607       x = ELX + Step[k * 4].x;
3608       y = ELY + Step[k * 4].y;
3609
3610       if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3611         continue;
3612
3613       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3614         continue;
3615
3616       break;
3617     }
3618
3619     if (i > 3)
3620     {
3621       laser.overloaded = (element == EL_BLOCK_STONE);
3622
3623       return;
3624     }
3625
3626     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3627
3628     Feld[ELX][ELY] = 0;
3629     Feld[x][y] = element;
3630
3631     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3632     DrawField_MM(x, y);
3633
3634     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3635     {
3636       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3637       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3638     }
3639
3640     ScanLaser();
3641
3642     return;
3643   }
3644
3645   if (element == EL_FUEL_FULL && CT > 10)
3646   {
3647     for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3648     {
3649 #if 0
3650       BlitBitmap(pix[PIX_DOOR], drawto,
3651                  DOOR_GFX_PAGEX4 + XX_ENERGY,
3652                  DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3653                  ENERGY_XSIZE, i, DX_ENERGY,
3654                  DY_ENERGY + ENERGY_YSIZE - i);
3655 #endif
3656
3657       redraw_mask |= REDRAW_DOOR_1;
3658       BackToFront();
3659
3660       Delay(20);
3661     }
3662
3663     game_mm.energy_left = MAX_LASER_ENERGY;
3664     Feld[ELX][ELY] = EL_FUEL_EMPTY;
3665     DrawField_MM(ELX, ELY);
3666
3667     DrawLaser(0, DL_LASER_ENABLED);
3668
3669     return;
3670   }
3671
3672   return;
3673 }
3674
3675 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3676 {
3677   ClickElement(action.lx, action.ly, action.button);
3678
3679   GameActions_MM_Ext(action, warp_mode);
3680 }
3681
3682 void MovePacMen()
3683 {
3684   static int p = -1;
3685   int mx, my, ox, oy, nx, ny;
3686   int element;
3687   int l;
3688
3689   if (++p >= game_mm.num_pacman)
3690     p = 0;
3691
3692   game_mm.pacman[p].dir--;
3693
3694   for (l = 1; l < 5; l++)
3695   {
3696     game_mm.pacman[p].dir++;
3697
3698     if (game_mm.pacman[p].dir > 4)
3699       game_mm.pacman[p].dir = 1;
3700
3701     if (game_mm.pacman[p].dir % 2)
3702     {
3703       mx = 0;
3704       my = game_mm.pacman[p].dir - 2;
3705     }
3706     else
3707     {
3708       my = 0;
3709       mx = 3 - game_mm.pacman[p].dir;
3710     }
3711
3712     ox = game_mm.pacman[p].x;
3713     oy = game_mm.pacman[p].y;
3714     nx = ox + mx;
3715     ny = oy + my;
3716     element = Feld[nx][ny];
3717
3718     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3719       continue;
3720
3721     if (!IS_EATABLE4PACMAN(element))
3722       continue;
3723
3724     if (ObjHit(nx, ny, HIT_POS_CENTER))
3725       continue;
3726
3727     Feld[ox][oy] = EL_EMPTY;
3728     Feld[nx][ny] =
3729       EL_PACMAN_RIGHT - 1 +
3730       (game_mm.pacman[p].dir - 1 +
3731        (game_mm.pacman[p].dir % 2) * 2);
3732
3733     game_mm.pacman[p].x = nx;
3734     game_mm.pacman[p].y = ny;
3735
3736     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3737
3738     if (element != EL_EMPTY)
3739     {
3740       int graphic = el2gfx(Feld[nx][ny]);
3741       Bitmap *bitmap;
3742       int src_x, src_y;
3743       int i;
3744
3745       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3746
3747       CT = FrameCounter;
3748       ox = SX + ox * TILEX;
3749       oy = SY + oy * TILEY;
3750
3751       for (i = 1; i < 33; i += 2)
3752         BlitBitmap(bitmap, window,
3753                    src_x, src_y, TILEX, TILEY,
3754                    ox + i * mx, oy + i * my);
3755       Ct = Ct + FrameCounter - CT;
3756     }
3757
3758     DrawField_MM(nx, ny);
3759     BackToFront();
3760
3761     if (!laser.fuse_off)
3762     {
3763       DrawLaser(0, DL_LASER_ENABLED);
3764
3765       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3766       {
3767         AddDamagedField(nx, ny);
3768
3769         laser.damage[laser.num_damages - 1].edge = 0;
3770       }
3771     }
3772
3773     if (element == EL_BOMB)
3774       DeletePacMan(nx, ny);
3775
3776     if (IS_WALL_AMOEBA(element) &&
3777         (LX + 2 * XS) / TILEX == nx &&
3778         (LY + 2 * YS) / TILEY == ny)
3779     {
3780       laser.num_edges--;
3781       ScanLaser();
3782     }
3783
3784     break;
3785   }
3786 }
3787
3788 void GameWon_MM()
3789 {
3790   int hi_pos;
3791   boolean raise_level = FALSE;
3792
3793 #if 0
3794   if (local_player->MovPos)
3795     return;
3796
3797   local_player->LevelSolved = FALSE;
3798 #endif
3799
3800   if (game_mm.energy_left)
3801   {
3802     if (setup.sound_loops)
3803       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3804                    SND_CTRL_PLAY_LOOP);
3805
3806     while (game_mm.energy_left > 0)
3807     {
3808       if (!setup.sound_loops)
3809         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3810
3811       /*
3812       if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3813         RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3814       */
3815
3816       RaiseScore_MM(5);
3817
3818       game_mm.energy_left--;
3819       if (game_mm.energy_left >= 0)
3820       {
3821 #if 0
3822         BlitBitmap(pix[PIX_DOOR], drawto,
3823                    DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3824                    ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3825                    DX_ENERGY, DY_ENERGY);
3826 #endif
3827         redraw_mask |= REDRAW_DOOR_1;
3828       }
3829
3830       BackToFront();
3831       Delay(10);
3832     }
3833
3834     if (setup.sound_loops)
3835       StopSound(SND_SIRR);
3836   }
3837   else if (native_mm_level.time == 0)           /* level without time limit */
3838   {
3839     if (setup.sound_loops)
3840       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3841                    SND_CTRL_PLAY_LOOP);
3842
3843     while (TimePlayed < 999)
3844     {
3845       if (!setup.sound_loops)
3846         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3847       if (TimePlayed < 999 && !(TimePlayed % 10))
3848         RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3849       if (TimePlayed < 900 && !(TimePlayed % 10))
3850         TimePlayed += 10;
3851       else
3852         TimePlayed++;
3853
3854       /*
3855       DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3856       */
3857
3858       BackToFront();
3859       Delay(10);
3860     }
3861
3862     if (setup.sound_loops)
3863       StopSound(SND_SIRR);
3864   }
3865
3866 #if 0
3867   FadeSounds();
3868 #endif
3869
3870   CloseDoor(DOOR_CLOSE_1);
3871
3872   Request("Level solved !", REQ_CONFIRM);
3873
3874   if (level_nr == leveldir_current->handicap_level)
3875   {
3876     leveldir_current->handicap_level++;
3877     SaveLevelSetup_SeriesInfo();
3878   }
3879
3880   if (level_editor_test_game)
3881     game_mm.score = -1;         /* no highscore when playing from editor */
3882   else if (level_nr < leveldir_current->last_level)
3883     raise_level = TRUE;         /* advance to next level */
3884
3885   if ((hi_pos = NewHiScore_MM()) >= 0)
3886   {
3887     game_status = HALLOFFAME;
3888
3889     // DrawHallOfFame(hi_pos);
3890
3891     if (raise_level)
3892       level_nr++;
3893   }
3894   else
3895   {
3896     game_status = MAINMENU;
3897
3898     if (raise_level)
3899       level_nr++;
3900
3901     // DrawMainMenu();
3902   }
3903
3904   BackToFront();
3905 }
3906
3907 int NewHiScore_MM()
3908 {
3909   int k, l;
3910   int position = -1;
3911
3912   // LoadScore(level_nr);
3913
3914   if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3915       game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3916     return -1;
3917
3918   for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3919   {
3920     if (game_mm.score > highscore[k].Score)
3921     {
3922       /* player has made it to the hall of fame */
3923
3924       if (k < MAX_SCORE_ENTRIES - 1)
3925       {
3926         int m = MAX_SCORE_ENTRIES - 1;
3927
3928 #ifdef ONE_PER_NAME
3929         for (l = k; l < MAX_SCORE_ENTRIES; l++)
3930           if (!strcmp(setup.player_name, highscore[l].Name))
3931             m = l;
3932         if (m == k)     /* player's new highscore overwrites his old one */
3933           goto put_into_list;
3934 #endif
3935
3936         for (l = m; l>k; l--)
3937         {
3938           strcpy(highscore[l].Name, highscore[l - 1].Name);
3939           highscore[l].Score = highscore[l - 1].Score;
3940         }
3941       }
3942
3943 #ifdef ONE_PER_NAME
3944       put_into_list:
3945 #endif
3946       strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3947       highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3948       highscore[k].Score = game_mm.score;
3949       position = k;
3950
3951       break;
3952     }
3953
3954 #ifdef ONE_PER_NAME
3955     else if (!strncmp(setup.player_name, highscore[k].Name,
3956                       MAX_PLAYER_NAME_LEN))
3957       break;    /* player already there with a higher score */
3958 #endif
3959
3960   }
3961
3962   // if (position >= 0)
3963   //   SaveScore(level_nr);
3964
3965   return position;
3966 }
3967
3968 static void InitMovingField_MM(int x, int y, int direction)
3969 {
3970   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3971   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3972
3973   MovDir[x][y] = direction;
3974   MovDir[newx][newy] = direction;
3975
3976   if (Feld[newx][newy] == EL_EMPTY)
3977     Feld[newx][newy] = EL_BLOCKED;
3978 }
3979
3980 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3981 {
3982   int direction = MovDir[x][y];
3983   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3984   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3985
3986   *goes_to_x = newx;
3987   *goes_to_y = newy;
3988 }
3989
3990 static void Blocked2Moving_MM(int x, int y,
3991                               int *comes_from_x, int *comes_from_y)
3992 {
3993   int oldx = x, oldy = y;
3994   int direction = MovDir[x][y];
3995
3996   if (direction == MV_LEFT)
3997     oldx++;
3998   else if (direction == MV_RIGHT)
3999     oldx--;
4000   else if (direction == MV_UP)
4001     oldy++;
4002   else if (direction == MV_DOWN)
4003     oldy--;
4004
4005   *comes_from_x = oldx;
4006   *comes_from_y = oldy;
4007 }
4008
4009 static int MovingOrBlocked2Element_MM(int x, int y)
4010 {
4011   int element = Feld[x][y];
4012
4013   if (element == EL_BLOCKED)
4014   {
4015     int oldx, oldy;
4016
4017     Blocked2Moving_MM(x, y, &oldx, &oldy);
4018
4019     return Feld[oldx][oldy];
4020   }
4021
4022   return element;
4023 }
4024
4025 #if 0
4026 static void RemoveField(int x, int y)
4027 {
4028   Feld[x][y] = EL_EMPTY;
4029   MovPos[x][y] = 0;
4030   MovDir[x][y] = 0;
4031   MovDelay[x][y] = 0;
4032 }
4033 #endif
4034
4035 static void RemoveMovingField_MM(int x, int y)
4036 {
4037   int oldx = x, oldy = y, newx = x, newy = y;
4038
4039   if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4040     return;
4041
4042   if (IS_MOVING(x, y))
4043   {
4044     Moving2Blocked_MM(x, y, &newx, &newy);
4045     if (Feld[newx][newy] != EL_BLOCKED)
4046       return;
4047   }
4048   else if (Feld[x][y] == EL_BLOCKED)
4049   {
4050     Blocked2Moving_MM(x, y, &oldx, &oldy);
4051     if (!IS_MOVING(oldx, oldy))
4052       return;
4053   }
4054
4055   Feld[oldx][oldy] = EL_EMPTY;
4056   Feld[newx][newy] = EL_EMPTY;
4057   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4058   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4059
4060   DrawLevelField_MM(oldx, oldy);
4061   DrawLevelField_MM(newx, newy);
4062 }
4063
4064 void PlaySoundLevel(int x, int y, int sound_nr)
4065 {
4066   int sx = SCREENX(x), sy = SCREENY(y);
4067   int volume, stereo;
4068   int silence_distance = 8;
4069
4070   if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4071       (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4072     return;
4073
4074   if (!IN_LEV_FIELD(x, y) ||
4075       sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4076       sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4077     return;
4078
4079   volume = SOUND_MAX_VOLUME;
4080
4081 #ifndef MSDOS
4082   stereo = (sx - SCR_FIELDX/2) * 12;
4083 #else
4084   stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4085   if (stereo > SOUND_MAX_RIGHT)
4086     stereo = SOUND_MAX_RIGHT;
4087   if (stereo < SOUND_MAX_LEFT)
4088     stereo = SOUND_MAX_LEFT;
4089 #endif
4090
4091   if (!IN_SCR_FIELD(sx, sy))
4092   {
4093     int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4094     int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4095
4096     volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4097   }
4098
4099   PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4100 }
4101
4102 static void RaiseScore_MM(int value)
4103 {
4104   game_mm.score += value;
4105
4106 #if 0
4107   DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4108            FONT_TEXT_2);
4109 #endif
4110 }
4111
4112 void RaiseScoreElement_MM(int element)
4113 {
4114   switch(element)
4115   {
4116     case EL_PACMAN:
4117       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4118       break;
4119
4120     case EL_KEY:
4121       RaiseScore_MM(native_mm_level.score[SC_KEY]);
4122       break;
4123
4124     default:
4125       break;
4126   }
4127 }