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();
841 if (setup.quick_doors)
851 if (setup.quick_doors)
857 if (game_mm.kettles_still_needed == 0)
860 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
861 SetTileCursorActive(TRUE);
863 // restart all delay counters after initially cycling game elements
864 ResetFrameCounter(&rotate_delay);
865 ResetFrameCounter(&pacman_delay);
866 ResetFrameCounter(&energy_delay);
867 ResetFrameCounter(&overload_delay);
870 static void FadeOutLaser(void)
874 for (i = 15; i >= 0; i--)
876 SetLaserColor(0x11 * i);
878 DrawLaser(0, DL_LASER_ENABLED);
881 Delay_WithScreenUpdates(50);
884 DrawLaser(0, DL_LASER_DISABLED);
886 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
889 static void GameOver_MM(int game_over_cause)
891 game_mm.game_over = TRUE;
892 game_mm.game_over_cause = game_over_cause;
893 game_mm.game_over_message = (game_mm.has_mcduffin ?
894 (game_over_cause == GAME_OVER_BOMB ?
895 "Bomb killed Mc Duffin!" :
896 game_over_cause == GAME_OVER_NO_ENERGY ?
897 "Out of magic energy!" :
898 game_over_cause == GAME_OVER_OVERLOADED ?
899 "Magic spell hit Mc Duffin!" :
901 (game_over_cause == GAME_OVER_BOMB ?
902 "Bomb destroyed laser cannon!" :
903 game_over_cause == GAME_OVER_NO_ENERGY ?
904 "Out of laser energy!" :
905 game_over_cause == GAME_OVER_OVERLOADED ?
906 "Laser beam hit laser cannon!" :
909 SetTileCursorActive(FALSE);
912 static void AddLaserEdge(int lx, int ly)
914 int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
915 int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
917 // check if laser is still inside visible playfield area (or inside level)
918 if (cSX + lx < REAL_SX || cSX + lx >= REAL_SX + full_sxsize ||
919 cSY + ly < REAL_SY || cSY + ly >= REAL_SY + full_sysize)
921 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
926 laser.edge[laser.num_edges].x = cSX2 + lx;
927 laser.edge[laser.num_edges].y = cSY2 + ly;
933 static void AddDamagedField(int ex, int ey)
935 // prevent adding the same field position again
936 if (laser.num_damages > 0 &&
937 laser.damage[laser.num_damages - 1].x == ex &&
938 laser.damage[laser.num_damages - 1].y == ey &&
939 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
942 laser.damage[laser.num_damages].is_mirror = FALSE;
943 laser.damage[laser.num_damages].angle = laser.current_angle;
944 laser.damage[laser.num_damages].edge = laser.num_edges;
945 laser.damage[laser.num_damages].x = ex;
946 laser.damage[laser.num_damages].y = ey;
950 static boolean StepBehind(void)
956 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
957 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
959 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
965 static int getMaskFromElement(int element)
967 if (IS_MCDUFFIN(element))
968 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
969 else if (IS_GRID(element))
970 return MM_MASK_GRID_1 + get_element_phase(element);
971 else if (IS_DF_GRID(element))
972 return MM_MASK_RECTANGLE;
973 else if (IS_DF_SLOPE(element))
974 return MM_MASK_SLOPE_1 + get_element_phase(element);
975 else if (IS_RECTANGLE(element))
976 return MM_MASK_RECTANGLE;
978 return MM_MASK_CIRCLE;
981 static int getLevelFromLaserX(int x)
983 return x / TILEX - (x < 0 ? 1 : 0); // correct negative values
986 static int getLevelFromLaserY(int y)
988 return y / TILEY - (y < 0 ? 1 : 0); // correct negative values
991 static int ScanPixel(void)
996 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
997 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
1000 // follow laser beam until it hits something (at least the screen border)
1001 while (hit_mask == HIT_MASK_NO_HIT)
1007 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
1008 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
1010 Debug("game:mm:ScanPixel", "touched screen border!");
1012 return HIT_MASK_ALL;
1016 // check if laser scan has crossed element boundaries (not just mini tiles)
1017 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1018 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1020 if (cross_x && cross_y)
1022 int elx1 = (LX - XS) / TILEX;
1023 int ely1 = (LY + YS) / TILEY;
1024 int elx2 = (LX + XS) / TILEX;
1025 int ely2 = (LY - YS) / TILEY;
1027 // add element corners left and right from the laser beam to damage list
1029 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
1030 AddDamagedField(elx1, ely1);
1032 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
1033 AddDamagedField(elx2, ely2);
1036 for (i = 0; i < 4; i++)
1038 int px = LX + (i % 2) * 2;
1039 int py = LY + (i / 2) * 2;
1040 int dx = px % TILEX;
1041 int dy = py % TILEY;
1042 int lx = getLevelFromLaserX(px);
1043 int ly = getLevelFromLaserY(py);
1046 if (IN_LEV_FIELD(lx, ly))
1048 int element = Tile[lx][ly];
1050 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
1054 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
1056 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1058 pixel = ((element & (1 << pos)) ? 1 : 0);
1062 int pos = getMaskFromElement(element);
1064 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
1069 // check if laser is still inside visible playfield area
1070 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
1071 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
1074 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
1075 hit_mask |= (1 << i);
1078 if (hit_mask == HIT_MASK_NO_HIT)
1080 // hit nothing -- go on with another step
1089 static void DeactivateLaserTargetElement(void)
1091 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1092 laser.dest_element_last == EL_MINE_ACTIVE ||
1093 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1094 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1096 int x = laser.dest_element_last_x;
1097 int y = laser.dest_element_last_y;
1098 int element = laser.dest_element_last;
1100 if (Tile[x][y] == element)
1101 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1102 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1104 if (Tile[x][y] == EL_GRAY_BALL)
1107 laser.dest_element_last = EL_EMPTY;
1108 laser.dest_element_last_x = -1;
1109 laser.dest_element_last_y = -1;
1113 static void ScanLaser(void)
1115 int element = EL_EMPTY;
1116 int last_element = EL_EMPTY;
1117 int end = 0, rf = laser.num_edges;
1119 // do not scan laser again after the game was lost for whatever reason
1120 if (game_mm.game_over)
1123 // do not scan laser if fuse is off
1127 DeactivateLaserTargetElement();
1129 laser.overloaded = FALSE;
1130 laser.stops_inside_element = FALSE;
1132 DrawLaser(0, DL_LASER_ENABLED);
1135 Debug("game:mm:ScanLaser",
1136 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1144 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1147 laser.overloaded = TRUE;
1152 hit_mask = ScanPixel();
1155 Debug("game:mm:ScanLaser",
1156 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1160 // check if laser scan has hit two diagonally adjacent element corners
1161 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1162 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1164 // check if laser scan has crossed element boundaries (not just mini tiles)
1165 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1166 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1168 if (cross_x || cross_y)
1170 // hit something at next tile -- check out what it was
1171 ELX = getLevelFromLaserX(LX + XS);
1172 ELY = getLevelFromLaserY(LY + YS);
1176 // hit something at same tile -- check out what it was
1177 ELX = getLevelFromLaserX(LX);
1178 ELY = getLevelFromLaserY(LY);
1182 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1183 hit_mask, LX, LY, ELX, ELY);
1186 if (!IN_LEV_FIELD(ELX, ELY))
1188 // laser next step position
1189 int x = cSX + LX + XS;
1190 int y = cSY + LY + YS;
1192 // check if next step of laser is still inside visible playfield area
1193 if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
1194 y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
1196 // go on with another step
1204 laser.dest_element = element;
1209 boolean diagonally_adjacent_hit = FALSE;
1211 // handle special case of laser hitting two diagonally adjacent elements
1212 // (with or without a third corner element behind these two elements)
1213 if ((diag_1 || diag_2) && cross_x && cross_y)
1215 diagonally_adjacent_hit = TRUE;
1217 // compare the two diagonally adjacent elements
1219 int yoffset = 2 * (diag_1 ? -1 : +1);
1220 int elx1 = (LX - xoffset) / TILEX;
1221 int ely1 = (LY + yoffset) / TILEY;
1222 int elx2 = (LX + xoffset) / TILEX;
1223 int ely2 = (LY - yoffset) / TILEY;
1224 int e1 = Tile[elx1][ely1];
1225 int e2 = Tile[elx2][ely2];
1226 boolean use_element_1 = FALSE;
1228 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1230 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1231 use_element_1 = (RND(2) ? TRUE : FALSE);
1232 else if (IS_WALL_ICE(e1))
1233 use_element_1 = TRUE;
1235 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1237 // if both tiles match, we can just select the first one
1238 if (IS_WALL_AMOEBA(e1))
1239 use_element_1 = TRUE;
1241 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1243 // if both tiles match, we can just select the first one
1244 if (IS_ABSORBING_BLOCK(e1))
1245 use_element_1 = TRUE;
1248 ELX = (use_element_1 ? elx1 : elx2);
1249 ELY = (use_element_1 ? ely1 : ely2);
1253 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1254 hit_mask, LX, LY, ELX, ELY);
1257 last_element = element;
1259 element = Tile[ELX][ELY];
1260 laser.dest_element = element;
1263 Debug("game:mm:ScanLaser",
1264 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1267 LX % TILEX, LY % TILEY,
1272 if (!IN_LEV_FIELD(ELX, ELY))
1273 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1277 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1278 if (element == EL_EMPTY &&
1279 IS_GRID_STEEL(last_element) &&
1280 laser.current_angle % 4) // angle is not 90°
1281 element = last_element;
1283 if (element == EL_EMPTY)
1285 if (!HitOnlyAnEdge(hit_mask))
1288 else if (element == EL_FUSE_ON)
1290 if (HitPolarizer(element, hit_mask))
1293 else if (IS_GRID(element) || IS_DF_GRID(element))
1295 if (HitPolarizer(element, hit_mask))
1298 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1299 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1301 if (HitBlock(element, hit_mask))
1308 else if (IS_MCDUFFIN(element))
1310 if (HitLaserSource(element, hit_mask))
1313 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1314 IS_RECEIVER(element))
1316 if (HitLaserDestination(element, hit_mask))
1319 else if (IS_WALL(element))
1321 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1323 if (HitReflectingWalls(element, hit_mask))
1328 if (HitAbsorbingWalls(element, hit_mask))
1332 else if (IS_DF_SLOPE(element))
1334 if (diagonally_adjacent_hit)
1336 laser.overloaded = TRUE;
1341 if (hit_mask == HIT_MASK_LEFT ||
1342 hit_mask == HIT_MASK_RIGHT ||
1343 hit_mask == HIT_MASK_TOP ||
1344 hit_mask == HIT_MASK_BOTTOM)
1346 if (HitReflectingWalls(element, hit_mask))
1351 if (HitElement(element, hit_mask))
1357 if (HitElement(element, hit_mask))
1362 DrawLaser(rf - 1, DL_LASER_ENABLED);
1363 rf = laser.num_edges;
1365 if (!IS_DF_WALL_STEEL(element))
1367 // only used for scanning DF steel walls; reset for all other elements
1375 if (laser.dest_element != Tile[ELX][ELY])
1377 Debug("game:mm:ScanLaser",
1378 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1379 laser.dest_element, Tile[ELX][ELY]);
1383 if (!end && !laser.stops_inside_element && !StepBehind())
1386 Debug("game:mm:ScanLaser", "Go one step back");
1392 AddLaserEdge(LX, LY);
1396 DrawLaser(rf - 1, DL_LASER_ENABLED);
1398 Ct = CT = FrameCounter;
1401 if (!IN_LEV_FIELD(ELX, ELY))
1402 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1406 static void ScanLaser_FromLastMirror(void)
1408 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1411 for (i = start_pos; i >= 0; i--)
1412 if (laser.damage[i].is_mirror)
1415 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1417 DrawLaser(start_edge, DL_LASER_DISABLED);
1422 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1428 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1429 start_edge, num_edges, mode);
1434 Warn("DrawLaserExt: start_edge < 0");
1441 Warn("DrawLaserExt: num_edges < 0");
1447 if (mode == DL_LASER_DISABLED)
1449 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1453 // now draw the laser to the backbuffer and (if enabled) to the screen
1454 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1456 redraw_mask |= REDRAW_FIELD;
1458 if (mode == DL_LASER_ENABLED)
1461 // after the laser was deleted, the "damaged" graphics must be restored
1462 if (laser.num_damages)
1464 int damage_start = 0;
1467 // determine the starting edge, from which graphics need to be restored
1470 for (i = 0; i < laser.num_damages; i++)
1472 if (laser.damage[i].edge == start_edge + 1)
1481 // restore graphics from this starting edge to the end of damage list
1482 for (i = damage_start; i < laser.num_damages; i++)
1484 int lx = laser.damage[i].x;
1485 int ly = laser.damage[i].y;
1486 int element = Tile[lx][ly];
1488 if (Hit[lx][ly] == laser.damage[i].edge)
1489 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1492 if (Box[lx][ly] == laser.damage[i].edge)
1495 if (IS_DRAWABLE(element))
1496 DrawField_MM(lx, ly);
1499 elx = laser.damage[damage_start].x;
1500 ely = laser.damage[damage_start].y;
1501 element = Tile[elx][ely];
1504 if (IS_BEAMER(element))
1508 for (i = 0; i < laser.num_beamers; i++)
1509 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1511 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1512 mode, elx, ely, Hit[elx][ely], start_edge);
1513 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1514 get_element_angle(element), laser.damage[damage_start].angle);
1518 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1519 laser.num_beamers > 0 &&
1520 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1522 // element is outgoing beamer
1523 laser.num_damages = damage_start + 1;
1525 if (IS_BEAMER(element))
1526 laser.current_angle = get_element_angle(element);
1530 // element is incoming beamer or other element
1531 laser.num_damages = damage_start;
1532 laser.current_angle = laser.damage[laser.num_damages].angle;
1537 // no damages but McDuffin himself (who needs to be redrawn anyway)
1539 elx = laser.start_edge.x;
1540 ely = laser.start_edge.y;
1541 element = Tile[elx][ely];
1544 laser.num_edges = start_edge + 1;
1545 if (start_edge == 0)
1546 laser.current_angle = laser.start_angle;
1548 LX = laser.edge[start_edge].x - cSX2;
1549 LY = laser.edge[start_edge].y - cSY2;
1550 XS = 2 * Step[laser.current_angle].x;
1551 YS = 2 * Step[laser.current_angle].y;
1554 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1560 if (IS_BEAMER(element) ||
1561 IS_FIBRE_OPTIC(element) ||
1562 IS_PACMAN(element) ||
1563 IS_POLAR(element) ||
1564 IS_POLAR_CROSS(element) ||
1565 element == EL_FUSE_ON)
1570 Debug("game:mm:DrawLaserExt", "element == %d", element);
1573 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1574 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1578 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1579 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1580 (laser.num_beamers == 0 ||
1581 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1583 // element is incoming beamer or other element
1584 step_size = -step_size;
1589 if (IS_BEAMER(element))
1590 Debug("game:mm:DrawLaserExt",
1591 "start_edge == %d, laser.beamer_edge == %d",
1592 start_edge, laser.beamer_edge);
1595 LX += step_size * XS;
1596 LY += step_size * YS;
1598 else if (element != EL_EMPTY)
1607 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1612 void DrawLaser(int start_edge, int mode)
1614 // do not draw laser if fuse is off
1615 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1618 if (mode == DL_LASER_DISABLED)
1619 DeactivateLaserTargetElement();
1621 if (laser.num_edges - start_edge < 0)
1623 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1628 // check if laser is interrupted by beamer element
1629 if (laser.num_beamers > 0 &&
1630 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1632 if (mode == DL_LASER_ENABLED)
1635 int tmp_start_edge = start_edge;
1637 // draw laser segments forward from the start to the last beamer
1638 for (i = 0; i < laser.num_beamers; i++)
1640 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1642 if (tmp_num_edges <= 0)
1646 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1647 i, laser.beamer_edge[i], tmp_start_edge);
1650 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1652 tmp_start_edge = laser.beamer_edge[i];
1655 // draw last segment from last beamer to the end
1656 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1662 int last_num_edges = laser.num_edges;
1663 int num_beamers = laser.num_beamers;
1665 // delete laser segments backward from the end to the first beamer
1666 for (i = num_beamers - 1; i >= 0; i--)
1668 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1670 if (laser.beamer_edge[i] - start_edge <= 0)
1673 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1675 last_num_edges = laser.beamer_edge[i];
1676 laser.num_beamers--;
1680 if (last_num_edges - start_edge <= 0)
1681 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1682 last_num_edges, start_edge);
1685 // special case when rotating first beamer: delete laser edge on beamer
1686 // (but do not start scanning on previous edge to prevent mirror sound)
1687 if (last_num_edges - start_edge == 1 && start_edge > 0)
1688 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1690 // delete first segment from start to the first beamer
1691 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1696 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1699 game_mm.laser_enabled = mode;
1702 void DrawLaser_MM(void)
1704 DrawLaser(0, game_mm.laser_enabled);
1707 static boolean HitElement(int element, int hit_mask)
1709 if (IS_DF_SLOPE(element))
1711 int mirrored_angle = get_mirrored_angle(laser.current_angle,
1712 get_element_angle(element));
1713 int opposite_angle = get_opposite_angle(laser.current_angle);
1715 // check if laser is reflected by slope by 180°
1716 if (mirrored_angle == opposite_angle)
1721 AddDamagedField(LX / TILEX, LY / TILEY);
1723 laser.overloaded = TRUE;
1730 if (HitOnlyAnEdge(hit_mask))
1734 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1735 element = MovingOrBlocked2Element_MM(ELX, ELY);
1738 Debug("game:mm:HitElement", "(1): element == %d", element);
1742 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1743 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1746 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1750 AddDamagedField(ELX, ELY);
1752 boolean through_center = ((ELX * TILEX + 14 - LX) * YS ==
1753 (ELY * TILEY + 14 - LY) * XS);
1755 // this is more precise: check if laser would go through the center
1756 if (!IS_DF_SLOPE(element) && !through_center)
1760 // prevent cutting through laser emitter with laser beam
1761 if (IS_LASER(element))
1764 // skip the whole element before continuing the scan
1772 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1774 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1776 /* skipping scan positions to the right and down skips one scan
1777 position too much, because this is only the top left scan position
1778 of totally four scan positions (plus one to the right, one to the
1779 bottom and one to the bottom right) */
1780 /* ... but only roll back scan position if more than one step done */
1790 Debug("game:mm:HitElement", "(2): element == %d", element);
1793 if (LX + 5 * XS < 0 ||
1803 Debug("game:mm:HitElement", "(3): element == %d", element);
1806 if (IS_POLAR(element) &&
1807 ((element - EL_POLAR_START) % 2 ||
1808 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1810 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1812 laser.num_damages--;
1817 if (IS_POLAR_CROSS(element) &&
1818 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1820 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1822 laser.num_damages--;
1827 if (IS_DF_SLOPE(element) && !through_center)
1831 if (hit_mask == HIT_MASK_ALL)
1833 // laser already inside slope -- go back half step
1840 AddLaserEdge(LX, LY);
1842 LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0);
1843 LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0);
1845 else if (!IS_BEAMER(element) &&
1846 !IS_FIBRE_OPTIC(element) &&
1847 !IS_GRID_WOOD(element) &&
1848 element != EL_FUEL_EMPTY)
1851 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1852 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1854 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1857 LX = ELX * TILEX + 14;
1858 LY = ELY * TILEY + 14;
1860 AddLaserEdge(LX, LY);
1863 if (IS_MIRROR(element) ||
1864 IS_MIRROR_FIXED(element) ||
1865 IS_POLAR(element) ||
1866 IS_POLAR_CROSS(element) ||
1867 IS_DF_MIRROR(element) ||
1868 IS_DF_MIRROR_AUTO(element) ||
1869 IS_DF_MIRROR_FIXED(element) ||
1870 IS_DF_SLOPE(element) ||
1871 element == EL_PRISM ||
1872 element == EL_REFRACTOR)
1874 int current_angle = laser.current_angle;
1877 laser.num_damages--;
1879 AddDamagedField(ELX, ELY);
1881 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1884 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1886 if (IS_MIRROR(element) ||
1887 IS_MIRROR_FIXED(element) ||
1888 IS_DF_MIRROR(element) ||
1889 IS_DF_MIRROR_AUTO(element) ||
1890 IS_DF_MIRROR_FIXED(element) ||
1891 IS_DF_SLOPE(element))
1892 laser.current_angle = get_mirrored_angle(laser.current_angle,
1893 get_element_angle(element));
1895 if (element == EL_PRISM || element == EL_REFRACTOR)
1896 laser.current_angle = RND(16);
1898 XS = 2 * Step[laser.current_angle].x;
1899 YS = 2 * Step[laser.current_angle].y;
1901 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1906 LX += step_size * XS;
1907 LY += step_size * YS;
1909 // draw sparkles on mirror
1910 if ((IS_MIRROR(element) ||
1911 IS_MIRROR_FIXED(element) ||
1912 element == EL_PRISM) &&
1913 current_angle != laser.current_angle)
1915 MovDelay[ELX][ELY] = 11; // start animation
1918 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1919 current_angle != laser.current_angle)
1920 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1923 (get_opposite_angle(laser.current_angle) ==
1924 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1926 if (IS_DF_SLOPE(element))
1928 // handle special cases for slope element
1930 if (IS_45_ANGLE(laser.current_angle))
1934 elx = getLevelFromLaserX(LX);
1935 ely = getLevelFromLaserY(LY);
1937 if (IN_LEV_FIELD(elx, ely))
1939 int element_next = Tile[elx][ely];
1941 // check if slope is followed by slope with opposite orientation
1942 if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2)
1943 laser.overloaded = TRUE;
1946 int nr = element - EL_DF_SLOPE_START;
1947 int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) :
1948 nr == 1 ? (XS > 0 ? TILEX : 1) :
1949 nr == 2 ? (XS > 0 ? TILEX : 1) :
1950 nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0);
1951 int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) :
1952 nr == 1 ? (YS > 0 ? TILEY - 1 : -1) :
1953 nr == 2 ? (YS > 0 ? TILEY : 0) :
1954 nr == 3 ? (YS > 0 ? TILEY : 0) : 0);
1956 int px = ELX * TILEX + dx;
1957 int py = ELY * TILEY + dy;
1962 elx = getLevelFromLaserX(px);
1963 ely = getLevelFromLaserY(py);
1965 if (IN_LEV_FIELD(elx, ely))
1967 int element_side = Tile[elx][ely];
1969 // check if end of slope is blocked by other element
1970 if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side))
1972 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1974 if (element & (1 << pos))
1975 laser.overloaded = TRUE;
1979 int pos = getMaskFromElement(element_side);
1981 if (mm_masks[pos][dx / 2][dx / 2] == 'X')
1982 laser.overloaded = TRUE;
1988 return (laser.overloaded ? TRUE : FALSE);
1991 if (element == EL_FUEL_FULL)
1993 laser.stops_inside_element = TRUE;
1998 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
2000 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2002 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
2003 element == EL_MINE ? EL_MINE_ACTIVE :
2004 EL_GRAY_BALL_ACTIVE);
2006 GfxFrame[ELX][ELY] = 0; // restart animation
2008 laser.dest_element_last = Tile[ELX][ELY];
2009 laser.dest_element_last_x = ELX;
2010 laser.dest_element_last_y = ELY;
2012 if (element == EL_MINE)
2013 laser.overloaded = TRUE;
2016 if (element == EL_KETTLE ||
2017 element == EL_CELL ||
2018 element == EL_KEY ||
2019 element == EL_LIGHTBALL ||
2020 element == EL_PACMAN ||
2021 IS_PACMAN(element) ||
2022 IS_ENVELOPE(element))
2024 if (!IS_PACMAN(element) &&
2025 !IS_ENVELOPE(element))
2028 if (element == EL_PACMAN)
2031 if (element == EL_KETTLE || element == EL_CELL)
2033 if (game_mm.kettles_still_needed > 0)
2034 game_mm.kettles_still_needed--;
2036 game.snapshot.collected_item = TRUE;
2038 if (game_mm.kettles_still_needed == 0)
2042 DrawLaser(0, DL_LASER_ENABLED);
2045 else if (element == EL_KEY)
2049 else if (IS_PACMAN(element))
2051 DeletePacMan(ELX, ELY);
2053 else if (IS_ENVELOPE(element))
2055 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
2058 RaiseScoreElement_MM(element);
2063 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
2065 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2067 DrawLaser(0, DL_LASER_ENABLED);
2069 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
2071 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
2072 game_mm.lights_still_needed--;
2076 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
2077 game_mm.lights_still_needed++;
2080 DrawField_MM(ELX, ELY);
2081 DrawLaser(0, DL_LASER_ENABLED);
2086 laser.stops_inside_element = TRUE;
2092 Debug("game:mm:HitElement", "(4): element == %d", element);
2095 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
2096 laser.num_beamers < MAX_NUM_BEAMERS &&
2097 laser.beamer[BEAMER_NR(element)][1].num)
2099 int beamer_angle = get_element_angle(element);
2100 int beamer_nr = BEAMER_NR(element);
2104 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
2107 laser.num_damages--;
2109 if (IS_FIBRE_OPTIC(element) ||
2110 laser.current_angle == get_opposite_angle(beamer_angle))
2114 LX = ELX * TILEX + 14;
2115 LY = ELY * TILEY + 14;
2117 AddLaserEdge(LX, LY);
2118 AddDamagedField(ELX, ELY);
2120 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2123 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2125 pos = (ELX == laser.beamer[beamer_nr][0].x &&
2126 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
2127 ELX = laser.beamer[beamer_nr][pos].x;
2128 ELY = laser.beamer[beamer_nr][pos].y;
2129 LX = ELX * TILEX + 14;
2130 LY = ELY * TILEY + 14;
2132 if (IS_BEAMER(element))
2134 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
2135 XS = 2 * Step[laser.current_angle].x;
2136 YS = 2 * Step[laser.current_angle].y;
2139 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
2141 AddLaserEdge(LX, LY);
2142 AddDamagedField(ELX, ELY);
2144 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2147 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2149 if (laser.current_angle == (laser.current_angle >> 1) << 1)
2154 LX += step_size * XS;
2155 LY += step_size * YS;
2157 laser.num_beamers++;
2166 static boolean HitOnlyAnEdge(int hit_mask)
2168 // check if the laser hit only the edge of an element and, if so, go on
2171 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
2175 if ((hit_mask == HIT_MASK_TOPLEFT ||
2176 hit_mask == HIT_MASK_TOPRIGHT ||
2177 hit_mask == HIT_MASK_BOTTOMLEFT ||
2178 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
2179 laser.current_angle % 4) // angle is not 90°
2183 if (hit_mask == HIT_MASK_TOPLEFT)
2188 else if (hit_mask == HIT_MASK_TOPRIGHT)
2193 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
2198 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
2204 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
2210 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
2217 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
2223 static boolean HitPolarizer(int element, int hit_mask)
2225 if (HitOnlyAnEdge(hit_mask))
2228 if (IS_DF_GRID(element))
2230 int grid_angle = get_element_angle(element);
2233 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2234 grid_angle, laser.current_angle);
2237 AddLaserEdge(LX, LY);
2238 AddDamagedField(ELX, ELY);
2241 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2243 if (laser.current_angle == grid_angle ||
2244 laser.current_angle == get_opposite_angle(grid_angle))
2246 // skip the whole element before continuing the scan
2252 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2254 if (LX/TILEX > ELX || LY/TILEY > ELY)
2256 /* skipping scan positions to the right and down skips one scan
2257 position too much, because this is only the top left scan position
2258 of totally four scan positions (plus one to the right, one to the
2259 bottom and one to the bottom right) */
2265 AddLaserEdge(LX, LY);
2271 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2273 LX / TILEX, LY / TILEY,
2274 LX % TILEX, LY % TILEY);
2279 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2281 return HitReflectingWalls(element, hit_mask);
2285 return HitAbsorbingWalls(element, hit_mask);
2288 else if (IS_GRID_STEEL(element))
2290 // may be required if graphics for steel grid redefined
2291 AddDamagedField(ELX, ELY);
2293 return HitReflectingWalls(element, hit_mask);
2295 else // IS_GRID_WOOD
2297 // may be required if graphics for wooden grid redefined
2298 AddDamagedField(ELX, ELY);
2300 return HitAbsorbingWalls(element, hit_mask);
2306 static boolean HitBlock(int element, int hit_mask)
2308 boolean check = FALSE;
2310 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2311 game_mm.num_keys == 0)
2314 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2317 int ex = ELX * TILEX + 14;
2318 int ey = ELY * TILEY + 14;
2322 for (i = 1; i < 32; i++)
2327 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2332 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2333 return HitAbsorbingWalls(element, hit_mask);
2337 AddLaserEdge(LX - XS, LY - YS);
2338 AddDamagedField(ELX, ELY);
2341 Box[ELX][ELY] = laser.num_edges;
2343 return HitReflectingWalls(element, hit_mask);
2346 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2348 int xs = XS / 2, ys = YS / 2;
2350 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2351 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2353 laser.overloaded = (element == EL_GATE_STONE);
2358 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2359 (hit_mask == HIT_MASK_TOP ||
2360 hit_mask == HIT_MASK_LEFT ||
2361 hit_mask == HIT_MASK_RIGHT ||
2362 hit_mask == HIT_MASK_BOTTOM))
2363 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2364 hit_mask == HIT_MASK_BOTTOM),
2365 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2366 hit_mask == HIT_MASK_RIGHT));
2367 AddLaserEdge(LX, LY);
2373 if (element == EL_GATE_STONE && Box[ELX][ELY])
2375 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2387 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2389 int xs = XS / 2, ys = YS / 2;
2391 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2392 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2394 laser.overloaded = (element == EL_BLOCK_STONE);
2399 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2400 (hit_mask == HIT_MASK_TOP ||
2401 hit_mask == HIT_MASK_LEFT ||
2402 hit_mask == HIT_MASK_RIGHT ||
2403 hit_mask == HIT_MASK_BOTTOM))
2404 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2405 hit_mask == HIT_MASK_BOTTOM),
2406 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2407 hit_mask == HIT_MASK_RIGHT));
2408 AddDamagedField(ELX, ELY);
2410 LX = ELX * TILEX + 14;
2411 LY = ELY * TILEY + 14;
2413 AddLaserEdge(LX, LY);
2415 laser.stops_inside_element = TRUE;
2423 static boolean HitLaserSource(int element, int hit_mask)
2425 if (HitOnlyAnEdge(hit_mask))
2428 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2430 laser.overloaded = TRUE;
2435 static boolean HitLaserDestination(int element, int hit_mask)
2437 if (HitOnlyAnEdge(hit_mask))
2440 if (element != EL_EXIT_OPEN &&
2441 !(IS_RECEIVER(element) &&
2442 game_mm.kettles_still_needed == 0 &&
2443 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2445 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2450 if (IS_RECEIVER(element) ||
2451 (IS_22_5_ANGLE(laser.current_angle) &&
2452 (ELX != (LX + 6 * XS) / TILEX ||
2453 ELY != (LY + 6 * YS) / TILEY ||
2462 LX = ELX * TILEX + 14;
2463 LY = ELY * TILEY + 14;
2465 laser.stops_inside_element = TRUE;
2468 AddLaserEdge(LX, LY);
2469 AddDamagedField(ELX, ELY);
2471 if (game_mm.lights_still_needed == 0)
2473 game_mm.level_solved = TRUE;
2475 SetTileCursorActive(FALSE);
2481 static boolean HitReflectingWalls(int element, int hit_mask)
2483 // check if laser hits side of a wall with an angle that is not 90°
2484 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2485 hit_mask == HIT_MASK_LEFT ||
2486 hit_mask == HIT_MASK_RIGHT ||
2487 hit_mask == HIT_MASK_BOTTOM))
2489 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2494 if (!IS_DF_GRID(element))
2495 AddLaserEdge(LX, LY);
2497 // check if laser hits wall with an angle of 45°
2498 if (!IS_22_5_ANGLE(laser.current_angle))
2500 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2503 laser.current_angle = get_mirrored_angle(laser.current_angle,
2506 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2509 laser.current_angle = get_mirrored_angle(laser.current_angle,
2513 AddLaserEdge(LX, LY);
2515 XS = 2 * Step[laser.current_angle].x;
2516 YS = 2 * Step[laser.current_angle].y;
2520 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2522 laser.current_angle = get_mirrored_angle(laser.current_angle,
2527 if (!IS_DF_GRID(element))
2528 AddLaserEdge(LX, LY);
2533 if (!IS_DF_GRID(element))
2534 AddLaserEdge(LX, LY + YS / 2);
2537 if (!IS_DF_GRID(element))
2538 AddLaserEdge(LX, LY);
2541 YS = 2 * Step[laser.current_angle].y;
2545 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2547 laser.current_angle = get_mirrored_angle(laser.current_angle,
2552 if (!IS_DF_GRID(element))
2553 AddLaserEdge(LX, LY);
2558 if (!IS_DF_GRID(element))
2559 AddLaserEdge(LX + XS / 2, LY);
2562 if (!IS_DF_GRID(element))
2563 AddLaserEdge(LX, LY);
2566 XS = 2 * Step[laser.current_angle].x;
2572 // reflection at the edge of reflecting DF style wall
2573 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2575 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2576 hit_mask == HIT_MASK_TOPRIGHT) ||
2577 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2578 hit_mask == HIT_MASK_TOPLEFT) ||
2579 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2580 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2581 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2582 hit_mask == HIT_MASK_BOTTOMRIGHT))
2585 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2586 ANG_MIRROR_135 : ANG_MIRROR_45);
2588 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2590 AddDamagedField(ELX, ELY);
2591 AddLaserEdge(LX, LY);
2593 laser.current_angle = get_mirrored_angle(laser.current_angle,
2601 AddLaserEdge(LX, LY);
2607 // reflection inside an edge of reflecting DF style wall
2608 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2610 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2611 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2612 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2613 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2614 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2615 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2616 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2617 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2620 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2621 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2622 ANG_MIRROR_135 : ANG_MIRROR_45);
2624 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2627 AddDamagedField(ELX, ELY);
2630 AddLaserEdge(LX - XS, LY - YS);
2631 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2632 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2634 laser.current_angle = get_mirrored_angle(laser.current_angle,
2642 AddLaserEdge(LX, LY);
2648 // check if laser hits DF style wall with an angle of 90°
2649 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2651 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2652 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2653 (IS_VERT_ANGLE(laser.current_angle) &&
2654 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2656 // laser at last step touched nothing or the same side of the wall
2657 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2659 AddDamagedField(ELX, ELY);
2666 last_hit_mask = hit_mask;
2673 if (!HitOnlyAnEdge(hit_mask))
2675 laser.overloaded = TRUE;
2683 static boolean HitAbsorbingWalls(int element, int hit_mask)
2685 if (HitOnlyAnEdge(hit_mask))
2689 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2691 AddLaserEdge(LX - XS, LY - YS);
2698 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2700 AddLaserEdge(LX - XS, LY - YS);
2706 if (IS_WALL_WOOD(element) ||
2707 IS_DF_WALL_WOOD(element) ||
2708 IS_GRID_WOOD(element) ||
2709 IS_GRID_WOOD_FIXED(element) ||
2710 IS_GRID_WOOD_AUTO(element) ||
2711 element == EL_FUSE_ON ||
2712 element == EL_BLOCK_WOOD ||
2713 element == EL_GATE_WOOD)
2715 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2720 if (IS_WALL_ICE(element))
2726 // check if laser hit adjacent edges of two diagonal tiles
2727 if (ELX != lx / TILEX)
2729 if (ELY != ly / TILEY)
2732 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2733 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2735 // check if laser hits wall with an angle of 90°
2736 if (IS_90_ANGLE(laser.current_angle))
2737 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2739 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2743 for (i = 0; i < 4; i++)
2745 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2746 mask = 15 - (8 >> i);
2747 else if (ABS(XS) == 4 &&
2749 (XS > 0) == (i % 2) &&
2750 (YS < 0) == (i / 2))
2751 mask = 3 + (i / 2) * 9;
2752 else if (ABS(YS) == 4 &&
2754 (XS < 0) == (i % 2) &&
2755 (YS > 0) == (i / 2))
2756 mask = 5 + (i % 2) * 5;
2760 laser.wall_mask = mask;
2762 else if (IS_WALL_AMOEBA(element))
2764 int elx = (LX - 2 * XS) / TILEX;
2765 int ely = (LY - 2 * YS) / TILEY;
2766 int element2 = Tile[elx][ely];
2769 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2771 laser.dest_element = EL_EMPTY;
2779 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2780 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2782 if (IS_90_ANGLE(laser.current_angle))
2783 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2785 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2787 laser.wall_mask = mask;
2793 static void OpenExit(int x, int y)
2797 if (!MovDelay[x][y]) // next animation frame
2798 MovDelay[x][y] = 4 * delay;
2800 if (MovDelay[x][y]) // wait some time before next frame
2805 phase = MovDelay[x][y] / delay;
2807 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2808 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2810 if (!MovDelay[x][y])
2812 Tile[x][y] = EL_EXIT_OPEN;
2818 static void OpenGrayBall(int x, int y)
2822 if (!MovDelay[x][y]) // next animation frame
2824 if (IS_WALL(Store[x][y]))
2826 DrawWalls_MM(x, y, Store[x][y]);
2828 // copy wall tile to spare bitmap for "melting" animation
2829 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2830 TILEX, TILEY, x * TILEX, y * TILEY);
2832 DrawElement_MM(x, y, EL_GRAY_BALL);
2835 MovDelay[x][y] = 50 * delay;
2838 if (MovDelay[x][y]) // wait some time before next frame
2842 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2846 int dx = RND(26), dy = RND(26);
2848 if (IS_WALL(Store[x][y]))
2850 // copy wall tile from spare bitmap for "melting" animation
2851 bitmap = bitmap_db_field;
2857 int graphic = el2gfx(Store[x][y]);
2859 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2862 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2863 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2865 laser.redraw = TRUE;
2867 MarkTileDirty(x, y);
2870 if (!MovDelay[x][y])
2872 Tile[x][y] = Store[x][y];
2873 Store[x][y] = Store2[x][y] = 0;
2874 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2876 InitField(x, y, FALSE);
2879 ScanLaser_FromLastMirror();
2884 static void OpenEnvelope(int x, int y)
2886 int num_frames = 8; // seven frames plus final empty space
2888 if (!MovDelay[x][y]) // next animation frame
2889 MovDelay[x][y] = num_frames;
2891 if (MovDelay[x][y]) // wait some time before next frame
2893 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2897 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2899 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2900 int frame = num_frames - MovDelay[x][y] - 1;
2902 DrawGraphicAnimation_MM(x, y, graphic, frame);
2904 laser.redraw = TRUE;
2907 if (MovDelay[x][y] == 0)
2909 Tile[x][y] = EL_EMPTY;
2920 static void MeltIce(int x, int y)
2925 if (!MovDelay[x][y]) // next animation frame
2926 MovDelay[x][y] = frames * delay;
2928 if (MovDelay[x][y]) // wait some time before next frame
2931 int wall_mask = Store2[x][y];
2932 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2935 phase = frames - MovDelay[x][y] / delay - 1;
2937 if (!MovDelay[x][y])
2939 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2940 Store[x][y] = Store2[x][y] = 0;
2942 DrawWalls_MM(x, y, Tile[x][y]);
2944 if (Tile[x][y] == EL_WALL_ICE_BASE)
2945 Tile[x][y] = EL_EMPTY;
2947 ScanLaser_FromLastMirror();
2949 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2951 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2953 laser.redraw = TRUE;
2958 static void GrowAmoeba(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_AMOEBA_BASE;
2973 phase = MovDelay[x][y] / delay;
2975 if (!MovDelay[x][y])
2977 Tile[x][y] = real_element;
2978 Store[x][y] = Store2[x][y] = 0;
2980 DrawWalls_MM(x, y, Tile[x][y]);
2981 DrawLaser(0, DL_LASER_ENABLED);
2983 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2985 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2990 static void DrawFieldAnimated_MM(int x, int y)
2994 laser.redraw = TRUE;
2997 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2999 int element = Tile[x][y];
3000 int graphic = el2gfx(element);
3002 if (!getGraphicInfo_NewFrame(x, y, graphic))
3007 laser.redraw = TRUE;
3010 static void DrawFieldTwinkle(int x, int y)
3012 if (MovDelay[x][y] != 0) // wait some time before next frame
3018 if (MovDelay[x][y] != 0)
3020 int graphic = IMG_TWINKLE_WHITE;
3021 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
3023 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
3026 laser.redraw = TRUE;
3030 static void Explode_MM(int x, int y, int phase, int mode)
3032 int num_phase = 9, delay = 2;
3033 int last_phase = num_phase * delay;
3034 int half_phase = (num_phase / 2) * delay;
3037 laser.redraw = TRUE;
3039 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
3041 center_element = Tile[x][y];
3043 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
3045 // put moving element to center field (and let it explode there)
3046 center_element = MovingOrBlocked2Element_MM(x, y);
3047 RemoveMovingField_MM(x, y);
3049 Tile[x][y] = center_element;
3052 if (center_element != EL_GRAY_BALL_ACTIVE)
3053 Store[x][y] = EL_EMPTY;
3054 Store2[x][y] = center_element;
3056 Tile[x][y] = EL_EXPLODING_OPAQUE;
3058 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
3059 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
3060 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
3063 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3065 ExplodePhase[x][y] = 1;
3071 GfxFrame[x][y] = 0; // restart explosion animation
3073 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
3075 center_element = Store2[x][y];
3077 if (phase == half_phase && Store[x][y] == EL_EMPTY)
3079 Tile[x][y] = EL_EXPLODING_TRANSP;
3081 if (x == ELX && y == ELY)
3085 if (phase == last_phase)
3087 if (center_element == EL_BOMB_ACTIVE)
3089 DrawLaser(0, DL_LASER_DISABLED);
3092 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3094 laser.overloaded = FALSE;
3096 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
3098 GameOver_MM(GAME_OVER_BOMB);
3101 Tile[x][y] = Store[x][y];
3103 Store[x][y] = Store2[x][y] = 0;
3104 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3106 InitField(x, y, FALSE);
3109 if (center_element == EL_GRAY_BALL_ACTIVE)
3110 ScanLaser_FromLastMirror();
3112 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
3114 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
3115 int frame = getGraphicAnimationFrameXY(graphic, x, y);
3117 DrawGraphicAnimation_MM(x, y, graphic, frame);
3119 MarkTileDirty(x, y);
3123 static void Bang_MM(int x, int y)
3125 int element = Tile[x][y];
3127 if (IS_PACMAN(element))
3128 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3129 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
3130 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3131 else if (element == EL_KEY)
3132 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3134 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3136 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
3139 static void TurnRound(int x, int y)
3151 { 0, 0 }, { 0, 0 }, { 0, 0 },
3156 int left, right, back;
3160 { MV_DOWN, MV_UP, MV_RIGHT },
3161 { MV_UP, MV_DOWN, MV_LEFT },
3163 { MV_LEFT, MV_RIGHT, MV_DOWN },
3167 { MV_RIGHT, MV_LEFT, MV_UP }
3170 int element = Tile[x][y];
3171 int old_move_dir = MovDir[x][y];
3172 int right_dir = turn[old_move_dir].right;
3173 int back_dir = turn[old_move_dir].back;
3174 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
3175 int right_x = x + right_dx, right_y = y + right_dy;
3177 if (element == EL_PACMAN)
3179 boolean can_turn_right = FALSE;
3181 if (IN_LEV_FIELD(right_x, right_y) &&
3182 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
3183 can_turn_right = TRUE;
3186 MovDir[x][y] = right_dir;
3188 MovDir[x][y] = back_dir;
3194 static void StartMoving_MM(int x, int y)
3196 int element = Tile[x][y];
3201 if (CAN_MOVE(element))
3205 if (MovDelay[x][y]) // wait some time before next movement
3213 // now make next step
3215 Moving2Blocked(x, y, &newx, &newy); // get next screen position
3217 if (element == EL_PACMAN &&
3218 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
3219 !ObjHit(newx, newy, HIT_POS_CENTER))
3221 Store[newx][newy] = Tile[newx][newy];
3222 Tile[newx][newy] = EL_EMPTY;
3224 DrawField_MM(newx, newy);
3226 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3227 ObjHit(newx, newy, HIT_POS_CENTER))
3229 // object was running against a wall
3236 InitMovingField_MM(x, y, MovDir[x][y]);
3240 ContinueMoving_MM(x, y);
3243 static void ContinueMoving_MM(int x, int y)
3245 int element = Tile[x][y];
3246 int direction = MovDir[x][y];
3247 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3248 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3249 int horiz_move = (dx!=0);
3250 int newx = x + dx, newy = y + dy;
3251 int step = (horiz_move ? dx : dy) * TILEX / 8;
3253 MovPos[x][y] += step;
3255 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3257 Tile[x][y] = EL_EMPTY;
3258 Tile[newx][newy] = element;
3260 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3261 MovDelay[newx][newy] = 0;
3263 if (!CAN_MOVE(element))
3264 MovDir[newx][newy] = 0;
3267 DrawField_MM(newx, newy);
3269 Stop[newx][newy] = TRUE;
3271 if (element == EL_PACMAN)
3273 if (Store[newx][newy] == EL_BOMB)
3274 Bang_MM(newx, newy);
3276 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3277 (LX + 2 * XS) / TILEX == newx &&
3278 (LY + 2 * YS) / TILEY == newy)
3285 else // still moving on
3290 laser.redraw = TRUE;
3293 boolean ClickElement(int x, int y, int button)
3295 static DelayCounter click_delay = { CLICK_DELAY };
3296 static boolean new_button = TRUE;
3297 boolean element_clicked = FALSE;
3302 // initialize static variables
3303 click_delay.count = 0;
3304 click_delay.value = CLICK_DELAY;
3310 // do not rotate objects hit by the laser after the game was solved
3311 if (game_mm.level_solved && Hit[x][y])
3314 if (button == MB_RELEASED)
3317 click_delay.value = CLICK_DELAY;
3319 // release eventually hold auto-rotating mirror
3320 RotateMirror(x, y, MB_RELEASED);
3325 if (!FrameReached(&click_delay) && !new_button)
3328 if (button == MB_MIDDLEBUTTON) // middle button has no function
3331 if (!IN_LEV_FIELD(x, y))
3334 if (Tile[x][y] == EL_EMPTY)
3337 element = Tile[x][y];
3339 if (IS_MIRROR(element) ||
3340 IS_BEAMER(element) ||
3341 IS_POLAR(element) ||
3342 IS_POLAR_CROSS(element) ||
3343 IS_DF_MIRROR(element) ||
3344 IS_DF_MIRROR_AUTO(element))
3346 RotateMirror(x, y, button);
3348 element_clicked = TRUE;
3350 else if (IS_MCDUFFIN(element))
3352 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3354 if (has_laser && !laser.fuse_off)
3355 DrawLaser(0, DL_LASER_DISABLED);
3357 element = get_rotated_element(element, BUTTON_ROTATION(button));
3359 Tile[x][y] = element;
3364 laser.start_angle = get_element_angle(element);
3368 if (!laser.fuse_off)
3372 element_clicked = TRUE;
3374 else if (element == EL_FUSE_ON && laser.fuse_off)
3376 if (x != laser.fuse_x || y != laser.fuse_y)
3379 laser.fuse_off = FALSE;
3380 laser.fuse_x = laser.fuse_y = -1;
3382 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3385 element_clicked = TRUE;
3387 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3389 laser.fuse_off = TRUE;
3392 laser.overloaded = FALSE;
3394 DrawLaser(0, DL_LASER_DISABLED);
3395 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3397 element_clicked = TRUE;
3399 else if (element == EL_LIGHTBALL)
3402 RaiseScoreElement_MM(element);
3403 DrawLaser(0, DL_LASER_ENABLED);
3405 element_clicked = TRUE;
3407 else if (IS_ENVELOPE(element))
3409 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3411 element_clicked = TRUE;
3414 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3417 return element_clicked;
3420 static void RotateMirror(int x, int y, int button)
3422 if (button == MB_RELEASED)
3424 // release eventually hold auto-rotating mirror
3431 if (IS_MIRROR(Tile[x][y]) ||
3432 IS_POLAR_CROSS(Tile[x][y]) ||
3433 IS_POLAR(Tile[x][y]) ||
3434 IS_BEAMER(Tile[x][y]) ||
3435 IS_DF_MIRROR(Tile[x][y]) ||
3436 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3437 IS_GRID_WOOD_AUTO(Tile[x][y]))
3439 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3441 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3443 if (button == MB_LEFTBUTTON)
3445 // left mouse button only for manual adjustment, no auto-rotating;
3446 // freeze mirror for until mouse button released
3450 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3452 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3456 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3458 int edge = Hit[x][y];
3464 DrawLaser(edge - 1, DL_LASER_DISABLED);
3468 else if (ObjHit(x, y, HIT_POS_CENTER))
3470 int edge = Hit[x][y];
3474 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3479 DrawLaser(edge - 1, DL_LASER_DISABLED);
3486 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3491 if ((IS_BEAMER(Tile[x][y]) ||
3492 IS_POLAR(Tile[x][y]) ||
3493 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3495 if (IS_BEAMER(Tile[x][y]))
3498 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3499 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3512 DrawLaser(0, DL_LASER_ENABLED);
3516 static void AutoRotateMirrors(void)
3520 if (!FrameReached(&rotate_delay))
3523 for (x = 0; x < lev_fieldx; x++)
3525 for (y = 0; y < lev_fieldy; y++)
3527 int element = Tile[x][y];
3529 // do not rotate objects hit by the laser after the game was solved
3530 if (game_mm.level_solved && Hit[x][y])
3533 if (IS_DF_MIRROR_AUTO(element) ||
3534 IS_GRID_WOOD_AUTO(element) ||
3535 IS_GRID_STEEL_AUTO(element) ||
3536 element == EL_REFRACTOR)
3538 RotateMirror(x, y, MB_RIGHTBUTTON);
3540 laser.redraw = TRUE;
3546 static boolean ObjHit(int obx, int oby, int bits)
3553 if (bits & HIT_POS_CENTER)
3555 if (CheckLaserPixel(cSX + obx + 15,
3560 if (bits & HIT_POS_EDGE)
3562 for (i = 0; i < 4; i++)
3563 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3564 cSY + oby + 31 * (i / 2)))
3568 if (bits & HIT_POS_BETWEEN)
3570 for (i = 0; i < 4; i++)
3571 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3572 cSY + 4 + oby + 22 * (i / 2)))
3579 static void DeletePacMan(int px, int py)
3585 if (game_mm.num_pacman <= 1)
3587 game_mm.num_pacman = 0;
3591 for (i = 0; i < game_mm.num_pacman; i++)
3592 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3595 game_mm.num_pacman--;
3597 for (j = i; j < game_mm.num_pacman; j++)
3599 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3600 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3601 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3605 static void GameActions_MM_Ext(void)
3612 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3615 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3617 element = Tile[x][y];
3619 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3620 StartMoving_MM(x, y);
3621 else if (IS_MOVING(x, y))
3622 ContinueMoving_MM(x, y);
3623 else if (IS_EXPLODING(element))
3624 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3625 else if (element == EL_EXIT_OPENING)
3627 else if (element == EL_GRAY_BALL_OPENING)
3629 else if (IS_ENVELOPE_OPENING(element))
3631 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3633 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3635 else if (IS_MIRROR(element) ||
3636 IS_MIRROR_FIXED(element) ||
3637 element == EL_PRISM)
3638 DrawFieldTwinkle(x, y);
3639 else if (element == EL_GRAY_BALL_ACTIVE ||
3640 element == EL_BOMB_ACTIVE ||
3641 element == EL_MINE_ACTIVE)
3642 DrawFieldAnimated_MM(x, y);
3643 else if (!IS_BLOCKED(x, y))
3644 DrawFieldAnimatedIfNeeded_MM(x, y);
3647 AutoRotateMirrors();
3650 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3652 // redraw after Explode_MM() ...
3654 DrawLaser(0, DL_LASER_ENABLED);
3655 laser.redraw = FALSE;
3660 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3664 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3666 DrawLaser(0, DL_LASER_DISABLED);
3671 // skip all following game actions if game is over
3672 if (game_mm.game_over)
3675 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3679 GameOver_MM(GAME_OVER_NO_ENERGY);
3684 if (FrameReached(&energy_delay))
3686 if (game_mm.energy_left > 0)
3687 game_mm.energy_left--;
3689 // when out of energy, wait another frame to play "out of time" sound
3692 element = laser.dest_element;
3695 if (element != Tile[ELX][ELY])
3697 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3698 element, Tile[ELX][ELY]);
3702 if (!laser.overloaded && laser.overload_value == 0 &&
3703 element != EL_BOMB &&
3704 element != EL_BOMB_ACTIVE &&
3705 element != EL_MINE &&
3706 element != EL_MINE_ACTIVE &&
3707 element != EL_GRAY_BALL &&
3708 element != EL_GRAY_BALL_ACTIVE &&
3709 element != EL_BLOCK_STONE &&
3710 element != EL_BLOCK_WOOD &&
3711 element != EL_FUSE_ON &&
3712 element != EL_FUEL_FULL &&
3713 !IS_WALL_ICE(element) &&
3714 !IS_WALL_AMOEBA(element))
3717 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3719 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3720 (!laser.overloaded && laser.overload_value > 0)) &&
3721 FrameReached(&overload_delay))
3723 if (laser.overloaded)
3724 laser.overload_value++;
3726 laser.overload_value--;
3728 if (game_mm.cheat_no_overload)
3730 laser.overloaded = FALSE;
3731 laser.overload_value = 0;
3734 game_mm.laser_overload_value = laser.overload_value;
3736 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3738 SetLaserColor(0xFF);
3740 DrawLaser(0, DL_LASER_ENABLED);
3743 if (!laser.overloaded)
3744 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3745 else if (setup.sound_loops)
3746 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3748 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3750 if (laser.overload_value == MAX_LASER_OVERLOAD)
3752 UpdateAndDisplayGameControlValues();
3756 GameOver_MM(GAME_OVER_OVERLOADED);
3767 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3769 if (game_mm.cheat_no_explosion)
3774 laser.dest_element = EL_EXPLODING_OPAQUE;
3779 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3781 laser.fuse_off = TRUE;
3785 DrawLaser(0, DL_LASER_DISABLED);
3786 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3789 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3791 if (!Store2[ELX][ELY]) // check if content element not yet determined
3793 int last_anim_random_frame = gfx.anim_random_frame;
3796 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3797 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3799 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3800 native_mm_level.ball_choice_mode, 0,
3801 game_mm.ball_choice_pos);
3803 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3804 gfx.anim_random_frame = last_anim_random_frame;
3806 game_mm.ball_choice_pos++;
3808 int new_element = native_mm_level.ball_content[element_pos];
3809 int new_element_base = map_wall_to_base_element(new_element);
3811 if (IS_WALL(new_element_base))
3813 // always use completely filled wall element
3814 new_element = new_element_base | 0x000f;
3816 else if (native_mm_level.rotate_ball_content &&
3817 get_num_elements(new_element) > 1)
3819 // randomly rotate newly created game element
3820 new_element = get_rotated_element(new_element, RND(16));
3823 Store[ELX][ELY] = new_element;
3824 Store2[ELX][ELY] = TRUE;
3827 if (native_mm_level.explode_ball)
3830 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3832 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3837 if (IS_WALL_ICE(element) && CT > 50)
3839 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3841 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3842 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3843 Store2[ELX][ELY] = laser.wall_mask;
3845 laser.dest_element = Tile[ELX][ELY];
3850 if (IS_WALL_AMOEBA(element) && CT > 60)
3853 int element2 = Tile[ELX][ELY];
3855 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3858 for (i = laser.num_damages - 1; i >= 0; i--)
3859 if (laser.damage[i].is_mirror)
3862 r = laser.num_edges;
3863 d = laser.num_damages;
3870 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3873 DrawLaser(0, DL_LASER_ENABLED);
3876 x = laser.damage[k1].x;
3877 y = laser.damage[k1].y;
3882 for (i = 0; i < 4; i++)
3884 if (laser.wall_mask & (1 << i))
3886 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3887 cSY + ELY * TILEY + 31 * (i / 2)))
3890 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3891 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3898 for (i = 0; i < 4; i++)
3900 if (laser.wall_mask & (1 << i))
3902 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3903 cSY + ELY * TILEY + 31 * (i / 2)))
3910 if (laser.num_beamers > 0 ||
3911 k1 < 1 || k2 < 4 || k3 < 4 ||
3912 CheckLaserPixel(cSX + ELX * TILEX + 14,
3913 cSY + ELY * TILEY + 14))
3915 laser.num_edges = r;
3916 laser.num_damages = d;
3918 DrawLaser(0, DL_LASER_DISABLED);
3921 Tile[ELX][ELY] = element | laser.wall_mask;
3923 int x = ELX, y = ELY;
3924 int wall_mask = laser.wall_mask;
3927 DrawLaser(0, DL_LASER_ENABLED);
3929 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3931 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3932 Store[x][y] = EL_WALL_AMOEBA_BASE;
3933 Store2[x][y] = wall_mask;
3938 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3939 laser.stops_inside_element && CT > native_mm_level.time_block)
3944 if (ABS(XS) > ABS(YS))
3951 for (i = 0; i < 4; i++)
3958 x = ELX + Step[k * 4].x;
3959 y = ELY + Step[k * 4].y;
3961 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3964 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3972 laser.overloaded = (element == EL_BLOCK_STONE);
3977 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3980 Tile[x][y] = element;
3982 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3985 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3987 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3988 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3996 if (element == EL_FUEL_FULL && CT > 10)
3998 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3999 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
4001 for (i = start; i <= num_init_game_frames; i++)
4003 if (i == num_init_game_frames)
4004 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4005 else if (setup.sound_loops)
4006 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4008 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4010 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
4012 UpdateAndDisplayGameControlValues();
4017 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
4019 DrawField_MM(ELX, ELY);
4021 DrawLaser(0, DL_LASER_ENABLED);
4027 void GameActions_MM(struct MouseActionInfo action)
4029 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
4030 boolean button_released = (action.button == MB_RELEASED);
4032 GameActions_MM_Ext();
4034 CheckSingleStepMode_MM(element_clicked, button_released);
4037 static void MovePacMen(void)
4039 int mx, my, ox, oy, nx, ny;
4043 if (++pacman_nr >= game_mm.num_pacman)
4046 game_mm.pacman[pacman_nr].dir--;
4048 for (l = 1; l < 5; l++)
4050 game_mm.pacman[pacman_nr].dir++;
4052 if (game_mm.pacman[pacman_nr].dir > 4)
4053 game_mm.pacman[pacman_nr].dir = 1;
4055 if (game_mm.pacman[pacman_nr].dir % 2)
4058 my = game_mm.pacman[pacman_nr].dir - 2;
4063 mx = 3 - game_mm.pacman[pacman_nr].dir;
4066 ox = game_mm.pacman[pacman_nr].x;
4067 oy = game_mm.pacman[pacman_nr].y;
4070 element = Tile[nx][ny];
4072 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
4075 if (!IS_EATABLE4PACMAN(element))
4078 if (ObjHit(nx, ny, HIT_POS_CENTER))
4081 Tile[ox][oy] = EL_EMPTY;
4083 EL_PACMAN_RIGHT - 1 +
4084 (game_mm.pacman[pacman_nr].dir - 1 +
4085 (game_mm.pacman[pacman_nr].dir % 2) * 2);
4087 game_mm.pacman[pacman_nr].x = nx;
4088 game_mm.pacman[pacman_nr].y = ny;
4090 DrawGraphic_MM(ox, oy, IMG_EMPTY);
4092 if (element != EL_EMPTY)
4094 int graphic = el2gfx(Tile[nx][ny]);
4099 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
4102 ox = cSX + ox * TILEX;
4103 oy = cSY + oy * TILEY;
4105 for (i = 1; i < 33; i += 2)
4106 BlitBitmap(bitmap, window,
4107 src_x, src_y, TILEX, TILEY,
4108 ox + i * mx, oy + i * my);
4109 Ct = Ct + FrameCounter - CT;
4112 DrawField_MM(nx, ny);
4115 if (!laser.fuse_off)
4117 DrawLaser(0, DL_LASER_ENABLED);
4119 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
4121 AddDamagedField(nx, ny);
4123 laser.damage[laser.num_damages - 1].edge = 0;
4127 if (element == EL_BOMB)
4128 DeletePacMan(nx, ny);
4130 if (IS_WALL_AMOEBA(element) &&
4131 (LX + 2 * XS) / TILEX == nx &&
4132 (LY + 2 * YS) / TILEY == ny)
4142 static void InitMovingField_MM(int x, int y, int direction)
4144 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4145 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4147 MovDir[x][y] = direction;
4148 MovDir[newx][newy] = direction;
4150 if (Tile[newx][newy] == EL_EMPTY)
4151 Tile[newx][newy] = EL_BLOCKED;
4154 static int MovingOrBlocked2Element_MM(int x, int y)
4156 int element = Tile[x][y];
4158 if (element == EL_BLOCKED)
4162 Blocked2Moving(x, y, &oldx, &oldy);
4164 return Tile[oldx][oldy];
4170 static void RemoveMovingField_MM(int x, int y)
4172 int oldx = x, oldy = y, newx = x, newy = y;
4174 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4177 if (IS_MOVING(x, y))
4179 Moving2Blocked(x, y, &newx, &newy);
4180 if (Tile[newx][newy] != EL_BLOCKED)
4183 else if (Tile[x][y] == EL_BLOCKED)
4185 Blocked2Moving(x, y, &oldx, &oldy);
4186 if (!IS_MOVING(oldx, oldy))
4190 Tile[oldx][oldy] = EL_EMPTY;
4191 Tile[newx][newy] = EL_EMPTY;
4192 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4193 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4195 DrawLevelField_MM(oldx, oldy);
4196 DrawLevelField_MM(newx, newy);
4199 static void RaiseScore_MM(int value)
4201 game_mm.score += value;
4204 void RaiseScoreElement_MM(int element)
4209 case EL_PACMAN_RIGHT:
4211 case EL_PACMAN_LEFT:
4212 case EL_PACMAN_DOWN:
4213 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4217 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4222 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4226 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4235 // ----------------------------------------------------------------------------
4236 // Mirror Magic game engine snapshot handling functions
4237 // ----------------------------------------------------------------------------
4239 void SaveEngineSnapshotValues_MM(void)
4243 engine_snapshot_mm.game_mm = game_mm;
4244 engine_snapshot_mm.laser = laser;
4246 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4248 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4250 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4251 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4252 engine_snapshot_mm.Box[x][y] = Box[x][y];
4253 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4257 engine_snapshot_mm.LX = LX;
4258 engine_snapshot_mm.LY = LY;
4259 engine_snapshot_mm.XS = XS;
4260 engine_snapshot_mm.YS = YS;
4261 engine_snapshot_mm.ELX = ELX;
4262 engine_snapshot_mm.ELY = ELY;
4263 engine_snapshot_mm.CT = CT;
4264 engine_snapshot_mm.Ct = Ct;
4266 engine_snapshot_mm.last_LX = last_LX;
4267 engine_snapshot_mm.last_LY = last_LY;
4268 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4269 engine_snapshot_mm.hold_x = hold_x;
4270 engine_snapshot_mm.hold_y = hold_y;
4271 engine_snapshot_mm.pacman_nr = pacman_nr;
4273 engine_snapshot_mm.rotate_delay = rotate_delay;
4274 engine_snapshot_mm.pacman_delay = pacman_delay;
4275 engine_snapshot_mm.energy_delay = energy_delay;
4276 engine_snapshot_mm.overload_delay = overload_delay;
4279 void LoadEngineSnapshotValues_MM(void)
4283 // stored engine snapshot buffers already restored at this point
4285 game_mm = engine_snapshot_mm.game_mm;
4286 laser = engine_snapshot_mm.laser;
4288 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4290 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4292 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4293 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4294 Box[x][y] = engine_snapshot_mm.Box[x][y];
4295 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4299 LX = engine_snapshot_mm.LX;
4300 LY = engine_snapshot_mm.LY;
4301 XS = engine_snapshot_mm.XS;
4302 YS = engine_snapshot_mm.YS;
4303 ELX = engine_snapshot_mm.ELX;
4304 ELY = engine_snapshot_mm.ELY;
4305 CT = engine_snapshot_mm.CT;
4306 Ct = engine_snapshot_mm.Ct;
4308 last_LX = engine_snapshot_mm.last_LX;
4309 last_LY = engine_snapshot_mm.last_LY;
4310 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4311 hold_x = engine_snapshot_mm.hold_x;
4312 hold_y = engine_snapshot_mm.hold_y;
4313 pacman_nr = engine_snapshot_mm.pacman_nr;
4315 rotate_delay = engine_snapshot_mm.rotate_delay;
4316 pacman_delay = engine_snapshot_mm.pacman_delay;
4317 energy_delay = engine_snapshot_mm.energy_delay;
4318 overload_delay = engine_snapshot_mm.overload_delay;
4320 RedrawPlayfield_MM();
4323 static int getAngleFromTouchDelta(int dx, int dy, int base)
4325 double pi = 3.141592653;
4326 double rad = atan2((double)-dy, (double)dx);
4327 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4328 double deg = rad2 * 180.0 / pi;
4330 return (int)(deg * base / 360.0 + 0.5) % base;
4333 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4335 // calculate start (source) position to be at the middle of the tile
4336 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4337 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4338 int dx = dst_mx - src_mx;
4339 int dy = dst_my - src_my;
4348 if (!IN_LEV_FIELD(x, y))
4351 element = Tile[x][y];
4353 if (!IS_MCDUFFIN(element) &&
4354 !IS_MIRROR(element) &&
4355 !IS_BEAMER(element) &&
4356 !IS_POLAR(element) &&
4357 !IS_POLAR_CROSS(element) &&
4358 !IS_DF_MIRROR(element))
4361 angle_old = get_element_angle(element);
4363 if (IS_MCDUFFIN(element))
4365 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4366 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4367 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4368 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4371 else if (IS_MIRROR(element) ||
4372 IS_DF_MIRROR(element))
4374 for (i = 0; i < laser.num_damages; i++)
4376 if (laser.damage[i].x == x &&
4377 laser.damage[i].y == y &&
4378 ObjHit(x, y, HIT_POS_CENTER))
4380 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4381 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4388 if (angle_new == -1)
4390 if (IS_MIRROR(element) ||
4391 IS_DF_MIRROR(element) ||
4395 if (IS_POLAR_CROSS(element))
4398 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4401 button = (angle_new == angle_old ? 0 :
4402 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4403 MB_LEFTBUTTON : MB_RIGHTBUTTON);