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