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