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