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