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 int mirrored_angle = get_mirrored_angle(laser.current_angle,
1715 get_element_angle(element));
1716 int opposite_angle = get_opposite_angle(laser.current_angle);
1718 // check if laser is reflected by slope by 180°
1719 if (mirrored_angle == opposite_angle)
1724 AddDamagedField(LX / TILEX, LY / TILEY);
1726 laser.overloaded = TRUE;
1733 if (HitOnlyAnEdge(hit_mask))
1737 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1738 element = MovingOrBlocked2Element_MM(ELX, ELY);
1741 Debug("game:mm:HitElement", "(1): element == %d", element);
1745 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1746 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1749 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1753 AddDamagedField(ELX, ELY);
1755 boolean through_center = ((ELX * TILEX + 14 - LX) * YS ==
1756 (ELY * TILEY + 14 - LY) * XS);
1758 // this is more precise: check if laser would go through the center
1759 if (!IS_DF_SLOPE(element) && !through_center)
1763 // prevent cutting through laser emitter with laser beam
1764 if (IS_LASER(element))
1767 // skip the whole element before continuing the scan
1775 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1777 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1779 /* skipping scan positions to the right and down skips one scan
1780 position too much, because this is only the top left scan position
1781 of totally four scan positions (plus one to the right, one to the
1782 bottom and one to the bottom right) */
1783 /* ... but only roll back scan position if more than one step done */
1793 Debug("game:mm:HitElement", "(2): element == %d", element);
1796 if (LX + 5 * XS < 0 ||
1806 Debug("game:mm:HitElement", "(3): element == %d", element);
1809 if (IS_POLAR(element) &&
1810 ((element - EL_POLAR_START) % 2 ||
1811 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1813 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1815 laser.num_damages--;
1820 if (IS_POLAR_CROSS(element) &&
1821 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1823 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1825 laser.num_damages--;
1830 if (IS_DF_SLOPE(element) && !through_center)
1834 if (hit_mask == HIT_MASK_ALL)
1836 // laser already inside slope -- go back half step
1843 AddLaserEdge(LX, LY);
1845 LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0);
1846 LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0);
1848 else if (!IS_BEAMER(element) &&
1849 !IS_FIBRE_OPTIC(element) &&
1850 !IS_GRID_WOOD(element) &&
1851 element != EL_FUEL_EMPTY)
1854 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1855 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1857 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1860 LX = ELX * TILEX + 14;
1861 LY = ELY * TILEY + 14;
1863 AddLaserEdge(LX, LY);
1866 if (IS_MIRROR(element) ||
1867 IS_MIRROR_FIXED(element) ||
1868 IS_POLAR(element) ||
1869 IS_POLAR_CROSS(element) ||
1870 IS_DF_MIRROR(element) ||
1871 IS_DF_MIRROR_AUTO(element) ||
1872 IS_DF_MIRROR_FIXED(element) ||
1873 IS_DF_SLOPE(element) ||
1874 element == EL_PRISM ||
1875 element == EL_REFRACTOR)
1877 int current_angle = laser.current_angle;
1880 laser.num_damages--;
1882 AddDamagedField(ELX, ELY);
1884 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1887 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1889 if (IS_MIRROR(element) ||
1890 IS_MIRROR_FIXED(element) ||
1891 IS_DF_MIRROR(element) ||
1892 IS_DF_MIRROR_AUTO(element) ||
1893 IS_DF_MIRROR_FIXED(element) ||
1894 IS_DF_SLOPE(element))
1895 laser.current_angle = get_mirrored_angle(laser.current_angle,
1896 get_element_angle(element));
1898 if (element == EL_PRISM || element == EL_REFRACTOR)
1899 laser.current_angle = RND(16);
1901 XS = 2 * Step[laser.current_angle].x;
1902 YS = 2 * Step[laser.current_angle].y;
1906 // start from center position for all game elements but slope
1907 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1912 LX += step_size * XS;
1913 LY += step_size * YS;
1917 // advance laser position until reaching the next tile (slopes)
1918 while (LX / TILEX == ELX && (LX + 2) / TILEX == ELX &&
1919 LY / TILEY == ELY && (LY + 2) / TILEY == ELY)
1926 // draw sparkles on mirror
1927 if ((IS_MIRROR(element) ||
1928 IS_MIRROR_FIXED(element) ||
1929 element == EL_PRISM) &&
1930 current_angle != laser.current_angle)
1932 MovDelay[ELX][ELY] = 11; // start animation
1935 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1936 current_angle != laser.current_angle)
1937 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1940 (get_opposite_angle(laser.current_angle) ==
1941 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1943 if (IS_DF_SLOPE(element))
1945 // handle special cases for slope element
1947 if (IS_45_ANGLE(laser.current_angle))
1951 elx = getLevelFromLaserX(LX);
1952 ely = getLevelFromLaserY(LY);
1954 if (IN_LEV_FIELD(elx, ely))
1956 int element_next = Tile[elx][ely];
1958 // check if slope is followed by slope with opposite orientation
1959 if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2)
1960 laser.overloaded = TRUE;
1963 int nr = element - EL_DF_SLOPE_START;
1964 int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) :
1965 nr == 1 ? (XS > 0 ? TILEX : 1) :
1966 nr == 2 ? (XS > 0 ? TILEX : 1) :
1967 nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0);
1968 int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) :
1969 nr == 1 ? (YS > 0 ? TILEY - 1 : -1) :
1970 nr == 2 ? (YS > 0 ? TILEY : 0) :
1971 nr == 3 ? (YS > 0 ? TILEY : 0) : 0);
1973 int px = ELX * TILEX + dx;
1974 int py = ELY * TILEY + dy;
1979 elx = getLevelFromLaserX(px);
1980 ely = getLevelFromLaserY(py);
1982 if (IN_LEV_FIELD(elx, ely))
1984 int element_side = Tile[elx][ely];
1986 // check if end of slope is blocked by other element
1987 if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side))
1989 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1991 if (element & (1 << pos))
1992 laser.overloaded = TRUE;
1996 int pos = getMaskFromElement(element_side);
1998 if (mm_masks[pos][dx / 2][dy / 2] == 'X')
1999 laser.overloaded = TRUE;
2005 return (laser.overloaded ? TRUE : FALSE);
2008 if (element == EL_FUEL_FULL)
2010 laser.stops_inside_element = TRUE;
2015 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
2017 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2019 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
2020 element == EL_MINE ? EL_MINE_ACTIVE :
2021 EL_GRAY_BALL_ACTIVE);
2023 GfxFrame[ELX][ELY] = 0; // restart animation
2025 laser.dest_element_last = Tile[ELX][ELY];
2026 laser.dest_element_last_x = ELX;
2027 laser.dest_element_last_y = ELY;
2029 if (element == EL_MINE)
2030 laser.overloaded = TRUE;
2033 if (element == EL_KETTLE ||
2034 element == EL_CELL ||
2035 element == EL_KEY ||
2036 element == EL_LIGHTBALL ||
2037 element == EL_PACMAN ||
2038 IS_PACMAN(element) ||
2039 IS_ENVELOPE(element))
2041 if (!IS_PACMAN(element) &&
2042 !IS_ENVELOPE(element))
2045 if (element == EL_PACMAN)
2048 if (element == EL_KETTLE || element == EL_CELL)
2050 if (game_mm.kettles_still_needed > 0)
2051 game_mm.kettles_still_needed--;
2053 game.snapshot.collected_item = TRUE;
2055 if (game_mm.kettles_still_needed == 0)
2059 DrawLaser(0, DL_LASER_ENABLED);
2062 else if (element == EL_KEY)
2066 else if (IS_PACMAN(element))
2068 DeletePacMan(ELX, ELY);
2070 else if (IS_ENVELOPE(element))
2072 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
2075 RaiseScoreElement_MM(element);
2080 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
2082 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2084 DrawLaser(0, DL_LASER_ENABLED);
2086 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
2088 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
2089 game_mm.lights_still_needed--;
2093 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
2094 game_mm.lights_still_needed++;
2097 DrawField_MM(ELX, ELY);
2098 DrawLaser(0, DL_LASER_ENABLED);
2103 laser.stops_inside_element = TRUE;
2109 Debug("game:mm:HitElement", "(4): element == %d", element);
2112 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
2113 laser.num_beamers < MAX_NUM_BEAMERS &&
2114 laser.beamer[BEAMER_NR(element)][1].num)
2116 int beamer_angle = get_element_angle(element);
2117 int beamer_nr = BEAMER_NR(element);
2121 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
2124 laser.num_damages--;
2126 if (IS_FIBRE_OPTIC(element) ||
2127 laser.current_angle == get_opposite_angle(beamer_angle))
2131 LX = ELX * TILEX + 14;
2132 LY = ELY * TILEY + 14;
2134 AddLaserEdge(LX, LY);
2135 AddDamagedField(ELX, ELY);
2137 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2140 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2142 pos = (ELX == laser.beamer[beamer_nr][0].x &&
2143 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
2144 ELX = laser.beamer[beamer_nr][pos].x;
2145 ELY = laser.beamer[beamer_nr][pos].y;
2146 LX = ELX * TILEX + 14;
2147 LY = ELY * TILEY + 14;
2149 if (IS_BEAMER(element))
2151 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
2152 XS = 2 * Step[laser.current_angle].x;
2153 YS = 2 * Step[laser.current_angle].y;
2156 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
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 if (laser.current_angle == (laser.current_angle >> 1) << 1)
2171 LX += step_size * XS;
2172 LY += step_size * YS;
2174 laser.num_beamers++;
2183 static boolean HitOnlyAnEdge(int hit_mask)
2185 // check if the laser hit only the edge of an element and, if so, go on
2188 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
2192 if ((hit_mask == HIT_MASK_TOPLEFT ||
2193 hit_mask == HIT_MASK_TOPRIGHT ||
2194 hit_mask == HIT_MASK_BOTTOMLEFT ||
2195 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
2196 laser.current_angle % 4) // angle is not 90°
2200 if (hit_mask == HIT_MASK_TOPLEFT)
2205 else if (hit_mask == HIT_MASK_TOPRIGHT)
2210 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
2215 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
2221 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
2227 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
2234 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
2240 static boolean HitPolarizer(int element, int hit_mask)
2242 if (HitOnlyAnEdge(hit_mask))
2245 if (IS_DF_GRID(element))
2247 int grid_angle = get_element_angle(element);
2250 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2251 grid_angle, laser.current_angle);
2254 AddLaserEdge(LX, LY);
2255 AddDamagedField(ELX, ELY);
2258 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2260 if (laser.current_angle == grid_angle ||
2261 laser.current_angle == get_opposite_angle(grid_angle))
2263 // skip the whole element before continuing the scan
2269 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2271 if (LX/TILEX > ELX || LY/TILEY > ELY)
2273 /* skipping scan positions to the right and down skips one scan
2274 position too much, because this is only the top left scan position
2275 of totally four scan positions (plus one to the right, one to the
2276 bottom and one to the bottom right) */
2282 AddLaserEdge(LX, LY);
2288 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2290 LX / TILEX, LY / TILEY,
2291 LX % TILEX, LY % TILEY);
2296 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2298 return HitReflectingWalls(element, hit_mask);
2302 return HitAbsorbingWalls(element, hit_mask);
2305 else if (IS_GRID_STEEL(element))
2307 // may be required if graphics for steel grid redefined
2308 AddDamagedField(ELX, ELY);
2310 return HitReflectingWalls(element, hit_mask);
2312 else // IS_GRID_WOOD
2314 // may be required if graphics for wooden grid redefined
2315 AddDamagedField(ELX, ELY);
2317 return HitAbsorbingWalls(element, hit_mask);
2323 static boolean HitBlock(int element, int hit_mask)
2325 boolean check = FALSE;
2327 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2328 game_mm.num_keys == 0)
2331 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2334 int ex = ELX * TILEX + 14;
2335 int ey = ELY * TILEY + 14;
2339 for (i = 1; i < 32; i++)
2344 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2349 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2350 return HitAbsorbingWalls(element, hit_mask);
2354 AddLaserEdge(LX - XS, LY - YS);
2355 AddDamagedField(ELX, ELY);
2358 Box[ELX][ELY] = laser.num_edges;
2360 return HitReflectingWalls(element, hit_mask);
2363 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2365 int xs = XS / 2, ys = YS / 2;
2367 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2368 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2370 laser.overloaded = (element == EL_GATE_STONE);
2375 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2376 (hit_mask == HIT_MASK_TOP ||
2377 hit_mask == HIT_MASK_LEFT ||
2378 hit_mask == HIT_MASK_RIGHT ||
2379 hit_mask == HIT_MASK_BOTTOM))
2380 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2381 hit_mask == HIT_MASK_BOTTOM),
2382 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2383 hit_mask == HIT_MASK_RIGHT));
2384 AddLaserEdge(LX, LY);
2390 if (element == EL_GATE_STONE && Box[ELX][ELY])
2392 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2404 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2406 int xs = XS / 2, ys = YS / 2;
2408 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2409 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2411 laser.overloaded = (element == EL_BLOCK_STONE);
2416 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2417 (hit_mask == HIT_MASK_TOP ||
2418 hit_mask == HIT_MASK_LEFT ||
2419 hit_mask == HIT_MASK_RIGHT ||
2420 hit_mask == HIT_MASK_BOTTOM))
2421 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2422 hit_mask == HIT_MASK_BOTTOM),
2423 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2424 hit_mask == HIT_MASK_RIGHT));
2425 AddDamagedField(ELX, ELY);
2427 LX = ELX * TILEX + 14;
2428 LY = ELY * TILEY + 14;
2430 AddLaserEdge(LX, LY);
2432 laser.stops_inside_element = TRUE;
2440 static boolean HitLaserSource(int element, int hit_mask)
2442 if (HitOnlyAnEdge(hit_mask))
2445 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2447 laser.overloaded = TRUE;
2452 static boolean HitLaserDestination(int element, int hit_mask)
2454 if (HitOnlyAnEdge(hit_mask))
2457 if (element != EL_EXIT_OPEN &&
2458 !(IS_RECEIVER(element) &&
2459 game_mm.kettles_still_needed == 0 &&
2460 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2462 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2467 if (IS_RECEIVER(element) ||
2468 (IS_22_5_ANGLE(laser.current_angle) &&
2469 (ELX != (LX + 6 * XS) / TILEX ||
2470 ELY != (LY + 6 * YS) / TILEY ||
2479 LX = ELX * TILEX + 14;
2480 LY = ELY * TILEY + 14;
2482 laser.stops_inside_element = TRUE;
2485 AddLaserEdge(LX, LY);
2486 AddDamagedField(ELX, ELY);
2488 if (game_mm.lights_still_needed == 0)
2490 game_mm.level_solved = TRUE;
2492 SetTileCursorActive(FALSE);
2498 static boolean HitReflectingWalls(int element, int hit_mask)
2500 // check if laser hits side of a wall with an angle that is not 90°
2501 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2502 hit_mask == HIT_MASK_LEFT ||
2503 hit_mask == HIT_MASK_RIGHT ||
2504 hit_mask == HIT_MASK_BOTTOM))
2506 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2511 if (!IS_DF_GRID(element))
2512 AddLaserEdge(LX, LY);
2514 // check if laser hits wall with an angle of 45°
2515 if (!IS_22_5_ANGLE(laser.current_angle))
2517 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2520 laser.current_angle = get_mirrored_angle(laser.current_angle,
2523 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2526 laser.current_angle = get_mirrored_angle(laser.current_angle,
2530 AddLaserEdge(LX, LY);
2532 XS = 2 * Step[laser.current_angle].x;
2533 YS = 2 * Step[laser.current_angle].y;
2537 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2539 laser.current_angle = get_mirrored_angle(laser.current_angle,
2544 if (!IS_DF_GRID(element))
2545 AddLaserEdge(LX, LY);
2550 if (!IS_DF_GRID(element))
2551 AddLaserEdge(LX, LY + YS / 2);
2554 if (!IS_DF_GRID(element))
2555 AddLaserEdge(LX, LY);
2558 YS = 2 * Step[laser.current_angle].y;
2562 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2564 laser.current_angle = get_mirrored_angle(laser.current_angle,
2569 if (!IS_DF_GRID(element))
2570 AddLaserEdge(LX, LY);
2575 if (!IS_DF_GRID(element))
2576 AddLaserEdge(LX + XS / 2, LY);
2579 if (!IS_DF_GRID(element))
2580 AddLaserEdge(LX, LY);
2583 XS = 2 * Step[laser.current_angle].x;
2589 // reflection at the edge of reflecting DF style wall
2590 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2592 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2593 hit_mask == HIT_MASK_TOPRIGHT) ||
2594 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2595 hit_mask == HIT_MASK_TOPLEFT) ||
2596 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2597 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2598 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2599 hit_mask == HIT_MASK_BOTTOMRIGHT))
2602 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2603 ANG_MIRROR_135 : ANG_MIRROR_45);
2605 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2607 AddDamagedField(ELX, ELY);
2608 AddLaserEdge(LX, LY);
2610 laser.current_angle = get_mirrored_angle(laser.current_angle,
2618 AddLaserEdge(LX, LY);
2624 // reflection inside an edge of reflecting DF style wall
2625 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2627 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2628 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2629 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2630 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2631 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2632 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2633 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2634 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2637 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2638 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2639 ANG_MIRROR_135 : ANG_MIRROR_45);
2641 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2644 AddDamagedField(ELX, ELY);
2647 AddLaserEdge(LX - XS, LY - YS);
2648 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2649 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2651 laser.current_angle = get_mirrored_angle(laser.current_angle,
2659 AddLaserEdge(LX, LY);
2665 // check if laser hits DF style wall with an angle of 90°
2666 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2668 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2669 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2670 (IS_VERT_ANGLE(laser.current_angle) &&
2671 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2673 // laser at last step touched nothing or the same side of the wall
2674 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2676 AddDamagedField(ELX, ELY);
2683 last_hit_mask = hit_mask;
2690 if (!HitOnlyAnEdge(hit_mask))
2692 laser.overloaded = TRUE;
2700 static boolean HitAbsorbingWalls(int element, int hit_mask)
2702 if (HitOnlyAnEdge(hit_mask))
2706 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2708 AddLaserEdge(LX - XS, LY - YS);
2715 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2717 AddLaserEdge(LX - XS, LY - YS);
2723 if (IS_WALL_WOOD(element) ||
2724 IS_DF_WALL_WOOD(element) ||
2725 IS_GRID_WOOD(element) ||
2726 IS_GRID_WOOD_FIXED(element) ||
2727 IS_GRID_WOOD_AUTO(element) ||
2728 element == EL_FUSE_ON ||
2729 element == EL_BLOCK_WOOD ||
2730 element == EL_GATE_WOOD)
2732 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2737 if (IS_WALL_ICE(element))
2743 // check if laser hit adjacent edges of two diagonal tiles
2744 if (ELX != lx / TILEX)
2746 if (ELY != ly / TILEY)
2749 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2750 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2752 // check if laser hits wall with an angle of 90°
2753 if (IS_90_ANGLE(laser.current_angle))
2754 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2756 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2760 for (i = 0; i < 4; i++)
2762 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2763 mask = 15 - (8 >> i);
2764 else if (ABS(XS) == 4 &&
2766 (XS > 0) == (i % 2) &&
2767 (YS < 0) == (i / 2))
2768 mask = 3 + (i / 2) * 9;
2769 else if (ABS(YS) == 4 &&
2771 (XS < 0) == (i % 2) &&
2772 (YS > 0) == (i / 2))
2773 mask = 5 + (i % 2) * 5;
2777 laser.wall_mask = mask;
2779 else if (IS_WALL_AMOEBA(element))
2781 int elx = (LX - 2 * XS) / TILEX;
2782 int ely = (LY - 2 * YS) / TILEY;
2783 int element2 = Tile[elx][ely];
2786 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2788 laser.dest_element = EL_EMPTY;
2796 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2797 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2799 if (IS_90_ANGLE(laser.current_angle))
2800 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2802 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2804 laser.wall_mask = mask;
2810 static void OpenExit(int x, int y)
2814 if (!MovDelay[x][y]) // next animation frame
2815 MovDelay[x][y] = 4 * delay;
2817 if (MovDelay[x][y]) // wait some time before next frame
2822 phase = MovDelay[x][y] / delay;
2824 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2825 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2827 if (!MovDelay[x][y])
2829 Tile[x][y] = EL_EXIT_OPEN;
2835 static void OpenGrayBall(int x, int y)
2839 if (!MovDelay[x][y]) // next animation frame
2841 if (IS_WALL(Store[x][y]))
2843 DrawWalls_MM(x, y, Store[x][y]);
2845 // copy wall tile to spare bitmap for "melting" animation
2846 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2847 TILEX, TILEY, x * TILEX, y * TILEY);
2849 DrawElement_MM(x, y, EL_GRAY_BALL);
2852 MovDelay[x][y] = 50 * delay;
2855 if (MovDelay[x][y]) // wait some time before next frame
2859 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2863 int dx = RND(26), dy = RND(26);
2865 if (IS_WALL(Store[x][y]))
2867 // copy wall tile from spare bitmap for "melting" animation
2868 bitmap = bitmap_db_field;
2874 int graphic = el2gfx(Store[x][y]);
2876 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2879 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2880 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2882 laser.redraw = TRUE;
2884 MarkTileDirty(x, y);
2887 if (!MovDelay[x][y])
2889 Tile[x][y] = Store[x][y];
2890 Store[x][y] = Store2[x][y] = 0;
2891 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2893 InitField(x, y, FALSE);
2896 ScanLaser_FromLastMirror();
2901 static void OpenEnvelope(int x, int y)
2903 int num_frames = 8; // seven frames plus final empty space
2905 if (!MovDelay[x][y]) // next animation frame
2906 MovDelay[x][y] = num_frames;
2908 if (MovDelay[x][y]) // wait some time before next frame
2910 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2914 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2916 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2917 int frame = num_frames - MovDelay[x][y] - 1;
2919 DrawGraphicAnimation_MM(x, y, graphic, frame);
2921 laser.redraw = TRUE;
2924 if (MovDelay[x][y] == 0)
2926 Tile[x][y] = EL_EMPTY;
2937 static void MeltIce(int x, int y)
2942 if (!MovDelay[x][y]) // next animation frame
2943 MovDelay[x][y] = frames * delay;
2945 if (MovDelay[x][y]) // wait some time before next frame
2948 int wall_mask = Store2[x][y];
2949 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2952 phase = frames - MovDelay[x][y] / delay - 1;
2954 if (!MovDelay[x][y])
2956 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2957 Store[x][y] = Store2[x][y] = 0;
2959 DrawWalls_MM(x, y, Tile[x][y]);
2961 if (Tile[x][y] == EL_WALL_ICE_BASE)
2962 Tile[x][y] = EL_EMPTY;
2964 ScanLaser_FromLastMirror();
2966 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2968 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2970 laser.redraw = TRUE;
2975 static void GrowAmoeba(int x, int y)
2980 if (!MovDelay[x][y]) // next animation frame
2981 MovDelay[x][y] = frames * delay;
2983 if (MovDelay[x][y]) // wait some time before next frame
2986 int wall_mask = Store2[x][y];
2987 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2990 phase = MovDelay[x][y] / delay;
2992 if (!MovDelay[x][y])
2994 Tile[x][y] = real_element;
2995 Store[x][y] = Store2[x][y] = 0;
2997 DrawWalls_MM(x, y, Tile[x][y]);
2998 DrawLaser(0, DL_LASER_ENABLED);
3000 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
3002 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
3007 static void DrawFieldAnimated_MM(int x, int y)
3011 laser.redraw = TRUE;
3014 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
3016 int element = Tile[x][y];
3017 int graphic = el2gfx(element);
3019 if (!getGraphicInfo_NewFrame(x, y, graphic))
3024 laser.redraw = TRUE;
3027 static void DrawFieldTwinkle(int x, int y)
3029 if (MovDelay[x][y] != 0) // wait some time before next frame
3035 if (MovDelay[x][y] != 0)
3037 int graphic = IMG_TWINKLE_WHITE;
3038 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
3040 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
3043 laser.redraw = TRUE;
3047 static void Explode_MM(int x, int y, int phase, int mode)
3049 int num_phase = 9, delay = 2;
3050 int last_phase = num_phase * delay;
3051 int half_phase = (num_phase / 2) * delay;
3054 laser.redraw = TRUE;
3056 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
3058 center_element = Tile[x][y];
3060 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
3062 // put moving element to center field (and let it explode there)
3063 center_element = MovingOrBlocked2Element_MM(x, y);
3064 RemoveMovingField_MM(x, y);
3066 Tile[x][y] = center_element;
3069 if (center_element != EL_GRAY_BALL_ACTIVE)
3070 Store[x][y] = EL_EMPTY;
3071 Store2[x][y] = center_element;
3073 Tile[x][y] = EL_EXPLODING_OPAQUE;
3075 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
3076 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
3077 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
3080 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3082 ExplodePhase[x][y] = 1;
3088 GfxFrame[x][y] = 0; // restart explosion animation
3090 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
3092 center_element = Store2[x][y];
3094 if (phase == half_phase && Store[x][y] == EL_EMPTY)
3096 Tile[x][y] = EL_EXPLODING_TRANSP;
3098 if (x == ELX && y == ELY)
3102 if (phase == last_phase)
3104 if (center_element == EL_BOMB_ACTIVE)
3106 DrawLaser(0, DL_LASER_DISABLED);
3109 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3111 laser.overloaded = FALSE;
3113 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
3115 GameOver_MM(GAME_OVER_BOMB);
3118 Tile[x][y] = Store[x][y];
3120 Store[x][y] = Store2[x][y] = 0;
3121 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3123 InitField(x, y, FALSE);
3126 if (center_element == EL_GRAY_BALL_ACTIVE)
3127 ScanLaser_FromLastMirror();
3129 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
3131 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
3132 int frame = getGraphicAnimationFrameXY(graphic, x, y);
3134 DrawGraphicAnimation_MM(x, y, graphic, frame);
3136 MarkTileDirty(x, y);
3140 static void Bang_MM(int x, int y)
3142 int element = Tile[x][y];
3144 if (IS_PACMAN(element))
3145 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3146 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
3147 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3148 else if (element == EL_KEY)
3149 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3151 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3153 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
3156 static void TurnRound(int x, int y)
3168 { 0, 0 }, { 0, 0 }, { 0, 0 },
3173 int left, right, back;
3177 { MV_DOWN, MV_UP, MV_RIGHT },
3178 { MV_UP, MV_DOWN, MV_LEFT },
3180 { MV_LEFT, MV_RIGHT, MV_DOWN },
3184 { MV_RIGHT, MV_LEFT, MV_UP }
3187 int element = Tile[x][y];
3188 int old_move_dir = MovDir[x][y];
3189 int right_dir = turn[old_move_dir].right;
3190 int back_dir = turn[old_move_dir].back;
3191 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
3192 int right_x = x + right_dx, right_y = y + right_dy;
3194 if (element == EL_PACMAN)
3196 boolean can_turn_right = FALSE;
3198 if (IN_LEV_FIELD(right_x, right_y) &&
3199 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
3200 can_turn_right = TRUE;
3203 MovDir[x][y] = right_dir;
3205 MovDir[x][y] = back_dir;
3211 static void StartMoving_MM(int x, int y)
3213 int element = Tile[x][y];
3218 if (CAN_MOVE(element))
3222 if (MovDelay[x][y]) // wait some time before next movement
3230 // now make next step
3232 Moving2Blocked(x, y, &newx, &newy); // get next screen position
3234 if (element == EL_PACMAN &&
3235 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
3236 !ObjHit(newx, newy, HIT_POS_CENTER))
3238 Store[newx][newy] = Tile[newx][newy];
3239 Tile[newx][newy] = EL_EMPTY;
3241 DrawField_MM(newx, newy);
3243 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3244 ObjHit(newx, newy, HIT_POS_CENTER))
3246 // object was running against a wall
3253 InitMovingField_MM(x, y, MovDir[x][y]);
3257 ContinueMoving_MM(x, y);
3260 static void ContinueMoving_MM(int x, int y)
3262 int element = Tile[x][y];
3263 int direction = MovDir[x][y];
3264 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3265 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3266 int horiz_move = (dx!=0);
3267 int newx = x + dx, newy = y + dy;
3268 int step = (horiz_move ? dx : dy) * TILEX / 8;
3270 MovPos[x][y] += step;
3272 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3274 Tile[x][y] = EL_EMPTY;
3275 Tile[newx][newy] = element;
3277 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3278 MovDelay[newx][newy] = 0;
3280 if (!CAN_MOVE(element))
3281 MovDir[newx][newy] = 0;
3284 DrawField_MM(newx, newy);
3286 Stop[newx][newy] = TRUE;
3288 if (element == EL_PACMAN)
3290 if (Store[newx][newy] == EL_BOMB)
3291 Bang_MM(newx, newy);
3293 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3294 (LX + 2 * XS) / TILEX == newx &&
3295 (LY + 2 * YS) / TILEY == newy)
3302 else // still moving on
3307 laser.redraw = TRUE;
3310 boolean ClickElement(int x, int y, int button)
3312 static DelayCounter click_delay = { CLICK_DELAY };
3313 static boolean new_button = TRUE;
3314 boolean element_clicked = FALSE;
3319 // initialize static variables
3320 click_delay.count = 0;
3321 click_delay.value = CLICK_DELAY;
3327 // do not rotate objects hit by the laser after the game was solved
3328 if (game_mm.level_solved && Hit[x][y])
3331 if (button == MB_RELEASED)
3334 click_delay.value = CLICK_DELAY;
3336 // release eventually hold auto-rotating mirror
3337 RotateMirror(x, y, MB_RELEASED);
3342 if (!FrameReached(&click_delay) && !new_button)
3345 if (button == MB_MIDDLEBUTTON) // middle button has no function
3348 if (!IN_LEV_FIELD(x, y))
3351 if (Tile[x][y] == EL_EMPTY)
3354 element = Tile[x][y];
3356 if (IS_MIRROR(element) ||
3357 IS_BEAMER(element) ||
3358 IS_POLAR(element) ||
3359 IS_POLAR_CROSS(element) ||
3360 IS_DF_MIRROR(element) ||
3361 IS_DF_MIRROR_AUTO(element))
3363 RotateMirror(x, y, button);
3365 element_clicked = TRUE;
3367 else if (IS_MCDUFFIN(element))
3369 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3371 if (has_laser && !laser.fuse_off)
3372 DrawLaser(0, DL_LASER_DISABLED);
3374 element = get_rotated_element(element, BUTTON_ROTATION(button));
3376 Tile[x][y] = element;
3381 laser.start_angle = get_element_angle(element);
3385 if (!laser.fuse_off)
3389 element_clicked = TRUE;
3391 else if (element == EL_FUSE_ON && laser.fuse_off)
3393 if (x != laser.fuse_x || y != laser.fuse_y)
3396 laser.fuse_off = FALSE;
3397 laser.fuse_x = laser.fuse_y = -1;
3399 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3402 element_clicked = TRUE;
3404 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3406 laser.fuse_off = TRUE;
3409 laser.overloaded = FALSE;
3411 DrawLaser(0, DL_LASER_DISABLED);
3412 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3414 element_clicked = TRUE;
3416 else if (element == EL_LIGHTBALL)
3419 RaiseScoreElement_MM(element);
3420 DrawLaser(0, DL_LASER_ENABLED);
3422 element_clicked = TRUE;
3424 else if (IS_ENVELOPE(element))
3426 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3428 element_clicked = TRUE;
3431 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3434 return element_clicked;
3437 static void RotateMirror(int x, int y, int button)
3439 if (button == MB_RELEASED)
3441 // release eventually hold auto-rotating mirror
3448 if (IS_MIRROR(Tile[x][y]) ||
3449 IS_POLAR_CROSS(Tile[x][y]) ||
3450 IS_POLAR(Tile[x][y]) ||
3451 IS_BEAMER(Tile[x][y]) ||
3452 IS_DF_MIRROR(Tile[x][y]) ||
3453 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3454 IS_GRID_WOOD_AUTO(Tile[x][y]))
3456 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3458 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3460 if (button == MB_LEFTBUTTON)
3462 // left mouse button only for manual adjustment, no auto-rotating;
3463 // freeze mirror for until mouse button released
3467 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3469 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3473 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3475 int edge = Hit[x][y];
3481 DrawLaser(edge - 1, DL_LASER_DISABLED);
3485 else if (ObjHit(x, y, HIT_POS_CENTER))
3487 int edge = Hit[x][y];
3491 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3496 DrawLaser(edge - 1, DL_LASER_DISABLED);
3503 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3508 if ((IS_BEAMER(Tile[x][y]) ||
3509 IS_POLAR(Tile[x][y]) ||
3510 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3512 if (IS_BEAMER(Tile[x][y]))
3515 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3516 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3529 DrawLaser(0, DL_LASER_ENABLED);
3533 static void AutoRotateMirrors(void)
3537 if (!FrameReached(&rotate_delay))
3540 for (x = 0; x < lev_fieldx; x++)
3542 for (y = 0; y < lev_fieldy; y++)
3544 int element = Tile[x][y];
3546 // do not rotate objects hit by the laser after the game was solved
3547 if (game_mm.level_solved && Hit[x][y])
3550 if (IS_DF_MIRROR_AUTO(element) ||
3551 IS_GRID_WOOD_AUTO(element) ||
3552 IS_GRID_STEEL_AUTO(element) ||
3553 element == EL_REFRACTOR)
3555 RotateMirror(x, y, MB_RIGHTBUTTON);
3557 laser.redraw = TRUE;
3563 static boolean ObjHit(int obx, int oby, int bits)
3570 if (bits & HIT_POS_CENTER)
3572 if (CheckLaserPixel(cSX + obx + 15,
3577 if (bits & HIT_POS_EDGE)
3579 for (i = 0; i < 4; i++)
3580 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3581 cSY + oby + 31 * (i / 2)))
3585 if (bits & HIT_POS_BETWEEN)
3587 for (i = 0; i < 4; i++)
3588 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3589 cSY + 4 + oby + 22 * (i / 2)))
3596 static void DeletePacMan(int px, int py)
3602 if (game_mm.num_pacman <= 1)
3604 game_mm.num_pacman = 0;
3608 for (i = 0; i < game_mm.num_pacman; i++)
3609 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3612 game_mm.num_pacman--;
3614 for (j = i; j < game_mm.num_pacman; j++)
3616 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3617 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3618 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3622 static void GameActions_MM_Ext(void)
3629 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3632 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3634 element = Tile[x][y];
3636 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3637 StartMoving_MM(x, y);
3638 else if (IS_MOVING(x, y))
3639 ContinueMoving_MM(x, y);
3640 else if (IS_EXPLODING(element))
3641 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3642 else if (element == EL_EXIT_OPENING)
3644 else if (element == EL_GRAY_BALL_OPENING)
3646 else if (IS_ENVELOPE_OPENING(element))
3648 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3650 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3652 else if (IS_MIRROR(element) ||
3653 IS_MIRROR_FIXED(element) ||
3654 element == EL_PRISM)
3655 DrawFieldTwinkle(x, y);
3656 else if (element == EL_GRAY_BALL_ACTIVE ||
3657 element == EL_BOMB_ACTIVE ||
3658 element == EL_MINE_ACTIVE)
3659 DrawFieldAnimated_MM(x, y);
3660 else if (!IS_BLOCKED(x, y))
3661 DrawFieldAnimatedIfNeeded_MM(x, y);
3664 AutoRotateMirrors();
3667 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3669 // redraw after Explode_MM() ...
3671 DrawLaser(0, DL_LASER_ENABLED);
3672 laser.redraw = FALSE;
3677 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3681 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3683 DrawLaser(0, DL_LASER_DISABLED);
3688 // skip all following game actions if game is over
3689 if (game_mm.game_over)
3692 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3696 GameOver_MM(GAME_OVER_NO_ENERGY);
3701 if (FrameReached(&energy_delay))
3703 if (game_mm.energy_left > 0)
3704 game_mm.energy_left--;
3706 // when out of energy, wait another frame to play "out of time" sound
3709 element = laser.dest_element;
3712 if (element != Tile[ELX][ELY])
3714 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3715 element, Tile[ELX][ELY]);
3719 if (!laser.overloaded && laser.overload_value == 0 &&
3720 element != EL_BOMB &&
3721 element != EL_BOMB_ACTIVE &&
3722 element != EL_MINE &&
3723 element != EL_MINE_ACTIVE &&
3724 element != EL_GRAY_BALL &&
3725 element != EL_GRAY_BALL_ACTIVE &&
3726 element != EL_BLOCK_STONE &&
3727 element != EL_BLOCK_WOOD &&
3728 element != EL_FUSE_ON &&
3729 element != EL_FUEL_FULL &&
3730 !IS_WALL_ICE(element) &&
3731 !IS_WALL_AMOEBA(element))
3734 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3736 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3737 (!laser.overloaded && laser.overload_value > 0)) &&
3738 FrameReached(&overload_delay))
3740 if (laser.overloaded)
3741 laser.overload_value++;
3743 laser.overload_value--;
3745 if (game_mm.cheat_no_overload)
3747 laser.overloaded = FALSE;
3748 laser.overload_value = 0;
3751 game_mm.laser_overload_value = laser.overload_value;
3753 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3755 SetLaserColor(0xFF);
3757 DrawLaser(0, DL_LASER_ENABLED);
3760 if (!laser.overloaded)
3761 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3762 else if (setup.sound_loops)
3763 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3765 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3767 if (laser.overload_value == MAX_LASER_OVERLOAD)
3769 UpdateAndDisplayGameControlValues();
3773 GameOver_MM(GAME_OVER_OVERLOADED);
3784 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3786 if (game_mm.cheat_no_explosion)
3791 laser.dest_element = EL_EXPLODING_OPAQUE;
3796 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3798 laser.fuse_off = TRUE;
3802 DrawLaser(0, DL_LASER_DISABLED);
3803 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3806 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3808 if (!Store2[ELX][ELY]) // check if content element not yet determined
3810 int last_anim_random_frame = gfx.anim_random_frame;
3813 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3814 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3816 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3817 native_mm_level.ball_choice_mode, 0,
3818 game_mm.ball_choice_pos);
3820 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3821 gfx.anim_random_frame = last_anim_random_frame;
3823 game_mm.ball_choice_pos++;
3825 int new_element = native_mm_level.ball_content[element_pos];
3826 int new_element_base = map_wall_to_base_element(new_element);
3828 if (IS_WALL(new_element_base))
3830 // always use completely filled wall element
3831 new_element = new_element_base | 0x000f;
3833 else if (native_mm_level.rotate_ball_content &&
3834 get_num_elements(new_element) > 1)
3836 // randomly rotate newly created game element
3837 new_element = get_rotated_element(new_element, RND(16));
3840 Store[ELX][ELY] = new_element;
3841 Store2[ELX][ELY] = TRUE;
3844 if (native_mm_level.explode_ball)
3847 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3849 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3854 if (IS_WALL_ICE(element) && CT > 50)
3856 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3858 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3859 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3860 Store2[ELX][ELY] = laser.wall_mask;
3862 laser.dest_element = Tile[ELX][ELY];
3867 if (IS_WALL_AMOEBA(element) && CT > 60)
3870 int element2 = Tile[ELX][ELY];
3872 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3875 for (i = laser.num_damages - 1; i >= 0; i--)
3876 if (laser.damage[i].is_mirror)
3879 r = laser.num_edges;
3880 d = laser.num_damages;
3887 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3890 DrawLaser(0, DL_LASER_ENABLED);
3893 x = laser.damage[k1].x;
3894 y = laser.damage[k1].y;
3899 for (i = 0; i < 4; i++)
3901 if (laser.wall_mask & (1 << i))
3903 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3904 cSY + ELY * TILEY + 31 * (i / 2)))
3907 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3908 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3915 for (i = 0; i < 4; i++)
3917 if (laser.wall_mask & (1 << i))
3919 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3920 cSY + ELY * TILEY + 31 * (i / 2)))
3927 if (laser.num_beamers > 0 ||
3928 k1 < 1 || k2 < 4 || k3 < 4 ||
3929 CheckLaserPixel(cSX + ELX * TILEX + 14,
3930 cSY + ELY * TILEY + 14))
3932 laser.num_edges = r;
3933 laser.num_damages = d;
3935 DrawLaser(0, DL_LASER_DISABLED);
3938 Tile[ELX][ELY] = element | laser.wall_mask;
3940 int x = ELX, y = ELY;
3941 int wall_mask = laser.wall_mask;
3944 DrawLaser(0, DL_LASER_ENABLED);
3946 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3948 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3949 Store[x][y] = EL_WALL_AMOEBA_BASE;
3950 Store2[x][y] = wall_mask;
3955 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3956 laser.stops_inside_element && CT > native_mm_level.time_block)
3961 if (ABS(XS) > ABS(YS))
3968 for (i = 0; i < 4; i++)
3975 x = ELX + Step[k * 4].x;
3976 y = ELY + Step[k * 4].y;
3978 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3981 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3989 laser.overloaded = (element == EL_BLOCK_STONE);
3994 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3997 Tile[x][y] = element;
3999 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
4002 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
4004 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
4005 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
4013 if (element == EL_FUEL_FULL && CT > 10)
4015 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
4016 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
4018 for (i = start; i <= num_init_game_frames; i++)
4020 if (i == num_init_game_frames)
4021 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4022 else if (setup.sound_loops)
4023 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4025 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4027 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
4029 UpdateAndDisplayGameControlValues();
4034 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
4036 DrawField_MM(ELX, ELY);
4038 DrawLaser(0, DL_LASER_ENABLED);
4044 void GameActions_MM(struct MouseActionInfo action)
4046 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
4047 boolean button_released = (action.button == MB_RELEASED);
4049 GameActions_MM_Ext();
4051 CheckSingleStepMode_MM(element_clicked, button_released);
4054 static void MovePacMen(void)
4056 int mx, my, ox, oy, nx, ny;
4060 if (++pacman_nr >= game_mm.num_pacman)
4063 game_mm.pacman[pacman_nr].dir--;
4065 for (l = 1; l < 5; l++)
4067 game_mm.pacman[pacman_nr].dir++;
4069 if (game_mm.pacman[pacman_nr].dir > 4)
4070 game_mm.pacman[pacman_nr].dir = 1;
4072 if (game_mm.pacman[pacman_nr].dir % 2)
4075 my = game_mm.pacman[pacman_nr].dir - 2;
4080 mx = 3 - game_mm.pacman[pacman_nr].dir;
4083 ox = game_mm.pacman[pacman_nr].x;
4084 oy = game_mm.pacman[pacman_nr].y;
4087 element = Tile[nx][ny];
4089 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
4092 if (!IS_EATABLE4PACMAN(element))
4095 if (ObjHit(nx, ny, HIT_POS_CENTER))
4098 Tile[ox][oy] = EL_EMPTY;
4100 EL_PACMAN_RIGHT - 1 +
4101 (game_mm.pacman[pacman_nr].dir - 1 +
4102 (game_mm.pacman[pacman_nr].dir % 2) * 2);
4104 game_mm.pacman[pacman_nr].x = nx;
4105 game_mm.pacman[pacman_nr].y = ny;
4107 DrawGraphic_MM(ox, oy, IMG_EMPTY);
4109 if (element != EL_EMPTY)
4111 int graphic = el2gfx(Tile[nx][ny]);
4116 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
4119 ox = cSX + ox * TILEX;
4120 oy = cSY + oy * TILEY;
4122 for (i = 1; i < 33; i += 2)
4123 BlitBitmap(bitmap, window,
4124 src_x, src_y, TILEX, TILEY,
4125 ox + i * mx, oy + i * my);
4126 Ct = Ct + FrameCounter - CT;
4129 DrawField_MM(nx, ny);
4132 if (!laser.fuse_off)
4134 DrawLaser(0, DL_LASER_ENABLED);
4136 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
4138 AddDamagedField(nx, ny);
4140 laser.damage[laser.num_damages - 1].edge = 0;
4144 if (element == EL_BOMB)
4145 DeletePacMan(nx, ny);
4147 if (IS_WALL_AMOEBA(element) &&
4148 (LX + 2 * XS) / TILEX == nx &&
4149 (LY + 2 * YS) / TILEY == ny)
4159 static void InitMovingField_MM(int x, int y, int direction)
4161 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4162 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4164 MovDir[x][y] = direction;
4165 MovDir[newx][newy] = direction;
4167 if (Tile[newx][newy] == EL_EMPTY)
4168 Tile[newx][newy] = EL_BLOCKED;
4171 static int MovingOrBlocked2Element_MM(int x, int y)
4173 int element = Tile[x][y];
4175 if (element == EL_BLOCKED)
4179 Blocked2Moving(x, y, &oldx, &oldy);
4181 return Tile[oldx][oldy];
4187 static void RemoveMovingField_MM(int x, int y)
4189 int oldx = x, oldy = y, newx = x, newy = y;
4191 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4194 if (IS_MOVING(x, y))
4196 Moving2Blocked(x, y, &newx, &newy);
4197 if (Tile[newx][newy] != EL_BLOCKED)
4200 else if (Tile[x][y] == EL_BLOCKED)
4202 Blocked2Moving(x, y, &oldx, &oldy);
4203 if (!IS_MOVING(oldx, oldy))
4207 Tile[oldx][oldy] = EL_EMPTY;
4208 Tile[newx][newy] = EL_EMPTY;
4209 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4210 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4212 DrawLevelField_MM(oldx, oldy);
4213 DrawLevelField_MM(newx, newy);
4216 static void RaiseScore_MM(int value)
4218 game_mm.score += value;
4221 void RaiseScoreElement_MM(int element)
4226 case EL_PACMAN_RIGHT:
4228 case EL_PACMAN_LEFT:
4229 case EL_PACMAN_DOWN:
4230 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4234 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4239 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4243 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4252 // ----------------------------------------------------------------------------
4253 // Mirror Magic game engine snapshot handling functions
4254 // ----------------------------------------------------------------------------
4256 void SaveEngineSnapshotValues_MM(void)
4260 engine_snapshot_mm.game_mm = game_mm;
4261 engine_snapshot_mm.laser = laser;
4263 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4265 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4267 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4268 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4269 engine_snapshot_mm.Box[x][y] = Box[x][y];
4270 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4274 engine_snapshot_mm.LX = LX;
4275 engine_snapshot_mm.LY = LY;
4276 engine_snapshot_mm.XS = XS;
4277 engine_snapshot_mm.YS = YS;
4278 engine_snapshot_mm.ELX = ELX;
4279 engine_snapshot_mm.ELY = ELY;
4280 engine_snapshot_mm.CT = CT;
4281 engine_snapshot_mm.Ct = Ct;
4283 engine_snapshot_mm.last_LX = last_LX;
4284 engine_snapshot_mm.last_LY = last_LY;
4285 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4286 engine_snapshot_mm.hold_x = hold_x;
4287 engine_snapshot_mm.hold_y = hold_y;
4288 engine_snapshot_mm.pacman_nr = pacman_nr;
4290 engine_snapshot_mm.rotate_delay = rotate_delay;
4291 engine_snapshot_mm.pacman_delay = pacman_delay;
4292 engine_snapshot_mm.energy_delay = energy_delay;
4293 engine_snapshot_mm.overload_delay = overload_delay;
4296 void LoadEngineSnapshotValues_MM(void)
4300 // stored engine snapshot buffers already restored at this point
4302 game_mm = engine_snapshot_mm.game_mm;
4303 laser = engine_snapshot_mm.laser;
4305 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4307 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4309 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4310 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4311 Box[x][y] = engine_snapshot_mm.Box[x][y];
4312 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4316 LX = engine_snapshot_mm.LX;
4317 LY = engine_snapshot_mm.LY;
4318 XS = engine_snapshot_mm.XS;
4319 YS = engine_snapshot_mm.YS;
4320 ELX = engine_snapshot_mm.ELX;
4321 ELY = engine_snapshot_mm.ELY;
4322 CT = engine_snapshot_mm.CT;
4323 Ct = engine_snapshot_mm.Ct;
4325 last_LX = engine_snapshot_mm.last_LX;
4326 last_LY = engine_snapshot_mm.last_LY;
4327 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4328 hold_x = engine_snapshot_mm.hold_x;
4329 hold_y = engine_snapshot_mm.hold_y;
4330 pacman_nr = engine_snapshot_mm.pacman_nr;
4332 rotate_delay = engine_snapshot_mm.rotate_delay;
4333 pacman_delay = engine_snapshot_mm.pacman_delay;
4334 energy_delay = engine_snapshot_mm.energy_delay;
4335 overload_delay = engine_snapshot_mm.overload_delay;
4337 RedrawPlayfield_MM();
4340 static int getAngleFromTouchDelta(int dx, int dy, int base)
4342 double pi = 3.141592653;
4343 double rad = atan2((double)-dy, (double)dx);
4344 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4345 double deg = rad2 * 180.0 / pi;
4347 return (int)(deg * base / 360.0 + 0.5) % base;
4350 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4352 // calculate start (source) position to be at the middle of the tile
4353 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4354 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4355 int dx = dst_mx - src_mx;
4356 int dy = dst_my - src_my;
4365 if (!IN_LEV_FIELD(x, y))
4368 element = Tile[x][y];
4370 if (!IS_MCDUFFIN(element) &&
4371 !IS_MIRROR(element) &&
4372 !IS_BEAMER(element) &&
4373 !IS_POLAR(element) &&
4374 !IS_POLAR_CROSS(element) &&
4375 !IS_DF_MIRROR(element))
4378 angle_old = get_element_angle(element);
4380 if (IS_MCDUFFIN(element))
4382 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4383 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4384 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4385 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4388 else if (IS_MIRROR(element) ||
4389 IS_DF_MIRROR(element))
4391 for (i = 0; i < laser.num_damages; i++)
4393 if (laser.damage[i].x == x &&
4394 laser.damage[i].y == y &&
4395 ObjHit(x, y, HIT_POS_CENTER))
4397 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4398 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4405 if (angle_new == -1)
4407 if (IS_MIRROR(element) ||
4408 IS_DF_MIRROR(element) ||
4412 if (IS_POLAR_CROSS(element))
4415 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4418 button = (angle_new == angle_old ? 0 :
4419 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4420 MB_LEFTBUTTON : MB_RIGHTBUTTON);