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