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 (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 #define BEGIN_NO_HEADLESS \
83 boolean last_headless = program.headless; \
85 program.headless = FALSE; \
87 #define END_NO_HEADLESS \
88 program.headless = last_headless; \
91 /* forward declaration for internal use */
92 static int MovingOrBlocked2Element_MM(int, int);
93 static void Bang_MM(int, int);
94 static void RaiseScore_MM(int);
95 static void RaiseScoreElement_MM(int);
96 static void RemoveMovingField_MM(int, int);
97 static void InitMovingField_MM(int, int, int);
98 static void ContinueMoving_MM(int, int);
99 static void Moving2Blocked_MM(int, int, int *, int *);
101 /* bitmap for laser beam detection */
102 static Bitmap *laser_bitmap = NULL;
104 /* variables for laser control */
105 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
106 static int hold_x = -1, hold_y = -1;
108 /* variables for pacman control */
109 static int pacman_nr = -1;
111 /* various game engine delay counters */
112 static unsigned int rotate_delay = 0;
113 static unsigned int pacman_delay = 0;
114 static unsigned int energy_delay = 0;
115 static unsigned int overload_delay = 0;
117 /* element masks for scanning pixels of MM elements */
118 static const char mm_masks[10][16][16 + 1] =
302 static int get_element_angle(int element)
304 int element_phase = get_element_phase(element);
306 if (IS_MIRROR_FIXED(element) ||
307 IS_MCDUFFIN(element) ||
309 IS_RECEIVER(element))
310 return 4 * element_phase;
312 return element_phase;
315 static int get_opposite_angle(int angle)
317 int opposite_angle = angle + ANG_RAY_180;
319 /* make sure "opposite_angle" is in valid interval [0, 15] */
320 return (opposite_angle + 16) % 16;
323 static int get_mirrored_angle(int laser_angle, int mirror_angle)
325 int reflected_angle = 16 - laser_angle + mirror_angle;
327 /* make sure "reflected_angle" is in valid interval [0, 15] */
328 return (reflected_angle + 16) % 16;
331 static void DrawLaserLines(struct XY *points, int num_points, int mode)
333 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
334 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
336 DrawLines(drawto, points, num_points, pixel_drawto);
340 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
345 static boolean CheckLaserPixel(int x, int y)
351 pixel = ReadPixel(laser_bitmap, x, y);
355 return (pixel == WHITE_PIXEL);
358 static void CheckExitMM()
360 int exit_element = EL_EMPTY;
364 static int xy[4][2] =
372 for (y = 0; y < lev_fieldy; y++)
374 for (x = 0; x < lev_fieldx; x++)
376 if (Feld[x][y] == EL_EXIT_CLOSED)
378 /* initiate opening animation of exit door */
379 Feld[x][y] = EL_EXIT_OPENING;
381 exit_element = EL_EXIT_OPEN;
385 else if (IS_RECEIVER(Feld[x][y]))
387 /* remove field that blocks receiver */
388 int phase = Feld[x][y] - EL_RECEIVER_START;
389 int blocking_x, blocking_y;
391 blocking_x = x + xy[phase][0];
392 blocking_y = y + xy[phase][1];
394 if (IN_LEV_FIELD(blocking_x, blocking_y))
396 Feld[blocking_x][blocking_y] = EL_EMPTY;
398 DrawField_MM(blocking_x, blocking_y);
401 exit_element = EL_RECEIVER;
408 if (exit_element != EL_EMPTY)
409 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
412 static void InitMovDir_MM(int x, int y)
414 int element = Feld[x][y];
415 static int direction[3][4] =
417 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
418 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
419 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
424 case EL_PACMAN_RIGHT:
428 Feld[x][y] = EL_PACMAN;
429 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
437 static void InitField(int x, int y, boolean init_game)
439 int element = Feld[x][y];
444 Feld[x][y] = EL_EMPTY;
449 if (native_mm_level.auto_count_kettles)
450 game_mm.kettles_still_needed++;
453 case EL_LIGHTBULB_OFF:
454 game_mm.lights_still_needed++;
458 if (IS_MIRROR(element) ||
459 IS_BEAMER_OLD(element) ||
460 IS_BEAMER(element) ||
462 IS_POLAR_CROSS(element) ||
463 IS_DF_MIRROR(element) ||
464 IS_DF_MIRROR_AUTO(element) ||
465 IS_GRID_STEEL_AUTO(element) ||
466 IS_GRID_WOOD_AUTO(element) ||
467 IS_FIBRE_OPTIC(element))
469 if (IS_BEAMER_OLD(element))
471 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
472 element = Feld[x][y];
475 if (!IS_FIBRE_OPTIC(element))
477 static int steps_grid_auto = 0;
479 if (game_mm.num_cycle == 0) /* initialize cycle steps for grids */
480 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
482 if (IS_GRID_STEEL_AUTO(element) ||
483 IS_GRID_WOOD_AUTO(element))
484 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
486 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
488 game_mm.cycle[game_mm.num_cycle].x = x;
489 game_mm.cycle[game_mm.num_cycle].y = y;
493 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
495 int beamer_nr = BEAMER_NR(element);
496 int nr = laser.beamer[beamer_nr][0].num;
498 laser.beamer[beamer_nr][nr].x = x;
499 laser.beamer[beamer_nr][nr].y = y;
500 laser.beamer[beamer_nr][nr].num = 1;
503 else if (IS_PACMAN(element))
507 else if (IS_MCDUFFIN(element) || IS_LASER(element))
509 laser.start_edge.x = x;
510 laser.start_edge.y = y;
511 laser.start_angle = get_element_angle(element);
518 static void InitCycleElements_RotateSingleStep()
522 if (game_mm.num_cycle == 0) /* no elements to cycle */
525 for (i = 0; i < game_mm.num_cycle; i++)
527 int x = game_mm.cycle[i].x;
528 int y = game_mm.cycle[i].y;
529 int step = SIGN(game_mm.cycle[i].steps);
530 int last_element = Feld[x][y];
531 int next_element = get_rotated_element(last_element, step);
533 if (!game_mm.cycle[i].steps)
536 Feld[x][y] = next_element;
539 game_mm.cycle[i].steps -= step;
543 static void InitLaser()
545 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
546 int step = (IS_LASER(start_element) ? 4 : 0);
548 LX = laser.start_edge.x * TILEX;
549 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
552 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
554 LY = laser.start_edge.y * TILEY;
555 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
556 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
560 XS = 2 * Step[laser.start_angle].x;
561 YS = 2 * Step[laser.start_angle].y;
563 laser.current_angle = laser.start_angle;
565 laser.num_damages = 0;
567 laser.num_beamers = 0;
568 laser.beamer_edge[0] = 0;
570 laser.dest_element = EL_EMPTY;
573 AddLaserEdge(LX, LY); /* set laser starting edge */
575 pen_ray = GetPixelFromRGB(window,
576 native_mm_level.laser_red * 0xFF,
577 native_mm_level.laser_green * 0xFF,
578 native_mm_level.laser_blue * 0xFF);
581 void InitGameEngine_MM()
587 /* initialize laser bitmap to current playfield (screen) size */
588 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
589 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
593 /* set global game control values */
594 game_mm.num_cycle = 0;
595 game_mm.num_pacman = 0;
598 game_mm.energy_left = 0; // later set to "native_mm_level.time"
599 game_mm.kettles_still_needed =
600 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
601 game_mm.lights_still_needed = 0;
602 game_mm.num_keys = 0;
604 game_mm.level_solved = FALSE;
605 game_mm.game_over = FALSE;
606 game_mm.game_over_cause = 0;
608 game_mm.laser_overload_value = 0;
610 /* set global laser control values (must be set before "InitLaser()") */
611 laser.start_edge.x = 0;
612 laser.start_edge.y = 0;
613 laser.start_angle = 0;
615 for (i = 0; i < MAX_NUM_BEAMERS; i++)
616 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
618 laser.overloaded = FALSE;
619 laser.overload_value = 0;
620 laser.fuse_off = FALSE;
621 laser.fuse_x = laser.fuse_y = -1;
623 laser.dest_element = EL_EMPTY;
642 ClickElement(-1, -1, -1);
644 for (x = 0; x < lev_fieldx; x++)
646 for (y = 0; y < lev_fieldy; y++)
648 Feld[x][y] = Ur[x][y];
649 Hit[x][y] = Box[x][y] = 0;
651 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
652 Store[x][y] = Store2[x][y] = 0;
656 InitField(x, y, TRUE);
661 CloseDoor(DOOR_CLOSE_1);
667 void InitGameActions_MM()
669 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
670 int cycle_steps_done = 0;
676 /* copy default game door content to main double buffer */
677 BlitBitmap(pix[PIX_DOOR], drawto,
678 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
682 DrawText(DX_LEVEL, DY_LEVEL,
683 int2str(level_nr, 2), FONT_TEXT_2);
684 DrawText(DX_KETTLES, DY_KETTLES,
685 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
686 DrawText(DX_SCORE, DY_SCORE,
687 int2str(game_mm.score, 4), FONT_TEXT_2);
696 /* copy actual game door content to door double buffer for OpenDoor() */
697 BlitBitmap(drawto, pix[PIX_DB_DOOR],
698 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
702 OpenDoor(DOOR_OPEN_ALL);
705 for (i = 0; i <= num_init_game_frames; i++)
707 if (i == num_init_game_frames)
708 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
709 else if (setup.sound_loops)
710 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
712 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
714 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
716 UpdateAndDisplayGameControlValues();
718 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
720 InitCycleElements_RotateSingleStep();
730 if (setup.quick_doors)
736 if (setup.sound_music && num_bg_loops)
737 PlayMusic(level_nr % num_bg_loops);
742 if (game_mm.kettles_still_needed == 0)
746 void AddLaserEdge(int lx, int ly)
748 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
750 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
755 laser.edge[laser.num_edges].x = SX + 2 + lx;
756 laser.edge[laser.num_edges].y = SY + 2 + ly;
762 void AddDamagedField(int ex, int ey)
764 laser.damage[laser.num_damages].is_mirror = FALSE;
765 laser.damage[laser.num_damages].angle = laser.current_angle;
766 laser.damage[laser.num_damages].edge = laser.num_edges;
767 laser.damage[laser.num_damages].x = ex;
768 laser.damage[laser.num_damages].y = ey;
778 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
779 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
781 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
787 static int getMaskFromElement(int element)
789 if (IS_GRID(element))
790 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
791 else if (IS_MCDUFFIN(element))
792 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
793 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
794 return IMG_MM_MASK_RECTANGLE;
796 return IMG_MM_MASK_CIRCLE;
804 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
805 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
808 /* follow laser beam until it hits something (at least the screen border) */
809 while (hit_mask == HIT_MASK_NO_HIT)
815 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
816 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
818 printf("ScanPixel: touched screen border!\n");
824 for (i = 0; i < 4; i++)
826 int px = LX + (i % 2) * 2;
827 int py = LY + (i / 2) * 2;
830 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
831 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
834 if (IN_LEV_FIELD(lx, ly))
836 int element = Feld[lx][ly];
838 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
842 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
844 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
846 pixel = ((element & (1 << pos)) ? 1 : 0);
850 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
852 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
857 pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
858 SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
861 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
862 hit_mask |= (1 << i);
865 if (hit_mask == HIT_MASK_NO_HIT)
867 /* hit nothing -- go on with another step */
879 int end = 0, rf = laser.num_edges;
881 /* do not scan laser again after the game was lost for whatever reason */
882 if (game_mm.game_over)
885 laser.overloaded = FALSE;
886 laser.stops_inside_element = FALSE;
888 DrawLaser(0, DL_LASER_ENABLED);
891 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
899 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
902 laser.overloaded = TRUE;
907 hit_mask = ScanPixel();
910 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
914 /* hit something -- check out what it was */
915 ELX = (LX + XS) / TILEX;
916 ELY = (LY + YS) / TILEY;
919 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
920 hit_mask, LX, LY, ELX, ELY);
923 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
926 laser.dest_element = element;
931 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
933 /* we have hit the top-right and bottom-left element --
934 choose the bottom-left one */
935 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
936 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
937 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
938 ELX = (LX - 2) / TILEX;
939 ELY = (LY + 2) / TILEY;
942 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
944 /* we have hit the top-left and bottom-right element --
945 choose the top-left one */
946 /* !!! SEE ABOVE !!! */
947 ELX = (LX - 2) / TILEX;
948 ELY = (LY - 2) / TILEY;
952 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
953 hit_mask, LX, LY, ELX, ELY);
956 element = Feld[ELX][ELY];
957 laser.dest_element = element;
960 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
963 LX % TILEX, LY % TILEY,
968 if (!IN_LEV_FIELD(ELX, ELY))
969 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
972 if (element == EL_EMPTY)
974 if (!HitOnlyAnEdge(element, hit_mask))
977 else if (element == EL_FUSE_ON)
979 if (HitPolarizer(element, hit_mask))
982 else if (IS_GRID(element) || IS_DF_GRID(element))
984 if (HitPolarizer(element, hit_mask))
987 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
988 element == EL_GATE_STONE || element == EL_GATE_WOOD)
990 if (HitBlock(element, hit_mask))
997 else if (IS_MCDUFFIN(element))
999 if (HitLaserSource(element, hit_mask))
1002 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1003 IS_RECEIVER(element))
1005 if (HitLaserDestination(element, hit_mask))
1008 else if (IS_WALL(element))
1010 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1012 if (HitReflectingWalls(element, hit_mask))
1017 if (HitAbsorbingWalls(element, hit_mask))
1023 if (HitElement(element, hit_mask))
1028 DrawLaser(rf - 1, DL_LASER_ENABLED);
1029 rf = laser.num_edges;
1033 if (laser.dest_element != Feld[ELX][ELY])
1035 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
1036 laser.dest_element, Feld[ELX][ELY]);
1040 if (!end && !laser.stops_inside_element && !StepBehind())
1043 printf("ScanLaser: Go one step back\n");
1049 AddLaserEdge(LX, LY);
1053 DrawLaser(rf - 1, DL_LASER_ENABLED);
1055 Ct = CT = FrameCounter;
1058 if (!IN_LEV_FIELD(ELX, ELY))
1059 printf("WARNING! (2) %d, %d\n", ELX, ELY);
1063 void DrawLaserExt(int start_edge, int num_edges, int mode)
1069 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
1070 start_edge, num_edges, mode);
1075 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
1082 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
1088 if (mode == DL_LASER_DISABLED)
1090 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
1094 /* now draw the laser to the backbuffer and (if enabled) to the screen */
1095 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1097 redraw_mask |= REDRAW_FIELD;
1099 if (mode == DL_LASER_ENABLED)
1102 /* after the laser was deleted, the "damaged" graphics must be restored */
1103 if (laser.num_damages)
1105 int damage_start = 0;
1108 /* determine the starting edge, from which graphics need to be restored */
1111 for (i = 0; i < laser.num_damages; i++)
1113 if (laser.damage[i].edge == start_edge + 1)
1122 /* restore graphics from this starting edge to the end of damage list */
1123 for (i = damage_start; i < laser.num_damages; i++)
1125 int lx = laser.damage[i].x;
1126 int ly = laser.damage[i].y;
1127 int element = Feld[lx][ly];
1129 if (Hit[lx][ly] == laser.damage[i].edge)
1130 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1133 if (Box[lx][ly] == laser.damage[i].edge)
1136 if (IS_DRAWABLE(element))
1137 DrawField_MM(lx, ly);
1140 elx = laser.damage[damage_start].x;
1141 ely = laser.damage[damage_start].y;
1142 element = Feld[elx][ely];
1145 if (IS_BEAMER(element))
1149 for (i = 0; i < laser.num_beamers; i++)
1150 printf("-> %d\n", laser.beamer_edge[i]);
1151 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
1152 mode, elx, ely, Hit[elx][ely], start_edge);
1153 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
1154 get_element_angle(element), laser.damage[damage_start].angle);
1158 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1159 laser.num_beamers > 0 &&
1160 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1162 /* element is outgoing beamer */
1163 laser.num_damages = damage_start + 1;
1165 if (IS_BEAMER(element))
1166 laser.current_angle = get_element_angle(element);
1170 /* element is incoming beamer or other element */
1171 laser.num_damages = damage_start;
1172 laser.current_angle = laser.damage[laser.num_damages].angle;
1177 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
1179 elx = laser.start_edge.x;
1180 ely = laser.start_edge.y;
1181 element = Feld[elx][ely];
1184 laser.num_edges = start_edge + 1;
1185 if (start_edge == 0)
1186 laser.current_angle = laser.start_angle;
1188 LX = laser.edge[start_edge].x - (SX + 2);
1189 LY = laser.edge[start_edge].y - (SY + 2);
1190 XS = 2 * Step[laser.current_angle].x;
1191 YS = 2 * Step[laser.current_angle].y;
1194 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
1200 if (IS_BEAMER(element) ||
1201 IS_FIBRE_OPTIC(element) ||
1202 IS_PACMAN(element) ||
1203 IS_POLAR(element) ||
1204 IS_POLAR_CROSS(element) ||
1205 element == EL_FUSE_ON)
1210 printf("element == %d\n", element);
1213 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
1214 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1218 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1219 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1220 (laser.num_beamers == 0 ||
1221 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1223 /* element is incoming beamer or other element */
1224 step_size = -step_size;
1229 if (IS_BEAMER(element))
1231 printf("start_edge == %d, laser.beamer_edge == %d\n",
1232 start_edge, laser.beamer_edge);
1236 LX += step_size * XS;
1237 LY += step_size * YS;
1239 else if (element != EL_EMPTY)
1248 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
1253 void DrawLaser(int start_edge, int mode)
1255 if (laser.num_edges - start_edge < 0)
1257 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
1262 /* check if laser is interrupted by beamer element */
1263 if (laser.num_beamers > 0 &&
1264 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1266 if (mode == DL_LASER_ENABLED)
1269 int tmp_start_edge = start_edge;
1271 /* draw laser segments forward from the start to the last beamer */
1272 for (i = 0; i < laser.num_beamers; i++)
1274 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1276 if (tmp_num_edges <= 0)
1280 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
1281 i, laser.beamer_edge[i], tmp_start_edge);
1284 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1286 tmp_start_edge = laser.beamer_edge[i];
1289 /* draw last segment from last beamer to the end */
1290 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1296 int last_num_edges = laser.num_edges;
1297 int num_beamers = laser.num_beamers;
1299 /* delete laser segments backward from the end to the first beamer */
1300 for (i = num_beamers - 1; i >= 0; i--)
1302 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1304 if (laser.beamer_edge[i] - start_edge <= 0)
1307 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1309 last_num_edges = laser.beamer_edge[i];
1310 laser.num_beamers--;
1314 if (last_num_edges - start_edge <= 0)
1315 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1316 last_num_edges, start_edge);
1319 // special case when rotating first beamer: delete laser edge on beamer
1320 // (but do not start scanning on previous edge to prevent mirror sound)
1321 if (last_num_edges - start_edge == 1 && start_edge > 0)
1322 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1324 /* delete first segment from start to the first beamer */
1325 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1330 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1334 boolean HitElement(int element, int hit_mask)
1336 if (HitOnlyAnEdge(element, hit_mask))
1339 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1340 element = MovingOrBlocked2Element_MM(ELX, ELY);
1343 printf("HitElement (1): element == %d\n", element);
1347 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1348 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1350 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1353 AddDamagedField(ELX, ELY);
1355 /* this is more precise: check if laser would go through the center */
1356 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1358 /* skip the whole element before continuing the scan */
1364 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1366 if (LX/TILEX > ELX || LY/TILEY > ELY)
1368 /* skipping scan positions to the right and down skips one scan
1369 position too much, because this is only the top left scan position
1370 of totally four scan positions (plus one to the right, one to the
1371 bottom and one to the bottom right) */
1381 printf("HitElement (2): element == %d\n", element);
1384 if (LX + 5 * XS < 0 ||
1394 printf("HitElement (3): element == %d\n", element);
1397 if (IS_POLAR(element) &&
1398 ((element - EL_POLAR_START) % 2 ||
1399 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1401 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1403 laser.num_damages--;
1408 if (IS_POLAR_CROSS(element) &&
1409 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1411 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1413 laser.num_damages--;
1418 if (!IS_BEAMER(element) &&
1419 !IS_FIBRE_OPTIC(element) &&
1420 !IS_GRID_WOOD(element) &&
1421 element != EL_FUEL_EMPTY)
1424 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1425 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1427 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1430 LX = ELX * TILEX + 14;
1431 LY = ELY * TILEY + 14;
1433 AddLaserEdge(LX, LY);
1436 if (IS_MIRROR(element) ||
1437 IS_MIRROR_FIXED(element) ||
1438 IS_POLAR(element) ||
1439 IS_POLAR_CROSS(element) ||
1440 IS_DF_MIRROR(element) ||
1441 IS_DF_MIRROR_AUTO(element) ||
1442 element == EL_PRISM ||
1443 element == EL_REFRACTOR)
1445 int current_angle = laser.current_angle;
1448 laser.num_damages--;
1450 AddDamagedField(ELX, ELY);
1452 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1455 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1457 if (IS_MIRROR(element) ||
1458 IS_MIRROR_FIXED(element) ||
1459 IS_DF_MIRROR(element) ||
1460 IS_DF_MIRROR_AUTO(element))
1461 laser.current_angle = get_mirrored_angle(laser.current_angle,
1462 get_element_angle(element));
1464 if (element == EL_PRISM || element == EL_REFRACTOR)
1465 laser.current_angle = RND(16);
1467 XS = 2 * Step[laser.current_angle].x;
1468 YS = 2 * Step[laser.current_angle].y;
1470 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1475 LX += step_size * XS;
1476 LY += step_size * YS;
1479 /* draw sparkles on mirror */
1480 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1481 current_angle != laser.current_angle)
1483 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1487 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1488 current_angle != laser.current_angle)
1489 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1492 (get_opposite_angle(laser.current_angle) ==
1493 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1495 return (laser.overloaded ? TRUE : FALSE);
1498 if (element == EL_FUEL_FULL)
1500 laser.stops_inside_element = TRUE;
1505 if (element == EL_BOMB || element == EL_MINE)
1507 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1509 if (element == EL_MINE)
1510 laser.overloaded = TRUE;
1513 if (element == EL_KETTLE ||
1514 element == EL_CELL ||
1515 element == EL_KEY ||
1516 element == EL_LIGHTBALL ||
1517 element == EL_PACMAN ||
1520 if (!IS_PACMAN(element))
1523 if (element == EL_PACMAN)
1526 if (element == EL_KETTLE || element == EL_CELL)
1528 if (game_mm.kettles_still_needed > 0)
1529 game_mm.kettles_still_needed--;
1531 if (game_mm.kettles_still_needed == 0)
1535 DrawLaser(0, DL_LASER_ENABLED);
1538 else if (element == EL_KEY)
1542 else if (IS_PACMAN(element))
1544 DeletePacMan(ELX, ELY);
1547 RaiseScoreElement_MM(element);
1552 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1554 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1556 DrawLaser(0, DL_LASER_ENABLED);
1558 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1560 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1561 game_mm.lights_still_needed--;
1565 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1566 game_mm.lights_still_needed++;
1569 DrawField_MM(ELX, ELY);
1570 DrawLaser(0, DL_LASER_ENABLED);
1575 laser.stops_inside_element = TRUE;
1581 printf("HitElement (4): element == %d\n", element);
1584 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1585 laser.num_beamers < MAX_NUM_BEAMERS &&
1586 laser.beamer[BEAMER_NR(element)][1].num)
1588 int beamer_angle = get_element_angle(element);
1589 int beamer_nr = BEAMER_NR(element);
1593 printf("HitElement (BEAMER): element == %d\n", element);
1596 laser.num_damages--;
1598 if (IS_FIBRE_OPTIC(element) ||
1599 laser.current_angle == get_opposite_angle(beamer_angle))
1603 LX = ELX * TILEX + 14;
1604 LY = ELY * TILEY + 14;
1606 AddLaserEdge(LX, LY);
1607 AddDamagedField(ELX, ELY);
1609 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1612 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1614 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1615 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1616 ELX = laser.beamer[beamer_nr][pos].x;
1617 ELY = laser.beamer[beamer_nr][pos].y;
1618 LX = ELX * TILEX + 14;
1619 LY = ELY * TILEY + 14;
1621 if (IS_BEAMER(element))
1623 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1624 XS = 2 * Step[laser.current_angle].x;
1625 YS = 2 * Step[laser.current_angle].y;
1628 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1630 AddLaserEdge(LX, LY);
1631 AddDamagedField(ELX, ELY);
1633 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1636 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1638 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1643 LX += step_size * XS;
1644 LY += step_size * YS;
1646 laser.num_beamers++;
1655 boolean HitOnlyAnEdge(int element, int hit_mask)
1657 /* check if the laser hit only the edge of an element and, if so, go on */
1660 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1663 if ((hit_mask == HIT_MASK_TOPLEFT ||
1664 hit_mask == HIT_MASK_TOPRIGHT ||
1665 hit_mask == HIT_MASK_BOTTOMLEFT ||
1666 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1667 laser.current_angle % 4) /* angle is not 90° */
1671 if (hit_mask == HIT_MASK_TOPLEFT)
1676 else if (hit_mask == HIT_MASK_TOPRIGHT)
1681 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1686 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1692 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1698 printf("[HitOnlyAnEdge() == TRUE]\n");
1705 printf("[HitOnlyAnEdge() == FALSE]\n");
1711 boolean HitPolarizer(int element, int hit_mask)
1713 if (HitOnlyAnEdge(element, hit_mask))
1716 if (IS_DF_GRID(element))
1718 int grid_angle = get_element_angle(element);
1721 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1722 grid_angle, laser.current_angle);
1725 AddLaserEdge(LX, LY);
1726 AddDamagedField(ELX, ELY);
1729 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1731 if (laser.current_angle == grid_angle ||
1732 laser.current_angle == get_opposite_angle(grid_angle))
1734 /* skip the whole element before continuing the scan */
1740 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1742 if (LX/TILEX > ELX || LY/TILEY > ELY)
1744 /* skipping scan positions to the right and down skips one scan
1745 position too much, because this is only the top left scan position
1746 of totally four scan positions (plus one to the right, one to the
1747 bottom and one to the bottom right) */
1753 AddLaserEdge(LX, LY);
1759 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1761 LX / TILEX, LY / TILEY,
1762 LX % TILEX, LY % TILEY);
1767 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1769 return HitReflectingWalls(element, hit_mask);
1773 return HitAbsorbingWalls(element, hit_mask);
1776 else if (IS_GRID_STEEL(element))
1778 return HitReflectingWalls(element, hit_mask);
1780 else /* IS_GRID_WOOD */
1782 return HitAbsorbingWalls(element, hit_mask);
1788 boolean HitBlock(int element, int hit_mask)
1790 boolean check = FALSE;
1792 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1793 game_mm.num_keys == 0)
1796 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1799 int ex = ELX * TILEX + 14;
1800 int ey = ELY * TILEY + 14;
1804 for (i = 1; i < 32; i++)
1809 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1814 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1815 return HitAbsorbingWalls(element, hit_mask);
1819 AddLaserEdge(LX - XS, LY - YS);
1820 AddDamagedField(ELX, ELY);
1823 Box[ELX][ELY] = laser.num_edges;
1825 return HitReflectingWalls(element, hit_mask);
1828 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1830 int xs = XS / 2, ys = YS / 2;
1831 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1832 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1834 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1835 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1837 laser.overloaded = (element == EL_GATE_STONE);
1842 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1843 (hit_mask == HIT_MASK_TOP ||
1844 hit_mask == HIT_MASK_LEFT ||
1845 hit_mask == HIT_MASK_RIGHT ||
1846 hit_mask == HIT_MASK_BOTTOM))
1847 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1848 hit_mask == HIT_MASK_BOTTOM),
1849 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1850 hit_mask == HIT_MASK_RIGHT));
1851 AddLaserEdge(LX, LY);
1857 if (element == EL_GATE_STONE && Box[ELX][ELY])
1859 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1871 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1873 int xs = XS / 2, ys = YS / 2;
1874 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1875 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1877 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1878 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1880 laser.overloaded = (element == EL_BLOCK_STONE);
1885 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1886 (hit_mask == HIT_MASK_TOP ||
1887 hit_mask == HIT_MASK_LEFT ||
1888 hit_mask == HIT_MASK_RIGHT ||
1889 hit_mask == HIT_MASK_BOTTOM))
1890 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1891 hit_mask == HIT_MASK_BOTTOM),
1892 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1893 hit_mask == HIT_MASK_RIGHT));
1894 AddDamagedField(ELX, ELY);
1896 LX = ELX * TILEX + 14;
1897 LY = ELY * TILEY + 14;
1899 AddLaserEdge(LX, LY);
1901 laser.stops_inside_element = TRUE;
1909 boolean HitLaserSource(int element, int hit_mask)
1911 if (HitOnlyAnEdge(element, hit_mask))
1914 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1916 laser.overloaded = TRUE;
1921 boolean HitLaserDestination(int element, int hit_mask)
1923 if (HitOnlyAnEdge(element, hit_mask))
1926 if (element != EL_EXIT_OPEN &&
1927 !(IS_RECEIVER(element) &&
1928 game_mm.kettles_still_needed == 0 &&
1929 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1931 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1936 if (IS_RECEIVER(element) ||
1937 (IS_22_5_ANGLE(laser.current_angle) &&
1938 (ELX != (LX + 6 * XS) / TILEX ||
1939 ELY != (LY + 6 * YS) / TILEY ||
1948 LX = ELX * TILEX + 14;
1949 LY = ELY * TILEY + 14;
1951 laser.stops_inside_element = TRUE;
1954 AddLaserEdge(LX, LY);
1955 AddDamagedField(ELX, ELY);
1957 if (game_mm.lights_still_needed == 0)
1958 game_mm.level_solved = TRUE;
1963 boolean HitReflectingWalls(int element, int hit_mask)
1965 /* check if laser hits side of a wall with an angle that is not 90° */
1966 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1967 hit_mask == HIT_MASK_LEFT ||
1968 hit_mask == HIT_MASK_RIGHT ||
1969 hit_mask == HIT_MASK_BOTTOM))
1971 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1976 if (!IS_DF_GRID(element))
1977 AddLaserEdge(LX, LY);
1979 /* check if laser hits wall with an angle of 45° */
1980 if (!IS_22_5_ANGLE(laser.current_angle))
1982 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1985 laser.current_angle = get_mirrored_angle(laser.current_angle,
1988 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1991 laser.current_angle = get_mirrored_angle(laser.current_angle,
1995 AddLaserEdge(LX, LY);
1997 XS = 2 * Step[laser.current_angle].x;
1998 YS = 2 * Step[laser.current_angle].y;
2002 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2004 laser.current_angle = get_mirrored_angle(laser.current_angle,
2009 if (!IS_DF_GRID(element))
2010 AddLaserEdge(LX, LY);
2015 if (!IS_DF_GRID(element))
2016 AddLaserEdge(LX, LY + YS / 2);
2019 if (!IS_DF_GRID(element))
2020 AddLaserEdge(LX, LY);
2023 YS = 2 * Step[laser.current_angle].y;
2027 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2029 laser.current_angle = get_mirrored_angle(laser.current_angle,
2034 if (!IS_DF_GRID(element))
2035 AddLaserEdge(LX, LY);
2040 if (!IS_DF_GRID(element))
2041 AddLaserEdge(LX + XS / 2, LY);
2044 if (!IS_DF_GRID(element))
2045 AddLaserEdge(LX, LY);
2048 XS = 2 * Step[laser.current_angle].x;
2054 /* reflection at the edge of reflecting DF style wall */
2055 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2057 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2058 hit_mask == HIT_MASK_TOPRIGHT) ||
2059 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2060 hit_mask == HIT_MASK_TOPLEFT) ||
2061 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2062 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2063 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2064 hit_mask == HIT_MASK_BOTTOMRIGHT))
2067 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2068 ANG_MIRROR_135 : ANG_MIRROR_45);
2070 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2072 AddDamagedField(ELX, ELY);
2073 AddLaserEdge(LX, LY);
2075 laser.current_angle = get_mirrored_angle(laser.current_angle,
2083 AddLaserEdge(LX, LY);
2089 /* reflection inside an edge of reflecting DF style wall */
2090 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2092 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2093 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2094 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2095 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2096 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2097 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2098 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2099 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2102 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2103 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2104 ANG_MIRROR_135 : ANG_MIRROR_45);
2106 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2109 AddDamagedField(ELX, ELY);
2112 AddLaserEdge(LX - XS, LY - YS);
2113 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2114 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2116 laser.current_angle = get_mirrored_angle(laser.current_angle,
2124 AddLaserEdge(LX, LY);
2130 /* check if laser hits DF style wall with an angle of 90° */
2131 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2133 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2134 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2135 (IS_VERT_ANGLE(laser.current_angle) &&
2136 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2138 /* laser at last step touched nothing or the same side of the wall */
2139 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2141 AddDamagedField(ELX, ELY);
2148 last_hit_mask = hit_mask;
2155 if (!HitOnlyAnEdge(element, hit_mask))
2157 laser.overloaded = TRUE;
2165 boolean HitAbsorbingWalls(int element, int hit_mask)
2167 if (HitOnlyAnEdge(element, hit_mask))
2171 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2173 AddLaserEdge(LX - XS, LY - YS);
2180 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2182 AddLaserEdge(LX - XS, LY - YS);
2188 if (IS_WALL_WOOD(element) ||
2189 IS_DF_WALL_WOOD(element) ||
2190 IS_GRID_WOOD(element) ||
2191 IS_GRID_WOOD_FIXED(element) ||
2192 IS_GRID_WOOD_AUTO(element) ||
2193 element == EL_FUSE_ON ||
2194 element == EL_BLOCK_WOOD ||
2195 element == EL_GATE_WOOD)
2197 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2202 if (IS_WALL_ICE(element))
2206 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
2207 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
2209 /* check if laser hits wall with an angle of 90° */
2210 if (IS_90_ANGLE(laser.current_angle))
2211 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2213 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2217 for (i = 0; i < 4; i++)
2219 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2220 mask = 15 - (8 >> i);
2221 else if (ABS(XS) == 4 &&
2223 (XS > 0) == (i % 2) &&
2224 (YS < 0) == (i / 2))
2225 mask = 3 + (i / 2) * 9;
2226 else if (ABS(YS) == 4 &&
2228 (XS < 0) == (i % 2) &&
2229 (YS > 0) == (i / 2))
2230 mask = 5 + (i % 2) * 5;
2234 laser.wall_mask = mask;
2236 else if (IS_WALL_AMOEBA(element))
2238 int elx = (LX - 2 * XS) / TILEX;
2239 int ely = (LY - 2 * YS) / TILEY;
2240 int element2 = Feld[elx][ely];
2243 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2245 laser.dest_element = EL_EMPTY;
2253 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2254 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2256 if (IS_90_ANGLE(laser.current_angle))
2257 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2259 laser.dest_element = element2 | EL_WALL_AMOEBA;
2261 laser.wall_mask = mask;
2267 void OpenExit(int x, int y)
2271 if (!MovDelay[x][y]) /* next animation frame */
2272 MovDelay[x][y] = 4 * delay;
2274 if (MovDelay[x][y]) /* wait some time before next frame */
2279 phase = MovDelay[x][y] / delay;
2281 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2282 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2284 if (!MovDelay[x][y])
2286 Feld[x][y] = EL_EXIT_OPEN;
2292 void OpenSurpriseBall(int x, int y)
2296 if (!MovDelay[x][y]) /* next animation frame */
2297 MovDelay[x][y] = 50 * delay;
2299 if (MovDelay[x][y]) /* wait some time before next frame */
2303 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2306 int graphic = el2gfx(Store[x][y]);
2308 int dx = RND(26), dy = RND(26);
2310 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2312 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2313 SX + x * TILEX + dx, SY + y * TILEY + dy);
2315 MarkTileDirty(x, y);
2318 if (!MovDelay[x][y])
2320 Feld[x][y] = Store[x][y];
2329 void MeltIce(int x, int y)
2334 if (!MovDelay[x][y]) /* next animation frame */
2335 MovDelay[x][y] = frames * delay;
2337 if (MovDelay[x][y]) /* wait some time before next frame */
2340 int wall_mask = Store2[x][y];
2341 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2344 phase = frames - MovDelay[x][y] / delay - 1;
2346 if (!MovDelay[x][y])
2350 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2351 Store[x][y] = Store2[x][y] = 0;
2353 DrawWalls_MM(x, y, Feld[x][y]);
2355 if (Feld[x][y] == EL_WALL_ICE)
2356 Feld[x][y] = EL_EMPTY;
2358 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2359 if (laser.damage[i].is_mirror)
2363 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2365 DrawLaser(0, DL_LASER_DISABLED);
2369 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2371 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2373 laser.redraw = TRUE;
2378 void GrowAmoeba(int x, int y)
2383 if (!MovDelay[x][y]) /* next animation frame */
2384 MovDelay[x][y] = frames * delay;
2386 if (MovDelay[x][y]) /* wait some time before next frame */
2389 int wall_mask = Store2[x][y];
2390 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2393 phase = MovDelay[x][y] / delay;
2395 if (!MovDelay[x][y])
2397 Feld[x][y] = real_element;
2398 Store[x][y] = Store2[x][y] = 0;
2400 DrawWalls_MM(x, y, Feld[x][y]);
2401 DrawLaser(0, DL_LASER_ENABLED);
2403 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2405 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2410 static void Explode_MM(int x, int y, int phase, int mode)
2412 int num_phase = 9, delay = 2;
2413 int last_phase = num_phase * delay;
2414 int half_phase = (num_phase / 2) * delay;
2416 laser.redraw = TRUE;
2418 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2420 int center_element = Feld[x][y];
2422 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2424 /* put moving element to center field (and let it explode there) */
2425 center_element = MovingOrBlocked2Element_MM(x, y);
2426 RemoveMovingField_MM(x, y);
2428 Feld[x][y] = center_element;
2431 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2432 Store[x][y] = center_element;
2434 Store[x][y] = EL_EMPTY;
2436 Store2[x][y] = mode;
2437 Feld[x][y] = EL_EXPLODING_OPAQUE;
2438 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2444 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2446 if (phase == half_phase)
2448 Feld[x][y] = EL_EXPLODING_TRANSP;
2450 if (x == ELX && y == ELY)
2454 if (phase == last_phase)
2456 if (Store[x][y] == EL_BOMB)
2458 DrawLaser(0, DL_LASER_DISABLED);
2461 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2462 Store[x][y] = EL_EMPTY;
2464 game_mm.game_over = TRUE;
2465 game_mm.game_over_cause = GAME_OVER_BOMB;
2467 laser.overloaded = FALSE;
2469 else if (IS_MCDUFFIN(Store[x][y]))
2471 Store[x][y] = EL_EMPTY;
2474 Feld[x][y] = Store[x][y];
2475 Store[x][y] = Store2[x][y] = 0;
2476 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2478 InitField(x, y, FALSE);
2481 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2483 int graphic = IMG_MM_DEFAULT_EXPLODING;
2484 int graphic_phase = (phase / delay - 1);
2488 if (Store2[x][y] == EX_KETTLE)
2490 if (graphic_phase < 3)
2492 graphic = IMG_MM_KETTLE_EXPLODING;
2494 else if (graphic_phase < 5)
2500 graphic = IMG_EMPTY;
2504 else if (Store2[x][y] == EX_SHORT)
2506 if (graphic_phase < 4)
2512 graphic = IMG_EMPTY;
2517 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2519 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2520 FX + x * TILEX, FY + y * TILEY);
2522 MarkTileDirty(x, y);
2526 static void Bang_MM(int x, int y)
2528 int element = Feld[x][y];
2529 int mode = EX_NORMAL;
2532 DrawLaser(0, DL_LASER_ENABLED);
2551 if (IS_PACMAN(element))
2552 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2553 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2554 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2555 else if (element == EL_KEY)
2556 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2558 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2560 Explode_MM(x, y, EX_PHASE_START, mode);
2563 void TurnRound(int x, int y)
2575 { 0, 0 }, { 0, 0 }, { 0, 0 },
2580 int left, right, back;
2584 { MV_DOWN, MV_UP, MV_RIGHT },
2585 { MV_UP, MV_DOWN, MV_LEFT },
2587 { MV_LEFT, MV_RIGHT, MV_DOWN },
2588 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2589 { MV_RIGHT, MV_LEFT, MV_UP }
2592 int element = Feld[x][y];
2593 int old_move_dir = MovDir[x][y];
2594 int right_dir = turn[old_move_dir].right;
2595 int back_dir = turn[old_move_dir].back;
2596 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2597 int right_x = x + right_dx, right_y = y + right_dy;
2599 if (element == EL_PACMAN)
2601 boolean can_turn_right = FALSE;
2603 if (IN_LEV_FIELD(right_x, right_y) &&
2604 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2605 can_turn_right = TRUE;
2608 MovDir[x][y] = right_dir;
2610 MovDir[x][y] = back_dir;
2616 static void StartMoving_MM(int x, int y)
2618 int element = Feld[x][y];
2623 if (CAN_MOVE(element))
2627 if (MovDelay[x][y]) /* wait some time before next movement */
2635 /* now make next step */
2637 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2639 if (element == EL_PACMAN &&
2640 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2641 !ObjHit(newx, newy, HIT_POS_CENTER))
2643 Store[newx][newy] = Feld[newx][newy];
2644 Feld[newx][newy] = EL_EMPTY;
2646 DrawField_MM(newx, newy);
2648 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2649 ObjHit(newx, newy, HIT_POS_CENTER))
2651 /* object was running against a wall */
2658 InitMovingField_MM(x, y, MovDir[x][y]);
2662 ContinueMoving_MM(x, y);
2665 static void ContinueMoving_MM(int x, int y)
2667 int element = Feld[x][y];
2668 int direction = MovDir[x][y];
2669 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2670 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2671 int horiz_move = (dx!=0);
2672 int newx = x + dx, newy = y + dy;
2673 int step = (horiz_move ? dx : dy) * TILEX / 8;
2675 MovPos[x][y] += step;
2677 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2679 Feld[x][y] = EL_EMPTY;
2680 Feld[newx][newy] = element;
2682 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2683 MovDelay[newx][newy] = 0;
2685 if (!CAN_MOVE(element))
2686 MovDir[newx][newy] = 0;
2689 DrawField_MM(newx, newy);
2691 Stop[newx][newy] = TRUE;
2693 if (element == EL_PACMAN)
2695 if (Store[newx][newy] == EL_BOMB)
2696 Bang_MM(newx, newy);
2698 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2699 (LX + 2 * XS) / TILEX == newx &&
2700 (LY + 2 * YS) / TILEY == newy)
2707 else /* still moving on */
2712 laser.redraw = TRUE;
2715 void ClickElement(int x, int y, int button)
2717 static unsigned int click_delay = 0;
2718 static int click_delay_value = CLICK_DELAY;
2719 static boolean new_button = TRUE;
2724 /* initialize static variables */
2726 click_delay_value = CLICK_DELAY;
2732 /* do not rotate objects hit by the laser after the game was solved */
2733 if (game_mm.level_solved && Hit[x][y])
2736 if (button == MB_RELEASED)
2739 click_delay_value = CLICK_DELAY;
2741 /* release eventually hold auto-rotating mirror */
2742 RotateMirror(x, y, MB_RELEASED);
2747 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2750 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2753 if (!IN_LEV_FIELD(x, y))
2756 if (Feld[x][y] == EL_EMPTY)
2759 element = Feld[x][y];
2761 if (IS_MIRROR(element) ||
2762 IS_BEAMER(element) ||
2763 IS_POLAR(element) ||
2764 IS_POLAR_CROSS(element) ||
2765 IS_DF_MIRROR(element) ||
2766 IS_DF_MIRROR_AUTO(element))
2768 RotateMirror(x, y, button);
2770 else if (IS_MCDUFFIN(element))
2772 if (!laser.fuse_off)
2774 DrawLaser(0, DL_LASER_DISABLED);
2781 element = get_rotated_element(element, BUTTON_ROTATION(button));
2782 laser.start_angle = get_element_angle(element);
2786 Feld[x][y] = element;
2793 if (!laser.fuse_off)
2796 else if (element == EL_FUSE_ON && laser.fuse_off)
2798 if (x != laser.fuse_x || y != laser.fuse_y)
2801 laser.fuse_off = FALSE;
2802 laser.fuse_x = laser.fuse_y = -1;
2804 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2807 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2809 laser.fuse_off = TRUE;
2812 laser.overloaded = FALSE;
2814 DrawLaser(0, DL_LASER_DISABLED);
2815 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2817 else if (element == EL_LIGHTBALL)
2820 RaiseScoreElement_MM(element);
2821 DrawLaser(0, DL_LASER_ENABLED);
2824 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2828 void RotateMirror(int x, int y, int button)
2830 if (button == MB_RELEASED)
2832 /* release eventually hold auto-rotating mirror */
2839 if (IS_MIRROR(Feld[x][y]) ||
2840 IS_POLAR_CROSS(Feld[x][y]) ||
2841 IS_POLAR(Feld[x][y]) ||
2842 IS_BEAMER(Feld[x][y]) ||
2843 IS_DF_MIRROR(Feld[x][y]) ||
2844 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2845 IS_GRID_WOOD_AUTO(Feld[x][y]))
2847 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2849 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2851 if (button == MB_LEFTBUTTON)
2853 /* left mouse button only for manual adjustment, no auto-rotating;
2854 freeze mirror for until mouse button released */
2858 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2860 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2864 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2866 int edge = Hit[x][y];
2872 DrawLaser(edge - 1, DL_LASER_DISABLED);
2876 else if (ObjHit(x, y, HIT_POS_CENTER))
2878 int edge = Hit[x][y];
2882 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2886 DrawLaser(edge - 1, DL_LASER_DISABLED);
2893 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2898 if ((IS_BEAMER(Feld[x][y]) ||
2899 IS_POLAR(Feld[x][y]) ||
2900 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2904 if (IS_BEAMER(Feld[x][y]))
2907 printf("TEST (%d, %d) [%d] [%d]\n",
2909 laser.beamer_edge, laser.beamer[1].num);
2919 DrawLaser(0, DL_LASER_ENABLED);
2923 void AutoRotateMirrors()
2927 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2930 for (x = 0; x < lev_fieldx; x++)
2932 for (y = 0; y < lev_fieldy; y++)
2934 int element = Feld[x][y];
2936 /* do not rotate objects hit by the laser after the game was solved */
2937 if (game_mm.level_solved && Hit[x][y])
2940 if (IS_DF_MIRROR_AUTO(element) ||
2941 IS_GRID_WOOD_AUTO(element) ||
2942 IS_GRID_STEEL_AUTO(element) ||
2943 element == EL_REFRACTOR)
2944 RotateMirror(x, y, MB_RIGHTBUTTON);
2949 boolean ObjHit(int obx, int oby, int bits)
2956 if (bits & HIT_POS_CENTER)
2958 if (CheckLaserPixel(SX + obx + 15,
2963 if (bits & HIT_POS_EDGE)
2965 for (i = 0; i < 4; i++)
2966 if (CheckLaserPixel(SX + obx + 31 * (i % 2),
2967 SY + oby + 31 * (i / 2)))
2971 if (bits & HIT_POS_BETWEEN)
2973 for (i = 0; i < 4; i++)
2974 if (CheckLaserPixel(SX + 4 + obx + 22 * (i % 2),
2975 SY + 4 + oby + 22 * (i / 2)))
2982 void DeletePacMan(int px, int py)
2988 if (game_mm.num_pacman <= 1)
2990 game_mm.num_pacman = 0;
2994 for (i = 0; i < game_mm.num_pacman; i++)
2995 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2998 game_mm.num_pacman--;
3000 for (j = i; j < game_mm.num_pacman; j++)
3002 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3003 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3004 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3008 void ColorCycling(void)
3010 static int CC, Cc = 0;
3012 static int color, old = 0xF00, new = 0x010, mult = 1;
3013 static unsigned short red, green, blue;
3015 if (color_status == STATIC_COLORS)
3020 if (CC < Cc || CC > Cc + 2)
3024 color = old + new * mult;
3030 if (ABS(mult) == 16)
3040 red = 0x0e00 * ((color & 0xF00) >> 8);
3041 green = 0x0e00 * ((color & 0x0F0) >> 4);
3042 blue = 0x0e00 * ((color & 0x00F));
3043 SetRGB(pen_magicolor[0], red, green, blue);
3045 red = 0x1111 * ((color & 0xF00) >> 8);
3046 green = 0x1111 * ((color & 0x0F0) >> 4);
3047 blue = 0x1111 * ((color & 0x00F));
3048 SetRGB(pen_magicolor[1], red, green, blue);
3052 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3059 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3062 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3064 element = Feld[x][y];
3066 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3067 StartMoving_MM(x, y);
3068 else if (IS_MOVING(x, y))
3069 ContinueMoving_MM(x, y);
3070 else if (IS_EXPLODING(element))
3071 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3072 else if (element == EL_EXIT_OPENING)
3074 else if (element == EL_GRAY_BALL_OPENING)
3075 OpenSurpriseBall(x, y);
3076 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3078 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3082 AutoRotateMirrors();
3085 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3087 /* redraw after Explode_MM() ... */
3089 DrawLaser(0, DL_LASER_ENABLED);
3090 laser.redraw = FALSE;
3095 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3099 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3101 DrawLaser(0, DL_LASER_DISABLED);
3106 if (FrameReached(&energy_delay, ENERGY_DELAY))
3108 if (game_mm.energy_left > 0)
3110 game_mm.energy_left--;
3113 BlitBitmap(pix[PIX_DOOR], drawto,
3114 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3115 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3116 DX_ENERGY, DY_ENERGY);
3118 redraw_mask |= REDRAW_DOOR_1;
3120 else if (setup.time_limit && !game_mm.game_over)
3124 for (i = 15; i >= 0; i--)
3127 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3129 pen_ray = GetPixelFromRGB(window,
3130 native_mm_level.laser_red * 0x11 * i,
3131 native_mm_level.laser_green * 0x11 * i,
3132 native_mm_level.laser_blue * 0x11 * i);
3134 DrawLaser(0, DL_LASER_ENABLED);
3139 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3142 DrawLaser(0, DL_LASER_DISABLED);
3143 game_mm.game_over = TRUE;
3144 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3147 if (Request("Out of magic energy ! Play it again ?",
3148 REQ_ASK | REQ_STAY_CLOSED))
3154 game_status = MAINMENU;
3163 element = laser.dest_element;
3166 if (element != Feld[ELX][ELY])
3168 printf("element == %d, Feld[ELX][ELY] == %d\n",
3169 element, Feld[ELX][ELY]);
3173 if (!laser.overloaded && laser.overload_value == 0 &&
3174 element != EL_BOMB &&
3175 element != EL_MINE &&
3176 element != EL_BALL_GRAY &&
3177 element != EL_BLOCK_STONE &&
3178 element != EL_BLOCK_WOOD &&
3179 element != EL_FUSE_ON &&
3180 element != EL_FUEL_FULL &&
3181 !IS_WALL_ICE(element) &&
3182 !IS_WALL_AMOEBA(element))
3185 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3186 (!laser.overloaded && laser.overload_value > 0)) &&
3187 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3189 if (laser.overloaded)
3190 laser.overload_value++;
3192 laser.overload_value--;
3194 if (game_mm.cheat_no_overload)
3196 laser.overloaded = FALSE;
3197 laser.overload_value = 0;
3200 game_mm.laser_overload_value = laser.overload_value;
3202 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3204 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3205 int color_down = 0xFF - color_up;
3208 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3209 (15 - (laser.overload_value / 6)) * color_scale);
3212 GetPixelFromRGB(window,
3213 (native_mm_level.laser_red ? 0xFF : color_up),
3214 (native_mm_level.laser_green ? color_down : 0x00),
3215 (native_mm_level.laser_blue ? color_down : 0x00));
3217 DrawLaser(0, DL_LASER_ENABLED);
3223 if (!laser.overloaded)
3224 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3225 else if (setup.sound_loops)
3226 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3228 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3230 if (laser.overloaded)
3233 BlitBitmap(pix[PIX_DOOR], drawto,
3234 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3235 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3236 - laser.overload_value,
3237 OVERLOAD_XSIZE, laser.overload_value,
3238 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3239 - laser.overload_value);
3241 redraw_mask |= REDRAW_DOOR_1;
3246 BlitBitmap(pix[PIX_DOOR], drawto,
3247 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3248 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3249 DX_OVERLOAD, DY_OVERLOAD);
3251 redraw_mask |= REDRAW_DOOR_1;
3254 if (laser.overload_value == MAX_LASER_OVERLOAD)
3258 for (i = 15; i >= 0; i--)
3261 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3264 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3266 DrawLaser(0, DL_LASER_ENABLED);
3271 DrawLaser(0, DL_LASER_DISABLED);
3273 game_mm.game_over = TRUE;
3274 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3277 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3278 REQ_ASK | REQ_STAY_CLOSED))
3284 game_status = MAINMENU;
3298 if (element == EL_BOMB && CT > 75)
3300 if (game_mm.cheat_no_explosion)
3304 laser.num_damages--;
3305 DrawLaser(0, DL_LASER_DISABLED);
3306 laser.num_edges = 0;
3311 laser.dest_element = EL_EXPLODING_OPAQUE;
3315 laser.num_damages--;
3316 DrawLaser(0, DL_LASER_DISABLED);
3318 laser.num_edges = 0;
3319 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3321 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3322 REQ_ASK | REQ_STAY_CLOSED))
3328 game_status = MAINMENU;
3336 if (element == EL_FUSE_ON && CT > 25)
3338 laser.fuse_off = TRUE;
3342 DrawLaser(0, DL_LASER_DISABLED);
3343 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3346 if (element == EL_BALL_GRAY && CT > 75)
3348 static int new_elements[] =
3351 EL_MIRROR_FIXED_START,
3353 EL_POLAR_CROSS_START,
3359 int num_new_elements = sizeof(new_elements) / sizeof(int);
3360 int new_element = new_elements[RND(num_new_elements)];
3362 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3363 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3365 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3376 element = EL_MIRROR_START + RND(16);
3382 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3389 element = (rnd == 0 ? EL_FUSE_ON :
3390 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3391 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3392 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3393 EL_MIRROR_FIXED_START + rnd - 25);
3398 graphic = el2gfx(element);
3400 for (i = 0; i < 50; i++)
3406 BlitBitmap(pix[PIX_BACK], drawto,
3407 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3408 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3409 SX + ELX * TILEX + x,
3410 SY + ELY * TILEY + y);
3412 MarkTileDirty(ELX, ELY);
3415 DrawLaser(0, DL_LASER_ENABLED);
3420 Feld[ELX][ELY] = element;
3421 DrawField_MM(ELX, ELY);
3424 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3427 /* above stuff: GRAY BALL -> PRISM !!! */
3429 LX = ELX * TILEX + 14;
3430 LY = ELY * TILEY + 14;
3431 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3438 laser.num_edges -= 2;
3439 laser.num_damages--;
3443 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3444 if (laser.damage[i].is_mirror)
3448 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3450 DrawLaser(0, DL_LASER_DISABLED);
3452 DrawLaser(0, DL_LASER_DISABLED);
3458 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3465 if (IS_WALL_ICE(element) && CT > 50)
3467 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3470 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3471 Store[ELX][ELY] = EL_WALL_ICE;
3472 Store2[ELX][ELY] = laser.wall_mask;
3474 laser.dest_element = Feld[ELX][ELY];
3479 for (i = 0; i < 5; i++)
3485 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3489 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3494 if (Feld[ELX][ELY] == EL_WALL_ICE)
3495 Feld[ELX][ELY] = EL_EMPTY;
3499 LX = laser.edge[laser.num_edges].x - (SX + 2);
3500 LY = laser.edge[laser.num_edges].y - (SY + 2);
3503 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3504 if (laser.damage[i].is_mirror)
3508 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3510 DrawLaser(0, DL_LASER_DISABLED);
3517 if (IS_WALL_AMOEBA(element) && CT > 60)
3519 int k1, k2, k3, dx, dy, de, dm;
3520 int element2 = Feld[ELX][ELY];
3522 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3525 for (i = laser.num_damages - 1; i >= 0; i--)
3526 if (laser.damage[i].is_mirror)
3529 r = laser.num_edges;
3530 d = laser.num_damages;
3537 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3540 DrawLaser(0, DL_LASER_ENABLED);
3543 x = laser.damage[k1].x;
3544 y = laser.damage[k1].y;
3549 for (i = 0; i < 4; i++)
3551 if (laser.wall_mask & (1 << i))
3553 if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3554 SY + ELY * TILEY + 31 * (i / 2)))
3557 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3558 SY + ELY * TILEY + 14 + (i / 2) * 2))
3565 for (i = 0; i < 4; i++)
3567 if (laser.wall_mask & (1 << i))
3569 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3570 SY + ELY * TILEY + 31 * (i / 2)))
3577 if (laser.num_beamers > 0 ||
3578 k1 < 1 || k2 < 4 || k3 < 4 ||
3579 CheckLaserPixel(SX + ELX * TILEX + 14,
3580 SY + ELY * TILEY + 14))
3582 laser.num_edges = r;
3583 laser.num_damages = d;
3585 DrawLaser(0, DL_LASER_DISABLED);
3588 Feld[ELX][ELY] = element | laser.wall_mask;
3592 de = Feld[ELX][ELY];
3593 dm = laser.wall_mask;
3597 int x = ELX, y = ELY;
3598 int wall_mask = laser.wall_mask;
3601 DrawLaser(0, DL_LASER_ENABLED);
3603 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3605 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3606 Store[x][y] = EL_WALL_AMOEBA;
3607 Store2[x][y] = wall_mask;
3613 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3615 DrawLaser(0, DL_LASER_ENABLED);
3617 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3619 for (i = 4; i >= 0; i--)
3621 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3627 DrawLaser(0, DL_LASER_ENABLED);
3632 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3633 laser.stops_inside_element && CT > 75)
3638 if (ABS(XS) > ABS(YS))
3645 for (i = 0; i < 4; i++)
3652 x = ELX + Step[k * 4].x;
3653 y = ELY + Step[k * 4].y;
3655 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3658 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3666 laser.overloaded = (element == EL_BLOCK_STONE);
3671 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3674 Feld[x][y] = element;
3676 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3679 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3681 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3682 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3690 if (element == EL_FUEL_FULL && CT > 10)
3692 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3695 BlitBitmap(pix[PIX_DOOR], drawto,
3696 DOOR_GFX_PAGEX4 + XX_ENERGY,
3697 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3698 ENERGY_XSIZE, i, DX_ENERGY,
3699 DY_ENERGY + ENERGY_YSIZE - i);
3702 redraw_mask |= REDRAW_DOOR_1;
3708 game_mm.energy_left = MAX_LASER_ENERGY;
3709 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3710 DrawField_MM(ELX, ELY);
3712 DrawLaser(0, DL_LASER_ENABLED);
3720 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3722 ClickElement(action.lx, action.ly, action.button);
3724 GameActions_MM_Ext(action, warp_mode);
3729 int mx, my, ox, oy, nx, ny;
3733 if (++pacman_nr >= game_mm.num_pacman)
3736 game_mm.pacman[pacman_nr].dir--;
3738 for (l = 1; l < 5; l++)
3740 game_mm.pacman[pacman_nr].dir++;
3742 if (game_mm.pacman[pacman_nr].dir > 4)
3743 game_mm.pacman[pacman_nr].dir = 1;
3745 if (game_mm.pacman[pacman_nr].dir % 2)
3748 my = game_mm.pacman[pacman_nr].dir - 2;
3753 mx = 3 - game_mm.pacman[pacman_nr].dir;
3756 ox = game_mm.pacman[pacman_nr].x;
3757 oy = game_mm.pacman[pacman_nr].y;
3760 element = Feld[nx][ny];
3762 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3765 if (!IS_EATABLE4PACMAN(element))
3768 if (ObjHit(nx, ny, HIT_POS_CENTER))
3771 Feld[ox][oy] = EL_EMPTY;
3773 EL_PACMAN_RIGHT - 1 +
3774 (game_mm.pacman[pacman_nr].dir - 1 +
3775 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3777 game_mm.pacman[pacman_nr].x = nx;
3778 game_mm.pacman[pacman_nr].y = ny;
3780 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3782 if (element != EL_EMPTY)
3784 int graphic = el2gfx(Feld[nx][ny]);
3789 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3792 ox = SX + ox * TILEX;
3793 oy = SY + oy * TILEY;
3795 for (i = 1; i < 33; i += 2)
3796 BlitBitmap(bitmap, window,
3797 src_x, src_y, TILEX, TILEY,
3798 ox + i * mx, oy + i * my);
3799 Ct = Ct + FrameCounter - CT;
3802 DrawField_MM(nx, ny);
3805 if (!laser.fuse_off)
3807 DrawLaser(0, DL_LASER_ENABLED);
3809 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3811 AddDamagedField(nx, ny);
3813 laser.damage[laser.num_damages - 1].edge = 0;
3817 if (element == EL_BOMB)
3818 DeletePacMan(nx, ny);
3820 if (IS_WALL_AMOEBA(element) &&
3821 (LX + 2 * XS) / TILEX == nx &&
3822 (LY + 2 * YS) / TILEY == ny)
3835 boolean raise_level = FALSE;
3838 if (local_player->MovPos)
3841 local_player->LevelSolved = FALSE;
3844 if (game_mm.energy_left)
3846 if (setup.sound_loops)
3847 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3848 SND_CTRL_PLAY_LOOP);
3850 while (game_mm.energy_left > 0)
3852 if (!setup.sound_loops)
3853 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3856 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3857 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3862 game_mm.energy_left--;
3863 if (game_mm.energy_left >= 0)
3866 BlitBitmap(pix[PIX_DOOR], drawto,
3867 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3868 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3869 DX_ENERGY, DY_ENERGY);
3871 redraw_mask |= REDRAW_DOOR_1;
3878 if (setup.sound_loops)
3879 StopSound(SND_SIRR);
3881 else if (native_mm_level.time == 0) /* level without time limit */
3883 if (setup.sound_loops)
3884 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3885 SND_CTRL_PLAY_LOOP);
3887 while (TimePlayed < 999)
3889 if (!setup.sound_loops)
3890 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3891 if (TimePlayed < 999 && !(TimePlayed % 10))
3892 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3893 if (TimePlayed < 900 && !(TimePlayed % 10))
3899 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3906 if (setup.sound_loops)
3907 StopSound(SND_SIRR);
3914 CloseDoor(DOOR_CLOSE_1);
3916 Request("Level solved !", REQ_CONFIRM);
3918 if (level_nr == leveldir_current->handicap_level)
3920 leveldir_current->handicap_level++;
3921 SaveLevelSetup_SeriesInfo();
3924 if (level_editor_test_game)
3925 game_mm.score = -1; /* no highscore when playing from editor */
3926 else if (level_nr < leveldir_current->last_level)
3927 raise_level = TRUE; /* advance to next level */
3929 if ((hi_pos = NewHiScore_MM()) >= 0)
3931 game_status = HALLOFFAME;
3933 // DrawHallOfFame(hi_pos);
3940 game_status = MAINMENU;
3956 // LoadScore(level_nr);
3958 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3959 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3962 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3964 if (game_mm.score > highscore[k].Score)
3966 /* player has made it to the hall of fame */
3968 if (k < MAX_SCORE_ENTRIES - 1)
3970 int m = MAX_SCORE_ENTRIES - 1;
3973 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3974 if (!strcmp(setup.player_name, highscore[l].Name))
3976 if (m == k) /* player's new highscore overwrites his old one */
3980 for (l = m; l>k; l--)
3982 strcpy(highscore[l].Name, highscore[l - 1].Name);
3983 highscore[l].Score = highscore[l - 1].Score;
3990 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3991 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3992 highscore[k].Score = game_mm.score;
3999 else if (!strncmp(setup.player_name, highscore[k].Name,
4000 MAX_PLAYER_NAME_LEN))
4001 break; /* player already there with a higher score */
4006 // if (position >= 0)
4007 // SaveScore(level_nr);
4012 static void InitMovingField_MM(int x, int y, int direction)
4014 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4015 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4017 MovDir[x][y] = direction;
4018 MovDir[newx][newy] = direction;
4020 if (Feld[newx][newy] == EL_EMPTY)
4021 Feld[newx][newy] = EL_BLOCKED;
4024 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4026 int direction = MovDir[x][y];
4027 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4028 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4034 static void Blocked2Moving_MM(int x, int y,
4035 int *comes_from_x, int *comes_from_y)
4037 int oldx = x, oldy = y;
4038 int direction = MovDir[x][y];
4040 if (direction == MV_LEFT)
4042 else if (direction == MV_RIGHT)
4044 else if (direction == MV_UP)
4046 else if (direction == MV_DOWN)
4049 *comes_from_x = oldx;
4050 *comes_from_y = oldy;
4053 static int MovingOrBlocked2Element_MM(int x, int y)
4055 int element = Feld[x][y];
4057 if (element == EL_BLOCKED)
4061 Blocked2Moving_MM(x, y, &oldx, &oldy);
4063 return Feld[oldx][oldy];
4070 static void RemoveField(int x, int y)
4072 Feld[x][y] = EL_EMPTY;
4079 static void RemoveMovingField_MM(int x, int y)
4081 int oldx = x, oldy = y, newx = x, newy = y;
4083 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4086 if (IS_MOVING(x, y))
4088 Moving2Blocked_MM(x, y, &newx, &newy);
4089 if (Feld[newx][newy] != EL_BLOCKED)
4092 else if (Feld[x][y] == EL_BLOCKED)
4094 Blocked2Moving_MM(x, y, &oldx, &oldy);
4095 if (!IS_MOVING(oldx, oldy))
4099 Feld[oldx][oldy] = EL_EMPTY;
4100 Feld[newx][newy] = EL_EMPTY;
4101 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4102 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4104 DrawLevelField_MM(oldx, oldy);
4105 DrawLevelField_MM(newx, newy);
4108 void PlaySoundLevel(int x, int y, int sound_nr)
4110 int sx = SCREENX(x), sy = SCREENY(y);
4112 int silence_distance = 8;
4114 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4115 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4118 if (!IN_LEV_FIELD(x, y) ||
4119 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4120 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4123 volume = SOUND_MAX_VOLUME;
4126 stereo = (sx - SCR_FIELDX/2) * 12;
4128 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4129 if (stereo > SOUND_MAX_RIGHT)
4130 stereo = SOUND_MAX_RIGHT;
4131 if (stereo < SOUND_MAX_LEFT)
4132 stereo = SOUND_MAX_LEFT;
4135 if (!IN_SCR_FIELD(sx, sy))
4137 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4138 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4140 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4143 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4146 static void RaiseScore_MM(int value)
4148 game_mm.score += value;
4151 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4156 void RaiseScoreElement_MM(int element)
4161 case EL_PACMAN_RIGHT:
4163 case EL_PACMAN_LEFT:
4164 case EL_PACMAN_DOWN:
4165 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4169 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4174 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4178 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);