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