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 getPixelFromMask(int pos, int dx, int dy)
986 return (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
989 static int getLevelFromLaserX(int x)
991 return x / TILEX - (x < 0 ? 1 : 0); // correct negative values
994 static int getLevelFromLaserY(int y)
996 return y / TILEY - (y < 0 ? 1 : 0); // correct negative values
999 static int ScanPixel(void)
1004 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
1005 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
1008 // follow laser beam until it hits something (at least the screen border)
1009 while (hit_mask == HIT_MASK_NO_HIT)
1015 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
1016 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
1018 Debug("game:mm:ScanPixel", "touched screen border!");
1020 return HIT_MASK_ALL;
1024 // check if laser scan has crossed element boundaries (not just mini tiles)
1025 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1026 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1028 if (cross_x && cross_y)
1030 int elx1 = (LX - XS) / TILEX;
1031 int ely1 = (LY + YS) / TILEY;
1032 int elx2 = (LX + XS) / TILEX;
1033 int ely2 = (LY - YS) / TILEY;
1035 // add element corners left and right from the laser beam to damage list
1037 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
1038 AddDamagedField(elx1, ely1);
1040 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
1041 AddDamagedField(elx2, ely2);
1044 for (i = 0; i < 4; i++)
1046 int px = LX + (i % 2) * 2;
1047 int py = LY + (i / 2) * 2;
1048 int dx = px % TILEX;
1049 int dy = py % TILEY;
1050 int lx = getLevelFromLaserX(px);
1051 int ly = getLevelFromLaserY(py);
1054 if (IN_LEV_FIELD(lx, ly))
1056 int element = Tile[lx][ly];
1058 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
1062 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
1064 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1066 pixel = ((element & (1 << pos)) ? 1 : 0);
1070 int pos = getMaskFromElement(element);
1072 pixel = getPixelFromMask(pos, dx, dy);
1077 // check if laser is still inside visible playfield area
1078 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
1079 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
1082 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
1083 hit_mask |= (1 << i);
1086 if (hit_mask == HIT_MASK_NO_HIT)
1088 // hit nothing -- go on with another step
1097 static void DeactivateLaserTargetElement(void)
1099 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1100 laser.dest_element_last == EL_MINE_ACTIVE ||
1101 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1102 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1104 int x = laser.dest_element_last_x;
1105 int y = laser.dest_element_last_y;
1106 int element = laser.dest_element_last;
1108 if (Tile[x][y] == element)
1109 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1110 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1112 if (Tile[x][y] == EL_GRAY_BALL)
1115 laser.dest_element_last = EL_EMPTY;
1116 laser.dest_element_last_x = -1;
1117 laser.dest_element_last_y = -1;
1121 static void ScanLaser(void)
1123 int element = EL_EMPTY;
1124 int last_element = EL_EMPTY;
1125 int end = 0, rf = laser.num_edges;
1127 // do not scan laser again after the game was lost for whatever reason
1128 if (game_mm.game_over)
1131 // do not scan laser if fuse is off
1135 DeactivateLaserTargetElement();
1137 laser.overloaded = FALSE;
1138 laser.stops_inside_element = FALSE;
1140 DrawLaser(0, DL_LASER_ENABLED);
1143 Debug("game:mm:ScanLaser",
1144 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1152 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1155 laser.overloaded = TRUE;
1160 hit_mask = ScanPixel();
1163 Debug("game:mm:ScanLaser",
1164 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1168 // check if laser scan has hit two diagonally adjacent element corners
1169 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1170 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1172 // check if laser scan has crossed element boundaries (not just mini tiles)
1173 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1174 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1176 if (cross_x || cross_y)
1178 // hit something at next tile -- check out what it was
1179 ELX = getLevelFromLaserX(LX + XS);
1180 ELY = getLevelFromLaserY(LY + YS);
1184 // hit something at same tile -- check out what it was
1185 ELX = getLevelFromLaserX(LX);
1186 ELY = getLevelFromLaserY(LY);
1190 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1191 hit_mask, LX, LY, ELX, ELY);
1194 if (!IN_LEV_FIELD(ELX, ELY))
1196 // laser next step position
1197 int x = cSX + LX + XS;
1198 int y = cSY + LY + YS;
1200 // check if next step of laser is still inside visible playfield area
1201 if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
1202 y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
1204 // go on with another step
1212 laser.dest_element = element;
1217 // handle special case of laser hitting two diagonally adjacent elements
1218 // (with or without a third corner element behind these two elements)
1219 if ((diag_1 || diag_2) && cross_x && cross_y)
1221 // compare the two diagonally adjacent elements
1223 int yoffset = 2 * (diag_1 ? -1 : +1);
1224 int elx1 = (LX - xoffset) / TILEX;
1225 int ely1 = (LY + yoffset) / TILEY;
1226 int elx2 = (LX + xoffset) / TILEX;
1227 int ely2 = (LY - yoffset) / TILEY;
1228 int e1 = Tile[elx1][ely1];
1229 int e2 = Tile[elx2][ely2];
1230 boolean use_element_1 = FALSE;
1232 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1234 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1235 use_element_1 = (RND(2) ? TRUE : FALSE);
1236 else if (IS_WALL_ICE(e1))
1237 use_element_1 = TRUE;
1239 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1241 // if both tiles match, we can just select the first one
1242 if (IS_WALL_AMOEBA(e1))
1243 use_element_1 = TRUE;
1245 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1247 // if both tiles match, we can just select the first one
1248 if (IS_ABSORBING_BLOCK(e1))
1249 use_element_1 = TRUE;
1252 ELX = (use_element_1 ? elx1 : elx2);
1253 ELY = (use_element_1 ? ely1 : ely2);
1257 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1258 hit_mask, LX, LY, ELX, ELY);
1261 last_element = element;
1263 element = Tile[ELX][ELY];
1264 laser.dest_element = element;
1267 Debug("game:mm:ScanLaser",
1268 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1271 LX % TILEX, LY % TILEY,
1276 if (!IN_LEV_FIELD(ELX, ELY))
1277 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1281 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1282 if (element == EL_EMPTY &&
1283 IS_GRID_STEEL(last_element) &&
1284 laser.current_angle % 4) // angle is not 90°
1285 element = last_element;
1287 if (element == EL_EMPTY)
1289 if (!HitOnlyAnEdge(hit_mask))
1292 else if (element == EL_FUSE_ON)
1294 if (HitPolarizer(element, hit_mask))
1297 else if (IS_GRID(element) || IS_DF_GRID(element))
1299 if (HitPolarizer(element, hit_mask))
1302 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1303 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1305 if (HitBlock(element, hit_mask))
1312 else if (IS_MCDUFFIN(element))
1314 if (HitLaserSource(element, hit_mask))
1317 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1318 IS_RECEIVER(element))
1320 if (HitLaserDestination(element, hit_mask))
1323 else if (IS_WALL(element))
1325 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1327 if (HitReflectingWalls(element, hit_mask))
1332 if (HitAbsorbingWalls(element, hit_mask))
1338 if (HitElement(element, hit_mask))
1343 DrawLaser(rf - 1, DL_LASER_ENABLED);
1344 rf = laser.num_edges;
1346 if (!IS_DF_WALL_STEEL(element))
1348 // only used for scanning DF steel walls; reset for all other elements
1356 if (laser.dest_element != Tile[ELX][ELY])
1358 Debug("game:mm:ScanLaser",
1359 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1360 laser.dest_element, Tile[ELX][ELY]);
1364 if (!end && !laser.stops_inside_element && !StepBehind())
1367 Debug("game:mm:ScanLaser", "Go one step back");
1373 AddLaserEdge(LX, LY);
1377 DrawLaser(rf - 1, DL_LASER_ENABLED);
1379 Ct = CT = FrameCounter;
1382 if (!IN_LEV_FIELD(ELX, ELY))
1383 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1387 static void ScanLaser_FromLastMirror(void)
1389 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1392 for (i = start_pos; i >= 0; i--)
1393 if (laser.damage[i].is_mirror)
1396 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1398 DrawLaser(start_edge, DL_LASER_DISABLED);
1403 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1409 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1410 start_edge, num_edges, mode);
1415 Warn("DrawLaserExt: start_edge < 0");
1422 Warn("DrawLaserExt: num_edges < 0");
1428 if (mode == DL_LASER_DISABLED)
1430 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1434 // now draw the laser to the backbuffer and (if enabled) to the screen
1435 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1437 redraw_mask |= REDRAW_FIELD;
1439 if (mode == DL_LASER_ENABLED)
1442 // after the laser was deleted, the "damaged" graphics must be restored
1443 if (laser.num_damages)
1445 int damage_start = 0;
1448 // determine the starting edge, from which graphics need to be restored
1451 for (i = 0; i < laser.num_damages; i++)
1453 if (laser.damage[i].edge == start_edge + 1)
1462 // restore graphics from this starting edge to the end of damage list
1463 for (i = damage_start; i < laser.num_damages; i++)
1465 int lx = laser.damage[i].x;
1466 int ly = laser.damage[i].y;
1467 int element = Tile[lx][ly];
1469 if (Hit[lx][ly] == laser.damage[i].edge)
1470 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1473 if (Box[lx][ly] == laser.damage[i].edge)
1476 if (IS_DRAWABLE(element))
1477 DrawField_MM(lx, ly);
1480 elx = laser.damage[damage_start].x;
1481 ely = laser.damage[damage_start].y;
1482 element = Tile[elx][ely];
1485 if (IS_BEAMER(element))
1489 for (i = 0; i < laser.num_beamers; i++)
1490 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1492 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1493 mode, elx, ely, Hit[elx][ely], start_edge);
1494 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1495 get_element_angle(element), laser.damage[damage_start].angle);
1499 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1500 laser.num_beamers > 0 &&
1501 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1503 // element is outgoing beamer
1504 laser.num_damages = damage_start + 1;
1506 if (IS_BEAMER(element))
1507 laser.current_angle = get_element_angle(element);
1511 // element is incoming beamer or other element
1512 laser.num_damages = damage_start;
1513 laser.current_angle = laser.damage[laser.num_damages].angle;
1518 // no damages but McDuffin himself (who needs to be redrawn anyway)
1520 elx = laser.start_edge.x;
1521 ely = laser.start_edge.y;
1522 element = Tile[elx][ely];
1525 laser.num_edges = start_edge + 1;
1526 if (start_edge == 0)
1527 laser.current_angle = laser.start_angle;
1529 LX = laser.edge[start_edge].x - cSX2;
1530 LY = laser.edge[start_edge].y - cSY2;
1531 XS = 2 * Step[laser.current_angle].x;
1532 YS = 2 * Step[laser.current_angle].y;
1535 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1541 if (IS_BEAMER(element) ||
1542 IS_FIBRE_OPTIC(element) ||
1543 IS_PACMAN(element) ||
1544 IS_POLAR(element) ||
1545 IS_POLAR_CROSS(element) ||
1546 element == EL_FUSE_ON)
1551 Debug("game:mm:DrawLaserExt", "element == %d", element);
1554 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1555 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1559 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1560 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1561 (laser.num_beamers == 0 ||
1562 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1564 // element is incoming beamer or other element
1565 step_size = -step_size;
1570 if (IS_BEAMER(element))
1571 Debug("game:mm:DrawLaserExt",
1572 "start_edge == %d, laser.beamer_edge == %d",
1573 start_edge, laser.beamer_edge);
1576 LX += step_size * XS;
1577 LY += step_size * YS;
1579 else if (element != EL_EMPTY)
1588 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1593 void DrawLaser(int start_edge, int mode)
1595 // do not draw laser if fuse is off
1596 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1599 if (mode == DL_LASER_DISABLED)
1600 DeactivateLaserTargetElement();
1602 if (laser.num_edges - start_edge < 0)
1604 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1609 // check if laser is interrupted by beamer element
1610 if (laser.num_beamers > 0 &&
1611 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1613 if (mode == DL_LASER_ENABLED)
1616 int tmp_start_edge = start_edge;
1618 // draw laser segments forward from the start to the last beamer
1619 for (i = 0; i < laser.num_beamers; i++)
1621 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1623 if (tmp_num_edges <= 0)
1627 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1628 i, laser.beamer_edge[i], tmp_start_edge);
1631 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1633 tmp_start_edge = laser.beamer_edge[i];
1636 // draw last segment from last beamer to the end
1637 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1643 int last_num_edges = laser.num_edges;
1644 int num_beamers = laser.num_beamers;
1646 // delete laser segments backward from the end to the first beamer
1647 for (i = num_beamers - 1; i >= 0; i--)
1649 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1651 if (laser.beamer_edge[i] - start_edge <= 0)
1654 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1656 last_num_edges = laser.beamer_edge[i];
1657 laser.num_beamers--;
1661 if (last_num_edges - start_edge <= 0)
1662 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1663 last_num_edges, start_edge);
1666 // special case when rotating first beamer: delete laser edge on beamer
1667 // (but do not start scanning on previous edge to prevent mirror sound)
1668 if (last_num_edges - start_edge == 1 && start_edge > 0)
1669 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1671 // delete first segment from start to the first beamer
1672 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1677 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1680 game_mm.laser_enabled = mode;
1683 void DrawLaser_MM(void)
1685 DrawLaser(0, game_mm.laser_enabled);
1688 static boolean HitElement(int element, int hit_mask)
1690 if (IS_DF_SLOPE(element))
1692 // check if laser scan has crossed element boundaries (not just mini tiles)
1693 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1694 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1695 int element_angle = get_element_angle(element);
1696 int mirrored_angle = get_mirrored_angle(laser.current_angle, element_angle);
1697 int opposite_angle = get_opposite_angle(laser.current_angle);
1699 // check if wall (horizontal or vertical) side of slope was hit
1700 if (hit_mask == HIT_MASK_LEFT ||
1701 hit_mask == HIT_MASK_RIGHT ||
1702 hit_mask == HIT_MASK_TOP ||
1703 hit_mask == HIT_MASK_BOTTOM)
1705 boolean hit_slope_corner_in_laser_direction =
1706 ((hit_mask == HIT_MASK_LEFT && (element == EL_DF_SLOPE_01 ||
1707 element == EL_DF_SLOPE_02)) ||
1708 (hit_mask == HIT_MASK_RIGHT && (element == EL_DF_SLOPE_00 ||
1709 element == EL_DF_SLOPE_03)) ||
1710 (hit_mask == HIT_MASK_TOP && (element == EL_DF_SLOPE_02 ||
1711 element == EL_DF_SLOPE_03)) ||
1712 (hit_mask == HIT_MASK_BOTTOM && (element == EL_DF_SLOPE_00 ||
1713 element == EL_DF_SLOPE_01)));
1715 boolean hit_slope_corner_in_laser_direction_double_checked =
1716 (cross_x && cross_y &&
1717 laser.current_angle == mirrored_angle &&
1718 hit_slope_corner_in_laser_direction);
1720 // check special case of laser hitting the corner of a slope and another
1721 // element (either wall or another slope), following the diagonal side
1722 // of the slope which has the same angle as the direction of the laser
1723 if (!hit_slope_corner_in_laser_direction_double_checked)
1724 return HitReflectingWalls(element, hit_mask);
1727 // check if an edge was hit while crossing element borders
1728 if (cross_x && cross_y && get_number_of_bits(hit_mask) == 1)
1730 // check both sides of potentially diagonal side of slope
1731 int dx1 = (LX + XS) % TILEX;
1732 int dy1 = (LY + YS) % TILEY;
1733 int dx2 = (LX + XS + 2) % TILEX;
1734 int dy2 = (LY + YS + 2) % TILEY;
1735 int pos = getMaskFromElement(element);
1737 // check if we are entering empty space area after hitting edge
1738 if (!getPixelFromMask(pos, dx1, dy1) &&
1739 !getPixelFromMask(pos, dx2, dy2))
1741 // we already know that we hit an edge, but use this function to go on
1742 if (HitOnlyAnEdge(hit_mask))
1747 // check if laser is reflected by slope by 180°
1748 if (mirrored_angle == opposite_angle)
1750 AddDamagedField(LX / TILEX, LY / TILEY);
1752 laser.overloaded = TRUE;
1759 if (HitOnlyAnEdge(hit_mask))
1763 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1764 element = MovingOrBlocked2Element_MM(ELX, ELY);
1767 Debug("game:mm:HitElement", "(1): element == %d", element);
1771 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1772 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1775 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1779 AddDamagedField(ELX, ELY);
1781 boolean through_center = ((ELX * TILEX + 14 - LX) * YS ==
1782 (ELY * TILEY + 14 - LY) * XS);
1784 // this is more precise: check if laser would go through the center
1785 if (!IS_DF_SLOPE(element) && !through_center)
1789 // prevent cutting through laser emitter with laser beam
1790 if (IS_LASER(element))
1793 // skip the whole element before continuing the scan
1801 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1803 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1805 /* skipping scan positions to the right and down skips one scan
1806 position too much, because this is only the top left scan position
1807 of totally four scan positions (plus one to the right, one to the
1808 bottom and one to the bottom right) */
1809 /* ... but only roll back scan position if more than one step done */
1819 Debug("game:mm:HitElement", "(2): element == %d", element);
1822 if (LX + 5 * XS < 0 ||
1832 Debug("game:mm:HitElement", "(3): element == %d", element);
1835 if (IS_POLAR(element) &&
1836 ((element - EL_POLAR_START) % 2 ||
1837 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1839 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1841 laser.num_damages--;
1846 if (IS_POLAR_CROSS(element) &&
1847 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1849 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1851 laser.num_damages--;
1856 if (IS_DF_SLOPE(element) && !through_center)
1860 if (hit_mask == HIT_MASK_ALL)
1862 // laser already inside slope -- go back half step
1869 AddLaserEdge(LX, LY);
1871 LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0);
1872 LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0);
1874 else if (!IS_BEAMER(element) &&
1875 !IS_FIBRE_OPTIC(element) &&
1876 !IS_GRID_WOOD(element) &&
1877 element != EL_FUEL_EMPTY)
1880 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1881 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1883 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1886 LX = ELX * TILEX + 14;
1887 LY = ELY * TILEY + 14;
1889 AddLaserEdge(LX, LY);
1892 if (IS_MIRROR(element) ||
1893 IS_MIRROR_FIXED(element) ||
1894 IS_POLAR(element) ||
1895 IS_POLAR_CROSS(element) ||
1896 IS_DF_MIRROR(element) ||
1897 IS_DF_MIRROR_AUTO(element) ||
1898 IS_DF_MIRROR_FIXED(element) ||
1899 IS_DF_SLOPE(element) ||
1900 element == EL_PRISM ||
1901 element == EL_REFRACTOR)
1903 int current_angle = laser.current_angle;
1906 laser.num_damages--;
1908 AddDamagedField(ELX, ELY);
1910 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1913 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1915 if (IS_MIRROR(element) ||
1916 IS_MIRROR_FIXED(element) ||
1917 IS_DF_MIRROR(element) ||
1918 IS_DF_MIRROR_AUTO(element) ||
1919 IS_DF_MIRROR_FIXED(element) ||
1920 IS_DF_SLOPE(element))
1921 laser.current_angle = get_mirrored_angle(laser.current_angle,
1922 get_element_angle(element));
1924 if (element == EL_PRISM || element == EL_REFRACTOR)
1925 laser.current_angle = RND(16);
1927 XS = 2 * Step[laser.current_angle].x;
1928 YS = 2 * Step[laser.current_angle].y;
1932 // start from center position for all game elements but slope
1933 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1938 LX += step_size * XS;
1939 LY += step_size * YS;
1943 // advance laser position until reaching the next tile (slopes)
1944 while (LX / TILEX == ELX && (LX + 2) / TILEX == ELX &&
1945 LY / TILEY == ELY && (LY + 2) / TILEY == ELY)
1952 // draw sparkles on mirror
1953 if ((IS_MIRROR(element) ||
1954 IS_MIRROR_FIXED(element) ||
1955 element == EL_PRISM) &&
1956 current_angle != laser.current_angle)
1958 MovDelay[ELX][ELY] = 11; // start animation
1961 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1962 current_angle != laser.current_angle)
1963 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1966 (get_opposite_angle(laser.current_angle) ==
1967 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1969 if (IS_DF_SLOPE(element))
1971 // handle special cases for slope element
1973 if (IS_45_ANGLE(laser.current_angle))
1977 elx = getLevelFromLaserX(LX + XS);
1978 ely = getLevelFromLaserY(LY + YS);
1980 if (IN_LEV_FIELD(elx, ely))
1982 int element_next = Tile[elx][ely];
1984 // check if slope is followed by slope with opposite orientation
1985 if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2)
1986 laser.overloaded = TRUE;
1989 int nr = element - EL_DF_SLOPE_START;
1990 int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) :
1991 nr == 1 ? (XS > 0 ? TILEX : 0) :
1992 nr == 2 ? (XS > 0 ? TILEX : 0) :
1993 nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0);
1994 int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) :
1995 nr == 1 ? (YS > 0 ? TILEY - 1 : -1) :
1996 nr == 2 ? (YS > 0 ? TILEY : 0) :
1997 nr == 3 ? (YS > 0 ? TILEY : 0) : 0);
1999 int px = ELX * TILEX + dx;
2000 int py = ELY * TILEY + dy;
2005 elx = getLevelFromLaserX(px);
2006 ely = getLevelFromLaserY(py);
2008 if (IN_LEV_FIELD(elx, ely))
2010 int element_side = Tile[elx][ely];
2012 // check if end of slope is blocked by other element
2013 if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side))
2015 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
2017 if (element & (1 << pos))
2018 laser.overloaded = TRUE;
2022 int pos = getMaskFromElement(element_side);
2024 if (getPixelFromMask(pos, dx, dy))
2025 laser.overloaded = TRUE;
2031 return (laser.overloaded ? TRUE : FALSE);
2034 if (element == EL_FUEL_FULL)
2036 laser.stops_inside_element = TRUE;
2041 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
2043 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2045 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
2046 element == EL_MINE ? EL_MINE_ACTIVE :
2047 EL_GRAY_BALL_ACTIVE);
2049 GfxFrame[ELX][ELY] = 0; // restart animation
2051 laser.dest_element_last = Tile[ELX][ELY];
2052 laser.dest_element_last_x = ELX;
2053 laser.dest_element_last_y = ELY;
2055 if (element == EL_MINE)
2056 laser.overloaded = TRUE;
2059 if (element == EL_KETTLE ||
2060 element == EL_CELL ||
2061 element == EL_KEY ||
2062 element == EL_LIGHTBALL ||
2063 element == EL_PACMAN ||
2064 IS_PACMAN(element) ||
2065 IS_ENVELOPE(element))
2067 if (!IS_PACMAN(element) &&
2068 !IS_ENVELOPE(element))
2071 if (element == EL_PACMAN)
2074 if (element == EL_KETTLE || element == EL_CELL)
2076 if (game_mm.kettles_still_needed > 0)
2077 game_mm.kettles_still_needed--;
2079 game.snapshot.collected_item = TRUE;
2081 if (game_mm.kettles_still_needed == 0)
2085 DrawLaser(0, DL_LASER_ENABLED);
2088 else if (element == EL_KEY)
2092 else if (IS_PACMAN(element))
2094 DeletePacMan(ELX, ELY);
2096 else if (IS_ENVELOPE(element))
2098 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
2101 RaiseScoreElement_MM(element);
2106 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
2108 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2110 DrawLaser(0, DL_LASER_ENABLED);
2112 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
2114 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
2115 game_mm.lights_still_needed--;
2119 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
2120 game_mm.lights_still_needed++;
2123 DrawField_MM(ELX, ELY);
2124 DrawLaser(0, DL_LASER_ENABLED);
2129 laser.stops_inside_element = TRUE;
2135 Debug("game:mm:HitElement", "(4): element == %d", element);
2138 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
2139 laser.num_beamers < MAX_NUM_BEAMERS &&
2140 laser.beamer[BEAMER_NR(element)][1].num)
2142 int beamer_angle = get_element_angle(element);
2143 int beamer_nr = BEAMER_NR(element);
2147 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
2150 laser.num_damages--;
2152 if (IS_FIBRE_OPTIC(element) ||
2153 laser.current_angle == get_opposite_angle(beamer_angle))
2157 LX = ELX * TILEX + 14;
2158 LY = ELY * TILEY + 14;
2160 AddLaserEdge(LX, LY);
2161 AddDamagedField(ELX, ELY);
2163 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2166 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2168 pos = (ELX == laser.beamer[beamer_nr][0].x &&
2169 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
2170 ELX = laser.beamer[beamer_nr][pos].x;
2171 ELY = laser.beamer[beamer_nr][pos].y;
2172 LX = ELX * TILEX + 14;
2173 LY = ELY * TILEY + 14;
2175 if (IS_BEAMER(element))
2177 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
2178 XS = 2 * Step[laser.current_angle].x;
2179 YS = 2 * Step[laser.current_angle].y;
2182 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
2184 AddLaserEdge(LX, LY);
2185 AddDamagedField(ELX, ELY);
2187 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2190 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2192 if (laser.current_angle == (laser.current_angle >> 1) << 1)
2197 LX += step_size * XS;
2198 LY += step_size * YS;
2200 laser.num_beamers++;
2209 static boolean HitOnlyAnEdge(int hit_mask)
2211 // check if the laser hit only the edge of an element and, if so, go on
2214 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
2218 if ((hit_mask == HIT_MASK_TOPLEFT ||
2219 hit_mask == HIT_MASK_TOPRIGHT ||
2220 hit_mask == HIT_MASK_BOTTOMLEFT ||
2221 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
2222 laser.current_angle % 4) // angle is not 90°
2226 if (hit_mask == HIT_MASK_TOPLEFT)
2231 else if (hit_mask == HIT_MASK_TOPRIGHT)
2236 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
2241 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
2247 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
2253 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
2260 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
2266 static boolean HitPolarizer(int element, int hit_mask)
2268 if (HitOnlyAnEdge(hit_mask))
2271 if (IS_DF_GRID(element))
2273 int grid_angle = get_element_angle(element);
2276 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2277 grid_angle, laser.current_angle);
2280 AddLaserEdge(LX, LY);
2281 AddDamagedField(ELX, ELY);
2284 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2286 if (laser.current_angle == grid_angle ||
2287 laser.current_angle == get_opposite_angle(grid_angle))
2289 // skip the whole element before continuing the scan
2295 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2297 if (LX/TILEX > ELX || LY/TILEY > ELY)
2299 /* skipping scan positions to the right and down skips one scan
2300 position too much, because this is only the top left scan position
2301 of totally four scan positions (plus one to the right, one to the
2302 bottom and one to the bottom right) */
2308 AddLaserEdge(LX, LY);
2314 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2316 LX / TILEX, LY / TILEY,
2317 LX % TILEX, LY % TILEY);
2322 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2324 return HitReflectingWalls(element, hit_mask);
2328 return HitAbsorbingWalls(element, hit_mask);
2331 else if (IS_GRID_STEEL(element))
2333 // may be required if graphics for steel grid redefined
2334 AddDamagedField(ELX, ELY);
2336 return HitReflectingWalls(element, hit_mask);
2338 else // IS_GRID_WOOD
2340 // may be required if graphics for wooden grid redefined
2341 AddDamagedField(ELX, ELY);
2343 return HitAbsorbingWalls(element, hit_mask);
2349 static boolean HitBlock(int element, int hit_mask)
2351 boolean check = FALSE;
2353 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2354 game_mm.num_keys == 0)
2357 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2360 int ex = ELX * TILEX + 14;
2361 int ey = ELY * TILEY + 14;
2365 for (i = 1; i < 32; i++)
2370 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2375 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2376 return HitAbsorbingWalls(element, hit_mask);
2380 AddLaserEdge(LX - XS, LY - YS);
2381 AddDamagedField(ELX, ELY);
2384 Box[ELX][ELY] = laser.num_edges;
2386 return HitReflectingWalls(element, hit_mask);
2389 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2391 int xs = XS / 2, ys = YS / 2;
2393 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2394 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2396 laser.overloaded = (element == EL_GATE_STONE);
2401 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2402 (hit_mask == HIT_MASK_TOP ||
2403 hit_mask == HIT_MASK_LEFT ||
2404 hit_mask == HIT_MASK_RIGHT ||
2405 hit_mask == HIT_MASK_BOTTOM))
2406 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2407 hit_mask == HIT_MASK_BOTTOM),
2408 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2409 hit_mask == HIT_MASK_RIGHT));
2410 AddLaserEdge(LX, LY);
2416 if (element == EL_GATE_STONE && Box[ELX][ELY])
2418 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2430 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2432 int xs = XS / 2, ys = YS / 2;
2434 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2435 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2437 laser.overloaded = (element == EL_BLOCK_STONE);
2442 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2443 (hit_mask == HIT_MASK_TOP ||
2444 hit_mask == HIT_MASK_LEFT ||
2445 hit_mask == HIT_MASK_RIGHT ||
2446 hit_mask == HIT_MASK_BOTTOM))
2447 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2448 hit_mask == HIT_MASK_BOTTOM),
2449 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2450 hit_mask == HIT_MASK_RIGHT));
2451 AddDamagedField(ELX, ELY);
2453 LX = ELX * TILEX + 14;
2454 LY = ELY * TILEY + 14;
2456 AddLaserEdge(LX, LY);
2458 laser.stops_inside_element = TRUE;
2466 static boolean HitLaserSource(int element, int hit_mask)
2468 if (HitOnlyAnEdge(hit_mask))
2471 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2473 laser.overloaded = TRUE;
2478 static boolean HitLaserDestination(int element, int hit_mask)
2480 if (HitOnlyAnEdge(hit_mask))
2483 if (element != EL_EXIT_OPEN &&
2484 !(IS_RECEIVER(element) &&
2485 game_mm.kettles_still_needed == 0 &&
2486 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2488 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2493 if (IS_RECEIVER(element) ||
2494 (IS_22_5_ANGLE(laser.current_angle) &&
2495 (ELX != (LX + 6 * XS) / TILEX ||
2496 ELY != (LY + 6 * YS) / TILEY ||
2505 LX = ELX * TILEX + 14;
2506 LY = ELY * TILEY + 14;
2508 laser.stops_inside_element = TRUE;
2511 AddLaserEdge(LX, LY);
2512 AddDamagedField(ELX, ELY);
2514 if (game_mm.lights_still_needed == 0)
2516 game_mm.level_solved = TRUE;
2518 SetTileCursorActive(FALSE);
2524 static boolean HitReflectingWalls(int element, int hit_mask)
2526 // check if laser hits side of a wall with an angle that is not 90°
2527 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2528 hit_mask == HIT_MASK_LEFT ||
2529 hit_mask == HIT_MASK_RIGHT ||
2530 hit_mask == HIT_MASK_BOTTOM))
2532 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2537 if (!IS_DF_GRID(element))
2538 AddLaserEdge(LX, LY);
2540 // check if laser hits wall with an angle of 45°
2541 if (!IS_22_5_ANGLE(laser.current_angle))
2543 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2546 laser.current_angle = get_mirrored_angle(laser.current_angle,
2549 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2552 laser.current_angle = get_mirrored_angle(laser.current_angle,
2556 AddLaserEdge(LX, LY);
2558 XS = 2 * Step[laser.current_angle].x;
2559 YS = 2 * Step[laser.current_angle].y;
2563 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2565 laser.current_angle = get_mirrored_angle(laser.current_angle,
2570 if (!IS_DF_GRID(element))
2571 AddLaserEdge(LX, LY);
2576 if (!IS_DF_GRID(element))
2577 AddLaserEdge(LX, LY + YS / 2);
2580 if (!IS_DF_GRID(element))
2581 AddLaserEdge(LX, LY);
2584 YS = 2 * Step[laser.current_angle].y;
2588 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2590 laser.current_angle = get_mirrored_angle(laser.current_angle,
2595 if (!IS_DF_GRID(element))
2596 AddLaserEdge(LX, LY);
2601 if (!IS_DF_GRID(element))
2602 AddLaserEdge(LX + XS / 2, LY);
2605 if (!IS_DF_GRID(element))
2606 AddLaserEdge(LX, LY);
2609 XS = 2 * Step[laser.current_angle].x;
2615 // reflection at the edge of reflecting DF style wall
2616 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2618 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2619 hit_mask == HIT_MASK_TOPRIGHT) ||
2620 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2621 hit_mask == HIT_MASK_TOPLEFT) ||
2622 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2623 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2624 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2625 hit_mask == HIT_MASK_BOTTOMRIGHT))
2628 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2629 ANG_MIRROR_135 : ANG_MIRROR_45);
2631 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2633 AddDamagedField(ELX, ELY);
2634 AddLaserEdge(LX, LY);
2636 laser.current_angle = get_mirrored_angle(laser.current_angle,
2644 AddLaserEdge(LX, LY);
2650 // reflection inside an edge of reflecting DF style wall
2651 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2653 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2654 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2655 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2656 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2657 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2658 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2659 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2660 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2663 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2664 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2665 ANG_MIRROR_135 : ANG_MIRROR_45);
2667 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2670 AddDamagedField(ELX, ELY);
2673 AddLaserEdge(LX - XS, LY - YS);
2674 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2675 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2677 laser.current_angle = get_mirrored_angle(laser.current_angle,
2685 AddLaserEdge(LX, LY);
2691 // check if laser hits DF style wall with an angle of 90°
2692 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2694 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2695 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2696 (IS_VERT_ANGLE(laser.current_angle) &&
2697 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2699 // laser at last step touched nothing or the same side of the wall
2700 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2702 AddDamagedField(ELX, ELY);
2709 last_hit_mask = hit_mask;
2716 if (!HitOnlyAnEdge(hit_mask))
2718 laser.overloaded = TRUE;
2726 static boolean HitAbsorbingWalls(int element, int hit_mask)
2728 if (HitOnlyAnEdge(hit_mask))
2732 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2734 AddLaserEdge(LX - XS, LY - YS);
2741 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2743 AddLaserEdge(LX - XS, LY - YS);
2749 if (IS_WALL_WOOD(element) ||
2750 IS_DF_WALL_WOOD(element) ||
2751 IS_GRID_WOOD(element) ||
2752 IS_GRID_WOOD_FIXED(element) ||
2753 IS_GRID_WOOD_AUTO(element) ||
2754 element == EL_FUSE_ON ||
2755 element == EL_BLOCK_WOOD ||
2756 element == EL_GATE_WOOD)
2758 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2763 if (IS_WALL_ICE(element))
2769 // check if laser hit adjacent edges of two diagonal tiles
2770 if (ELX != lx / TILEX)
2772 if (ELY != ly / TILEY)
2775 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2776 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2778 // check if laser hits wall with an angle of 90°
2779 if (IS_90_ANGLE(laser.current_angle))
2780 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2782 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2786 for (i = 0; i < 4; i++)
2788 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2789 mask = 15 - (8 >> i);
2790 else if (ABS(XS) == 4 &&
2792 (XS > 0) == (i % 2) &&
2793 (YS < 0) == (i / 2))
2794 mask = 3 + (i / 2) * 9;
2795 else if (ABS(YS) == 4 &&
2797 (XS < 0) == (i % 2) &&
2798 (YS > 0) == (i / 2))
2799 mask = 5 + (i % 2) * 5;
2803 laser.wall_mask = mask;
2805 else if (IS_WALL_AMOEBA(element))
2807 int elx = (LX - 2 * XS) / TILEX;
2808 int ely = (LY - 2 * YS) / TILEY;
2809 int element2 = Tile[elx][ely];
2812 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2814 laser.dest_element = EL_EMPTY;
2822 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2823 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2825 if (IS_90_ANGLE(laser.current_angle))
2826 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2828 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2830 laser.wall_mask = mask;
2836 static void OpenExit(int x, int y)
2840 if (!MovDelay[x][y]) // next animation frame
2841 MovDelay[x][y] = 4 * delay;
2843 if (MovDelay[x][y]) // wait some time before next frame
2848 phase = MovDelay[x][y] / delay;
2850 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2851 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2853 if (!MovDelay[x][y])
2855 Tile[x][y] = EL_EXIT_OPEN;
2861 static void OpenGrayBall(int x, int y)
2865 if (!MovDelay[x][y]) // next animation frame
2867 if (IS_WALL(Store[x][y]))
2869 DrawWalls_MM(x, y, Store[x][y]);
2871 // copy wall tile to spare bitmap for "melting" animation
2872 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2873 TILEX, TILEY, x * TILEX, y * TILEY);
2875 DrawElement_MM(x, y, EL_GRAY_BALL);
2878 MovDelay[x][y] = 50 * delay;
2881 if (MovDelay[x][y]) // wait some time before next frame
2885 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2889 int dx = RND(26), dy = RND(26);
2891 if (IS_WALL(Store[x][y]))
2893 // copy wall tile from spare bitmap for "melting" animation
2894 bitmap = bitmap_db_field;
2900 int graphic = el2gfx(Store[x][y]);
2902 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2905 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2906 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2908 laser.redraw = TRUE;
2910 MarkTileDirty(x, y);
2913 if (!MovDelay[x][y])
2915 Tile[x][y] = Store[x][y];
2916 Store[x][y] = Store2[x][y] = 0;
2917 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2919 InitField(x, y, FALSE);
2922 ScanLaser_FromLastMirror();
2927 static void OpenEnvelope(int x, int y)
2929 int num_frames = 8; // seven frames plus final empty space
2931 if (!MovDelay[x][y]) // next animation frame
2932 MovDelay[x][y] = num_frames;
2934 if (MovDelay[x][y]) // wait some time before next frame
2936 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2940 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2942 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2943 int frame = num_frames - MovDelay[x][y] - 1;
2945 DrawGraphicAnimation_MM(x, y, graphic, frame);
2947 laser.redraw = TRUE;
2950 if (MovDelay[x][y] == 0)
2952 Tile[x][y] = EL_EMPTY;
2963 static void MeltIce(int x, int y)
2968 if (!MovDelay[x][y]) // next animation frame
2969 MovDelay[x][y] = frames * delay;
2971 if (MovDelay[x][y]) // wait some time before next frame
2974 int wall_mask = Store2[x][y];
2975 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2978 phase = frames - MovDelay[x][y] / delay - 1;
2980 if (!MovDelay[x][y])
2982 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2983 Store[x][y] = Store2[x][y] = 0;
2985 DrawWalls_MM(x, y, Tile[x][y]);
2987 if (Tile[x][y] == EL_WALL_ICE_BASE)
2988 Tile[x][y] = EL_EMPTY;
2990 ScanLaser_FromLastMirror();
2992 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2994 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2996 laser.redraw = TRUE;
3001 static void GrowAmoeba(int x, int y)
3006 if (!MovDelay[x][y]) // next animation frame
3007 MovDelay[x][y] = frames * delay;
3009 if (MovDelay[x][y]) // wait some time before next frame
3012 int wall_mask = Store2[x][y];
3013 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
3016 phase = MovDelay[x][y] / delay;
3018 if (!MovDelay[x][y])
3020 Tile[x][y] = real_element;
3021 Store[x][y] = Store2[x][y] = 0;
3023 DrawWalls_MM(x, y, Tile[x][y]);
3024 DrawLaser(0, DL_LASER_ENABLED);
3026 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
3028 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
3033 static void DrawFieldAnimated_MM(int x, int y)
3037 laser.redraw = TRUE;
3040 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
3042 int element = Tile[x][y];
3043 int graphic = el2gfx(element);
3045 if (!getGraphicInfo_NewFrame(x, y, graphic))
3050 laser.redraw = TRUE;
3053 static void DrawFieldTwinkle(int x, int y)
3055 if (MovDelay[x][y] != 0) // wait some time before next frame
3061 if (MovDelay[x][y] != 0)
3063 int graphic = IMG_TWINKLE_WHITE;
3064 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
3066 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
3069 laser.redraw = TRUE;
3073 static void Explode_MM(int x, int y, int phase, int mode)
3075 int num_phase = 9, delay = 2;
3076 int last_phase = num_phase * delay;
3077 int half_phase = (num_phase / 2) * delay;
3080 laser.redraw = TRUE;
3082 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
3084 center_element = Tile[x][y];
3086 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
3088 // put moving element to center field (and let it explode there)
3089 center_element = MovingOrBlocked2Element_MM(x, y);
3090 RemoveMovingField_MM(x, y);
3092 Tile[x][y] = center_element;
3095 if (center_element != EL_GRAY_BALL_ACTIVE)
3096 Store[x][y] = EL_EMPTY;
3097 Store2[x][y] = center_element;
3099 Tile[x][y] = EL_EXPLODING_OPAQUE;
3101 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
3102 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
3103 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
3106 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3108 ExplodePhase[x][y] = 1;
3114 GfxFrame[x][y] = 0; // restart explosion animation
3116 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
3118 center_element = Store2[x][y];
3120 if (phase == half_phase && Store[x][y] == EL_EMPTY)
3122 Tile[x][y] = EL_EXPLODING_TRANSP;
3124 if (x == ELX && y == ELY)
3128 if (phase == last_phase)
3130 if (center_element == EL_BOMB_ACTIVE)
3132 DrawLaser(0, DL_LASER_DISABLED);
3135 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3137 laser.overloaded = FALSE;
3139 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
3141 GameOver_MM(GAME_OVER_BOMB);
3144 Tile[x][y] = Store[x][y];
3146 Store[x][y] = Store2[x][y] = 0;
3147 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3149 InitField(x, y, FALSE);
3152 if (center_element == EL_GRAY_BALL_ACTIVE)
3153 ScanLaser_FromLastMirror();
3155 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
3157 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
3158 int frame = getGraphicAnimationFrameXY(graphic, x, y);
3160 DrawGraphicAnimation_MM(x, y, graphic, frame);
3162 MarkTileDirty(x, y);
3166 static void Bang_MM(int x, int y)
3168 int element = Tile[x][y];
3170 if (IS_PACMAN(element))
3171 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3172 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
3173 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3174 else if (element == EL_KEY)
3175 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3177 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3179 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
3182 static void TurnRound(int x, int y)
3194 { 0, 0 }, { 0, 0 }, { 0, 0 },
3199 int left, right, back;
3203 { MV_DOWN, MV_UP, MV_RIGHT },
3204 { MV_UP, MV_DOWN, MV_LEFT },
3206 { MV_LEFT, MV_RIGHT, MV_DOWN },
3210 { MV_RIGHT, MV_LEFT, MV_UP }
3213 int element = Tile[x][y];
3214 int old_move_dir = MovDir[x][y];
3215 int right_dir = turn[old_move_dir].right;
3216 int back_dir = turn[old_move_dir].back;
3217 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
3218 int right_x = x + right_dx, right_y = y + right_dy;
3220 if (element == EL_PACMAN)
3222 boolean can_turn_right = FALSE;
3224 if (IN_LEV_FIELD(right_x, right_y) &&
3225 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
3226 can_turn_right = TRUE;
3229 MovDir[x][y] = right_dir;
3231 MovDir[x][y] = back_dir;
3237 static void StartMoving_MM(int x, int y)
3239 int element = Tile[x][y];
3244 if (CAN_MOVE(element))
3248 if (MovDelay[x][y]) // wait some time before next movement
3256 // now make next step
3258 Moving2Blocked(x, y, &newx, &newy); // get next screen position
3260 if (element == EL_PACMAN &&
3261 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
3262 !ObjHit(newx, newy, HIT_POS_CENTER))
3264 Store[newx][newy] = Tile[newx][newy];
3265 Tile[newx][newy] = EL_EMPTY;
3267 DrawField_MM(newx, newy);
3269 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3270 ObjHit(newx, newy, HIT_POS_CENTER))
3272 // object was running against a wall
3279 InitMovingField_MM(x, y, MovDir[x][y]);
3283 ContinueMoving_MM(x, y);
3286 static void ContinueMoving_MM(int x, int y)
3288 int element = Tile[x][y];
3289 int direction = MovDir[x][y];
3290 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3291 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3292 int horiz_move = (dx != 0);
3293 int newx = x + dx, newy = y + dy;
3294 int step = (horiz_move ? dx : dy) * TILEX / 8;
3296 MovPos[x][y] += step;
3298 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3300 Tile[x][y] = EL_EMPTY;
3301 Tile[newx][newy] = element;
3303 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3304 MovDelay[newx][newy] = 0;
3306 if (!CAN_MOVE(element))
3307 MovDir[newx][newy] = 0;
3310 DrawField_MM(newx, newy);
3312 Stop[newx][newy] = TRUE;
3314 if (element == EL_PACMAN)
3316 if (Store[newx][newy] == EL_BOMB)
3317 Bang_MM(newx, newy);
3319 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3320 (LX + 2 * XS) / TILEX == newx &&
3321 (LY + 2 * YS) / TILEY == newy)
3328 else // still moving on
3333 laser.redraw = TRUE;
3336 boolean ClickElement(int x, int y, int button)
3338 static DelayCounter click_delay = { CLICK_DELAY };
3339 static boolean new_button = TRUE;
3340 boolean element_clicked = FALSE;
3345 // initialize static variables
3346 click_delay.count = 0;
3347 click_delay.value = CLICK_DELAY;
3353 // do not rotate objects hit by the laser after the game was solved
3354 if (game_mm.level_solved && Hit[x][y])
3357 if (button == MB_RELEASED)
3360 click_delay.value = CLICK_DELAY;
3362 // release eventually hold auto-rotating mirror
3363 RotateMirror(x, y, MB_RELEASED);
3368 if (!FrameReached(&click_delay) && !new_button)
3371 if (button == MB_MIDDLEBUTTON) // middle button has no function
3374 if (!IN_LEV_FIELD(x, y))
3377 if (Tile[x][y] == EL_EMPTY)
3380 element = Tile[x][y];
3382 if (IS_MIRROR(element) ||
3383 IS_BEAMER(element) ||
3384 IS_POLAR(element) ||
3385 IS_POLAR_CROSS(element) ||
3386 IS_DF_MIRROR(element) ||
3387 IS_DF_MIRROR_AUTO(element))
3389 RotateMirror(x, y, button);
3391 element_clicked = TRUE;
3393 else if (IS_MCDUFFIN(element))
3395 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3397 if (has_laser && !laser.fuse_off)
3398 DrawLaser(0, DL_LASER_DISABLED);
3400 element = get_rotated_element(element, BUTTON_ROTATION(button));
3402 Tile[x][y] = element;
3407 laser.start_angle = get_element_angle(element);
3411 if (!laser.fuse_off)
3415 element_clicked = TRUE;
3417 else if (element == EL_FUSE_ON && laser.fuse_off)
3419 if (x != laser.fuse_x || y != laser.fuse_y)
3422 laser.fuse_off = FALSE;
3423 laser.fuse_x = laser.fuse_y = -1;
3425 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3428 element_clicked = TRUE;
3430 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3432 laser.fuse_off = TRUE;
3435 laser.overloaded = FALSE;
3437 DrawLaser(0, DL_LASER_DISABLED);
3438 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3440 element_clicked = TRUE;
3442 else if (element == EL_LIGHTBALL)
3445 RaiseScoreElement_MM(element);
3446 DrawLaser(0, DL_LASER_ENABLED);
3448 element_clicked = TRUE;
3450 else if (IS_ENVELOPE(element))
3452 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3454 element_clicked = TRUE;
3457 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3460 return element_clicked;
3463 static void RotateMirror(int x, int y, int button)
3465 if (button == MB_RELEASED)
3467 // release eventually hold auto-rotating mirror
3474 if (IS_MIRROR(Tile[x][y]) ||
3475 IS_POLAR_CROSS(Tile[x][y]) ||
3476 IS_POLAR(Tile[x][y]) ||
3477 IS_BEAMER(Tile[x][y]) ||
3478 IS_DF_MIRROR(Tile[x][y]) ||
3479 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3480 IS_GRID_WOOD_AUTO(Tile[x][y]))
3482 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3484 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3486 if (button == MB_LEFTBUTTON)
3488 // left mouse button only for manual adjustment, no auto-rotating;
3489 // freeze mirror for until mouse button released
3493 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3495 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3499 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3501 int edge = Hit[x][y];
3507 DrawLaser(edge - 1, DL_LASER_DISABLED);
3511 else if (ObjHit(x, y, HIT_POS_CENTER))
3513 int edge = Hit[x][y];
3517 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3522 DrawLaser(edge - 1, DL_LASER_DISABLED);
3529 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3534 if ((IS_BEAMER(Tile[x][y]) ||
3535 IS_POLAR(Tile[x][y]) ||
3536 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3538 if (IS_BEAMER(Tile[x][y]))
3541 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3542 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3555 DrawLaser(0, DL_LASER_ENABLED);
3559 static void AutoRotateMirrors(void)
3563 if (!FrameReached(&rotate_delay))
3566 for (x = 0; x < lev_fieldx; x++)
3568 for (y = 0; y < lev_fieldy; y++)
3570 int element = Tile[x][y];
3572 // do not rotate objects hit by the laser after the game was solved
3573 if (game_mm.level_solved && Hit[x][y])
3576 if (IS_DF_MIRROR_AUTO(element) ||
3577 IS_GRID_WOOD_AUTO(element) ||
3578 IS_GRID_STEEL_AUTO(element) ||
3579 element == EL_REFRACTOR)
3581 RotateMirror(x, y, MB_RIGHTBUTTON);
3583 laser.redraw = TRUE;
3589 static boolean ObjHit(int obx, int oby, int bits)
3596 if (bits & HIT_POS_CENTER)
3598 if (CheckLaserPixel(cSX + obx + 15,
3603 if (bits & HIT_POS_EDGE)
3605 for (i = 0; i < 4; i++)
3606 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3607 cSY + oby + 31 * (i / 2)))
3611 if (bits & HIT_POS_BETWEEN)
3613 for (i = 0; i < 4; i++)
3614 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3615 cSY + 4 + oby + 22 * (i / 2)))
3622 static void DeletePacMan(int px, int py)
3628 if (game_mm.num_pacman <= 1)
3630 game_mm.num_pacman = 0;
3634 for (i = 0; i < game_mm.num_pacman; i++)
3635 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3638 game_mm.num_pacman--;
3640 for (j = i; j < game_mm.num_pacman; j++)
3642 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3643 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3644 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3648 static void GameActions_MM_Ext(void)
3655 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3658 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3660 element = Tile[x][y];
3662 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3663 StartMoving_MM(x, y);
3664 else if (IS_MOVING(x, y))
3665 ContinueMoving_MM(x, y);
3666 else if (IS_EXPLODING(element))
3667 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3668 else if (element == EL_EXIT_OPENING)
3670 else if (element == EL_GRAY_BALL_OPENING)
3672 else if (IS_ENVELOPE_OPENING(element))
3674 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3676 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3678 else if (IS_MIRROR(element) ||
3679 IS_MIRROR_FIXED(element) ||
3680 element == EL_PRISM)
3681 DrawFieldTwinkle(x, y);
3682 else if (element == EL_GRAY_BALL_ACTIVE ||
3683 element == EL_BOMB_ACTIVE ||
3684 element == EL_MINE_ACTIVE)
3685 DrawFieldAnimated_MM(x, y);
3686 else if (!IS_BLOCKED(x, y))
3687 DrawFieldAnimatedIfNeeded_MM(x, y);
3690 AutoRotateMirrors();
3693 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3695 // redraw after Explode_MM() ...
3697 DrawLaser(0, DL_LASER_ENABLED);
3698 laser.redraw = FALSE;
3703 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3707 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3709 DrawLaser(0, DL_LASER_DISABLED);
3714 // skip all following game actions if game is over
3715 if (game_mm.game_over)
3718 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3722 GameOver_MM(GAME_OVER_NO_ENERGY);
3727 if (FrameReached(&energy_delay))
3729 if (game_mm.energy_left > 0)
3730 game_mm.energy_left--;
3732 // when out of energy, wait another frame to play "out of time" sound
3735 element = laser.dest_element;
3738 if (element != Tile[ELX][ELY])
3740 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3741 element, Tile[ELX][ELY]);
3745 if (!laser.overloaded && laser.overload_value == 0 &&
3746 element != EL_BOMB &&
3747 element != EL_BOMB_ACTIVE &&
3748 element != EL_MINE &&
3749 element != EL_MINE_ACTIVE &&
3750 element != EL_GRAY_BALL &&
3751 element != EL_GRAY_BALL_ACTIVE &&
3752 element != EL_BLOCK_STONE &&
3753 element != EL_BLOCK_WOOD &&
3754 element != EL_FUSE_ON &&
3755 element != EL_FUEL_FULL &&
3756 !IS_WALL_ICE(element) &&
3757 !IS_WALL_AMOEBA(element))
3760 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3762 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3763 (!laser.overloaded && laser.overload_value > 0)) &&
3764 FrameReached(&overload_delay))
3766 if (laser.overloaded)
3767 laser.overload_value++;
3769 laser.overload_value--;
3771 if (game_mm.cheat_no_overload)
3773 laser.overloaded = FALSE;
3774 laser.overload_value = 0;
3777 game_mm.laser_overload_value = laser.overload_value;
3779 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3781 SetLaserColor(0xFF);
3783 DrawLaser(0, DL_LASER_ENABLED);
3786 if (!laser.overloaded)
3787 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3788 else if (setup.sound_loops)
3789 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3791 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3793 if (laser.overload_value == MAX_LASER_OVERLOAD)
3795 UpdateAndDisplayGameControlValues();
3799 GameOver_MM(GAME_OVER_OVERLOADED);
3810 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3812 if (game_mm.cheat_no_explosion)
3817 laser.dest_element = EL_EXPLODING_OPAQUE;
3822 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3824 laser.fuse_off = TRUE;
3828 DrawLaser(0, DL_LASER_DISABLED);
3829 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3832 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3834 if (!Store2[ELX][ELY]) // check if content element not yet determined
3836 int last_anim_random_frame = gfx.anim_random_frame;
3839 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3840 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3842 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3843 native_mm_level.ball_choice_mode, 0,
3844 game_mm.ball_choice_pos);
3846 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3847 gfx.anim_random_frame = last_anim_random_frame;
3849 game_mm.ball_choice_pos++;
3851 int new_element = native_mm_level.ball_content[element_pos];
3852 int new_element_base = map_wall_to_base_element(new_element);
3854 if (IS_WALL(new_element_base))
3856 // always use completely filled wall element
3857 new_element = new_element_base | 0x000f;
3859 else if (native_mm_level.rotate_ball_content &&
3860 get_num_elements(new_element) > 1)
3862 // randomly rotate newly created game element
3863 new_element = get_rotated_element(new_element, RND(16));
3866 Store[ELX][ELY] = new_element;
3867 Store2[ELX][ELY] = TRUE;
3870 if (native_mm_level.explode_ball)
3873 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3875 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3880 if (IS_WALL_ICE(element) && CT > 50)
3882 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3884 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3885 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3886 Store2[ELX][ELY] = laser.wall_mask;
3888 laser.dest_element = Tile[ELX][ELY];
3893 if (IS_WALL_AMOEBA(element) && CT > 60)
3896 int element2 = Tile[ELX][ELY];
3898 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3901 for (i = laser.num_damages - 1; i >= 0; i--)
3902 if (laser.damage[i].is_mirror)
3905 r = laser.num_edges;
3906 d = laser.num_damages;
3913 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3916 DrawLaser(0, DL_LASER_ENABLED);
3919 x = laser.damage[k1].x;
3920 y = laser.damage[k1].y;
3925 for (i = 0; i < 4; i++)
3927 if (laser.wall_mask & (1 << i))
3929 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3930 cSY + ELY * TILEY + 31 * (i / 2)))
3933 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3934 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3941 for (i = 0; i < 4; i++)
3943 if (laser.wall_mask & (1 << i))
3945 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3946 cSY + ELY * TILEY + 31 * (i / 2)))
3953 if (laser.num_beamers > 0 ||
3954 k1 < 1 || k2 < 4 || k3 < 4 ||
3955 CheckLaserPixel(cSX + ELX * TILEX + 14,
3956 cSY + ELY * TILEY + 14))
3958 laser.num_edges = r;
3959 laser.num_damages = d;
3961 DrawLaser(0, DL_LASER_DISABLED);
3964 Tile[ELX][ELY] = element | laser.wall_mask;
3966 int x = ELX, y = ELY;
3967 int wall_mask = laser.wall_mask;
3970 DrawLaser(0, DL_LASER_ENABLED);
3972 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3974 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3975 Store[x][y] = EL_WALL_AMOEBA_BASE;
3976 Store2[x][y] = wall_mask;
3981 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3982 laser.stops_inside_element && CT > native_mm_level.time_block)
3987 if (ABS(XS) > ABS(YS))
3994 for (i = 0; i < 4; i++)
4001 x = ELX + Step[k * 4].x;
4002 y = ELY + Step[k * 4].y;
4004 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
4007 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
4015 laser.overloaded = (element == EL_BLOCK_STONE);
4020 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
4023 Tile[x][y] = element;
4025 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
4028 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
4030 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
4031 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
4039 if (element == EL_FUEL_FULL && CT > 10)
4041 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
4042 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
4044 for (i = start; i <= num_init_game_frames; i++)
4046 if (i == num_init_game_frames)
4047 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4048 else if (setup.sound_loops)
4049 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4051 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4053 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
4055 UpdateAndDisplayGameControlValues();
4060 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
4062 DrawField_MM(ELX, ELY);
4064 DrawLaser(0, DL_LASER_ENABLED);
4070 void GameActions_MM(struct MouseActionInfo action)
4072 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
4073 boolean button_released = (action.button == MB_RELEASED);
4075 GameActions_MM_Ext();
4077 CheckSingleStepMode_MM(element_clicked, button_released);
4080 static void MovePacMen(void)
4082 int mx, my, ox, oy, nx, ny;
4086 if (++pacman_nr >= game_mm.num_pacman)
4089 game_mm.pacman[pacman_nr].dir--;
4091 for (l = 1; l < 5; l++)
4093 game_mm.pacman[pacman_nr].dir++;
4095 if (game_mm.pacman[pacman_nr].dir > 4)
4096 game_mm.pacman[pacman_nr].dir = 1;
4098 if (game_mm.pacman[pacman_nr].dir % 2)
4101 my = game_mm.pacman[pacman_nr].dir - 2;
4106 mx = 3 - game_mm.pacman[pacman_nr].dir;
4109 ox = game_mm.pacman[pacman_nr].x;
4110 oy = game_mm.pacman[pacman_nr].y;
4113 element = Tile[nx][ny];
4115 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
4118 if (!IS_EATABLE4PACMAN(element))
4121 if (ObjHit(nx, ny, HIT_POS_CENTER))
4124 Tile[ox][oy] = EL_EMPTY;
4126 EL_PACMAN_RIGHT - 1 +
4127 (game_mm.pacman[pacman_nr].dir - 1 +
4128 (game_mm.pacman[pacman_nr].dir % 2) * 2);
4130 game_mm.pacman[pacman_nr].x = nx;
4131 game_mm.pacman[pacman_nr].y = ny;
4133 DrawGraphic_MM(ox, oy, IMG_EMPTY);
4135 if (element != EL_EMPTY)
4137 int graphic = el2gfx(Tile[nx][ny]);
4142 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
4145 ox = cSX + ox * TILEX;
4146 oy = cSY + oy * TILEY;
4148 for (i = 1; i < 33; i += 2)
4149 BlitBitmap(bitmap, window,
4150 src_x, src_y, TILEX, TILEY,
4151 ox + i * mx, oy + i * my);
4152 Ct = Ct + FrameCounter - CT;
4155 DrawField_MM(nx, ny);
4158 if (!laser.fuse_off)
4160 DrawLaser(0, DL_LASER_ENABLED);
4162 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
4164 AddDamagedField(nx, ny);
4166 laser.damage[laser.num_damages - 1].edge = 0;
4170 if (element == EL_BOMB)
4171 DeletePacMan(nx, ny);
4173 if (IS_WALL_AMOEBA(element) &&
4174 (LX + 2 * XS) / TILEX == nx &&
4175 (LY + 2 * YS) / TILEY == ny)
4185 static void InitMovingField_MM(int x, int y, int direction)
4187 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4188 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4190 MovDir[x][y] = direction;
4191 MovDir[newx][newy] = direction;
4193 if (Tile[newx][newy] == EL_EMPTY)
4194 Tile[newx][newy] = EL_BLOCKED;
4197 static int MovingOrBlocked2Element_MM(int x, int y)
4199 int element = Tile[x][y];
4201 if (element == EL_BLOCKED)
4205 Blocked2Moving(x, y, &oldx, &oldy);
4207 return Tile[oldx][oldy];
4213 static void RemoveMovingField_MM(int x, int y)
4215 int oldx = x, oldy = y, newx = x, newy = y;
4217 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4220 if (IS_MOVING(x, y))
4222 Moving2Blocked(x, y, &newx, &newy);
4223 if (Tile[newx][newy] != EL_BLOCKED)
4226 else if (Tile[x][y] == EL_BLOCKED)
4228 Blocked2Moving(x, y, &oldx, &oldy);
4229 if (!IS_MOVING(oldx, oldy))
4233 Tile[oldx][oldy] = EL_EMPTY;
4234 Tile[newx][newy] = EL_EMPTY;
4235 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4236 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4238 DrawLevelField_MM(oldx, oldy);
4239 DrawLevelField_MM(newx, newy);
4242 static void RaiseScore_MM(int value)
4244 game_mm.score += value;
4247 void RaiseScoreElement_MM(int element)
4252 case EL_PACMAN_RIGHT:
4254 case EL_PACMAN_LEFT:
4255 case EL_PACMAN_DOWN:
4256 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4260 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4265 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4269 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4278 // ----------------------------------------------------------------------------
4279 // Mirror Magic game engine snapshot handling functions
4280 // ----------------------------------------------------------------------------
4282 void SaveEngineSnapshotValues_MM(void)
4286 engine_snapshot_mm.game_mm = game_mm;
4287 engine_snapshot_mm.laser = laser;
4289 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4291 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4293 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4294 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4295 engine_snapshot_mm.Box[x][y] = Box[x][y];
4296 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4300 engine_snapshot_mm.LX = LX;
4301 engine_snapshot_mm.LY = LY;
4302 engine_snapshot_mm.XS = XS;
4303 engine_snapshot_mm.YS = YS;
4304 engine_snapshot_mm.ELX = ELX;
4305 engine_snapshot_mm.ELY = ELY;
4306 engine_snapshot_mm.CT = CT;
4307 engine_snapshot_mm.Ct = Ct;
4309 engine_snapshot_mm.last_LX = last_LX;
4310 engine_snapshot_mm.last_LY = last_LY;
4311 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4312 engine_snapshot_mm.hold_x = hold_x;
4313 engine_snapshot_mm.hold_y = hold_y;
4314 engine_snapshot_mm.pacman_nr = pacman_nr;
4316 engine_snapshot_mm.rotate_delay = rotate_delay;
4317 engine_snapshot_mm.pacman_delay = pacman_delay;
4318 engine_snapshot_mm.energy_delay = energy_delay;
4319 engine_snapshot_mm.overload_delay = overload_delay;
4322 void LoadEngineSnapshotValues_MM(void)
4326 // stored engine snapshot buffers already restored at this point
4328 game_mm = engine_snapshot_mm.game_mm;
4329 laser = engine_snapshot_mm.laser;
4331 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4333 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4335 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4336 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4337 Box[x][y] = engine_snapshot_mm.Box[x][y];
4338 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4342 LX = engine_snapshot_mm.LX;
4343 LY = engine_snapshot_mm.LY;
4344 XS = engine_snapshot_mm.XS;
4345 YS = engine_snapshot_mm.YS;
4346 ELX = engine_snapshot_mm.ELX;
4347 ELY = engine_snapshot_mm.ELY;
4348 CT = engine_snapshot_mm.CT;
4349 Ct = engine_snapshot_mm.Ct;
4351 last_LX = engine_snapshot_mm.last_LX;
4352 last_LY = engine_snapshot_mm.last_LY;
4353 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4354 hold_x = engine_snapshot_mm.hold_x;
4355 hold_y = engine_snapshot_mm.hold_y;
4356 pacman_nr = engine_snapshot_mm.pacman_nr;
4358 rotate_delay = engine_snapshot_mm.rotate_delay;
4359 pacman_delay = engine_snapshot_mm.pacman_delay;
4360 energy_delay = engine_snapshot_mm.energy_delay;
4361 overload_delay = engine_snapshot_mm.overload_delay;
4363 RedrawPlayfield_MM();
4366 static int getAngleFromTouchDelta(int dx, int dy, int base)
4368 double pi = 3.141592653;
4369 double rad = atan2((double)-dy, (double)dx);
4370 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4371 double deg = rad2 * 180.0 / pi;
4373 return (int)(deg * base / 360.0 + 0.5) % base;
4376 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4378 // calculate start (source) position to be at the middle of the tile
4379 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4380 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4381 int dx = dst_mx - src_mx;
4382 int dy = dst_my - src_my;
4391 if (!IN_LEV_FIELD(x, y))
4394 element = Tile[x][y];
4396 if (!IS_MCDUFFIN(element) &&
4397 !IS_MIRROR(element) &&
4398 !IS_BEAMER(element) &&
4399 !IS_POLAR(element) &&
4400 !IS_POLAR_CROSS(element) &&
4401 !IS_DF_MIRROR(element))
4404 angle_old = get_element_angle(element);
4406 if (IS_MCDUFFIN(element))
4408 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4409 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4410 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4411 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4414 else if (IS_MIRROR(element) ||
4415 IS_DF_MIRROR(element))
4417 for (i = 0; i < laser.num_damages; i++)
4419 if (laser.damage[i].x == x &&
4420 laser.damage[i].y == y &&
4421 ObjHit(x, y, HIT_POS_CENTER))
4423 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4424 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4431 if (angle_new == -1)
4433 if (IS_MIRROR(element) ||
4434 IS_DF_MIRROR(element) ||
4438 if (IS_POLAR_CROSS(element))
4441 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4444 button = (angle_new == angle_old ? 0 :
4445 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4446 MB_LEFTBUTTON : MB_RIGHTBUTTON);