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