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