1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
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
24 /* values for Explode_MM() */
25 #define EX_PHASE_START 0
30 /* special positions in the game control window (relative to control window) */
39 #define XX_OVERLOAD 60
40 #define YY_OVERLOAD YY_ENERGY
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)
54 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
55 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
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
62 #define NUM_GAME_BUTTONS 3
64 /* values for DrawLaser() */
65 #define DL_LASER_DISABLED 0
66 #define DL_LASER_ENABLED 1
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 */
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)
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 *);
91 static int get_element_angle(int element)
93 int element_phase = get_element_phase(element);
95 if (IS_MIRROR_FIXED(element) ||
96 IS_MCDUFFIN(element) ||
99 return 4 * element_phase;
101 return element_phase;
104 static int get_opposite_angle(int angle)
106 int opposite_angle = angle + ANG_RAY_180;
108 /* make sure "opposite_angle" is in valid interval [0, 15] */
109 return (opposite_angle + 16) % 16;
112 static int get_mirrored_angle(int laser_angle, int mirror_angle)
114 int reflected_angle = 16 - laser_angle + mirror_angle;
116 /* make sure "reflected_angle" is in valid interval [0, 15] */
117 return (reflected_angle + 16) % 16;
120 static void InitMovDir_MM(int x, int y)
122 int element = Feld[x][y];
123 static int direction[3][4] =
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 }
132 case EL_PACMAN_RIGHT:
136 Feld[x][y] = EL_PACMAN;
137 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
145 static void InitField(int x, int y, boolean init_game)
147 int element = Feld[x][y];
152 Feld[x][y] = EL_EMPTY;
157 if (native_mm_level.auto_count_kettles)
158 game_mm.kettles_still_needed++;
161 case EL_LIGHTBULB_OFF:
162 game_mm.lights_still_needed++;
166 if (IS_MIRROR(element) ||
167 IS_BEAMER_OLD(element) ||
168 IS_BEAMER(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))
177 if (IS_BEAMER_OLD(element))
179 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
180 element = Feld[x][y];
183 if (!IS_FIBRE_OPTIC(element))
185 static int steps_grid_auto = 0;
187 if (game_mm.num_cycle == 0) /* initialize cycle steps for grids */
188 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
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;
194 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
196 game_mm.cycle[game_mm.num_cycle].x = x;
197 game_mm.cycle[game_mm.num_cycle].y = y;
201 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
203 int beamer_nr = BEAMER_NR(element);
204 int nr = laser.beamer[beamer_nr][0].num;
206 laser.beamer[beamer_nr][nr].x = x;
207 laser.beamer[beamer_nr][nr].y = y;
208 laser.beamer[beamer_nr][nr].num = 1;
211 else if (IS_PACMAN(element))
215 else if (IS_MCDUFFIN(element) || IS_LASER(element))
217 laser.start_edge.x = x;
218 laser.start_edge.y = y;
219 laser.start_angle = get_element_angle(element);
226 static void InitCycleElements_RotateSingleStep()
230 if (game_mm.num_cycle == 0) /* no elements to cycle */
233 for (i = 0; i < game_mm.num_cycle; i++)
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);
241 if (!game_mm.cycle[i].steps)
244 Feld[x][y] = next_element;
247 game_mm.cycle[i].steps -= step;
251 static void InitLaser()
253 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
254 int step = (IS_LASER(start_element) ? 4 : 0);
256 LX = laser.start_edge.x * TILEX;
257 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
260 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
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);
268 XS = 2 * Step[laser.start_angle].x;
269 YS = 2 * Step[laser.start_angle].y;
271 laser.current_angle = laser.start_angle;
273 laser.num_damages = 0;
275 laser.num_beamers = 0;
276 laser.beamer_edge[0] = 0;
278 laser.dest_element = EL_EMPTY;
281 AddLaserEdge(LX, LY); /* set laser starting edge */
283 pen_ray = GetPixelFromRGB(window,
284 native_mm_level.laser_red * 0xFF,
285 native_mm_level.laser_green * 0xFF,
286 native_mm_level.laser_blue * 0xFF);
289 void InitGameEngine_MM()
293 /* set global game control values */
294 game_mm.num_cycle = 0;
295 game_mm.num_pacman = 0;
298 game_mm.energy_left = 0; // later set to "native_mm_level.time"
299 game_mm.kettles_still_needed =
300 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
301 game_mm.lights_still_needed = 0;
302 game_mm.num_keys = 0;
304 game_mm.level_solved = FALSE;
305 game_mm.game_over = FALSE;
306 game_mm.game_over_cause = 0;
308 game_mm.laser_overload_value = 0;
310 /* set global laser control values (must be set before "InitLaser()") */
311 laser.start_edge.x = 0;
312 laser.start_edge.y = 0;
313 laser.start_angle = 0;
315 for (i = 0; i < MAX_NUM_BEAMERS; i++)
316 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
318 laser.overloaded = FALSE;
319 laser.overload_value = 0;
320 laser.fuse_off = FALSE;
321 laser.fuse_x = laser.fuse_y = -1;
323 laser.dest_element = EL_EMPTY;
328 for (x = 0; x < lev_fieldx; x++)
330 for (y = 0; y < lev_fieldy; y++)
332 Feld[x][y] = Ur[x][y];
333 Hit[x][y] = Box[x][y] = 0;
335 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
336 Store[x][y] = Store2[x][y] = 0;
340 InitField(x, y, TRUE);
345 CloseDoor(DOOR_CLOSE_1);
351 void InitGameActions_MM()
353 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
354 int cycle_steps_done = 0;
360 /* copy default game door content to main double buffer */
361 BlitBitmap(pix[PIX_DOOR], drawto,
362 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
366 DrawText(DX_LEVEL, DY_LEVEL,
367 int2str(level_nr, 2), FONT_TEXT_2);
368 DrawText(DX_KETTLES, DY_KETTLES,
369 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
370 DrawText(DX_SCORE, DY_SCORE,
371 int2str(game_mm.score, 4), FONT_TEXT_2);
380 /* copy actual game door content to door double buffer for OpenDoor() */
381 BlitBitmap(drawto, pix[PIX_DB_DOOR],
382 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
386 OpenDoor(DOOR_OPEN_ALL);
389 for (i = 0; i <= num_init_game_frames; i++)
391 if (i == num_init_game_frames)
392 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
393 else if (setup.sound_loops)
394 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
396 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
398 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
400 UpdateAndDisplayGameControlValues();
402 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
404 InitCycleElements_RotateSingleStep();
414 if (setup.quick_doors)
420 if (setup.sound_music && num_bg_loops)
421 PlayMusic(level_nr % num_bg_loops);
427 void AddLaserEdge(int lx, int ly)
429 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
431 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
436 laser.edge[laser.num_edges].x = SX + 2 + lx;
437 laser.edge[laser.num_edges].y = SY + 2 + ly;
443 void AddDamagedField(int ex, int ey)
445 laser.damage[laser.num_damages].is_mirror = FALSE;
446 laser.damage[laser.num_damages].angle = laser.current_angle;
447 laser.damage[laser.num_damages].edge = laser.num_edges;
448 laser.damage[laser.num_damages].x = ex;
449 laser.damage[laser.num_damages].y = ey;
459 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
460 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
462 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
468 static int getMaskFromElement(int element)
470 if (IS_GRID(element))
471 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
472 else if (IS_MCDUFFIN(element))
473 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
474 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
475 return IMG_MM_MASK_RECTANGLE;
477 return IMG_MM_MASK_CIRCLE;
485 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
486 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
489 /* follow laser beam until it hits something (at least the screen border) */
490 while (hit_mask == HIT_MASK_NO_HIT)
496 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
497 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
499 printf("ScanPixel: touched screen border!\n");
505 for (i = 0; i < 4; i++)
507 int px = LX + (i % 2) * 2;
508 int py = LY + (i / 2) * 2;
511 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
512 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
515 if (IN_LEV_FIELD(lx, ly))
517 int element = Feld[lx][ly];
519 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
523 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
525 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
527 pixel = ((element & (1 << pos)) ? 1 : 0);
531 int graphic_mask = getMaskFromElement(element);
536 getGraphicSource(graphic_mask, 0, &bitmap, &src_x, &src_y);
541 pixel = (ReadPixel(bitmap, mask_x, mask_y) ? 1 : 0);
546 pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
547 SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
550 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
551 hit_mask |= (1 << i);
554 if (hit_mask == HIT_MASK_NO_HIT)
556 /* hit nothing -- go on with another step */
568 int end = 0, rf = laser.num_edges;
570 /* do not scan laser again after the game was lost for whatever reason */
571 if (game_mm.game_over)
574 laser.overloaded = FALSE;
575 laser.stops_inside_element = FALSE;
577 DrawLaser(0, DL_LASER_ENABLED);
580 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
588 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
591 laser.overloaded = TRUE;
596 hit_mask = ScanPixel();
599 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
603 /* hit something -- check out what it was */
604 ELX = (LX + XS) / TILEX;
605 ELY = (LY + YS) / TILEY;
608 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
609 hit_mask, LX, LY, ELX, ELY);
612 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
615 laser.dest_element = element;
620 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
622 /* we have hit the top-right and bottom-left element --
623 choose the bottom-left one */
624 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
625 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
626 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
627 ELX = (LX - 2) / TILEX;
628 ELY = (LY + 2) / TILEY;
631 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
633 /* we have hit the top-left and bottom-right element --
634 choose the top-left one */
635 /* !!! SEE ABOVE !!! */
636 ELX = (LX - 2) / TILEX;
637 ELY = (LY - 2) / TILEY;
641 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
642 hit_mask, LX, LY, ELX, ELY);
645 element = Feld[ELX][ELY];
646 laser.dest_element = element;
649 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
652 LX % TILEX, LY % TILEY,
657 if (!IN_LEV_FIELD(ELX, ELY))
658 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
661 if (element == EL_EMPTY)
663 if (!HitOnlyAnEdge(element, hit_mask))
666 else if (element == EL_FUSE_ON)
668 if (HitPolarizer(element, hit_mask))
671 else if (IS_GRID(element) || IS_DF_GRID(element))
673 if (HitPolarizer(element, hit_mask))
676 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
677 element == EL_GATE_STONE || element == EL_GATE_WOOD)
679 if (HitBlock(element, hit_mask))
686 else if (IS_MCDUFFIN(element))
688 if (HitLaserSource(element, hit_mask))
691 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
692 IS_RECEIVER(element))
694 if (HitLaserDestination(element, hit_mask))
697 else if (IS_WALL(element))
699 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
701 if (HitReflectingWalls(element, hit_mask))
706 if (HitAbsorbingWalls(element, hit_mask))
712 if (HitElement(element, hit_mask))
717 DrawLaser(rf - 1, DL_LASER_ENABLED);
718 rf = laser.num_edges;
722 if (laser.dest_element != Feld[ELX][ELY])
724 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
725 laser.dest_element, Feld[ELX][ELY]);
729 if (!end && !laser.stops_inside_element && !StepBehind())
732 printf("ScanLaser: Go one step back\n");
738 AddLaserEdge(LX, LY);
742 DrawLaser(rf - 1, DL_LASER_ENABLED);
744 Ct = CT = FrameCounter;
747 if (!IN_LEV_FIELD(ELX, ELY))
748 printf("WARNING! (2) %d, %d\n", ELX, ELY);
752 void DrawLaserExt(int start_edge, int num_edges, int mode)
758 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
759 start_edge, num_edges, mode);
764 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
771 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
777 if (mode == DL_LASER_DISABLED)
779 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
783 /* now draw the laser to the backbuffer and (if enabled) to the screen */
784 DrawLines(drawto, &laser.edge[start_edge], num_edges,
785 (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
787 redraw_mask |= REDRAW_FIELD;
789 if (mode == DL_LASER_ENABLED)
792 /* after the laser was deleted, the "damaged" graphics must be restored */
793 if (laser.num_damages)
795 int damage_start = 0;
798 /* determine the starting edge, from which graphics need to be restored */
801 for (i = 0; i < laser.num_damages; i++)
803 if (laser.damage[i].edge == start_edge + 1)
812 /* restore graphics from this starting edge to the end of damage list */
813 for (i = damage_start; i < laser.num_damages; i++)
815 int lx = laser.damage[i].x;
816 int ly = laser.damage[i].y;
817 int element = Feld[lx][ly];
819 if (Hit[lx][ly] == laser.damage[i].edge)
820 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
823 if (Box[lx][ly] == laser.damage[i].edge)
826 if (IS_DRAWABLE(element))
827 DrawField_MM(lx, ly);
830 elx = laser.damage[damage_start].x;
831 ely = laser.damage[damage_start].y;
832 element = Feld[elx][ely];
835 if (IS_BEAMER(element))
839 for (i = 0; i < laser.num_beamers; i++)
840 printf("-> %d\n", laser.beamer_edge[i]);
841 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
842 mode, elx, ely, Hit[elx][ely], start_edge);
843 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
844 get_element_angle(element), laser.damage[damage_start].angle);
848 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
849 laser.num_beamers > 0 &&
850 start_edge == laser.beamer_edge[laser.num_beamers - 1])
852 /* element is outgoing beamer */
853 laser.num_damages = damage_start + 1;
855 if (IS_BEAMER(element))
856 laser.current_angle = get_element_angle(element);
860 /* element is incoming beamer or other element */
861 laser.num_damages = damage_start;
862 laser.current_angle = laser.damage[laser.num_damages].angle;
867 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
869 elx = laser.start_edge.x;
870 ely = laser.start_edge.y;
871 element = Feld[elx][ely];
874 laser.num_edges = start_edge + 1;
876 laser.current_angle = laser.start_angle;
878 LX = laser.edge[start_edge].x - (SX + 2);
879 LY = laser.edge[start_edge].y - (SY + 2);
880 XS = 2 * Step[laser.current_angle].x;
881 YS = 2 * Step[laser.current_angle].y;
884 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
890 if (IS_BEAMER(element) ||
891 IS_FIBRE_OPTIC(element) ||
892 IS_PACMAN(element) ||
894 IS_POLAR_CROSS(element) ||
895 element == EL_FUSE_ON)
900 printf("element == %d\n", element);
903 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
904 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
908 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
909 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
910 (laser.num_beamers == 0 ||
911 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
913 /* element is incoming beamer or other element */
914 step_size = -step_size;
919 if (IS_BEAMER(element))
921 printf("start_edge == %d, laser.beamer_edge == %d\n",
922 start_edge, laser.beamer_edge);
926 LX += step_size * XS;
927 LY += step_size * YS;
929 else if (element != EL_EMPTY)
938 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
943 void DrawLaser(int start_edge, int mode)
945 if (laser.num_edges - start_edge < 0)
947 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
952 /* check if laser is interrupted by beamer element */
953 if (laser.num_beamers > 0 &&
954 start_edge < laser.beamer_edge[laser.num_beamers - 1])
956 if (mode == DL_LASER_ENABLED)
959 int tmp_start_edge = start_edge;
961 /* draw laser segments forward from the start to the last beamer */
962 for (i = 0; i < laser.num_beamers; i++)
964 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
966 if (tmp_num_edges <= 0)
970 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
971 i, laser.beamer_edge[i], tmp_start_edge);
974 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
976 tmp_start_edge = laser.beamer_edge[i];
979 /* draw last segment from last beamer to the end */
980 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
986 int last_num_edges = laser.num_edges;
987 int num_beamers = laser.num_beamers;
989 /* delete laser segments backward from the end to the first beamer */
990 for (i = num_beamers-1; i >= 0; i--)
992 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
994 if (laser.beamer_edge[i] - start_edge <= 0)
997 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
999 last_num_edges = laser.beamer_edge[i];
1000 laser.num_beamers--;
1004 if (last_num_edges - start_edge <= 0)
1005 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1006 last_num_edges, start_edge);
1009 /* delete first segment from start to the first beamer */
1010 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1015 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1019 boolean HitElement(int element, int hit_mask)
1021 if (HitOnlyAnEdge(element, hit_mask))
1024 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1025 element = MovingOrBlocked2Element_MM(ELX, ELY);
1028 printf("HitElement (1): element == %d\n", element);
1032 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1033 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1035 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1038 AddDamagedField(ELX, ELY);
1040 /* this is more precise: check if laser would go through the center */
1041 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1043 /* skip the whole element before continuing the scan */
1049 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1051 if (LX/TILEX > ELX || LY/TILEY > ELY)
1053 /* skipping scan positions to the right and down skips one scan
1054 position too much, because this is only the top left scan position
1055 of totally four scan positions (plus one to the right, one to the
1056 bottom and one to the bottom right) */
1066 printf("HitElement (2): element == %d\n", element);
1069 if (LX + 5 * XS < 0 ||
1079 printf("HitElement (3): element == %d\n", element);
1082 if (IS_POLAR(element) &&
1083 ((element - EL_POLAR_START) % 2 ||
1084 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1086 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1088 laser.num_damages--;
1093 if (IS_POLAR_CROSS(element) &&
1094 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1096 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1098 laser.num_damages--;
1103 if (!IS_BEAMER(element) &&
1104 !IS_FIBRE_OPTIC(element) &&
1105 !IS_GRID_WOOD(element) &&
1106 element != EL_FUEL_EMPTY)
1109 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1110 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1112 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1115 LX = ELX * TILEX + 14;
1116 LY = ELY * TILEY + 14;
1118 AddLaserEdge(LX, LY);
1121 if (IS_MIRROR(element) ||
1122 IS_MIRROR_FIXED(element) ||
1123 IS_POLAR(element) ||
1124 IS_POLAR_CROSS(element) ||
1125 IS_DF_MIRROR(element) ||
1126 IS_DF_MIRROR_AUTO(element) ||
1127 element == EL_PRISM ||
1128 element == EL_REFRACTOR)
1130 int current_angle = laser.current_angle;
1133 laser.num_damages--;
1135 AddDamagedField(ELX, ELY);
1137 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1140 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1142 if (IS_MIRROR(element) ||
1143 IS_MIRROR_FIXED(element) ||
1144 IS_DF_MIRROR(element) ||
1145 IS_DF_MIRROR_AUTO(element))
1146 laser.current_angle = get_mirrored_angle(laser.current_angle,
1147 get_element_angle(element));
1149 if (element == EL_PRISM || element == EL_REFRACTOR)
1150 laser.current_angle = RND(16);
1152 XS = 2 * Step[laser.current_angle].x;
1153 YS = 2 * Step[laser.current_angle].y;
1155 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1160 LX += step_size * XS;
1161 LY += step_size * YS;
1164 /* draw sparkles on mirror */
1165 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1166 current_angle != laser.current_angle)
1168 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1172 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1173 current_angle != laser.current_angle)
1174 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1177 (get_opposite_angle(laser.current_angle) ==
1178 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1180 return (laser.overloaded ? TRUE : FALSE);
1183 if (element == EL_FUEL_FULL)
1185 laser.stops_inside_element = TRUE;
1190 if (element == EL_BOMB || element == EL_MINE)
1192 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1194 if (element == EL_MINE)
1195 laser.overloaded = TRUE;
1198 if (element == EL_KETTLE ||
1199 element == EL_CELL ||
1200 element == EL_KEY ||
1201 element == EL_LIGHTBALL ||
1202 element == EL_PACMAN ||
1205 if (!IS_PACMAN(element))
1208 if (element == EL_PACMAN)
1211 if (element == EL_KETTLE || element == EL_CELL)
1213 if (game_mm.kettles_still_needed > 0)
1214 game_mm.kettles_still_needed--;
1218 if (game_mm.kettles_still_needed == 0)
1220 int exit_element = (element == EL_KETTLE ? EL_EXIT_OPEN : EL_RECEIVER);
1222 static int xy[4][2] =
1230 PlayLevelSound_MM(ELX, ELY, exit_element, MM_ACTION_OPENING);
1232 for (y = 0; y < lev_fieldy; y++)
1234 for (x = 0; x < lev_fieldx; x++)
1236 /* initiate opening animation of exit door */
1237 if (Feld[x][y] == EL_EXIT_CLOSED)
1238 Feld[x][y] = EL_EXIT_OPENING;
1240 /* remove field that blocks receiver */
1241 if (IS_RECEIVER(Feld[x][y]))
1243 int phase = Feld[x][y] - EL_RECEIVER_START;
1244 int blocking_x, blocking_y;
1246 blocking_x = x + xy[phase][0];
1247 blocking_y = y + xy[phase][1];
1249 if (IN_LEV_FIELD(blocking_x, blocking_y))
1251 Feld[blocking_x][blocking_y] = EL_EMPTY;
1253 DrawField_MM(blocking_x, blocking_y);
1259 DrawLaser(0, DL_LASER_ENABLED);
1262 else if (element == EL_KEY)
1266 else if (element == EL_LIGHTBALL)
1270 else if (IS_PACMAN(element))
1272 DeletePacMan(ELX, ELY);
1279 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1281 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1283 DrawLaser(0, DL_LASER_ENABLED);
1285 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1287 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1288 game_mm.lights_still_needed--;
1292 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1293 game_mm.lights_still_needed++;
1296 DrawField_MM(ELX, ELY);
1297 DrawLaser(0, DL_LASER_ENABLED);
1302 laser.stops_inside_element = TRUE;
1308 printf("HitElement (4): element == %d\n", element);
1311 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1312 laser.num_beamers < MAX_NUM_BEAMERS &&
1313 laser.beamer[BEAMER_NR(element)][1].num)
1315 int beamer_angle = get_element_angle(element);
1316 int beamer_nr = BEAMER_NR(element);
1320 printf("HitElement (BEAMER): element == %d\n", element);
1323 laser.num_damages--;
1325 if (IS_FIBRE_OPTIC(element) ||
1326 laser.current_angle == get_opposite_angle(beamer_angle))
1330 LX = ELX * TILEX + 14;
1331 LY = ELY * TILEY + 14;
1333 AddLaserEdge(LX, LY);
1334 AddDamagedField(ELX, ELY);
1336 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1339 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1341 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1342 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1343 ELX = laser.beamer[beamer_nr][pos].x;
1344 ELY = laser.beamer[beamer_nr][pos].y;
1345 LX = ELX * TILEX + 14;
1346 LY = ELY * TILEY + 14;
1348 if (IS_BEAMER(element))
1350 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1351 XS = 2 * Step[laser.current_angle].x;
1352 YS = 2 * Step[laser.current_angle].y;
1355 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1357 AddLaserEdge(LX, LY);
1358 AddDamagedField(ELX, ELY);
1360 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1363 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1365 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1370 LX += step_size * XS;
1371 LY += step_size * YS;
1373 laser.num_beamers++;
1382 boolean HitOnlyAnEdge(int element, int hit_mask)
1384 /* check if the laser hit only the edge of an element and, if so, go on */
1387 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1390 if ((hit_mask == HIT_MASK_TOPLEFT ||
1391 hit_mask == HIT_MASK_TOPRIGHT ||
1392 hit_mask == HIT_MASK_BOTTOMLEFT ||
1393 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1394 laser.current_angle % 4) /* angle is not 90° */
1398 if (hit_mask == HIT_MASK_TOPLEFT)
1403 else if (hit_mask == HIT_MASK_TOPRIGHT)
1408 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1413 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1419 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1425 printf("[HitOnlyAnEdge() == TRUE]\n");
1432 printf("[HitOnlyAnEdge() == FALSE]\n");
1438 boolean HitPolarizer(int element, int hit_mask)
1440 if (HitOnlyAnEdge(element, hit_mask))
1443 if (IS_DF_GRID(element))
1445 int grid_angle = get_element_angle(element);
1448 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1449 grid_angle, laser.current_angle);
1452 AddLaserEdge(LX, LY);
1453 AddDamagedField(ELX, ELY);
1456 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1458 if (laser.current_angle == grid_angle ||
1459 laser.current_angle == get_opposite_angle(grid_angle))
1461 /* skip the whole element before continuing the scan */
1467 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1469 if (LX/TILEX > ELX || LY/TILEY > ELY)
1471 /* skipping scan positions to the right and down skips one scan
1472 position too much, because this is only the top left scan position
1473 of totally four scan positions (plus one to the right, one to the
1474 bottom and one to the bottom right) */
1480 AddLaserEdge(LX, LY);
1486 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1488 LX / TILEX, LY / TILEY,
1489 LX % TILEX, LY % TILEY);
1494 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1496 return HitReflectingWalls(element, hit_mask);
1500 return HitAbsorbingWalls(element, hit_mask);
1503 else if (IS_GRID_STEEL(element))
1505 return HitReflectingWalls(element, hit_mask);
1507 else /* IS_GRID_WOOD */
1509 return HitAbsorbingWalls(element, hit_mask);
1515 boolean HitBlock(int element, int hit_mask)
1517 boolean check = FALSE;
1519 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1520 game_mm.num_keys == 0)
1523 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1526 int ex = ELX * TILEX + 14;
1527 int ey = ELY * TILEY + 14;
1531 for (i = 1; i < 32; i++)
1536 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1541 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1542 return HitAbsorbingWalls(element, hit_mask);
1546 AddLaserEdge(LX - XS, LY - YS);
1547 AddDamagedField(ELX, ELY);
1550 Box[ELX][ELY] = laser.num_edges;
1552 return HitReflectingWalls(element, hit_mask);
1555 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1557 int xs = XS / 2, ys = YS / 2;
1558 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1559 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1561 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1562 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1564 laser.overloaded = (element == EL_GATE_STONE);
1569 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1570 (hit_mask == HIT_MASK_TOP ||
1571 hit_mask == HIT_MASK_LEFT ||
1572 hit_mask == HIT_MASK_RIGHT ||
1573 hit_mask == HIT_MASK_BOTTOM))
1574 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1575 hit_mask == HIT_MASK_BOTTOM),
1576 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1577 hit_mask == HIT_MASK_RIGHT));
1578 AddLaserEdge(LX, LY);
1584 if (element == EL_GATE_STONE && Box[ELX][ELY])
1586 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1598 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1600 int xs = XS / 2, ys = YS / 2;
1601 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1602 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1604 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1605 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1607 laser.overloaded = (element == EL_BLOCK_STONE);
1612 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1613 (hit_mask == HIT_MASK_TOP ||
1614 hit_mask == HIT_MASK_LEFT ||
1615 hit_mask == HIT_MASK_RIGHT ||
1616 hit_mask == HIT_MASK_BOTTOM))
1617 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1618 hit_mask == HIT_MASK_BOTTOM),
1619 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1620 hit_mask == HIT_MASK_RIGHT));
1621 AddDamagedField(ELX, ELY);
1623 LX = ELX * TILEX + 14;
1624 LY = ELY * TILEY + 14;
1626 AddLaserEdge(LX, LY);
1628 laser.stops_inside_element = TRUE;
1636 boolean HitLaserSource(int element, int hit_mask)
1638 if (HitOnlyAnEdge(element, hit_mask))
1641 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1643 laser.overloaded = TRUE;
1648 boolean HitLaserDestination(int element, int hit_mask)
1650 if (HitOnlyAnEdge(element, hit_mask))
1653 if (element != EL_EXIT_OPEN &&
1654 !(IS_RECEIVER(element) &&
1655 game_mm.kettles_still_needed == 0 &&
1656 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1658 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1663 if (IS_RECEIVER(element) ||
1664 (IS_22_5_ANGLE(laser.current_angle) &&
1665 (ELX != (LX + 6 * XS) / TILEX ||
1666 ELY != (LY + 6 * YS) / TILEY ||
1675 LX = ELX * TILEX + 14;
1676 LY = ELY * TILEY + 14;
1678 laser.stops_inside_element = TRUE;
1681 AddLaserEdge(LX, LY);
1682 AddDamagedField(ELX, ELY);
1684 if (game_mm.lights_still_needed == 0)
1685 game_mm.level_solved = TRUE;
1690 boolean HitReflectingWalls(int element, int hit_mask)
1692 /* check if laser hits side of a wall with an angle that is not 90° */
1693 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1694 hit_mask == HIT_MASK_LEFT ||
1695 hit_mask == HIT_MASK_RIGHT ||
1696 hit_mask == HIT_MASK_BOTTOM))
1698 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1703 if (!IS_DF_GRID(element))
1704 AddLaserEdge(LX, LY);
1706 /* check if laser hits wall with an angle of 45° */
1707 if (!IS_22_5_ANGLE(laser.current_angle))
1709 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1712 laser.current_angle = get_mirrored_angle(laser.current_angle,
1715 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1718 laser.current_angle = get_mirrored_angle(laser.current_angle,
1722 AddLaserEdge(LX, LY);
1724 XS = 2 * Step[laser.current_angle].x;
1725 YS = 2 * Step[laser.current_angle].y;
1729 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1731 laser.current_angle = get_mirrored_angle(laser.current_angle,
1736 if (!IS_DF_GRID(element))
1737 AddLaserEdge(LX, LY);
1742 if (!IS_DF_GRID(element))
1743 AddLaserEdge(LX, LY + YS / 2);
1746 if (!IS_DF_GRID(element))
1747 AddLaserEdge(LX, LY);
1750 YS = 2 * Step[laser.current_angle].y;
1754 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1756 laser.current_angle = get_mirrored_angle(laser.current_angle,
1761 if (!IS_DF_GRID(element))
1762 AddLaserEdge(LX, LY);
1767 if (!IS_DF_GRID(element))
1768 AddLaserEdge(LX + XS / 2, LY);
1771 if (!IS_DF_GRID(element))
1772 AddLaserEdge(LX, LY);
1775 XS = 2 * Step[laser.current_angle].x;
1781 /* reflection at the edge of reflecting DF style wall */
1782 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1784 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1785 hit_mask == HIT_MASK_TOPRIGHT) ||
1786 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1787 hit_mask == HIT_MASK_TOPLEFT) ||
1788 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1789 hit_mask == HIT_MASK_BOTTOMLEFT) ||
1790 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1791 hit_mask == HIT_MASK_BOTTOMRIGHT))
1794 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
1795 ANG_MIRROR_135 : ANG_MIRROR_45);
1797 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1799 AddDamagedField(ELX, ELY);
1800 AddLaserEdge(LX, LY);
1802 laser.current_angle = get_mirrored_angle(laser.current_angle,
1810 AddLaserEdge(LX, LY);
1816 /* reflection inside an edge of reflecting DF style wall */
1817 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1819 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1820 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
1821 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1822 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
1823 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1824 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
1825 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1826 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
1829 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
1830 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
1831 ANG_MIRROR_135 : ANG_MIRROR_45);
1833 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1836 AddDamagedField(ELX, ELY);
1839 AddLaserEdge(LX - XS, LY - YS);
1840 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
1841 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
1843 laser.current_angle = get_mirrored_angle(laser.current_angle,
1851 AddLaserEdge(LX, LY);
1857 /* check if laser hits DF style wall with an angle of 90° */
1858 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
1860 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
1861 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
1862 (IS_VERT_ANGLE(laser.current_angle) &&
1863 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
1865 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
1867 /* laser at last step touched nothing or the same side of the wall */
1868 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
1870 AddDamagedField(ELX, ELY);
1877 last_hit_mask = hit_mask;
1884 if (!HitOnlyAnEdge(element, hit_mask))
1886 laser.overloaded = TRUE;
1894 boolean HitAbsorbingWalls(int element, int hit_mask)
1896 if (HitOnlyAnEdge(element, hit_mask))
1900 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
1902 AddLaserEdge(LX - XS, LY - YS);
1909 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
1911 AddLaserEdge(LX - XS, LY - YS);
1917 if (IS_WALL_WOOD(element) ||
1918 IS_DF_WALL_WOOD(element) ||
1919 IS_GRID_WOOD(element) ||
1920 IS_GRID_WOOD_FIXED(element) ||
1921 IS_GRID_WOOD_AUTO(element) ||
1922 element == EL_FUSE_ON ||
1923 element == EL_BLOCK_WOOD ||
1924 element == EL_GATE_WOOD)
1926 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1931 if (IS_WALL_ICE(element))
1935 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
1936 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
1938 /* check if laser hits wall with an angle of 90° */
1939 if (IS_90_ANGLE(laser.current_angle))
1940 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1942 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
1946 for (i = 0; i < 4; i++)
1948 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
1949 mask = 15 - (8 >> i);
1950 else if (ABS(XS) == 4 &&
1952 (XS > 0) == (i % 2) &&
1953 (YS < 0) == (i / 2))
1954 mask = 3 + (i / 2) * 9;
1955 else if (ABS(YS) == 4 &&
1957 (XS < 0) == (i % 2) &&
1958 (YS > 0) == (i / 2))
1959 mask = 5 + (i % 2) * 5;
1963 laser.wall_mask = mask;
1965 else if (IS_WALL_AMOEBA(element))
1967 int elx = (LX - 2 * XS) / TILEX;
1968 int ely = (LY - 2 * YS) / TILEY;
1969 int element2 = Feld[elx][ely];
1972 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
1974 laser.dest_element = EL_EMPTY;
1982 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
1983 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
1985 if (IS_90_ANGLE(laser.current_angle))
1986 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1988 laser.dest_element = element2 | EL_WALL_AMOEBA;
1990 laser.wall_mask = mask;
1996 void OpenExit(int x, int y)
2000 if (!MovDelay[x][y]) /* next animation frame */
2001 MovDelay[x][y] = 4 * delay;
2003 if (MovDelay[x][y]) /* wait some time before next frame */
2008 phase = MovDelay[x][y] / delay;
2010 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2011 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2013 if (!MovDelay[x][y])
2015 Feld[x][y] = EL_EXIT_OPEN;
2021 void OpenSurpriseBall(int x, int y)
2025 if (!MovDelay[x][y]) /* next animation frame */
2026 MovDelay[x][y] = 50 * delay;
2028 if (MovDelay[x][y]) /* wait some time before next frame */
2032 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2035 int graphic = el2gfx(Store[x][y]);
2037 int dx = RND(26), dy = RND(26);
2039 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2041 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2042 SX + x * TILEX + dx, SY + y * TILEY + dy);
2044 MarkTileDirty(x, y);
2047 if (!MovDelay[x][y])
2049 Feld[x][y] = Store[x][y];
2058 void MeltIce(int x, int y)
2063 if (!MovDelay[x][y]) /* next animation frame */
2064 MovDelay[x][y] = frames * delay;
2066 if (MovDelay[x][y]) /* wait some time before next frame */
2069 int wall_mask = Store2[x][y];
2070 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2073 phase = frames - MovDelay[x][y] / delay - 1;
2075 if (!MovDelay[x][y])
2079 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2080 Store[x][y] = Store2[x][y] = 0;
2082 DrawWalls_MM(x, y, Feld[x][y]);
2084 if (Feld[x][y] == EL_WALL_ICE)
2085 Feld[x][y] = EL_EMPTY;
2087 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2088 if (laser.damage[i].is_mirror)
2092 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2094 DrawLaser(0, DL_LASER_DISABLED);
2098 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2100 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2102 laser.redraw = TRUE;
2107 void GrowAmoeba(int x, int y)
2112 if (!MovDelay[x][y]) /* next animation frame */
2113 MovDelay[x][y] = frames * delay;
2115 if (MovDelay[x][y]) /* wait some time before next frame */
2118 int wall_mask = Store2[x][y];
2119 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2122 phase = MovDelay[x][y] / delay;
2124 if (!MovDelay[x][y])
2126 Feld[x][y] = real_element;
2127 Store[x][y] = Store2[x][y] = 0;
2129 DrawWalls_MM(x, y, Feld[x][y]);
2130 DrawLaser(0, DL_LASER_ENABLED);
2132 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2134 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2139 static void Explode_MM(int x, int y, int phase, int mode)
2141 int num_phase = 9, delay = 2;
2142 int last_phase = num_phase * delay;
2143 int half_phase = (num_phase / 2) * delay;
2145 laser.redraw = TRUE;
2147 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2149 int center_element = Feld[x][y];
2151 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2153 /* put moving element to center field (and let it explode there) */
2154 center_element = MovingOrBlocked2Element_MM(x, y);
2155 RemoveMovingField_MM(x, y);
2157 Feld[x][y] = center_element;
2160 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2161 Store[x][y] = center_element;
2163 Store[x][y] = EL_EMPTY;
2165 Store2[x][y] = mode;
2166 Feld[x][y] = EL_EXPLODING_OPAQUE;
2167 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2173 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2175 if (phase == half_phase)
2177 Feld[x][y] = EL_EXPLODING_TRANSP;
2179 if (x == ELX && y == ELY)
2183 if (phase == last_phase)
2185 if (Store[x][y] == EL_BOMB)
2187 DrawLaser(0, DL_LASER_DISABLED);
2190 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2191 Store[x][y] = EL_EMPTY;
2193 game_mm.game_over = TRUE;
2194 game_mm.game_over_cause = GAME_OVER_BOMB;
2196 laser.overloaded = FALSE;
2198 else if (IS_MCDUFFIN(Store[x][y]))
2200 Store[x][y] = EL_EMPTY;
2203 Feld[x][y] = Store[x][y];
2204 Store[x][y] = Store2[x][y] = 0;
2205 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2207 InitField(x, y, FALSE);
2210 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2212 int graphic = IMG_MM_DEFAULT_EXPLODING;
2213 int graphic_phase = (phase / delay - 1);
2217 if (Store2[x][y] == EX_KETTLE)
2219 if (graphic_phase < 3)
2221 graphic = IMG_MM_KETTLE_EXPLODING;
2223 else if (graphic_phase < 5)
2229 graphic = IMG_EMPTY;
2233 else if (Store2[x][y] == EX_SHORT)
2235 if (graphic_phase < 4)
2241 graphic = IMG_EMPTY;
2246 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2248 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2249 FX + x * TILEX, FY + y * TILEY);
2251 MarkTileDirty(x, y);
2255 static void Bang_MM(int x, int y)
2257 int element = Feld[x][y];
2258 int mode = EX_NORMAL;
2261 DrawLaser(0, DL_LASER_ENABLED);
2280 if (IS_PACMAN(element))
2281 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2282 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2283 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2284 else if (element == EL_KEY)
2285 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2287 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2289 Explode_MM(x, y, EX_PHASE_START, mode);
2292 void TurnRound(int x, int y)
2304 { 0, 0 }, { 0, 0 }, { 0, 0 },
2309 int left, right, back;
2313 { MV_DOWN, MV_UP, MV_RIGHT },
2314 { MV_UP, MV_DOWN, MV_LEFT },
2316 { MV_LEFT, MV_RIGHT, MV_DOWN },
2317 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2318 { MV_RIGHT, MV_LEFT, MV_UP }
2321 int element = Feld[x][y];
2322 int old_move_dir = MovDir[x][y];
2323 int right_dir = turn[old_move_dir].right;
2324 int back_dir = turn[old_move_dir].back;
2325 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2326 int right_x = x + right_dx, right_y = y + right_dy;
2328 if (element == EL_PACMAN)
2330 boolean can_turn_right = FALSE;
2332 if (IN_LEV_FIELD(right_x, right_y) &&
2333 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2334 can_turn_right = TRUE;
2337 MovDir[x][y] = right_dir;
2339 MovDir[x][y] = back_dir;
2345 static void StartMoving_MM(int x, int y)
2347 int element = Feld[x][y];
2352 if (CAN_MOVE(element))
2356 if (MovDelay[x][y]) /* wait some time before next movement */
2364 /* now make next step */
2366 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2368 if (element == EL_PACMAN &&
2369 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2370 !ObjHit(newx, newy, HIT_POS_CENTER))
2372 Store[newx][newy] = Feld[newx][newy];
2373 Feld[newx][newy] = EL_EMPTY;
2375 DrawField_MM(newx, newy);
2377 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2378 ObjHit(newx, newy, HIT_POS_CENTER))
2380 /* object was running against a wall */
2387 InitMovingField_MM(x, y, MovDir[x][y]);
2391 ContinueMoving_MM(x, y);
2394 static void ContinueMoving_MM(int x, int y)
2396 int element = Feld[x][y];
2397 int direction = MovDir[x][y];
2398 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2399 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2400 int horiz_move = (dx!=0);
2401 int newx = x + dx, newy = y + dy;
2402 int step = (horiz_move ? dx : dy) * TILEX / 8;
2404 MovPos[x][y] += step;
2406 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2408 Feld[x][y] = EL_EMPTY;
2409 Feld[newx][newy] = element;
2411 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2412 MovDelay[newx][newy] = 0;
2414 if (!CAN_MOVE(element))
2415 MovDir[newx][newy] = 0;
2418 DrawField_MM(newx, newy);
2420 Stop[newx][newy] = TRUE;
2422 if (element == EL_PACMAN)
2424 if (Store[newx][newy] == EL_BOMB)
2425 Bang_MM(newx, newy);
2427 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2428 (LX + 2 * XS) / TILEX == newx &&
2429 (LY + 2 * YS) / TILEY == newy)
2436 else /* still moving on */
2441 laser.redraw = TRUE;
2444 void ClickElement(int x, int y, int button)
2446 static unsigned int click_delay = 0;
2447 static int click_delay_value = CLICK_DELAY;
2448 static boolean new_button = TRUE;
2451 /* do not rotate objects hit by the laser after the game was solved */
2452 if (game_mm.level_solved && Hit[x][y])
2455 if (button == MB_RELEASED)
2458 click_delay_value = CLICK_DELAY;
2460 /* release eventually hold auto-rotating mirror */
2461 RotateMirror(x, y, MB_RELEASED);
2466 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2469 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2472 if (!IN_LEV_FIELD(x, y))
2475 if (Feld[x][y] == EL_EMPTY)
2478 element = Feld[x][y];
2480 if (IS_MIRROR(element) ||
2481 IS_BEAMER(element) ||
2482 IS_POLAR(element) ||
2483 IS_POLAR_CROSS(element) ||
2484 IS_DF_MIRROR(element) ||
2485 IS_DF_MIRROR_AUTO(element))
2487 RotateMirror(x, y, button);
2489 else if (IS_MCDUFFIN(element))
2491 if (!laser.fuse_off)
2493 DrawLaser(0, DL_LASER_DISABLED);
2500 element = get_rotated_element(element, BUTTON_ROTATION(button));
2501 laser.start_angle = get_element_angle(element);
2505 Feld[x][y] = element;
2512 if (!laser.fuse_off)
2515 else if (element == EL_FUSE_ON && laser.fuse_off)
2517 if (x != laser.fuse_x || y != laser.fuse_y)
2520 laser.fuse_off = FALSE;
2521 laser.fuse_x = laser.fuse_y = -1;
2523 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2526 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2528 laser.fuse_off = TRUE;
2531 laser.overloaded = FALSE;
2533 DrawLaser(0, DL_LASER_DISABLED);
2534 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2536 else if (element == EL_LIGHTBALL)
2540 DrawLaser(0, DL_LASER_ENABLED);
2543 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2547 void RotateMirror(int x, int y, int button)
2549 static int hold_x = -1, hold_y = -1;
2551 if (button == MB_RELEASED)
2553 /* release eventually hold auto-rotating mirror */
2560 if (IS_MIRROR(Feld[x][y]) ||
2561 IS_POLAR_CROSS(Feld[x][y]) ||
2562 IS_POLAR(Feld[x][y]) ||
2563 IS_BEAMER(Feld[x][y]) ||
2564 IS_DF_MIRROR(Feld[x][y]) ||
2565 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2566 IS_GRID_WOOD_AUTO(Feld[x][y]))
2568 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2570 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2572 if (button == MB_LEFTBUTTON)
2574 /* left mouse button only for manual adjustment, no auto-rotating;
2575 freeze mirror for until mouse button released */
2579 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2581 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2585 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2587 int edge = Hit[x][y];
2593 DrawLaser(edge - 1, DL_LASER_DISABLED);
2597 else if (ObjHit(x, y, HIT_POS_CENTER))
2599 int edge = Hit[x][y];
2603 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2607 DrawLaser(edge - 1, DL_LASER_DISABLED);
2614 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2619 if ((IS_BEAMER(Feld[x][y]) ||
2620 IS_POLAR(Feld[x][y]) ||
2621 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2625 if (IS_BEAMER(Feld[x][y]))
2628 printf("TEST (%d, %d) [%d] [%d]\n",
2630 laser.beamer_edge, laser.beamer[1].num);
2640 DrawLaser(0, DL_LASER_ENABLED);
2644 void AutoRotateMirrors()
2646 static unsigned int rotate_delay = 0;
2649 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2652 for (x = 0; x < lev_fieldx; x++)
2654 for (y = 0; y < lev_fieldy; y++)
2656 int element = Feld[x][y];
2658 /* do not rotate objects hit by the laser after the game was solved */
2659 if (game_mm.level_solved && Hit[x][y])
2662 if (IS_DF_MIRROR_AUTO(element) ||
2663 IS_GRID_WOOD_AUTO(element) ||
2664 IS_GRID_STEEL_AUTO(element) ||
2665 element == EL_REFRACTOR)
2666 RotateMirror(x, y, MB_RIGHTBUTTON);
2671 boolean ObjHit(int obx, int oby, int bits)
2678 if (bits & HIT_POS_CENTER)
2680 if (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
2684 if (bits & HIT_POS_EDGE)
2686 for (i = 0; i < 4; i++)
2687 if (ReadPixel(drawto,
2688 SX + obx + 31 * (i % 2),
2689 SY + oby + 31 * (i / 2)) == pen_ray)
2693 if (bits & HIT_POS_BETWEEN)
2695 for (i = 0; i < 4; i++)
2696 if (ReadPixel(drawto,
2697 SX + 4 + obx + 22 * (i % 2),
2698 SY + 4 + oby + 22 * (i / 2)) == pen_ray)
2705 void DeletePacMan(int px, int py)
2711 if (game_mm.num_pacman <= 1)
2713 game_mm.num_pacman = 0;
2717 for (i = 0; i < game_mm.num_pacman; i++)
2718 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2721 game_mm.num_pacman--;
2723 for (j = i; j < game_mm.num_pacman; j++)
2725 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
2726 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
2727 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2731 void ColorCycling(void)
2733 static int CC, Cc = 0;
2735 static int color, old = 0xF00, new = 0x010, mult = 1;
2736 static unsigned short red, green, blue;
2738 if (color_status == STATIC_COLORS)
2743 if (CC < Cc || CC > Cc + 2)
2747 color = old + new * mult;
2753 if (ABS(mult) == 16)
2763 red = 0x0e00 * ((color & 0xF00) >> 8);
2764 green = 0x0e00 * ((color & 0x0F0) >> 4);
2765 blue = 0x0e00 * ((color & 0x00F));
2766 SetRGB(pen_magicolor[0], red, green, blue);
2768 red = 0x1111 * ((color & 0xF00) >> 8);
2769 green = 0x1111 * ((color & 0x0F0) >> 4);
2770 blue = 0x1111 * ((color & 0x00F));
2771 SetRGB(pen_magicolor[1], red, green, blue);
2775 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
2777 static unsigned int pacman_delay = 0;
2778 static unsigned int energy_delay = 0;
2779 static unsigned int overload_delay = 0;
2785 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
2788 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
2790 element = Feld[x][y];
2792 if (!IS_MOVING(x, y) && CAN_MOVE(element))
2793 StartMoving_MM(x, y);
2794 else if (IS_MOVING(x, y))
2795 ContinueMoving_MM(x, y);
2796 else if (IS_EXPLODING(element))
2797 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
2798 else if (element == EL_EXIT_OPENING)
2800 else if (element == EL_GRAY_BALL_OPENING)
2801 OpenSurpriseBall(x, y);
2802 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
2804 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
2808 AutoRotateMirrors();
2811 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
2813 /* redraw after Explode_MM() ... */
2815 DrawLaser(0, DL_LASER_ENABLED);
2816 laser.redraw = FALSE;
2821 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
2825 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
2827 DrawLaser(0, DL_LASER_DISABLED);
2832 if (FrameReached(&energy_delay, ENERGY_DELAY))
2834 game_mm.energy_left--;
2835 if (game_mm.energy_left >= 0)
2838 BlitBitmap(pix[PIX_DOOR], drawto,
2839 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
2840 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
2841 DX_ENERGY, DY_ENERGY);
2843 redraw_mask |= REDRAW_DOOR_1;
2845 else if (setup.time_limit)
2849 for (i = 15; i >= 0; i--)
2852 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
2854 pen_ray = GetPixelFromRGB(window,
2855 native_mm_level.laser_red * 0x11 * i,
2856 native_mm_level.laser_green * 0x11 * i,
2857 native_mm_level.laser_blue * 0x11 * i);
2859 DrawLaser(0, DL_LASER_ENABLED);
2864 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
2867 DrawLaser(0, DL_LASER_DISABLED);
2868 game_mm.game_over = TRUE;
2869 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
2872 if (Request("Out of magic energy ! Play it again ?",
2873 REQ_ASK | REQ_STAY_CLOSED))
2879 game_status = MAINMENU;
2888 element = laser.dest_element;
2891 if (element != Feld[ELX][ELY])
2893 printf("element == %d, Feld[ELX][ELY] == %d\n",
2894 element, Feld[ELX][ELY]);
2898 if (!laser.overloaded && laser.overload_value == 0 &&
2899 element != EL_BOMB &&
2900 element != EL_MINE &&
2901 element != EL_BALL_GRAY &&
2902 element != EL_BLOCK_STONE &&
2903 element != EL_BLOCK_WOOD &&
2904 element != EL_FUSE_ON &&
2905 element != EL_FUEL_FULL &&
2906 !IS_WALL_ICE(element) &&
2907 !IS_WALL_AMOEBA(element))
2910 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
2911 (!laser.overloaded && laser.overload_value > 0)) &&
2912 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
2914 if (laser.overloaded)
2915 laser.overload_value++;
2917 laser.overload_value--;
2919 if (game_mm.cheat_no_overload)
2921 laser.overloaded = FALSE;
2922 laser.overload_value = 0;
2925 game_mm.laser_overload_value = laser.overload_value;
2927 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
2929 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
2930 int color_down = 0xFF - color_up;
2933 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
2934 (15 - (laser.overload_value / 6)) * color_scale);
2937 GetPixelFromRGB(window,
2938 (native_mm_level.laser_red ? 0xFF : color_up),
2939 (native_mm_level.laser_green ? color_down : 0x00),
2940 (native_mm_level.laser_blue ? color_down : 0x00));
2942 DrawLaser(0, DL_LASER_ENABLED);
2948 if (!laser.overloaded)
2949 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
2950 else if (setup.sound_loops)
2951 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
2953 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
2955 if (laser.overloaded)
2958 BlitBitmap(pix[PIX_DOOR], drawto,
2959 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
2960 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
2961 - laser.overload_value,
2962 OVERLOAD_XSIZE, laser.overload_value,
2963 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
2964 - laser.overload_value);
2966 redraw_mask |= REDRAW_DOOR_1;
2971 BlitBitmap(pix[PIX_DOOR], drawto,
2972 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
2973 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
2974 DX_OVERLOAD, DY_OVERLOAD);
2976 redraw_mask |= REDRAW_DOOR_1;
2979 if (laser.overload_value == MAX_LASER_OVERLOAD)
2983 for (i = 15; i >= 0; i--)
2986 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
2989 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
2991 DrawLaser(0, DL_LASER_ENABLED);
2996 DrawLaser(0, DL_LASER_DISABLED);
2998 game_mm.game_over = TRUE;
2999 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3002 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3003 REQ_ASK | REQ_STAY_CLOSED))
3009 game_status = MAINMENU;
3023 if (element == EL_BOMB && CT > 75)
3025 if (game_mm.cheat_no_explosion)
3029 laser.num_damages--;
3030 DrawLaser(0, DL_LASER_DISABLED);
3031 laser.num_edges = 0;
3036 laser.dest_element = EL_EXPLODING_OPAQUE;
3040 laser.num_damages--;
3041 DrawLaser(0, DL_LASER_DISABLED);
3043 laser.num_edges = 0;
3044 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3046 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3047 REQ_ASK | REQ_STAY_CLOSED))
3053 game_status = MAINMENU;
3061 if (element == EL_FUSE_ON && CT > 25)
3063 laser.fuse_off = TRUE;
3067 DrawLaser(0, DL_LASER_DISABLED);
3068 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3071 if (element == EL_BALL_GRAY && CT > 75)
3073 static int new_elements[] =
3076 EL_MIRROR_FIXED_START,
3078 EL_POLAR_CROSS_START,
3084 int num_new_elements = sizeof(new_elements) / sizeof(int);
3085 int new_element = new_elements[RND(num_new_elements)];
3087 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3088 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3090 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3101 element = EL_MIRROR_START + RND(16);
3107 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3114 element = (rnd == 0 ? EL_FUSE_ON :
3115 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3116 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3117 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3118 EL_MIRROR_FIXED_START + rnd - 25);
3123 graphic = el2gfx(element);
3125 for (i = 0; i < 50; i++)
3131 BlitBitmap(pix[PIX_BACK], drawto,
3132 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3133 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3134 SX + ELX * TILEX + x,
3135 SY + ELY * TILEY + y);
3137 MarkTileDirty(ELX, ELY);
3140 DrawLaser(0, DL_LASER_ENABLED);
3145 Feld[ELX][ELY] = element;
3146 DrawField_MM(ELX, ELY);
3149 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3152 /* above stuff: GRAY BALL -> PRISM !!! */
3154 LX = ELX * TILEX + 14;
3155 LY = ELY * TILEY + 14;
3156 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3163 laser.num_edges -= 2;
3164 laser.num_damages--;
3168 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3169 if (laser.damage[i].is_mirror)
3173 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3175 DrawLaser(0, DL_LASER_DISABLED);
3177 DrawLaser(0, DL_LASER_DISABLED);
3183 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3190 if (IS_WALL_ICE(element) && CT > 50)
3192 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3195 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3196 Store[ELX][ELY] = EL_WALL_ICE;
3197 Store2[ELX][ELY] = laser.wall_mask;
3199 laser.dest_element = Feld[ELX][ELY];
3204 for (i = 0; i < 5; i++)
3210 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3214 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3219 if (Feld[ELX][ELY] == EL_WALL_ICE)
3220 Feld[ELX][ELY] = EL_EMPTY;
3224 LX = laser.edge[laser.num_edges].x - (SX + 2);
3225 LY = laser.edge[laser.num_edges].y - (SY + 2);
3228 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3229 if (laser.damage[i].is_mirror)
3233 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3235 DrawLaser(0, DL_LASER_DISABLED);
3242 if (IS_WALL_AMOEBA(element) && CT > 60)
3244 int k1, k2, k3, dx, dy, de, dm;
3245 int element2 = Feld[ELX][ELY];
3247 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3250 for (i = laser.num_damages - 1; i >= 0; i--)
3251 if (laser.damage[i].is_mirror)
3254 r = laser.num_edges;
3255 d = laser.num_damages;
3262 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3265 DrawLaser(0, DL_LASER_ENABLED);
3268 x = laser.damage[k1].x;
3269 y = laser.damage[k1].y;
3274 for (i = 0; i < 4; i++)
3276 if (laser.wall_mask & (1 << i))
3278 if (ReadPixel(drawto,
3279 SX + ELX * TILEX + 14 + (i % 2) * 2,
3280 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3282 if (ReadPixel(drawto,
3283 SX + ELX * TILEX + 31 * (i % 2),
3284 SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3291 for (i = 0; i < 4; i++)
3293 if (laser.wall_mask & (1 << i))
3295 if (ReadPixel(drawto,
3296 SX + ELX * TILEX + 31 * (i % 2),
3297 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3304 if (laser.num_beamers > 0 ||
3305 k1 < 1 || k2 < 4 || k3 < 4 ||
3306 ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3309 laser.num_edges = r;
3310 laser.num_damages = d;
3312 DrawLaser(0, DL_LASER_DISABLED);
3315 Feld[ELX][ELY] = element | laser.wall_mask;
3319 de = Feld[ELX][ELY];
3320 dm = laser.wall_mask;
3324 int x = ELX, y = ELY;
3325 int wall_mask = laser.wall_mask;
3328 DrawLaser(0, DL_LASER_ENABLED);
3330 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3332 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3333 Store[x][y] = EL_WALL_AMOEBA;
3334 Store2[x][y] = wall_mask;
3340 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3342 DrawLaser(0, DL_LASER_ENABLED);
3344 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3346 for (i = 4; i >= 0; i--)
3348 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3354 DrawLaser(0, DL_LASER_ENABLED);
3359 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3360 laser.stops_inside_element && CT > 75)
3365 if (ABS(XS) > ABS(YS))
3372 for (i = 0; i < 4; i++)
3379 x = ELX + Step[k * 4].x;
3380 y = ELY + Step[k * 4].y;
3382 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3385 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3393 laser.overloaded = (element == EL_BLOCK_STONE);
3398 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3401 Feld[x][y] = element;
3403 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3406 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3408 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3409 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3417 if (element == EL_FUEL_FULL && CT > 10)
3419 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3422 BlitBitmap(pix[PIX_DOOR], drawto,
3423 DOOR_GFX_PAGEX4 + XX_ENERGY,
3424 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3425 ENERGY_XSIZE, i, DX_ENERGY,
3426 DY_ENERGY + ENERGY_YSIZE - i);
3429 redraw_mask |= REDRAW_DOOR_1;
3435 game_mm.energy_left = MAX_LASER_ENERGY;
3436 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3437 DrawField_MM(ELX, ELY);
3439 DrawLaser(0, DL_LASER_ENABLED);
3447 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3449 ClickElement(action.lx, action.ly, action.button);
3451 GameActions_MM_Ext(action, warp_mode);
3457 int mx, my, ox, oy, nx, ny;
3461 if (++p >= game_mm.num_pacman)
3464 game_mm.pacman[p].dir--;
3466 for (l = 1; l < 5; l++)
3468 game_mm.pacman[p].dir++;
3470 if (game_mm.pacman[p].dir > 4)
3471 game_mm.pacman[p].dir = 1;
3473 if (game_mm.pacman[p].dir % 2)
3476 my = game_mm.pacman[p].dir - 2;
3481 mx = 3 - game_mm.pacman[p].dir;
3484 ox = game_mm.pacman[p].x;
3485 oy = game_mm.pacman[p].y;
3488 element = Feld[nx][ny];
3490 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3493 if (!IS_EATABLE4PACMAN(element))
3496 if (ObjHit(nx, ny, HIT_POS_CENTER))
3499 Feld[ox][oy] = EL_EMPTY;
3501 EL_PACMAN_RIGHT - 1 +
3502 (game_mm.pacman[p].dir - 1 +
3503 (game_mm.pacman[p].dir % 2) * 2);
3505 game_mm.pacman[p].x = nx;
3506 game_mm.pacman[p].y = ny;
3508 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3510 if (element != EL_EMPTY)
3512 int graphic = el2gfx(Feld[nx][ny]);
3517 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3520 ox = SX + ox * TILEX;
3521 oy = SY + oy * TILEY;
3523 for (i = 1; i < 33; i += 2)
3524 BlitBitmap(bitmap, window,
3525 src_x, src_y, TILEX, TILEY,
3526 ox + i * mx, oy + i * my);
3527 Ct = Ct + FrameCounter - CT;
3530 DrawField_MM(nx, ny);
3533 if (!laser.fuse_off)
3535 DrawLaser(0, DL_LASER_ENABLED);
3537 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3539 AddDamagedField(nx, ny);
3541 laser.damage[laser.num_damages - 1].edge = 0;
3545 if (element == EL_BOMB)
3546 DeletePacMan(nx, ny);
3548 if (IS_WALL_AMOEBA(element) &&
3549 (LX + 2 * XS) / TILEX == nx &&
3550 (LY + 2 * YS) / TILEY == ny)
3563 boolean raise_level = FALSE;
3566 if (local_player->MovPos)
3569 local_player->LevelSolved = FALSE;
3572 if (game_mm.energy_left)
3574 if (setup.sound_loops)
3575 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3576 SND_CTRL_PLAY_LOOP);
3578 while (game_mm.energy_left > 0)
3580 if (!setup.sound_loops)
3581 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3584 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3585 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3590 game_mm.energy_left--;
3591 if (game_mm.energy_left >= 0)
3594 BlitBitmap(pix[PIX_DOOR], drawto,
3595 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3596 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3597 DX_ENERGY, DY_ENERGY);
3599 redraw_mask |= REDRAW_DOOR_1;
3606 if (setup.sound_loops)
3607 StopSound(SND_SIRR);
3609 else if (native_mm_level.time == 0) /* level without time limit */
3611 if (setup.sound_loops)
3612 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3613 SND_CTRL_PLAY_LOOP);
3615 while (TimePlayed < 999)
3617 if (!setup.sound_loops)
3618 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3619 if (TimePlayed < 999 && !(TimePlayed % 10))
3620 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3621 if (TimePlayed < 900 && !(TimePlayed % 10))
3627 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3634 if (setup.sound_loops)
3635 StopSound(SND_SIRR);
3642 CloseDoor(DOOR_CLOSE_1);
3644 Request("Level solved !", REQ_CONFIRM);
3646 if (level_nr == leveldir_current->handicap_level)
3648 leveldir_current->handicap_level++;
3649 SaveLevelSetup_SeriesInfo();
3652 if (level_editor_test_game)
3653 game_mm.score = -1; /* no highscore when playing from editor */
3654 else if (level_nr < leveldir_current->last_level)
3655 raise_level = TRUE; /* advance to next level */
3657 if ((hi_pos = NewHiScore_MM()) >= 0)
3659 game_status = HALLOFFAME;
3661 // DrawHallOfFame(hi_pos);
3668 game_status = MAINMENU;
3684 // LoadScore(level_nr);
3686 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3687 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3690 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3692 if (game_mm.score > highscore[k].Score)
3694 /* player has made it to the hall of fame */
3696 if (k < MAX_SCORE_ENTRIES - 1)
3698 int m = MAX_SCORE_ENTRIES - 1;
3701 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3702 if (!strcmp(setup.player_name, highscore[l].Name))
3704 if (m == k) /* player's new highscore overwrites his old one */
3708 for (l = m; l>k; l--)
3710 strcpy(highscore[l].Name, highscore[l - 1].Name);
3711 highscore[l].Score = highscore[l - 1].Score;
3718 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3719 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3720 highscore[k].Score = game_mm.score;
3727 else if (!strncmp(setup.player_name, highscore[k].Name,
3728 MAX_PLAYER_NAME_LEN))
3729 break; /* player already there with a higher score */
3734 // if (position >= 0)
3735 // SaveScore(level_nr);
3740 static void InitMovingField_MM(int x, int y, int direction)
3742 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3743 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3745 MovDir[x][y] = direction;
3746 MovDir[newx][newy] = direction;
3748 if (Feld[newx][newy] == EL_EMPTY)
3749 Feld[newx][newy] = EL_BLOCKED;
3752 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3754 int direction = MovDir[x][y];
3755 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3756 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3762 static void Blocked2Moving_MM(int x, int y,
3763 int *comes_from_x, int *comes_from_y)
3765 int oldx = x, oldy = y;
3766 int direction = MovDir[x][y];
3768 if (direction == MV_LEFT)
3770 else if (direction == MV_RIGHT)
3772 else if (direction == MV_UP)
3774 else if (direction == MV_DOWN)
3777 *comes_from_x = oldx;
3778 *comes_from_y = oldy;
3781 static int MovingOrBlocked2Element_MM(int x, int y)
3783 int element = Feld[x][y];
3785 if (element == EL_BLOCKED)
3789 Blocked2Moving_MM(x, y, &oldx, &oldy);
3791 return Feld[oldx][oldy];
3798 static void RemoveField(int x, int y)
3800 Feld[x][y] = EL_EMPTY;
3807 static void RemoveMovingField_MM(int x, int y)
3809 int oldx = x, oldy = y, newx = x, newy = y;
3811 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3814 if (IS_MOVING(x, y))
3816 Moving2Blocked_MM(x, y, &newx, &newy);
3817 if (Feld[newx][newy] != EL_BLOCKED)
3820 else if (Feld[x][y] == EL_BLOCKED)
3822 Blocked2Moving_MM(x, y, &oldx, &oldy);
3823 if (!IS_MOVING(oldx, oldy))
3827 Feld[oldx][oldy] = EL_EMPTY;
3828 Feld[newx][newy] = EL_EMPTY;
3829 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3830 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3832 DrawLevelField_MM(oldx, oldy);
3833 DrawLevelField_MM(newx, newy);
3836 void PlaySoundLevel(int x, int y, int sound_nr)
3838 int sx = SCREENX(x), sy = SCREENY(y);
3840 int silence_distance = 8;
3842 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3843 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3846 if (!IN_LEV_FIELD(x, y) ||
3847 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3848 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3851 volume = SOUND_MAX_VOLUME;
3854 stereo = (sx - SCR_FIELDX/2) * 12;
3856 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3857 if (stereo > SOUND_MAX_RIGHT)
3858 stereo = SOUND_MAX_RIGHT;
3859 if (stereo < SOUND_MAX_LEFT)
3860 stereo = SOUND_MAX_LEFT;
3863 if (!IN_SCR_FIELD(sx, sy))
3865 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3866 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3868 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3871 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3874 static void RaiseScore_MM(int value)
3876 game_mm.score += value;
3879 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
3884 void RaiseScoreElement_MM(int element)
3889 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3893 RaiseScore_MM(native_mm_level.score[SC_KEY]);