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