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