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