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