fixed bug with reduced initial 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 (FrameReached(&energy_delay))
3206   {
3207     if (game_mm.energy_left > 0)
3208     {
3209       game_mm.energy_left--;
3210
3211       redraw_mask |= REDRAW_DOOR_1;
3212     }
3213     else if (game.time_limit && !game_mm.game_over)
3214     {
3215       FadeOutLaser();
3216
3217       GameOver_MM(GAME_OVER_NO_ENERGY);
3218
3219       return;
3220     }
3221   }
3222
3223   element = laser.dest_element;
3224
3225 #if 0
3226   if (element != Tile[ELX][ELY])
3227   {
3228     Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3229           element, Tile[ELX][ELY]);
3230   }
3231 #endif
3232
3233   if (!laser.overloaded && laser.overload_value == 0 &&
3234       element != EL_BOMB &&
3235       element != EL_MINE &&
3236       element != EL_BALL_GRAY &&
3237       element != EL_BLOCK_STONE &&
3238       element != EL_BLOCK_WOOD &&
3239       element != EL_FUSE_ON &&
3240       element != EL_FUEL_FULL &&
3241       !IS_WALL_ICE(element) &&
3242       !IS_WALL_AMOEBA(element))
3243     return;
3244
3245   overload_delay.value = HEALTH_DELAY(laser.overloaded);
3246
3247   if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3248        (!laser.overloaded && laser.overload_value > 0)) &&
3249       FrameReached(&overload_delay))
3250   {
3251     if (laser.overloaded)
3252       laser.overload_value++;
3253     else
3254       laser.overload_value--;
3255
3256     if (game_mm.cheat_no_overload)
3257     {
3258       laser.overloaded = FALSE;
3259       laser.overload_value = 0;
3260     }
3261
3262     game_mm.laser_overload_value = laser.overload_value;
3263
3264     if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3265     {
3266       SetLaserColor(0xFF);
3267
3268       DrawLaser(0, DL_LASER_ENABLED);
3269     }
3270
3271     if (!laser.overloaded)
3272       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3273     else if (setup.sound_loops)
3274       PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3275     else
3276       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3277
3278     if (laser.overloaded)
3279     {
3280 #if 0
3281       BlitBitmap(pix[PIX_DOOR], drawto,
3282                  DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3283                  DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3284                  - laser.overload_value,
3285                  OVERLOAD_XSIZE, laser.overload_value,
3286                  DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3287                  - laser.overload_value);
3288 #endif
3289       redraw_mask |= REDRAW_DOOR_1;
3290     }
3291     else
3292     {
3293 #if 0
3294       BlitBitmap(pix[PIX_DOOR], drawto,
3295                  DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3296                  OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3297                  DX_OVERLOAD, DY_OVERLOAD);
3298 #endif
3299       redraw_mask |= REDRAW_DOOR_1;
3300     }
3301
3302     if (laser.overload_value == MAX_LASER_OVERLOAD)
3303     {
3304       UpdateAndDisplayGameControlValues();
3305
3306       FadeOutLaser();
3307
3308       GameOver_MM(GAME_OVER_OVERLOADED);
3309
3310       return;
3311     }
3312   }
3313
3314   if (laser.fuse_off)
3315     return;
3316
3317   CT -= Ct;
3318
3319   if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3320   {
3321     if (game_mm.cheat_no_explosion)
3322       return;
3323
3324     Bang_MM(ELX, ELY);
3325
3326     laser.dest_element = EL_EXPLODING_OPAQUE;
3327
3328     return;
3329   }
3330
3331   if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3332   {
3333     laser.fuse_off = TRUE;
3334     laser.fuse_x = ELX;
3335     laser.fuse_y = ELY;
3336
3337     DrawLaser(0, DL_LASER_DISABLED);
3338     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3339   }
3340
3341   if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3342   {
3343     static int new_elements[] =
3344     {
3345       EL_MIRROR_START,
3346       EL_MIRROR_FIXED_START,
3347       EL_POLAR_START,
3348       EL_POLAR_CROSS_START,
3349       EL_PACMAN_START,
3350       EL_KETTLE,
3351       EL_BOMB,
3352       EL_PRISM
3353     };
3354     int num_new_elements = sizeof(new_elements) / sizeof(int);
3355     int new_element = new_elements[RND(num_new_elements)];
3356
3357     Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3358     Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3359
3360     // !!! CHECK AGAIN: Laser on Polarizer !!!
3361     ScanLaser();
3362
3363     return;
3364
3365 #if 0
3366     int graphic;
3367
3368     switch (RND(5))
3369     {
3370       case 0:
3371         element = EL_MIRROR_START + RND(16);
3372         break;
3373       case 1:
3374         {
3375           int rnd = RND(3);
3376
3377           element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3378         }
3379         break;
3380       default:
3381         {
3382           int rnd = RND(3);
3383
3384           element = (rnd == 0 ? EL_FUSE_ON :
3385                      rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3386                      rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3387                      rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3388                      EL_MIRROR_FIXED_START + rnd - 25);
3389         }
3390         break;
3391     }
3392
3393     graphic = el2gfx(element);
3394
3395     for (i = 0; i < 50; i++)
3396     {
3397       int x = RND(26);
3398       int y = RND(26);
3399
3400 #if 0
3401       BlitBitmap(pix[PIX_BACK], drawto,
3402                  SX + (graphic % GFX_PER_LINE) * TILEX + x,
3403                  SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3404                  SX + ELX * TILEX + x,
3405                  SY + ELY * TILEY + y);
3406 #endif
3407       MarkTileDirty(ELX, ELY);
3408       BackToFront();
3409
3410       DrawLaser(0, DL_LASER_ENABLED);
3411
3412       Delay_WithScreenUpdates(50);
3413     }
3414
3415     Tile[ELX][ELY] = element;
3416     DrawField_MM(ELX, ELY);
3417
3418 #if 0
3419     Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3420 #endif
3421
3422     // above stuff: GRAY BALL -> PRISM !!!
3423 /*
3424     LX = ELX * TILEX + 14;
3425     LY = ELY * TILEY + 14;
3426     if (laser.current_angle == (laser.current_angle >> 1) << 1)
3427       OK = 8;
3428     else
3429       OK = 4;
3430     LX -= OK * XS;
3431     LY -= OK * YS;
3432
3433     laser.num_edges -= 2;
3434     laser.num_damages--;
3435 */
3436
3437 #if 0
3438     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3439       if (laser.damage[i].is_mirror)
3440         break;
3441
3442     if (i > 0)
3443       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3444     else
3445       DrawLaser(0, DL_LASER_DISABLED);
3446 #else
3447     DrawLaser(0, DL_LASER_DISABLED);
3448 #endif
3449
3450     ScanLaser();
3451 #endif
3452
3453     return;
3454   }
3455
3456   if (IS_WALL_ICE(element) && CT > 50)
3457   {
3458     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3459
3460     {
3461       Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3462       Store[ELX][ELY] = EL_WALL_ICE;
3463       Store2[ELX][ELY] = laser.wall_mask;
3464
3465       laser.dest_element = Tile[ELX][ELY];
3466
3467       return;
3468     }
3469
3470     for (i = 0; i < 5; i++)
3471     {
3472       int phase = i + 1;
3473
3474       if (i == 4)
3475       {
3476         Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3477         phase = 0;
3478       }
3479
3480       DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3481       BackToFront();
3482       Delay_WithScreenUpdates(100);
3483     }
3484
3485     if (Tile[ELX][ELY] == EL_WALL_ICE)
3486       Tile[ELX][ELY] = EL_EMPTY;
3487
3488 /*
3489     laser.num_edges--;
3490     LX = laser.edge[laser.num_edges].x - cSX2;
3491     LY = laser.edge[laser.num_edges].y - cSY2;
3492 */
3493
3494     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3495       if (laser.damage[i].is_mirror)
3496         break;
3497
3498     if (i > 0)
3499       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3500     else
3501       DrawLaser(0, DL_LASER_DISABLED);
3502
3503     ScanLaser();
3504
3505     return;
3506   }
3507
3508   if (IS_WALL_AMOEBA(element) && CT > 60)
3509   {
3510     int k1, k2, k3, dx, dy, de, dm;
3511     int element2 = Tile[ELX][ELY];
3512
3513     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3514       return;
3515
3516     for (i = laser.num_damages - 1; i >= 0; i--)
3517       if (laser.damage[i].is_mirror)
3518         break;
3519
3520     r = laser.num_edges;
3521     d = laser.num_damages;
3522     k1 = i;
3523
3524     if (k1 > 0)
3525     {
3526       int x, y;
3527
3528       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3529
3530       laser.num_edges++;
3531       DrawLaser(0, DL_LASER_ENABLED);
3532       laser.num_edges--;
3533
3534       x = laser.damage[k1].x;
3535       y = laser.damage[k1].y;
3536
3537       DrawField_MM(x, y);
3538     }
3539
3540     for (i = 0; i < 4; i++)
3541     {
3542       if (laser.wall_mask & (1 << i))
3543       {
3544         if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3545                             cSY + ELY * TILEY + 31 * (i / 2)))
3546           break;
3547
3548         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3549                             cSY + ELY * TILEY + 14 + (i / 2) * 2))
3550           break;
3551       }
3552     }
3553
3554     k2 = i;
3555
3556     for (i = 0; i < 4; i++)
3557     {
3558       if (laser.wall_mask & (1 << i))
3559       {
3560         if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3561                             cSY + ELY * TILEY + 31 * (i / 2)))
3562           break;
3563       }
3564     }
3565
3566     k3 = i;
3567
3568     if (laser.num_beamers > 0 ||
3569         k1 < 1 || k2 < 4 || k3 < 4 ||
3570         CheckLaserPixel(cSX + ELX * TILEX + 14,
3571                         cSY + ELY * TILEY + 14))
3572     {
3573       laser.num_edges = r;
3574       laser.num_damages = d;
3575
3576       DrawLaser(0, DL_LASER_DISABLED);
3577     }
3578
3579     Tile[ELX][ELY] = element | laser.wall_mask;
3580
3581     dx = ELX;
3582     dy = ELY;
3583     de = Tile[ELX][ELY];
3584     dm = laser.wall_mask;
3585
3586 #if 1
3587     {
3588       int x = ELX, y = ELY;
3589       int wall_mask = laser.wall_mask;
3590
3591       ScanLaser();
3592       DrawLaser(0, DL_LASER_ENABLED);
3593
3594       PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3595
3596       Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3597       Store[x][y] = EL_WALL_AMOEBA;
3598       Store2[x][y] = wall_mask;
3599
3600       return;
3601     }
3602 #endif
3603
3604     DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3605     ScanLaser();
3606     DrawLaser(0, DL_LASER_ENABLED);
3607
3608     PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3609
3610     for (i = 4; i >= 0; i--)
3611     {
3612       DrawWallsAnimation_MM(dx, dy, de, i, dm);
3613
3614       BackToFront();
3615       Delay_WithScreenUpdates(20);
3616     }
3617
3618     DrawLaser(0, DL_LASER_ENABLED);
3619
3620     return;
3621   }
3622
3623   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3624       laser.stops_inside_element && CT > native_mm_level.time_block)
3625   {
3626     int x, y;
3627     int k;
3628
3629     if (ABS(XS) > ABS(YS))
3630       k = 0;
3631     else
3632       k = 1;
3633     if (XS < YS)
3634       k += 2;
3635
3636     for (i = 0; i < 4; i++)
3637     {
3638       if (i)
3639         k++;
3640       if (k > 3)
3641         k = 0;
3642
3643       x = ELX + Step[k * 4].x;
3644       y = ELY + Step[k * 4].y;
3645
3646       if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3647         continue;
3648
3649       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3650         continue;
3651
3652       break;
3653     }
3654
3655     if (i > 3)
3656     {
3657       laser.overloaded = (element == EL_BLOCK_STONE);
3658
3659       return;
3660     }
3661
3662     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3663
3664     Tile[ELX][ELY] = 0;
3665     Tile[x][y] = element;
3666
3667     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3668     DrawField_MM(x, y);
3669
3670     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3671     {
3672       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3673       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3674     }
3675
3676     ScanLaser();
3677
3678     return;
3679   }
3680
3681   if (element == EL_FUEL_FULL && CT > 10)
3682   {
3683     for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3684     {
3685 #if 0
3686       BlitBitmap(pix[PIX_DOOR], drawto,
3687                  DOOR_GFX_PAGEX4 + XX_ENERGY,
3688                  DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3689                  ENERGY_XSIZE, i, DX_ENERGY,
3690                  DY_ENERGY + ENERGY_YSIZE - i);
3691 #endif
3692
3693       redraw_mask |= REDRAW_DOOR_1;
3694       BackToFront();
3695
3696       Delay_WithScreenUpdates(20);
3697     }
3698
3699     game_mm.energy_left = MAX_LASER_ENERGY;
3700     Tile[ELX][ELY] = EL_FUEL_EMPTY;
3701     DrawField_MM(ELX, ELY);
3702
3703     DrawLaser(0, DL_LASER_ENABLED);
3704
3705     return;
3706   }
3707
3708   return;
3709 }
3710
3711 void GameActions_MM(struct MouseActionInfo action)
3712 {
3713   boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3714   boolean button_released = (action.button == MB_RELEASED);
3715
3716   GameActions_MM_Ext();
3717
3718   CheckSingleStepMode_MM(element_clicked, button_released);
3719 }
3720
3721 void MovePacMen(void)
3722 {
3723   int mx, my, ox, oy, nx, ny;
3724   int element;
3725   int l;
3726
3727   if (++pacman_nr >= game_mm.num_pacman)
3728     pacman_nr = 0;
3729
3730   game_mm.pacman[pacman_nr].dir--;
3731
3732   for (l = 1; l < 5; l++)
3733   {
3734     game_mm.pacman[pacman_nr].dir++;
3735
3736     if (game_mm.pacman[pacman_nr].dir > 4)
3737       game_mm.pacman[pacman_nr].dir = 1;
3738
3739     if (game_mm.pacman[pacman_nr].dir % 2)
3740     {
3741       mx = 0;
3742       my = game_mm.pacman[pacman_nr].dir - 2;
3743     }
3744     else
3745     {
3746       my = 0;
3747       mx = 3 - game_mm.pacman[pacman_nr].dir;
3748     }
3749
3750     ox = game_mm.pacman[pacman_nr].x;
3751     oy = game_mm.pacman[pacman_nr].y;
3752     nx = ox + mx;
3753     ny = oy + my;
3754     element = Tile[nx][ny];
3755
3756     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3757       continue;
3758
3759     if (!IS_EATABLE4PACMAN(element))
3760       continue;
3761
3762     if (ObjHit(nx, ny, HIT_POS_CENTER))
3763       continue;
3764
3765     Tile[ox][oy] = EL_EMPTY;
3766     Tile[nx][ny] =
3767       EL_PACMAN_RIGHT - 1 +
3768       (game_mm.pacman[pacman_nr].dir - 1 +
3769        (game_mm.pacman[pacman_nr].dir % 2) * 2);
3770
3771     game_mm.pacman[pacman_nr].x = nx;
3772     game_mm.pacman[pacman_nr].y = ny;
3773
3774     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3775
3776     if (element != EL_EMPTY)
3777     {
3778       int graphic = el2gfx(Tile[nx][ny]);
3779       Bitmap *bitmap;
3780       int src_x, src_y;
3781       int i;
3782
3783       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3784
3785       CT = FrameCounter;
3786       ox = cSX + ox * TILEX;
3787       oy = cSY + oy * TILEY;
3788
3789       for (i = 1; i < 33; i += 2)
3790         BlitBitmap(bitmap, window,
3791                    src_x, src_y, TILEX, TILEY,
3792                    ox + i * mx, oy + i * my);
3793       Ct = Ct + FrameCounter - CT;
3794     }
3795
3796     DrawField_MM(nx, ny);
3797     BackToFront();
3798
3799     if (!laser.fuse_off)
3800     {
3801       DrawLaser(0, DL_LASER_ENABLED);
3802
3803       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3804       {
3805         AddDamagedField(nx, ny);
3806
3807         laser.damage[laser.num_damages - 1].edge = 0;
3808       }
3809     }
3810
3811     if (element == EL_BOMB)
3812       DeletePacMan(nx, ny);
3813
3814     if (IS_WALL_AMOEBA(element) &&
3815         (LX + 2 * XS) / TILEX == nx &&
3816         (LY + 2 * YS) / TILEY == ny)
3817     {
3818       laser.num_edges--;
3819       ScanLaser();
3820     }
3821
3822     break;
3823   }
3824 }
3825
3826 static void InitMovingField_MM(int x, int y, int direction)
3827 {
3828   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3829   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3830
3831   MovDir[x][y] = direction;
3832   MovDir[newx][newy] = direction;
3833
3834   if (Tile[newx][newy] == EL_EMPTY)
3835     Tile[newx][newy] = EL_BLOCKED;
3836 }
3837
3838 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3839 {
3840   int direction = MovDir[x][y];
3841   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3842   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3843
3844   *goes_to_x = newx;
3845   *goes_to_y = newy;
3846 }
3847
3848 static void Blocked2Moving_MM(int x, int y,
3849                               int *comes_from_x, int *comes_from_y)
3850 {
3851   int oldx = x, oldy = y;
3852   int direction = MovDir[x][y];
3853
3854   if (direction == MV_LEFT)
3855     oldx++;
3856   else if (direction == MV_RIGHT)
3857     oldx--;
3858   else if (direction == MV_UP)
3859     oldy++;
3860   else if (direction == MV_DOWN)
3861     oldy--;
3862
3863   *comes_from_x = oldx;
3864   *comes_from_y = oldy;
3865 }
3866
3867 static int MovingOrBlocked2Element_MM(int x, int y)
3868 {
3869   int element = Tile[x][y];
3870
3871   if (element == EL_BLOCKED)
3872   {
3873     int oldx, oldy;
3874
3875     Blocked2Moving_MM(x, y, &oldx, &oldy);
3876
3877     return Tile[oldx][oldy];
3878   }
3879
3880   return element;
3881 }
3882
3883 #if 0
3884 static void RemoveField(int x, int y)
3885 {
3886   Tile[x][y] = EL_EMPTY;
3887   MovPos[x][y] = 0;
3888   MovDir[x][y] = 0;
3889   MovDelay[x][y] = 0;
3890 }
3891 #endif
3892
3893 static void RemoveMovingField_MM(int x, int y)
3894 {
3895   int oldx = x, oldy = y, newx = x, newy = y;
3896
3897   if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3898     return;
3899
3900   if (IS_MOVING(x, y))
3901   {
3902     Moving2Blocked_MM(x, y, &newx, &newy);
3903     if (Tile[newx][newy] != EL_BLOCKED)
3904       return;
3905   }
3906   else if (Tile[x][y] == EL_BLOCKED)
3907   {
3908     Blocked2Moving_MM(x, y, &oldx, &oldy);
3909     if (!IS_MOVING(oldx, oldy))
3910       return;
3911   }
3912
3913   Tile[oldx][oldy] = EL_EMPTY;
3914   Tile[newx][newy] = EL_EMPTY;
3915   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3916   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3917
3918   DrawLevelField_MM(oldx, oldy);
3919   DrawLevelField_MM(newx, newy);
3920 }
3921
3922 void PlaySoundLevel(int x, int y, int sound_nr)
3923 {
3924   int sx = SCREENX(x), sy = SCREENY(y);
3925   int volume, stereo;
3926   int silence_distance = 8;
3927
3928   if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3929       (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3930     return;
3931
3932   if (!IN_LEV_FIELD(x, y) ||
3933       sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3934       sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3935     return;
3936
3937   volume = SOUND_MAX_VOLUME;
3938
3939 #ifndef MSDOS
3940   stereo = (sx - SCR_FIELDX/2) * 12;
3941 #else
3942   stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3943   if (stereo > SOUND_MAX_RIGHT)
3944     stereo = SOUND_MAX_RIGHT;
3945   if (stereo < SOUND_MAX_LEFT)
3946     stereo = SOUND_MAX_LEFT;
3947 #endif
3948
3949   if (!IN_SCR_FIELD(sx, sy))
3950   {
3951     int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3952     int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3953
3954     volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3955   }
3956
3957   PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3958 }
3959
3960 static void RaiseScore_MM(int value)
3961 {
3962   game_mm.score += value;
3963 }
3964
3965 void RaiseScoreElement_MM(int element)
3966 {
3967   switch (element)
3968   {
3969     case EL_PACMAN:
3970     case EL_PACMAN_RIGHT:
3971     case EL_PACMAN_UP:
3972     case EL_PACMAN_LEFT:
3973     case EL_PACMAN_DOWN:
3974       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3975       break;
3976
3977     case EL_KEY:
3978       RaiseScore_MM(native_mm_level.score[SC_KEY]);
3979       break;
3980
3981     case EL_KETTLE:
3982     case EL_CELL:
3983       RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3984       break;
3985
3986     case EL_LIGHTBALL:
3987       RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3988       break;
3989
3990     default:
3991       break;
3992   }
3993 }
3994
3995
3996 // ----------------------------------------------------------------------------
3997 // Mirror Magic game engine snapshot handling functions
3998 // ----------------------------------------------------------------------------
3999
4000 void SaveEngineSnapshotValues_MM(void)
4001 {
4002   int x, y;
4003
4004   engine_snapshot_mm.game_mm = game_mm;
4005   engine_snapshot_mm.laser = laser;
4006
4007   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4008   {
4009     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4010     {
4011       engine_snapshot_mm.Ur[x][y]    = Ur[x][y];
4012       engine_snapshot_mm.Hit[x][y]   = Hit[x][y];
4013       engine_snapshot_mm.Box[x][y]   = Box[x][y];
4014       engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4015       engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4016     }
4017   }
4018
4019   engine_snapshot_mm.LX = LX;
4020   engine_snapshot_mm.LY = LY;
4021   engine_snapshot_mm.XS = XS;
4022   engine_snapshot_mm.YS = YS;
4023   engine_snapshot_mm.ELX = ELX;
4024   engine_snapshot_mm.ELY = ELY;
4025   engine_snapshot_mm.CT = CT;
4026   engine_snapshot_mm.Ct = Ct;
4027
4028   engine_snapshot_mm.last_LX = last_LX;
4029   engine_snapshot_mm.last_LY = last_LY;
4030   engine_snapshot_mm.last_hit_mask = last_hit_mask;
4031   engine_snapshot_mm.hold_x = hold_x;
4032   engine_snapshot_mm.hold_y = hold_y;
4033   engine_snapshot_mm.pacman_nr = pacman_nr;
4034
4035   engine_snapshot_mm.rotate_delay = rotate_delay;
4036   engine_snapshot_mm.pacman_delay = pacman_delay;
4037   engine_snapshot_mm.energy_delay = energy_delay;
4038   engine_snapshot_mm.overload_delay = overload_delay;
4039 }
4040
4041 void LoadEngineSnapshotValues_MM(void)
4042 {
4043   int x, y;
4044
4045   // stored engine snapshot buffers already restored at this point
4046
4047   game_mm = engine_snapshot_mm.game_mm;
4048   laser   = engine_snapshot_mm.laser;
4049
4050   for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4051   {
4052     for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4053     {
4054       Ur[x][y]    = engine_snapshot_mm.Ur[x][y];
4055       Hit[x][y]   = engine_snapshot_mm.Hit[x][y];
4056       Box[x][y]   = engine_snapshot_mm.Box[x][y];
4057       Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4058       Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4059     }
4060   }
4061
4062   LX  = engine_snapshot_mm.LX;
4063   LY  = engine_snapshot_mm.LY;
4064   XS  = engine_snapshot_mm.XS;
4065   YS  = engine_snapshot_mm.YS;
4066   ELX = engine_snapshot_mm.ELX;
4067   ELY = engine_snapshot_mm.ELY;
4068   CT  = engine_snapshot_mm.CT;
4069   Ct  = engine_snapshot_mm.Ct;
4070
4071   last_LX       = engine_snapshot_mm.last_LX;
4072   last_LY       = engine_snapshot_mm.last_LY;
4073   last_hit_mask = engine_snapshot_mm.last_hit_mask;
4074   hold_x        = engine_snapshot_mm.hold_x;
4075   hold_y        = engine_snapshot_mm.hold_y;
4076   pacman_nr     = engine_snapshot_mm.pacman_nr;
4077
4078   rotate_delay   = engine_snapshot_mm.rotate_delay;
4079   pacman_delay   = engine_snapshot_mm.pacman_delay;
4080   energy_delay   = engine_snapshot_mm.energy_delay;
4081   overload_delay = engine_snapshot_mm.overload_delay;
4082
4083   RedrawPlayfield_MM();
4084 }
4085
4086 static int getAngleFromTouchDelta(int dx, int dy,  int base)
4087 {
4088   double pi = 3.141592653;
4089   double rad = atan2((double)-dy, (double)dx);
4090   double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4091   double deg = rad2 * 180.0 / pi;
4092
4093   return (int)(deg * base / 360.0 + 0.5) % base;
4094 }
4095
4096 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4097 {
4098   // calculate start (source) position to be at the middle of the tile
4099   int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4100   int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4101   int dx = dst_mx - src_mx;
4102   int dy = dst_my - src_my;
4103   int element;
4104   int base = 16;
4105   int phases = 16;
4106   int angle_old = -1;
4107   int angle_new = -1;
4108   int button = 0;
4109   int i;
4110
4111   if (!IN_LEV_FIELD(x, y))
4112     return 0;
4113
4114   element = Tile[x][y];
4115
4116   if (!IS_MCDUFFIN(element) &&
4117       !IS_MIRROR(element) &&
4118       !IS_BEAMER(element) &&
4119       !IS_POLAR(element) &&
4120       !IS_POLAR_CROSS(element) &&
4121       !IS_DF_MIRROR(element))
4122     return 0;
4123
4124   angle_old = get_element_angle(element);
4125
4126   if (IS_MCDUFFIN(element))
4127   {
4128     angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4129                  dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4130                  dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4131                  dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4132                  -1);
4133   }
4134   else if (IS_MIRROR(element) ||
4135            IS_DF_MIRROR(element))
4136   {
4137     for (i = 0; i < laser.num_damages; i++)
4138     {
4139       if (laser.damage[i].x == x &&
4140           laser.damage[i].y == y &&
4141           ObjHit(x, y, HIT_POS_CENTER))
4142       {
4143         angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4144         angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4145
4146         break;
4147       }
4148     }
4149   }
4150
4151   if (angle_new == -1)
4152   {
4153     if (IS_MIRROR(element) ||
4154         IS_DF_MIRROR(element) ||
4155         IS_POLAR(element))
4156       base = 32;
4157
4158     if (IS_POLAR_CROSS(element))
4159       phases = 4;
4160
4161     angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4162   }
4163
4164   button = (angle_new == angle_old ? 0 :
4165             (angle_new - angle_old + phases) % phases < (phases / 2) ?
4166             MB_LEFTBUTTON : MB_RIGHTBUTTON);
4167
4168   return button;
4169 }