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