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_SHORT 125
70 #define CLICK_DELAY_LONG 250
72 #define AUTO_ROTATE_DELAY CLICK_DELAY_SHORT
73 #define INIT_GAME_ACTIONS_DELAY ONE_SECOND_DELAY
74 #define NUM_INIT_CYCLE_STEPS 16
76 /* forward declaration for internal use */
77 static int MovingOrBlocked2Element_MM(int, int);
78 static void Bang_MM(int, int);
79 static void RaiseScore_MM(int);
80 static void RemoveMovingField_MM(int, int);
81 static void InitMovingField_MM(int, int, int);
82 static void ContinueMoving_MM(int, int);
83 static void Moving2Blocked_MM(int, int, int *, int *);
86 static int get_element_angle(int element)
88 int element_phase = get_element_phase(element);
90 if (IS_MIRROR_FIXED(element) ||
91 IS_MCDUFFIN(element) ||
94 return 4 * element_phase;
99 static int get_opposite_angle(int angle)
101 int opposite_angle = angle + ANG_RAY_180;
103 /* make sure "opposite_angle" is in valid interval [0, 15] */
104 return (opposite_angle + 16) % 16;
107 static int get_mirrored_angle(int laser_angle, int mirror_angle)
109 int reflected_angle = 16 - laser_angle + mirror_angle;
111 /* make sure "reflected_angle" is in valid interval [0, 15] */
112 return (reflected_angle + 16) % 16;
115 static void InitMovDir_MM(int x, int y)
117 int element = Feld[x][y];
118 static int direction[3][4] =
120 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
121 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
122 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
127 case EL_PACMAN_RIGHT:
131 Feld[x][y] = EL_PACMAN;
132 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
140 static void InitField(int x, int y, boolean init_game)
142 int element = Feld[x][y];
147 Feld[x][y] = EL_EMPTY;
152 if (native_mm_level.auto_count_kettles)
153 game_mm.kettles_still_needed++;
156 case EL_LIGHTBULB_OFF:
157 game_mm.lights_still_needed++;
161 if (IS_MIRROR(element) ||
162 IS_BEAMER_OLD(element) ||
163 IS_BEAMER(element) ||
165 IS_POLAR_CROSS(element) ||
166 IS_DF_MIRROR(element) ||
167 IS_DF_MIRROR_AUTO(element) ||
168 IS_GRID_STEEL_AUTO(element) ||
169 IS_GRID_WOOD_AUTO(element) ||
170 IS_FIBRE_OPTIC(element))
172 if (IS_BEAMER_OLD(element))
174 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
175 element = Feld[x][y];
178 if (!IS_FIBRE_OPTIC(element))
180 static int steps_grid_auto = 0;
182 if (game_mm.num_cycle == 0) /* initialize cycle steps for grids */
183 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
185 if (IS_GRID_STEEL_AUTO(element) ||
186 IS_GRID_WOOD_AUTO(element))
187 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
189 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
191 game_mm.cycle[game_mm.num_cycle].x = x;
192 game_mm.cycle[game_mm.num_cycle].y = y;
196 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
198 int beamer_nr = BEAMER_NR(element);
199 int nr = laser.beamer[beamer_nr][0].num;
201 laser.beamer[beamer_nr][nr].x = x;
202 laser.beamer[beamer_nr][nr].y = y;
203 laser.beamer[beamer_nr][nr].num = 1;
206 else if (IS_PACMAN(element))
210 else if (IS_MCDUFFIN(element) || IS_LASER(element))
212 laser.start_edge.x = x;
213 laser.start_edge.y = y;
214 laser.start_angle = get_element_angle(element);
221 static void InitCycleElements_RotateSingleStep()
225 if (game_mm.num_cycle == 0) /* no elements to cycle */
228 for (i = 0; i < game_mm.num_cycle; i++)
230 int x = game_mm.cycle[i].x;
231 int y = game_mm.cycle[i].y;
232 int step = SIGN(game_mm.cycle[i].steps);
233 int last_element = Feld[x][y];
234 int next_element = get_rotated_element(last_element, step);
236 if (!game_mm.cycle[i].steps)
239 Feld[x][y] = next_element;
242 game_mm.cycle[i].steps -= step;
246 static void InitLaser()
248 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
249 int step = (IS_LASER(start_element) ? 4 : 0);
251 LX = laser.start_edge.x * TILEX;
252 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
255 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
257 LY = laser.start_edge.y * TILEY;
258 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
259 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
263 XS = 2 * Step[laser.start_angle].x;
264 YS = 2 * Step[laser.start_angle].y;
266 laser.current_angle = laser.start_angle;
268 laser.num_damages = 0;
270 laser.num_beamers = 0;
271 laser.beamer_edge[0] = 0;
273 AddLaserEdge(LX, LY); /* set laser starting edge */
275 pen_ray = GetPixelFromRGB(window,
276 native_mm_level.laser_red * 0xFF,
277 native_mm_level.laser_green * 0xFF,
278 native_mm_level.laser_blue * 0xFF);
281 void InitGameEngine_MM()
285 /* set global game control values */
286 game_mm.num_cycle = 0;
287 game_mm.num_pacman = 0;
290 game_mm.energy_left = 0; // later set to "native_mm_level.time"
291 game_mm.kettles_still_needed =
292 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
293 game_mm.lights_still_needed = 0;
294 game_mm.num_keys = 0;
296 game_mm.level_solved = FALSE;
297 game_mm.game_over = FALSE;
298 game_mm.game_over_cause = 0;
300 game_mm.laser_overload_value = 0;
302 /* set global laser control values (must be set before "InitLaser()") */
303 laser.start_edge.x = 0;
304 laser.start_edge.y = 0;
305 laser.start_angle = 0;
307 for (i = 0; i < MAX_NUM_BEAMERS; i++)
308 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
310 laser.overloaded = FALSE;
311 laser.overload_value = 0;
312 laser.fuse_off = FALSE;
313 laser.fuse_x = laser.fuse_y = -1;
315 laser.dest_element = EL_EMPTY;
320 for (x = 0; x < lev_fieldx; x++)
322 for (y = 0; y < lev_fieldy; y++)
324 Feld[x][y] = Ur[x][y];
325 Hit[x][y] = Box[x][y] = 0;
327 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
328 Store[x][y] = Store2[x][y] = 0;
332 InitField(x, y, TRUE);
337 CloseDoor(DOOR_CLOSE_1);
343 void InitGameActions_MM()
345 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY / GAME_FRAME_DELAY;
346 int cycle_steps_done = 0;
352 /* copy default game door content to main double buffer */
353 BlitBitmap(pix[PIX_DOOR], drawto,
354 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
358 DrawText(DX_LEVEL, DY_LEVEL,
359 int2str(level_nr, 2), FONT_TEXT_2);
360 DrawText(DX_KETTLES, DY_KETTLES,
361 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
362 DrawText(DX_SCORE, DY_SCORE,
363 int2str(game_mm.score, 4), FONT_TEXT_2);
372 /* copy actual game door content to door double buffer for OpenDoor() */
373 BlitBitmap(drawto, pix[PIX_DB_DOOR],
374 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
378 OpenDoor(DOOR_OPEN_ALL);
381 if (setup.sound_loops)
382 PlaySoundExt(SND_FUEL, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
384 for (i = 0; i <= num_init_game_frames; i++)
386 if (!setup.sound_loops)
387 PlaySoundStereo(SND_FUEL, SOUND_MAX_RIGHT);
389 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
391 UpdateAndDisplayGameControlValues();
393 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
395 InitCycleElements_RotateSingleStep();
405 if (setup.quick_doors)
410 if (setup.sound_loops)
414 if (setup.sound_music && num_bg_loops)
415 PlayMusic(level_nr % num_bg_loops);
421 void AddLaserEdge(int lx, int ly)
423 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
425 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
430 laser.edge[laser.num_edges].x = SX + 2 + lx;
431 laser.edge[laser.num_edges].y = SY + 2 + ly;
437 void AddDamagedField(int ex, int ey)
439 laser.damage[laser.num_damages].is_mirror = FALSE;
440 laser.damage[laser.num_damages].angle = laser.current_angle;
441 laser.damage[laser.num_damages].edge = laser.num_edges;
442 laser.damage[laser.num_damages].x = ex;
443 laser.damage[laser.num_damages].y = ey;
453 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
454 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
456 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
462 static int getMaskFromElement(int element)
464 if (IS_GRID(element))
465 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
466 else if (IS_MCDUFFIN(element))
467 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
468 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
469 return IMG_MM_MASK_RECTANGLE;
471 return IMG_MM_MASK_CIRCLE;
479 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
480 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
483 /* follow laser beam until it hits something (at least the screen border) */
484 while (hit_mask == HIT_MASK_NO_HIT)
490 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
491 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
493 printf("ScanPixel: touched screen border!\n");
499 for (i = 0; i < 4; i++)
501 int px = LX + (i % 2) * 2;
502 int py = LY + (i / 2) * 2;
505 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
506 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
509 if (IN_LEV_FIELD(lx, ly))
511 int element = Feld[lx][ly];
513 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
517 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
519 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
521 pixel = ((element & (1 << pos)) ? 1 : 0);
525 int graphic_mask = getMaskFromElement(element);
530 getGraphicSource(graphic_mask, 0, &bitmap, &src_x, &src_y);
535 pixel = (ReadPixel(bitmap, mask_x, mask_y) ? 1 : 0);
540 pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
541 SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
544 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
545 hit_mask |= (1 << i);
548 if (hit_mask == HIT_MASK_NO_HIT)
550 /* hit nothing -- go on with another step */
562 int end = 0, rf = laser.num_edges;
564 /* do not scan laser again after the game was lost for whatever reason */
565 if (game_mm.game_over)
568 laser.overloaded = FALSE;
569 laser.stops_inside_element = FALSE;
571 DrawLaser(0, DL_LASER_ENABLED);
574 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
582 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
585 laser.overloaded = TRUE;
590 hit_mask = ScanPixel();
593 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
597 /* hit something -- check out what it was */
598 ELX = (LX + XS) / TILEX;
599 ELY = (LY + YS) / TILEY;
602 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
603 hit_mask, LX, LY, ELX, ELY);
606 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
609 laser.dest_element = element;
614 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
616 /* we have hit the top-right and bottom-left element --
617 choose the bottom-left one */
618 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
619 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
620 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
621 ELX = (LX - 2) / TILEX;
622 ELY = (LY + 2) / TILEY;
625 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
627 /* we have hit the top-left and bottom-right element --
628 choose the top-left one */
629 /* !!! SEE ABOVE !!! */
630 ELX = (LX - 2) / TILEX;
631 ELY = (LY - 2) / TILEY;
635 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
636 hit_mask, LX, LY, ELX, ELY);
639 element = Feld[ELX][ELY];
640 laser.dest_element = element;
643 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
646 LX % TILEX, LY % TILEY,
651 if (!IN_LEV_FIELD(ELX, ELY))
652 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
655 if (element == EL_EMPTY)
657 if (!HitOnlyAnEdge(element, hit_mask))
660 else if (element == EL_FUSE_ON)
662 if (HitPolarizer(element, hit_mask))
665 else if (IS_GRID(element) || IS_DF_GRID(element))
667 if (HitPolarizer(element, hit_mask))
670 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
671 element == EL_GATE_STONE || element == EL_GATE_WOOD)
673 if (HitBlock(element, hit_mask))
680 else if (IS_MCDUFFIN(element))
682 if (HitLaserSource(element, hit_mask))
685 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
686 IS_RECEIVER(element))
688 if (HitLaserDestination(element, hit_mask))
691 else if (IS_WALL(element))
693 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
695 if (HitReflectingWalls(element, hit_mask))
700 if (HitAbsorbingWalls(element, hit_mask))
706 if (HitElement(element, hit_mask))
711 DrawLaser(rf - 1, DL_LASER_ENABLED);
712 rf = laser.num_edges;
716 if (laser.dest_element != Feld[ELX][ELY])
718 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
719 laser.dest_element, Feld[ELX][ELY]);
723 if (!end && !laser.stops_inside_element && !StepBehind())
726 printf("ScanLaser: Go one step back\n");
732 AddLaserEdge(LX, LY);
736 DrawLaser(rf - 1, DL_LASER_ENABLED);
741 if (!IN_LEV_FIELD(ELX, ELY))
742 printf("WARNING! (2) %d, %d\n", ELX, ELY);
746 void DrawLaserExt(int start_edge, int num_edges, int mode)
752 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
753 start_edge, num_edges, mode);
758 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
765 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
771 if (mode == DL_LASER_DISABLED)
773 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
777 /* now draw the laser to the backbuffer and (if enabled) to the screen */
778 DrawLines(drawto, &laser.edge[start_edge], num_edges,
779 (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
781 redraw_mask |= REDRAW_FIELD;
783 if (mode == DL_LASER_ENABLED)
786 /* after the laser was deleted, the "damaged" graphics must be restored */
787 if (laser.num_damages)
789 int damage_start = 0;
792 /* determine the starting edge, from which graphics need to be restored */
795 for (i = 0; i < laser.num_damages; i++)
797 if (laser.damage[i].edge == start_edge + 1)
806 /* restore graphics from this starting edge to the end of damage list */
807 for (i = damage_start; i < laser.num_damages; i++)
809 int lx = laser.damage[i].x;
810 int ly = laser.damage[i].y;
811 int element = Feld[lx][ly];
813 if (Hit[lx][ly] == laser.damage[i].edge)
814 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
817 if (Box[lx][ly] == laser.damage[i].edge)
820 if (IS_DRAWABLE(element))
821 DrawField_MM(lx, ly);
824 elx = laser.damage[damage_start].x;
825 ely = laser.damage[damage_start].y;
826 element = Feld[elx][ely];
829 if (IS_BEAMER(element))
833 for (i = 0; i < laser.num_beamers; i++)
834 printf("-> %d\n", laser.beamer_edge[i]);
835 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
836 mode, elx, ely, Hit[elx][ely], start_edge);
837 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
838 get_element_angle(element), laser.damage[damage_start].angle);
842 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
843 laser.num_beamers > 0 &&
844 start_edge == laser.beamer_edge[laser.num_beamers - 1])
846 /* element is outgoing beamer */
847 laser.num_damages = damage_start + 1;
849 if (IS_BEAMER(element))
850 laser.current_angle = get_element_angle(element);
854 /* element is incoming beamer or other element */
855 laser.num_damages = damage_start;
856 laser.current_angle = laser.damage[laser.num_damages].angle;
861 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
863 elx = laser.start_edge.x;
864 ely = laser.start_edge.y;
865 element = Feld[elx][ely];
868 laser.num_edges = start_edge + 1;
870 laser.current_angle = laser.start_angle;
872 LX = laser.edge[start_edge].x - (SX + 2);
873 LY = laser.edge[start_edge].y - (SY + 2);
874 XS = 2 * Step[laser.current_angle].x;
875 YS = 2 * Step[laser.current_angle].y;
878 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
884 if (IS_BEAMER(element) ||
885 IS_FIBRE_OPTIC(element) ||
886 IS_PACMAN(element) ||
888 IS_POLAR_CROSS(element) ||
889 element == EL_FUSE_ON)
894 printf("element == %d\n", element);
897 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
898 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
902 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
903 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
904 (laser.num_beamers == 0 ||
905 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
907 /* element is incoming beamer or other element */
908 step_size = -step_size;
913 if (IS_BEAMER(element))
915 printf("start_edge == %d, laser.beamer_edge == %d\n",
916 start_edge, laser.beamer_edge);
920 LX += step_size * XS;
921 LY += step_size * YS;
923 else if (element != EL_EMPTY)
932 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
937 void DrawLaser(int start_edge, int mode)
939 if (laser.num_edges - start_edge < 0)
941 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
946 /* check if laser is interrupted by beamer element */
947 if (laser.num_beamers > 0 &&
948 start_edge < laser.beamer_edge[laser.num_beamers - 1])
950 if (mode == DL_LASER_ENABLED)
953 int tmp_start_edge = start_edge;
955 /* draw laser segments forward from the start to the last beamer */
956 for (i = 0; i < laser.num_beamers; i++)
958 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
960 if (tmp_num_edges <= 0)
964 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
965 i, laser.beamer_edge[i], tmp_start_edge);
968 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
970 tmp_start_edge = laser.beamer_edge[i];
973 /* draw last segment from last beamer to the end */
974 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
980 int last_num_edges = laser.num_edges;
981 int num_beamers = laser.num_beamers;
983 /* delete laser segments backward from the end to the first beamer */
984 for (i = num_beamers-1; i >= 0; i--)
986 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
988 if (laser.beamer_edge[i] - start_edge <= 0)
991 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
993 last_num_edges = laser.beamer_edge[i];
998 if (last_num_edges - start_edge <= 0)
999 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1000 last_num_edges, start_edge);
1003 /* delete first segment from start to the first beamer */
1004 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1009 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1013 boolean HitElement(int element, int hit_mask)
1015 if (HitOnlyAnEdge(element, hit_mask))
1018 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1019 element = MovingOrBlocked2Element_MM(ELX, ELY);
1022 printf("HitElement (1): element == %d\n", element);
1026 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1027 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1029 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1032 AddDamagedField(ELX, ELY);
1034 /* this is more precise: check if laser would go through the center */
1035 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1037 /* skip the whole element before continuing the scan */
1043 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1045 if (LX/TILEX > ELX || LY/TILEY > ELY)
1047 /* skipping scan positions to the right and down skips one scan
1048 position too much, because this is only the top left scan position
1049 of totally four scan positions (plus one to the right, one to the
1050 bottom and one to the bottom right) */
1060 printf("HitElement (2): element == %d\n", element);
1063 if (LX + 5 * XS < 0 ||
1073 printf("HitElement (3): element == %d\n", element);
1076 if (IS_POLAR(element) &&
1077 ((element - EL_POLAR_START) % 2 ||
1078 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1080 PlaySoundStereo(SND_KINK, ST(ELX));
1082 laser.num_damages--;
1087 if (IS_POLAR_CROSS(element) &&
1088 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1090 PlaySoundStereo(SND_KINK, ST(ELX));
1092 laser.num_damages--;
1097 if (!IS_BEAMER(element) &&
1098 !IS_FIBRE_OPTIC(element) &&
1099 !IS_GRID_WOOD(element) &&
1100 element != EL_FUEL_EMPTY)
1103 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1104 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1106 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1109 LX = ELX * TILEX + 14;
1110 LY = ELY * TILEY + 14;
1112 AddLaserEdge(LX, LY);
1115 if (IS_MIRROR(element) ||
1116 IS_MIRROR_FIXED(element) ||
1117 IS_POLAR(element) ||
1118 IS_POLAR_CROSS(element) ||
1119 IS_DF_MIRROR(element) ||
1120 IS_DF_MIRROR_AUTO(element) ||
1121 element == EL_PRISM ||
1122 element == EL_REFRACTOR)
1124 int current_angle = laser.current_angle;
1127 laser.num_damages--;
1129 AddDamagedField(ELX, ELY);
1131 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1134 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1136 if (IS_MIRROR(element) ||
1137 IS_MIRROR_FIXED(element) ||
1138 IS_DF_MIRROR(element) ||
1139 IS_DF_MIRROR_AUTO(element))
1140 laser.current_angle = get_mirrored_angle(laser.current_angle,
1141 get_element_angle(element));
1143 if (element == EL_PRISM || element == EL_REFRACTOR)
1144 laser.current_angle = RND(16);
1146 XS = 2 * Step[laser.current_angle].x;
1147 YS = 2 * Step[laser.current_angle].y;
1149 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1154 LX += step_size * XS;
1155 LY += step_size * YS;
1158 /* draw sparkles on mirror */
1159 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1160 current_angle != laser.current_angle)
1162 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1166 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1167 current_angle != laser.current_angle)
1168 PlaySoundStereo(SND_LASER, ST(ELX));
1171 (get_opposite_angle(laser.current_angle) ==
1172 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1174 return (laser.overloaded ? TRUE : FALSE);
1177 if (element == EL_FUEL_FULL)
1179 laser.stops_inside_element = TRUE;
1184 if (element == EL_BOMB || element == EL_MINE)
1186 PlaySoundStereo(SND_KINK, ST(ELX));
1188 if (element == EL_MINE)
1189 laser.overloaded = TRUE;
1192 if (element == EL_KETTLE ||
1193 element == EL_CELL ||
1194 element == EL_KEY ||
1195 element == EL_LIGHTBALL ||
1196 element == EL_PACMAN ||
1199 if (!IS_PACMAN(element))
1202 if (element == EL_PACMAN)
1205 if (element == EL_KETTLE || element == EL_CELL)
1207 if (game_mm.kettles_still_needed > 0)
1208 game_mm.kettles_still_needed--;
1212 if (game_mm.kettles_still_needed == 0)
1215 static int xy[4][2] =
1223 PlaySoundStereo(SND_KLING, ST(ELX));
1225 for (y = 0; y < lev_fieldy; y++)
1227 for (x = 0; x < lev_fieldx; x++)
1229 /* initiate opening animation of exit door */
1230 if (Feld[x][y] == EL_EXIT_CLOSED)
1231 Feld[x][y] = EL_EXIT_OPENING;
1233 /* remove field that blocks receiver */
1234 if (IS_RECEIVER(Feld[x][y]))
1236 int phase = Feld[x][y] - EL_RECEIVER_START;
1237 int blocking_x, blocking_y;
1239 blocking_x = x + xy[phase][0];
1240 blocking_y = y + xy[phase][1];
1242 if (IN_LEV_FIELD(blocking_x, blocking_y))
1244 Feld[blocking_x][blocking_y] = EL_EMPTY;
1246 DrawField_MM(blocking_x, blocking_y);
1252 DrawLaser(0, DL_LASER_ENABLED);
1255 else if (element == EL_KEY)
1259 else if (element == EL_LIGHTBALL)
1263 else if (IS_PACMAN(element))
1265 DeletePacMan(ELX, ELY);
1272 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1274 PlaySoundStereo(SND_KINK, ST(ELX));
1276 DrawLaser(0, DL_LASER_ENABLED);
1278 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1280 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1281 game_mm.lights_still_needed--;
1285 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1286 game_mm.lights_still_needed++;
1289 DrawField_MM(ELX, ELY);
1290 DrawLaser(0, DL_LASER_ENABLED);
1295 laser.stops_inside_element = TRUE;
1301 printf("HitElement (4): element == %d\n", element);
1304 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1305 laser.num_beamers < MAX_NUM_BEAMERS &&
1306 laser.beamer[BEAMER_NR(element)][1].num)
1308 int beamer_angle = get_element_angle(element);
1309 int beamer_nr = BEAMER_NR(element);
1313 printf("HitElement (BEAMER): element == %d\n", element);
1316 laser.num_damages--;
1318 if (IS_FIBRE_OPTIC(element) ||
1319 laser.current_angle == get_opposite_angle(beamer_angle))
1323 LX = ELX * TILEX + 14;
1324 LY = ELY * TILEY + 14;
1326 AddLaserEdge(LX, LY);
1327 AddDamagedField(ELX, ELY);
1329 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1332 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1334 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1335 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1336 ELX = laser.beamer[beamer_nr][pos].x;
1337 ELY = laser.beamer[beamer_nr][pos].y;
1338 LX = ELX * TILEX + 14;
1339 LY = ELY * TILEY + 14;
1341 if (IS_BEAMER(element))
1343 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1344 XS = 2 * Step[laser.current_angle].x;
1345 YS = 2 * Step[laser.current_angle].y;
1348 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1350 AddLaserEdge(LX, LY);
1351 AddDamagedField(ELX, ELY);
1353 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1356 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1358 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1363 LX += step_size * XS;
1364 LY += step_size * YS;
1366 laser.num_beamers++;
1375 boolean HitOnlyAnEdge(int element, int hit_mask)
1377 /* check if the laser hit only the edge of an element and, if so, go on */
1380 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1383 if ((hit_mask == HIT_MASK_TOPLEFT ||
1384 hit_mask == HIT_MASK_TOPRIGHT ||
1385 hit_mask == HIT_MASK_BOTTOMLEFT ||
1386 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1387 laser.current_angle % 4) /* angle is not 90° */
1391 if (hit_mask == HIT_MASK_TOPLEFT)
1396 else if (hit_mask == HIT_MASK_TOPRIGHT)
1401 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1406 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1412 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1418 printf("[HitOnlyAnEdge() == TRUE]\n");
1425 printf("[HitOnlyAnEdge() == FALSE]\n");
1431 boolean HitPolarizer(int element, int hit_mask)
1433 if (HitOnlyAnEdge(element, hit_mask))
1436 if (IS_DF_GRID(element))
1438 int grid_angle = get_element_angle(element);
1441 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1442 grid_angle, laser.current_angle);
1445 AddLaserEdge(LX, LY);
1446 AddDamagedField(ELX, ELY);
1449 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1451 if (laser.current_angle == grid_angle ||
1452 laser.current_angle == get_opposite_angle(grid_angle))
1454 /* skip the whole element before continuing the scan */
1460 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1462 if (LX/TILEX > ELX || LY/TILEY > ELY)
1464 /* skipping scan positions to the right and down skips one scan
1465 position too much, because this is only the top left scan position
1466 of totally four scan positions (plus one to the right, one to the
1467 bottom and one to the bottom right) */
1473 AddLaserEdge(LX, LY);
1479 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1481 LX / TILEX, LY / TILEY,
1482 LX % TILEX, LY % TILEY);
1487 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1489 return HitReflectingWalls(element, hit_mask);
1493 return HitAbsorbingWalls(element, hit_mask);
1496 else if (IS_GRID_STEEL(element))
1498 return HitReflectingWalls(element, hit_mask);
1500 else /* IS_GRID_WOOD */
1502 return HitAbsorbingWalls(element, hit_mask);
1508 boolean HitBlock(int element, int hit_mask)
1510 boolean check = FALSE;
1512 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1513 game_mm.num_keys == 0)
1516 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1519 int ex = ELX * TILEX + 14;
1520 int ey = ELY * TILEY + 14;
1524 for (i = 1; i < 32; i++)
1529 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1534 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1535 return HitAbsorbingWalls(element, hit_mask);
1539 AddLaserEdge(LX - XS, LY - YS);
1540 AddDamagedField(ELX, ELY);
1543 Box[ELX][ELY] = laser.num_edges;
1545 return HitReflectingWalls(element, hit_mask);
1548 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1550 int xs = XS / 2, ys = YS / 2;
1551 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1552 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1554 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1555 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1557 laser.overloaded = (element == EL_GATE_STONE);
1562 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1563 (hit_mask == HIT_MASK_TOP ||
1564 hit_mask == HIT_MASK_LEFT ||
1565 hit_mask == HIT_MASK_RIGHT ||
1566 hit_mask == HIT_MASK_BOTTOM))
1567 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1568 hit_mask == HIT_MASK_BOTTOM),
1569 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1570 hit_mask == HIT_MASK_RIGHT));
1571 AddLaserEdge(LX, LY);
1577 if (element == EL_GATE_STONE && Box[ELX][ELY])
1579 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1591 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1593 int xs = XS / 2, ys = YS / 2;
1594 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1595 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1597 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1598 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1600 laser.overloaded = (element == EL_BLOCK_STONE);
1605 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1606 (hit_mask == HIT_MASK_TOP ||
1607 hit_mask == HIT_MASK_LEFT ||
1608 hit_mask == HIT_MASK_RIGHT ||
1609 hit_mask == HIT_MASK_BOTTOM))
1610 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1611 hit_mask == HIT_MASK_BOTTOM),
1612 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1613 hit_mask == HIT_MASK_RIGHT));
1614 AddDamagedField(ELX, ELY);
1616 LX = ELX * TILEX + 14;
1617 LY = ELY * TILEY + 14;
1619 AddLaserEdge(LX, LY);
1621 laser.stops_inside_element = TRUE;
1629 boolean HitLaserSource(int element, int hit_mask)
1631 if (HitOnlyAnEdge(element, hit_mask))
1634 PlaySoundStereo(SND_AUTSCH, ST(ELX));
1635 laser.overloaded = TRUE;
1640 boolean HitLaserDestination(int element, int hit_mask)
1642 if (HitOnlyAnEdge(element, hit_mask))
1645 if (element != EL_EXIT_OPEN &&
1646 !(IS_RECEIVER(element) &&
1647 game_mm.kettles_still_needed == 0 &&
1648 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1650 PlaySoundStereo(SND_HOLZ, ST(ELX));
1654 if (IS_RECEIVER(element) ||
1655 (IS_22_5_ANGLE(laser.current_angle) &&
1656 (ELX != (LX + 6 * XS) / TILEX ||
1657 ELY != (LY + 6 * YS) / TILEY ||
1666 LX = ELX * TILEX + 14;
1667 LY = ELY * TILEY + 14;
1669 laser.stops_inside_element = TRUE;
1672 AddLaserEdge(LX, LY);
1673 AddDamagedField(ELX, ELY);
1675 if (game_mm.lights_still_needed == 0)
1676 game_mm.level_solved = TRUE;
1681 boolean HitReflectingWalls(int element, int hit_mask)
1683 /* check if laser hits side of a wall with an angle that is not 90° */
1684 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1685 hit_mask == HIT_MASK_LEFT ||
1686 hit_mask == HIT_MASK_RIGHT ||
1687 hit_mask == HIT_MASK_BOTTOM))
1689 PlaySoundStereo(SND_HUI, ST(ELX));
1694 if (!IS_DF_GRID(element))
1695 AddLaserEdge(LX, LY);
1697 /* check if laser hits wall with an angle of 45° */
1698 if (!IS_22_5_ANGLE(laser.current_angle))
1700 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1703 laser.current_angle = get_mirrored_angle(laser.current_angle,
1706 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1709 laser.current_angle = get_mirrored_angle(laser.current_angle,
1713 AddLaserEdge(LX, LY);
1715 XS = 2 * Step[laser.current_angle].x;
1716 YS = 2 * Step[laser.current_angle].y;
1720 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1722 laser.current_angle = get_mirrored_angle(laser.current_angle,
1727 if (!IS_DF_GRID(element))
1728 AddLaserEdge(LX, LY);
1733 if (!IS_DF_GRID(element))
1734 AddLaserEdge(LX, LY + YS / 2);
1737 if (!IS_DF_GRID(element))
1738 AddLaserEdge(LX, LY);
1741 YS = 2 * Step[laser.current_angle].y;
1745 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1747 laser.current_angle = get_mirrored_angle(laser.current_angle,
1752 if (!IS_DF_GRID(element))
1753 AddLaserEdge(LX, LY);
1758 if (!IS_DF_GRID(element))
1759 AddLaserEdge(LX + XS / 2, LY);
1762 if (!IS_DF_GRID(element))
1763 AddLaserEdge(LX, LY);
1766 XS = 2 * Step[laser.current_angle].x;
1772 /* reflection at the edge of reflecting DF style wall */
1773 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1775 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1776 hit_mask == HIT_MASK_TOPRIGHT) ||
1777 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1778 hit_mask == HIT_MASK_TOPLEFT) ||
1779 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1780 hit_mask == HIT_MASK_BOTTOMLEFT) ||
1781 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1782 hit_mask == HIT_MASK_BOTTOMRIGHT))
1785 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
1786 ANG_MIRROR_135 : ANG_MIRROR_45);
1788 PlaySoundStereo(SND_HUI, ST(ELX));
1790 AddDamagedField(ELX, ELY);
1791 AddLaserEdge(LX, LY);
1793 laser.current_angle = get_mirrored_angle(laser.current_angle,
1801 AddLaserEdge(LX, LY);
1807 /* reflection inside an edge of reflecting DF style wall */
1808 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1810 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1811 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
1812 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1813 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
1814 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1815 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
1816 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1817 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
1820 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
1821 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
1822 ANG_MIRROR_135 : ANG_MIRROR_45);
1824 PlaySoundStereo(SND_HUI, ST(ELX));
1827 AddDamagedField(ELX, ELY);
1830 AddLaserEdge(LX - XS, LY - YS);
1831 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
1832 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
1834 laser.current_angle = get_mirrored_angle(laser.current_angle,
1842 AddLaserEdge(LX, LY);
1848 /* check if laser hits DF style wall with an angle of 90° */
1849 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
1851 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
1852 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
1853 (IS_VERT_ANGLE(laser.current_angle) &&
1854 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
1856 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
1858 /* laser at last step touched nothing or the same side of the wall */
1859 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
1861 AddDamagedField(ELX, ELY);
1868 last_hit_mask = hit_mask;
1875 if (!HitOnlyAnEdge(element, hit_mask))
1877 laser.overloaded = TRUE;
1885 boolean HitAbsorbingWalls(int element, int hit_mask)
1887 if (HitOnlyAnEdge(element, hit_mask))
1891 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
1893 AddLaserEdge(LX - XS, LY - YS);
1900 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
1902 AddLaserEdge(LX - XS, LY - YS);
1908 if (IS_WALL_WOOD(element) ||
1909 IS_DF_WALL_WOOD(element) ||
1910 IS_GRID_WOOD(element) ||
1911 IS_GRID_WOOD_FIXED(element) ||
1912 IS_GRID_WOOD_AUTO(element) ||
1913 element == EL_FUSE_ON ||
1914 element == EL_BLOCK_WOOD ||
1915 element == EL_GATE_WOOD)
1917 PlaySoundStereo(SND_HOLZ, ST(ELX));
1922 if (IS_WALL_ICE(element))
1926 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
1927 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
1929 /* check if laser hits wall with an angle of 90° */
1930 if (IS_90_ANGLE(laser.current_angle))
1931 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1933 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
1937 for (i = 0; i < 4; i++)
1939 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
1940 mask = 15 - (8 >> i);
1941 else if (ABS(XS) == 4 &&
1943 (XS > 0) == (i % 2) &&
1944 (YS < 0) == (i / 2))
1945 mask = 3 + (i / 2) * 9;
1946 else if (ABS(YS) == 4 &&
1948 (XS < 0) == (i % 2) &&
1949 (YS > 0) == (i / 2))
1950 mask = 5 + (i % 2) * 5;
1954 laser.wall_mask = mask;
1956 else if (IS_WALL_AMOEBA(element))
1958 int elx = (LX - 2 * XS) / TILEX;
1959 int ely = (LY - 2 * YS) / TILEY;
1960 int element2 = Feld[elx][ely];
1963 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
1965 laser.dest_element = EL_EMPTY;
1973 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
1974 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
1976 if (IS_90_ANGLE(laser.current_angle))
1977 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1979 laser.dest_element = element2 | EL_WALL_AMOEBA;
1981 laser.wall_mask = mask;
1987 void OpenExit(int x, int y)
1991 if (!MovDelay[x][y]) /* next animation frame */
1992 MovDelay[x][y] = 4 * delay;
1994 if (MovDelay[x][y]) /* wait some time before next frame */
1999 phase = MovDelay[x][y] / delay;
2001 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2002 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2004 if (!MovDelay[x][y])
2006 Feld[x][y] = EL_EXIT_OPEN;
2012 void OpenSurpriseBall(int x, int y)
2016 if (!MovDelay[x][y]) /* next animation frame */
2017 MovDelay[x][y] = 50 * delay;
2019 if (MovDelay[x][y]) /* wait some time before next frame */
2023 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2026 int graphic = el2gfx(Store[x][y]);
2028 int dx = RND(26), dy = RND(26);
2030 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2032 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2033 SX + x * TILEX + dx, SY + y * TILEY + dy);
2035 MarkTileDirty(x, y);
2038 if (!MovDelay[x][y])
2040 Feld[x][y] = Store[x][y];
2049 void MeltIce(int x, int y)
2054 if (!MovDelay[x][y]) /* next animation frame */
2055 MovDelay[x][y] = frames * delay;
2057 if (MovDelay[x][y]) /* wait some time before next frame */
2060 int wall_mask = Store2[x][y];
2061 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2064 phase = frames - MovDelay[x][y] / delay - 1;
2066 if (!MovDelay[x][y])
2070 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2071 Store[x][y] = Store2[x][y] = 0;
2073 DrawWalls_MM(x, y, Feld[x][y]);
2075 if (Feld[x][y] == EL_WALL_ICE)
2076 Feld[x][y] = EL_EMPTY;
2078 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2079 if (laser.damage[i].is_mirror)
2083 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2085 DrawLaser(0, DL_LASER_DISABLED);
2089 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2091 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2093 laser.redraw = TRUE;
2098 void GrowAmoeba(int x, int y)
2103 if (!MovDelay[x][y]) /* next animation frame */
2104 MovDelay[x][y] = frames * delay;
2106 if (MovDelay[x][y]) /* wait some time before next frame */
2109 int wall_mask = Store2[x][y];
2110 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2113 phase = MovDelay[x][y] / delay;
2115 if (!MovDelay[x][y])
2117 Feld[x][y] = real_element;
2118 Store[x][y] = Store2[x][y] = 0;
2120 DrawWalls_MM(x, y, Feld[x][y]);
2121 DrawLaser(0, DL_LASER_ENABLED);
2123 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2125 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2130 static void Explode_MM(int x, int y, int phase, int mode)
2132 int num_phase = 9, delay = 2;
2133 int last_phase = num_phase * delay;
2134 int half_phase = (num_phase / 2) * delay;
2136 laser.redraw = TRUE;
2138 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2140 int center_element = Feld[x][y];
2142 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2144 /* put moving element to center field (and let it explode there) */
2145 center_element = MovingOrBlocked2Element_MM(x, y);
2146 RemoveMovingField_MM(x, y);
2148 Feld[x][y] = center_element;
2151 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2152 Store[x][y] = center_element;
2154 Store[x][y] = EL_EMPTY;
2156 Store2[x][y] = mode;
2157 Feld[x][y] = EL_EXPLODING_OPAQUE;
2158 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2164 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2166 if (phase == half_phase)
2168 Feld[x][y] = EL_EXPLODING_TRANSP;
2170 if (x == ELX && y == ELY)
2174 if (phase == last_phase)
2176 if (Store[x][y] == EL_BOMB)
2178 laser.num_damages--;
2179 DrawLaser(0, DL_LASER_DISABLED);
2180 laser.num_edges = 0;
2182 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2183 Store[x][y] = EL_EMPTY;
2185 else if (IS_MCDUFFIN(Store[x][y]))
2187 game_mm.game_over = TRUE;
2188 game_mm.game_over_cause = GAME_OVER_BOMB;
2189 Store[x][y] = EL_EMPTY;
2192 Feld[x][y] = Store[x][y];
2193 Store[x][y] = Store2[x][y] = 0;
2194 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2196 InitField(x, y, FALSE);
2199 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2201 int graphic = IMG_MM_DEFAULT_EXPLODING;
2202 int graphic_phase = (phase / delay - 1);
2206 if (Store2[x][y] == EX_KETTLE)
2208 if (graphic_phase < 3)
2210 graphic = IMG_MM_KETTLE_EXPLODING;
2212 else if (graphic_phase < 5)
2218 graphic = IMG_EMPTY;
2222 else if (Store2[x][y] == EX_SHORT)
2224 if (graphic_phase < 4)
2230 graphic = IMG_EMPTY;
2235 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2237 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2238 FX + x * TILEX, FY + y * TILEY);
2240 MarkTileDirty(x, y);
2244 static void Bang_MM(int x, int y)
2246 int element = Feld[x][y];
2247 int mode = EX_NORMAL;
2250 DrawLaser(0, DL_LASER_ENABLED);
2269 if (IS_PACMAN(element))
2270 PlaySoundStereo(SND_QUIEK, ST(x));
2271 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2272 PlaySoundStereo(SND_ROAAAR, ST(x));
2273 else if (element == EL_KEY)
2274 PlaySoundStereo(SND_KLING, ST(x));
2276 PlaySoundStereo((mode == EX_SHORT ? SND_WHOOSH : SND_KABUMM), ST(x));
2278 Explode_MM(x, y, EX_PHASE_START, mode);
2281 void TurnRound(int x, int y)
2293 { 0, 0 }, { 0, 0 }, { 0, 0 },
2298 int left, right, back;
2302 { MV_DOWN, MV_UP, MV_RIGHT },
2303 { MV_UP, MV_DOWN, MV_LEFT },
2305 { MV_LEFT, MV_RIGHT, MV_DOWN },
2306 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2307 { MV_RIGHT, MV_LEFT, MV_UP }
2310 int element = Feld[x][y];
2311 int old_move_dir = MovDir[x][y];
2312 int right_dir = turn[old_move_dir].right;
2313 int back_dir = turn[old_move_dir].back;
2314 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2315 int right_x = x + right_dx, right_y = y + right_dy;
2317 if (element == EL_PACMAN)
2319 boolean can_turn_right = FALSE;
2321 if (IN_LEV_FIELD(right_x, right_y) &&
2322 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2323 can_turn_right = TRUE;
2326 MovDir[x][y] = right_dir;
2328 MovDir[x][y] = back_dir;
2334 static void StartMoving_MM(int x, int y)
2336 int element = Feld[x][y];
2341 if (CAN_MOVE(element))
2345 if (MovDelay[x][y]) /* wait some time before next movement */
2353 /* now make next step */
2355 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2357 if (element == EL_PACMAN &&
2358 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2359 !ObjHit(newx, newy, HIT_POS_CENTER))
2361 Store[newx][newy] = Feld[newx][newy];
2362 Feld[newx][newy] = EL_EMPTY;
2364 DrawField_MM(newx, newy);
2366 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2367 ObjHit(newx, newy, HIT_POS_CENTER))
2369 /* object was running against a wall */
2376 InitMovingField_MM(x, y, MovDir[x][y]);
2380 ContinueMoving_MM(x, y);
2383 static void ContinueMoving_MM(int x, int y)
2385 int element = Feld[x][y];
2386 int direction = MovDir[x][y];
2387 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2388 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2389 int horiz_move = (dx!=0);
2390 int newx = x + dx, newy = y + dy;
2391 int step = (horiz_move ? dx : dy) * TILEX / 8;
2393 MovPos[x][y] += step;
2395 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2397 Feld[x][y] = EL_EMPTY;
2398 Feld[newx][newy] = element;
2400 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2401 MovDelay[newx][newy] = 0;
2403 if (!CAN_MOVE(element))
2404 MovDir[newx][newy] = 0;
2407 DrawField_MM(newx, newy);
2409 Stop[newx][newy] = TRUE;
2411 if (element == EL_PACMAN)
2413 if (Store[newx][newy] == EL_BOMB)
2414 Bang_MM(newx, newy);
2416 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2417 (LX + 2 * XS) / TILEX == newx &&
2418 (LY + 2 * YS) / TILEY == newy)
2425 else /* still moving on */
2430 laser.redraw = TRUE;
2433 void ClickElement(int x, int y, int button)
2435 static unsigned int click_delay = 0;
2436 static int click_delay_value = CLICK_DELAY_SHORT;
2437 static boolean new_button = TRUE;
2440 /* do not rotate objects hit by the laser after the game was solved */
2441 if (game_mm.level_solved && Hit[x][y])
2444 if (button == MB_RELEASED)
2447 click_delay_value = CLICK_DELAY_SHORT;
2449 /* release eventually hold auto-rotating mirror */
2450 RotateMirror(x, y, MB_RELEASED);
2455 if (!DelayReached(&click_delay, click_delay_value) && !new_button)
2458 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2461 if (!IN_LEV_FIELD(x, y))
2464 if (Feld[x][y] == EL_EMPTY)
2467 element = Feld[x][y];
2469 if (IS_MIRROR(element) ||
2470 IS_BEAMER(element) ||
2471 IS_POLAR(element) ||
2472 IS_POLAR_CROSS(element) ||
2473 IS_DF_MIRROR(element) ||
2474 IS_DF_MIRROR_AUTO(element))
2476 RotateMirror(x, y, button);
2478 else if (IS_MCDUFFIN(element))
2480 if (!laser.fuse_off)
2482 DrawLaser(0, DL_LASER_DISABLED);
2489 element = get_rotated_element(element, BUTTON_ROTATION(button));
2490 laser.start_angle = get_element_angle(element);
2494 Feld[x][y] = element;
2501 if (!laser.fuse_off)
2504 else if (element == EL_FUSE_ON && laser.fuse_off)
2506 if (x != laser.fuse_x || y != laser.fuse_y)
2509 laser.fuse_off = FALSE;
2510 laser.fuse_x = laser.fuse_y = -1;
2512 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2515 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2517 laser.fuse_off = TRUE;
2520 laser.overloaded = FALSE;
2522 DrawLaser(0, DL_LASER_DISABLED);
2523 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2525 else if (element == EL_LIGHTBALL)
2529 DrawLaser(0, DL_LASER_ENABLED);
2532 click_delay_value = (new_button ? CLICK_DELAY_LONG : CLICK_DELAY_SHORT);
2536 void RotateMirror(int x, int y, int button)
2538 static int hold_x = -1, hold_y = -1;
2540 if (button == MB_RELEASED)
2542 /* release eventually hold auto-rotating mirror */
2549 if (IS_MIRROR(Feld[x][y]) ||
2550 IS_POLAR_CROSS(Feld[x][y]) ||
2551 IS_POLAR(Feld[x][y]) ||
2552 IS_BEAMER(Feld[x][y]) ||
2553 IS_DF_MIRROR(Feld[x][y]) ||
2554 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2555 IS_GRID_WOOD_AUTO(Feld[x][y]))
2557 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2559 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2561 if (button == MB_LEFTBUTTON)
2563 /* left mouse button only for manual adjustment, no auto-rotating;
2564 freeze mirror for until mouse button released */
2568 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2570 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2574 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2576 int edge = Hit[x][y];
2582 DrawLaser(edge - 1, DL_LASER_DISABLED);
2586 else if (ObjHit(x, y, HIT_POS_CENTER))
2588 int edge = Hit[x][y];
2592 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2596 DrawLaser(edge - 1, DL_LASER_DISABLED);
2603 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2608 if ((IS_BEAMER(Feld[x][y]) ||
2609 IS_POLAR(Feld[x][y]) ||
2610 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2614 if (IS_BEAMER(Feld[x][y]))
2617 printf("TEST (%d, %d) [%d] [%d]\n",
2619 laser.beamer_edge, laser.beamer[1].num);
2629 DrawLaser(0, DL_LASER_ENABLED);
2633 void AutoRotateMirrors()
2635 static unsigned int rotate_delay = 0;
2638 if (!DelayReached(&rotate_delay, AUTO_ROTATE_DELAY))
2641 for (x = 0; x < lev_fieldx; x++)
2643 for (y = 0; y < lev_fieldy; y++)
2645 int element = Feld[x][y];
2647 /* do not rotate objects hit by the laser after the game was solved */
2648 if (game_mm.level_solved && Hit[x][y])
2651 if (IS_DF_MIRROR_AUTO(element) ||
2652 IS_GRID_WOOD_AUTO(element) ||
2653 IS_GRID_STEEL_AUTO(element) ||
2654 element == EL_REFRACTOR)
2655 RotateMirror(x, y, MB_RIGHTBUTTON);
2660 boolean ObjHit(int obx, int oby, int bits)
2667 if (bits & HIT_POS_CENTER)
2669 if (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
2673 if (bits & HIT_POS_EDGE)
2675 for (i = 0; i < 4; i++)
2676 if (ReadPixel(drawto,
2677 SX + obx + 31 * (i % 2),
2678 SY + oby + 31 * (i / 2)) == pen_ray)
2682 if (bits & HIT_POS_BETWEEN)
2684 for (i = 0; i < 4; i++)
2685 if (ReadPixel(drawto,
2686 SX + 4 + obx + 22 * (i % 2),
2687 SY + 4 + oby + 22 * (i / 2)) == pen_ray)
2694 void DeletePacMan(int px, int py)
2700 if (game_mm.num_pacman <= 1)
2702 game_mm.num_pacman = 0;
2706 for (i = 0; i < game_mm.num_pacman; i++)
2707 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2710 game_mm.num_pacman--;
2712 for (j = i; j < game_mm.num_pacman; j++)
2714 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
2715 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
2716 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2720 void ColorCycling(void)
2722 static int CC, Cc = 0;
2724 static int color, old = 0xF00, new = 0x010, mult = 1;
2725 static unsigned short red, green, blue;
2727 if (color_status == STATIC_COLORS)
2732 if (CC < Cc || CC > Cc + 50)
2736 color = old + new * mult;
2742 if (ABS(mult) == 16)
2752 red = 0x0e00 * ((color & 0xF00) >> 8);
2753 green = 0x0e00 * ((color & 0x0F0) >> 4);
2754 blue = 0x0e00 * ((color & 0x00F));
2755 SetRGB(pen_magicolor[0], red, green, blue);
2757 red = 0x1111 * ((color & 0xF00) >> 8);
2758 green = 0x1111 * ((color & 0x0F0) >> 4);
2759 blue = 0x1111 * ((color & 0x00F));
2760 SetRGB(pen_magicolor[1], red, green, blue);
2764 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
2766 static unsigned int action_delay = 0;
2767 static unsigned int pacman_delay = 0;
2768 static unsigned int energy_delay = 0;
2769 static unsigned int overload_delay = 0;
2775 WaitUntilDelayReached(&action_delay, GameFrameDelay);
2777 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
2780 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
2782 element = Feld[x][y];
2784 if (!IS_MOVING(x, y) && CAN_MOVE(element))
2785 StartMoving_MM(x, y);
2786 else if (IS_MOVING(x, y))
2787 ContinueMoving_MM(x, y);
2788 else if (IS_EXPLODING(element))
2789 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
2790 else if (element == EL_EXIT_OPENING)
2792 else if (element == EL_GRAY_BALL_OPENING)
2793 OpenSurpriseBall(x, y);
2794 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
2796 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
2800 AutoRotateMirrors();
2803 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
2805 /* redraw after Explode_MM() ... */
2807 DrawLaser(0, DL_LASER_ENABLED);
2808 laser.redraw = FALSE;
2813 if (game_mm.num_pacman && DelayReached(&pacman_delay, 250))
2817 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
2819 DrawLaser(0, DL_LASER_DISABLED);
2824 if (DelayReached(&energy_delay, 4000))
2826 game_mm.energy_left--;
2827 if (game_mm.energy_left >= 0)
2830 BlitBitmap(pix[PIX_DOOR], drawto,
2831 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
2832 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
2833 DX_ENERGY, DY_ENERGY);
2835 redraw_mask |= REDRAW_DOOR_1;
2837 else if (setup.time_limit)
2841 for (i = 15; i >= 0; i--)
2844 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
2846 pen_ray = GetPixelFromRGB(window,
2847 native_mm_level.laser_red * 0x11 * i,
2848 native_mm_level.laser_green * 0x11 * i,
2849 native_mm_level.laser_blue * 0x11 * i);
2851 DrawLaser(0, DL_LASER_ENABLED);
2856 StopSound(SND_WARNTON);
2859 DrawLaser(0, DL_LASER_DISABLED);
2860 game_mm.game_over = TRUE;
2861 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
2864 if (Request("Out of magic energy ! Play it again ?",
2865 REQ_ASK | REQ_STAY_CLOSED))
2871 game_status = MAINMENU;
2880 element = laser.dest_element;
2883 if (element != Feld[ELX][ELY])
2885 printf("element == %d, Feld[ELX][ELY] == %d\n",
2886 element, Feld[ELX][ELY]);
2890 if (!laser.overloaded && laser.overload_value == 0 &&
2891 element != EL_BOMB &&
2892 element != EL_MINE &&
2893 element != EL_BALL_GRAY &&
2894 element != EL_BLOCK_STONE &&
2895 element != EL_BLOCK_WOOD &&
2896 element != EL_FUSE_ON &&
2897 element != EL_FUEL_FULL &&
2898 !IS_WALL_ICE(element) &&
2899 !IS_WALL_AMOEBA(element))
2902 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
2903 (!laser.overloaded && laser.overload_value > 0)) &&
2904 DelayReached(&overload_delay, 60 + !laser.overloaded * 120))
2906 if (laser.overloaded)
2907 laser.overload_value++;
2909 laser.overload_value--;
2911 if (game_mm.cheat_no_overload)
2913 laser.overloaded = FALSE;
2914 laser.overload_value = 0;
2917 game_mm.laser_overload_value = laser.overload_value;
2919 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
2921 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
2922 int color_down = 0xFF - color_up;
2925 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
2926 (15 - (laser.overload_value / 6)) * color_scale);
2929 GetPixelFromRGB(window,
2930 (native_mm_level.laser_red ? 0xFF : color_up),
2931 (native_mm_level.laser_green ? color_down : 0x00),
2932 (native_mm_level.laser_blue ? color_down : 0x00));
2934 DrawLaser(0, DL_LASER_ENABLED);
2938 if (laser.overloaded)
2940 if (setup.sound_loops)
2941 PlaySoundExt(SND_WARNTON, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
2942 SND_CTRL_PLAY_LOOP);
2944 PlaySoundStereo(SND_WARNTON, SOUND_MAX_RIGHT);
2947 if (!laser.overloaded)
2948 StopSound(SND_WARNTON);
2950 if (laser.overloaded)
2953 BlitBitmap(pix[PIX_DOOR], drawto,
2954 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
2955 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
2956 - laser.overload_value,
2957 OVERLOAD_XSIZE, laser.overload_value,
2958 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
2959 - laser.overload_value);
2961 redraw_mask |= REDRAW_DOOR_1;
2966 BlitBitmap(pix[PIX_DOOR], drawto,
2967 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
2968 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
2969 DX_OVERLOAD, DY_OVERLOAD);
2971 redraw_mask |= REDRAW_DOOR_1;
2974 if (laser.overload_value == MAX_LASER_OVERLOAD)
2978 for (i = 15; i >= 0; i--)
2981 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
2984 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
2986 DrawLaser(0, DL_LASER_ENABLED);
2991 DrawLaser(0, DL_LASER_DISABLED);
2993 game_mm.game_over = TRUE;
2994 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
2997 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
2998 REQ_ASK | REQ_STAY_CLOSED))
3004 game_status = MAINMENU;
3018 if (element == EL_BOMB && CT > 1500)
3020 if (game_mm.cheat_no_explosion)
3024 laser.num_damages--;
3025 DrawLaser(0, DL_LASER_DISABLED);
3026 laser.num_edges = 0;
3031 laser.dest_element = EL_EXPLODING_OPAQUE;
3035 laser.num_damages--;
3036 DrawLaser(0, DL_LASER_DISABLED);
3038 laser.num_edges = 0;
3039 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3041 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3042 REQ_ASK | REQ_STAY_CLOSED))
3048 game_status = MAINMENU;
3056 if (element == EL_FUSE_ON && CT > 500)
3058 laser.fuse_off = TRUE;
3062 DrawLaser(0, DL_LASER_DISABLED);
3063 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3066 if (element == EL_BALL_GRAY && CT > 1500)
3068 static int new_elements[] =
3071 EL_MIRROR_FIXED_START,
3073 EL_POLAR_CROSS_START,
3079 int num_new_elements = sizeof(new_elements) / sizeof(int);
3080 int new_element = new_elements[RND(num_new_elements)];
3082 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3083 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3085 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3096 element = EL_MIRROR_START + RND(16);
3102 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3109 element = (rnd == 0 ? EL_FUSE_ON :
3110 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3111 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3112 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3113 EL_MIRROR_FIXED_START + rnd - 25);
3118 graphic = el2gfx(element);
3120 for (i = 0; i < 50; i++)
3126 BlitBitmap(pix[PIX_BACK], drawto,
3127 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3128 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3129 SX + ELX * TILEX + x,
3130 SY + ELY * TILEY + y);
3132 MarkTileDirty(ELX, ELY);
3135 DrawLaser(0, DL_LASER_ENABLED);
3140 Feld[ELX][ELY] = element;
3141 DrawField_MM(ELX, ELY);
3144 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3147 /* above stuff: GRAY BALL -> PRISM !!! */
3149 LX = ELX * TILEX + 14;
3150 LY = ELY * TILEY + 14;
3151 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3158 laser.num_edges -= 2;
3159 laser.num_damages--;
3163 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3164 if (laser.damage[i].is_mirror)
3168 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3170 DrawLaser(0, DL_LASER_DISABLED);
3172 DrawLaser(0, DL_LASER_DISABLED);
3178 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3185 if (IS_WALL_ICE(element) && CT > 1000)
3187 PlaySoundStereo(SND_SLURP, ST(ELX));
3190 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3191 Store[ELX][ELY] = EL_WALL_ICE;
3192 Store2[ELX][ELY] = laser.wall_mask;
3194 laser.dest_element = Feld[ELX][ELY];
3199 for (i = 0; i < 5; i++)
3205 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3209 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3214 if (Feld[ELX][ELY] == EL_WALL_ICE)
3215 Feld[ELX][ELY] = EL_EMPTY;
3219 LX = laser.edge[laser.num_edges].x - (SX + 2);
3220 LY = laser.edge[laser.num_edges].y - (SY + 2);
3223 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3224 if (laser.damage[i].is_mirror)
3228 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3230 DrawLaser(0, DL_LASER_DISABLED);
3237 if (IS_WALL_AMOEBA(element) && CT > 1200)
3239 int k1, k2, k3, dx, dy, de, dm;
3240 int element2 = Feld[ELX][ELY];
3242 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3245 for (i = laser.num_damages - 1; i >= 0; i--)
3246 if (laser.damage[i].is_mirror)
3249 r = laser.num_edges;
3250 d = laser.num_damages;
3257 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3260 DrawLaser(0, DL_LASER_ENABLED);
3263 x = laser.damage[k1].x;
3264 y = laser.damage[k1].y;
3269 for (i = 0; i < 4; i++)
3271 if (laser.wall_mask & (1 << i))
3273 if (ReadPixel(drawto,
3274 SX + ELX * TILEX + 14 + (i % 2) * 2,
3275 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3277 if (ReadPixel(drawto,
3278 SX + ELX * TILEX + 31 * (i % 2),
3279 SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3286 for (i = 0; i < 4; i++)
3288 if (laser.wall_mask & (1 << i))
3290 if (ReadPixel(drawto,
3291 SX + ELX * TILEX + 31 * (i % 2),
3292 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3299 if (laser.num_beamers > 0 ||
3300 k1 < 1 || k2 < 4 || k3 < 4 ||
3301 ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3304 laser.num_edges = r;
3305 laser.num_damages = d;
3307 DrawLaser(0, DL_LASER_DISABLED);
3310 Feld[ELX][ELY] = element | laser.wall_mask;
3314 de = Feld[ELX][ELY];
3315 dm = laser.wall_mask;
3319 int x = ELX, y = ELY;
3320 int wall_mask = laser.wall_mask;
3323 DrawLaser(0, DL_LASER_ENABLED);
3325 PlaySoundStereo(SND_AMOEBE, ST(dx));
3327 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3328 Store[x][y] = EL_WALL_AMOEBA;
3329 Store2[x][y] = wall_mask;
3335 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3337 DrawLaser(0, DL_LASER_ENABLED);
3339 PlaySoundStereo(SND_AMOEBE, ST(dx));
3341 for (i = 4; i >= 0; i--)
3343 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3349 DrawLaser(0, DL_LASER_ENABLED);
3354 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3355 laser.stops_inside_element && CT > 1500)
3360 if (ABS(XS) > ABS(YS))
3367 for (i = 0; i < 4; i++)
3374 x = ELX + Step[k * 4].x;
3375 y = ELY + Step[k * 4].y;
3377 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3380 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3388 laser.overloaded = (element == EL_BLOCK_STONE);
3393 PlaySoundStereo(SND_BONG, ST(ELX));
3396 Feld[x][y] = element;
3398 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3401 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3403 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3404 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3412 if (element == EL_FUEL_FULL && CT > 200)
3414 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3417 BlitBitmap(pix[PIX_DOOR], drawto,
3418 DOOR_GFX_PAGEX4 + XX_ENERGY,
3419 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3420 ENERGY_XSIZE, i, DX_ENERGY,
3421 DY_ENERGY + ENERGY_YSIZE - i);
3424 redraw_mask |= REDRAW_DOOR_1;
3430 game_mm.energy_left = MAX_LASER_ENERGY;
3431 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3432 DrawField_MM(ELX, ELY);
3434 DrawLaser(0, DL_LASER_ENABLED);
3442 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3444 ClickElement(action.lx, action.ly, action.button);
3446 GameActions_MM_Ext(action, warp_mode);
3452 int mx, my, ox, oy, nx, ny;
3456 if (++p >= game_mm.num_pacman)
3459 game_mm.pacman[p].dir--;
3461 for (l = 1; l < 5; l++)
3463 game_mm.pacman[p].dir++;
3465 if (game_mm.pacman[p].dir > 4)
3466 game_mm.pacman[p].dir = 1;
3468 if (game_mm.pacman[p].dir % 2)
3471 my = game_mm.pacman[p].dir - 2;
3476 mx = 3 - game_mm.pacman[p].dir;
3479 ox = game_mm.pacman[p].x;
3480 oy = game_mm.pacman[p].y;
3483 element = Feld[nx][ny];
3485 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3488 if (!IS_EATABLE4PACMAN(element))
3491 if (ObjHit(nx, ny, HIT_POS_CENTER))
3494 Feld[ox][oy] = EL_EMPTY;
3496 EL_PACMAN_RIGHT - 1 +
3497 (game_mm.pacman[p].dir - 1 +
3498 (game_mm.pacman[p].dir % 2) * 2);
3500 game_mm.pacman[p].x = nx;
3501 game_mm.pacman[p].y = ny;
3503 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3505 if (element != EL_EMPTY)
3507 int graphic = el2gfx(Feld[nx][ny]);
3512 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3515 ox = SX + ox * TILEX;
3516 oy = SY + oy * TILEY;
3518 for (i = 1; i < 33; i += 2)
3519 BlitBitmap(bitmap, window,
3520 src_x, src_y, TILEX, TILEY,
3521 ox + i * mx, oy + i * my);
3522 Ct = Ct + Counter() - CT;
3525 DrawField_MM(nx, ny);
3528 if (!laser.fuse_off)
3530 DrawLaser(0, DL_LASER_ENABLED);
3532 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3534 AddDamagedField(nx, ny);
3536 laser.damage[laser.num_damages - 1].edge = 0;
3540 if (element == EL_BOMB)
3541 DeletePacMan(nx, ny);
3543 if (IS_WALL_AMOEBA(element) &&
3544 (LX + 2 * XS) / TILEX == nx &&
3545 (LY + 2 * YS) / TILEY == ny)
3558 boolean raise_level = FALSE;
3561 if (local_player->MovPos)
3564 local_player->LevelSolved = FALSE;
3567 if (game_mm.energy_left)
3569 if (setup.sound_loops)
3570 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3571 SND_CTRL_PLAY_LOOP);
3573 while (game_mm.energy_left > 0)
3575 if (!setup.sound_loops)
3576 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3579 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3580 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3585 game_mm.energy_left--;
3586 if (game_mm.energy_left >= 0)
3589 BlitBitmap(pix[PIX_DOOR], drawto,
3590 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3591 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3592 DX_ENERGY, DY_ENERGY);
3594 redraw_mask |= REDRAW_DOOR_1;
3601 if (setup.sound_loops)
3602 StopSound(SND_SIRR);
3604 else if (native_mm_level.time == 0) /* level without time limit */
3606 if (setup.sound_loops)
3607 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3608 SND_CTRL_PLAY_LOOP);
3610 while (TimePlayed < 999)
3612 if (!setup.sound_loops)
3613 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3614 if (TimePlayed < 999 && !(TimePlayed % 10))
3615 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3616 if (TimePlayed < 900 && !(TimePlayed % 10))
3622 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3629 if (setup.sound_loops)
3630 StopSound(SND_SIRR);
3637 CloseDoor(DOOR_CLOSE_1);
3639 Request("Level solved !", REQ_CONFIRM);
3641 if (level_nr == leveldir_current->handicap_level)
3643 leveldir_current->handicap_level++;
3644 SaveLevelSetup_SeriesInfo();
3647 if (level_editor_test_game)
3648 game_mm.score = -1; /* no highscore when playing from editor */
3649 else if (level_nr < leveldir_current->last_level)
3650 raise_level = TRUE; /* advance to next level */
3652 if ((hi_pos = NewHiScore_MM()) >= 0)
3654 game_status = HALLOFFAME;
3656 // DrawHallOfFame(hi_pos);
3663 game_status = MAINMENU;
3679 // LoadScore(level_nr);
3681 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3682 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3685 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3687 if (game_mm.score > highscore[k].Score)
3689 /* player has made it to the hall of fame */
3691 if (k < MAX_SCORE_ENTRIES - 1)
3693 int m = MAX_SCORE_ENTRIES - 1;
3696 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3697 if (!strcmp(setup.player_name, highscore[l].Name))
3699 if (m == k) /* player's new highscore overwrites his old one */
3703 for (l = m; l>k; l--)
3705 strcpy(highscore[l].Name, highscore[l - 1].Name);
3706 highscore[l].Score = highscore[l - 1].Score;
3713 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3714 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3715 highscore[k].Score = game_mm.score;
3722 else if (!strncmp(setup.player_name, highscore[k].Name,
3723 MAX_PLAYER_NAME_LEN))
3724 break; /* player already there with a higher score */
3729 // if (position >= 0)
3730 // SaveScore(level_nr);
3735 static void InitMovingField_MM(int x, int y, int direction)
3737 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3738 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3740 MovDir[x][y] = direction;
3741 MovDir[newx][newy] = direction;
3743 if (Feld[newx][newy] == EL_EMPTY)
3744 Feld[newx][newy] = EL_BLOCKED;
3747 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3749 int direction = MovDir[x][y];
3750 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3751 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3757 static void Blocked2Moving_MM(int x, int y,
3758 int *comes_from_x, int *comes_from_y)
3760 int oldx = x, oldy = y;
3761 int direction = MovDir[x][y];
3763 if (direction == MV_LEFT)
3765 else if (direction == MV_RIGHT)
3767 else if (direction == MV_UP)
3769 else if (direction == MV_DOWN)
3772 *comes_from_x = oldx;
3773 *comes_from_y = oldy;
3776 static int MovingOrBlocked2Element_MM(int x, int y)
3778 int element = Feld[x][y];
3780 if (element == EL_BLOCKED)
3784 Blocked2Moving_MM(x, y, &oldx, &oldy);
3786 return Feld[oldx][oldy];
3793 static void RemoveField(int x, int y)
3795 Feld[x][y] = EL_EMPTY;
3802 static void RemoveMovingField_MM(int x, int y)
3804 int oldx = x, oldy = y, newx = x, newy = y;
3806 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3809 if (IS_MOVING(x, y))
3811 Moving2Blocked_MM(x, y, &newx, &newy);
3812 if (Feld[newx][newy] != EL_BLOCKED)
3815 else if (Feld[x][y] == EL_BLOCKED)
3817 Blocked2Moving_MM(x, y, &oldx, &oldy);
3818 if (!IS_MOVING(oldx, oldy))
3822 Feld[oldx][oldy] = EL_EMPTY;
3823 Feld[newx][newy] = EL_EMPTY;
3824 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3825 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3827 DrawLevelField_MM(oldx, oldy);
3828 DrawLevelField_MM(newx, newy);
3831 void PlaySoundLevel(int x, int y, int sound_nr)
3833 int sx = SCREENX(x), sy = SCREENY(y);
3835 int silence_distance = 8;
3837 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3838 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3841 if (!IN_LEV_FIELD(x, y) ||
3842 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3843 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3846 volume = SOUND_MAX_VOLUME;
3849 stereo = (sx - SCR_FIELDX/2) * 12;
3851 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3852 if (stereo > SOUND_MAX_RIGHT)
3853 stereo = SOUND_MAX_RIGHT;
3854 if (stereo < SOUND_MAX_LEFT)
3855 stereo = SOUND_MAX_LEFT;
3858 if (!IN_SCR_FIELD(sx, sy))
3860 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3861 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3863 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3866 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3869 static void RaiseScore_MM(int value)
3871 game_mm.score += value;
3874 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
3879 void RaiseScoreElement_MM(int element)
3884 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3888 RaiseScore_MM(native_mm_level.score[SC_KEY]);