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