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)
1745 AddDamagedField(LX / TILEX, LY / TILEY);
1747 laser.overloaded = TRUE;
1754 if (HitOnlyAnEdge(hit_mask))
1758 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1759 element = MovingOrBlocked2Element_MM(ELX, ELY);
1762 Debug("game:mm:HitElement", "(1): element == %d", element);
1766 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1767 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1770 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1774 AddDamagedField(ELX, ELY);
1776 boolean through_center = ((ELX * TILEX + 14 - LX) * YS ==
1777 (ELY * TILEY + 14 - LY) * XS);
1779 // this is more precise: check if laser would go through the center
1780 if (!IS_DF_SLOPE(element) && !through_center)
1784 // prevent cutting through laser emitter with laser beam
1785 if (IS_LASER(element))
1788 // skip the whole element before continuing the scan
1796 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1798 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1800 /* skipping scan positions to the right and down skips one scan
1801 position too much, because this is only the top left scan position
1802 of totally four scan positions (plus one to the right, one to the
1803 bottom and one to the bottom right) */
1804 /* ... but only roll back scan position if more than one step done */
1814 Debug("game:mm:HitElement", "(2): element == %d", element);
1817 if (LX + 5 * XS < 0 ||
1827 Debug("game:mm:HitElement", "(3): element == %d", element);
1830 if (IS_POLAR(element) &&
1831 ((element - EL_POLAR_START) % 2 ||
1832 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1834 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1836 laser.num_damages--;
1841 if (IS_POLAR_CROSS(element) &&
1842 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1844 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1846 laser.num_damages--;
1851 if (IS_DF_SLOPE(element) && !through_center)
1855 if (hit_mask == HIT_MASK_ALL)
1857 // laser already inside slope -- go back half step
1864 AddLaserEdge(LX, LY);
1866 LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0);
1867 LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0);
1869 else if (!IS_BEAMER(element) &&
1870 !IS_FIBRE_OPTIC(element) &&
1871 !IS_GRID_WOOD(element) &&
1872 element != EL_FUEL_EMPTY)
1875 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1876 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1878 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1881 LX = ELX * TILEX + 14;
1882 LY = ELY * TILEY + 14;
1884 AddLaserEdge(LX, LY);
1887 if (IS_MIRROR(element) ||
1888 IS_MIRROR_FIXED(element) ||
1889 IS_POLAR(element) ||
1890 IS_POLAR_CROSS(element) ||
1891 IS_DF_MIRROR(element) ||
1892 IS_DF_MIRROR_AUTO(element) ||
1893 IS_DF_MIRROR_FIXED(element) ||
1894 IS_DF_SLOPE(element) ||
1895 element == EL_PRISM ||
1896 element == EL_REFRACTOR)
1898 int current_angle = laser.current_angle;
1901 laser.num_damages--;
1903 AddDamagedField(ELX, ELY);
1905 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1908 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1910 if (IS_MIRROR(element) ||
1911 IS_MIRROR_FIXED(element) ||
1912 IS_DF_MIRROR(element) ||
1913 IS_DF_MIRROR_AUTO(element) ||
1914 IS_DF_MIRROR_FIXED(element) ||
1915 IS_DF_SLOPE(element))
1916 laser.current_angle = get_mirrored_angle(laser.current_angle,
1917 get_element_angle(element));
1919 if (element == EL_PRISM || element == EL_REFRACTOR)
1920 laser.current_angle = RND(16);
1922 XS = 2 * Step[laser.current_angle].x;
1923 YS = 2 * Step[laser.current_angle].y;
1927 // start from center position for all game elements but slope
1928 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1933 LX += step_size * XS;
1934 LY += step_size * YS;
1938 // advance laser position until reaching the next tile (slopes)
1939 while (LX / TILEX == ELX && (LX + 2) / TILEX == ELX &&
1940 LY / TILEY == ELY && (LY + 2) / TILEY == ELY)
1947 // draw sparkles on mirror
1948 if ((IS_MIRROR(element) ||
1949 IS_MIRROR_FIXED(element) ||
1950 element == EL_PRISM) &&
1951 current_angle != laser.current_angle)
1953 MovDelay[ELX][ELY] = 11; // start animation
1956 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1957 current_angle != laser.current_angle)
1958 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1961 (get_opposite_angle(laser.current_angle) ==
1962 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1964 if (IS_DF_SLOPE(element))
1966 // handle special cases for slope element
1968 if (IS_45_ANGLE(laser.current_angle))
1972 elx = getLevelFromLaserX(LX);
1973 ely = getLevelFromLaserY(LY);
1975 if (IN_LEV_FIELD(elx, ely))
1977 int element_next = Tile[elx][ely];
1979 // check if slope is followed by slope with opposite orientation
1980 if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2)
1981 laser.overloaded = TRUE;
1984 int nr = element - EL_DF_SLOPE_START;
1985 int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) :
1986 nr == 1 ? (XS > 0 ? TILEX : 1) :
1987 nr == 2 ? (XS > 0 ? TILEX : 1) :
1988 nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0);
1989 int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) :
1990 nr == 1 ? (YS > 0 ? TILEY - 1 : -1) :
1991 nr == 2 ? (YS > 0 ? TILEY : 0) :
1992 nr == 3 ? (YS > 0 ? TILEY : 0) : 0);
1994 int px = ELX * TILEX + dx;
1995 int py = ELY * TILEY + dy;
2000 elx = getLevelFromLaserX(px);
2001 ely = getLevelFromLaserY(py);
2003 if (IN_LEV_FIELD(elx, ely))
2005 int element_side = Tile[elx][ely];
2007 // check if end of slope is blocked by other element
2008 if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side))
2010 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
2012 if (element & (1 << pos))
2013 laser.overloaded = TRUE;
2017 int pos = getMaskFromElement(element_side);
2019 if (mm_masks[pos][dx / 2][dy / 2] == 'X')
2020 laser.overloaded = TRUE;
2026 return (laser.overloaded ? TRUE : FALSE);
2029 if (element == EL_FUEL_FULL)
2031 laser.stops_inside_element = TRUE;
2036 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
2038 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2040 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
2041 element == EL_MINE ? EL_MINE_ACTIVE :
2042 EL_GRAY_BALL_ACTIVE);
2044 GfxFrame[ELX][ELY] = 0; // restart animation
2046 laser.dest_element_last = Tile[ELX][ELY];
2047 laser.dest_element_last_x = ELX;
2048 laser.dest_element_last_y = ELY;
2050 if (element == EL_MINE)
2051 laser.overloaded = TRUE;
2054 if (element == EL_KETTLE ||
2055 element == EL_CELL ||
2056 element == EL_KEY ||
2057 element == EL_LIGHTBALL ||
2058 element == EL_PACMAN ||
2059 IS_PACMAN(element) ||
2060 IS_ENVELOPE(element))
2062 if (!IS_PACMAN(element) &&
2063 !IS_ENVELOPE(element))
2066 if (element == EL_PACMAN)
2069 if (element == EL_KETTLE || element == EL_CELL)
2071 if (game_mm.kettles_still_needed > 0)
2072 game_mm.kettles_still_needed--;
2074 game.snapshot.collected_item = TRUE;
2076 if (game_mm.kettles_still_needed == 0)
2080 DrawLaser(0, DL_LASER_ENABLED);
2083 else if (element == EL_KEY)
2087 else if (IS_PACMAN(element))
2089 DeletePacMan(ELX, ELY);
2091 else if (IS_ENVELOPE(element))
2093 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
2096 RaiseScoreElement_MM(element);
2101 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
2103 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2105 DrawLaser(0, DL_LASER_ENABLED);
2107 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
2109 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
2110 game_mm.lights_still_needed--;
2114 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
2115 game_mm.lights_still_needed++;
2118 DrawField_MM(ELX, ELY);
2119 DrawLaser(0, DL_LASER_ENABLED);
2124 laser.stops_inside_element = TRUE;
2130 Debug("game:mm:HitElement", "(4): element == %d", element);
2133 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
2134 laser.num_beamers < MAX_NUM_BEAMERS &&
2135 laser.beamer[BEAMER_NR(element)][1].num)
2137 int beamer_angle = get_element_angle(element);
2138 int beamer_nr = BEAMER_NR(element);
2142 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
2145 laser.num_damages--;
2147 if (IS_FIBRE_OPTIC(element) ||
2148 laser.current_angle == get_opposite_angle(beamer_angle))
2152 LX = ELX * TILEX + 14;
2153 LY = ELY * TILEY + 14;
2155 AddLaserEdge(LX, LY);
2156 AddDamagedField(ELX, ELY);
2158 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2161 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2163 pos = (ELX == laser.beamer[beamer_nr][0].x &&
2164 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
2165 ELX = laser.beamer[beamer_nr][pos].x;
2166 ELY = laser.beamer[beamer_nr][pos].y;
2167 LX = ELX * TILEX + 14;
2168 LY = ELY * TILEY + 14;
2170 if (IS_BEAMER(element))
2172 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
2173 XS = 2 * Step[laser.current_angle].x;
2174 YS = 2 * Step[laser.current_angle].y;
2177 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
2179 AddLaserEdge(LX, LY);
2180 AddDamagedField(ELX, ELY);
2182 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2185 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2187 if (laser.current_angle == (laser.current_angle >> 1) << 1)
2192 LX += step_size * XS;
2193 LY += step_size * YS;
2195 laser.num_beamers++;
2204 static boolean HitOnlyAnEdge(int hit_mask)
2206 // check if the laser hit only the edge of an element and, if so, go on
2209 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
2213 if ((hit_mask == HIT_MASK_TOPLEFT ||
2214 hit_mask == HIT_MASK_TOPRIGHT ||
2215 hit_mask == HIT_MASK_BOTTOMLEFT ||
2216 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
2217 laser.current_angle % 4) // angle is not 90°
2221 if (hit_mask == HIT_MASK_TOPLEFT)
2226 else if (hit_mask == HIT_MASK_TOPRIGHT)
2231 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
2236 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
2242 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
2248 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
2255 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
2261 static boolean HitPolarizer(int element, int hit_mask)
2263 if (HitOnlyAnEdge(hit_mask))
2266 if (IS_DF_GRID(element))
2268 int grid_angle = get_element_angle(element);
2271 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2272 grid_angle, laser.current_angle);
2275 AddLaserEdge(LX, LY);
2276 AddDamagedField(ELX, ELY);
2279 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2281 if (laser.current_angle == grid_angle ||
2282 laser.current_angle == get_opposite_angle(grid_angle))
2284 // skip the whole element before continuing the scan
2290 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2292 if (LX/TILEX > ELX || LY/TILEY > ELY)
2294 /* skipping scan positions to the right and down skips one scan
2295 position too much, because this is only the top left scan position
2296 of totally four scan positions (plus one to the right, one to the
2297 bottom and one to the bottom right) */
2303 AddLaserEdge(LX, LY);
2309 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2311 LX / TILEX, LY / TILEY,
2312 LX % TILEX, LY % TILEY);
2317 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2319 return HitReflectingWalls(element, hit_mask);
2323 return HitAbsorbingWalls(element, hit_mask);
2326 else if (IS_GRID_STEEL(element))
2328 // may be required if graphics for steel grid redefined
2329 AddDamagedField(ELX, ELY);
2331 return HitReflectingWalls(element, hit_mask);
2333 else // IS_GRID_WOOD
2335 // may be required if graphics for wooden grid redefined
2336 AddDamagedField(ELX, ELY);
2338 return HitAbsorbingWalls(element, hit_mask);
2344 static boolean HitBlock(int element, int hit_mask)
2346 boolean check = FALSE;
2348 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2349 game_mm.num_keys == 0)
2352 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2355 int ex = ELX * TILEX + 14;
2356 int ey = ELY * TILEY + 14;
2360 for (i = 1; i < 32; i++)
2365 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2370 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2371 return HitAbsorbingWalls(element, hit_mask);
2375 AddLaserEdge(LX - XS, LY - YS);
2376 AddDamagedField(ELX, ELY);
2379 Box[ELX][ELY] = laser.num_edges;
2381 return HitReflectingWalls(element, hit_mask);
2384 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2386 int xs = XS / 2, ys = YS / 2;
2388 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2389 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2391 laser.overloaded = (element == EL_GATE_STONE);
2396 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2397 (hit_mask == HIT_MASK_TOP ||
2398 hit_mask == HIT_MASK_LEFT ||
2399 hit_mask == HIT_MASK_RIGHT ||
2400 hit_mask == HIT_MASK_BOTTOM))
2401 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2402 hit_mask == HIT_MASK_BOTTOM),
2403 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2404 hit_mask == HIT_MASK_RIGHT));
2405 AddLaserEdge(LX, LY);
2411 if (element == EL_GATE_STONE && Box[ELX][ELY])
2413 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2425 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2427 int xs = XS / 2, ys = YS / 2;
2429 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2430 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2432 laser.overloaded = (element == EL_BLOCK_STONE);
2437 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2438 (hit_mask == HIT_MASK_TOP ||
2439 hit_mask == HIT_MASK_LEFT ||
2440 hit_mask == HIT_MASK_RIGHT ||
2441 hit_mask == HIT_MASK_BOTTOM))
2442 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2443 hit_mask == HIT_MASK_BOTTOM),
2444 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2445 hit_mask == HIT_MASK_RIGHT));
2446 AddDamagedField(ELX, ELY);
2448 LX = ELX * TILEX + 14;
2449 LY = ELY * TILEY + 14;
2451 AddLaserEdge(LX, LY);
2453 laser.stops_inside_element = TRUE;
2461 static boolean HitLaserSource(int element, int hit_mask)
2463 if (HitOnlyAnEdge(hit_mask))
2466 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2468 laser.overloaded = TRUE;
2473 static boolean HitLaserDestination(int element, int hit_mask)
2475 if (HitOnlyAnEdge(hit_mask))
2478 if (element != EL_EXIT_OPEN &&
2479 !(IS_RECEIVER(element) &&
2480 game_mm.kettles_still_needed == 0 &&
2481 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2483 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2488 if (IS_RECEIVER(element) ||
2489 (IS_22_5_ANGLE(laser.current_angle) &&
2490 (ELX != (LX + 6 * XS) / TILEX ||
2491 ELY != (LY + 6 * YS) / TILEY ||
2500 LX = ELX * TILEX + 14;
2501 LY = ELY * TILEY + 14;
2503 laser.stops_inside_element = TRUE;
2506 AddLaserEdge(LX, LY);
2507 AddDamagedField(ELX, ELY);
2509 if (game_mm.lights_still_needed == 0)
2511 game_mm.level_solved = TRUE;
2513 SetTileCursorActive(FALSE);
2519 static boolean HitReflectingWalls(int element, int hit_mask)
2521 // check if laser hits side of a wall with an angle that is not 90°
2522 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2523 hit_mask == HIT_MASK_LEFT ||
2524 hit_mask == HIT_MASK_RIGHT ||
2525 hit_mask == HIT_MASK_BOTTOM))
2527 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2532 if (!IS_DF_GRID(element))
2533 AddLaserEdge(LX, LY);
2535 // check if laser hits wall with an angle of 45°
2536 if (!IS_22_5_ANGLE(laser.current_angle))
2538 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2541 laser.current_angle = get_mirrored_angle(laser.current_angle,
2544 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2547 laser.current_angle = get_mirrored_angle(laser.current_angle,
2551 AddLaserEdge(LX, LY);
2553 XS = 2 * Step[laser.current_angle].x;
2554 YS = 2 * Step[laser.current_angle].y;
2558 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2560 laser.current_angle = get_mirrored_angle(laser.current_angle,
2565 if (!IS_DF_GRID(element))
2566 AddLaserEdge(LX, LY);
2571 if (!IS_DF_GRID(element))
2572 AddLaserEdge(LX, LY + YS / 2);
2575 if (!IS_DF_GRID(element))
2576 AddLaserEdge(LX, LY);
2579 YS = 2 * Step[laser.current_angle].y;
2583 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2585 laser.current_angle = get_mirrored_angle(laser.current_angle,
2590 if (!IS_DF_GRID(element))
2591 AddLaserEdge(LX, LY);
2596 if (!IS_DF_GRID(element))
2597 AddLaserEdge(LX + XS / 2, LY);
2600 if (!IS_DF_GRID(element))
2601 AddLaserEdge(LX, LY);
2604 XS = 2 * Step[laser.current_angle].x;
2610 // reflection at the edge of reflecting DF style wall
2611 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2613 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2614 hit_mask == HIT_MASK_TOPRIGHT) ||
2615 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2616 hit_mask == HIT_MASK_TOPLEFT) ||
2617 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2618 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2619 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2620 hit_mask == HIT_MASK_BOTTOMRIGHT))
2623 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2624 ANG_MIRROR_135 : ANG_MIRROR_45);
2626 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2628 AddDamagedField(ELX, ELY);
2629 AddLaserEdge(LX, LY);
2631 laser.current_angle = get_mirrored_angle(laser.current_angle,
2639 AddLaserEdge(LX, LY);
2645 // reflection inside an edge of reflecting DF style wall
2646 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2648 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2649 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2650 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2651 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2652 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2653 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2654 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2655 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2658 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2659 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2660 ANG_MIRROR_135 : ANG_MIRROR_45);
2662 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2665 AddDamagedField(ELX, ELY);
2668 AddLaserEdge(LX - XS, LY - YS);
2669 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2670 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2672 laser.current_angle = get_mirrored_angle(laser.current_angle,
2680 AddLaserEdge(LX, LY);
2686 // check if laser hits DF style wall with an angle of 90°
2687 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2689 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2690 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2691 (IS_VERT_ANGLE(laser.current_angle) &&
2692 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2694 // laser at last step touched nothing or the same side of the wall
2695 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2697 AddDamagedField(ELX, ELY);
2704 last_hit_mask = hit_mask;
2711 if (!HitOnlyAnEdge(hit_mask))
2713 laser.overloaded = TRUE;
2721 static boolean HitAbsorbingWalls(int element, int hit_mask)
2723 if (HitOnlyAnEdge(hit_mask))
2727 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2729 AddLaserEdge(LX - XS, LY - YS);
2736 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2738 AddLaserEdge(LX - XS, LY - YS);
2744 if (IS_WALL_WOOD(element) ||
2745 IS_DF_WALL_WOOD(element) ||
2746 IS_GRID_WOOD(element) ||
2747 IS_GRID_WOOD_FIXED(element) ||
2748 IS_GRID_WOOD_AUTO(element) ||
2749 element == EL_FUSE_ON ||
2750 element == EL_BLOCK_WOOD ||
2751 element == EL_GATE_WOOD)
2753 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2758 if (IS_WALL_ICE(element))
2764 // check if laser hit adjacent edges of two diagonal tiles
2765 if (ELX != lx / TILEX)
2767 if (ELY != ly / TILEY)
2770 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2771 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2773 // check if laser hits wall with an angle of 90°
2774 if (IS_90_ANGLE(laser.current_angle))
2775 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2777 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2781 for (i = 0; i < 4; i++)
2783 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2784 mask = 15 - (8 >> i);
2785 else if (ABS(XS) == 4 &&
2787 (XS > 0) == (i % 2) &&
2788 (YS < 0) == (i / 2))
2789 mask = 3 + (i / 2) * 9;
2790 else if (ABS(YS) == 4 &&
2792 (XS < 0) == (i % 2) &&
2793 (YS > 0) == (i / 2))
2794 mask = 5 + (i % 2) * 5;
2798 laser.wall_mask = mask;
2800 else if (IS_WALL_AMOEBA(element))
2802 int elx = (LX - 2 * XS) / TILEX;
2803 int ely = (LY - 2 * YS) / TILEY;
2804 int element2 = Tile[elx][ely];
2807 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2809 laser.dest_element = EL_EMPTY;
2817 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2818 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2820 if (IS_90_ANGLE(laser.current_angle))
2821 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2823 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2825 laser.wall_mask = mask;
2831 static void OpenExit(int x, int y)
2835 if (!MovDelay[x][y]) // next animation frame
2836 MovDelay[x][y] = 4 * delay;
2838 if (MovDelay[x][y]) // wait some time before next frame
2843 phase = MovDelay[x][y] / delay;
2845 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2846 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2848 if (!MovDelay[x][y])
2850 Tile[x][y] = EL_EXIT_OPEN;
2856 static void OpenGrayBall(int x, int y)
2860 if (!MovDelay[x][y]) // next animation frame
2862 if (IS_WALL(Store[x][y]))
2864 DrawWalls_MM(x, y, Store[x][y]);
2866 // copy wall tile to spare bitmap for "melting" animation
2867 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2868 TILEX, TILEY, x * TILEX, y * TILEY);
2870 DrawElement_MM(x, y, EL_GRAY_BALL);
2873 MovDelay[x][y] = 50 * delay;
2876 if (MovDelay[x][y]) // wait some time before next frame
2880 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2884 int dx = RND(26), dy = RND(26);
2886 if (IS_WALL(Store[x][y]))
2888 // copy wall tile from spare bitmap for "melting" animation
2889 bitmap = bitmap_db_field;
2895 int graphic = el2gfx(Store[x][y]);
2897 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2900 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2901 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2903 laser.redraw = TRUE;
2905 MarkTileDirty(x, y);
2908 if (!MovDelay[x][y])
2910 Tile[x][y] = Store[x][y];
2911 Store[x][y] = Store2[x][y] = 0;
2912 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2914 InitField(x, y, FALSE);
2917 ScanLaser_FromLastMirror();
2922 static void OpenEnvelope(int x, int y)
2924 int num_frames = 8; // seven frames plus final empty space
2926 if (!MovDelay[x][y]) // next animation frame
2927 MovDelay[x][y] = num_frames;
2929 if (MovDelay[x][y]) // wait some time before next frame
2931 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2935 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2937 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2938 int frame = num_frames - MovDelay[x][y] - 1;
2940 DrawGraphicAnimation_MM(x, y, graphic, frame);
2942 laser.redraw = TRUE;
2945 if (MovDelay[x][y] == 0)
2947 Tile[x][y] = EL_EMPTY;
2958 static void MeltIce(int x, int y)
2963 if (!MovDelay[x][y]) // next animation frame
2964 MovDelay[x][y] = frames * delay;
2966 if (MovDelay[x][y]) // wait some time before next frame
2969 int wall_mask = Store2[x][y];
2970 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2973 phase = frames - MovDelay[x][y] / delay - 1;
2975 if (!MovDelay[x][y])
2977 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2978 Store[x][y] = Store2[x][y] = 0;
2980 DrawWalls_MM(x, y, Tile[x][y]);
2982 if (Tile[x][y] == EL_WALL_ICE_BASE)
2983 Tile[x][y] = EL_EMPTY;
2985 ScanLaser_FromLastMirror();
2987 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2989 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2991 laser.redraw = TRUE;
2996 static void GrowAmoeba(int x, int y)
3001 if (!MovDelay[x][y]) // next animation frame
3002 MovDelay[x][y] = frames * delay;
3004 if (MovDelay[x][y]) // wait some time before next frame
3007 int wall_mask = Store2[x][y];
3008 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
3011 phase = MovDelay[x][y] / delay;
3013 if (!MovDelay[x][y])
3015 Tile[x][y] = real_element;
3016 Store[x][y] = Store2[x][y] = 0;
3018 DrawWalls_MM(x, y, Tile[x][y]);
3019 DrawLaser(0, DL_LASER_ENABLED);
3021 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
3023 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
3028 static void DrawFieldAnimated_MM(int x, int y)
3032 laser.redraw = TRUE;
3035 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
3037 int element = Tile[x][y];
3038 int graphic = el2gfx(element);
3040 if (!getGraphicInfo_NewFrame(x, y, graphic))
3045 laser.redraw = TRUE;
3048 static void DrawFieldTwinkle(int x, int y)
3050 if (MovDelay[x][y] != 0) // wait some time before next frame
3056 if (MovDelay[x][y] != 0)
3058 int graphic = IMG_TWINKLE_WHITE;
3059 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
3061 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
3064 laser.redraw = TRUE;
3068 static void Explode_MM(int x, int y, int phase, int mode)
3070 int num_phase = 9, delay = 2;
3071 int last_phase = num_phase * delay;
3072 int half_phase = (num_phase / 2) * delay;
3075 laser.redraw = TRUE;
3077 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
3079 center_element = Tile[x][y];
3081 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
3083 // put moving element to center field (and let it explode there)
3084 center_element = MovingOrBlocked2Element_MM(x, y);
3085 RemoveMovingField_MM(x, y);
3087 Tile[x][y] = center_element;
3090 if (center_element != EL_GRAY_BALL_ACTIVE)
3091 Store[x][y] = EL_EMPTY;
3092 Store2[x][y] = center_element;
3094 Tile[x][y] = EL_EXPLODING_OPAQUE;
3096 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
3097 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
3098 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
3101 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3103 ExplodePhase[x][y] = 1;
3109 GfxFrame[x][y] = 0; // restart explosion animation
3111 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
3113 center_element = Store2[x][y];
3115 if (phase == half_phase && Store[x][y] == EL_EMPTY)
3117 Tile[x][y] = EL_EXPLODING_TRANSP;
3119 if (x == ELX && y == ELY)
3123 if (phase == last_phase)
3125 if (center_element == EL_BOMB_ACTIVE)
3127 DrawLaser(0, DL_LASER_DISABLED);
3130 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3132 laser.overloaded = FALSE;
3134 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
3136 GameOver_MM(GAME_OVER_BOMB);
3139 Tile[x][y] = Store[x][y];
3141 Store[x][y] = Store2[x][y] = 0;
3142 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3144 InitField(x, y, FALSE);
3147 if (center_element == EL_GRAY_BALL_ACTIVE)
3148 ScanLaser_FromLastMirror();
3150 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
3152 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
3153 int frame = getGraphicAnimationFrameXY(graphic, x, y);
3155 DrawGraphicAnimation_MM(x, y, graphic, frame);
3157 MarkTileDirty(x, y);
3161 static void Bang_MM(int x, int y)
3163 int element = Tile[x][y];
3165 if (IS_PACMAN(element))
3166 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3167 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
3168 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3169 else if (element == EL_KEY)
3170 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3172 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3174 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
3177 static void TurnRound(int x, int y)
3189 { 0, 0 }, { 0, 0 }, { 0, 0 },
3194 int left, right, back;
3198 { MV_DOWN, MV_UP, MV_RIGHT },
3199 { MV_UP, MV_DOWN, MV_LEFT },
3201 { MV_LEFT, MV_RIGHT, MV_DOWN },
3205 { MV_RIGHT, MV_LEFT, MV_UP }
3208 int element = Tile[x][y];
3209 int old_move_dir = MovDir[x][y];
3210 int right_dir = turn[old_move_dir].right;
3211 int back_dir = turn[old_move_dir].back;
3212 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
3213 int right_x = x + right_dx, right_y = y + right_dy;
3215 if (element == EL_PACMAN)
3217 boolean can_turn_right = FALSE;
3219 if (IN_LEV_FIELD(right_x, right_y) &&
3220 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
3221 can_turn_right = TRUE;
3224 MovDir[x][y] = right_dir;
3226 MovDir[x][y] = back_dir;
3232 static void StartMoving_MM(int x, int y)
3234 int element = Tile[x][y];
3239 if (CAN_MOVE(element))
3243 if (MovDelay[x][y]) // wait some time before next movement
3251 // now make next step
3253 Moving2Blocked(x, y, &newx, &newy); // get next screen position
3255 if (element == EL_PACMAN &&
3256 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
3257 !ObjHit(newx, newy, HIT_POS_CENTER))
3259 Store[newx][newy] = Tile[newx][newy];
3260 Tile[newx][newy] = EL_EMPTY;
3262 DrawField_MM(newx, newy);
3264 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3265 ObjHit(newx, newy, HIT_POS_CENTER))
3267 // object was running against a wall
3274 InitMovingField_MM(x, y, MovDir[x][y]);
3278 ContinueMoving_MM(x, y);
3281 static void ContinueMoving_MM(int x, int y)
3283 int element = Tile[x][y];
3284 int direction = MovDir[x][y];
3285 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3286 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3287 int horiz_move = (dx!=0);
3288 int newx = x + dx, newy = y + dy;
3289 int step = (horiz_move ? dx : dy) * TILEX / 8;
3291 MovPos[x][y] += step;
3293 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3295 Tile[x][y] = EL_EMPTY;
3296 Tile[newx][newy] = element;
3298 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3299 MovDelay[newx][newy] = 0;
3301 if (!CAN_MOVE(element))
3302 MovDir[newx][newy] = 0;
3305 DrawField_MM(newx, newy);
3307 Stop[newx][newy] = TRUE;
3309 if (element == EL_PACMAN)
3311 if (Store[newx][newy] == EL_BOMB)
3312 Bang_MM(newx, newy);
3314 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3315 (LX + 2 * XS) / TILEX == newx &&
3316 (LY + 2 * YS) / TILEY == newy)
3323 else // still moving on
3328 laser.redraw = TRUE;
3331 boolean ClickElement(int x, int y, int button)
3333 static DelayCounter click_delay = { CLICK_DELAY };
3334 static boolean new_button = TRUE;
3335 boolean element_clicked = FALSE;
3340 // initialize static variables
3341 click_delay.count = 0;
3342 click_delay.value = CLICK_DELAY;
3348 // do not rotate objects hit by the laser after the game was solved
3349 if (game_mm.level_solved && Hit[x][y])
3352 if (button == MB_RELEASED)
3355 click_delay.value = CLICK_DELAY;
3357 // release eventually hold auto-rotating mirror
3358 RotateMirror(x, y, MB_RELEASED);
3363 if (!FrameReached(&click_delay) && !new_button)
3366 if (button == MB_MIDDLEBUTTON) // middle button has no function
3369 if (!IN_LEV_FIELD(x, y))
3372 if (Tile[x][y] == EL_EMPTY)
3375 element = Tile[x][y];
3377 if (IS_MIRROR(element) ||
3378 IS_BEAMER(element) ||
3379 IS_POLAR(element) ||
3380 IS_POLAR_CROSS(element) ||
3381 IS_DF_MIRROR(element) ||
3382 IS_DF_MIRROR_AUTO(element))
3384 RotateMirror(x, y, button);
3386 element_clicked = TRUE;
3388 else if (IS_MCDUFFIN(element))
3390 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3392 if (has_laser && !laser.fuse_off)
3393 DrawLaser(0, DL_LASER_DISABLED);
3395 element = get_rotated_element(element, BUTTON_ROTATION(button));
3397 Tile[x][y] = element;
3402 laser.start_angle = get_element_angle(element);
3406 if (!laser.fuse_off)
3410 element_clicked = TRUE;
3412 else if (element == EL_FUSE_ON && laser.fuse_off)
3414 if (x != laser.fuse_x || y != laser.fuse_y)
3417 laser.fuse_off = FALSE;
3418 laser.fuse_x = laser.fuse_y = -1;
3420 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3423 element_clicked = TRUE;
3425 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3427 laser.fuse_off = TRUE;
3430 laser.overloaded = FALSE;
3432 DrawLaser(0, DL_LASER_DISABLED);
3433 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3435 element_clicked = TRUE;
3437 else if (element == EL_LIGHTBALL)
3440 RaiseScoreElement_MM(element);
3441 DrawLaser(0, DL_LASER_ENABLED);
3443 element_clicked = TRUE;
3445 else if (IS_ENVELOPE(element))
3447 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3449 element_clicked = TRUE;
3452 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3455 return element_clicked;
3458 static void RotateMirror(int x, int y, int button)
3460 if (button == MB_RELEASED)
3462 // release eventually hold auto-rotating mirror
3469 if (IS_MIRROR(Tile[x][y]) ||
3470 IS_POLAR_CROSS(Tile[x][y]) ||
3471 IS_POLAR(Tile[x][y]) ||
3472 IS_BEAMER(Tile[x][y]) ||
3473 IS_DF_MIRROR(Tile[x][y]) ||
3474 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3475 IS_GRID_WOOD_AUTO(Tile[x][y]))
3477 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3479 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3481 if (button == MB_LEFTBUTTON)
3483 // left mouse button only for manual adjustment, no auto-rotating;
3484 // freeze mirror for until mouse button released
3488 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3490 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3494 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3496 int edge = Hit[x][y];
3502 DrawLaser(edge - 1, DL_LASER_DISABLED);
3506 else if (ObjHit(x, y, HIT_POS_CENTER))
3508 int edge = Hit[x][y];
3512 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3517 DrawLaser(edge - 1, DL_LASER_DISABLED);
3524 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3529 if ((IS_BEAMER(Tile[x][y]) ||
3530 IS_POLAR(Tile[x][y]) ||
3531 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3533 if (IS_BEAMER(Tile[x][y]))
3536 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3537 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3550 DrawLaser(0, DL_LASER_ENABLED);
3554 static void AutoRotateMirrors(void)
3558 if (!FrameReached(&rotate_delay))
3561 for (x = 0; x < lev_fieldx; x++)
3563 for (y = 0; y < lev_fieldy; y++)
3565 int element = Tile[x][y];
3567 // do not rotate objects hit by the laser after the game was solved
3568 if (game_mm.level_solved && Hit[x][y])
3571 if (IS_DF_MIRROR_AUTO(element) ||
3572 IS_GRID_WOOD_AUTO(element) ||
3573 IS_GRID_STEEL_AUTO(element) ||
3574 element == EL_REFRACTOR)
3576 RotateMirror(x, y, MB_RIGHTBUTTON);
3578 laser.redraw = TRUE;
3584 static boolean ObjHit(int obx, int oby, int bits)
3591 if (bits & HIT_POS_CENTER)
3593 if (CheckLaserPixel(cSX + obx + 15,
3598 if (bits & HIT_POS_EDGE)
3600 for (i = 0; i < 4; i++)
3601 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3602 cSY + oby + 31 * (i / 2)))
3606 if (bits & HIT_POS_BETWEEN)
3608 for (i = 0; i < 4; i++)
3609 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3610 cSY + 4 + oby + 22 * (i / 2)))
3617 static void DeletePacMan(int px, int py)
3623 if (game_mm.num_pacman <= 1)
3625 game_mm.num_pacman = 0;
3629 for (i = 0; i < game_mm.num_pacman; i++)
3630 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3633 game_mm.num_pacman--;
3635 for (j = i; j < game_mm.num_pacman; j++)
3637 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3638 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3639 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3643 static void GameActions_MM_Ext(void)
3650 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3653 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3655 element = Tile[x][y];
3657 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3658 StartMoving_MM(x, y);
3659 else if (IS_MOVING(x, y))
3660 ContinueMoving_MM(x, y);
3661 else if (IS_EXPLODING(element))
3662 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3663 else if (element == EL_EXIT_OPENING)
3665 else if (element == EL_GRAY_BALL_OPENING)
3667 else if (IS_ENVELOPE_OPENING(element))
3669 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3671 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3673 else if (IS_MIRROR(element) ||
3674 IS_MIRROR_FIXED(element) ||
3675 element == EL_PRISM)
3676 DrawFieldTwinkle(x, y);
3677 else if (element == EL_GRAY_BALL_ACTIVE ||
3678 element == EL_BOMB_ACTIVE ||
3679 element == EL_MINE_ACTIVE)
3680 DrawFieldAnimated_MM(x, y);
3681 else if (!IS_BLOCKED(x, y))
3682 DrawFieldAnimatedIfNeeded_MM(x, y);
3685 AutoRotateMirrors();
3688 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3690 // redraw after Explode_MM() ...
3692 DrawLaser(0, DL_LASER_ENABLED);
3693 laser.redraw = FALSE;
3698 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3702 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3704 DrawLaser(0, DL_LASER_DISABLED);
3709 // skip all following game actions if game is over
3710 if (game_mm.game_over)
3713 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3717 GameOver_MM(GAME_OVER_NO_ENERGY);
3722 if (FrameReached(&energy_delay))
3724 if (game_mm.energy_left > 0)
3725 game_mm.energy_left--;
3727 // when out of energy, wait another frame to play "out of time" sound
3730 element = laser.dest_element;
3733 if (element != Tile[ELX][ELY])
3735 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3736 element, Tile[ELX][ELY]);
3740 if (!laser.overloaded && laser.overload_value == 0 &&
3741 element != EL_BOMB &&
3742 element != EL_BOMB_ACTIVE &&
3743 element != EL_MINE &&
3744 element != EL_MINE_ACTIVE &&
3745 element != EL_GRAY_BALL &&
3746 element != EL_GRAY_BALL_ACTIVE &&
3747 element != EL_BLOCK_STONE &&
3748 element != EL_BLOCK_WOOD &&
3749 element != EL_FUSE_ON &&
3750 element != EL_FUEL_FULL &&
3751 !IS_WALL_ICE(element) &&
3752 !IS_WALL_AMOEBA(element))
3755 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3757 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3758 (!laser.overloaded && laser.overload_value > 0)) &&
3759 FrameReached(&overload_delay))
3761 if (laser.overloaded)
3762 laser.overload_value++;
3764 laser.overload_value--;
3766 if (game_mm.cheat_no_overload)
3768 laser.overloaded = FALSE;
3769 laser.overload_value = 0;
3772 game_mm.laser_overload_value = laser.overload_value;
3774 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3776 SetLaserColor(0xFF);
3778 DrawLaser(0, DL_LASER_ENABLED);
3781 if (!laser.overloaded)
3782 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3783 else if (setup.sound_loops)
3784 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3786 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3788 if (laser.overload_value == MAX_LASER_OVERLOAD)
3790 UpdateAndDisplayGameControlValues();
3794 GameOver_MM(GAME_OVER_OVERLOADED);
3805 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3807 if (game_mm.cheat_no_explosion)
3812 laser.dest_element = EL_EXPLODING_OPAQUE;
3817 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3819 laser.fuse_off = TRUE;
3823 DrawLaser(0, DL_LASER_DISABLED);
3824 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3827 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3829 if (!Store2[ELX][ELY]) // check if content element not yet determined
3831 int last_anim_random_frame = gfx.anim_random_frame;
3834 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3835 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3837 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3838 native_mm_level.ball_choice_mode, 0,
3839 game_mm.ball_choice_pos);
3841 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3842 gfx.anim_random_frame = last_anim_random_frame;
3844 game_mm.ball_choice_pos++;
3846 int new_element = native_mm_level.ball_content[element_pos];
3847 int new_element_base = map_wall_to_base_element(new_element);
3849 if (IS_WALL(new_element_base))
3851 // always use completely filled wall element
3852 new_element = new_element_base | 0x000f;
3854 else if (native_mm_level.rotate_ball_content &&
3855 get_num_elements(new_element) > 1)
3857 // randomly rotate newly created game element
3858 new_element = get_rotated_element(new_element, RND(16));
3861 Store[ELX][ELY] = new_element;
3862 Store2[ELX][ELY] = TRUE;
3865 if (native_mm_level.explode_ball)
3868 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3870 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3875 if (IS_WALL_ICE(element) && CT > 50)
3877 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3879 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3880 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3881 Store2[ELX][ELY] = laser.wall_mask;
3883 laser.dest_element = Tile[ELX][ELY];
3888 if (IS_WALL_AMOEBA(element) && CT > 60)
3891 int element2 = Tile[ELX][ELY];
3893 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3896 for (i = laser.num_damages - 1; i >= 0; i--)
3897 if (laser.damage[i].is_mirror)
3900 r = laser.num_edges;
3901 d = laser.num_damages;
3908 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3911 DrawLaser(0, DL_LASER_ENABLED);
3914 x = laser.damage[k1].x;
3915 y = laser.damage[k1].y;
3920 for (i = 0; i < 4; i++)
3922 if (laser.wall_mask & (1 << i))
3924 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3925 cSY + ELY * TILEY + 31 * (i / 2)))
3928 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3929 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3936 for (i = 0; i < 4; i++)
3938 if (laser.wall_mask & (1 << i))
3940 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3941 cSY + ELY * TILEY + 31 * (i / 2)))
3948 if (laser.num_beamers > 0 ||
3949 k1 < 1 || k2 < 4 || k3 < 4 ||
3950 CheckLaserPixel(cSX + ELX * TILEX + 14,
3951 cSY + ELY * TILEY + 14))
3953 laser.num_edges = r;
3954 laser.num_damages = d;
3956 DrawLaser(0, DL_LASER_DISABLED);
3959 Tile[ELX][ELY] = element | laser.wall_mask;
3961 int x = ELX, y = ELY;
3962 int wall_mask = laser.wall_mask;
3965 DrawLaser(0, DL_LASER_ENABLED);
3967 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3969 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3970 Store[x][y] = EL_WALL_AMOEBA_BASE;
3971 Store2[x][y] = wall_mask;
3976 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3977 laser.stops_inside_element && CT > native_mm_level.time_block)
3982 if (ABS(XS) > ABS(YS))
3989 for (i = 0; i < 4; i++)
3996 x = ELX + Step[k * 4].x;
3997 y = ELY + Step[k * 4].y;
3999 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
4002 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
4010 laser.overloaded = (element == EL_BLOCK_STONE);
4015 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
4018 Tile[x][y] = element;
4020 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
4023 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
4025 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
4026 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
4034 if (element == EL_FUEL_FULL && CT > 10)
4036 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
4037 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
4039 for (i = start; i <= num_init_game_frames; i++)
4041 if (i == num_init_game_frames)
4042 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4043 else if (setup.sound_loops)
4044 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4046 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4048 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
4050 UpdateAndDisplayGameControlValues();
4055 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
4057 DrawField_MM(ELX, ELY);
4059 DrawLaser(0, DL_LASER_ENABLED);
4065 void GameActions_MM(struct MouseActionInfo action)
4067 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
4068 boolean button_released = (action.button == MB_RELEASED);
4070 GameActions_MM_Ext();
4072 CheckSingleStepMode_MM(element_clicked, button_released);
4075 static void MovePacMen(void)
4077 int mx, my, ox, oy, nx, ny;
4081 if (++pacman_nr >= game_mm.num_pacman)
4084 game_mm.pacman[pacman_nr].dir--;
4086 for (l = 1; l < 5; l++)
4088 game_mm.pacman[pacman_nr].dir++;
4090 if (game_mm.pacman[pacman_nr].dir > 4)
4091 game_mm.pacman[pacman_nr].dir = 1;
4093 if (game_mm.pacman[pacman_nr].dir % 2)
4096 my = game_mm.pacman[pacman_nr].dir - 2;
4101 mx = 3 - game_mm.pacman[pacman_nr].dir;
4104 ox = game_mm.pacman[pacman_nr].x;
4105 oy = game_mm.pacman[pacman_nr].y;
4108 element = Tile[nx][ny];
4110 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
4113 if (!IS_EATABLE4PACMAN(element))
4116 if (ObjHit(nx, ny, HIT_POS_CENTER))
4119 Tile[ox][oy] = EL_EMPTY;
4121 EL_PACMAN_RIGHT - 1 +
4122 (game_mm.pacman[pacman_nr].dir - 1 +
4123 (game_mm.pacman[pacman_nr].dir % 2) * 2);
4125 game_mm.pacman[pacman_nr].x = nx;
4126 game_mm.pacman[pacman_nr].y = ny;
4128 DrawGraphic_MM(ox, oy, IMG_EMPTY);
4130 if (element != EL_EMPTY)
4132 int graphic = el2gfx(Tile[nx][ny]);
4137 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
4140 ox = cSX + ox * TILEX;
4141 oy = cSY + oy * TILEY;
4143 for (i = 1; i < 33; i += 2)
4144 BlitBitmap(bitmap, window,
4145 src_x, src_y, TILEX, TILEY,
4146 ox + i * mx, oy + i * my);
4147 Ct = Ct + FrameCounter - CT;
4150 DrawField_MM(nx, ny);
4153 if (!laser.fuse_off)
4155 DrawLaser(0, DL_LASER_ENABLED);
4157 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
4159 AddDamagedField(nx, ny);
4161 laser.damage[laser.num_damages - 1].edge = 0;
4165 if (element == EL_BOMB)
4166 DeletePacMan(nx, ny);
4168 if (IS_WALL_AMOEBA(element) &&
4169 (LX + 2 * XS) / TILEX == nx &&
4170 (LY + 2 * YS) / TILEY == ny)
4180 static void InitMovingField_MM(int x, int y, int direction)
4182 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4183 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4185 MovDir[x][y] = direction;
4186 MovDir[newx][newy] = direction;
4188 if (Tile[newx][newy] == EL_EMPTY)
4189 Tile[newx][newy] = EL_BLOCKED;
4192 static int MovingOrBlocked2Element_MM(int x, int y)
4194 int element = Tile[x][y];
4196 if (element == EL_BLOCKED)
4200 Blocked2Moving(x, y, &oldx, &oldy);
4202 return Tile[oldx][oldy];
4208 static void RemoveMovingField_MM(int x, int y)
4210 int oldx = x, oldy = y, newx = x, newy = y;
4212 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4215 if (IS_MOVING(x, y))
4217 Moving2Blocked(x, y, &newx, &newy);
4218 if (Tile[newx][newy] != EL_BLOCKED)
4221 else if (Tile[x][y] == EL_BLOCKED)
4223 Blocked2Moving(x, y, &oldx, &oldy);
4224 if (!IS_MOVING(oldx, oldy))
4228 Tile[oldx][oldy] = EL_EMPTY;
4229 Tile[newx][newy] = EL_EMPTY;
4230 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4231 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4233 DrawLevelField_MM(oldx, oldy);
4234 DrawLevelField_MM(newx, newy);
4237 static void RaiseScore_MM(int value)
4239 game_mm.score += value;
4242 void RaiseScoreElement_MM(int element)
4247 case EL_PACMAN_RIGHT:
4249 case EL_PACMAN_LEFT:
4250 case EL_PACMAN_DOWN:
4251 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4255 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4260 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4264 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4273 // ----------------------------------------------------------------------------
4274 // Mirror Magic game engine snapshot handling functions
4275 // ----------------------------------------------------------------------------
4277 void SaveEngineSnapshotValues_MM(void)
4281 engine_snapshot_mm.game_mm = game_mm;
4282 engine_snapshot_mm.laser = laser;
4284 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4286 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4288 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4289 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4290 engine_snapshot_mm.Box[x][y] = Box[x][y];
4291 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4295 engine_snapshot_mm.LX = LX;
4296 engine_snapshot_mm.LY = LY;
4297 engine_snapshot_mm.XS = XS;
4298 engine_snapshot_mm.YS = YS;
4299 engine_snapshot_mm.ELX = ELX;
4300 engine_snapshot_mm.ELY = ELY;
4301 engine_snapshot_mm.CT = CT;
4302 engine_snapshot_mm.Ct = Ct;
4304 engine_snapshot_mm.last_LX = last_LX;
4305 engine_snapshot_mm.last_LY = last_LY;
4306 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4307 engine_snapshot_mm.hold_x = hold_x;
4308 engine_snapshot_mm.hold_y = hold_y;
4309 engine_snapshot_mm.pacman_nr = pacman_nr;
4311 engine_snapshot_mm.rotate_delay = rotate_delay;
4312 engine_snapshot_mm.pacman_delay = pacman_delay;
4313 engine_snapshot_mm.energy_delay = energy_delay;
4314 engine_snapshot_mm.overload_delay = overload_delay;
4317 void LoadEngineSnapshotValues_MM(void)
4321 // stored engine snapshot buffers already restored at this point
4323 game_mm = engine_snapshot_mm.game_mm;
4324 laser = engine_snapshot_mm.laser;
4326 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4328 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4330 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4331 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4332 Box[x][y] = engine_snapshot_mm.Box[x][y];
4333 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4337 LX = engine_snapshot_mm.LX;
4338 LY = engine_snapshot_mm.LY;
4339 XS = engine_snapshot_mm.XS;
4340 YS = engine_snapshot_mm.YS;
4341 ELX = engine_snapshot_mm.ELX;
4342 ELY = engine_snapshot_mm.ELY;
4343 CT = engine_snapshot_mm.CT;
4344 Ct = engine_snapshot_mm.Ct;
4346 last_LX = engine_snapshot_mm.last_LX;
4347 last_LY = engine_snapshot_mm.last_LY;
4348 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4349 hold_x = engine_snapshot_mm.hold_x;
4350 hold_y = engine_snapshot_mm.hold_y;
4351 pacman_nr = engine_snapshot_mm.pacman_nr;
4353 rotate_delay = engine_snapshot_mm.rotate_delay;
4354 pacman_delay = engine_snapshot_mm.pacman_delay;
4355 energy_delay = engine_snapshot_mm.energy_delay;
4356 overload_delay = engine_snapshot_mm.overload_delay;
4358 RedrawPlayfield_MM();
4361 static int getAngleFromTouchDelta(int dx, int dy, int base)
4363 double pi = 3.141592653;
4364 double rad = atan2((double)-dy, (double)dx);
4365 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4366 double deg = rad2 * 180.0 / pi;
4368 return (int)(deg * base / 360.0 + 0.5) % base;
4371 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4373 // calculate start (source) position to be at the middle of the tile
4374 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4375 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4376 int dx = dst_mx - src_mx;
4377 int dy = dst_my - src_my;
4386 if (!IN_LEV_FIELD(x, y))
4389 element = Tile[x][y];
4391 if (!IS_MCDUFFIN(element) &&
4392 !IS_MIRROR(element) &&
4393 !IS_BEAMER(element) &&
4394 !IS_POLAR(element) &&
4395 !IS_POLAR_CROSS(element) &&
4396 !IS_DF_MIRROR(element))
4399 angle_old = get_element_angle(element);
4401 if (IS_MCDUFFIN(element))
4403 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4404 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4405 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4406 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4409 else if (IS_MIRROR(element) ||
4410 IS_DF_MIRROR(element))
4412 for (i = 0; i < laser.num_damages; i++)
4414 if (laser.damage[i].x == x &&
4415 laser.damage[i].y == y &&
4416 ObjHit(x, y, HIT_POS_CENTER))
4418 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4419 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4426 if (angle_new == -1)
4428 if (IS_MIRROR(element) ||
4429 IS_DF_MIRROR(element) ||
4433 if (IS_POLAR_CROSS(element))
4436 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4439 button = (angle_new == angle_old ? 0 :
4440 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4441 MB_LEFTBUTTON : MB_RIGHTBUTTON);