removed unneeded delayed screen update when overloading laser (MM engine)
[rocksndiamonds.git] / src / game_mm / mm_game.c
1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // mm_game.c
10 // ============================================================================
11
12 #include "main_mm.h"
13
14 #include "mm_main.h"
15 #include "mm_game.h"
16 #include "mm_tools.h"
17
18 /* graphic position values for game controls */
19 #define ENERGY_XSIZE            32
20 #define ENERGY_YSIZE            MAX_LASER_ENERGY
21 #define OVERLOAD_XSIZE          ENERGY_XSIZE
22 #define OVERLOAD_YSIZE          MAX_LASER_OVERLOAD
23
24 /* values for Explode_MM() */
25 #define EX_PHASE_START          0
26 #define EX_NORMAL               0
27 #define EX_KETTLE               1
28 #define EX_SHORT                2
29
30 /* special positions in the game control window (relative to control window) */
31 #define XX_LEVEL                36
32 #define YY_LEVEL                23
33 #define XX_KETTLES              29
34 #define YY_KETTLES              63
35 #define XX_SCORE                22
36 #define YY_SCORE                101
37 #define XX_ENERGY               8
38 #define YY_ENERGY               158
39 #define XX_OVERLOAD             60
40 #define YY_OVERLOAD             YY_ENERGY
41
42 /* special positions in the game control window (relative to main window) */
43 #define DX_LEVEL                (DX + XX_LEVEL)
44 #define DY_LEVEL                (DY + YY_LEVEL)
45 #define DX_KETTLES              (DX + XX_KETTLES)
46 #define DY_KETTLES              (DY + YY_KETTLES)
47 #define DX_SCORE                (DX + XX_SCORE)
48 #define DY_SCORE                (DY + YY_SCORE)
49 #define DX_ENERGY               (DX + XX_ENERGY)
50 #define DY_ENERGY               (DY + YY_ENERGY)
51 #define DX_OVERLOAD             (DX + XX_OVERLOAD)
52 #define DY_OVERLOAD             (DY + YY_OVERLOAD)
53
54 #define IS_LOOP_SOUND(s)        ((s) == SND_FUEL)
55 #define IS_MUSIC_SOUND(s)       ((s) == SND_TYGER || (s) == SND_VOYAGER)
56
57 /* game button identifiers */
58 #define GAME_CTRL_ID_LEFT       0
59 #define GAME_CTRL_ID_MIDDLE     1
60 #define GAME_CTRL_ID_RIGHT      2
61
62 #define NUM_GAME_BUTTONS        3
63
64 /* values for DrawLaser() */
65 #define DL_LASER_DISABLED       0
66 #define DL_LASER_ENABLED        1
67
68 /* values for 'click_delay_value' in ClickElement() */
69 #define CLICK_DELAY_FIRST       12      /* delay (frames) after first click */
70 #define CLICK_DELAY             6       /* delay (frames) for pressed butten */
71
72 #define AUTO_ROTATE_DELAY       CLICK_DELAY
73 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
74 #define NUM_INIT_CYCLE_STEPS    16
75 #define PACMAN_MOVE_DELAY       12
76 #define ENERGY_DELAY            (4 * ONE_SECOND_DELAY / GAME_FRAME_DELAY)
77 #define HEALTH_DEC_DELAY        3
78 #define HEALTH_INC_DELAY        9
79 #define HEALTH_DELAY(x)         ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
80
81 /* forward declaration for internal use */
82 static int MovingOrBlocked2Element_MM(int, int);
83 static void Bang_MM(int, int);
84 static void RaiseScore_MM(int);
85 static void RemoveMovingField_MM(int, int);
86 static void InitMovingField_MM(int, int, int);
87 static void ContinueMoving_MM(int, int);
88 static void Moving2Blocked_MM(int, int, int *, int *);
89
90
91 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 #if 0
2939       BackToFront();
2940 #endif
2941     }
2942
2943     if (!laser.overloaded)
2944       StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
2945     else if (setup.sound_loops)
2946       PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
2947     else
2948       PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
2949
2950     if (laser.overloaded)
2951     {
2952 #if 0
2953       BlitBitmap(pix[PIX_DOOR], drawto,
2954                  DOOR_GFX_PAGEX4 + XX_OVERLOAD,
2955                  DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
2956                  - laser.overload_value,
2957                  OVERLOAD_XSIZE, laser.overload_value,
2958                  DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
2959                  - laser.overload_value);
2960 #endif
2961       redraw_mask |= REDRAW_DOOR_1;
2962     }
2963     else
2964     {
2965 #if 0
2966       BlitBitmap(pix[PIX_DOOR], drawto,
2967                  DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
2968                  OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
2969                  DX_OVERLOAD, DY_OVERLOAD);
2970 #endif
2971       redraw_mask |= REDRAW_DOOR_1;
2972     }
2973
2974     if (laser.overload_value == MAX_LASER_OVERLOAD)
2975     {
2976       int i;
2977
2978       for (i = 15; i >= 0; i--)
2979       {
2980 #if 0
2981         SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
2982 #endif
2983
2984         pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
2985
2986         DrawLaser(0, DL_LASER_ENABLED);
2987         BackToFront();
2988         Delay(50);
2989       }
2990
2991       DrawLaser(0, DL_LASER_DISABLED);
2992
2993       game_mm.game_over = TRUE;
2994       game_mm.game_over_cause = GAME_OVER_OVERLOADED;
2995
2996 #if 0
2997       if (Request("Magic spell hit Mc Duffin ! Play it again ?",
2998                   REQ_ASK | REQ_STAY_CLOSED))
2999       {
3000         InitGame();
3001       }
3002       else
3003       {
3004         game_status = MAINMENU;
3005         DrawMainMenu();
3006       }
3007 #endif
3008
3009       return;
3010     }
3011   }
3012
3013   if (laser.fuse_off)
3014     return;
3015
3016   CT -= Ct;
3017
3018   if (element == EL_BOMB && CT > 1500)
3019   {
3020     if (game_mm.cheat_no_explosion)
3021       return;
3022
3023 #if 0
3024     laser.num_damages--;
3025     DrawLaser(0, DL_LASER_DISABLED);
3026     laser.num_edges = 0;
3027 #endif
3028
3029     Bang_MM(ELX, ELY);
3030
3031     laser.dest_element = EL_EXPLODING_OPAQUE;
3032
3033 #if 0
3034     Bang_MM(ELX, ELY);
3035     laser.num_damages--;
3036     DrawLaser(0, DL_LASER_DISABLED);
3037
3038     laser.num_edges = 0;
3039     Bang_MM(laser.start_edge.x, laser.start_edge.y);
3040
3041     if (Request("Bomb killed Mc Duffin ! Play it again ?",
3042                 REQ_ASK | REQ_STAY_CLOSED))
3043     {
3044       InitGame();
3045     }
3046     else
3047     {
3048       game_status = MAINMENU;
3049       DrawMainMenu();
3050     }
3051 #endif
3052
3053     return;
3054   }
3055
3056   if (element == EL_FUSE_ON && CT > 500)
3057   {
3058     laser.fuse_off = TRUE;
3059     laser.fuse_x = ELX;
3060     laser.fuse_y = ELY;
3061
3062     DrawLaser(0, DL_LASER_DISABLED);
3063     DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3064   }
3065
3066   if (element == EL_BALL_GRAY && CT > 1500)
3067   {
3068     static int new_elements[] =
3069     {
3070       EL_MIRROR_START,
3071       EL_MIRROR_FIXED_START,
3072       EL_POLAR_START,
3073       EL_POLAR_CROSS_START,
3074       EL_PACMAN_START,
3075       EL_KETTLE,
3076       EL_BOMB,
3077       EL_PRISM
3078     };
3079     int num_new_elements = sizeof(new_elements) / sizeof(int);
3080     int new_element = new_elements[RND(num_new_elements)];
3081
3082     Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3083     Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3084
3085     /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3086     ScanLaser();
3087
3088     return;
3089
3090 #if 0
3091     int graphic;
3092
3093     switch (RND(5))
3094     {
3095       case 0:
3096         element = EL_MIRROR_START + RND(16);
3097         break;
3098       case 1:
3099         {
3100           int rnd = RND(3);
3101
3102           element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3103         }
3104         break;
3105       default:
3106         {
3107           int rnd = RND(3);
3108
3109           element = (rnd == 0 ? EL_FUSE_ON :
3110                      rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3111                      rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3112                      rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3113                      EL_MIRROR_FIXED_START + rnd - 25);
3114         }
3115         break;
3116     }
3117
3118     graphic = el2gfx(element);
3119
3120     for (i = 0; i < 50; i++)
3121     {
3122       int x = RND(26);
3123       int y = RND(26);
3124
3125 #if 0
3126       BlitBitmap(pix[PIX_BACK], drawto,
3127                  SX + (graphic % GFX_PER_LINE) * TILEX + x,
3128                  SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3129                  SX + ELX * TILEX + x,
3130                  SY + ELY * TILEY + y);
3131 #endif
3132       MarkTileDirty(ELX, ELY);
3133       BackToFront();
3134
3135       DrawLaser(0, DL_LASER_ENABLED);
3136
3137       Delay(50);
3138     }
3139
3140     Feld[ELX][ELY] = element;
3141     DrawField_MM(ELX, ELY);
3142
3143 #if 0
3144     printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3145 #endif
3146
3147     /* above stuff: GRAY BALL -> PRISM !!! */
3148 /*
3149     LX = ELX * TILEX + 14;
3150     LY = ELY * TILEY + 14;
3151     if (laser.current_angle == (laser.current_angle >> 1) << 1)
3152       OK = 8;
3153     else
3154       OK = 4;
3155     LX -= OK * XS;
3156     LY -= OK * YS;
3157
3158     laser.num_edges -= 2;
3159     laser.num_damages--;
3160 */
3161
3162 #if 0
3163     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3164       if (laser.damage[i].is_mirror)
3165         break;
3166
3167     if (i > 0)
3168       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3169     else
3170       DrawLaser(0, DL_LASER_DISABLED);
3171 #else
3172     DrawLaser(0, DL_LASER_DISABLED);
3173 #endif
3174
3175     ScanLaser();
3176
3177     /*
3178     printf("TEST ELEMENT: %d\n", Feld[0][0]);
3179     */
3180 #endif
3181
3182     return;
3183   }
3184
3185   if (IS_WALL_ICE(element) && CT > 1000)
3186   {
3187     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3188
3189     {
3190       Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3191       Store[ELX][ELY] = EL_WALL_ICE;
3192       Store2[ELX][ELY] = laser.wall_mask;
3193
3194       laser.dest_element = Feld[ELX][ELY];
3195
3196       return;
3197     }
3198
3199     for (i = 0; i < 5; i++)
3200     {
3201       int phase = i + 1;
3202
3203       if (i == 4)
3204       {
3205         Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3206         phase = 0;
3207       }
3208
3209       DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3210       BackToFront();
3211       Delay(100);
3212     }
3213
3214     if (Feld[ELX][ELY] == EL_WALL_ICE)
3215       Feld[ELX][ELY] = EL_EMPTY;
3216
3217 /*
3218     laser.num_edges--;
3219     LX = laser.edge[laser.num_edges].x - (SX + 2);
3220     LY = laser.edge[laser.num_edges].y - (SY + 2);
3221 */
3222
3223     for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3224       if (laser.damage[i].is_mirror)
3225         break;
3226
3227     if (i > 0)
3228       DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3229     else
3230       DrawLaser(0, DL_LASER_DISABLED);
3231
3232     ScanLaser();
3233
3234     return;
3235   }
3236
3237   if (IS_WALL_AMOEBA(element) && CT > 1200)
3238   {
3239     int k1, k2, k3, dx, dy, de, dm;
3240     int element2 = Feld[ELX][ELY];
3241
3242     if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3243       return;
3244
3245     for (i = laser.num_damages - 1; i >= 0; i--)
3246       if (laser.damage[i].is_mirror)
3247         break;
3248
3249     r = laser.num_edges;
3250     d = laser.num_damages;
3251     k1 = i;
3252
3253     if (k1 > 0)
3254     {
3255       int x, y;
3256
3257       DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3258
3259       laser.num_edges++;
3260       DrawLaser(0, DL_LASER_ENABLED);
3261       laser.num_edges--;
3262
3263       x = laser.damage[k1].x;
3264       y = laser.damage[k1].y;
3265
3266       DrawField_MM(x, y);
3267     }
3268
3269     for (i = 0; i < 4; i++)
3270     {
3271       if (laser.wall_mask & (1 << i))
3272       {
3273         if (ReadPixel(drawto,
3274                       SX + ELX * TILEX + 14 + (i % 2) * 2,
3275                       SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3276           break;
3277         if (ReadPixel(drawto,
3278                       SX + ELX * TILEX + 31 * (i % 2),
3279                       SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3280           break;
3281       }
3282     }
3283
3284     k2 = i;
3285
3286     for (i = 0; i < 4; i++)
3287     {
3288       if (laser.wall_mask & (1 << i))
3289       {
3290         if (ReadPixel(drawto,
3291                       SX + ELX * TILEX + 31 * (i % 2),
3292                       SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3293           break;
3294       }
3295     }
3296
3297     k3 = i;
3298
3299     if (laser.num_beamers > 0 ||
3300         k1 < 1 || k2 < 4 || k3 < 4 ||
3301         ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3302         == pen_ray)
3303     {
3304       laser.num_edges = r;
3305       laser.num_damages = d;
3306
3307       DrawLaser(0, DL_LASER_DISABLED);
3308     }
3309
3310     Feld[ELX][ELY] = element | laser.wall_mask;
3311
3312     dx = ELX;
3313     dy = ELY;
3314     de = Feld[ELX][ELY];
3315     dm = laser.wall_mask;
3316
3317 #if 1
3318     {
3319       int x = ELX, y = ELY;
3320       int wall_mask = laser.wall_mask;
3321
3322       ScanLaser();
3323       DrawLaser(0, DL_LASER_ENABLED);
3324
3325       PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3326
3327       Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3328       Store[x][y] = EL_WALL_AMOEBA;
3329       Store2[x][y] = wall_mask;
3330
3331       return;
3332     }
3333 #endif
3334
3335     DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3336     ScanLaser();
3337     DrawLaser(0, DL_LASER_ENABLED);
3338
3339     PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3340
3341     for (i = 4; i >= 0; i--)
3342     {
3343       DrawWallsAnimation_MM(dx, dy, de, i, dm);
3344
3345       BackToFront();
3346       Delay(20);
3347     }
3348
3349     DrawLaser(0, DL_LASER_ENABLED);
3350
3351     return;
3352   }
3353
3354   if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3355       laser.stops_inside_element && CT > 1500)
3356   {
3357     int x, y;
3358     int k;
3359
3360     if (ABS(XS) > ABS(YS))
3361       k = 0;
3362     else
3363       k = 1;
3364     if (XS < YS)
3365       k += 2;
3366
3367     for (i = 0; i < 4; i++)
3368     {
3369       if (i)
3370         k++;
3371       if (k > 3)
3372         k = 0;
3373
3374       x = ELX + Step[k * 4].x;
3375       y = ELY + Step[k * 4].y;
3376
3377       if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3378         continue;
3379
3380       if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3381         continue;
3382
3383       break;
3384     }
3385
3386     if (i > 3)
3387     {
3388       laser.overloaded = (element == EL_BLOCK_STONE);
3389
3390       return;
3391     }
3392
3393     PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3394
3395     Feld[ELX][ELY] = 0;
3396     Feld[x][y] = element;
3397
3398     DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3399     DrawField_MM(x, y);
3400
3401     if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3402     {
3403       DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3404       DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3405     }
3406
3407     ScanLaser();
3408
3409     return;
3410   }
3411
3412   if (element == EL_FUEL_FULL && CT > 200)
3413   {
3414     for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3415     {
3416 #if 0
3417       BlitBitmap(pix[PIX_DOOR], drawto,
3418                  DOOR_GFX_PAGEX4 + XX_ENERGY,
3419                  DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3420                  ENERGY_XSIZE, i, DX_ENERGY,
3421                  DY_ENERGY + ENERGY_YSIZE - i);
3422 #endif
3423
3424       redraw_mask |= REDRAW_DOOR_1;
3425       BackToFront();
3426
3427       Delay(20);
3428     }
3429
3430     game_mm.energy_left = MAX_LASER_ENERGY;
3431     Feld[ELX][ELY] = EL_FUEL_EMPTY;
3432     DrawField_MM(ELX, ELY);
3433
3434     DrawLaser(0, DL_LASER_ENABLED);
3435
3436     return;
3437   }
3438
3439   return;
3440 }
3441
3442 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3443 {
3444   ClickElement(action.lx, action.ly, action.button);
3445
3446   GameActions_MM_Ext(action, warp_mode);
3447 }
3448
3449 void MovePacMen()
3450 {
3451   static int p = -1;
3452   int mx, my, ox, oy, nx, ny;
3453   int element;
3454   int l;
3455
3456   if (++p >= game_mm.num_pacman)
3457     p = 0;
3458
3459   game_mm.pacman[p].dir--;
3460
3461   for (l = 1; l < 5; l++)
3462   {
3463     game_mm.pacman[p].dir++;
3464
3465     if (game_mm.pacman[p].dir > 4)
3466       game_mm.pacman[p].dir = 1;
3467
3468     if (game_mm.pacman[p].dir % 2)
3469     {
3470       mx = 0;
3471       my = game_mm.pacman[p].dir - 2;
3472     }
3473     else
3474     {
3475       my = 0;
3476       mx = 3 - game_mm.pacman[p].dir;
3477     }
3478
3479     ox = game_mm.pacman[p].x;
3480     oy = game_mm.pacman[p].y;
3481     nx = ox + mx;
3482     ny = oy + my;
3483     element = Feld[nx][ny];
3484
3485     if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3486       continue;
3487
3488     if (!IS_EATABLE4PACMAN(element))
3489       continue;
3490
3491     if (ObjHit(nx, ny, HIT_POS_CENTER))
3492       continue;
3493
3494     Feld[ox][oy] = EL_EMPTY;
3495     Feld[nx][ny] =
3496       EL_PACMAN_RIGHT - 1 +
3497       (game_mm.pacman[p].dir - 1 +
3498        (game_mm.pacman[p].dir % 2) * 2);
3499
3500     game_mm.pacman[p].x = nx;
3501     game_mm.pacman[p].y = ny;
3502
3503     DrawGraphic_MM(ox, oy, IMG_EMPTY);
3504
3505     if (element != EL_EMPTY)
3506     {
3507       int graphic = el2gfx(Feld[nx][ny]);
3508       Bitmap *bitmap;
3509       int src_x, src_y;
3510       int i;
3511
3512       getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3513
3514       CT = Counter();
3515       ox = SX + ox * TILEX;
3516       oy = SY + oy * TILEY;
3517
3518       for (i = 1; i < 33; i += 2)
3519         BlitBitmap(bitmap, window,
3520                    src_x, src_y, TILEX, TILEY,
3521                    ox + i * mx, oy + i * my);
3522       Ct = Ct + Counter() - CT;
3523     }
3524
3525     DrawField_MM(nx, ny);
3526     BackToFront();
3527
3528     if (!laser.fuse_off)
3529     {
3530       DrawLaser(0, DL_LASER_ENABLED);
3531
3532       if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3533       {
3534         AddDamagedField(nx, ny);
3535
3536         laser.damage[laser.num_damages - 1].edge = 0;
3537       }
3538     }
3539
3540     if (element == EL_BOMB)
3541       DeletePacMan(nx, ny);
3542
3543     if (IS_WALL_AMOEBA(element) &&
3544         (LX + 2 * XS) / TILEX == nx &&
3545         (LY + 2 * YS) / TILEY == ny)
3546     {
3547       laser.num_edges--;
3548       ScanLaser();
3549     }
3550
3551     break;
3552   }
3553 }
3554
3555 void GameWon_MM()
3556 {
3557   int hi_pos;
3558   boolean raise_level = FALSE;
3559
3560 #if 0
3561   if (local_player->MovPos)
3562     return;
3563
3564   local_player->LevelSolved = FALSE;
3565 #endif
3566
3567   if (game_mm.energy_left)
3568   {
3569     if (setup.sound_loops)
3570       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3571                    SND_CTRL_PLAY_LOOP);
3572
3573     while (game_mm.energy_left > 0)
3574     {
3575       if (!setup.sound_loops)
3576         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3577
3578       /*
3579       if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3580         RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3581       */
3582
3583       RaiseScore_MM(5);
3584
3585       game_mm.energy_left--;
3586       if (game_mm.energy_left >= 0)
3587       {
3588 #if 0
3589         BlitBitmap(pix[PIX_DOOR], drawto,
3590                    DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3591                    ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3592                    DX_ENERGY, DY_ENERGY);
3593 #endif
3594         redraw_mask |= REDRAW_DOOR_1;
3595       }
3596
3597       BackToFront();
3598       Delay(10);
3599     }
3600
3601     if (setup.sound_loops)
3602       StopSound(SND_SIRR);
3603   }
3604   else if (native_mm_level.time == 0)           /* level without time limit */
3605   {
3606     if (setup.sound_loops)
3607       PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3608                    SND_CTRL_PLAY_LOOP);
3609
3610     while (TimePlayed < 999)
3611     {
3612       if (!setup.sound_loops)
3613         PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3614       if (TimePlayed < 999 && !(TimePlayed % 10))
3615         RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3616       if (TimePlayed < 900 && !(TimePlayed % 10))
3617         TimePlayed += 10;
3618       else
3619         TimePlayed++;
3620
3621       /*
3622       DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3623       */
3624
3625       BackToFront();
3626       Delay(10);
3627     }
3628
3629     if (setup.sound_loops)
3630       StopSound(SND_SIRR);
3631   }
3632
3633 #if 0
3634   FadeSounds();
3635 #endif
3636
3637   CloseDoor(DOOR_CLOSE_1);
3638
3639   Request("Level solved !", REQ_CONFIRM);
3640
3641   if (level_nr == leveldir_current->handicap_level)
3642   {
3643     leveldir_current->handicap_level++;
3644     SaveLevelSetup_SeriesInfo();
3645   }
3646
3647   if (level_editor_test_game)
3648     game_mm.score = -1;         /* no highscore when playing from editor */
3649   else if (level_nr < leveldir_current->last_level)
3650     raise_level = TRUE;         /* advance to next level */
3651
3652   if ((hi_pos = NewHiScore_MM()) >= 0)
3653   {
3654     game_status = HALLOFFAME;
3655
3656     // DrawHallOfFame(hi_pos);
3657
3658     if (raise_level)
3659       level_nr++;
3660   }
3661   else
3662   {
3663     game_status = MAINMENU;
3664
3665     if (raise_level)
3666       level_nr++;
3667
3668     // DrawMainMenu();
3669   }
3670
3671   BackToFront();
3672 }
3673
3674 int NewHiScore_MM()
3675 {
3676   int k, l;
3677   int position = -1;
3678
3679   // LoadScore(level_nr);
3680
3681   if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3682       game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3683     return -1;
3684
3685   for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3686   {
3687     if (game_mm.score > highscore[k].Score)
3688     {
3689       /* player has made it to the hall of fame */
3690
3691       if (k < MAX_SCORE_ENTRIES - 1)
3692       {
3693         int m = MAX_SCORE_ENTRIES - 1;
3694
3695 #ifdef ONE_PER_NAME
3696         for (l = k; l < MAX_SCORE_ENTRIES; l++)
3697           if (!strcmp(setup.player_name, highscore[l].Name))
3698             m = l;
3699         if (m == k)     /* player's new highscore overwrites his old one */
3700           goto put_into_list;
3701 #endif
3702
3703         for (l = m; l>k; l--)
3704         {
3705           strcpy(highscore[l].Name, highscore[l - 1].Name);
3706           highscore[l].Score = highscore[l - 1].Score;
3707         }
3708       }
3709
3710 #ifdef ONE_PER_NAME
3711       put_into_list:
3712 #endif
3713       strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3714       highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3715       highscore[k].Score = game_mm.score;
3716       position = k;
3717
3718       break;
3719     }
3720
3721 #ifdef ONE_PER_NAME
3722     else if (!strncmp(setup.player_name, highscore[k].Name,
3723                       MAX_PLAYER_NAME_LEN))
3724       break;    /* player already there with a higher score */
3725 #endif
3726
3727   }
3728
3729   // if (position >= 0)
3730   //   SaveScore(level_nr);
3731
3732   return position;
3733 }
3734
3735 static void InitMovingField_MM(int x, int y, int direction)
3736 {
3737   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3738   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3739
3740   MovDir[x][y] = direction;
3741   MovDir[newx][newy] = direction;
3742
3743   if (Feld[newx][newy] == EL_EMPTY)
3744     Feld[newx][newy] = EL_BLOCKED;
3745 }
3746
3747 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3748 {
3749   int direction = MovDir[x][y];
3750   int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3751   int newy = y + (direction == MV_UP   ? -1 : direction == MV_DOWN  ? +1 : 0);
3752
3753   *goes_to_x = newx;
3754   *goes_to_y = newy;
3755 }
3756
3757 static void Blocked2Moving_MM(int x, int y,
3758                               int *comes_from_x, int *comes_from_y)
3759 {
3760   int oldx = x, oldy = y;
3761   int direction = MovDir[x][y];
3762
3763   if (direction == MV_LEFT)
3764     oldx++;
3765   else if (direction == MV_RIGHT)
3766     oldx--;
3767   else if (direction == MV_UP)
3768     oldy++;
3769   else if (direction == MV_DOWN)
3770     oldy--;
3771
3772   *comes_from_x = oldx;
3773   *comes_from_y = oldy;
3774 }
3775
3776 static int MovingOrBlocked2Element_MM(int x, int y)
3777 {
3778   int element = Feld[x][y];
3779
3780   if (element == EL_BLOCKED)
3781   {
3782     int oldx, oldy;
3783
3784     Blocked2Moving_MM(x, y, &oldx, &oldy);
3785
3786     return Feld[oldx][oldy];
3787   }
3788
3789   return element;
3790 }
3791
3792 #if 0
3793 static void RemoveField(int x, int y)
3794 {
3795   Feld[x][y] = EL_EMPTY;
3796   MovPos[x][y] = 0;
3797   MovDir[x][y] = 0;
3798   MovDelay[x][y] = 0;
3799 }
3800 #endif
3801
3802 static void RemoveMovingField_MM(int x, int y)
3803 {
3804   int oldx = x, oldy = y, newx = x, newy = y;
3805
3806   if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3807     return;
3808
3809   if (IS_MOVING(x, y))
3810   {
3811     Moving2Blocked_MM(x, y, &newx, &newy);
3812     if (Feld[newx][newy] != EL_BLOCKED)
3813       return;
3814   }
3815   else if (Feld[x][y] == EL_BLOCKED)
3816   {
3817     Blocked2Moving_MM(x, y, &oldx, &oldy);
3818     if (!IS_MOVING(oldx, oldy))
3819       return;
3820   }
3821
3822   Feld[oldx][oldy] = EL_EMPTY;
3823   Feld[newx][newy] = EL_EMPTY;
3824   MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3825   MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3826
3827   DrawLevelField_MM(oldx, oldy);
3828   DrawLevelField_MM(newx, newy);
3829 }
3830
3831 void PlaySoundLevel(int x, int y, int sound_nr)
3832 {
3833   int sx = SCREENX(x), sy = SCREENY(y);
3834   int volume, stereo;
3835   int silence_distance = 8;
3836
3837   if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3838       (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3839     return;
3840
3841   if (!IN_LEV_FIELD(x, y) ||
3842       sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3843       sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3844     return;
3845
3846   volume = SOUND_MAX_VOLUME;
3847
3848 #ifndef MSDOS
3849   stereo = (sx - SCR_FIELDX/2) * 12;
3850 #else
3851   stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3852   if (stereo > SOUND_MAX_RIGHT)
3853     stereo = SOUND_MAX_RIGHT;
3854   if (stereo < SOUND_MAX_LEFT)
3855     stereo = SOUND_MAX_LEFT;
3856 #endif
3857
3858   if (!IN_SCR_FIELD(sx, sy))
3859   {
3860     int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3861     int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3862
3863     volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3864   }
3865
3866   PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3867 }
3868
3869 static void RaiseScore_MM(int value)
3870 {
3871   game_mm.score += value;
3872
3873 #if 0
3874   DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
3875            FONT_TEXT_2);
3876 #endif
3877 }
3878
3879 void RaiseScoreElement_MM(int element)
3880 {
3881   switch(element)
3882   {
3883     case EL_PACMAN:
3884       RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3885       break;
3886
3887     case EL_KEY:
3888       RaiseScore_MM(native_mm_level.score[SC_KEY]);
3889       break;
3890
3891     default:
3892       break;
3893   }
3894 }