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