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