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