fixed bug that prevents playing levels without time limit 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   ResetFrameCounter(&energy_delay);
728 }
729
730 static void FadeOutLaser(void)
731 {
732   int i;
733
734   for (i = 15; i >= 0; i--)
735   {
736     SetLaserColor(0x11 * i);
737
738     DrawLaser(0, DL_LASER_ENABLED);
739
740     BackToFront();
741     Delay_WithScreenUpdates(50);
742   }
743
744   DrawLaser(0, DL_LASER_DISABLED);
745
746   StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
747 }
748
749 static void GameOver_MM(int game_over_cause)
750 {
751   // do not handle game over if request dialog is already active
752   if (game.request_active)
753     return;
754
755   game_mm.game_over = TRUE;
756   game_mm.game_over_cause = game_over_cause;
757
758   if (setup.ask_on_game_over)
759     game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
760                                  "Bomb killed Mc Duffin! Play it again?" :
761                                  game_over_cause == GAME_OVER_NO_ENERGY ?
762                                  "Out of magic energy! Play it again?" :
763                                  game_over_cause == GAME_OVER_OVERLOADED ?
764                                  "Magic spell hit Mc Duffin! Play it again?" :
765                                  NULL);
766
767   SetTileCursorActive(FALSE);
768 }
769
770 void AddLaserEdge(int lx, int ly)
771 {
772   int clx = dSX + lx;
773   int cly = dSY + ly;
774
775   if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
776   {
777     Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
778
779     return;
780   }
781
782   laser.edge[laser.num_edges].x = cSX2 + lx;
783   laser.edge[laser.num_edges].y = cSY2 + ly;
784   laser.num_edges++;
785
786   laser.redraw = TRUE;
787 }
788
789 void AddDamagedField(int ex, int ey)
790 {
791   laser.damage[laser.num_damages].is_mirror = FALSE;
792   laser.damage[laser.num_damages].angle = laser.current_angle;
793   laser.damage[laser.num_damages].edge = laser.num_edges;
794   laser.damage[laser.num_damages].x = ex;
795   laser.damage[laser.num_damages].y = ey;
796   laser.num_damages++;
797 }
798
799 static boolean StepBehind(void)
800 {
801   if (laser.num_edges)
802   {
803     int x = LX - XS;
804     int y = LY - YS;
805     int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
806     int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
807
808     return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
809   }
810
811   return FALSE;
812 }
813
814 static int getMaskFromElement(int element)
815 {
816   if (IS_GRID(element))
817     return IMG_MM_MASK_GRID_1 + get_element_phase(element);
818   else if (IS_MCDUFFIN(element))
819     return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
820   else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
821     return IMG_MM_MASK_RECTANGLE;
822   else
823     return IMG_MM_MASK_CIRCLE;
824 }
825
826 static int ScanPixel(void)
827 {
828   int hit_mask = 0;
829
830 #if 0
831   Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
832         LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
833 #endif
834
835   // follow laser beam until it hits something (at least the screen border)
836   while (hit_mask == HIT_MASK_NO_HIT)
837   {
838     int i;
839
840 #if 0
841     // for safety
842     if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
843         SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
844     {
845       Debug("game:mm:ScanPixel", "touched screen border!");
846
847       return HIT_MASK_ALL;
848     }
849 #endif
850
851     for (i = 0; i < 4; i++)
852     {
853       int px = LX + (i % 2) * 2;
854       int py = LY + (i / 2) * 2;
855       int dx = px % TILEX;
856       int dy = py % TILEY;
857       int lx = (px + TILEX) / TILEX - 1;  // ...+TILEX...-1 to get correct
858       int ly = (py + TILEY) / TILEY - 1;  // negative values!
859       Pixel pixel;
860
861       if (IN_LEV_FIELD(lx, ly))
862       {
863         int element = Tile[lx][ly];
864
865         if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
866         {
867           pixel = 0;
868         }
869         else if (IS_WALL(element) || IS_WALL_CHANGING(element))
870         {
871           int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
872
873           pixel = ((element & (1 << pos)) ? 1 : 0);
874         }
875         else
876         {
877           int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
878
879           pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
880         }
881       }
882       else
883       {
884         pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
885                  cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
886       }
887
888       if ((Sign[laser.current_angle] & (1 << i)) && pixel)
889         hit_mask |= (1 << i);
890     }
891
892     if (hit_mask == HIT_MASK_NO_HIT)
893     {
894       // hit nothing -- go on with another step
895       LX += XS;
896       LY += YS;
897     }
898   }
899
900   return hit_mask;
901 }
902
903 void ScanLaser(void)
904 {
905   int element;
906   int end = 0, rf = laser.num_edges;
907
908   // do not scan laser again after the game was lost for whatever reason
909   if (game_mm.game_over)
910     return;
911
912   laser.overloaded = FALSE;
913   laser.stops_inside_element = FALSE;
914
915   DrawLaser(0, DL_LASER_ENABLED);
916
917 #if 0
918   Debug("game:mm:ScanLaser",
919         "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
920         LX, LY, XS, YS);
921 #endif
922
923   while (1)
924   {
925     int hit_mask;
926
927     if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
928     {
929       end = 1;
930       laser.overloaded = TRUE;
931
932       break;
933     }
934
935     hit_mask = ScanPixel();
936
937 #if 0
938     Debug("game:mm:ScanLaser",
939           "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
940           LX, LY, XS, YS);
941 #endif
942
943     // hit something -- check out what it was
944     ELX = (LX + XS) / TILEX;
945     ELY = (LY + YS) / TILEY;
946
947 #if 0
948     Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
949           hit_mask, LX, LY, ELX, ELY);
950 #endif
951
952     if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
953     {
954       element = EL_EMPTY;
955       laser.dest_element = element;
956
957       break;
958     }
959
960     if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
961     {
962       /* we have hit the top-right and bottom-left element --
963          choose the bottom-left one */
964       /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
965          ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
966          THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
967       ELX = (LX - 2) / TILEX;
968       ELY = (LY + 2) / TILEY;
969     }
970
971     if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
972     {
973       /* we have hit the top-left and bottom-right element --
974          choose the top-left one */
975       // !!! SEE ABOVE !!!
976       ELX = (LX - 2) / TILEX;
977       ELY = (LY - 2) / TILEY;
978     }
979
980 #if 0
981     Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
982           hit_mask, LX, LY, ELX, ELY);
983 #endif
984
985     element = Tile[ELX][ELY];
986     laser.dest_element = element;
987
988 #if 0
989     Debug("game:mm:ScanLaser",
990           "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
991           element, ELX, ELY,
992           LX, LY,
993           LX % TILEX, LY % TILEY,
994           hit_mask);
995 #endif
996
997 #if 0
998     if (!IN_LEV_FIELD(ELX, ELY))
999       Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1000             ELX, ELY, element);
1001 #endif
1002
1003     if (element == EL_EMPTY)
1004     {
1005       if (!HitOnlyAnEdge(hit_mask))
1006         break;
1007     }
1008     else if (element == EL_FUSE_ON)
1009     {
1010       if (HitPolarizer(element, hit_mask))
1011         break;
1012     }
1013     else if (IS_GRID(element) || IS_DF_GRID(element))
1014     {
1015       if (HitPolarizer(element, hit_mask))
1016         break;
1017     }
1018     else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1019              element == EL_GATE_STONE || element == EL_GATE_WOOD)
1020     {
1021       if (HitBlock(element, hit_mask))
1022       {
1023         rf = 1;
1024
1025         break;
1026       }
1027     }
1028     else if (IS_MCDUFFIN(element))
1029     {
1030       if (HitLaserSource(element, hit_mask))
1031         break;
1032     }
1033     else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1034              IS_RECEIVER(element))
1035     {
1036       if (HitLaserDestination(element, hit_mask))
1037         break;
1038     }
1039     else if (IS_WALL(element))
1040     {
1041       if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1042       {
1043         if (HitReflectingWalls(element, hit_mask))
1044           break;
1045       }
1046       else
1047       {
1048         if (HitAbsorbingWalls(element, hit_mask))
1049           break;
1050       }
1051     }
1052     else
1053     {
1054       if (HitElement(element, hit_mask))
1055         break;
1056     }
1057
1058     if (rf)
1059       DrawLaser(rf - 1, DL_LASER_ENABLED);
1060     rf = laser.num_edges;
1061   }
1062
1063 #if 0
1064   if (laser.dest_element != Tile[ELX][ELY])
1065   {
1066     Debug("game:mm:ScanLaser",
1067           "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1068           laser.dest_element, Tile[ELX][ELY]);
1069   }
1070 #endif
1071
1072   if (!end && !laser.stops_inside_element && !StepBehind())
1073   {
1074 #if 0
1075     Debug("game:mm:ScanLaser", "Go one step back");
1076 #endif
1077
1078     LX -= XS;
1079     LY -= YS;
1080
1081     AddLaserEdge(LX, LY);
1082   }
1083
1084   if (rf)
1085     DrawLaser(rf - 1, DL_LASER_ENABLED);
1086
1087   Ct = CT = FrameCounter;
1088
1089 #if 0
1090     if (!IN_LEV_FIELD(ELX, ELY))
1091       Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1092 #endif
1093 }
1094
1095 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1096 {
1097   int element;
1098   int elx, ely;
1099
1100 #if 0
1101   Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1102         start_edge, num_edges, mode);
1103 #endif
1104
1105   if (start_edge < 0)
1106   {
1107     Warn("DrawLaserExt: start_edge < 0");
1108
1109     return;
1110   }
1111
1112   if (num_edges < 0)
1113   {
1114     Warn("DrawLaserExt: num_edges < 0");
1115
1116     return;
1117   }
1118
1119 #if 0
1120   if (mode == DL_LASER_DISABLED)
1121   {
1122     Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1123   }
1124 #endif
1125
1126   // now draw the laser to the backbuffer and (if enabled) to the screen
1127   DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1128
1129   redraw_mask |= REDRAW_FIELD;
1130
1131   if (mode == DL_LASER_ENABLED)
1132     return;
1133
1134   // after the laser was deleted, the "damaged" graphics must be restored
1135   if (laser.num_damages)
1136   {
1137     int damage_start = 0;
1138     int i;
1139
1140     // determine the starting edge, from which graphics need to be restored
1141     if (start_edge > 0)
1142     {
1143       for (i = 0; i < laser.num_damages; i++)
1144       {
1145         if (laser.damage[i].edge == start_edge + 1)
1146         {
1147           damage_start = i;
1148
1149           break;
1150         }
1151       }
1152     }
1153
1154     // restore graphics from this starting edge to the end of damage list
1155     for (i = damage_start; i < laser.num_damages; i++)
1156     {
1157       int lx = laser.damage[i].x;
1158       int ly = laser.damage[i].y;
1159       int element = Tile[lx][ly];
1160
1161       if (Hit[lx][ly] == laser.damage[i].edge)
1162         if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1163                i == damage_start))
1164           Hit[lx][ly] = 0;
1165       if (Box[lx][ly] == laser.damage[i].edge)
1166         Box[lx][ly] = 0;
1167
1168       if (IS_DRAWABLE(element))
1169         DrawField_MM(lx, ly);
1170     }
1171
1172     elx = laser.damage[damage_start].x;
1173     ely = laser.damage[damage_start].y;
1174     element = Tile[elx][ely];
1175
1176 #if 0
1177     if (IS_BEAMER(element))
1178     {
1179       int i;
1180
1181       for (i = 0; i < laser.num_beamers; i++)
1182         Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1183
1184       Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1185             mode, elx, ely, Hit[elx][ely], start_edge);
1186       Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1187             get_element_angle(element), laser.damage[damage_start].angle);
1188     }
1189 #endif
1190
1191     if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1192         laser.num_beamers > 0 &&
1193         start_edge == laser.beamer_edge[laser.num_beamers - 1])
1194     {
1195       // element is outgoing beamer
1196       laser.num_damages = damage_start + 1;
1197
1198       if (IS_BEAMER(element))
1199         laser.current_angle = get_element_angle(element);
1200     }
1201     else
1202     {
1203       // element is incoming beamer or other element
1204       laser.num_damages = damage_start;
1205       laser.current_angle = laser.damage[laser.num_damages].angle;
1206     }
1207   }
1208   else
1209   {
1210     // no damages but McDuffin himself (who needs to be redrawn anyway)
1211
1212     elx = laser.start_edge.x;
1213     ely = laser.start_edge.y;
1214     element = Tile[elx][ely];
1215   }
1216
1217   laser.num_edges = start_edge + 1;
1218   if (start_edge == 0)
1219     laser.current_angle = laser.start_angle;
1220
1221   LX = laser.edge[start_edge].x - cSX2;
1222   LY = laser.edge[start_edge].y - cSY2;
1223   XS = 2 * Step[laser.current_angle].x;
1224   YS = 2 * Step[laser.current_angle].y;
1225
1226 #if 0
1227   Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1228         LX, LY, element);
1229 #endif
1230
1231   if (start_edge > 0)
1232   {
1233     if (IS_BEAMER(element) ||
1234         IS_FIBRE_OPTIC(element) ||
1235         IS_PACMAN(element) ||
1236         IS_POLAR(element) ||
1237         IS_POLAR_CROSS(element) ||
1238         element == EL_FUSE_ON)
1239     {
1240       int step_size;
1241
1242 #if 0
1243       Debug("game:mm:DrawLaserExt", "element == %d", element);
1244 #endif
1245
1246       if (IS_22_5_ANGLE(laser.current_angle))   // neither 90° nor 45° angle
1247         step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1248       else
1249         step_size = 8;
1250
1251       if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1252           ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1253            (laser.num_beamers == 0 ||
1254             start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1255       {
1256         // element is incoming beamer or other element
1257         step_size = -step_size;
1258         laser.num_edges--;
1259       }
1260
1261 #if 0
1262       if (IS_BEAMER(element))
1263         Debug("game:mm:DrawLaserExt",
1264               "start_edge == %d, laser.beamer_edge == %d",
1265               start_edge, laser.beamer_edge);
1266 #endif
1267
1268       LX += step_size * XS;
1269       LY += step_size * YS;
1270     }
1271     else if (element != EL_EMPTY)
1272     {
1273       LX -= 3 * XS;
1274       LY -= 3 * YS;
1275       laser.num_edges--;
1276     }
1277   }
1278
1279 #if 0
1280   Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1281         LX, LY, element);
1282 #endif
1283 }
1284
1285 void DrawLaser(int start_edge, int mode)
1286 {
1287   if (laser.num_edges - start_edge < 0)
1288   {
1289     Warn("DrawLaser: laser.num_edges - start_edge < 0");
1290
1291     return;
1292   }
1293
1294   // check if laser is interrupted by beamer element
1295   if (laser.num_beamers > 0 &&
1296       start_edge < laser.beamer_edge[laser.num_beamers - 1])
1297   {
1298     if (mode == DL_LASER_ENABLED)
1299     {
1300       int i;
1301       int tmp_start_edge = start_edge;
1302
1303       // draw laser segments forward from the start to the last beamer
1304       for (i = 0; i < laser.num_beamers; i++)
1305       {
1306         int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1307
1308         if (tmp_num_edges <= 0)
1309           continue;
1310
1311 #if 0
1312         Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1313               i, laser.beamer_edge[i], tmp_start_edge);
1314 #endif
1315
1316         DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1317
1318         tmp_start_edge = laser.beamer_edge[i];
1319       }
1320
1321       // draw last segment from last beamer to the end
1322       DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1323                    DL_LASER_ENABLED);
1324     }
1325     else
1326     {
1327       int i;
1328       int last_num_edges = laser.num_edges;
1329       int num_beamers = laser.num_beamers;
1330
1331       // delete laser segments backward from the end to the first beamer
1332       for (i = num_beamers - 1; i >= 0; i--)
1333       {
1334         int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1335
1336         if (laser.beamer_edge[i] - start_edge <= 0)
1337           break;
1338
1339         DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1340
1341         last_num_edges = laser.beamer_edge[i];
1342         laser.num_beamers--;
1343       }
1344
1345 #if 0
1346       if (last_num_edges - start_edge <= 0)
1347         Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1348               last_num_edges, start_edge);
1349 #endif
1350
1351       // special case when rotating first beamer: delete laser edge on beamer
1352       // (but do not start scanning on previous edge to prevent mirror sound)
1353       if (last_num_edges - start_edge == 1 && start_edge > 0)
1354         DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1355
1356       // delete first segment from start to the first beamer
1357       DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1358     }
1359   }
1360   else
1361   {
1362     DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1363   }
1364
1365   game_mm.laser_enabled = mode;
1366 }
1367
1368 void DrawLaser_MM(void)
1369 {
1370   DrawLaser(0, game_mm.laser_enabled);
1371 }
1372
1373 boolean HitElement(int element, int hit_mask)
1374 {
1375   if (HitOnlyAnEdge(hit_mask))
1376     return FALSE;
1377
1378   if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1379     element = MovingOrBlocked2Element_MM(ELX, ELY);
1380
1381 #if 0
1382   Debug("game:mm:HitElement", "(1): element == %d", element);
1383 #endif
1384
1385 #if 0
1386   if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1387     Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1388           element, ELX, ELY);
1389   else
1390     Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1391           element, ELX, ELY);
1392 #endif
1393
1394   AddDamagedField(ELX, ELY);
1395
1396   // this is more precise: check if laser would go through the center
1397   if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1398   {
1399     // skip the whole element before continuing the scan
1400     do
1401     {
1402       LX += XS;
1403       LY += YS;
1404     }
1405     while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1406
1407     if (LX/TILEX > ELX || LY/TILEY > ELY)
1408     {
1409       /* skipping scan positions to the right and down skips one scan
1410          position too much, because this is only the top left scan position
1411          of totally four scan positions (plus one to the right, one to the
1412          bottom and one to the bottom right) */
1413
1414       LX -= XS;
1415       LY -= YS;
1416     }
1417
1418     return FALSE;
1419   }
1420
1421 #if 0
1422   Debug("game:mm:HitElement", "(2): element == %d", element);
1423 #endif
1424
1425   if (LX + 5 * XS < 0 ||
1426       LY + 5 * YS < 0)
1427   {
1428     LX += 2 * XS;
1429     LY += 2 * YS;
1430
1431     return FALSE;
1432   }
1433
1434 #if 0
1435   Debug("game:mm:HitElement", "(3): element == %d", element);
1436 #endif
1437
1438   if (IS_POLAR(element) &&
1439       ((element - EL_POLAR_START) % 2 ||
1440        (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1441   {
1442     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1443
1444     laser.num_damages--;
1445
1446     return TRUE;
1447   }
1448
1449   if (IS_POLAR_CROSS(element) &&
1450       (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1451   {
1452     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1453
1454     laser.num_damages--;
1455
1456     return TRUE;
1457   }
1458
1459   if (!IS_BEAMER(element) &&
1460       !IS_FIBRE_OPTIC(element) &&
1461       !IS_GRID_WOOD(element) &&
1462       element != EL_FUEL_EMPTY)
1463   {
1464 #if 0
1465     if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1466       Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1467     else
1468       Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1469 #endif
1470
1471     LX = ELX * TILEX + 14;
1472     LY = ELY * TILEY + 14;
1473
1474     AddLaserEdge(LX, LY);
1475   }
1476
1477   if (IS_MIRROR(element) ||
1478       IS_MIRROR_FIXED(element) ||
1479       IS_POLAR(element) ||
1480       IS_POLAR_CROSS(element) ||
1481       IS_DF_MIRROR(element) ||
1482       IS_DF_MIRROR_AUTO(element) ||
1483       element == EL_PRISM ||
1484       element == EL_REFRACTOR)
1485   {
1486     int current_angle = laser.current_angle;
1487     int step_size;
1488
1489     laser.num_damages--;
1490
1491     AddDamagedField(ELX, ELY);
1492
1493     laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1494
1495     if (!Hit[ELX][ELY])
1496       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1497
1498     if (IS_MIRROR(element) ||
1499         IS_MIRROR_FIXED(element) ||
1500         IS_DF_MIRROR(element) ||
1501         IS_DF_MIRROR_AUTO(element))
1502       laser.current_angle = get_mirrored_angle(laser.current_angle,
1503                                                get_element_angle(element));
1504
1505     if (element == EL_PRISM || element == EL_REFRACTOR)
1506       laser.current_angle = RND(16);
1507
1508     XS = 2 * Step[laser.current_angle].x;
1509     YS = 2 * Step[laser.current_angle].y;
1510
1511     if (!IS_22_5_ANGLE(laser.current_angle))    // 90° or 45° angle
1512       step_size = 8;
1513     else
1514       step_size = 4;
1515
1516     LX += step_size * XS;
1517     LY += step_size * YS;
1518
1519     // draw sparkles on mirror
1520     if ((IS_MIRROR(element) ||
1521          IS_MIRROR_FIXED(element) ||
1522          element == EL_PRISM) &&
1523         current_angle != laser.current_angle)
1524     {
1525       MovDelay[ELX][ELY] = 11;          // start animation
1526     }
1527
1528     if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1529         current_angle != laser.current_angle)
1530       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1531
1532     laser.overloaded =
1533       (get_opposite_angle(laser.current_angle) ==
1534        laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1535
1536     return (laser.overloaded ? TRUE : FALSE);
1537   }
1538
1539   if (element == EL_FUEL_FULL)
1540   {
1541     laser.stops_inside_element = TRUE;
1542
1543     return TRUE;
1544   }
1545
1546   if (element == EL_BOMB || element == EL_MINE)
1547   {
1548     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1549
1550     if (element == EL_MINE)
1551       laser.overloaded = TRUE;
1552   }
1553
1554   if (element == EL_KETTLE ||
1555       element == EL_CELL ||
1556       element == EL_KEY ||
1557       element == EL_LIGHTBALL ||
1558       element == EL_PACMAN ||
1559       IS_PACMAN(element))
1560   {
1561     if (!IS_PACMAN(element))
1562       Bang_MM(ELX, ELY);
1563
1564     if (element == EL_PACMAN)
1565       Bang_MM(ELX, ELY);
1566
1567     if (element == EL_KETTLE || element == EL_CELL)
1568     {
1569       if (game_mm.kettles_still_needed > 0)
1570         game_mm.kettles_still_needed--;
1571
1572       game.snapshot.collected_item = TRUE;
1573
1574       if (game_mm.kettles_still_needed == 0)
1575       {
1576         CheckExitMM();
1577
1578         DrawLaser(0, DL_LASER_ENABLED);
1579       }
1580     }
1581     else if (element == EL_KEY)
1582     {
1583       game_mm.num_keys++;
1584     }
1585     else if (IS_PACMAN(element))
1586     {
1587       DeletePacMan(ELX, ELY);
1588     }
1589
1590     RaiseScoreElement_MM(element);
1591
1592     return FALSE;
1593   }
1594
1595   if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1596   {
1597     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1598
1599     DrawLaser(0, DL_LASER_ENABLED);
1600
1601     if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1602     {
1603       Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1604       game_mm.lights_still_needed--;
1605     }
1606     else
1607     {
1608       Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1609       game_mm.lights_still_needed++;
1610     }
1611
1612     DrawField_MM(ELX, ELY);
1613     DrawLaser(0, DL_LASER_ENABLED);
1614
1615     /*
1616     BackToFront();
1617     */
1618     laser.stops_inside_element = TRUE;
1619
1620     return TRUE;
1621   }
1622
1623 #if 0
1624   Debug("game:mm:HitElement", "(4): element == %d", element);
1625 #endif
1626
1627   if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1628       laser.num_beamers < MAX_NUM_BEAMERS &&
1629       laser.beamer[BEAMER_NR(element)][1].num)
1630   {
1631     int beamer_angle = get_element_angle(element);
1632     int beamer_nr = BEAMER_NR(element);
1633     int step_size;
1634
1635 #if 0
1636     Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1637 #endif
1638
1639     laser.num_damages--;
1640
1641     if (IS_FIBRE_OPTIC(element) ||
1642         laser.current_angle == get_opposite_angle(beamer_angle))
1643     {
1644       int pos;
1645
1646       LX = ELX * TILEX + 14;
1647       LY = ELY * TILEY + 14;
1648
1649       AddLaserEdge(LX, LY);
1650       AddDamagedField(ELX, ELY);
1651
1652       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1653
1654       if (!Hit[ELX][ELY])
1655         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1656
1657       pos = (ELX == laser.beamer[beamer_nr][0].x &&
1658              ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1659       ELX = laser.beamer[beamer_nr][pos].x;
1660       ELY = laser.beamer[beamer_nr][pos].y;
1661       LX = ELX * TILEX + 14;
1662       LY = ELY * TILEY + 14;
1663
1664       if (IS_BEAMER(element))
1665       {
1666         laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1667         XS = 2 * Step[laser.current_angle].x;
1668         YS = 2 * Step[laser.current_angle].y;
1669       }
1670
1671       laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1672
1673       AddLaserEdge(LX, LY);
1674       AddDamagedField(ELX, ELY);
1675
1676       laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1677
1678       if (!Hit[ELX][ELY])
1679         Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1680
1681       if (laser.current_angle == (laser.current_angle >> 1) << 1)
1682         step_size = 8;
1683       else
1684         step_size = 4;
1685
1686       LX += step_size * XS;
1687       LY += step_size * YS;
1688
1689       laser.num_beamers++;
1690
1691       return FALSE;
1692     }
1693   }
1694
1695   return TRUE;
1696 }
1697
1698 boolean HitOnlyAnEdge(int hit_mask)
1699 {
1700   // check if the laser hit only the edge of an element and, if so, go on
1701
1702 #if 0
1703   Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1704         LX, LY, hit_mask);
1705 #endif
1706
1707   if ((hit_mask == HIT_MASK_TOPLEFT ||
1708        hit_mask == HIT_MASK_TOPRIGHT ||
1709        hit_mask == HIT_MASK_BOTTOMLEFT ||
1710        hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1711       laser.current_angle % 4)                  // angle is not 90°
1712   {
1713     int dx, dy;
1714
1715     if (hit_mask == HIT_MASK_TOPLEFT)
1716     {
1717       dx = -1;
1718       dy = -1;
1719     }
1720     else if (hit_mask == HIT_MASK_TOPRIGHT)
1721     {
1722       dx = +1;
1723       dy = -1;
1724     }
1725     else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1726     {
1727       dx = -1;
1728       dy = +1;
1729     }
1730     else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1731     {
1732       dx = +1;
1733       dy = +1;
1734     }
1735
1736     AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1737
1738     LX += XS;
1739     LY += YS;
1740
1741 #if 0
1742     Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1743 #endif
1744
1745     return TRUE;
1746   }
1747
1748 #if 0
1749   Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1750 #endif
1751
1752   return FALSE;
1753 }
1754
1755 boolean HitPolarizer(int element, int hit_mask)
1756 {
1757   if (HitOnlyAnEdge(hit_mask))
1758     return FALSE;
1759
1760   if (IS_DF_GRID(element))
1761   {
1762     int grid_angle = get_element_angle(element);
1763
1764 #if 0
1765     Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1766           grid_angle, laser.current_angle);
1767 #endif
1768
1769     AddLaserEdge(LX, LY);
1770     AddDamagedField(ELX, ELY);
1771
1772     if (!Hit[ELX][ELY])
1773       Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1774
1775     if (laser.current_angle == grid_angle ||
1776         laser.current_angle == get_opposite_angle(grid_angle))
1777     {
1778       // skip the whole element before continuing the scan
1779       do
1780       {
1781         LX += XS;
1782         LY += YS;
1783       }
1784       while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1785
1786       if (LX/TILEX > ELX || LY/TILEY > ELY)
1787       {
1788         /* skipping scan positions to the right and down skips one scan
1789            position too much, because this is only the top left scan position
1790            of totally four scan positions (plus one to the right, one to the
1791            bottom and one to the bottom right) */
1792
1793         LX -= XS;
1794         LY -= YS;
1795       }
1796
1797       AddLaserEdge(LX, LY);
1798
1799       LX += XS;
1800       LY += YS;
1801
1802 #if 0
1803       Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1804             LX, LY,
1805             LX / TILEX, LY / TILEY,
1806             LX % TILEX, LY % TILEY);
1807 #endif
1808
1809       return FALSE;
1810     }
1811     else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1812     {
1813       return HitReflectingWalls(element, hit_mask);
1814     }
1815     else
1816     {
1817       return HitAbsorbingWalls(element, hit_mask);
1818     }
1819   }
1820   else if (IS_GRID_STEEL(element))
1821   {
1822     return HitReflectingWalls(element, hit_mask);
1823   }
1824   else  // IS_GRID_WOOD
1825   {
1826     return HitAbsorbingWalls(element, hit_mask);
1827   }
1828
1829   return TRUE;
1830 }
1831
1832 boolean HitBlock(int element, int hit_mask)
1833 {
1834   boolean check = FALSE;
1835
1836   if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1837       game_mm.num_keys == 0)
1838     check = TRUE;
1839
1840   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1841   {
1842     int i, x, y;
1843     int ex = ELX * TILEX + 14;
1844     int ey = ELY * TILEY + 14;
1845
1846     check = TRUE;
1847
1848     for (i = 1; i < 32; i++)
1849     {
1850       x = LX + i * XS;
1851       y = LY + i * YS;
1852
1853       if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1854         check = FALSE;
1855     }
1856   }
1857
1858   if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1859     return HitAbsorbingWalls(element, hit_mask);
1860
1861   if (check)
1862   {
1863     AddLaserEdge(LX - XS, LY - YS);
1864     AddDamagedField(ELX, ELY);
1865
1866     if (!Box[ELX][ELY])
1867       Box[ELX][ELY] = laser.num_edges;
1868
1869     return HitReflectingWalls(element, hit_mask);
1870   }
1871
1872   if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1873   {
1874     int xs = XS / 2, ys = YS / 2;
1875     int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1876     int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1877
1878     if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1879         (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1880     {
1881       laser.overloaded = (element == EL_GATE_STONE);
1882
1883       return TRUE;
1884     }
1885
1886     if (ABS(xs) == 1 && ABS(ys) == 1 &&
1887         (hit_mask == HIT_MASK_TOP ||
1888          hit_mask == HIT_MASK_LEFT ||
1889          hit_mask == HIT_MASK_RIGHT ||
1890          hit_mask == HIT_MASK_BOTTOM))
1891       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1892                                   hit_mask == HIT_MASK_BOTTOM),
1893                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1894                                   hit_mask == HIT_MASK_RIGHT));
1895     AddLaserEdge(LX, LY);
1896
1897     Bang_MM(ELX, ELY);
1898
1899     game_mm.num_keys--;
1900
1901     if (element == EL_GATE_STONE && Box[ELX][ELY])
1902     {
1903       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1904       /*
1905       BackToFront();
1906       */
1907       ScanLaser();
1908
1909       return TRUE;
1910     }
1911
1912     return FALSE;
1913   }
1914
1915   if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1916   {
1917     int xs = XS / 2, ys = YS / 2;
1918     int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1919     int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1920
1921     if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1922         (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1923     {
1924       laser.overloaded = (element == EL_BLOCK_STONE);
1925
1926       return TRUE;
1927     }
1928
1929     if (ABS(xs) == 1 && ABS(ys) == 1 &&
1930         (hit_mask == HIT_MASK_TOP ||
1931          hit_mask == HIT_MASK_LEFT ||
1932          hit_mask == HIT_MASK_RIGHT ||
1933          hit_mask == HIT_MASK_BOTTOM))
1934       AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1935                                   hit_mask == HIT_MASK_BOTTOM),
1936                       ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1937                                   hit_mask == HIT_MASK_RIGHT));
1938     AddDamagedField(ELX, ELY);
1939
1940     LX = ELX * TILEX + 14;
1941     LY = ELY * TILEY + 14;
1942
1943     AddLaserEdge(LX, LY);
1944
1945     laser.stops_inside_element = TRUE;
1946
1947     return TRUE;
1948   }
1949
1950   return TRUE;
1951 }
1952
1953 boolean HitLaserSource(int element, int hit_mask)
1954 {
1955   if (HitOnlyAnEdge(hit_mask))
1956     return FALSE;
1957
1958   PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1959
1960   laser.overloaded = TRUE;
1961
1962   return TRUE;
1963 }
1964
1965 boolean HitLaserDestination(int element, int hit_mask)
1966 {
1967   if (HitOnlyAnEdge(hit_mask))
1968     return FALSE;
1969
1970   if (element != EL_EXIT_OPEN &&
1971       !(IS_RECEIVER(element) &&
1972         game_mm.kettles_still_needed == 0 &&
1973         laser.current_angle == get_opposite_angle(get_element_angle(element))))
1974   {
1975     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1976
1977     return TRUE;
1978   }
1979
1980   if (IS_RECEIVER(element) ||
1981       (IS_22_5_ANGLE(laser.current_angle) &&
1982        (ELX != (LX + 6 * XS) / TILEX ||
1983         ELY != (LY + 6 * YS) / TILEY ||
1984         LX + 6 * XS < 0 ||
1985         LY + 6 * YS < 0)))
1986   {
1987     LX -= XS;
1988     LY -= YS;
1989   }
1990   else
1991   {
1992     LX = ELX * TILEX + 14;
1993     LY = ELY * TILEY + 14;
1994
1995     laser.stops_inside_element = TRUE;
1996   }
1997
1998   AddLaserEdge(LX, LY);
1999   AddDamagedField(ELX, ELY);
2000
2001   if (game_mm.lights_still_needed == 0)
2002   {
2003     game_mm.level_solved = TRUE;
2004
2005     SetTileCursorActive(FALSE);
2006   }
2007
2008   return TRUE;
2009 }
2010
2011 boolean HitReflectingWalls(int element, int hit_mask)
2012 {
2013   // check if laser hits side of a wall with an angle that is not 90°
2014   if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2015                                             hit_mask == HIT_MASK_LEFT ||
2016                                             hit_mask == HIT_MASK_RIGHT ||
2017                                             hit_mask == HIT_MASK_BOTTOM))
2018   {
2019     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2020
2021     LX -= XS;
2022     LY -= YS;
2023
2024     if (!IS_DF_GRID(element))
2025       AddLaserEdge(LX, LY);
2026
2027     // check if laser hits wall with an angle of 45°
2028     if (!IS_22_5_ANGLE(laser.current_angle))
2029     {
2030       if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2031       {
2032         LX += 2 * XS;
2033         laser.current_angle = get_mirrored_angle(laser.current_angle,
2034                                                  ANG_MIRROR_0);
2035       }
2036       else      // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2037       {
2038         LY += 2 * YS;
2039         laser.current_angle = get_mirrored_angle(laser.current_angle,
2040                                                  ANG_MIRROR_90);
2041       }
2042
2043       AddLaserEdge(LX, LY);
2044
2045       XS = 2 * Step[laser.current_angle].x;
2046       YS = 2 * Step[laser.current_angle].y;
2047
2048       return FALSE;
2049     }
2050     else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2051     {
2052       laser.current_angle = get_mirrored_angle(laser.current_angle,
2053                                                ANG_MIRROR_0);
2054       if (ABS(XS) == 4)
2055       {
2056         LX += 2 * XS;
2057         if (!IS_DF_GRID(element))
2058           AddLaserEdge(LX, LY);
2059       }
2060       else
2061       {
2062         LX += XS;
2063         if (!IS_DF_GRID(element))
2064           AddLaserEdge(LX, LY + YS / 2);
2065
2066         LX += XS;
2067         if (!IS_DF_GRID(element))
2068           AddLaserEdge(LX, LY);
2069       }
2070
2071       YS = 2 * Step[laser.current_angle].y;
2072
2073       return FALSE;
2074     }
2075     else        // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2076     {
2077       laser.current_angle = get_mirrored_angle(laser.current_angle,
2078                                                ANG_MIRROR_90);
2079       if (ABS(YS) == 4)
2080       {
2081         LY += 2 * YS;
2082         if (!IS_DF_GRID(element))
2083           AddLaserEdge(LX, LY);
2084       }
2085       else
2086       {
2087         LY += YS;
2088         if (!IS_DF_GRID(element))
2089           AddLaserEdge(LX + XS / 2, LY);
2090
2091         LY += YS;
2092         if (!IS_DF_GRID(element))
2093           AddLaserEdge(LX, LY);
2094       }
2095
2096       XS = 2 * Step[laser.current_angle].x;
2097
2098       return FALSE;
2099     }
2100   }
2101
2102   // reflection at the edge of reflecting DF style wall
2103   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2104   {
2105     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2106          hit_mask == HIT_MASK_TOPRIGHT) ||
2107         ((laser.current_angle == 5 || laser.current_angle == 7) &&
2108          hit_mask == HIT_MASK_TOPLEFT) ||
2109         ((laser.current_angle == 9 || laser.current_angle == 11) &&
2110          hit_mask == HIT_MASK_BOTTOMLEFT) ||
2111         ((laser.current_angle == 13 || laser.current_angle == 15) &&
2112          hit_mask == HIT_MASK_BOTTOMRIGHT))
2113     {
2114       int mirror_angle =
2115         (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2116          ANG_MIRROR_135 : ANG_MIRROR_45);
2117
2118       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2119
2120       AddDamagedField(ELX, ELY);
2121       AddLaserEdge(LX, LY);
2122
2123       laser.current_angle = get_mirrored_angle(laser.current_angle,
2124                                                mirror_angle);
2125       XS = 8 / -XS;
2126       YS = 8 / -YS;
2127
2128       LX += XS;
2129       LY += YS;
2130
2131       AddLaserEdge(LX, LY);
2132
2133       return FALSE;
2134     }
2135   }
2136
2137   // reflection inside an edge of reflecting DF style wall
2138   if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2139   {
2140     if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2141          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2142         ((laser.current_angle == 5 || laser.current_angle == 7) &&
2143          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2144         ((laser.current_angle == 9 || laser.current_angle == 11) &&
2145          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2146         ((laser.current_angle == 13 || laser.current_angle == 15) &&
2147          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2148     {
2149       int mirror_angle =
2150         (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2151          hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2152          ANG_MIRROR_135 : ANG_MIRROR_45);
2153
2154       PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2155
2156       /*
2157       AddDamagedField(ELX, ELY);
2158       */
2159
2160       AddLaserEdge(LX - XS, LY - YS);
2161       AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2162                    LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2163
2164       laser.current_angle = get_mirrored_angle(laser.current_angle,
2165                                                mirror_angle);
2166       XS = 8 / -XS;
2167       YS = 8 / -YS;
2168
2169       LX += XS;
2170       LY += YS;
2171
2172       AddLaserEdge(LX, LY);
2173
2174       return FALSE;
2175     }
2176   }
2177
2178   // check if laser hits DF style wall with an angle of 90°
2179   if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2180   {
2181     if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2182          (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2183         (IS_VERT_ANGLE(laser.current_angle) &&
2184          (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2185     {
2186       // laser at last step touched nothing or the same side of the wall
2187       if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2188       {
2189         AddDamagedField(ELX, ELY);
2190
2191         LX += 8 * XS;
2192         LY += 8 * YS;
2193
2194         last_LX = LX;
2195         last_LY = LY;
2196         last_hit_mask = hit_mask;
2197
2198         return FALSE;
2199       }
2200     }
2201   }
2202
2203   if (!HitOnlyAnEdge(hit_mask))
2204   {
2205     laser.overloaded = TRUE;
2206
2207     return TRUE;
2208   }
2209
2210   return FALSE;
2211 }
2212
2213 boolean HitAbsorbingWalls(int element, int hit_mask)
2214 {
2215   if (HitOnlyAnEdge(hit_mask))
2216     return FALSE;
2217
2218   if (ABS(XS) == 4 &&
2219       (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2220   {
2221     AddLaserEdge(LX - XS, LY - YS);
2222
2223     LX = LX + XS / 2;
2224     LY = LY + YS;
2225   }
2226
2227   if (ABS(YS) == 4 &&
2228       (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2229   {
2230     AddLaserEdge(LX - XS, LY - YS);
2231
2232     LX = LX + XS;
2233     LY = LY + YS / 2;
2234   }
2235
2236   if (IS_WALL_WOOD(element) ||
2237       IS_DF_WALL_WOOD(element) ||
2238       IS_GRID_WOOD(element) ||
2239       IS_GRID_WOOD_FIXED(element) ||
2240       IS_GRID_WOOD_AUTO(element) ||
2241       element == EL_FUSE_ON ||
2242       element == EL_BLOCK_WOOD ||
2243       element == EL_GATE_WOOD)
2244   {
2245     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2246
2247     return TRUE;
2248   }
2249
2250   if (IS_WALL_ICE(element))
2251   {
2252     int mask;
2253
2254     mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1;    // Quadrant (horizontal)
2255     mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2;  // || (vertical)
2256
2257     // check if laser hits wall with an angle of 90°
2258     if (IS_90_ANGLE(laser.current_angle))
2259       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2260
2261     if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2262     {
2263       int i;
2264
2265       for (i = 0; i < 4; i++)
2266       {
2267         if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2268           mask = 15 - (8 >> i);
2269         else if (ABS(XS) == 4 &&
2270                  mask == (1 << i) &&
2271                  (XS > 0) == (i % 2) &&
2272                  (YS < 0) == (i / 2))
2273           mask = 3 + (i / 2) * 9;
2274         else if (ABS(YS) == 4 &&
2275                  mask == (1 << i) &&
2276                  (XS < 0) == (i % 2) &&
2277                  (YS > 0) == (i / 2))
2278           mask = 5 + (i % 2) * 5;
2279       }
2280     }
2281
2282     laser.wall_mask = mask;
2283   }
2284   else if (IS_WALL_AMOEBA(element))
2285   {
2286     int elx = (LX - 2 * XS) / TILEX;
2287     int ely = (LY - 2 * YS) / TILEY;
2288     int element2 = Tile[elx][ely];
2289     int mask;
2290
2291     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2292     {
2293       laser.dest_element = EL_EMPTY;
2294
2295       return TRUE;
2296     }
2297
2298     ELX = elx;
2299     ELY = ely;
2300
2301     mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2302     mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2303
2304     if (IS_90_ANGLE(laser.current_angle))
2305       mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2306
2307     laser.dest_element = element2 | EL_WALL_AMOEBA;
2308
2309     laser.wall_mask = mask;
2310   }
2311
2312   return TRUE;
2313 }
2314
2315 static void OpenExit(int x, int y)
2316 {
2317   int delay = 6;
2318
2319   if (!MovDelay[x][y])          // next animation frame
2320     MovDelay[x][y] = 4 * delay;
2321
2322   if (MovDelay[x][y])           // wait some time before next frame
2323   {
2324     int phase;
2325
2326     MovDelay[x][y]--;
2327     phase = MovDelay[x][y] / delay;
2328
2329     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2330       DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2331
2332     if (!MovDelay[x][y])
2333     {
2334       Tile[x][y] = EL_EXIT_OPEN;
2335       DrawField_MM(x, y);
2336     }
2337   }
2338 }
2339
2340 static void OpenSurpriseBall(int x, int y)
2341 {
2342   int delay = 2;
2343
2344   if (!MovDelay[x][y])          // next animation frame
2345     MovDelay[x][y] = 50 * delay;
2346
2347   if (MovDelay[x][y])           // wait some time before next frame
2348   {
2349     MovDelay[x][y]--;
2350
2351     if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2352     {
2353       Bitmap *bitmap;
2354       int graphic = el2gfx(Store[x][y]);
2355       int gx, gy;
2356       int dx = RND(26), dy = RND(26);
2357
2358       getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2359
2360       BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2361                  cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2362
2363       MarkTileDirty(x, y);
2364     }
2365
2366     if (!MovDelay[x][y])
2367     {
2368       Tile[x][y] = Store[x][y];
2369       Store[x][y] = 0;
2370       DrawField_MM(x, y);
2371
2372       ScanLaser();
2373     }
2374   }
2375 }
2376
2377 static void MeltIce(int x, int y)
2378 {
2379   int frames = 5;
2380   int delay = 5;
2381
2382   if (!MovDelay[x][y])          // next animation frame
2383     MovDelay[x][y] = frames * delay;
2384
2385   if (MovDelay[x][y])           // wait some time before next frame
2386   {
2387     int phase;
2388     int wall_mask = Store2[x][y];
2389     int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2390
2391     MovDelay[x][y]--;
2392     phase = frames - MovDelay[x][y] / delay - 1;
2393
2394     if (!MovDelay[x][y])
2395     {
2396       int i;
2397
2398       Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2399       Store[x][y] = Store2[x][y] = 0;
2400
2401       DrawWalls_MM(x, y, Tile[x][y]);
2402
2403       if (Tile[x][y] == EL_WALL_ICE)
2404         Tile[x][y] = EL_EMPTY;
2405
2406       for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2407         if (laser.damage[i].is_mirror)
2408           break;
2409
2410       if (i > 0)
2411         DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2412       else
2413         DrawLaser(0, DL_LASER_DISABLED);
2414
2415       ScanLaser();
2416     }
2417     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2418     {
2419       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2420
2421       laser.redraw = TRUE;
2422     }
2423   }
2424 }
2425
2426 static void GrowAmoeba(int x, int y)
2427 {
2428   int frames = 5;
2429   int delay = 1;
2430
2431   if (!MovDelay[x][y])          // next animation frame
2432     MovDelay[x][y] = frames * delay;
2433
2434   if (MovDelay[x][y])           // wait some time before next frame
2435   {
2436     int phase;
2437     int wall_mask = Store2[x][y];
2438     int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2439
2440     MovDelay[x][y]--;
2441     phase = MovDelay[x][y] / delay;
2442
2443     if (!MovDelay[x][y])
2444     {
2445       Tile[x][y] = real_element;
2446       Store[x][y] = Store2[x][y] = 0;
2447
2448       DrawWalls_MM(x, y, Tile[x][y]);
2449       DrawLaser(0, DL_LASER_ENABLED);
2450     }
2451     else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2452     {
2453       DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2454     }
2455   }
2456 }
2457
2458 static void DrawFieldAnimated_MM(int x, int y)
2459 {
2460   int element = Tile[x][y];
2461
2462   if (IS_BLOCKED(x, y))
2463     return;
2464
2465   DrawField_MM(x, y);
2466
2467   if (IS_MIRROR(element) ||
2468       IS_MIRROR_FIXED(element) ||
2469       element == EL_PRISM)
2470   {
2471     if (MovDelay[x][y] != 0)    // wait some time before next frame
2472     {
2473       MovDelay[x][y]--;
2474
2475       if (MovDelay[x][y] != 0)
2476       {
2477         int graphic = IMG_TWINKLE_WHITE;
2478         int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2479
2480         DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2481       }
2482     }
2483   }
2484
2485   laser.redraw = TRUE;
2486 }
2487
2488 static void Explode_MM(int x, int y, int phase, int mode)
2489 {
2490   int num_phase = 9, delay = 2;
2491   int last_phase = num_phase * delay;
2492   int half_phase = (num_phase / 2) * delay;
2493
2494   laser.redraw = TRUE;
2495
2496   if (phase == EX_PHASE_START)          // initialize 'Store[][]' field
2497   {
2498     int center_element = Tile[x][y];
2499
2500     if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2501     {
2502       // put moving element to center field (and let it explode there)
2503       center_element = MovingOrBlocked2Element_MM(x, y);
2504       RemoveMovingField_MM(x, y);
2505
2506       Tile[x][y] = center_element;
2507     }
2508
2509     if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2510       Store[x][y] = center_element;
2511     else
2512       Store[x][y] = EL_EMPTY;
2513
2514     Store2[x][y] = mode;
2515     Tile[x][y] = EL_EXPLODING_OPAQUE;
2516     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2517     Frame[x][y] = 1;
2518
2519     return;
2520   }
2521
2522   Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2523
2524   if (phase == half_phase)
2525   {
2526     Tile[x][y] = EL_EXPLODING_TRANSP;
2527
2528     if (x == ELX && y == ELY)
2529       ScanLaser();
2530   }
2531
2532   if (phase == last_phase)
2533   {
2534     if (Store[x][y] == EL_BOMB)
2535     {
2536       DrawLaser(0, DL_LASER_DISABLED);
2537       InitLaser();
2538
2539       Bang_MM(laser.start_edge.x, laser.start_edge.y);
2540       Store[x][y] = EL_EMPTY;
2541
2542       GameOver_MM(GAME_OVER_DELAYED);
2543
2544       laser.overloaded = FALSE;
2545     }
2546     else if (IS_MCDUFFIN(Store[x][y]))
2547     {
2548       Store[x][y] = EL_EMPTY;
2549
2550       GameOver_MM(GAME_OVER_BOMB);
2551     }
2552
2553     Tile[x][y] = Store[x][y];
2554     Store[x][y] = Store2[x][y] = 0;
2555     MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2556
2557     InitField(x, y);
2558     DrawField_MM(x, y);
2559   }
2560   else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2561   {
2562     int graphic = IMG_MM_DEFAULT_EXPLODING;
2563     int graphic_phase = (phase / delay - 1);
2564     Bitmap *bitmap;
2565     int src_x, src_y;
2566
2567     if (Store2[x][y] == EX_KETTLE)
2568     {
2569       if (graphic_phase < 3)
2570       {
2571         graphic = IMG_MM_KETTLE_EXPLODING;
2572       }
2573       else if (graphic_phase < 5)
2574       {
2575         graphic_phase += 3;
2576       }
2577       else
2578       {
2579         graphic = IMG_EMPTY;
2580         graphic_phase = 0;
2581       }
2582     }
2583     else if (Store2[x][y] == EX_SHORT)
2584     {
2585       if (graphic_phase < 4)
2586       {
2587         graphic_phase += 4;
2588       }
2589       else
2590       {
2591         graphic = IMG_EMPTY;
2592         graphic_phase = 0;
2593       }
2594     }
2595
2596     getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2597
2598     BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2599                cFX + x * TILEX, cFY + y * TILEY);
2600
2601     MarkTileDirty(x, y);
2602   }
2603 }
2604
2605 static void Bang_MM(int x, int y)
2606 {
2607   int element = Tile[x][y];
2608   int mode = EX_NORMAL;
2609
2610 #if 0
2611   DrawLaser(0, DL_LASER_ENABLED);
2612 #endif
2613
2614   switch (element)
2615   {
2616     case EL_KETTLE:
2617       mode = EX_KETTLE;
2618       break;
2619
2620     case EL_GATE_STONE:
2621     case EL_GATE_WOOD:
2622       mode = EX_SHORT;
2623       break;
2624
2625     default:
2626       mode = EX_NORMAL;
2627       break;
2628   }
2629
2630   if (IS_PACMAN(element))
2631     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2632   else if (element == EL_BOMB || IS_MCDUFFIN(element))
2633     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2634   else if (element == EL_KEY)
2635     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2636   else
2637     PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2638
2639   Explode_MM(x, y, EX_PHASE_START, mode);
2640 }
2641
2642 void TurnRound(int x, int y)
2643 {
2644   static struct
2645   {
2646     int x, y;
2647   } move_xy[] =
2648   {
2649     { 0, 0 },
2650     {-1, 0 },
2651     {+1, 0 },
2652     { 0, 0 },
2653     { 0, -1 },
2654     { 0, 0 }, { 0, 0 }, { 0, 0 },
2655     { 0, +1 }
2656   };
2657   static struct
2658   {
2659     int left, right, back;
2660   } turn[] =
2661   {
2662     { 0,        0,              0 },
2663     { MV_DOWN,  MV_UP,          MV_RIGHT },
2664     { MV_UP,    MV_DOWN,        MV_LEFT },
2665     { 0,        0,              0 },
2666     { MV_LEFT,  MV_RIGHT,       MV_DOWN },
2667     { 0,0,0 },  { 0,0,0 },      { 0,0,0 },
2668     { MV_RIGHT, MV_LEFT,        MV_UP }
2669   };
2670
2671   int element = Tile[x][y];
2672   int old_move_dir = MovDir[x][y];
2673   int right_dir = turn[old_move_dir].right;
2674   int back_dir = turn[old_move_dir].back;
2675   int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2676   int right_x = x + right_dx, right_y = y + right_dy;
2677
2678   if (element == EL_PACMAN)
2679   {
2680     boolean can_turn_right = FALSE;
2681
2682     if (IN_LEV_FIELD(right_x, right_y) &&
2683         IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2684       can_turn_right = TRUE;
2685
2686     if (can_turn_right)
2687       MovDir[x][y] = right_dir;
2688     else
2689       MovDir[x][y] = back_dir;
2690
2691     MovDelay[x][y] = 0;
2692   }
2693 }
2694
2695 static void StartMoving_MM(int x, int y)
2696 {
2697   int element = Tile[x][y];
2698
2699   if (Stop[x][y])
2700     return;
2701
2702   if (CAN_MOVE(element))
2703   {
2704     int newx, newy;
2705
2706     if (MovDelay[x][y])         // wait some time before next movement
2707     {
2708       MovDelay[x][y]--;
2709
2710       if (MovDelay[x][y])
2711         return;
2712     }
2713
2714     // now make next step
2715
2716     Moving2Blocked_MM(x, y, &newx, &newy);      // get next screen position
2717
2718     if (element == EL_PACMAN &&
2719         IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2720         !ObjHit(newx, newy, HIT_POS_CENTER))
2721     {
2722       Store[newx][newy] = Tile[newx][newy];
2723       Tile[newx][newy] = EL_EMPTY;
2724
2725       DrawField_MM(newx, newy);
2726     }
2727     else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2728              ObjHit(newx, newy, HIT_POS_CENTER))
2729     {
2730       // object was running against a wall
2731
2732       TurnRound(x, y);
2733
2734       return;
2735     }
2736
2737     InitMovingField_MM(x, y, MovDir[x][y]);
2738   }
2739
2740   if (MovDir[x][y])
2741     ContinueMoving_MM(x, y);
2742 }
2743
2744 static void ContinueMoving_MM(int x, int y)
2745 {
2746   int element = Tile[x][y];
2747   int direction = MovDir[x][y];
2748   int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2749   int dy = (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
2750   int horiz_move = (dx!=0);
2751   int newx = x + dx, newy = y + dy;
2752   int step = (horiz_move ? dx : dy) * TILEX / 8;
2753
2754   MovPos[x][y] += step;
2755
2756   if (ABS(MovPos[x][y]) >= TILEX)       // object reached its destination
2757   {
2758     Tile[x][y] = EL_EMPTY;
2759     Tile[newx][newy] = element;
2760
2761     MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2762     MovDelay[newx][newy] = 0;
2763
2764     if (!CAN_MOVE(element))
2765       MovDir[newx][newy] = 0;
2766
2767     DrawField_MM(x, y);
2768     DrawField_MM(newx, newy);
2769
2770     Stop[newx][newy] = TRUE;
2771
2772     if (element == EL_PACMAN)
2773     {
2774       if (Store[newx][newy] == EL_BOMB)
2775         Bang_MM(newx, newy);
2776
2777       if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2778           (LX + 2 * XS) / TILEX == newx &&
2779           (LY + 2 * YS) / TILEY == newy)
2780       {
2781         laser.num_edges--;
2782         ScanLaser();
2783       }
2784     }
2785   }
2786   else                          // still moving on
2787   {
2788     DrawField_MM(x, y);
2789   }
2790
2791   laser.redraw = TRUE;
2792 }
2793
2794 boolean ClickElement(int x, int y, int button)
2795 {
2796   static DelayCounter click_delay = { CLICK_DELAY };
2797   static boolean new_button = TRUE;
2798   boolean element_clicked = FALSE;
2799   int element;
2800
2801   if (button == -1)
2802   {
2803     // initialize static variables
2804     click_delay.count = 0;
2805     click_delay.value = CLICK_DELAY;
2806     new_button = TRUE;
2807
2808     return FALSE;
2809   }
2810
2811   // do not rotate objects hit by the laser after the game was solved
2812   if (game_mm.level_solved && Hit[x][y])
2813     return FALSE;
2814
2815   if (button == MB_RELEASED)
2816   {
2817     new_button = TRUE;
2818     click_delay.value = CLICK_DELAY;
2819
2820     // release eventually hold auto-rotating mirror
2821     RotateMirror(x, y, MB_RELEASED);
2822
2823     return FALSE;
2824   }
2825
2826   if (!FrameReached(&click_delay) && !new_button)
2827     return FALSE;
2828
2829   if (button == MB_MIDDLEBUTTON)        // middle button has no function
2830     return FALSE;
2831
2832   if (!IN_LEV_FIELD(x, y))
2833     return FALSE;
2834
2835   if (Tile[x][y] == EL_EMPTY)
2836     return FALSE;
2837
2838   element = Tile[x][y];
2839
2840   if (IS_MIRROR(element) ||
2841       IS_BEAMER(element) ||
2842       IS_POLAR(element) ||
2843       IS_POLAR_CROSS(element) ||
2844       IS_DF_MIRROR(element) ||
2845       IS_DF_MIRROR_AUTO(element))
2846   {
2847     RotateMirror(x, y, button);
2848
2849     element_clicked = TRUE;
2850   }
2851   else if (IS_MCDUFFIN(element))
2852   {
2853     if (!laser.fuse_off)
2854     {
2855       DrawLaser(0, DL_LASER_DISABLED);
2856
2857       /*
2858       BackToFront();
2859       */
2860     }
2861
2862     element = get_rotated_element(element, BUTTON_ROTATION(button));
2863     laser.start_angle = get_element_angle(element);
2864
2865     InitLaser();
2866
2867     Tile[x][y] = element;
2868     DrawField_MM(x, y);
2869
2870     /*
2871     BackToFront();
2872     */
2873
2874     if (!laser.fuse_off)
2875       ScanLaser();
2876
2877     element_clicked = TRUE;
2878   }
2879   else if (element == EL_FUSE_ON && laser.fuse_off)
2880   {
2881     if (x != laser.fuse_x || y != laser.fuse_y)
2882       return FALSE;
2883
2884     laser.fuse_off = FALSE;
2885     laser.fuse_x = laser.fuse_y = -1;
2886
2887     DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2888     ScanLaser();
2889
2890     element_clicked = TRUE;
2891   }
2892   else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2893   {
2894     laser.fuse_off = TRUE;
2895     laser.fuse_x = x;
2896     laser.fuse_y = y;
2897     laser.overloaded = FALSE;
2898
2899     DrawLaser(0, DL_LASER_DISABLED);
2900     DrawGraphic_MM(x, y, IMG_MM_FUSE);
2901
2902     element_clicked = TRUE;
2903   }
2904   else if (element == EL_LIGHTBALL)
2905   {
2906     Bang_MM(x, y);
2907     RaiseScoreElement_MM(element);
2908     DrawLaser(0, DL_LASER_ENABLED);
2909
2910     element_clicked = TRUE;
2911   }
2912
2913   click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2914   new_button = FALSE;
2915
2916   return element_clicked;
2917 }
2918
2919 void RotateMirror(int x, int y, int button)
2920 {
2921   if (button == MB_RELEASED)
2922   {
2923     // release eventually hold auto-rotating mirror
2924     hold_x = -1;
2925     hold_y = -1;
2926
2927     return;
2928   }
2929
2930   if (IS_MIRROR(Tile[x][y]) ||
2931       IS_POLAR_CROSS(Tile[x][y]) ||
2932       IS_POLAR(Tile[x][y]) ||
2933       IS_BEAMER(Tile[x][y]) ||
2934       IS_DF_MIRROR(Tile[x][y]) ||
2935       IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2936       IS_GRID_WOOD_AUTO(Tile[x][y]))
2937   {
2938     Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2939   }
2940   else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2941   {
2942     if (button == MB_LEFTBUTTON)
2943     {
2944       // left mouse button only for manual adjustment, no auto-rotating;
2945       // freeze mirror for until mouse button released
2946       hold_x = x;
2947       hold_y = y;
2948     }
2949     else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2950     {
2951       Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2952     }
2953   }
2954
2955   if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2956   {
2957     int edge = Hit[x][y];
2958
2959     DrawField_MM(x, y);
2960
2961     if (edge > 0)
2962     {
2963       DrawLaser(edge - 1, DL_LASER_DISABLED);
2964       ScanLaser();
2965     }
2966   }
2967   else if (ObjHit(x, y, HIT_POS_CENTER))
2968   {
2969     int edge = Hit[x][y];
2970
2971     if (edge == 0)
2972     {
2973       Warn("RotateMirror: inconsistent field Hit[][]!\n");
2974
2975       edge = 1;
2976     }
2977
2978     DrawLaser(edge - 1, DL_LASER_DISABLED);
2979     ScanLaser();
2980   }
2981   else
2982   {
2983     int check = 1;
2984
2985     if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2986       check = 2;
2987
2988     DrawField_MM(x, y);
2989
2990     if ((IS_BEAMER(Tile[x][y]) ||
2991          IS_POLAR(Tile[x][y]) ||
2992          IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2993     {
2994       check = 0;
2995
2996       if (IS_BEAMER(Tile[x][y]))
2997       {
2998 #if 0
2999         Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3000               LX, LY, laser.beamer_edge, laser.beamer[1].num);
3001 #endif
3002
3003 #if 0
3004         laser.num_edges--;
3005 #endif
3006       }
3007
3008       ScanLaser();
3009     }
3010
3011     if (check == 2)
3012       DrawLaser(0, DL_LASER_ENABLED);
3013   }
3014 }
3015
3016 static void AutoRotateMirrors(void)
3017 {
3018   int x, y;
3019
3020   if (!FrameReached(&rotate_delay))
3021     return;
3022
3023   for (x = 0; x < lev_fieldx; x++)
3024   {
3025     for (y = 0; y < lev_fieldy; y++)
3026     {
3027       int element = Tile[x][y];
3028
3029       // do not rotate objects hit by the laser after the game was solved
3030       if (game_mm.level_solved && Hit[x][y])
3031         continue;
3032
3033       if (IS_DF_MIRROR_AUTO(element) ||
3034           IS_GRID_WOOD_AUTO(element) ||
3035           IS_GRID_STEEL_AUTO(element) ||
3036           element == EL_REFRACTOR)
3037         RotateMirror(x, y, MB_RIGHTBUTTON);
3038     }
3039   }
3040 }
3041
3042 boolean ObjHit(int obx, int oby, int bits)
3043 {
3044   int i;
3045
3046   obx *= TILEX;
3047   oby *= TILEY;
3048
3049   if (bits & HIT_POS_CENTER)
3050   {
3051     if (CheckLaserPixel(cSX + obx + 15,
3052                         cSY + oby + 15))
3053       return TRUE;
3054   }
3055
3056   if (bits & HIT_POS_EDGE)
3057   {
3058     for (i = 0; i < 4; i++)
3059       if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3060                           cSY + oby + 31 * (i / 2)))
3061         return TRUE;
3062   }
3063
3064   if (bits & HIT_POS_BETWEEN)
3065   {
3066     for (i = 0; i < 4; i++)
3067       if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3068                           cSY + 4 + oby + 22 * (i / 2)))
3069         return TRUE;
3070   }
3071
3072   return FALSE;
3073 }
3074
3075 void DeletePacMan(int px, int py)
3076 {
3077   int i, j;
3078
3079   Bang_MM(px, py);
3080
3081   if (game_mm.num_pacman <= 1)
3082   {
3083     game_mm.num_pacman = 0;
3084     return;
3085   }
3086
3087   for (i = 0; i < game_mm.num_pacman; i++)
3088     if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3089       break;
3090
3091   game_mm.num_pacman--;
3092
3093   for (j = i; j < game_mm.num_pacman; j++)
3094   {
3095     game_mm.pacman[j].x   = game_mm.pacman[j + 1].x;
3096     game_mm.pacman[j].y   = game_mm.pacman[j + 1].y;
3097     game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3098   }
3099 }
3100
3101 void ColorCycling(void)
3102 {
3103   static int CC, Cc = 0;
3104
3105   static int color, old = 0xF00, new = 0x010, mult = 1;
3106   static unsigned short red, green, blue;
3107
3108   if (color_status == STATIC_COLORS)
3109     return;
3110
3111   CC = FrameCounter;
3112
3113   if (CC < Cc || CC > Cc + 2)
3114   {
3115     Cc = CC;
3116
3117     color = old + new * mult;
3118     if (mult > 0)
3119       mult++;
3120     else
3121       mult--;
3122
3123     if (ABS(mult) == 16)
3124     {
3125       mult =- mult / 16;
3126       old = color;
3127       new = new << 4;
3128
3129       if (new > 0x100)
3130         new = 0x001;
3131     }
3132
3133     red   = 0x0e00 * ((color & 0xF00) >> 8);
3134     green = 0x0e00 * ((color & 0x0F0) >> 4);
3135     blue  = 0x0e00 * ((color & 0x00F));
3136     SetRGB(pen_magicolor[0], red, green, blue);
3137
3138     red   = 0x1111 * ((color & 0xF00) >> 8);
3139     green = 0x1111 * ((color & 0x0F0) >> 4);
3140     blue  = 0x1111 * ((color & 0x00F));
3141     SetRGB(pen_magicolor[1], red, green, blue);
3142   }
3143 }
3144
3145 static void GameActions_MM_Ext(void)
3146 {
3147   int element;
3148   int x, y, i;
3149
3150   int r, d;
3151
3152   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3153     Stop[x][y] = FALSE;
3154
3155   for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3156   {
3157     element = Tile[x][y];
3158
3159     if (!IS_MOVING(x, y) && CAN_MOVE(element))
3160       StartMoving_MM(x, y);
3161     else if (IS_MOVING(x, y))
3162       ContinueMoving_MM(x, y);
3163     else if (IS_EXPLODING(element))
3164       Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3165     else if (element == EL_EXIT_OPENING)
3166       OpenExit(x, y);
3167     else if (element == EL_GRAY_BALL_OPENING)
3168       OpenSurpriseBall(x, y);
3169     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3170       MeltIce(x, y);
3171     else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3172       GrowAmoeba(x, y);
3173     else
3174       DrawFieldAnimated_MM(x, y);
3175   }
3176
3177   AutoRotateMirrors();
3178
3179 #if 1
3180   // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3181
3182   // redraw after Explode_MM() ...
3183   if (laser.redraw)
3184     DrawLaser(0, DL_LASER_ENABLED);
3185   laser.redraw = FALSE;
3186 #endif
3187
3188   CT = FrameCounter;
3189
3190   if (game_mm.num_pacman && FrameReached(&pacman_delay))
3191   {
3192     MovePacMen();
3193
3194     if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3195     {
3196       DrawLaser(0, DL_LASER_DISABLED);
3197       ScanLaser();
3198     }
3199   }
3200
3201   // skip all following game actions if game is over
3202   if (game_mm.game_over)
3203     return;
3204
3205   if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3206   {
3207     FadeOutLaser();
3208
3209     GameOver_MM(GAME_OVER_NO_ENERGY);
3210
3211     return;
3212   }
3213
3214   if (FrameReached(&energy_delay))
3215   {
3216     if (game_mm.energy_left > 0)
3217       game_mm.energy_left--;
3218
3219     // when out of energy, wait another frame to play "out of time" sound
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();
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 }