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