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