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