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();
845 if (setup.quick_doors)
852 if (game_mm.kettles_still_needed == 0)
855 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
856 SetTileCursorActive(TRUE);
858 // restart all delay counters after initially cycling game elements
859 ResetFrameCounter(&rotate_delay);
860 ResetFrameCounter(&pacman_delay);
861 ResetFrameCounter(&energy_delay);
862 ResetFrameCounter(&overload_delay);
865 static void FadeOutLaser(void)
869 for (i = 15; i >= 0; i--)
871 SetLaserColor(0x11 * i);
873 DrawLaser(0, DL_LASER_ENABLED);
876 Delay_WithScreenUpdates(50);
879 DrawLaser(0, DL_LASER_DISABLED);
881 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
884 static void GameOver_MM(int game_over_cause)
886 game_mm.game_over = TRUE;
887 game_mm.game_over_cause = game_over_cause;
888 game_mm.game_over_message = (game_mm.has_mcduffin ?
889 (game_over_cause == GAME_OVER_BOMB ?
890 "Bomb killed Mc Duffin!" :
891 game_over_cause == GAME_OVER_NO_ENERGY ?
892 "Out of magic energy!" :
893 game_over_cause == GAME_OVER_OVERLOADED ?
894 "Magic spell hit Mc Duffin!" :
896 (game_over_cause == GAME_OVER_BOMB ?
897 "Bomb destroyed laser cannon!" :
898 game_over_cause == GAME_OVER_NO_ENERGY ?
899 "Out of laser energy!" :
900 game_over_cause == GAME_OVER_OVERLOADED ?
901 "Laser beam hit laser cannon!" :
904 SetTileCursorActive(FALSE);
907 static void AddLaserEdge(int lx, int ly)
909 int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
910 int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
912 // check if laser is still inside visible playfield area (or inside level)
913 if (cSX + lx < REAL_SX || cSX + lx >= REAL_SX + full_sxsize ||
914 cSY + ly < REAL_SY || cSY + ly >= REAL_SY + full_sysize)
916 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
921 laser.edge[laser.num_edges].x = cSX2 + lx;
922 laser.edge[laser.num_edges].y = cSY2 + ly;
928 static void AddDamagedField(int ex, int ey)
930 // prevent adding the same field position again
931 if (laser.num_damages > 0 &&
932 laser.damage[laser.num_damages - 1].x == ex &&
933 laser.damage[laser.num_damages - 1].y == ey &&
934 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
937 laser.damage[laser.num_damages].is_mirror = FALSE;
938 laser.damage[laser.num_damages].angle = laser.current_angle;
939 laser.damage[laser.num_damages].edge = laser.num_edges;
940 laser.damage[laser.num_damages].x = ex;
941 laser.damage[laser.num_damages].y = ey;
945 static boolean StepBehind(void)
951 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
952 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
954 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
960 static int getMaskFromElement(int element)
962 if (IS_MCDUFFIN(element))
963 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
964 else if (IS_GRID(element))
965 return MM_MASK_GRID_1 + get_element_phase(element);
966 else if (IS_DF_GRID(element))
967 return MM_MASK_RECTANGLE;
968 else if (IS_DF_SLOPE(element))
969 return MM_MASK_SLOPE_1 + get_element_phase(element);
970 else if (IS_RECTANGLE(element))
971 return MM_MASK_RECTANGLE;
973 return MM_MASK_CIRCLE;
976 static int getLevelFromLaserX(int x)
978 return x / TILEX - (x < 0 ? 1 : 0); // correct negative values
981 static int getLevelFromLaserY(int y)
983 return y / TILEY - (y < 0 ? 1 : 0); // correct negative values
986 static int ScanPixel(void)
991 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
992 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
995 // follow laser beam until it hits something (at least the screen border)
996 while (hit_mask == HIT_MASK_NO_HIT)
1002 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
1003 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
1005 Debug("game:mm:ScanPixel", "touched screen border!");
1007 return HIT_MASK_ALL;
1011 // check if laser scan has crossed element boundaries (not just mini tiles)
1012 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1013 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1015 if (cross_x && cross_y)
1017 int elx1 = (LX - XS) / TILEX;
1018 int ely1 = (LY + YS) / TILEY;
1019 int elx2 = (LX + XS) / TILEX;
1020 int ely2 = (LY - YS) / TILEY;
1022 // add element corners left and right from the laser beam to damage list
1024 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
1025 AddDamagedField(elx1, ely1);
1027 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
1028 AddDamagedField(elx2, ely2);
1031 for (i = 0; i < 4; i++)
1033 int px = LX + (i % 2) * 2;
1034 int py = LY + (i / 2) * 2;
1035 int dx = px % TILEX;
1036 int dy = py % TILEY;
1037 int lx = getLevelFromLaserX(px);
1038 int ly = getLevelFromLaserY(py);
1041 if (IN_LEV_FIELD(lx, ly))
1043 int element = Tile[lx][ly];
1045 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
1049 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
1051 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1053 pixel = ((element & (1 << pos)) ? 1 : 0);
1057 int pos = getMaskFromElement(element);
1059 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
1064 // check if laser is still inside visible playfield area
1065 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
1066 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
1069 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
1070 hit_mask |= (1 << i);
1073 if (hit_mask == HIT_MASK_NO_HIT)
1075 // hit nothing -- go on with another step
1084 static void DeactivateLaserTargetElement(void)
1086 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1087 laser.dest_element_last == EL_MINE_ACTIVE ||
1088 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1089 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1091 int x = laser.dest_element_last_x;
1092 int y = laser.dest_element_last_y;
1093 int element = laser.dest_element_last;
1095 if (Tile[x][y] == element)
1096 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1097 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1099 if (Tile[x][y] == EL_GRAY_BALL)
1102 laser.dest_element_last = EL_EMPTY;
1103 laser.dest_element_last_x = -1;
1104 laser.dest_element_last_y = -1;
1108 static void ScanLaser(void)
1110 int element = EL_EMPTY;
1111 int last_element = EL_EMPTY;
1112 int end = 0, rf = laser.num_edges;
1114 // do not scan laser again after the game was lost for whatever reason
1115 if (game_mm.game_over)
1118 // do not scan laser if fuse is off
1122 DeactivateLaserTargetElement();
1124 laser.overloaded = FALSE;
1125 laser.stops_inside_element = FALSE;
1127 DrawLaser(0, DL_LASER_ENABLED);
1130 Debug("game:mm:ScanLaser",
1131 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1139 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1142 laser.overloaded = TRUE;
1147 hit_mask = ScanPixel();
1150 Debug("game:mm:ScanLaser",
1151 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1155 // check if laser scan has hit two diagonally adjacent element corners
1156 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1157 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1159 // check if laser scan has crossed element boundaries (not just mini tiles)
1160 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1161 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1163 if (cross_x || cross_y)
1165 // hit something at next tile -- check out what it was
1166 ELX = getLevelFromLaserX(LX + XS);
1167 ELY = getLevelFromLaserY(LY + YS);
1171 // hit something at same tile -- check out what it was
1172 ELX = getLevelFromLaserX(LX);
1173 ELY = getLevelFromLaserY(LY);
1177 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1178 hit_mask, LX, LY, ELX, ELY);
1181 if (!IN_LEV_FIELD(ELX, ELY))
1183 // laser next step position
1184 int x = cSX + LX + XS;
1185 int y = cSY + LY + YS;
1187 // check if next step of laser is still inside visible playfield area
1188 if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
1189 y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
1191 // go on with another step
1199 laser.dest_element = element;
1204 boolean diagonally_adjacent_hit = FALSE;
1206 // handle special case of laser hitting two diagonally adjacent elements
1207 // (with or without a third corner element behind these two elements)
1208 if ((diag_1 || diag_2) && cross_x && cross_y)
1210 diagonally_adjacent_hit = TRUE;
1212 // compare the two diagonally adjacent elements
1214 int yoffset = 2 * (diag_1 ? -1 : +1);
1215 int elx1 = (LX - xoffset) / TILEX;
1216 int ely1 = (LY + yoffset) / TILEY;
1217 int elx2 = (LX + xoffset) / TILEX;
1218 int ely2 = (LY - yoffset) / TILEY;
1219 int e1 = Tile[elx1][ely1];
1220 int e2 = Tile[elx2][ely2];
1221 boolean use_element_1 = FALSE;
1223 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1225 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1226 use_element_1 = (RND(2) ? TRUE : FALSE);
1227 else if (IS_WALL_ICE(e1))
1228 use_element_1 = TRUE;
1230 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1232 // if both tiles match, we can just select the first one
1233 if (IS_WALL_AMOEBA(e1))
1234 use_element_1 = TRUE;
1236 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1238 // if both tiles match, we can just select the first one
1239 if (IS_ABSORBING_BLOCK(e1))
1240 use_element_1 = TRUE;
1243 ELX = (use_element_1 ? elx1 : elx2);
1244 ELY = (use_element_1 ? ely1 : ely2);
1248 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1249 hit_mask, LX, LY, ELX, ELY);
1252 last_element = element;
1254 element = Tile[ELX][ELY];
1255 laser.dest_element = element;
1258 Debug("game:mm:ScanLaser",
1259 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1262 LX % TILEX, LY % TILEY,
1267 if (!IN_LEV_FIELD(ELX, ELY))
1268 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1272 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1273 if (element == EL_EMPTY &&
1274 IS_GRID_STEEL(last_element) &&
1275 laser.current_angle % 4) // angle is not 90°
1276 element = last_element;
1278 if (element == EL_EMPTY)
1280 if (!HitOnlyAnEdge(hit_mask))
1283 else if (element == EL_FUSE_ON)
1285 if (HitPolarizer(element, hit_mask))
1288 else if (IS_GRID(element) || IS_DF_GRID(element))
1290 if (HitPolarizer(element, hit_mask))
1293 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1294 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1296 if (HitBlock(element, hit_mask))
1303 else if (IS_MCDUFFIN(element))
1305 if (HitLaserSource(element, hit_mask))
1308 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1309 IS_RECEIVER(element))
1311 if (HitLaserDestination(element, hit_mask))
1314 else if (IS_WALL(element))
1316 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1318 if (HitReflectingWalls(element, hit_mask))
1323 if (HitAbsorbingWalls(element, hit_mask))
1327 else if (IS_DF_SLOPE(element))
1329 if (diagonally_adjacent_hit)
1331 laser.overloaded = TRUE;
1336 if (hit_mask == HIT_MASK_LEFT ||
1337 hit_mask == HIT_MASK_RIGHT ||
1338 hit_mask == HIT_MASK_TOP ||
1339 hit_mask == HIT_MASK_BOTTOM)
1341 if (HitReflectingWalls(element, hit_mask))
1346 if (HitElement(element, hit_mask))
1352 if (HitElement(element, hit_mask))
1357 DrawLaser(rf - 1, DL_LASER_ENABLED);
1358 rf = laser.num_edges;
1360 if (!IS_DF_WALL_STEEL(element))
1362 // only used for scanning DF steel walls; reset for all other elements
1370 if (laser.dest_element != Tile[ELX][ELY])
1372 Debug("game:mm:ScanLaser",
1373 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1374 laser.dest_element, Tile[ELX][ELY]);
1378 if (!end && !laser.stops_inside_element && !StepBehind())
1381 Debug("game:mm:ScanLaser", "Go one step back");
1387 AddLaserEdge(LX, LY);
1391 DrawLaser(rf - 1, DL_LASER_ENABLED);
1393 Ct = CT = FrameCounter;
1396 if (!IN_LEV_FIELD(ELX, ELY))
1397 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1401 static void ScanLaser_FromLastMirror(void)
1403 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1406 for (i = start_pos; i >= 0; i--)
1407 if (laser.damage[i].is_mirror)
1410 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1412 DrawLaser(start_edge, DL_LASER_DISABLED);
1417 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1423 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1424 start_edge, num_edges, mode);
1429 Warn("DrawLaserExt: start_edge < 0");
1436 Warn("DrawLaserExt: num_edges < 0");
1442 if (mode == DL_LASER_DISABLED)
1444 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1448 // now draw the laser to the backbuffer and (if enabled) to the screen
1449 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1451 redraw_mask |= REDRAW_FIELD;
1453 if (mode == DL_LASER_ENABLED)
1456 // after the laser was deleted, the "damaged" graphics must be restored
1457 if (laser.num_damages)
1459 int damage_start = 0;
1462 // determine the starting edge, from which graphics need to be restored
1465 for (i = 0; i < laser.num_damages; i++)
1467 if (laser.damage[i].edge == start_edge + 1)
1476 // restore graphics from this starting edge to the end of damage list
1477 for (i = damage_start; i < laser.num_damages; i++)
1479 int lx = laser.damage[i].x;
1480 int ly = laser.damage[i].y;
1481 int element = Tile[lx][ly];
1483 if (Hit[lx][ly] == laser.damage[i].edge)
1484 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1487 if (Box[lx][ly] == laser.damage[i].edge)
1490 if (IS_DRAWABLE(element))
1491 DrawField_MM(lx, ly);
1494 elx = laser.damage[damage_start].x;
1495 ely = laser.damage[damage_start].y;
1496 element = Tile[elx][ely];
1499 if (IS_BEAMER(element))
1503 for (i = 0; i < laser.num_beamers; i++)
1504 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1506 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1507 mode, elx, ely, Hit[elx][ely], start_edge);
1508 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1509 get_element_angle(element), laser.damage[damage_start].angle);
1513 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1514 laser.num_beamers > 0 &&
1515 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1517 // element is outgoing beamer
1518 laser.num_damages = damage_start + 1;
1520 if (IS_BEAMER(element))
1521 laser.current_angle = get_element_angle(element);
1525 // element is incoming beamer or other element
1526 laser.num_damages = damage_start;
1527 laser.current_angle = laser.damage[laser.num_damages].angle;
1532 // no damages but McDuffin himself (who needs to be redrawn anyway)
1534 elx = laser.start_edge.x;
1535 ely = laser.start_edge.y;
1536 element = Tile[elx][ely];
1539 laser.num_edges = start_edge + 1;
1540 if (start_edge == 0)
1541 laser.current_angle = laser.start_angle;
1543 LX = laser.edge[start_edge].x - cSX2;
1544 LY = laser.edge[start_edge].y - cSY2;
1545 XS = 2 * Step[laser.current_angle].x;
1546 YS = 2 * Step[laser.current_angle].y;
1549 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1555 if (IS_BEAMER(element) ||
1556 IS_FIBRE_OPTIC(element) ||
1557 IS_PACMAN(element) ||
1558 IS_POLAR(element) ||
1559 IS_POLAR_CROSS(element) ||
1560 element == EL_FUSE_ON)
1565 Debug("game:mm:DrawLaserExt", "element == %d", element);
1568 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1569 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1573 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1574 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1575 (laser.num_beamers == 0 ||
1576 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1578 // element is incoming beamer or other element
1579 step_size = -step_size;
1584 if (IS_BEAMER(element))
1585 Debug("game:mm:DrawLaserExt",
1586 "start_edge == %d, laser.beamer_edge == %d",
1587 start_edge, laser.beamer_edge);
1590 LX += step_size * XS;
1591 LY += step_size * YS;
1593 else if (element != EL_EMPTY)
1602 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1607 void DrawLaser(int start_edge, int mode)
1609 // do not draw laser if fuse is off
1610 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1613 if (mode == DL_LASER_DISABLED)
1614 DeactivateLaserTargetElement();
1616 if (laser.num_edges - start_edge < 0)
1618 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1623 // check if laser is interrupted by beamer element
1624 if (laser.num_beamers > 0 &&
1625 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1627 if (mode == DL_LASER_ENABLED)
1630 int tmp_start_edge = start_edge;
1632 // draw laser segments forward from the start to the last beamer
1633 for (i = 0; i < laser.num_beamers; i++)
1635 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1637 if (tmp_num_edges <= 0)
1641 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1642 i, laser.beamer_edge[i], tmp_start_edge);
1645 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1647 tmp_start_edge = laser.beamer_edge[i];
1650 // draw last segment from last beamer to the end
1651 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1657 int last_num_edges = laser.num_edges;
1658 int num_beamers = laser.num_beamers;
1660 // delete laser segments backward from the end to the first beamer
1661 for (i = num_beamers - 1; i >= 0; i--)
1663 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1665 if (laser.beamer_edge[i] - start_edge <= 0)
1668 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1670 last_num_edges = laser.beamer_edge[i];
1671 laser.num_beamers--;
1675 if (last_num_edges - start_edge <= 0)
1676 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1677 last_num_edges, start_edge);
1680 // special case when rotating first beamer: delete laser edge on beamer
1681 // (but do not start scanning on previous edge to prevent mirror sound)
1682 if (last_num_edges - start_edge == 1 && start_edge > 0)
1683 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1685 // delete first segment from start to the first beamer
1686 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1691 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1694 game_mm.laser_enabled = mode;
1697 void DrawLaser_MM(void)
1699 DrawLaser(0, game_mm.laser_enabled);
1702 static boolean HitElement(int element, int hit_mask)
1704 if (IS_DF_SLOPE(element))
1706 int mirrored_angle = get_mirrored_angle(laser.current_angle,
1707 get_element_angle(element));
1708 int opposite_angle = get_opposite_angle(laser.current_angle);
1710 // check if laser is reflected by slope by 180°
1711 if (mirrored_angle == opposite_angle)
1716 AddDamagedField(LX / TILEX, LY / TILEY);
1718 laser.overloaded = TRUE;
1725 if (HitOnlyAnEdge(hit_mask))
1729 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1730 element = MovingOrBlocked2Element_MM(ELX, ELY);
1733 Debug("game:mm:HitElement", "(1): element == %d", element);
1737 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1738 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1741 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1745 AddDamagedField(ELX, ELY);
1747 boolean through_center = ((ELX * TILEX + 14 - LX) * YS ==
1748 (ELY * TILEY + 14 - LY) * XS);
1750 // this is more precise: check if laser would go through the center
1751 if (!IS_DF_SLOPE(element) && !through_center)
1755 // prevent cutting through laser emitter with laser beam
1756 if (IS_LASER(element))
1759 // skip the whole element before continuing the scan
1767 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1769 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1771 /* skipping scan positions to the right and down skips one scan
1772 position too much, because this is only the top left scan position
1773 of totally four scan positions (plus one to the right, one to the
1774 bottom and one to the bottom right) */
1775 /* ... but only roll back scan position if more than one step done */
1785 Debug("game:mm:HitElement", "(2): element == %d", element);
1788 if (LX + 5 * XS < 0 ||
1798 Debug("game:mm:HitElement", "(3): element == %d", element);
1801 if (IS_POLAR(element) &&
1802 ((element - EL_POLAR_START) % 2 ||
1803 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1805 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1807 laser.num_damages--;
1812 if (IS_POLAR_CROSS(element) &&
1813 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1815 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1817 laser.num_damages--;
1822 if (IS_DF_SLOPE(element) && !through_center)
1826 if (hit_mask == HIT_MASK_ALL)
1828 // laser already inside slope -- go back half step
1835 AddLaserEdge(LX, LY);
1837 LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0);
1838 LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0);
1840 else if (!IS_BEAMER(element) &&
1841 !IS_FIBRE_OPTIC(element) &&
1842 !IS_GRID_WOOD(element) &&
1843 element != EL_FUEL_EMPTY)
1846 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1847 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1849 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1852 LX = ELX * TILEX + 14;
1853 LY = ELY * TILEY + 14;
1855 AddLaserEdge(LX, LY);
1858 if (IS_MIRROR(element) ||
1859 IS_MIRROR_FIXED(element) ||
1860 IS_POLAR(element) ||
1861 IS_POLAR_CROSS(element) ||
1862 IS_DF_MIRROR(element) ||
1863 IS_DF_MIRROR_AUTO(element) ||
1864 IS_DF_MIRROR_FIXED(element) ||
1865 IS_DF_SLOPE(element) ||
1866 element == EL_PRISM ||
1867 element == EL_REFRACTOR)
1869 int current_angle = laser.current_angle;
1872 laser.num_damages--;
1874 AddDamagedField(ELX, ELY);
1876 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1879 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1881 if (IS_MIRROR(element) ||
1882 IS_MIRROR_FIXED(element) ||
1883 IS_DF_MIRROR(element) ||
1884 IS_DF_MIRROR_AUTO(element) ||
1885 IS_DF_MIRROR_FIXED(element) ||
1886 IS_DF_SLOPE(element))
1887 laser.current_angle = get_mirrored_angle(laser.current_angle,
1888 get_element_angle(element));
1890 if (element == EL_PRISM || element == EL_REFRACTOR)
1891 laser.current_angle = RND(16);
1893 XS = 2 * Step[laser.current_angle].x;
1894 YS = 2 * Step[laser.current_angle].y;
1896 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1901 LX += step_size * XS;
1902 LY += step_size * YS;
1904 // draw sparkles on mirror
1905 if ((IS_MIRROR(element) ||
1906 IS_MIRROR_FIXED(element) ||
1907 element == EL_PRISM) &&
1908 current_angle != laser.current_angle)
1910 MovDelay[ELX][ELY] = 11; // start animation
1913 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1914 current_angle != laser.current_angle)
1915 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1918 (get_opposite_angle(laser.current_angle) ==
1919 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1921 if (IS_DF_SLOPE(element))
1923 // handle special cases for slope element
1925 if (IS_45_ANGLE(laser.current_angle))
1929 elx = getLevelFromLaserX(LX);
1930 ely = getLevelFromLaserY(LY);
1932 if (IN_LEV_FIELD(elx, ely))
1934 int element_next = Tile[elx][ely];
1936 // check if slope is followed by slope with opposite orientation
1937 if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2)
1938 laser.overloaded = TRUE;
1941 int nr = element - EL_DF_SLOPE_START;
1942 int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) :
1943 nr == 1 ? (XS > 0 ? TILEX : 1) :
1944 nr == 2 ? (XS > 0 ? TILEX : 1) :
1945 nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0);
1946 int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) :
1947 nr == 1 ? (YS > 0 ? TILEY - 1 : -1) :
1948 nr == 2 ? (YS > 0 ? TILEY : 0) :
1949 nr == 3 ? (YS > 0 ? TILEY : 0) : 0);
1951 int px = ELX * TILEX + dx;
1952 int py = ELY * TILEY + dy;
1957 elx = getLevelFromLaserX(px);
1958 ely = getLevelFromLaserY(py);
1960 if (IN_LEV_FIELD(elx, ely))
1962 int element_side = Tile[elx][ely];
1964 // check if end of slope is blocked by other element
1965 if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side))
1967 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1969 if (element & (1 << pos))
1970 laser.overloaded = TRUE;
1974 int pos = getMaskFromElement(element_side);
1976 if (mm_masks[pos][dx / 2][dx / 2] == 'X')
1977 laser.overloaded = TRUE;
1983 return (laser.overloaded ? TRUE : FALSE);
1986 if (element == EL_FUEL_FULL)
1988 laser.stops_inside_element = TRUE;
1993 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1995 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1997 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1998 element == EL_MINE ? EL_MINE_ACTIVE :
1999 EL_GRAY_BALL_ACTIVE);
2001 GfxFrame[ELX][ELY] = 0; // restart animation
2003 laser.dest_element_last = Tile[ELX][ELY];
2004 laser.dest_element_last_x = ELX;
2005 laser.dest_element_last_y = ELY;
2007 if (element == EL_MINE)
2008 laser.overloaded = TRUE;
2011 if (element == EL_KETTLE ||
2012 element == EL_CELL ||
2013 element == EL_KEY ||
2014 element == EL_LIGHTBALL ||
2015 element == EL_PACMAN ||
2016 IS_PACMAN(element) ||
2017 IS_ENVELOPE(element))
2019 if (!IS_PACMAN(element) &&
2020 !IS_ENVELOPE(element))
2023 if (element == EL_PACMAN)
2026 if (element == EL_KETTLE || element == EL_CELL)
2028 if (game_mm.kettles_still_needed > 0)
2029 game_mm.kettles_still_needed--;
2031 game.snapshot.collected_item = TRUE;
2033 if (game_mm.kettles_still_needed == 0)
2037 DrawLaser(0, DL_LASER_ENABLED);
2040 else if (element == EL_KEY)
2044 else if (IS_PACMAN(element))
2046 DeletePacMan(ELX, ELY);
2048 else if (IS_ENVELOPE(element))
2050 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
2053 RaiseScoreElement_MM(element);
2058 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
2060 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2062 DrawLaser(0, DL_LASER_ENABLED);
2064 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
2066 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
2067 game_mm.lights_still_needed--;
2071 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
2072 game_mm.lights_still_needed++;
2075 DrawField_MM(ELX, ELY);
2076 DrawLaser(0, DL_LASER_ENABLED);
2081 laser.stops_inside_element = TRUE;
2087 Debug("game:mm:HitElement", "(4): element == %d", element);
2090 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
2091 laser.num_beamers < MAX_NUM_BEAMERS &&
2092 laser.beamer[BEAMER_NR(element)][1].num)
2094 int beamer_angle = get_element_angle(element);
2095 int beamer_nr = BEAMER_NR(element);
2099 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
2102 laser.num_damages--;
2104 if (IS_FIBRE_OPTIC(element) ||
2105 laser.current_angle == get_opposite_angle(beamer_angle))
2109 LX = ELX * TILEX + 14;
2110 LY = ELY * TILEY + 14;
2112 AddLaserEdge(LX, LY);
2113 AddDamagedField(ELX, ELY);
2115 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2118 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2120 pos = (ELX == laser.beamer[beamer_nr][0].x &&
2121 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
2122 ELX = laser.beamer[beamer_nr][pos].x;
2123 ELY = laser.beamer[beamer_nr][pos].y;
2124 LX = ELX * TILEX + 14;
2125 LY = ELY * TILEY + 14;
2127 if (IS_BEAMER(element))
2129 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
2130 XS = 2 * Step[laser.current_angle].x;
2131 YS = 2 * Step[laser.current_angle].y;
2134 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
2136 AddLaserEdge(LX, LY);
2137 AddDamagedField(ELX, ELY);
2139 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2142 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2144 if (laser.current_angle == (laser.current_angle >> 1) << 1)
2149 LX += step_size * XS;
2150 LY += step_size * YS;
2152 laser.num_beamers++;
2161 static boolean HitOnlyAnEdge(int hit_mask)
2163 // check if the laser hit only the edge of an element and, if so, go on
2166 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
2170 if ((hit_mask == HIT_MASK_TOPLEFT ||
2171 hit_mask == HIT_MASK_TOPRIGHT ||
2172 hit_mask == HIT_MASK_BOTTOMLEFT ||
2173 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
2174 laser.current_angle % 4) // angle is not 90°
2178 if (hit_mask == HIT_MASK_TOPLEFT)
2183 else if (hit_mask == HIT_MASK_TOPRIGHT)
2188 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
2193 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
2199 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
2205 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
2212 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
2218 static boolean HitPolarizer(int element, int hit_mask)
2220 if (HitOnlyAnEdge(hit_mask))
2223 if (IS_DF_GRID(element))
2225 int grid_angle = get_element_angle(element);
2228 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2229 grid_angle, laser.current_angle);
2232 AddLaserEdge(LX, LY);
2233 AddDamagedField(ELX, ELY);
2236 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2238 if (laser.current_angle == grid_angle ||
2239 laser.current_angle == get_opposite_angle(grid_angle))
2241 // skip the whole element before continuing the scan
2247 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2249 if (LX/TILEX > ELX || LY/TILEY > ELY)
2251 /* skipping scan positions to the right and down skips one scan
2252 position too much, because this is only the top left scan position
2253 of totally four scan positions (plus one to the right, one to the
2254 bottom and one to the bottom right) */
2260 AddLaserEdge(LX, LY);
2266 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2268 LX / TILEX, LY / TILEY,
2269 LX % TILEX, LY % TILEY);
2274 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2276 return HitReflectingWalls(element, hit_mask);
2280 return HitAbsorbingWalls(element, hit_mask);
2283 else if (IS_GRID_STEEL(element))
2285 // may be required if graphics for steel grid redefined
2286 AddDamagedField(ELX, ELY);
2288 return HitReflectingWalls(element, hit_mask);
2290 else // IS_GRID_WOOD
2292 // may be required if graphics for wooden grid redefined
2293 AddDamagedField(ELX, ELY);
2295 return HitAbsorbingWalls(element, hit_mask);
2301 static boolean HitBlock(int element, int hit_mask)
2303 boolean check = FALSE;
2305 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2306 game_mm.num_keys == 0)
2309 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2312 int ex = ELX * TILEX + 14;
2313 int ey = ELY * TILEY + 14;
2317 for (i = 1; i < 32; i++)
2322 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2327 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2328 return HitAbsorbingWalls(element, hit_mask);
2332 AddLaserEdge(LX - XS, LY - YS);
2333 AddDamagedField(ELX, ELY);
2336 Box[ELX][ELY] = laser.num_edges;
2338 return HitReflectingWalls(element, hit_mask);
2341 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2343 int xs = XS / 2, ys = YS / 2;
2345 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2346 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2348 laser.overloaded = (element == EL_GATE_STONE);
2353 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2354 (hit_mask == HIT_MASK_TOP ||
2355 hit_mask == HIT_MASK_LEFT ||
2356 hit_mask == HIT_MASK_RIGHT ||
2357 hit_mask == HIT_MASK_BOTTOM))
2358 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2359 hit_mask == HIT_MASK_BOTTOM),
2360 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2361 hit_mask == HIT_MASK_RIGHT));
2362 AddLaserEdge(LX, LY);
2368 if (element == EL_GATE_STONE && Box[ELX][ELY])
2370 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2382 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2384 int xs = XS / 2, ys = YS / 2;
2386 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2387 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2389 laser.overloaded = (element == EL_BLOCK_STONE);
2394 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2395 (hit_mask == HIT_MASK_TOP ||
2396 hit_mask == HIT_MASK_LEFT ||
2397 hit_mask == HIT_MASK_RIGHT ||
2398 hit_mask == HIT_MASK_BOTTOM))
2399 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2400 hit_mask == HIT_MASK_BOTTOM),
2401 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2402 hit_mask == HIT_MASK_RIGHT));
2403 AddDamagedField(ELX, ELY);
2405 LX = ELX * TILEX + 14;
2406 LY = ELY * TILEY + 14;
2408 AddLaserEdge(LX, LY);
2410 laser.stops_inside_element = TRUE;
2418 static boolean HitLaserSource(int element, int hit_mask)
2420 if (HitOnlyAnEdge(hit_mask))
2423 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2425 laser.overloaded = TRUE;
2430 static boolean HitLaserDestination(int element, int hit_mask)
2432 if (HitOnlyAnEdge(hit_mask))
2435 if (element != EL_EXIT_OPEN &&
2436 !(IS_RECEIVER(element) &&
2437 game_mm.kettles_still_needed == 0 &&
2438 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2440 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2445 if (IS_RECEIVER(element) ||
2446 (IS_22_5_ANGLE(laser.current_angle) &&
2447 (ELX != (LX + 6 * XS) / TILEX ||
2448 ELY != (LY + 6 * YS) / TILEY ||
2457 LX = ELX * TILEX + 14;
2458 LY = ELY * TILEY + 14;
2460 laser.stops_inside_element = TRUE;
2463 AddLaserEdge(LX, LY);
2464 AddDamagedField(ELX, ELY);
2466 if (game_mm.lights_still_needed == 0)
2468 game_mm.level_solved = TRUE;
2470 SetTileCursorActive(FALSE);
2476 static boolean HitReflectingWalls(int element, int hit_mask)
2478 // check if laser hits side of a wall with an angle that is not 90°
2479 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2480 hit_mask == HIT_MASK_LEFT ||
2481 hit_mask == HIT_MASK_RIGHT ||
2482 hit_mask == HIT_MASK_BOTTOM))
2484 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2489 if (!IS_DF_GRID(element))
2490 AddLaserEdge(LX, LY);
2492 // check if laser hits wall with an angle of 45°
2493 if (!IS_22_5_ANGLE(laser.current_angle))
2495 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2498 laser.current_angle = get_mirrored_angle(laser.current_angle,
2501 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2504 laser.current_angle = get_mirrored_angle(laser.current_angle,
2508 AddLaserEdge(LX, LY);
2510 XS = 2 * Step[laser.current_angle].x;
2511 YS = 2 * Step[laser.current_angle].y;
2515 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2517 laser.current_angle = get_mirrored_angle(laser.current_angle,
2522 if (!IS_DF_GRID(element))
2523 AddLaserEdge(LX, LY);
2528 if (!IS_DF_GRID(element))
2529 AddLaserEdge(LX, LY + YS / 2);
2532 if (!IS_DF_GRID(element))
2533 AddLaserEdge(LX, LY);
2536 YS = 2 * Step[laser.current_angle].y;
2540 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2542 laser.current_angle = get_mirrored_angle(laser.current_angle,
2547 if (!IS_DF_GRID(element))
2548 AddLaserEdge(LX, LY);
2553 if (!IS_DF_GRID(element))
2554 AddLaserEdge(LX + XS / 2, LY);
2557 if (!IS_DF_GRID(element))
2558 AddLaserEdge(LX, LY);
2561 XS = 2 * Step[laser.current_angle].x;
2567 // reflection at the edge of reflecting DF style wall
2568 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2570 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2571 hit_mask == HIT_MASK_TOPRIGHT) ||
2572 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2573 hit_mask == HIT_MASK_TOPLEFT) ||
2574 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2575 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2576 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2577 hit_mask == HIT_MASK_BOTTOMRIGHT))
2580 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2581 ANG_MIRROR_135 : ANG_MIRROR_45);
2583 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2585 AddDamagedField(ELX, ELY);
2586 AddLaserEdge(LX, LY);
2588 laser.current_angle = get_mirrored_angle(laser.current_angle,
2596 AddLaserEdge(LX, LY);
2602 // reflection inside an edge of reflecting DF style wall
2603 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2605 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2606 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2607 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2608 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2609 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2610 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2611 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2612 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2615 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2616 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2617 ANG_MIRROR_135 : ANG_MIRROR_45);
2619 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2622 AddDamagedField(ELX, ELY);
2625 AddLaserEdge(LX - XS, LY - YS);
2626 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2627 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2629 laser.current_angle = get_mirrored_angle(laser.current_angle,
2637 AddLaserEdge(LX, LY);
2643 // check if laser hits DF style wall with an angle of 90°
2644 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2646 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2647 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2648 (IS_VERT_ANGLE(laser.current_angle) &&
2649 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2651 // laser at last step touched nothing or the same side of the wall
2652 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2654 AddDamagedField(ELX, ELY);
2661 last_hit_mask = hit_mask;
2668 if (!HitOnlyAnEdge(hit_mask))
2670 laser.overloaded = TRUE;
2678 static boolean HitAbsorbingWalls(int element, int hit_mask)
2680 if (HitOnlyAnEdge(hit_mask))
2684 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2686 AddLaserEdge(LX - XS, LY - YS);
2693 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2695 AddLaserEdge(LX - XS, LY - YS);
2701 if (IS_WALL_WOOD(element) ||
2702 IS_DF_WALL_WOOD(element) ||
2703 IS_GRID_WOOD(element) ||
2704 IS_GRID_WOOD_FIXED(element) ||
2705 IS_GRID_WOOD_AUTO(element) ||
2706 element == EL_FUSE_ON ||
2707 element == EL_BLOCK_WOOD ||
2708 element == EL_GATE_WOOD)
2710 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2715 if (IS_WALL_ICE(element))
2721 // check if laser hit adjacent edges of two diagonal tiles
2722 if (ELX != lx / TILEX)
2724 if (ELY != ly / TILEY)
2727 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2728 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2730 // check if laser hits wall with an angle of 90°
2731 if (IS_90_ANGLE(laser.current_angle))
2732 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2734 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2738 for (i = 0; i < 4; i++)
2740 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2741 mask = 15 - (8 >> i);
2742 else if (ABS(XS) == 4 &&
2744 (XS > 0) == (i % 2) &&
2745 (YS < 0) == (i / 2))
2746 mask = 3 + (i / 2) * 9;
2747 else if (ABS(YS) == 4 &&
2749 (XS < 0) == (i % 2) &&
2750 (YS > 0) == (i / 2))
2751 mask = 5 + (i % 2) * 5;
2755 laser.wall_mask = mask;
2757 else if (IS_WALL_AMOEBA(element))
2759 int elx = (LX - 2 * XS) / TILEX;
2760 int ely = (LY - 2 * YS) / TILEY;
2761 int element2 = Tile[elx][ely];
2764 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2766 laser.dest_element = EL_EMPTY;
2774 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2775 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2777 if (IS_90_ANGLE(laser.current_angle))
2778 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2780 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2782 laser.wall_mask = mask;
2788 static void OpenExit(int x, int y)
2792 if (!MovDelay[x][y]) // next animation frame
2793 MovDelay[x][y] = 4 * delay;
2795 if (MovDelay[x][y]) // wait some time before next frame
2800 phase = MovDelay[x][y] / delay;
2802 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2803 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2805 if (!MovDelay[x][y])
2807 Tile[x][y] = EL_EXIT_OPEN;
2813 static void OpenGrayBall(int x, int y)
2817 if (!MovDelay[x][y]) // next animation frame
2819 if (IS_WALL(Store[x][y]))
2821 DrawWalls_MM(x, y, Store[x][y]);
2823 // copy wall tile to spare bitmap for "melting" animation
2824 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2825 TILEX, TILEY, x * TILEX, y * TILEY);
2827 DrawElement_MM(x, y, EL_GRAY_BALL);
2830 MovDelay[x][y] = 50 * delay;
2833 if (MovDelay[x][y]) // wait some time before next frame
2837 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2841 int dx = RND(26), dy = RND(26);
2843 if (IS_WALL(Store[x][y]))
2845 // copy wall tile from spare bitmap for "melting" animation
2846 bitmap = bitmap_db_field;
2852 int graphic = el2gfx(Store[x][y]);
2854 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2857 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2858 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2860 laser.redraw = TRUE;
2862 MarkTileDirty(x, y);
2865 if (!MovDelay[x][y])
2867 Tile[x][y] = Store[x][y];
2868 Store[x][y] = Store2[x][y] = 0;
2869 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2871 InitField(x, y, FALSE);
2874 ScanLaser_FromLastMirror();
2879 static void OpenEnvelope(int x, int y)
2881 int num_frames = 8; // seven frames plus final empty space
2883 if (!MovDelay[x][y]) // next animation frame
2884 MovDelay[x][y] = num_frames;
2886 if (MovDelay[x][y]) // wait some time before next frame
2888 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2892 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2894 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2895 int frame = num_frames - MovDelay[x][y] - 1;
2897 DrawGraphicAnimation_MM(x, y, graphic, frame);
2899 laser.redraw = TRUE;
2902 if (MovDelay[x][y] == 0)
2904 Tile[x][y] = EL_EMPTY;
2915 static void MeltIce(int x, int y)
2920 if (!MovDelay[x][y]) // next animation frame
2921 MovDelay[x][y] = frames * delay;
2923 if (MovDelay[x][y]) // wait some time before next frame
2926 int wall_mask = Store2[x][y];
2927 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2930 phase = frames - MovDelay[x][y] / delay - 1;
2932 if (!MovDelay[x][y])
2934 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2935 Store[x][y] = Store2[x][y] = 0;
2937 DrawWalls_MM(x, y, Tile[x][y]);
2939 if (Tile[x][y] == EL_WALL_ICE_BASE)
2940 Tile[x][y] = EL_EMPTY;
2942 ScanLaser_FromLastMirror();
2944 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2946 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2948 laser.redraw = TRUE;
2953 static void GrowAmoeba(int x, int y)
2958 if (!MovDelay[x][y]) // next animation frame
2959 MovDelay[x][y] = frames * delay;
2961 if (MovDelay[x][y]) // wait some time before next frame
2964 int wall_mask = Store2[x][y];
2965 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2968 phase = MovDelay[x][y] / delay;
2970 if (!MovDelay[x][y])
2972 Tile[x][y] = real_element;
2973 Store[x][y] = Store2[x][y] = 0;
2975 DrawWalls_MM(x, y, Tile[x][y]);
2976 DrawLaser(0, DL_LASER_ENABLED);
2978 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2980 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2985 static void DrawFieldAnimated_MM(int x, int y)
2989 laser.redraw = TRUE;
2992 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2994 int element = Tile[x][y];
2995 int graphic = el2gfx(element);
2997 if (!getGraphicInfo_NewFrame(x, y, graphic))
3002 laser.redraw = TRUE;
3005 static void DrawFieldTwinkle(int x, int y)
3007 if (MovDelay[x][y] != 0) // wait some time before next frame
3013 if (MovDelay[x][y] != 0)
3015 int graphic = IMG_TWINKLE_WHITE;
3016 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
3018 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
3021 laser.redraw = TRUE;
3025 static void Explode_MM(int x, int y, int phase, int mode)
3027 int num_phase = 9, delay = 2;
3028 int last_phase = num_phase * delay;
3029 int half_phase = (num_phase / 2) * delay;
3032 laser.redraw = TRUE;
3034 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
3036 center_element = Tile[x][y];
3038 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
3040 // put moving element to center field (and let it explode there)
3041 center_element = MovingOrBlocked2Element_MM(x, y);
3042 RemoveMovingField_MM(x, y);
3044 Tile[x][y] = center_element;
3047 if (center_element != EL_GRAY_BALL_ACTIVE)
3048 Store[x][y] = EL_EMPTY;
3049 Store2[x][y] = center_element;
3051 Tile[x][y] = EL_EXPLODING_OPAQUE;
3053 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
3054 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
3055 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
3058 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3060 ExplodePhase[x][y] = 1;
3066 GfxFrame[x][y] = 0; // restart explosion animation
3068 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
3070 center_element = Store2[x][y];
3072 if (phase == half_phase && Store[x][y] == EL_EMPTY)
3074 Tile[x][y] = EL_EXPLODING_TRANSP;
3076 if (x == ELX && y == ELY)
3080 if (phase == last_phase)
3082 if (center_element == EL_BOMB_ACTIVE)
3084 DrawLaser(0, DL_LASER_DISABLED);
3087 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3089 laser.overloaded = FALSE;
3091 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
3093 GameOver_MM(GAME_OVER_BOMB);
3096 Tile[x][y] = Store[x][y];
3098 Store[x][y] = Store2[x][y] = 0;
3099 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3101 InitField(x, y, FALSE);
3104 if (center_element == EL_GRAY_BALL_ACTIVE)
3105 ScanLaser_FromLastMirror();
3107 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
3109 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
3110 int frame = getGraphicAnimationFrameXY(graphic, x, y);
3112 DrawGraphicAnimation_MM(x, y, graphic, frame);
3114 MarkTileDirty(x, y);
3118 static void Bang_MM(int x, int y)
3120 int element = Tile[x][y];
3122 if (IS_PACMAN(element))
3123 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3124 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
3125 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3126 else if (element == EL_KEY)
3127 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3129 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3131 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
3134 static void TurnRound(int x, int y)
3146 { 0, 0 }, { 0, 0 }, { 0, 0 },
3151 int left, right, back;
3155 { MV_DOWN, MV_UP, MV_RIGHT },
3156 { MV_UP, MV_DOWN, MV_LEFT },
3158 { MV_LEFT, MV_RIGHT, MV_DOWN },
3162 { MV_RIGHT, MV_LEFT, MV_UP }
3165 int element = Tile[x][y];
3166 int old_move_dir = MovDir[x][y];
3167 int right_dir = turn[old_move_dir].right;
3168 int back_dir = turn[old_move_dir].back;
3169 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
3170 int right_x = x + right_dx, right_y = y + right_dy;
3172 if (element == EL_PACMAN)
3174 boolean can_turn_right = FALSE;
3176 if (IN_LEV_FIELD(right_x, right_y) &&
3177 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
3178 can_turn_right = TRUE;
3181 MovDir[x][y] = right_dir;
3183 MovDir[x][y] = back_dir;
3189 static void StartMoving_MM(int x, int y)
3191 int element = Tile[x][y];
3196 if (CAN_MOVE(element))
3200 if (MovDelay[x][y]) // wait some time before next movement
3208 // now make next step
3210 Moving2Blocked(x, y, &newx, &newy); // get next screen position
3212 if (element == EL_PACMAN &&
3213 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
3214 !ObjHit(newx, newy, HIT_POS_CENTER))
3216 Store[newx][newy] = Tile[newx][newy];
3217 Tile[newx][newy] = EL_EMPTY;
3219 DrawField_MM(newx, newy);
3221 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3222 ObjHit(newx, newy, HIT_POS_CENTER))
3224 // object was running against a wall
3231 InitMovingField_MM(x, y, MovDir[x][y]);
3235 ContinueMoving_MM(x, y);
3238 static void ContinueMoving_MM(int x, int y)
3240 int element = Tile[x][y];
3241 int direction = MovDir[x][y];
3242 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3243 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3244 int horiz_move = (dx!=0);
3245 int newx = x + dx, newy = y + dy;
3246 int step = (horiz_move ? dx : dy) * TILEX / 8;
3248 MovPos[x][y] += step;
3250 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3252 Tile[x][y] = EL_EMPTY;
3253 Tile[newx][newy] = element;
3255 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3256 MovDelay[newx][newy] = 0;
3258 if (!CAN_MOVE(element))
3259 MovDir[newx][newy] = 0;
3262 DrawField_MM(newx, newy);
3264 Stop[newx][newy] = TRUE;
3266 if (element == EL_PACMAN)
3268 if (Store[newx][newy] == EL_BOMB)
3269 Bang_MM(newx, newy);
3271 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3272 (LX + 2 * XS) / TILEX == newx &&
3273 (LY + 2 * YS) / TILEY == newy)
3280 else // still moving on
3285 laser.redraw = TRUE;
3288 boolean ClickElement(int x, int y, int button)
3290 static DelayCounter click_delay = { CLICK_DELAY };
3291 static boolean new_button = TRUE;
3292 boolean element_clicked = FALSE;
3297 // initialize static variables
3298 click_delay.count = 0;
3299 click_delay.value = CLICK_DELAY;
3305 // do not rotate objects hit by the laser after the game was solved
3306 if (game_mm.level_solved && Hit[x][y])
3309 if (button == MB_RELEASED)
3312 click_delay.value = CLICK_DELAY;
3314 // release eventually hold auto-rotating mirror
3315 RotateMirror(x, y, MB_RELEASED);
3320 if (!FrameReached(&click_delay) && !new_button)
3323 if (button == MB_MIDDLEBUTTON) // middle button has no function
3326 if (!IN_LEV_FIELD(x, y))
3329 if (Tile[x][y] == EL_EMPTY)
3332 element = Tile[x][y];
3334 if (IS_MIRROR(element) ||
3335 IS_BEAMER(element) ||
3336 IS_POLAR(element) ||
3337 IS_POLAR_CROSS(element) ||
3338 IS_DF_MIRROR(element) ||
3339 IS_DF_MIRROR_AUTO(element))
3341 RotateMirror(x, y, button);
3343 element_clicked = TRUE;
3345 else if (IS_MCDUFFIN(element))
3347 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3349 if (has_laser && !laser.fuse_off)
3350 DrawLaser(0, DL_LASER_DISABLED);
3352 element = get_rotated_element(element, BUTTON_ROTATION(button));
3354 Tile[x][y] = element;
3359 laser.start_angle = get_element_angle(element);
3363 if (!laser.fuse_off)
3367 element_clicked = TRUE;
3369 else if (element == EL_FUSE_ON && laser.fuse_off)
3371 if (x != laser.fuse_x || y != laser.fuse_y)
3374 laser.fuse_off = FALSE;
3375 laser.fuse_x = laser.fuse_y = -1;
3377 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3380 element_clicked = TRUE;
3382 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3384 laser.fuse_off = TRUE;
3387 laser.overloaded = FALSE;
3389 DrawLaser(0, DL_LASER_DISABLED);
3390 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3392 element_clicked = TRUE;
3394 else if (element == EL_LIGHTBALL)
3397 RaiseScoreElement_MM(element);
3398 DrawLaser(0, DL_LASER_ENABLED);
3400 element_clicked = TRUE;
3402 else if (IS_ENVELOPE(element))
3404 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3406 element_clicked = TRUE;
3409 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3412 return element_clicked;
3415 static void RotateMirror(int x, int y, int button)
3417 if (button == MB_RELEASED)
3419 // release eventually hold auto-rotating mirror
3426 if (IS_MIRROR(Tile[x][y]) ||
3427 IS_POLAR_CROSS(Tile[x][y]) ||
3428 IS_POLAR(Tile[x][y]) ||
3429 IS_BEAMER(Tile[x][y]) ||
3430 IS_DF_MIRROR(Tile[x][y]) ||
3431 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3432 IS_GRID_WOOD_AUTO(Tile[x][y]))
3434 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3436 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3438 if (button == MB_LEFTBUTTON)
3440 // left mouse button only for manual adjustment, no auto-rotating;
3441 // freeze mirror for until mouse button released
3445 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3447 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3451 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3453 int edge = Hit[x][y];
3459 DrawLaser(edge - 1, DL_LASER_DISABLED);
3463 else if (ObjHit(x, y, HIT_POS_CENTER))
3465 int edge = Hit[x][y];
3469 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3474 DrawLaser(edge - 1, DL_LASER_DISABLED);
3481 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3486 if ((IS_BEAMER(Tile[x][y]) ||
3487 IS_POLAR(Tile[x][y]) ||
3488 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3490 if (IS_BEAMER(Tile[x][y]))
3493 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3494 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3507 DrawLaser(0, DL_LASER_ENABLED);
3511 static void AutoRotateMirrors(void)
3515 if (!FrameReached(&rotate_delay))
3518 for (x = 0; x < lev_fieldx; x++)
3520 for (y = 0; y < lev_fieldy; y++)
3522 int element = Tile[x][y];
3524 // do not rotate objects hit by the laser after the game was solved
3525 if (game_mm.level_solved && Hit[x][y])
3528 if (IS_DF_MIRROR_AUTO(element) ||
3529 IS_GRID_WOOD_AUTO(element) ||
3530 IS_GRID_STEEL_AUTO(element) ||
3531 element == EL_REFRACTOR)
3533 RotateMirror(x, y, MB_RIGHTBUTTON);
3535 laser.redraw = TRUE;
3541 static boolean ObjHit(int obx, int oby, int bits)
3548 if (bits & HIT_POS_CENTER)
3550 if (CheckLaserPixel(cSX + obx + 15,
3555 if (bits & HIT_POS_EDGE)
3557 for (i = 0; i < 4; i++)
3558 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3559 cSY + oby + 31 * (i / 2)))
3563 if (bits & HIT_POS_BETWEEN)
3565 for (i = 0; i < 4; i++)
3566 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3567 cSY + 4 + oby + 22 * (i / 2)))
3574 static void DeletePacMan(int px, int py)
3580 if (game_mm.num_pacman <= 1)
3582 game_mm.num_pacman = 0;
3586 for (i = 0; i < game_mm.num_pacman; i++)
3587 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3590 game_mm.num_pacman--;
3592 for (j = i; j < game_mm.num_pacman; j++)
3594 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3595 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3596 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3600 static void GameActions_MM_Ext(void)
3607 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3610 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3612 element = Tile[x][y];
3614 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3615 StartMoving_MM(x, y);
3616 else if (IS_MOVING(x, y))
3617 ContinueMoving_MM(x, y);
3618 else if (IS_EXPLODING(element))
3619 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3620 else if (element == EL_EXIT_OPENING)
3622 else if (element == EL_GRAY_BALL_OPENING)
3624 else if (IS_ENVELOPE_OPENING(element))
3626 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3628 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3630 else if (IS_MIRROR(element) ||
3631 IS_MIRROR_FIXED(element) ||
3632 element == EL_PRISM)
3633 DrawFieldTwinkle(x, y);
3634 else if (element == EL_GRAY_BALL_ACTIVE ||
3635 element == EL_BOMB_ACTIVE ||
3636 element == EL_MINE_ACTIVE)
3637 DrawFieldAnimated_MM(x, y);
3638 else if (!IS_BLOCKED(x, y))
3639 DrawFieldAnimatedIfNeeded_MM(x, y);
3642 AutoRotateMirrors();
3645 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3647 // redraw after Explode_MM() ...
3649 DrawLaser(0, DL_LASER_ENABLED);
3650 laser.redraw = FALSE;
3655 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3659 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3661 DrawLaser(0, DL_LASER_DISABLED);
3666 // skip all following game actions if game is over
3667 if (game_mm.game_over)
3670 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3674 GameOver_MM(GAME_OVER_NO_ENERGY);
3679 if (FrameReached(&energy_delay))
3681 if (game_mm.energy_left > 0)
3682 game_mm.energy_left--;
3684 // when out of energy, wait another frame to play "out of time" sound
3687 element = laser.dest_element;
3690 if (element != Tile[ELX][ELY])
3692 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3693 element, Tile[ELX][ELY]);
3697 if (!laser.overloaded && laser.overload_value == 0 &&
3698 element != EL_BOMB &&
3699 element != EL_BOMB_ACTIVE &&
3700 element != EL_MINE &&
3701 element != EL_MINE_ACTIVE &&
3702 element != EL_GRAY_BALL &&
3703 element != EL_GRAY_BALL_ACTIVE &&
3704 element != EL_BLOCK_STONE &&
3705 element != EL_BLOCK_WOOD &&
3706 element != EL_FUSE_ON &&
3707 element != EL_FUEL_FULL &&
3708 !IS_WALL_ICE(element) &&
3709 !IS_WALL_AMOEBA(element))
3712 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3714 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3715 (!laser.overloaded && laser.overload_value > 0)) &&
3716 FrameReached(&overload_delay))
3718 if (laser.overloaded)
3719 laser.overload_value++;
3721 laser.overload_value--;
3723 if (game_mm.cheat_no_overload)
3725 laser.overloaded = FALSE;
3726 laser.overload_value = 0;
3729 game_mm.laser_overload_value = laser.overload_value;
3731 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3733 SetLaserColor(0xFF);
3735 DrawLaser(0, DL_LASER_ENABLED);
3738 if (!laser.overloaded)
3739 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3740 else if (setup.sound_loops)
3741 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3743 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3745 if (laser.overload_value == MAX_LASER_OVERLOAD)
3747 UpdateAndDisplayGameControlValues();
3751 GameOver_MM(GAME_OVER_OVERLOADED);
3762 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3764 if (game_mm.cheat_no_explosion)
3769 laser.dest_element = EL_EXPLODING_OPAQUE;
3774 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3776 laser.fuse_off = TRUE;
3780 DrawLaser(0, DL_LASER_DISABLED);
3781 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3784 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3786 if (!Store2[ELX][ELY]) // check if content element not yet determined
3788 int last_anim_random_frame = gfx.anim_random_frame;
3791 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3792 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3794 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3795 native_mm_level.ball_choice_mode, 0,
3796 game_mm.ball_choice_pos);
3798 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3799 gfx.anim_random_frame = last_anim_random_frame;
3801 game_mm.ball_choice_pos++;
3803 int new_element = native_mm_level.ball_content[element_pos];
3804 int new_element_base = map_wall_to_base_element(new_element);
3806 if (IS_WALL(new_element_base))
3808 // always use completely filled wall element
3809 new_element = new_element_base | 0x000f;
3811 else if (native_mm_level.rotate_ball_content &&
3812 get_num_elements(new_element) > 1)
3814 // randomly rotate newly created game element
3815 new_element = get_rotated_element(new_element, RND(16));
3818 Store[ELX][ELY] = new_element;
3819 Store2[ELX][ELY] = TRUE;
3822 if (native_mm_level.explode_ball)
3825 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3827 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3832 if (IS_WALL_ICE(element) && CT > 50)
3834 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3836 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3837 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3838 Store2[ELX][ELY] = laser.wall_mask;
3840 laser.dest_element = Tile[ELX][ELY];
3845 if (IS_WALL_AMOEBA(element) && CT > 60)
3848 int element2 = Tile[ELX][ELY];
3850 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3853 for (i = laser.num_damages - 1; i >= 0; i--)
3854 if (laser.damage[i].is_mirror)
3857 r = laser.num_edges;
3858 d = laser.num_damages;
3865 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3868 DrawLaser(0, DL_LASER_ENABLED);
3871 x = laser.damage[k1].x;
3872 y = laser.damage[k1].y;
3877 for (i = 0; i < 4; i++)
3879 if (laser.wall_mask & (1 << i))
3881 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3882 cSY + ELY * TILEY + 31 * (i / 2)))
3885 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3886 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3893 for (i = 0; i < 4; i++)
3895 if (laser.wall_mask & (1 << i))
3897 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3898 cSY + ELY * TILEY + 31 * (i / 2)))
3905 if (laser.num_beamers > 0 ||
3906 k1 < 1 || k2 < 4 || k3 < 4 ||
3907 CheckLaserPixel(cSX + ELX * TILEX + 14,
3908 cSY + ELY * TILEY + 14))
3910 laser.num_edges = r;
3911 laser.num_damages = d;
3913 DrawLaser(0, DL_LASER_DISABLED);
3916 Tile[ELX][ELY] = element | laser.wall_mask;
3918 int x = ELX, y = ELY;
3919 int wall_mask = laser.wall_mask;
3922 DrawLaser(0, DL_LASER_ENABLED);
3924 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3926 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3927 Store[x][y] = EL_WALL_AMOEBA_BASE;
3928 Store2[x][y] = wall_mask;
3933 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3934 laser.stops_inside_element && CT > native_mm_level.time_block)
3939 if (ABS(XS) > ABS(YS))
3946 for (i = 0; i < 4; i++)
3953 x = ELX + Step[k * 4].x;
3954 y = ELY + Step[k * 4].y;
3956 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3959 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3967 laser.overloaded = (element == EL_BLOCK_STONE);
3972 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3975 Tile[x][y] = element;
3977 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3980 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3982 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3983 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3991 if (element == EL_FUEL_FULL && CT > 10)
3993 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3994 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3996 for (i = start; i <= num_init_game_frames; i++)
3998 if (i == num_init_game_frames)
3999 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4000 else if (setup.sound_loops)
4001 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4003 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4005 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
4007 UpdateAndDisplayGameControlValues();
4012 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
4014 DrawField_MM(ELX, ELY);
4016 DrawLaser(0, DL_LASER_ENABLED);
4022 void GameActions_MM(struct MouseActionInfo action)
4024 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
4025 boolean button_released = (action.button == MB_RELEASED);
4027 GameActions_MM_Ext();
4029 CheckSingleStepMode_MM(element_clicked, button_released);
4032 static void MovePacMen(void)
4034 int mx, my, ox, oy, nx, ny;
4038 if (++pacman_nr >= game_mm.num_pacman)
4041 game_mm.pacman[pacman_nr].dir--;
4043 for (l = 1; l < 5; l++)
4045 game_mm.pacman[pacman_nr].dir++;
4047 if (game_mm.pacman[pacman_nr].dir > 4)
4048 game_mm.pacman[pacman_nr].dir = 1;
4050 if (game_mm.pacman[pacman_nr].dir % 2)
4053 my = game_mm.pacman[pacman_nr].dir - 2;
4058 mx = 3 - game_mm.pacman[pacman_nr].dir;
4061 ox = game_mm.pacman[pacman_nr].x;
4062 oy = game_mm.pacman[pacman_nr].y;
4065 element = Tile[nx][ny];
4067 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
4070 if (!IS_EATABLE4PACMAN(element))
4073 if (ObjHit(nx, ny, HIT_POS_CENTER))
4076 Tile[ox][oy] = EL_EMPTY;
4078 EL_PACMAN_RIGHT - 1 +
4079 (game_mm.pacman[pacman_nr].dir - 1 +
4080 (game_mm.pacman[pacman_nr].dir % 2) * 2);
4082 game_mm.pacman[pacman_nr].x = nx;
4083 game_mm.pacman[pacman_nr].y = ny;
4085 DrawGraphic_MM(ox, oy, IMG_EMPTY);
4087 if (element != EL_EMPTY)
4089 int graphic = el2gfx(Tile[nx][ny]);
4094 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
4097 ox = cSX + ox * TILEX;
4098 oy = cSY + oy * TILEY;
4100 for (i = 1; i < 33; i += 2)
4101 BlitBitmap(bitmap, window,
4102 src_x, src_y, TILEX, TILEY,
4103 ox + i * mx, oy + i * my);
4104 Ct = Ct + FrameCounter - CT;
4107 DrawField_MM(nx, ny);
4110 if (!laser.fuse_off)
4112 DrawLaser(0, DL_LASER_ENABLED);
4114 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
4116 AddDamagedField(nx, ny);
4118 laser.damage[laser.num_damages - 1].edge = 0;
4122 if (element == EL_BOMB)
4123 DeletePacMan(nx, ny);
4125 if (IS_WALL_AMOEBA(element) &&
4126 (LX + 2 * XS) / TILEX == nx &&
4127 (LY + 2 * YS) / TILEY == ny)
4137 static void InitMovingField_MM(int x, int y, int direction)
4139 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4140 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4142 MovDir[x][y] = direction;
4143 MovDir[newx][newy] = direction;
4145 if (Tile[newx][newy] == EL_EMPTY)
4146 Tile[newx][newy] = EL_BLOCKED;
4149 static int MovingOrBlocked2Element_MM(int x, int y)
4151 int element = Tile[x][y];
4153 if (element == EL_BLOCKED)
4157 Blocked2Moving(x, y, &oldx, &oldy);
4159 return Tile[oldx][oldy];
4165 static void RemoveMovingField_MM(int x, int y)
4167 int oldx = x, oldy = y, newx = x, newy = y;
4169 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4172 if (IS_MOVING(x, y))
4174 Moving2Blocked(x, y, &newx, &newy);
4175 if (Tile[newx][newy] != EL_BLOCKED)
4178 else if (Tile[x][y] == EL_BLOCKED)
4180 Blocked2Moving(x, y, &oldx, &oldy);
4181 if (!IS_MOVING(oldx, oldy))
4185 Tile[oldx][oldy] = EL_EMPTY;
4186 Tile[newx][newy] = EL_EMPTY;
4187 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4188 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4190 DrawLevelField_MM(oldx, oldy);
4191 DrawLevelField_MM(newx, newy);
4194 static void RaiseScore_MM(int value)
4196 game_mm.score += value;
4199 void RaiseScoreElement_MM(int element)
4204 case EL_PACMAN_RIGHT:
4206 case EL_PACMAN_LEFT:
4207 case EL_PACMAN_DOWN:
4208 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4212 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4217 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4221 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4230 // ----------------------------------------------------------------------------
4231 // Mirror Magic game engine snapshot handling functions
4232 // ----------------------------------------------------------------------------
4234 void SaveEngineSnapshotValues_MM(void)
4238 engine_snapshot_mm.game_mm = game_mm;
4239 engine_snapshot_mm.laser = laser;
4241 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4243 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4245 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4246 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4247 engine_snapshot_mm.Box[x][y] = Box[x][y];
4248 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4252 engine_snapshot_mm.LX = LX;
4253 engine_snapshot_mm.LY = LY;
4254 engine_snapshot_mm.XS = XS;
4255 engine_snapshot_mm.YS = YS;
4256 engine_snapshot_mm.ELX = ELX;
4257 engine_snapshot_mm.ELY = ELY;
4258 engine_snapshot_mm.CT = CT;
4259 engine_snapshot_mm.Ct = Ct;
4261 engine_snapshot_mm.last_LX = last_LX;
4262 engine_snapshot_mm.last_LY = last_LY;
4263 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4264 engine_snapshot_mm.hold_x = hold_x;
4265 engine_snapshot_mm.hold_y = hold_y;
4266 engine_snapshot_mm.pacman_nr = pacman_nr;
4268 engine_snapshot_mm.rotate_delay = rotate_delay;
4269 engine_snapshot_mm.pacman_delay = pacman_delay;
4270 engine_snapshot_mm.energy_delay = energy_delay;
4271 engine_snapshot_mm.overload_delay = overload_delay;
4274 void LoadEngineSnapshotValues_MM(void)
4278 // stored engine snapshot buffers already restored at this point
4280 game_mm = engine_snapshot_mm.game_mm;
4281 laser = engine_snapshot_mm.laser;
4283 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4285 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4287 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4288 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4289 Box[x][y] = engine_snapshot_mm.Box[x][y];
4290 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4294 LX = engine_snapshot_mm.LX;
4295 LY = engine_snapshot_mm.LY;
4296 XS = engine_snapshot_mm.XS;
4297 YS = engine_snapshot_mm.YS;
4298 ELX = engine_snapshot_mm.ELX;
4299 ELY = engine_snapshot_mm.ELY;
4300 CT = engine_snapshot_mm.CT;
4301 Ct = engine_snapshot_mm.Ct;
4303 last_LX = engine_snapshot_mm.last_LX;
4304 last_LY = engine_snapshot_mm.last_LY;
4305 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4306 hold_x = engine_snapshot_mm.hold_x;
4307 hold_y = engine_snapshot_mm.hold_y;
4308 pacman_nr = engine_snapshot_mm.pacman_nr;
4310 rotate_delay = engine_snapshot_mm.rotate_delay;
4311 pacman_delay = engine_snapshot_mm.pacman_delay;
4312 energy_delay = engine_snapshot_mm.energy_delay;
4313 overload_delay = engine_snapshot_mm.overload_delay;
4315 RedrawPlayfield_MM();
4318 static int getAngleFromTouchDelta(int dx, int dy, int base)
4320 double pi = 3.141592653;
4321 double rad = atan2((double)-dy, (double)dx);
4322 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4323 double deg = rad2 * 180.0 / pi;
4325 return (int)(deg * base / 360.0 + 0.5) % base;
4328 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4330 // calculate start (source) position to be at the middle of the tile
4331 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4332 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4333 int dx = dst_mx - src_mx;
4334 int dy = dst_my - src_my;
4343 if (!IN_LEV_FIELD(x, y))
4346 element = Tile[x][y];
4348 if (!IS_MCDUFFIN(element) &&
4349 !IS_MIRROR(element) &&
4350 !IS_BEAMER(element) &&
4351 !IS_POLAR(element) &&
4352 !IS_POLAR_CROSS(element) &&
4353 !IS_DF_MIRROR(element))
4356 angle_old = get_element_angle(element);
4358 if (IS_MCDUFFIN(element))
4360 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4361 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4362 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4363 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4366 else if (IS_MIRROR(element) ||
4367 IS_DF_MIRROR(element))
4369 for (i = 0; i < laser.num_damages; i++)
4371 if (laser.damage[i].x == x &&
4372 laser.damage[i].y == y &&
4373 ObjHit(x, y, HIT_POS_CENTER))
4375 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4376 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4383 if (angle_new == -1)
4385 if (IS_MIRROR(element) ||
4386 IS_DF_MIRROR(element) ||
4390 if (IS_POLAR_CROSS(element))
4393 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4396 button = (angle_new == angle_old ? 0 :
4397 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4398 MB_LEFTBUTTON : MB_RIGHTBUTTON);