1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
101 static void AddLaserEdge(int, int);
102 static void ScanLaser(void);
103 static void DrawLaser(int, int);
104 static boolean HitElement(int, int);
105 static boolean HitOnlyAnEdge(int);
106 static boolean HitPolarizer(int, int);
107 static boolean HitBlock(int, int);
108 static boolean HitLaserSource(int, int);
109 static boolean HitLaserDestination(int, int);
110 static boolean HitReflectingWalls(int, int);
111 static boolean HitAbsorbingWalls(int, int);
112 static void RotateMirror(int, int, int);
113 static boolean ObjHit(int, int, int);
114 static void DeletePacMan(int, int);
115 static void MovePacMen(void);
117 // bitmap for laser beam detection
118 static Bitmap *laser_bitmap = NULL;
120 // variables for laser control
121 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
122 static int hold_x = -1, hold_y = -1;
124 // variables for pacman control
125 static int pacman_nr = -1;
127 // various game engine delay counters
128 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
129 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
130 static DelayCounter energy_delay = { ENERGY_DELAY };
131 static DelayCounter overload_delay = { 0 };
133 // element mask positions for scanning pixels of MM elements
134 #define MM_MASK_MCDUFFIN_RIGHT 0
135 #define MM_MASK_MCDUFFIN_UP 1
136 #define MM_MASK_MCDUFFIN_LEFT 2
137 #define MM_MASK_MCDUFFIN_DOWN 3
138 #define MM_MASK_GRID_1 4
139 #define MM_MASK_GRID_2 5
140 #define MM_MASK_GRID_3 6
141 #define MM_MASK_GRID_4 7
142 #define MM_MASK_SLOPE_1 8
143 #define MM_MASK_SLOPE_2 9
144 #define MM_MASK_SLOPE_3 10
145 #define MM_MASK_SLOPE_4 11
146 #define MM_MASK_RECTANGLE 12
147 #define MM_MASK_CIRCLE 13
149 #define NUM_MM_MASKS 14
151 // element masks for scanning pixels of MM elements
152 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
408 static int get_element_angle(int element)
410 int element_phase = get_element_phase(element);
412 if (IS_MIRROR_FIXED(element) ||
413 IS_MCDUFFIN(element) ||
415 IS_RECEIVER(element))
416 return 4 * element_phase;
417 else if (IS_DF_SLOPE(element))
418 return 4 + (element_phase % 2) * 8;
420 return element_phase;
423 static int get_opposite_angle(int angle)
425 int opposite_angle = angle + ANG_RAY_180;
427 // make sure "opposite_angle" is in valid interval [0, 15]
428 return (opposite_angle + 16) % 16;
431 static int get_mirrored_angle(int laser_angle, int mirror_angle)
433 int reflected_angle = 16 - laser_angle + mirror_angle;
435 // make sure "reflected_angle" is in valid interval [0, 15]
436 return (reflected_angle + 16) % 16;
439 static void DrawLaserLines(struct XY *points, int num_points, int mode)
441 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
442 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
444 DrawLines(drawto_mm, points, num_points, pixel_drawto);
448 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
453 static boolean CheckLaserPixel(int x, int y)
459 pixel = ReadPixel(laser_bitmap, x, y);
463 return (pixel == WHITE_PIXEL);
466 static void CheckExitMM(void)
468 int exit_element = EL_EMPTY;
472 static int xy[4][2] =
480 for (y = 0; y < lev_fieldy; y++)
482 for (x = 0; x < lev_fieldx; x++)
484 if (Tile[x][y] == EL_EXIT_CLOSED)
486 // initiate opening animation of exit door
487 Tile[x][y] = EL_EXIT_OPENING;
489 exit_element = EL_EXIT_OPEN;
493 else if (IS_RECEIVER(Tile[x][y]))
495 // remove field that blocks receiver
496 int phase = Tile[x][y] - EL_RECEIVER_START;
497 int blocking_x, blocking_y;
499 blocking_x = x + xy[phase][0];
500 blocking_y = y + xy[phase][1];
502 if (IN_LEV_FIELD(blocking_x, blocking_y))
504 Tile[blocking_x][blocking_y] = EL_EMPTY;
506 DrawField_MM(blocking_x, blocking_y);
509 exit_element = EL_RECEIVER;
516 if (exit_element != EL_EMPTY)
517 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
520 static void SetLaserColor(int brightness)
522 int color_min = 0x00;
523 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
524 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
525 int color_down = color_max - color_up;
528 GetPixelFromRGB(window,
529 (game_mm.laser_red ? color_max : color_up),
530 (game_mm.laser_green ? color_down : color_min),
531 (game_mm.laser_blue ? color_down : color_min));
534 static void InitMovDir_MM(int x, int y)
536 int element = Tile[x][y];
537 static int direction[3][4] =
539 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
540 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
541 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
546 case EL_PACMAN_RIGHT:
550 Tile[x][y] = EL_PACMAN;
551 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
559 static void InitField(int x, int y, boolean init_game)
561 int element = Tile[x][y];
566 Tile[x][y] = EL_EMPTY;
571 if (init_game && native_mm_level.auto_count_kettles)
572 game_mm.kettles_still_needed++;
575 case EL_LIGHTBULB_OFF:
576 game_mm.lights_still_needed++;
580 if (IS_MIRROR(element) ||
581 IS_BEAMER_OLD(element) ||
582 IS_BEAMER(element) ||
584 IS_POLAR_CROSS(element) ||
585 IS_DF_MIRROR(element) ||
586 IS_DF_MIRROR_AUTO(element) ||
587 IS_GRID_STEEL_AUTO(element) ||
588 IS_GRID_WOOD_AUTO(element) ||
589 IS_FIBRE_OPTIC(element))
591 if (IS_BEAMER_OLD(element))
593 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
594 element = Tile[x][y];
597 if (!IS_FIBRE_OPTIC(element))
599 static int steps_grid_auto = 0;
601 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
602 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
604 if (IS_GRID_STEEL_AUTO(element) ||
605 IS_GRID_WOOD_AUTO(element))
606 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
608 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
610 game_mm.cycle[game_mm.num_cycle].x = x;
611 game_mm.cycle[game_mm.num_cycle].y = y;
615 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
617 int beamer_nr = BEAMER_NR(element);
618 int nr = laser.beamer[beamer_nr][0].num;
620 laser.beamer[beamer_nr][nr].x = x;
621 laser.beamer[beamer_nr][nr].y = y;
622 laser.beamer[beamer_nr][nr].num = 1;
625 else if (IS_PACMAN(element))
629 else if (IS_MCDUFFIN(element) || IS_LASER(element))
633 laser.start_edge.x = x;
634 laser.start_edge.y = y;
635 laser.start_angle = get_element_angle(element);
638 if (IS_MCDUFFIN(element))
640 game_mm.laser_red = native_mm_level.mm_laser_red;
641 game_mm.laser_green = native_mm_level.mm_laser_green;
642 game_mm.laser_blue = native_mm_level.mm_laser_blue;
646 game_mm.laser_red = native_mm_level.df_laser_red;
647 game_mm.laser_green = native_mm_level.df_laser_green;
648 game_mm.laser_blue = native_mm_level.df_laser_blue;
651 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
658 static void InitCycleElements_RotateSingleStep(void)
662 if (game_mm.num_cycle == 0) // no elements to cycle
665 for (i = 0; i < game_mm.num_cycle; i++)
667 int x = game_mm.cycle[i].x;
668 int y = game_mm.cycle[i].y;
669 int step = SIGN(game_mm.cycle[i].steps);
670 int last_element = Tile[x][y];
671 int next_element = get_rotated_element(last_element, step);
673 if (!game_mm.cycle[i].steps)
676 Tile[x][y] = next_element;
678 game_mm.cycle[i].steps -= step;
682 static void InitLaser(void)
684 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
685 int step = (IS_LASER(start_element) ? 4 : 0);
687 LX = laser.start_edge.x * TILEX;
688 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
691 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
693 LY = laser.start_edge.y * TILEY;
694 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
695 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
699 XS = 2 * Step[laser.start_angle].x;
700 YS = 2 * Step[laser.start_angle].y;
702 laser.current_angle = laser.start_angle;
704 laser.num_damages = 0;
706 laser.num_beamers = 0;
707 laser.beamer_edge[0] = 0;
709 laser.dest_element = EL_EMPTY;
712 AddLaserEdge(LX, LY); // set laser starting edge
717 void InitGameEngine_MM(void)
723 // initialize laser bitmap to current playfield (screen) size
724 ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
725 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
729 // set global game control values
730 game_mm.num_cycle = 0;
731 game_mm.num_pacman = 0;
734 game_mm.energy_left = 0; // later set to "native_mm_level.time"
735 game_mm.kettles_still_needed =
736 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
737 game_mm.lights_still_needed = 0;
738 game_mm.num_keys = 0;
739 game_mm.ball_choice_pos = 0;
741 game_mm.laser_red = FALSE;
742 game_mm.laser_green = FALSE;
743 game_mm.laser_blue = TRUE;
744 game_mm.has_mcduffin = TRUE;
746 game_mm.level_solved = FALSE;
747 game_mm.game_over = FALSE;
748 game_mm.game_over_cause = 0;
749 game_mm.game_over_message = NULL;
751 game_mm.laser_overload_value = 0;
752 game_mm.laser_enabled = FALSE;
754 // set global laser control values (must be set before "InitLaser()")
755 laser.start_edge.x = 0;
756 laser.start_edge.y = 0;
757 laser.start_angle = 0;
759 for (i = 0; i < MAX_NUM_BEAMERS; i++)
760 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
762 laser.overloaded = FALSE;
763 laser.overload_value = 0;
764 laser.fuse_off = FALSE;
765 laser.fuse_x = laser.fuse_y = -1;
767 laser.dest_element = EL_EMPTY;
768 laser.dest_element_last = EL_EMPTY;
769 laser.dest_element_last_x = -1;
770 laser.dest_element_last_y = -1;
784 rotate_delay.count = 0;
785 pacman_delay.count = 0;
786 energy_delay.count = 0;
787 overload_delay.count = 0;
789 ClickElement(-1, -1, -1);
791 for (x = 0; x < lev_fieldx; x++)
793 for (y = 0; y < lev_fieldy; y++)
795 Tile[x][y] = Ur[x][y];
796 Hit[x][y] = Box[x][y] = 0;
798 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
799 Store[x][y] = Store2[x][y] = 0;
802 InitField(x, y, TRUE);
809 void InitGameActions_MM(void)
811 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
812 int cycle_steps_done = 0;
817 for (i = 0; i <= num_init_game_frames; i++)
819 if (i == num_init_game_frames)
820 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
821 else if (setup.sound_loops)
822 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
824 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
826 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
828 UpdateAndDisplayGameControlValues();
830 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
832 InitCycleElements_RotateSingleStep();
837 AdvanceFrameCounter();
840 if (PendingEscapeKeyEvent())
844 if (setup.quick_doors)
854 if (setup.quick_doors)
860 if (game_mm.kettles_still_needed == 0)
863 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
864 SetTileCursorActive(TRUE);
866 // restart all delay counters after initially cycling game elements
867 ResetFrameCounter(&rotate_delay);
868 ResetFrameCounter(&pacman_delay);
869 ResetFrameCounter(&energy_delay);
870 ResetFrameCounter(&overload_delay);
873 static void FadeOutLaser(void)
877 for (i = 15; i >= 0; i--)
879 SetLaserColor(0x11 * i);
881 DrawLaser(0, DL_LASER_ENABLED);
884 Delay_WithScreenUpdates(50);
887 DrawLaser(0, DL_LASER_DISABLED);
889 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
892 static void GameOver_MM(int game_over_cause)
894 game_mm.game_over = TRUE;
895 game_mm.game_over_cause = game_over_cause;
896 game_mm.game_over_message = (game_mm.has_mcduffin ?
897 (game_over_cause == GAME_OVER_BOMB ?
898 "Bomb killed Mc Duffin!" :
899 game_over_cause == GAME_OVER_NO_ENERGY ?
900 "Out of magic energy!" :
901 game_over_cause == GAME_OVER_OVERLOADED ?
902 "Magic spell hit Mc Duffin!" :
904 (game_over_cause == GAME_OVER_BOMB ?
905 "Bomb destroyed laser cannon!" :
906 game_over_cause == GAME_OVER_NO_ENERGY ?
907 "Out of laser energy!" :
908 game_over_cause == GAME_OVER_OVERLOADED ?
909 "Laser beam hit laser cannon!" :
912 SetTileCursorActive(FALSE);
915 static void AddLaserEdge(int lx, int ly)
917 int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
918 int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
920 // check if laser is still inside visible playfield area (or inside level)
921 if (cSX + lx < REAL_SX || cSX + lx >= REAL_SX + full_sxsize ||
922 cSY + ly < REAL_SY || cSY + ly >= REAL_SY + full_sysize)
924 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
929 laser.edge[laser.num_edges].x = cSX2 + lx;
930 laser.edge[laser.num_edges].y = cSY2 + ly;
936 static void AddDamagedField(int ex, int ey)
938 // prevent adding the same field position again
939 if (laser.num_damages > 0 &&
940 laser.damage[laser.num_damages - 1].x == ex &&
941 laser.damage[laser.num_damages - 1].y == ey &&
942 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
945 laser.damage[laser.num_damages].is_mirror = FALSE;
946 laser.damage[laser.num_damages].angle = laser.current_angle;
947 laser.damage[laser.num_damages].edge = laser.num_edges;
948 laser.damage[laser.num_damages].x = ex;
949 laser.damage[laser.num_damages].y = ey;
953 static boolean StepBehind(void)
959 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
960 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
962 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
968 static int getMaskFromElement(int element)
970 if (IS_MCDUFFIN(element))
971 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
972 else if (IS_GRID(element))
973 return MM_MASK_GRID_1 + get_element_phase(element);
974 else if (IS_DF_GRID(element))
975 return MM_MASK_RECTANGLE;
976 else if (IS_DF_SLOPE(element))
977 return MM_MASK_SLOPE_1 + get_element_phase(element);
978 else if (IS_RECTANGLE(element))
979 return MM_MASK_RECTANGLE;
981 return MM_MASK_CIRCLE;
984 static int getLevelFromLaserX(int x)
986 return x / TILEX - (x < 0 ? 1 : 0); // correct negative values
989 static int getLevelFromLaserY(int y)
991 return y / TILEY - (y < 0 ? 1 : 0); // correct negative values
994 static int ScanPixel(void)
999 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
1000 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
1003 // follow laser beam until it hits something (at least the screen border)
1004 while (hit_mask == HIT_MASK_NO_HIT)
1010 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
1011 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
1013 Debug("game:mm:ScanPixel", "touched screen border!");
1015 return HIT_MASK_ALL;
1019 // check if laser scan has crossed element boundaries (not just mini tiles)
1020 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1021 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1023 if (cross_x && cross_y)
1025 int elx1 = (LX - XS) / TILEX;
1026 int ely1 = (LY + YS) / TILEY;
1027 int elx2 = (LX + XS) / TILEX;
1028 int ely2 = (LY - YS) / TILEY;
1030 // add element corners left and right from the laser beam to damage list
1032 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
1033 AddDamagedField(elx1, ely1);
1035 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
1036 AddDamagedField(elx2, ely2);
1039 for (i = 0; i < 4; i++)
1041 int px = LX + (i % 2) * 2;
1042 int py = LY + (i / 2) * 2;
1043 int dx = px % TILEX;
1044 int dy = py % TILEY;
1045 int lx = getLevelFromLaserX(px);
1046 int ly = getLevelFromLaserY(py);
1049 if (IN_LEV_FIELD(lx, ly))
1051 int element = Tile[lx][ly];
1053 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
1057 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
1059 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1061 pixel = ((element & (1 << pos)) ? 1 : 0);
1065 int pos = getMaskFromElement(element);
1067 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
1072 // check if laser is still inside visible playfield area
1073 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
1074 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
1077 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
1078 hit_mask |= (1 << i);
1081 if (hit_mask == HIT_MASK_NO_HIT)
1083 // hit nothing -- go on with another step
1092 static void DeactivateLaserTargetElement(void)
1094 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1095 laser.dest_element_last == EL_MINE_ACTIVE ||
1096 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1097 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1099 int x = laser.dest_element_last_x;
1100 int y = laser.dest_element_last_y;
1101 int element = laser.dest_element_last;
1103 if (Tile[x][y] == element)
1104 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1105 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1107 if (Tile[x][y] == EL_GRAY_BALL)
1110 laser.dest_element_last = EL_EMPTY;
1111 laser.dest_element_last_x = -1;
1112 laser.dest_element_last_y = -1;
1116 static void ScanLaser(void)
1118 int element = EL_EMPTY;
1119 int last_element = EL_EMPTY;
1120 int end = 0, rf = laser.num_edges;
1122 // do not scan laser again after the game was lost for whatever reason
1123 if (game_mm.game_over)
1126 // do not scan laser if fuse is off
1130 DeactivateLaserTargetElement();
1132 laser.overloaded = FALSE;
1133 laser.stops_inside_element = FALSE;
1135 DrawLaser(0, DL_LASER_ENABLED);
1138 Debug("game:mm:ScanLaser",
1139 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1147 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1150 laser.overloaded = TRUE;
1155 hit_mask = ScanPixel();
1158 Debug("game:mm:ScanLaser",
1159 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1163 // check if laser scan has hit two diagonally adjacent element corners
1164 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1165 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1167 // check if laser scan has crossed element boundaries (not just mini tiles)
1168 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1169 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1171 if (cross_x || cross_y)
1173 // hit something at next tile -- check out what it was
1174 ELX = getLevelFromLaserX(LX + XS);
1175 ELY = getLevelFromLaserY(LY + YS);
1179 // hit something at same tile -- check out what it was
1180 ELX = getLevelFromLaserX(LX);
1181 ELY = getLevelFromLaserY(LY);
1185 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1186 hit_mask, LX, LY, ELX, ELY);
1189 if (!IN_LEV_FIELD(ELX, ELY))
1191 // laser next step position
1192 int x = cSX + LX + XS;
1193 int y = cSY + LY + YS;
1195 // check if next step of laser is still inside visible playfield area
1196 if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
1197 y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
1199 // go on with another step
1207 laser.dest_element = element;
1212 boolean diagonally_adjacent_hit = FALSE;
1214 // handle special case of laser hitting two diagonally adjacent elements
1215 // (with or without a third corner element behind these two elements)
1216 if ((diag_1 || diag_2) && cross_x && cross_y)
1218 diagonally_adjacent_hit = TRUE;
1220 // compare the two diagonally adjacent elements
1222 int yoffset = 2 * (diag_1 ? -1 : +1);
1223 int elx1 = (LX - xoffset) / TILEX;
1224 int ely1 = (LY + yoffset) / TILEY;
1225 int elx2 = (LX + xoffset) / TILEX;
1226 int ely2 = (LY - yoffset) / TILEY;
1227 int e1 = Tile[elx1][ely1];
1228 int e2 = Tile[elx2][ely2];
1229 boolean use_element_1 = FALSE;
1231 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1233 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1234 use_element_1 = (RND(2) ? TRUE : FALSE);
1235 else if (IS_WALL_ICE(e1))
1236 use_element_1 = TRUE;
1238 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1240 // if both tiles match, we can just select the first one
1241 if (IS_WALL_AMOEBA(e1))
1242 use_element_1 = TRUE;
1244 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1246 // if both tiles match, we can just select the first one
1247 if (IS_ABSORBING_BLOCK(e1))
1248 use_element_1 = TRUE;
1251 ELX = (use_element_1 ? elx1 : elx2);
1252 ELY = (use_element_1 ? ely1 : ely2);
1256 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1257 hit_mask, LX, LY, ELX, ELY);
1260 last_element = element;
1262 element = Tile[ELX][ELY];
1263 laser.dest_element = element;
1266 Debug("game:mm:ScanLaser",
1267 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1270 LX % TILEX, LY % TILEY,
1275 if (!IN_LEV_FIELD(ELX, ELY))
1276 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1280 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1281 if (element == EL_EMPTY &&
1282 IS_GRID_STEEL(last_element) &&
1283 laser.current_angle % 4) // angle is not 90°
1284 element = last_element;
1286 if (element == EL_EMPTY)
1288 if (!HitOnlyAnEdge(hit_mask))
1291 else if (element == EL_FUSE_ON)
1293 if (HitPolarizer(element, hit_mask))
1296 else if (IS_GRID(element) || IS_DF_GRID(element))
1298 if (HitPolarizer(element, hit_mask))
1301 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1302 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1304 if (HitBlock(element, hit_mask))
1311 else if (IS_MCDUFFIN(element))
1313 if (HitLaserSource(element, hit_mask))
1316 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1317 IS_RECEIVER(element))
1319 if (HitLaserDestination(element, hit_mask))
1322 else if (IS_WALL(element))
1324 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1326 if (HitReflectingWalls(element, hit_mask))
1331 if (HitAbsorbingWalls(element, hit_mask))
1335 else if (IS_DF_SLOPE(element))
1337 if (diagonally_adjacent_hit)
1339 laser.overloaded = TRUE;
1344 if (hit_mask == HIT_MASK_LEFT ||
1345 hit_mask == HIT_MASK_RIGHT ||
1346 hit_mask == HIT_MASK_TOP ||
1347 hit_mask == HIT_MASK_BOTTOM)
1349 if (HitReflectingWalls(element, hit_mask))
1354 if (HitElement(element, hit_mask))
1360 if (HitElement(element, hit_mask))
1365 DrawLaser(rf - 1, DL_LASER_ENABLED);
1366 rf = laser.num_edges;
1368 if (!IS_DF_WALL_STEEL(element))
1370 // only used for scanning DF steel walls; reset for all other elements
1378 if (laser.dest_element != Tile[ELX][ELY])
1380 Debug("game:mm:ScanLaser",
1381 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1382 laser.dest_element, Tile[ELX][ELY]);
1386 if (!end && !laser.stops_inside_element && !StepBehind())
1389 Debug("game:mm:ScanLaser", "Go one step back");
1395 AddLaserEdge(LX, LY);
1399 DrawLaser(rf - 1, DL_LASER_ENABLED);
1401 Ct = CT = FrameCounter;
1404 if (!IN_LEV_FIELD(ELX, ELY))
1405 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1409 static void ScanLaser_FromLastMirror(void)
1411 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1414 for (i = start_pos; i >= 0; i--)
1415 if (laser.damage[i].is_mirror)
1418 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1420 DrawLaser(start_edge, DL_LASER_DISABLED);
1425 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1431 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1432 start_edge, num_edges, mode);
1437 Warn("DrawLaserExt: start_edge < 0");
1444 Warn("DrawLaserExt: num_edges < 0");
1450 if (mode == DL_LASER_DISABLED)
1452 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1456 // now draw the laser to the backbuffer and (if enabled) to the screen
1457 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1459 redraw_mask |= REDRAW_FIELD;
1461 if (mode == DL_LASER_ENABLED)
1464 // after the laser was deleted, the "damaged" graphics must be restored
1465 if (laser.num_damages)
1467 int damage_start = 0;
1470 // determine the starting edge, from which graphics need to be restored
1473 for (i = 0; i < laser.num_damages; i++)
1475 if (laser.damage[i].edge == start_edge + 1)
1484 // restore graphics from this starting edge to the end of damage list
1485 for (i = damage_start; i < laser.num_damages; i++)
1487 int lx = laser.damage[i].x;
1488 int ly = laser.damage[i].y;
1489 int element = Tile[lx][ly];
1491 if (Hit[lx][ly] == laser.damage[i].edge)
1492 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1495 if (Box[lx][ly] == laser.damage[i].edge)
1498 if (IS_DRAWABLE(element))
1499 DrawField_MM(lx, ly);
1502 elx = laser.damage[damage_start].x;
1503 ely = laser.damage[damage_start].y;
1504 element = Tile[elx][ely];
1507 if (IS_BEAMER(element))
1511 for (i = 0; i < laser.num_beamers; i++)
1512 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1514 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1515 mode, elx, ely, Hit[elx][ely], start_edge);
1516 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1517 get_element_angle(element), laser.damage[damage_start].angle);
1521 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1522 laser.num_beamers > 0 &&
1523 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1525 // element is outgoing beamer
1526 laser.num_damages = damage_start + 1;
1528 if (IS_BEAMER(element))
1529 laser.current_angle = get_element_angle(element);
1533 // element is incoming beamer or other element
1534 laser.num_damages = damage_start;
1535 laser.current_angle = laser.damage[laser.num_damages].angle;
1540 // no damages but McDuffin himself (who needs to be redrawn anyway)
1542 elx = laser.start_edge.x;
1543 ely = laser.start_edge.y;
1544 element = Tile[elx][ely];
1547 laser.num_edges = start_edge + 1;
1548 if (start_edge == 0)
1549 laser.current_angle = laser.start_angle;
1551 LX = laser.edge[start_edge].x - cSX2;
1552 LY = laser.edge[start_edge].y - cSY2;
1553 XS = 2 * Step[laser.current_angle].x;
1554 YS = 2 * Step[laser.current_angle].y;
1557 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1563 if (IS_BEAMER(element) ||
1564 IS_FIBRE_OPTIC(element) ||
1565 IS_PACMAN(element) ||
1566 IS_POLAR(element) ||
1567 IS_POLAR_CROSS(element) ||
1568 element == EL_FUSE_ON)
1573 Debug("game:mm:DrawLaserExt", "element == %d", element);
1576 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1577 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1581 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1582 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1583 (laser.num_beamers == 0 ||
1584 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1586 // element is incoming beamer or other element
1587 step_size = -step_size;
1592 if (IS_BEAMER(element))
1593 Debug("game:mm:DrawLaserExt",
1594 "start_edge == %d, laser.beamer_edge == %d",
1595 start_edge, laser.beamer_edge);
1598 LX += step_size * XS;
1599 LY += step_size * YS;
1601 else if (element != EL_EMPTY)
1610 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1615 void DrawLaser(int start_edge, int mode)
1617 // do not draw laser if fuse is off
1618 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1621 if (mode == DL_LASER_DISABLED)
1622 DeactivateLaserTargetElement();
1624 if (laser.num_edges - start_edge < 0)
1626 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1631 // check if laser is interrupted by beamer element
1632 if (laser.num_beamers > 0 &&
1633 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1635 if (mode == DL_LASER_ENABLED)
1638 int tmp_start_edge = start_edge;
1640 // draw laser segments forward from the start to the last beamer
1641 for (i = 0; i < laser.num_beamers; i++)
1643 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1645 if (tmp_num_edges <= 0)
1649 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1650 i, laser.beamer_edge[i], tmp_start_edge);
1653 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1655 tmp_start_edge = laser.beamer_edge[i];
1658 // draw last segment from last beamer to the end
1659 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1665 int last_num_edges = laser.num_edges;
1666 int num_beamers = laser.num_beamers;
1668 // delete laser segments backward from the end to the first beamer
1669 for (i = num_beamers - 1; i >= 0; i--)
1671 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1673 if (laser.beamer_edge[i] - start_edge <= 0)
1676 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1678 last_num_edges = laser.beamer_edge[i];
1679 laser.num_beamers--;
1683 if (last_num_edges - start_edge <= 0)
1684 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1685 last_num_edges, start_edge);
1688 // special case when rotating first beamer: delete laser edge on beamer
1689 // (but do not start scanning on previous edge to prevent mirror sound)
1690 if (last_num_edges - start_edge == 1 && start_edge > 0)
1691 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1693 // delete first segment from start to the first beamer
1694 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1699 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1702 game_mm.laser_enabled = mode;
1705 void DrawLaser_MM(void)
1707 DrawLaser(0, game_mm.laser_enabled);
1710 static boolean HitElement(int element, int hit_mask)
1712 if (IS_DF_SLOPE(element))
1714 // check if laser scan has crossed element boundaries (not just mini tiles)
1715 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1716 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1718 // check if an edge was hit while crossing element borders
1719 if (cross_x && cross_y && get_number_of_bits(hit_mask) == 1)
1721 // check both sides of potentially diagonal side of slope
1722 int dx1 = (LX + XS) % TILEX;
1723 int dy1 = (LY + YS) % TILEY;
1724 int dx2 = (LX + XS + 2) % TILEX;
1725 int dy2 = (LY + YS + 2) % TILEY;
1726 int pos = getMaskFromElement(element);
1728 // check if we are entering empty space area after hitting edge
1729 if (mm_masks[pos][dx1 / 2][dy1 / 2] != 'X' &&
1730 mm_masks[pos][dx2 / 2][dy2 / 2] != 'X')
1732 // we already know that we hit an edge, but use this function to go on
1733 if (HitOnlyAnEdge(hit_mask))
1738 int mirrored_angle = get_mirrored_angle(laser.current_angle,
1739 get_element_angle(element));
1740 int opposite_angle = get_opposite_angle(laser.current_angle);
1742 // check if laser is reflected by slope by 180°
1743 if (mirrored_angle == opposite_angle)
1748 AddDamagedField(LX / TILEX, LY / TILEY);
1750 laser.overloaded = TRUE;
1757 if (HitOnlyAnEdge(hit_mask))
1761 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1762 element = MovingOrBlocked2Element_MM(ELX, ELY);
1765 Debug("game:mm:HitElement", "(1): element == %d", element);
1769 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1770 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1773 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1777 AddDamagedField(ELX, ELY);
1779 boolean through_center = ((ELX * TILEX + 14 - LX) * YS ==
1780 (ELY * TILEY + 14 - LY) * XS);
1782 // this is more precise: check if laser would go through the center
1783 if (!IS_DF_SLOPE(element) && !through_center)
1787 // prevent cutting through laser emitter with laser beam
1788 if (IS_LASER(element))
1791 // skip the whole element before continuing the scan
1799 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1801 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1803 /* skipping scan positions to the right and down skips one scan
1804 position too much, because this is only the top left scan position
1805 of totally four scan positions (plus one to the right, one to the
1806 bottom and one to the bottom right) */
1807 /* ... but only roll back scan position if more than one step done */
1817 Debug("game:mm:HitElement", "(2): element == %d", element);
1820 if (LX + 5 * XS < 0 ||
1830 Debug("game:mm:HitElement", "(3): element == %d", element);
1833 if (IS_POLAR(element) &&
1834 ((element - EL_POLAR_START) % 2 ||
1835 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1837 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1839 laser.num_damages--;
1844 if (IS_POLAR_CROSS(element) &&
1845 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1847 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1849 laser.num_damages--;
1854 if (IS_DF_SLOPE(element) && !through_center)
1858 if (hit_mask == HIT_MASK_ALL)
1860 // laser already inside slope -- go back half step
1867 AddLaserEdge(LX, LY);
1869 LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0);
1870 LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0);
1872 else if (!IS_BEAMER(element) &&
1873 !IS_FIBRE_OPTIC(element) &&
1874 !IS_GRID_WOOD(element) &&
1875 element != EL_FUEL_EMPTY)
1878 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1879 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1881 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1884 LX = ELX * TILEX + 14;
1885 LY = ELY * TILEY + 14;
1887 AddLaserEdge(LX, LY);
1890 if (IS_MIRROR(element) ||
1891 IS_MIRROR_FIXED(element) ||
1892 IS_POLAR(element) ||
1893 IS_POLAR_CROSS(element) ||
1894 IS_DF_MIRROR(element) ||
1895 IS_DF_MIRROR_AUTO(element) ||
1896 IS_DF_MIRROR_FIXED(element) ||
1897 IS_DF_SLOPE(element) ||
1898 element == EL_PRISM ||
1899 element == EL_REFRACTOR)
1901 int current_angle = laser.current_angle;
1904 laser.num_damages--;
1906 AddDamagedField(ELX, ELY);
1908 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1911 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1913 if (IS_MIRROR(element) ||
1914 IS_MIRROR_FIXED(element) ||
1915 IS_DF_MIRROR(element) ||
1916 IS_DF_MIRROR_AUTO(element) ||
1917 IS_DF_MIRROR_FIXED(element) ||
1918 IS_DF_SLOPE(element))
1919 laser.current_angle = get_mirrored_angle(laser.current_angle,
1920 get_element_angle(element));
1922 if (element == EL_PRISM || element == EL_REFRACTOR)
1923 laser.current_angle = RND(16);
1925 XS = 2 * Step[laser.current_angle].x;
1926 YS = 2 * Step[laser.current_angle].y;
1930 // start from center position for all game elements but slope
1931 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1936 LX += step_size * XS;
1937 LY += step_size * YS;
1941 // advance laser position until reaching the next tile (slopes)
1942 while (LX / TILEX == ELX && (LX + 2) / TILEX == ELX &&
1943 LY / TILEY == ELY && (LY + 2) / TILEY == ELY)
1950 // draw sparkles on mirror
1951 if ((IS_MIRROR(element) ||
1952 IS_MIRROR_FIXED(element) ||
1953 element == EL_PRISM) &&
1954 current_angle != laser.current_angle)
1956 MovDelay[ELX][ELY] = 11; // start animation
1959 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1960 current_angle != laser.current_angle)
1961 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1964 (get_opposite_angle(laser.current_angle) ==
1965 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1967 if (IS_DF_SLOPE(element))
1969 // handle special cases for slope element
1971 if (IS_45_ANGLE(laser.current_angle))
1975 elx = getLevelFromLaserX(LX);
1976 ely = getLevelFromLaserY(LY);
1978 if (IN_LEV_FIELD(elx, ely))
1980 int element_next = Tile[elx][ely];
1982 // check if slope is followed by slope with opposite orientation
1983 if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2)
1984 laser.overloaded = TRUE;
1987 int nr = element - EL_DF_SLOPE_START;
1988 int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) :
1989 nr == 1 ? (XS > 0 ? TILEX : 1) :
1990 nr == 2 ? (XS > 0 ? TILEX : 1) :
1991 nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0);
1992 int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) :
1993 nr == 1 ? (YS > 0 ? TILEY - 1 : -1) :
1994 nr == 2 ? (YS > 0 ? TILEY : 0) :
1995 nr == 3 ? (YS > 0 ? TILEY : 0) : 0);
1997 int px = ELX * TILEX + dx;
1998 int py = ELY * TILEY + dy;
2003 elx = getLevelFromLaserX(px);
2004 ely = getLevelFromLaserY(py);
2006 if (IN_LEV_FIELD(elx, ely))
2008 int element_side = Tile[elx][ely];
2010 // check if end of slope is blocked by other element
2011 if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side))
2013 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
2015 if (element & (1 << pos))
2016 laser.overloaded = TRUE;
2020 int pos = getMaskFromElement(element_side);
2022 if (mm_masks[pos][dx / 2][dy / 2] == 'X')
2023 laser.overloaded = TRUE;
2029 return (laser.overloaded ? TRUE : FALSE);
2032 if (element == EL_FUEL_FULL)
2034 laser.stops_inside_element = TRUE;
2039 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
2041 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2043 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
2044 element == EL_MINE ? EL_MINE_ACTIVE :
2045 EL_GRAY_BALL_ACTIVE);
2047 GfxFrame[ELX][ELY] = 0; // restart animation
2049 laser.dest_element_last = Tile[ELX][ELY];
2050 laser.dest_element_last_x = ELX;
2051 laser.dest_element_last_y = ELY;
2053 if (element == EL_MINE)
2054 laser.overloaded = TRUE;
2057 if (element == EL_KETTLE ||
2058 element == EL_CELL ||
2059 element == EL_KEY ||
2060 element == EL_LIGHTBALL ||
2061 element == EL_PACMAN ||
2062 IS_PACMAN(element) ||
2063 IS_ENVELOPE(element))
2065 if (!IS_PACMAN(element) &&
2066 !IS_ENVELOPE(element))
2069 if (element == EL_PACMAN)
2072 if (element == EL_KETTLE || element == EL_CELL)
2074 if (game_mm.kettles_still_needed > 0)
2075 game_mm.kettles_still_needed--;
2077 game.snapshot.collected_item = TRUE;
2079 if (game_mm.kettles_still_needed == 0)
2083 DrawLaser(0, DL_LASER_ENABLED);
2086 else if (element == EL_KEY)
2090 else if (IS_PACMAN(element))
2092 DeletePacMan(ELX, ELY);
2094 else if (IS_ENVELOPE(element))
2096 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
2099 RaiseScoreElement_MM(element);
2104 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
2106 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2108 DrawLaser(0, DL_LASER_ENABLED);
2110 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
2112 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
2113 game_mm.lights_still_needed--;
2117 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
2118 game_mm.lights_still_needed++;
2121 DrawField_MM(ELX, ELY);
2122 DrawLaser(0, DL_LASER_ENABLED);
2127 laser.stops_inside_element = TRUE;
2133 Debug("game:mm:HitElement", "(4): element == %d", element);
2136 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
2137 laser.num_beamers < MAX_NUM_BEAMERS &&
2138 laser.beamer[BEAMER_NR(element)][1].num)
2140 int beamer_angle = get_element_angle(element);
2141 int beamer_nr = BEAMER_NR(element);
2145 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
2148 laser.num_damages--;
2150 if (IS_FIBRE_OPTIC(element) ||
2151 laser.current_angle == get_opposite_angle(beamer_angle))
2155 LX = ELX * TILEX + 14;
2156 LY = ELY * TILEY + 14;
2158 AddLaserEdge(LX, LY);
2159 AddDamagedField(ELX, ELY);
2161 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2164 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2166 pos = (ELX == laser.beamer[beamer_nr][0].x &&
2167 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
2168 ELX = laser.beamer[beamer_nr][pos].x;
2169 ELY = laser.beamer[beamer_nr][pos].y;
2170 LX = ELX * TILEX + 14;
2171 LY = ELY * TILEY + 14;
2173 if (IS_BEAMER(element))
2175 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
2176 XS = 2 * Step[laser.current_angle].x;
2177 YS = 2 * Step[laser.current_angle].y;
2180 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
2182 AddLaserEdge(LX, LY);
2183 AddDamagedField(ELX, ELY);
2185 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2188 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2190 if (laser.current_angle == (laser.current_angle >> 1) << 1)
2195 LX += step_size * XS;
2196 LY += step_size * YS;
2198 laser.num_beamers++;
2207 static boolean HitOnlyAnEdge(int hit_mask)
2209 // check if the laser hit only the edge of an element and, if so, go on
2212 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
2216 if ((hit_mask == HIT_MASK_TOPLEFT ||
2217 hit_mask == HIT_MASK_TOPRIGHT ||
2218 hit_mask == HIT_MASK_BOTTOMLEFT ||
2219 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
2220 laser.current_angle % 4) // angle is not 90°
2224 if (hit_mask == HIT_MASK_TOPLEFT)
2229 else if (hit_mask == HIT_MASK_TOPRIGHT)
2234 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
2239 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
2245 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
2251 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
2258 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
2264 static boolean HitPolarizer(int element, int hit_mask)
2266 if (HitOnlyAnEdge(hit_mask))
2269 if (IS_DF_GRID(element))
2271 int grid_angle = get_element_angle(element);
2274 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2275 grid_angle, laser.current_angle);
2278 AddLaserEdge(LX, LY);
2279 AddDamagedField(ELX, ELY);
2282 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2284 if (laser.current_angle == grid_angle ||
2285 laser.current_angle == get_opposite_angle(grid_angle))
2287 // skip the whole element before continuing the scan
2293 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2295 if (LX/TILEX > ELX || LY/TILEY > ELY)
2297 /* skipping scan positions to the right and down skips one scan
2298 position too much, because this is only the top left scan position
2299 of totally four scan positions (plus one to the right, one to the
2300 bottom and one to the bottom right) */
2306 AddLaserEdge(LX, LY);
2312 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2314 LX / TILEX, LY / TILEY,
2315 LX % TILEX, LY % TILEY);
2320 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2322 return HitReflectingWalls(element, hit_mask);
2326 return HitAbsorbingWalls(element, hit_mask);
2329 else if (IS_GRID_STEEL(element))
2331 // may be required if graphics for steel grid redefined
2332 AddDamagedField(ELX, ELY);
2334 return HitReflectingWalls(element, hit_mask);
2336 else // IS_GRID_WOOD
2338 // may be required if graphics for wooden grid redefined
2339 AddDamagedField(ELX, ELY);
2341 return HitAbsorbingWalls(element, hit_mask);
2347 static boolean HitBlock(int element, int hit_mask)
2349 boolean check = FALSE;
2351 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2352 game_mm.num_keys == 0)
2355 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2358 int ex = ELX * TILEX + 14;
2359 int ey = ELY * TILEY + 14;
2363 for (i = 1; i < 32; i++)
2368 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2373 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2374 return HitAbsorbingWalls(element, hit_mask);
2378 AddLaserEdge(LX - XS, LY - YS);
2379 AddDamagedField(ELX, ELY);
2382 Box[ELX][ELY] = laser.num_edges;
2384 return HitReflectingWalls(element, hit_mask);
2387 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2389 int xs = XS / 2, ys = YS / 2;
2391 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2392 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2394 laser.overloaded = (element == EL_GATE_STONE);
2399 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2400 (hit_mask == HIT_MASK_TOP ||
2401 hit_mask == HIT_MASK_LEFT ||
2402 hit_mask == HIT_MASK_RIGHT ||
2403 hit_mask == HIT_MASK_BOTTOM))
2404 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2405 hit_mask == HIT_MASK_BOTTOM),
2406 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2407 hit_mask == HIT_MASK_RIGHT));
2408 AddLaserEdge(LX, LY);
2414 if (element == EL_GATE_STONE && Box[ELX][ELY])
2416 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2428 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2430 int xs = XS / 2, ys = YS / 2;
2432 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2433 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2435 laser.overloaded = (element == EL_BLOCK_STONE);
2440 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2441 (hit_mask == HIT_MASK_TOP ||
2442 hit_mask == HIT_MASK_LEFT ||
2443 hit_mask == HIT_MASK_RIGHT ||
2444 hit_mask == HIT_MASK_BOTTOM))
2445 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2446 hit_mask == HIT_MASK_BOTTOM),
2447 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2448 hit_mask == HIT_MASK_RIGHT));
2449 AddDamagedField(ELX, ELY);
2451 LX = ELX * TILEX + 14;
2452 LY = ELY * TILEY + 14;
2454 AddLaserEdge(LX, LY);
2456 laser.stops_inside_element = TRUE;
2464 static boolean HitLaserSource(int element, int hit_mask)
2466 if (HitOnlyAnEdge(hit_mask))
2469 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2471 laser.overloaded = TRUE;
2476 static boolean HitLaserDestination(int element, int hit_mask)
2478 if (HitOnlyAnEdge(hit_mask))
2481 if (element != EL_EXIT_OPEN &&
2482 !(IS_RECEIVER(element) &&
2483 game_mm.kettles_still_needed == 0 &&
2484 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2486 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2491 if (IS_RECEIVER(element) ||
2492 (IS_22_5_ANGLE(laser.current_angle) &&
2493 (ELX != (LX + 6 * XS) / TILEX ||
2494 ELY != (LY + 6 * YS) / TILEY ||
2503 LX = ELX * TILEX + 14;
2504 LY = ELY * TILEY + 14;
2506 laser.stops_inside_element = TRUE;
2509 AddLaserEdge(LX, LY);
2510 AddDamagedField(ELX, ELY);
2512 if (game_mm.lights_still_needed == 0)
2514 game_mm.level_solved = TRUE;
2516 SetTileCursorActive(FALSE);
2522 static boolean HitReflectingWalls(int element, int hit_mask)
2524 // check if laser hits side of a wall with an angle that is not 90°
2525 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2526 hit_mask == HIT_MASK_LEFT ||
2527 hit_mask == HIT_MASK_RIGHT ||
2528 hit_mask == HIT_MASK_BOTTOM))
2530 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2535 if (!IS_DF_GRID(element))
2536 AddLaserEdge(LX, LY);
2538 // check if laser hits wall with an angle of 45°
2539 if (!IS_22_5_ANGLE(laser.current_angle))
2541 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2544 laser.current_angle = get_mirrored_angle(laser.current_angle,
2547 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2550 laser.current_angle = get_mirrored_angle(laser.current_angle,
2554 AddLaserEdge(LX, LY);
2556 XS = 2 * Step[laser.current_angle].x;
2557 YS = 2 * Step[laser.current_angle].y;
2561 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2563 laser.current_angle = get_mirrored_angle(laser.current_angle,
2568 if (!IS_DF_GRID(element))
2569 AddLaserEdge(LX, LY);
2574 if (!IS_DF_GRID(element))
2575 AddLaserEdge(LX, LY + YS / 2);
2578 if (!IS_DF_GRID(element))
2579 AddLaserEdge(LX, LY);
2582 YS = 2 * Step[laser.current_angle].y;
2586 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2588 laser.current_angle = get_mirrored_angle(laser.current_angle,
2593 if (!IS_DF_GRID(element))
2594 AddLaserEdge(LX, LY);
2599 if (!IS_DF_GRID(element))
2600 AddLaserEdge(LX + XS / 2, LY);
2603 if (!IS_DF_GRID(element))
2604 AddLaserEdge(LX, LY);
2607 XS = 2 * Step[laser.current_angle].x;
2613 // reflection at the edge of reflecting DF style wall
2614 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2616 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2617 hit_mask == HIT_MASK_TOPRIGHT) ||
2618 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2619 hit_mask == HIT_MASK_TOPLEFT) ||
2620 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2621 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2622 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2623 hit_mask == HIT_MASK_BOTTOMRIGHT))
2626 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2627 ANG_MIRROR_135 : ANG_MIRROR_45);
2629 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2631 AddDamagedField(ELX, ELY);
2632 AddLaserEdge(LX, LY);
2634 laser.current_angle = get_mirrored_angle(laser.current_angle,
2642 AddLaserEdge(LX, LY);
2648 // reflection inside an edge of reflecting DF style wall
2649 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2651 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2652 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2653 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2654 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2655 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2656 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2657 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2658 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2661 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2662 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2663 ANG_MIRROR_135 : ANG_MIRROR_45);
2665 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2668 AddDamagedField(ELX, ELY);
2671 AddLaserEdge(LX - XS, LY - YS);
2672 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2673 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2675 laser.current_angle = get_mirrored_angle(laser.current_angle,
2683 AddLaserEdge(LX, LY);
2689 // check if laser hits DF style wall with an angle of 90°
2690 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2692 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2693 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2694 (IS_VERT_ANGLE(laser.current_angle) &&
2695 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2697 // laser at last step touched nothing or the same side of the wall
2698 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2700 AddDamagedField(ELX, ELY);
2707 last_hit_mask = hit_mask;
2714 if (!HitOnlyAnEdge(hit_mask))
2716 laser.overloaded = TRUE;
2724 static boolean HitAbsorbingWalls(int element, int hit_mask)
2726 if (HitOnlyAnEdge(hit_mask))
2730 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2732 AddLaserEdge(LX - XS, LY - YS);
2739 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2741 AddLaserEdge(LX - XS, LY - YS);
2747 if (IS_WALL_WOOD(element) ||
2748 IS_DF_WALL_WOOD(element) ||
2749 IS_GRID_WOOD(element) ||
2750 IS_GRID_WOOD_FIXED(element) ||
2751 IS_GRID_WOOD_AUTO(element) ||
2752 element == EL_FUSE_ON ||
2753 element == EL_BLOCK_WOOD ||
2754 element == EL_GATE_WOOD)
2756 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2761 if (IS_WALL_ICE(element))
2767 // check if laser hit adjacent edges of two diagonal tiles
2768 if (ELX != lx / TILEX)
2770 if (ELY != ly / TILEY)
2773 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2774 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2776 // check if laser hits wall with an angle of 90°
2777 if (IS_90_ANGLE(laser.current_angle))
2778 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2780 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2784 for (i = 0; i < 4; i++)
2786 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2787 mask = 15 - (8 >> i);
2788 else if (ABS(XS) == 4 &&
2790 (XS > 0) == (i % 2) &&
2791 (YS < 0) == (i / 2))
2792 mask = 3 + (i / 2) * 9;
2793 else if (ABS(YS) == 4 &&
2795 (XS < 0) == (i % 2) &&
2796 (YS > 0) == (i / 2))
2797 mask = 5 + (i % 2) * 5;
2801 laser.wall_mask = mask;
2803 else if (IS_WALL_AMOEBA(element))
2805 int elx = (LX - 2 * XS) / TILEX;
2806 int ely = (LY - 2 * YS) / TILEY;
2807 int element2 = Tile[elx][ely];
2810 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2812 laser.dest_element = EL_EMPTY;
2820 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2821 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2823 if (IS_90_ANGLE(laser.current_angle))
2824 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2826 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2828 laser.wall_mask = mask;
2834 static void OpenExit(int x, int y)
2838 if (!MovDelay[x][y]) // next animation frame
2839 MovDelay[x][y] = 4 * delay;
2841 if (MovDelay[x][y]) // wait some time before next frame
2846 phase = MovDelay[x][y] / delay;
2848 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2849 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2851 if (!MovDelay[x][y])
2853 Tile[x][y] = EL_EXIT_OPEN;
2859 static void OpenGrayBall(int x, int y)
2863 if (!MovDelay[x][y]) // next animation frame
2865 if (IS_WALL(Store[x][y]))
2867 DrawWalls_MM(x, y, Store[x][y]);
2869 // copy wall tile to spare bitmap for "melting" animation
2870 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2871 TILEX, TILEY, x * TILEX, y * TILEY);
2873 DrawElement_MM(x, y, EL_GRAY_BALL);
2876 MovDelay[x][y] = 50 * delay;
2879 if (MovDelay[x][y]) // wait some time before next frame
2883 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2887 int dx = RND(26), dy = RND(26);
2889 if (IS_WALL(Store[x][y]))
2891 // copy wall tile from spare bitmap for "melting" animation
2892 bitmap = bitmap_db_field;
2898 int graphic = el2gfx(Store[x][y]);
2900 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2903 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2904 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2906 laser.redraw = TRUE;
2908 MarkTileDirty(x, y);
2911 if (!MovDelay[x][y])
2913 Tile[x][y] = Store[x][y];
2914 Store[x][y] = Store2[x][y] = 0;
2915 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2917 InitField(x, y, FALSE);
2920 ScanLaser_FromLastMirror();
2925 static void OpenEnvelope(int x, int y)
2927 int num_frames = 8; // seven frames plus final empty space
2929 if (!MovDelay[x][y]) // next animation frame
2930 MovDelay[x][y] = num_frames;
2932 if (MovDelay[x][y]) // wait some time before next frame
2934 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2938 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2940 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2941 int frame = num_frames - MovDelay[x][y] - 1;
2943 DrawGraphicAnimation_MM(x, y, graphic, frame);
2945 laser.redraw = TRUE;
2948 if (MovDelay[x][y] == 0)
2950 Tile[x][y] = EL_EMPTY;
2961 static void MeltIce(int x, int y)
2966 if (!MovDelay[x][y]) // next animation frame
2967 MovDelay[x][y] = frames * delay;
2969 if (MovDelay[x][y]) // wait some time before next frame
2972 int wall_mask = Store2[x][y];
2973 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2976 phase = frames - MovDelay[x][y] / delay - 1;
2978 if (!MovDelay[x][y])
2980 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2981 Store[x][y] = Store2[x][y] = 0;
2983 DrawWalls_MM(x, y, Tile[x][y]);
2985 if (Tile[x][y] == EL_WALL_ICE_BASE)
2986 Tile[x][y] = EL_EMPTY;
2988 ScanLaser_FromLastMirror();
2990 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2992 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2994 laser.redraw = TRUE;
2999 static void GrowAmoeba(int x, int y)
3004 if (!MovDelay[x][y]) // next animation frame
3005 MovDelay[x][y] = frames * delay;
3007 if (MovDelay[x][y]) // wait some time before next frame
3010 int wall_mask = Store2[x][y];
3011 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
3014 phase = MovDelay[x][y] / delay;
3016 if (!MovDelay[x][y])
3018 Tile[x][y] = real_element;
3019 Store[x][y] = Store2[x][y] = 0;
3021 DrawWalls_MM(x, y, Tile[x][y]);
3022 DrawLaser(0, DL_LASER_ENABLED);
3024 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
3026 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
3031 static void DrawFieldAnimated_MM(int x, int y)
3035 laser.redraw = TRUE;
3038 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
3040 int element = Tile[x][y];
3041 int graphic = el2gfx(element);
3043 if (!getGraphicInfo_NewFrame(x, y, graphic))
3048 laser.redraw = TRUE;
3051 static void DrawFieldTwinkle(int x, int y)
3053 if (MovDelay[x][y] != 0) // wait some time before next frame
3059 if (MovDelay[x][y] != 0)
3061 int graphic = IMG_TWINKLE_WHITE;
3062 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
3064 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
3067 laser.redraw = TRUE;
3071 static void Explode_MM(int x, int y, int phase, int mode)
3073 int num_phase = 9, delay = 2;
3074 int last_phase = num_phase * delay;
3075 int half_phase = (num_phase / 2) * delay;
3078 laser.redraw = TRUE;
3080 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
3082 center_element = Tile[x][y];
3084 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
3086 // put moving element to center field (and let it explode there)
3087 center_element = MovingOrBlocked2Element_MM(x, y);
3088 RemoveMovingField_MM(x, y);
3090 Tile[x][y] = center_element;
3093 if (center_element != EL_GRAY_BALL_ACTIVE)
3094 Store[x][y] = EL_EMPTY;
3095 Store2[x][y] = center_element;
3097 Tile[x][y] = EL_EXPLODING_OPAQUE;
3099 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
3100 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
3101 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
3104 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3106 ExplodePhase[x][y] = 1;
3112 GfxFrame[x][y] = 0; // restart explosion animation
3114 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
3116 center_element = Store2[x][y];
3118 if (phase == half_phase && Store[x][y] == EL_EMPTY)
3120 Tile[x][y] = EL_EXPLODING_TRANSP;
3122 if (x == ELX && y == ELY)
3126 if (phase == last_phase)
3128 if (center_element == EL_BOMB_ACTIVE)
3130 DrawLaser(0, DL_LASER_DISABLED);
3133 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3135 laser.overloaded = FALSE;
3137 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
3139 GameOver_MM(GAME_OVER_BOMB);
3142 Tile[x][y] = Store[x][y];
3144 Store[x][y] = Store2[x][y] = 0;
3145 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3147 InitField(x, y, FALSE);
3150 if (center_element == EL_GRAY_BALL_ACTIVE)
3151 ScanLaser_FromLastMirror();
3153 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
3155 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
3156 int frame = getGraphicAnimationFrameXY(graphic, x, y);
3158 DrawGraphicAnimation_MM(x, y, graphic, frame);
3160 MarkTileDirty(x, y);
3164 static void Bang_MM(int x, int y)
3166 int element = Tile[x][y];
3168 if (IS_PACMAN(element))
3169 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3170 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
3171 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3172 else if (element == EL_KEY)
3173 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3175 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3177 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
3180 static void TurnRound(int x, int y)
3192 { 0, 0 }, { 0, 0 }, { 0, 0 },
3197 int left, right, back;
3201 { MV_DOWN, MV_UP, MV_RIGHT },
3202 { MV_UP, MV_DOWN, MV_LEFT },
3204 { MV_LEFT, MV_RIGHT, MV_DOWN },
3208 { MV_RIGHT, MV_LEFT, MV_UP }
3211 int element = Tile[x][y];
3212 int old_move_dir = MovDir[x][y];
3213 int right_dir = turn[old_move_dir].right;
3214 int back_dir = turn[old_move_dir].back;
3215 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
3216 int right_x = x + right_dx, right_y = y + right_dy;
3218 if (element == EL_PACMAN)
3220 boolean can_turn_right = FALSE;
3222 if (IN_LEV_FIELD(right_x, right_y) &&
3223 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
3224 can_turn_right = TRUE;
3227 MovDir[x][y] = right_dir;
3229 MovDir[x][y] = back_dir;
3235 static void StartMoving_MM(int x, int y)
3237 int element = Tile[x][y];
3242 if (CAN_MOVE(element))
3246 if (MovDelay[x][y]) // wait some time before next movement
3254 // now make next step
3256 Moving2Blocked(x, y, &newx, &newy); // get next screen position
3258 if (element == EL_PACMAN &&
3259 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
3260 !ObjHit(newx, newy, HIT_POS_CENTER))
3262 Store[newx][newy] = Tile[newx][newy];
3263 Tile[newx][newy] = EL_EMPTY;
3265 DrawField_MM(newx, newy);
3267 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3268 ObjHit(newx, newy, HIT_POS_CENTER))
3270 // object was running against a wall
3277 InitMovingField_MM(x, y, MovDir[x][y]);
3281 ContinueMoving_MM(x, y);
3284 static void ContinueMoving_MM(int x, int y)
3286 int element = Tile[x][y];
3287 int direction = MovDir[x][y];
3288 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3289 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3290 int horiz_move = (dx!=0);
3291 int newx = x + dx, newy = y + dy;
3292 int step = (horiz_move ? dx : dy) * TILEX / 8;
3294 MovPos[x][y] += step;
3296 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3298 Tile[x][y] = EL_EMPTY;
3299 Tile[newx][newy] = element;
3301 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3302 MovDelay[newx][newy] = 0;
3304 if (!CAN_MOVE(element))
3305 MovDir[newx][newy] = 0;
3308 DrawField_MM(newx, newy);
3310 Stop[newx][newy] = TRUE;
3312 if (element == EL_PACMAN)
3314 if (Store[newx][newy] == EL_BOMB)
3315 Bang_MM(newx, newy);
3317 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3318 (LX + 2 * XS) / TILEX == newx &&
3319 (LY + 2 * YS) / TILEY == newy)
3326 else // still moving on
3331 laser.redraw = TRUE;
3334 boolean ClickElement(int x, int y, int button)
3336 static DelayCounter click_delay = { CLICK_DELAY };
3337 static boolean new_button = TRUE;
3338 boolean element_clicked = FALSE;
3343 // initialize static variables
3344 click_delay.count = 0;
3345 click_delay.value = CLICK_DELAY;
3351 // do not rotate objects hit by the laser after the game was solved
3352 if (game_mm.level_solved && Hit[x][y])
3355 if (button == MB_RELEASED)
3358 click_delay.value = CLICK_DELAY;
3360 // release eventually hold auto-rotating mirror
3361 RotateMirror(x, y, MB_RELEASED);
3366 if (!FrameReached(&click_delay) && !new_button)
3369 if (button == MB_MIDDLEBUTTON) // middle button has no function
3372 if (!IN_LEV_FIELD(x, y))
3375 if (Tile[x][y] == EL_EMPTY)
3378 element = Tile[x][y];
3380 if (IS_MIRROR(element) ||
3381 IS_BEAMER(element) ||
3382 IS_POLAR(element) ||
3383 IS_POLAR_CROSS(element) ||
3384 IS_DF_MIRROR(element) ||
3385 IS_DF_MIRROR_AUTO(element))
3387 RotateMirror(x, y, button);
3389 element_clicked = TRUE;
3391 else if (IS_MCDUFFIN(element))
3393 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3395 if (has_laser && !laser.fuse_off)
3396 DrawLaser(0, DL_LASER_DISABLED);
3398 element = get_rotated_element(element, BUTTON_ROTATION(button));
3400 Tile[x][y] = element;
3405 laser.start_angle = get_element_angle(element);
3409 if (!laser.fuse_off)
3413 element_clicked = TRUE;
3415 else if (element == EL_FUSE_ON && laser.fuse_off)
3417 if (x != laser.fuse_x || y != laser.fuse_y)
3420 laser.fuse_off = FALSE;
3421 laser.fuse_x = laser.fuse_y = -1;
3423 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3426 element_clicked = TRUE;
3428 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3430 laser.fuse_off = TRUE;
3433 laser.overloaded = FALSE;
3435 DrawLaser(0, DL_LASER_DISABLED);
3436 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3438 element_clicked = TRUE;
3440 else if (element == EL_LIGHTBALL)
3443 RaiseScoreElement_MM(element);
3444 DrawLaser(0, DL_LASER_ENABLED);
3446 element_clicked = TRUE;
3448 else if (IS_ENVELOPE(element))
3450 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3452 element_clicked = TRUE;
3455 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3458 return element_clicked;
3461 static void RotateMirror(int x, int y, int button)
3463 if (button == MB_RELEASED)
3465 // release eventually hold auto-rotating mirror
3472 if (IS_MIRROR(Tile[x][y]) ||
3473 IS_POLAR_CROSS(Tile[x][y]) ||
3474 IS_POLAR(Tile[x][y]) ||
3475 IS_BEAMER(Tile[x][y]) ||
3476 IS_DF_MIRROR(Tile[x][y]) ||
3477 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3478 IS_GRID_WOOD_AUTO(Tile[x][y]))
3480 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3482 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3484 if (button == MB_LEFTBUTTON)
3486 // left mouse button only for manual adjustment, no auto-rotating;
3487 // freeze mirror for until mouse button released
3491 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3493 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3497 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3499 int edge = Hit[x][y];
3505 DrawLaser(edge - 1, DL_LASER_DISABLED);
3509 else if (ObjHit(x, y, HIT_POS_CENTER))
3511 int edge = Hit[x][y];
3515 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3520 DrawLaser(edge - 1, DL_LASER_DISABLED);
3527 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3532 if ((IS_BEAMER(Tile[x][y]) ||
3533 IS_POLAR(Tile[x][y]) ||
3534 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3536 if (IS_BEAMER(Tile[x][y]))
3539 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3540 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3553 DrawLaser(0, DL_LASER_ENABLED);
3557 static void AutoRotateMirrors(void)
3561 if (!FrameReached(&rotate_delay))
3564 for (x = 0; x < lev_fieldx; x++)
3566 for (y = 0; y < lev_fieldy; y++)
3568 int element = Tile[x][y];
3570 // do not rotate objects hit by the laser after the game was solved
3571 if (game_mm.level_solved && Hit[x][y])
3574 if (IS_DF_MIRROR_AUTO(element) ||
3575 IS_GRID_WOOD_AUTO(element) ||
3576 IS_GRID_STEEL_AUTO(element) ||
3577 element == EL_REFRACTOR)
3579 RotateMirror(x, y, MB_RIGHTBUTTON);
3581 laser.redraw = TRUE;
3587 static boolean ObjHit(int obx, int oby, int bits)
3594 if (bits & HIT_POS_CENTER)
3596 if (CheckLaserPixel(cSX + obx + 15,
3601 if (bits & HIT_POS_EDGE)
3603 for (i = 0; i < 4; i++)
3604 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3605 cSY + oby + 31 * (i / 2)))
3609 if (bits & HIT_POS_BETWEEN)
3611 for (i = 0; i < 4; i++)
3612 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3613 cSY + 4 + oby + 22 * (i / 2)))
3620 static void DeletePacMan(int px, int py)
3626 if (game_mm.num_pacman <= 1)
3628 game_mm.num_pacman = 0;
3632 for (i = 0; i < game_mm.num_pacman; i++)
3633 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3636 game_mm.num_pacman--;
3638 for (j = i; j < game_mm.num_pacman; j++)
3640 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3641 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3642 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3646 static void GameActions_MM_Ext(void)
3653 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3656 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3658 element = Tile[x][y];
3660 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3661 StartMoving_MM(x, y);
3662 else if (IS_MOVING(x, y))
3663 ContinueMoving_MM(x, y);
3664 else if (IS_EXPLODING(element))
3665 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3666 else if (element == EL_EXIT_OPENING)
3668 else if (element == EL_GRAY_BALL_OPENING)
3670 else if (IS_ENVELOPE_OPENING(element))
3672 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3674 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3676 else if (IS_MIRROR(element) ||
3677 IS_MIRROR_FIXED(element) ||
3678 element == EL_PRISM)
3679 DrawFieldTwinkle(x, y);
3680 else if (element == EL_GRAY_BALL_ACTIVE ||
3681 element == EL_BOMB_ACTIVE ||
3682 element == EL_MINE_ACTIVE)
3683 DrawFieldAnimated_MM(x, y);
3684 else if (!IS_BLOCKED(x, y))
3685 DrawFieldAnimatedIfNeeded_MM(x, y);
3688 AutoRotateMirrors();
3691 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3693 // redraw after Explode_MM() ...
3695 DrawLaser(0, DL_LASER_ENABLED);
3696 laser.redraw = FALSE;
3701 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3705 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3707 DrawLaser(0, DL_LASER_DISABLED);
3712 // skip all following game actions if game is over
3713 if (game_mm.game_over)
3716 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3720 GameOver_MM(GAME_OVER_NO_ENERGY);
3725 if (FrameReached(&energy_delay))
3727 if (game_mm.energy_left > 0)
3728 game_mm.energy_left--;
3730 // when out of energy, wait another frame to play "out of time" sound
3733 element = laser.dest_element;
3736 if (element != Tile[ELX][ELY])
3738 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3739 element, Tile[ELX][ELY]);
3743 if (!laser.overloaded && laser.overload_value == 0 &&
3744 element != EL_BOMB &&
3745 element != EL_BOMB_ACTIVE &&
3746 element != EL_MINE &&
3747 element != EL_MINE_ACTIVE &&
3748 element != EL_GRAY_BALL &&
3749 element != EL_GRAY_BALL_ACTIVE &&
3750 element != EL_BLOCK_STONE &&
3751 element != EL_BLOCK_WOOD &&
3752 element != EL_FUSE_ON &&
3753 element != EL_FUEL_FULL &&
3754 !IS_WALL_ICE(element) &&
3755 !IS_WALL_AMOEBA(element))
3758 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3760 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3761 (!laser.overloaded && laser.overload_value > 0)) &&
3762 FrameReached(&overload_delay))
3764 if (laser.overloaded)
3765 laser.overload_value++;
3767 laser.overload_value--;
3769 if (game_mm.cheat_no_overload)
3771 laser.overloaded = FALSE;
3772 laser.overload_value = 0;
3775 game_mm.laser_overload_value = laser.overload_value;
3777 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3779 SetLaserColor(0xFF);
3781 DrawLaser(0, DL_LASER_ENABLED);
3784 if (!laser.overloaded)
3785 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3786 else if (setup.sound_loops)
3787 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3789 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3791 if (laser.overload_value == MAX_LASER_OVERLOAD)
3793 UpdateAndDisplayGameControlValues();
3797 GameOver_MM(GAME_OVER_OVERLOADED);
3808 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3810 if (game_mm.cheat_no_explosion)
3815 laser.dest_element = EL_EXPLODING_OPAQUE;
3820 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3822 laser.fuse_off = TRUE;
3826 DrawLaser(0, DL_LASER_DISABLED);
3827 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3830 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3832 if (!Store2[ELX][ELY]) // check if content element not yet determined
3834 int last_anim_random_frame = gfx.anim_random_frame;
3837 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3838 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3840 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3841 native_mm_level.ball_choice_mode, 0,
3842 game_mm.ball_choice_pos);
3844 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3845 gfx.anim_random_frame = last_anim_random_frame;
3847 game_mm.ball_choice_pos++;
3849 int new_element = native_mm_level.ball_content[element_pos];
3850 int new_element_base = map_wall_to_base_element(new_element);
3852 if (IS_WALL(new_element_base))
3854 // always use completely filled wall element
3855 new_element = new_element_base | 0x000f;
3857 else if (native_mm_level.rotate_ball_content &&
3858 get_num_elements(new_element) > 1)
3860 // randomly rotate newly created game element
3861 new_element = get_rotated_element(new_element, RND(16));
3864 Store[ELX][ELY] = new_element;
3865 Store2[ELX][ELY] = TRUE;
3868 if (native_mm_level.explode_ball)
3871 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3873 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3878 if (IS_WALL_ICE(element) && CT > 50)
3880 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3882 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3883 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3884 Store2[ELX][ELY] = laser.wall_mask;
3886 laser.dest_element = Tile[ELX][ELY];
3891 if (IS_WALL_AMOEBA(element) && CT > 60)
3894 int element2 = Tile[ELX][ELY];
3896 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3899 for (i = laser.num_damages - 1; i >= 0; i--)
3900 if (laser.damage[i].is_mirror)
3903 r = laser.num_edges;
3904 d = laser.num_damages;
3911 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3914 DrawLaser(0, DL_LASER_ENABLED);
3917 x = laser.damage[k1].x;
3918 y = laser.damage[k1].y;
3923 for (i = 0; i < 4; i++)
3925 if (laser.wall_mask & (1 << i))
3927 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3928 cSY + ELY * TILEY + 31 * (i / 2)))
3931 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3932 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3939 for (i = 0; i < 4; i++)
3941 if (laser.wall_mask & (1 << i))
3943 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3944 cSY + ELY * TILEY + 31 * (i / 2)))
3951 if (laser.num_beamers > 0 ||
3952 k1 < 1 || k2 < 4 || k3 < 4 ||
3953 CheckLaserPixel(cSX + ELX * TILEX + 14,
3954 cSY + ELY * TILEY + 14))
3956 laser.num_edges = r;
3957 laser.num_damages = d;
3959 DrawLaser(0, DL_LASER_DISABLED);
3962 Tile[ELX][ELY] = element | laser.wall_mask;
3964 int x = ELX, y = ELY;
3965 int wall_mask = laser.wall_mask;
3968 DrawLaser(0, DL_LASER_ENABLED);
3970 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3972 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3973 Store[x][y] = EL_WALL_AMOEBA_BASE;
3974 Store2[x][y] = wall_mask;
3979 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3980 laser.stops_inside_element && CT > native_mm_level.time_block)
3985 if (ABS(XS) > ABS(YS))
3992 for (i = 0; i < 4; i++)
3999 x = ELX + Step[k * 4].x;
4000 y = ELY + Step[k * 4].y;
4002 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
4005 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
4013 laser.overloaded = (element == EL_BLOCK_STONE);
4018 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
4021 Tile[x][y] = element;
4023 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
4026 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
4028 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
4029 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
4037 if (element == EL_FUEL_FULL && CT > 10)
4039 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
4040 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
4042 for (i = start; i <= num_init_game_frames; i++)
4044 if (i == num_init_game_frames)
4045 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4046 else if (setup.sound_loops)
4047 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4049 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4051 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
4053 UpdateAndDisplayGameControlValues();
4058 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
4060 DrawField_MM(ELX, ELY);
4062 DrawLaser(0, DL_LASER_ENABLED);
4068 void GameActions_MM(struct MouseActionInfo action)
4070 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
4071 boolean button_released = (action.button == MB_RELEASED);
4073 GameActions_MM_Ext();
4075 CheckSingleStepMode_MM(element_clicked, button_released);
4078 static void MovePacMen(void)
4080 int mx, my, ox, oy, nx, ny;
4084 if (++pacman_nr >= game_mm.num_pacman)
4087 game_mm.pacman[pacman_nr].dir--;
4089 for (l = 1; l < 5; l++)
4091 game_mm.pacman[pacman_nr].dir++;
4093 if (game_mm.pacman[pacman_nr].dir > 4)
4094 game_mm.pacman[pacman_nr].dir = 1;
4096 if (game_mm.pacman[pacman_nr].dir % 2)
4099 my = game_mm.pacman[pacman_nr].dir - 2;
4104 mx = 3 - game_mm.pacman[pacman_nr].dir;
4107 ox = game_mm.pacman[pacman_nr].x;
4108 oy = game_mm.pacman[pacman_nr].y;
4111 element = Tile[nx][ny];
4113 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
4116 if (!IS_EATABLE4PACMAN(element))
4119 if (ObjHit(nx, ny, HIT_POS_CENTER))
4122 Tile[ox][oy] = EL_EMPTY;
4124 EL_PACMAN_RIGHT - 1 +
4125 (game_mm.pacman[pacman_nr].dir - 1 +
4126 (game_mm.pacman[pacman_nr].dir % 2) * 2);
4128 game_mm.pacman[pacman_nr].x = nx;
4129 game_mm.pacman[pacman_nr].y = ny;
4131 DrawGraphic_MM(ox, oy, IMG_EMPTY);
4133 if (element != EL_EMPTY)
4135 int graphic = el2gfx(Tile[nx][ny]);
4140 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
4143 ox = cSX + ox * TILEX;
4144 oy = cSY + oy * TILEY;
4146 for (i = 1; i < 33; i += 2)
4147 BlitBitmap(bitmap, window,
4148 src_x, src_y, TILEX, TILEY,
4149 ox + i * mx, oy + i * my);
4150 Ct = Ct + FrameCounter - CT;
4153 DrawField_MM(nx, ny);
4156 if (!laser.fuse_off)
4158 DrawLaser(0, DL_LASER_ENABLED);
4160 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
4162 AddDamagedField(nx, ny);
4164 laser.damage[laser.num_damages - 1].edge = 0;
4168 if (element == EL_BOMB)
4169 DeletePacMan(nx, ny);
4171 if (IS_WALL_AMOEBA(element) &&
4172 (LX + 2 * XS) / TILEX == nx &&
4173 (LY + 2 * YS) / TILEY == ny)
4183 static void InitMovingField_MM(int x, int y, int direction)
4185 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4186 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4188 MovDir[x][y] = direction;
4189 MovDir[newx][newy] = direction;
4191 if (Tile[newx][newy] == EL_EMPTY)
4192 Tile[newx][newy] = EL_BLOCKED;
4195 static int MovingOrBlocked2Element_MM(int x, int y)
4197 int element = Tile[x][y];
4199 if (element == EL_BLOCKED)
4203 Blocked2Moving(x, y, &oldx, &oldy);
4205 return Tile[oldx][oldy];
4211 static void RemoveMovingField_MM(int x, int y)
4213 int oldx = x, oldy = y, newx = x, newy = y;
4215 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4218 if (IS_MOVING(x, y))
4220 Moving2Blocked(x, y, &newx, &newy);
4221 if (Tile[newx][newy] != EL_BLOCKED)
4224 else if (Tile[x][y] == EL_BLOCKED)
4226 Blocked2Moving(x, y, &oldx, &oldy);
4227 if (!IS_MOVING(oldx, oldy))
4231 Tile[oldx][oldy] = EL_EMPTY;
4232 Tile[newx][newy] = EL_EMPTY;
4233 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4234 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4236 DrawLevelField_MM(oldx, oldy);
4237 DrawLevelField_MM(newx, newy);
4240 static void RaiseScore_MM(int value)
4242 game_mm.score += value;
4245 void RaiseScoreElement_MM(int element)
4250 case EL_PACMAN_RIGHT:
4252 case EL_PACMAN_LEFT:
4253 case EL_PACMAN_DOWN:
4254 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4258 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4263 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4267 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4276 // ----------------------------------------------------------------------------
4277 // Mirror Magic game engine snapshot handling functions
4278 // ----------------------------------------------------------------------------
4280 void SaveEngineSnapshotValues_MM(void)
4284 engine_snapshot_mm.game_mm = game_mm;
4285 engine_snapshot_mm.laser = laser;
4287 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4289 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4291 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4292 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4293 engine_snapshot_mm.Box[x][y] = Box[x][y];
4294 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4298 engine_snapshot_mm.LX = LX;
4299 engine_snapshot_mm.LY = LY;
4300 engine_snapshot_mm.XS = XS;
4301 engine_snapshot_mm.YS = YS;
4302 engine_snapshot_mm.ELX = ELX;
4303 engine_snapshot_mm.ELY = ELY;
4304 engine_snapshot_mm.CT = CT;
4305 engine_snapshot_mm.Ct = Ct;
4307 engine_snapshot_mm.last_LX = last_LX;
4308 engine_snapshot_mm.last_LY = last_LY;
4309 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4310 engine_snapshot_mm.hold_x = hold_x;
4311 engine_snapshot_mm.hold_y = hold_y;
4312 engine_snapshot_mm.pacman_nr = pacman_nr;
4314 engine_snapshot_mm.rotate_delay = rotate_delay;
4315 engine_snapshot_mm.pacman_delay = pacman_delay;
4316 engine_snapshot_mm.energy_delay = energy_delay;
4317 engine_snapshot_mm.overload_delay = overload_delay;
4320 void LoadEngineSnapshotValues_MM(void)
4324 // stored engine snapshot buffers already restored at this point
4326 game_mm = engine_snapshot_mm.game_mm;
4327 laser = engine_snapshot_mm.laser;
4329 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4331 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4333 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4334 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4335 Box[x][y] = engine_snapshot_mm.Box[x][y];
4336 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4340 LX = engine_snapshot_mm.LX;
4341 LY = engine_snapshot_mm.LY;
4342 XS = engine_snapshot_mm.XS;
4343 YS = engine_snapshot_mm.YS;
4344 ELX = engine_snapshot_mm.ELX;
4345 ELY = engine_snapshot_mm.ELY;
4346 CT = engine_snapshot_mm.CT;
4347 Ct = engine_snapshot_mm.Ct;
4349 last_LX = engine_snapshot_mm.last_LX;
4350 last_LY = engine_snapshot_mm.last_LY;
4351 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4352 hold_x = engine_snapshot_mm.hold_x;
4353 hold_y = engine_snapshot_mm.hold_y;
4354 pacman_nr = engine_snapshot_mm.pacman_nr;
4356 rotate_delay = engine_snapshot_mm.rotate_delay;
4357 pacman_delay = engine_snapshot_mm.pacman_delay;
4358 energy_delay = engine_snapshot_mm.energy_delay;
4359 overload_delay = engine_snapshot_mm.overload_delay;
4361 RedrawPlayfield_MM();
4364 static int getAngleFromTouchDelta(int dx, int dy, int base)
4366 double pi = 3.141592653;
4367 double rad = atan2((double)-dy, (double)dx);
4368 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4369 double deg = rad2 * 180.0 / pi;
4371 return (int)(deg * base / 360.0 + 0.5) % base;
4374 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4376 // calculate start (source) position to be at the middle of the tile
4377 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4378 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4379 int dx = dst_mx - src_mx;
4380 int dy = dst_my - src_my;
4389 if (!IN_LEV_FIELD(x, y))
4392 element = Tile[x][y];
4394 if (!IS_MCDUFFIN(element) &&
4395 !IS_MIRROR(element) &&
4396 !IS_BEAMER(element) &&
4397 !IS_POLAR(element) &&
4398 !IS_POLAR_CROSS(element) &&
4399 !IS_DF_MIRROR(element))
4402 angle_old = get_element_angle(element);
4404 if (IS_MCDUFFIN(element))
4406 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4407 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4408 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4409 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4412 else if (IS_MIRROR(element) ||
4413 IS_DF_MIRROR(element))
4415 for (i = 0; i < laser.num_damages; i++)
4417 if (laser.damage[i].x == x &&
4418 laser.damage[i].y == y &&
4419 ObjHit(x, y, HIT_POS_CENTER))
4421 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4422 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4429 if (angle_new == -1)
4431 if (IS_MIRROR(element) ||
4432 IS_DF_MIRROR(element) ||
4436 if (IS_POLAR_CROSS(element))
4439 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4442 button = (angle_new == angle_old ? 0 :
4443 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4444 MB_LEFTBUTTON : MB_RIGHTBUTTON);