1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
101 static void AddLaserEdge(int, int);
102 static void ScanLaser(void);
103 static void DrawLaser(int, int);
104 static boolean HitElement(int, int);
105 static boolean HitOnlyAnEdge(int);
106 static boolean HitPolarizer(int, int);
107 static boolean HitBlock(int, int);
108 static boolean HitLaserSource(int, int);
109 static boolean HitLaserDestination(int, int);
110 static boolean HitReflectingWalls(int, int);
111 static boolean HitAbsorbingWalls(int, int);
112 static void RotateMirror(int, int, int);
113 static boolean ObjHit(int, int, int);
114 static void DeletePacMan(int, int);
115 static void MovePacMen(void);
117 // bitmap for laser beam detection
118 static Bitmap *laser_bitmap = NULL;
120 // variables for laser control
121 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
122 static int hold_x = -1, hold_y = -1;
124 // variables for pacman control
125 static int pacman_nr = -1;
127 // various game engine delay counters
128 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
129 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
130 static DelayCounter energy_delay = { ENERGY_DELAY };
131 static DelayCounter overload_delay = { 0 };
133 // element mask positions for scanning pixels of MM elements
134 #define MM_MASK_MCDUFFIN_RIGHT 0
135 #define MM_MASK_MCDUFFIN_UP 1
136 #define MM_MASK_MCDUFFIN_LEFT 2
137 #define MM_MASK_MCDUFFIN_DOWN 3
138 #define MM_MASK_GRID_1 4
139 #define MM_MASK_GRID_2 5
140 #define MM_MASK_GRID_3 6
141 #define MM_MASK_GRID_4 7
142 #define MM_MASK_SLOPE_1 8
143 #define MM_MASK_SLOPE_2 9
144 #define MM_MASK_SLOPE_3 10
145 #define MM_MASK_SLOPE_4 11
146 #define MM_MASK_RECTANGLE 12
147 #define MM_MASK_CIRCLE 13
149 #define NUM_MM_MASKS 14
151 // element masks for scanning pixels of MM elements
152 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
408 static int get_element_angle(int element)
410 int element_phase = get_element_phase(element);
412 if (IS_MIRROR_FIXED(element) ||
413 IS_MCDUFFIN(element) ||
415 IS_RECEIVER(element))
416 return 4 * element_phase;
417 else if (IS_DF_SLOPE(element))
418 return 4 + (element_phase % 2) * 8;
420 return element_phase;
423 static int get_opposite_angle(int angle)
425 int opposite_angle = angle + ANG_RAY_180;
427 // make sure "opposite_angle" is in valid interval [0, 15]
428 return (opposite_angle + 16) % 16;
431 static int get_mirrored_angle(int laser_angle, int mirror_angle)
433 int reflected_angle = 16 - laser_angle + mirror_angle;
435 // make sure "reflected_angle" is in valid interval [0, 15]
436 return (reflected_angle + 16) % 16;
439 static void DrawLaserLines(struct XY *points, int num_points, int mode)
441 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
442 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
444 DrawLines(drawto_mm, points, num_points, pixel_drawto);
448 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
453 static boolean CheckLaserPixel(int x, int y)
459 pixel = ReadPixel(laser_bitmap, x, y);
463 return (pixel == WHITE_PIXEL);
466 static void CheckExitMM(void)
468 int exit_element = EL_EMPTY;
472 static int xy[4][2] =
480 for (y = 0; y < lev_fieldy; y++)
482 for (x = 0; x < lev_fieldx; x++)
484 if (Tile[x][y] == EL_EXIT_CLOSED)
486 // initiate opening animation of exit door
487 Tile[x][y] = EL_EXIT_OPENING;
489 exit_element = EL_EXIT_OPEN;
493 else if (IS_RECEIVER(Tile[x][y]))
495 // remove field that blocks receiver
496 int phase = Tile[x][y] - EL_RECEIVER_START;
497 int blocking_x, blocking_y;
499 blocking_x = x + xy[phase][0];
500 blocking_y = y + xy[phase][1];
502 if (IN_LEV_FIELD(blocking_x, blocking_y))
504 Tile[blocking_x][blocking_y] = EL_EMPTY;
506 DrawField_MM(blocking_x, blocking_y);
509 exit_element = EL_RECEIVER;
516 if (exit_element != EL_EMPTY)
517 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
520 static void SetLaserColor(int brightness)
522 int color_min = 0x00;
523 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
524 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
525 int color_down = color_max - color_up;
528 GetPixelFromRGB(window,
529 (game_mm.laser_red ? color_max : color_up),
530 (game_mm.laser_green ? color_down : color_min),
531 (game_mm.laser_blue ? color_down : color_min));
534 static void InitMovDir_MM(int x, int y)
536 int element = Tile[x][y];
537 static int direction[3][4] =
539 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
540 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
541 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
546 case EL_PACMAN_RIGHT:
550 Tile[x][y] = EL_PACMAN;
551 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
559 static void InitField(int x, int y, boolean init_game)
561 int element = Tile[x][y];
566 Tile[x][y] = EL_EMPTY;
571 if (init_game && native_mm_level.auto_count_kettles)
572 game_mm.kettles_still_needed++;
575 case EL_LIGHTBULB_OFF:
576 game_mm.lights_still_needed++;
580 if (IS_MIRROR(element) ||
581 IS_BEAMER_OLD(element) ||
582 IS_BEAMER(element) ||
584 IS_POLAR_CROSS(element) ||
585 IS_DF_MIRROR(element) ||
586 IS_DF_MIRROR_AUTO(element) ||
587 IS_GRID_STEEL_AUTO(element) ||
588 IS_GRID_WOOD_AUTO(element) ||
589 IS_FIBRE_OPTIC(element))
591 if (IS_BEAMER_OLD(element))
593 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
594 element = Tile[x][y];
597 if (!IS_FIBRE_OPTIC(element))
599 static int steps_grid_auto = 0;
601 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
602 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
604 if (IS_GRID_STEEL_AUTO(element) ||
605 IS_GRID_WOOD_AUTO(element))
606 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
608 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
610 game_mm.cycle[game_mm.num_cycle].x = x;
611 game_mm.cycle[game_mm.num_cycle].y = y;
615 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
617 int beamer_nr = BEAMER_NR(element);
618 int nr = laser.beamer[beamer_nr][0].num;
620 laser.beamer[beamer_nr][nr].x = x;
621 laser.beamer[beamer_nr][nr].y = y;
622 laser.beamer[beamer_nr][nr].num = 1;
625 else if (IS_PACMAN(element))
629 else if (IS_MCDUFFIN(element) || IS_LASER(element))
633 laser.start_edge.x = x;
634 laser.start_edge.y = y;
635 laser.start_angle = get_element_angle(element);
638 if (IS_MCDUFFIN(element))
640 game_mm.laser_red = native_mm_level.mm_laser_red;
641 game_mm.laser_green = native_mm_level.mm_laser_green;
642 game_mm.laser_blue = native_mm_level.mm_laser_blue;
646 game_mm.laser_red = native_mm_level.df_laser_red;
647 game_mm.laser_green = native_mm_level.df_laser_green;
648 game_mm.laser_blue = native_mm_level.df_laser_blue;
651 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
658 static void InitCycleElements_RotateSingleStep(void)
662 if (game_mm.num_cycle == 0) // no elements to cycle
665 for (i = 0; i < game_mm.num_cycle; i++)
667 int x = game_mm.cycle[i].x;
668 int y = game_mm.cycle[i].y;
669 int step = SIGN(game_mm.cycle[i].steps);
670 int last_element = Tile[x][y];
671 int next_element = get_rotated_element(last_element, step);
673 if (!game_mm.cycle[i].steps)
676 Tile[x][y] = next_element;
678 game_mm.cycle[i].steps -= step;
682 static void InitLaser(void)
684 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
685 int step = (IS_LASER(start_element) ? 4 : 0);
687 LX = laser.start_edge.x * TILEX;
688 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
691 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
693 LY = laser.start_edge.y * TILEY;
694 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
695 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
699 XS = 2 * Step[laser.start_angle].x;
700 YS = 2 * Step[laser.start_angle].y;
702 laser.current_angle = laser.start_angle;
704 laser.num_damages = 0;
706 laser.num_beamers = 0;
707 laser.beamer_edge[0] = 0;
709 laser.dest_element = EL_EMPTY;
712 AddLaserEdge(LX, LY); // set laser starting edge
717 void InitGameEngine_MM(void)
723 // initialize laser bitmap to current playfield (screen) size
724 ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
725 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
729 // set global game control values
730 game_mm.num_cycle = 0;
731 game_mm.num_pacman = 0;
734 game_mm.energy_left = 0; // later set to "native_mm_level.time"
735 game_mm.kettles_still_needed =
736 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
737 game_mm.lights_still_needed = 0;
738 game_mm.num_keys = 0;
739 game_mm.ball_choice_pos = 0;
741 game_mm.laser_red = FALSE;
742 game_mm.laser_green = FALSE;
743 game_mm.laser_blue = TRUE;
744 game_mm.has_mcduffin = TRUE;
746 game_mm.level_solved = FALSE;
747 game_mm.game_over = FALSE;
748 game_mm.game_over_cause = 0;
749 game_mm.game_over_message = NULL;
751 game_mm.laser_overload_value = 0;
752 game_mm.laser_enabled = FALSE;
754 // set global laser control values (must be set before "InitLaser()")
755 laser.start_edge.x = 0;
756 laser.start_edge.y = 0;
757 laser.start_angle = 0;
759 for (i = 0; i < MAX_NUM_BEAMERS; i++)
760 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
762 laser.overloaded = FALSE;
763 laser.overload_value = 0;
764 laser.fuse_off = FALSE;
765 laser.fuse_x = laser.fuse_y = -1;
767 laser.dest_element = EL_EMPTY;
768 laser.dest_element_last = EL_EMPTY;
769 laser.dest_element_last_x = -1;
770 laser.dest_element_last_y = -1;
784 rotate_delay.count = 0;
785 pacman_delay.count = 0;
786 energy_delay.count = 0;
787 overload_delay.count = 0;
789 ClickElement(-1, -1, -1);
791 for (x = 0; x < lev_fieldx; x++)
793 for (y = 0; y < lev_fieldy; y++)
795 Tile[x][y] = Ur[x][y];
796 Hit[x][y] = Box[x][y] = 0;
798 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
799 Store[x][y] = Store2[x][y] = 0;
802 InitField(x, y, TRUE);
809 void InitGameActions_MM(void)
811 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
812 int cycle_steps_done = 0;
817 for (i = 0; i <= num_init_game_frames; i++)
819 if (i == num_init_game_frames)
820 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
821 else if (setup.sound_loops)
822 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
824 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
826 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
828 UpdateAndDisplayGameControlValues();
830 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
832 InitCycleElements_RotateSingleStep();
837 AdvanceFrameCounter();
840 if (PendingEscapeKeyEvent())
844 if (setup.quick_doors)
854 if (setup.quick_doors)
860 if (game_mm.kettles_still_needed == 0)
863 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
864 SetTileCursorActive(TRUE);
866 // restart all delay counters after initially cycling game elements
867 ResetFrameCounter(&rotate_delay);
868 ResetFrameCounter(&pacman_delay);
869 ResetFrameCounter(&energy_delay);
870 ResetFrameCounter(&overload_delay);
873 static void FadeOutLaser(void)
877 for (i = 15; i >= 0; i--)
879 SetLaserColor(0x11 * i);
881 DrawLaser(0, DL_LASER_ENABLED);
884 Delay_WithScreenUpdates(50);
887 DrawLaser(0, DL_LASER_DISABLED);
889 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
892 static void GameOver_MM(int game_over_cause)
894 game_mm.game_over = TRUE;
895 game_mm.game_over_cause = game_over_cause;
896 game_mm.game_over_message = (game_mm.has_mcduffin ?
897 (game_over_cause == GAME_OVER_BOMB ?
898 "Bomb killed Mc Duffin!" :
899 game_over_cause == GAME_OVER_NO_ENERGY ?
900 "Out of magic energy!" :
901 game_over_cause == GAME_OVER_OVERLOADED ?
902 "Magic spell hit Mc Duffin!" :
904 (game_over_cause == GAME_OVER_BOMB ?
905 "Bomb destroyed laser cannon!" :
906 game_over_cause == GAME_OVER_NO_ENERGY ?
907 "Out of laser energy!" :
908 game_over_cause == GAME_OVER_OVERLOADED ?
909 "Laser beam hit laser cannon!" :
912 SetTileCursorActive(FALSE);
915 static void AddLaserEdge(int lx, int ly)
917 int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
918 int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
920 // check if laser is still inside visible playfield area (or inside level)
921 if (cSX + lx < REAL_SX || cSX + lx >= REAL_SX + full_sxsize ||
922 cSY + ly < REAL_SY || cSY + ly >= REAL_SY + full_sysize)
924 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
929 laser.edge[laser.num_edges].x = cSX2 + lx;
930 laser.edge[laser.num_edges].y = cSY2 + ly;
936 static void AddDamagedField(int ex, int ey)
938 // prevent adding the same field position again
939 if (laser.num_damages > 0 &&
940 laser.damage[laser.num_damages - 1].x == ex &&
941 laser.damage[laser.num_damages - 1].y == ey &&
942 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
945 laser.damage[laser.num_damages].is_mirror = FALSE;
946 laser.damage[laser.num_damages].angle = laser.current_angle;
947 laser.damage[laser.num_damages].edge = laser.num_edges;
948 laser.damage[laser.num_damages].x = ex;
949 laser.damage[laser.num_damages].y = ey;
953 static boolean StepBehind(void)
959 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
960 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
962 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
968 static int getMaskFromElement(int element)
970 if (IS_MCDUFFIN(element))
971 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
972 else if (IS_GRID(element))
973 return MM_MASK_GRID_1 + get_element_phase(element);
974 else if (IS_DF_GRID(element))
975 return MM_MASK_RECTANGLE;
976 else if (IS_DF_SLOPE(element))
977 return MM_MASK_SLOPE_1 + get_element_phase(element);
978 else if (IS_RECTANGLE(element))
979 return MM_MASK_RECTANGLE;
981 return MM_MASK_CIRCLE;
984 static int getLevelFromLaserX(int x)
986 return x / TILEX - (x < 0 ? 1 : 0); // correct negative values
989 static int getLevelFromLaserY(int y)
991 return y / TILEY - (y < 0 ? 1 : 0); // correct negative values
994 static int ScanPixel(void)
999 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
1000 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
1003 // follow laser beam until it hits something (at least the screen border)
1004 while (hit_mask == HIT_MASK_NO_HIT)
1010 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
1011 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
1013 Debug("game:mm:ScanPixel", "touched screen border!");
1015 return HIT_MASK_ALL;
1019 // check if laser scan has crossed element boundaries (not just mini tiles)
1020 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1021 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1023 if (cross_x && cross_y)
1025 int elx1 = (LX - XS) / TILEX;
1026 int ely1 = (LY + YS) / TILEY;
1027 int elx2 = (LX + XS) / TILEX;
1028 int ely2 = (LY - YS) / TILEY;
1030 // add element corners left and right from the laser beam to damage list
1032 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
1033 AddDamagedField(elx1, ely1);
1035 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
1036 AddDamagedField(elx2, ely2);
1039 for (i = 0; i < 4; i++)
1041 int px = LX + (i % 2) * 2;
1042 int py = LY + (i / 2) * 2;
1043 int dx = px % TILEX;
1044 int dy = py % TILEY;
1045 int lx = getLevelFromLaserX(px);
1046 int ly = getLevelFromLaserY(py);
1049 if (IN_LEV_FIELD(lx, ly))
1051 int element = Tile[lx][ly];
1053 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
1057 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
1059 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1061 pixel = ((element & (1 << pos)) ? 1 : 0);
1065 int pos = getMaskFromElement(element);
1067 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
1072 // check if laser is still inside visible playfield area
1073 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
1074 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
1077 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
1078 hit_mask |= (1 << i);
1081 if (hit_mask == HIT_MASK_NO_HIT)
1083 // hit nothing -- go on with another step
1092 static void DeactivateLaserTargetElement(void)
1094 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1095 laser.dest_element_last == EL_MINE_ACTIVE ||
1096 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1097 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1099 int x = laser.dest_element_last_x;
1100 int y = laser.dest_element_last_y;
1101 int element = laser.dest_element_last;
1103 if (Tile[x][y] == element)
1104 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1105 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1107 if (Tile[x][y] == EL_GRAY_BALL)
1110 laser.dest_element_last = EL_EMPTY;
1111 laser.dest_element_last_x = -1;
1112 laser.dest_element_last_y = -1;
1116 static void ScanLaser(void)
1118 int element = EL_EMPTY;
1119 int last_element = EL_EMPTY;
1120 int end = 0, rf = laser.num_edges;
1122 // do not scan laser again after the game was lost for whatever reason
1123 if (game_mm.game_over)
1126 // do not scan laser if fuse is off
1130 DeactivateLaserTargetElement();
1132 laser.overloaded = FALSE;
1133 laser.stops_inside_element = FALSE;
1135 DrawLaser(0, DL_LASER_ENABLED);
1138 Debug("game:mm:ScanLaser",
1139 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1147 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1150 laser.overloaded = TRUE;
1155 hit_mask = ScanPixel();
1158 Debug("game:mm:ScanLaser",
1159 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1163 // check if laser scan has hit two diagonally adjacent element corners
1164 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1165 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1167 // check if laser scan has crossed element boundaries (not just mini tiles)
1168 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1169 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1171 if (cross_x || cross_y)
1173 // hit something at next tile -- check out what it was
1174 ELX = getLevelFromLaserX(LX + XS);
1175 ELY = getLevelFromLaserY(LY + YS);
1179 // hit something at same tile -- check out what it was
1180 ELX = getLevelFromLaserX(LX);
1181 ELY = getLevelFromLaserY(LY);
1185 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1186 hit_mask, LX, LY, ELX, ELY);
1189 if (!IN_LEV_FIELD(ELX, ELY))
1191 // laser next step position
1192 int x = cSX + LX + XS;
1193 int y = cSY + LY + YS;
1195 // check if next step of laser is still inside visible playfield area
1196 if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
1197 y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
1199 // go on with another step
1207 laser.dest_element = element;
1212 boolean diagonally_adjacent_hit = FALSE;
1214 // handle special case of laser hitting two diagonally adjacent elements
1215 // (with or without a third corner element behind these two elements)
1216 if ((diag_1 || diag_2) && cross_x && cross_y)
1218 diagonally_adjacent_hit = TRUE;
1220 // compare the two diagonally adjacent elements
1222 int yoffset = 2 * (diag_1 ? -1 : +1);
1223 int elx1 = (LX - xoffset) / TILEX;
1224 int ely1 = (LY + yoffset) / TILEY;
1225 int elx2 = (LX + xoffset) / TILEX;
1226 int ely2 = (LY - yoffset) / TILEY;
1227 int e1 = Tile[elx1][ely1];
1228 int e2 = Tile[elx2][ely2];
1229 boolean use_element_1 = FALSE;
1231 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1233 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1234 use_element_1 = (RND(2) ? TRUE : FALSE);
1235 else if (IS_WALL_ICE(e1))
1236 use_element_1 = TRUE;
1238 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1240 // if both tiles match, we can just select the first one
1241 if (IS_WALL_AMOEBA(e1))
1242 use_element_1 = TRUE;
1244 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1246 // if both tiles match, we can just select the first one
1247 if (IS_ABSORBING_BLOCK(e1))
1248 use_element_1 = TRUE;
1251 ELX = (use_element_1 ? elx1 : elx2);
1252 ELY = (use_element_1 ? ely1 : ely2);
1256 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1257 hit_mask, LX, LY, ELX, ELY);
1260 last_element = element;
1262 element = Tile[ELX][ELY];
1263 laser.dest_element = element;
1266 Debug("game:mm:ScanLaser",
1267 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1270 LX % TILEX, LY % TILEY,
1275 if (!IN_LEV_FIELD(ELX, ELY))
1276 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1280 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1281 if (element == EL_EMPTY &&
1282 IS_GRID_STEEL(last_element) &&
1283 laser.current_angle % 4) // angle is not 90°
1284 element = last_element;
1286 if (element == EL_EMPTY)
1288 if (!HitOnlyAnEdge(hit_mask))
1291 else if (element == EL_FUSE_ON)
1293 if (HitPolarizer(element, hit_mask))
1296 else if (IS_GRID(element) || IS_DF_GRID(element))
1298 if (HitPolarizer(element, hit_mask))
1301 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1302 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1304 if (HitBlock(element, hit_mask))
1311 else if (IS_MCDUFFIN(element))
1313 if (HitLaserSource(element, hit_mask))
1316 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1317 IS_RECEIVER(element))
1319 if (HitLaserDestination(element, hit_mask))
1322 else if (IS_WALL(element))
1324 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1326 if (HitReflectingWalls(element, hit_mask))
1331 if (HitAbsorbingWalls(element, hit_mask))
1335 else if (IS_DF_SLOPE(element))
1337 if (diagonally_adjacent_hit)
1339 laser.overloaded = TRUE;
1344 if (hit_mask == HIT_MASK_LEFT ||
1345 hit_mask == HIT_MASK_RIGHT ||
1346 hit_mask == HIT_MASK_TOP ||
1347 hit_mask == HIT_MASK_BOTTOM)
1349 if (HitReflectingWalls(element, hit_mask))
1354 if (HitElement(element, hit_mask))
1360 if (HitElement(element, hit_mask))
1365 DrawLaser(rf - 1, DL_LASER_ENABLED);
1366 rf = laser.num_edges;
1368 if (!IS_DF_WALL_STEEL(element))
1370 // only used for scanning DF steel walls; reset for all other elements
1378 if (laser.dest_element != Tile[ELX][ELY])
1380 Debug("game:mm:ScanLaser",
1381 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1382 laser.dest_element, Tile[ELX][ELY]);
1386 if (!end && !laser.stops_inside_element && !StepBehind())
1389 Debug("game:mm:ScanLaser", "Go one step back");
1395 AddLaserEdge(LX, LY);
1399 DrawLaser(rf - 1, DL_LASER_ENABLED);
1401 Ct = CT = FrameCounter;
1404 if (!IN_LEV_FIELD(ELX, ELY))
1405 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1409 static void ScanLaser_FromLastMirror(void)
1411 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1414 for (i = start_pos; i >= 0; i--)
1415 if (laser.damage[i].is_mirror)
1418 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1420 DrawLaser(start_edge, DL_LASER_DISABLED);
1425 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1431 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1432 start_edge, num_edges, mode);
1437 Warn("DrawLaserExt: start_edge < 0");
1444 Warn("DrawLaserExt: num_edges < 0");
1450 if (mode == DL_LASER_DISABLED)
1452 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1456 // now draw the laser to the backbuffer and (if enabled) to the screen
1457 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1459 redraw_mask |= REDRAW_FIELD;
1461 if (mode == DL_LASER_ENABLED)
1464 // after the laser was deleted, the "damaged" graphics must be restored
1465 if (laser.num_damages)
1467 int damage_start = 0;
1470 // determine the starting edge, from which graphics need to be restored
1473 for (i = 0; i < laser.num_damages; i++)
1475 if (laser.damage[i].edge == start_edge + 1)
1484 // restore graphics from this starting edge to the end of damage list
1485 for (i = damage_start; i < laser.num_damages; i++)
1487 int lx = laser.damage[i].x;
1488 int ly = laser.damage[i].y;
1489 int element = Tile[lx][ly];
1491 if (Hit[lx][ly] == laser.damage[i].edge)
1492 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1495 if (Box[lx][ly] == laser.damage[i].edge)
1498 if (IS_DRAWABLE(element))
1499 DrawField_MM(lx, ly);
1502 elx = laser.damage[damage_start].x;
1503 ely = laser.damage[damage_start].y;
1504 element = Tile[elx][ely];
1507 if (IS_BEAMER(element))
1511 for (i = 0; i < laser.num_beamers; i++)
1512 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1514 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1515 mode, elx, ely, Hit[elx][ely], start_edge);
1516 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1517 get_element_angle(element), laser.damage[damage_start].angle);
1521 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1522 laser.num_beamers > 0 &&
1523 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1525 // element is outgoing beamer
1526 laser.num_damages = damage_start + 1;
1528 if (IS_BEAMER(element))
1529 laser.current_angle = get_element_angle(element);
1533 // element is incoming beamer or other element
1534 laser.num_damages = damage_start;
1535 laser.current_angle = laser.damage[laser.num_damages].angle;
1540 // no damages but McDuffin himself (who needs to be redrawn anyway)
1542 elx = laser.start_edge.x;
1543 ely = laser.start_edge.y;
1544 element = Tile[elx][ely];
1547 laser.num_edges = start_edge + 1;
1548 if (start_edge == 0)
1549 laser.current_angle = laser.start_angle;
1551 LX = laser.edge[start_edge].x - cSX2;
1552 LY = laser.edge[start_edge].y - cSY2;
1553 XS = 2 * Step[laser.current_angle].x;
1554 YS = 2 * Step[laser.current_angle].y;
1557 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1563 if (IS_BEAMER(element) ||
1564 IS_FIBRE_OPTIC(element) ||
1565 IS_PACMAN(element) ||
1566 IS_POLAR(element) ||
1567 IS_POLAR_CROSS(element) ||
1568 element == EL_FUSE_ON)
1573 Debug("game:mm:DrawLaserExt", "element == %d", element);
1576 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1577 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1581 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1582 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1583 (laser.num_beamers == 0 ||
1584 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1586 // element is incoming beamer or other element
1587 step_size = -step_size;
1592 if (IS_BEAMER(element))
1593 Debug("game:mm:DrawLaserExt",
1594 "start_edge == %d, laser.beamer_edge == %d",
1595 start_edge, laser.beamer_edge);
1598 LX += step_size * XS;
1599 LY += step_size * YS;
1601 else if (element != EL_EMPTY)
1610 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1615 void DrawLaser(int start_edge, int mode)
1617 // do not draw laser if fuse is off
1618 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1621 if (mode == DL_LASER_DISABLED)
1622 DeactivateLaserTargetElement();
1624 if (laser.num_edges - start_edge < 0)
1626 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1631 // check if laser is interrupted by beamer element
1632 if (laser.num_beamers > 0 &&
1633 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1635 if (mode == DL_LASER_ENABLED)
1638 int tmp_start_edge = start_edge;
1640 // draw laser segments forward from the start to the last beamer
1641 for (i = 0; i < laser.num_beamers; i++)
1643 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1645 if (tmp_num_edges <= 0)
1649 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1650 i, laser.beamer_edge[i], tmp_start_edge);
1653 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1655 tmp_start_edge = laser.beamer_edge[i];
1658 // draw last segment from last beamer to the end
1659 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1665 int last_num_edges = laser.num_edges;
1666 int num_beamers = laser.num_beamers;
1668 // delete laser segments backward from the end to the first beamer
1669 for (i = num_beamers - 1; i >= 0; i--)
1671 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1673 if (laser.beamer_edge[i] - start_edge <= 0)
1676 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1678 last_num_edges = laser.beamer_edge[i];
1679 laser.num_beamers--;
1683 if (last_num_edges - start_edge <= 0)
1684 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1685 last_num_edges, start_edge);
1688 // special case when rotating first beamer: delete laser edge on beamer
1689 // (but do not start scanning on previous edge to prevent mirror sound)
1690 if (last_num_edges - start_edge == 1 && start_edge > 0)
1691 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1693 // delete first segment from start to the first beamer
1694 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1699 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1702 game_mm.laser_enabled = mode;
1705 void DrawLaser_MM(void)
1707 DrawLaser(0, game_mm.laser_enabled);
1710 static boolean HitElement(int element, int hit_mask)
1712 if (IS_DF_SLOPE(element))
1714 int mirrored_angle = get_mirrored_angle(laser.current_angle,
1715 get_element_angle(element));
1716 int opposite_angle = get_opposite_angle(laser.current_angle);
1718 // check if laser is reflected by slope by 180°
1719 if (mirrored_angle == opposite_angle)
1724 AddDamagedField(LX / TILEX, LY / TILEY);
1726 laser.overloaded = TRUE;
1733 if (HitOnlyAnEdge(hit_mask))
1737 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1738 element = MovingOrBlocked2Element_MM(ELX, ELY);
1741 Debug("game:mm:HitElement", "(1): element == %d", element);
1745 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1746 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1749 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1753 AddDamagedField(ELX, ELY);
1755 boolean through_center = ((ELX * TILEX + 14 - LX) * YS ==
1756 (ELY * TILEY + 14 - LY) * XS);
1758 // this is more precise: check if laser would go through the center
1759 if (!IS_DF_SLOPE(element) && !through_center)
1763 // prevent cutting through laser emitter with laser beam
1764 if (IS_LASER(element))
1767 // skip the whole element before continuing the scan
1775 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1777 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1779 /* skipping scan positions to the right and down skips one scan
1780 position too much, because this is only the top left scan position
1781 of totally four scan positions (plus one to the right, one to the
1782 bottom and one to the bottom right) */
1783 /* ... but only roll back scan position if more than one step done */
1793 Debug("game:mm:HitElement", "(2): element == %d", element);
1796 if (LX + 5 * XS < 0 ||
1806 Debug("game:mm:HitElement", "(3): element == %d", element);
1809 if (IS_POLAR(element) &&
1810 ((element - EL_POLAR_START) % 2 ||
1811 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1813 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1815 laser.num_damages--;
1820 if (IS_POLAR_CROSS(element) &&
1821 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1823 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1825 laser.num_damages--;
1830 if (IS_DF_SLOPE(element) && !through_center)
1834 if (hit_mask == HIT_MASK_ALL)
1836 // laser already inside slope -- go back half step
1843 AddLaserEdge(LX, LY);
1845 LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0);
1846 LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0);
1848 else if (!IS_BEAMER(element) &&
1849 !IS_FIBRE_OPTIC(element) &&
1850 !IS_GRID_WOOD(element) &&
1851 element != EL_FUEL_EMPTY)
1854 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1855 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1857 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1860 LX = ELX * TILEX + 14;
1861 LY = ELY * TILEY + 14;
1863 AddLaserEdge(LX, LY);
1866 if (IS_MIRROR(element) ||
1867 IS_MIRROR_FIXED(element) ||
1868 IS_POLAR(element) ||
1869 IS_POLAR_CROSS(element) ||
1870 IS_DF_MIRROR(element) ||
1871 IS_DF_MIRROR_AUTO(element) ||
1872 IS_DF_MIRROR_FIXED(element) ||
1873 IS_DF_SLOPE(element) ||
1874 element == EL_PRISM ||
1875 element == EL_REFRACTOR)
1877 int current_angle = laser.current_angle;
1880 laser.num_damages--;
1882 AddDamagedField(ELX, ELY);
1884 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1887 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1889 if (IS_MIRROR(element) ||
1890 IS_MIRROR_FIXED(element) ||
1891 IS_DF_MIRROR(element) ||
1892 IS_DF_MIRROR_AUTO(element) ||
1893 IS_DF_MIRROR_FIXED(element) ||
1894 IS_DF_SLOPE(element))
1895 laser.current_angle = get_mirrored_angle(laser.current_angle,
1896 get_element_angle(element));
1898 if (element == EL_PRISM || element == EL_REFRACTOR)
1899 laser.current_angle = RND(16);
1901 XS = 2 * Step[laser.current_angle].x;
1902 YS = 2 * Step[laser.current_angle].y;
1904 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1909 LX += step_size * XS;
1910 LY += step_size * YS;
1912 // draw sparkles on mirror
1913 if ((IS_MIRROR(element) ||
1914 IS_MIRROR_FIXED(element) ||
1915 element == EL_PRISM) &&
1916 current_angle != laser.current_angle)
1918 MovDelay[ELX][ELY] = 11; // start animation
1921 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1922 current_angle != laser.current_angle)
1923 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1926 (get_opposite_angle(laser.current_angle) ==
1927 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1929 if (IS_DF_SLOPE(element))
1931 // handle special cases for slope element
1933 if (IS_45_ANGLE(laser.current_angle))
1937 elx = getLevelFromLaserX(LX);
1938 ely = getLevelFromLaserY(LY);
1940 if (IN_LEV_FIELD(elx, ely))
1942 int element_next = Tile[elx][ely];
1944 // check if slope is followed by slope with opposite orientation
1945 if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2)
1946 laser.overloaded = TRUE;
1949 int nr = element - EL_DF_SLOPE_START;
1950 int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) :
1951 nr == 1 ? (XS > 0 ? TILEX : 1) :
1952 nr == 2 ? (XS > 0 ? TILEX : 1) :
1953 nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0);
1954 int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) :
1955 nr == 1 ? (YS > 0 ? TILEY - 1 : -1) :
1956 nr == 2 ? (YS > 0 ? TILEY : 0) :
1957 nr == 3 ? (YS > 0 ? TILEY : 0) : 0);
1959 int px = ELX * TILEX + dx;
1960 int py = ELY * TILEY + dy;
1965 elx = getLevelFromLaserX(px);
1966 ely = getLevelFromLaserY(py);
1968 if (IN_LEV_FIELD(elx, ely))
1970 int element_side = Tile[elx][ely];
1972 // check if end of slope is blocked by other element
1973 if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side))
1975 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1977 if (element & (1 << pos))
1978 laser.overloaded = TRUE;
1982 int pos = getMaskFromElement(element_side);
1984 if (mm_masks[pos][dx / 2][dy / 2] == 'X')
1985 laser.overloaded = TRUE;
1991 return (laser.overloaded ? TRUE : FALSE);
1994 if (element == EL_FUEL_FULL)
1996 laser.stops_inside_element = TRUE;
2001 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
2003 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2005 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
2006 element == EL_MINE ? EL_MINE_ACTIVE :
2007 EL_GRAY_BALL_ACTIVE);
2009 GfxFrame[ELX][ELY] = 0; // restart animation
2011 laser.dest_element_last = Tile[ELX][ELY];
2012 laser.dest_element_last_x = ELX;
2013 laser.dest_element_last_y = ELY;
2015 if (element == EL_MINE)
2016 laser.overloaded = TRUE;
2019 if (element == EL_KETTLE ||
2020 element == EL_CELL ||
2021 element == EL_KEY ||
2022 element == EL_LIGHTBALL ||
2023 element == EL_PACMAN ||
2024 IS_PACMAN(element) ||
2025 IS_ENVELOPE(element))
2027 if (!IS_PACMAN(element) &&
2028 !IS_ENVELOPE(element))
2031 if (element == EL_PACMAN)
2034 if (element == EL_KETTLE || element == EL_CELL)
2036 if (game_mm.kettles_still_needed > 0)
2037 game_mm.kettles_still_needed--;
2039 game.snapshot.collected_item = TRUE;
2041 if (game_mm.kettles_still_needed == 0)
2045 DrawLaser(0, DL_LASER_ENABLED);
2048 else if (element == EL_KEY)
2052 else if (IS_PACMAN(element))
2054 DeletePacMan(ELX, ELY);
2056 else if (IS_ENVELOPE(element))
2058 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
2061 RaiseScoreElement_MM(element);
2066 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
2068 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2070 DrawLaser(0, DL_LASER_ENABLED);
2072 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
2074 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
2075 game_mm.lights_still_needed--;
2079 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
2080 game_mm.lights_still_needed++;
2083 DrawField_MM(ELX, ELY);
2084 DrawLaser(0, DL_LASER_ENABLED);
2089 laser.stops_inside_element = TRUE;
2095 Debug("game:mm:HitElement", "(4): element == %d", element);
2098 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
2099 laser.num_beamers < MAX_NUM_BEAMERS &&
2100 laser.beamer[BEAMER_NR(element)][1].num)
2102 int beamer_angle = get_element_angle(element);
2103 int beamer_nr = BEAMER_NR(element);
2107 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
2110 laser.num_damages--;
2112 if (IS_FIBRE_OPTIC(element) ||
2113 laser.current_angle == get_opposite_angle(beamer_angle))
2117 LX = ELX * TILEX + 14;
2118 LY = ELY * TILEY + 14;
2120 AddLaserEdge(LX, LY);
2121 AddDamagedField(ELX, ELY);
2123 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2126 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2128 pos = (ELX == laser.beamer[beamer_nr][0].x &&
2129 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
2130 ELX = laser.beamer[beamer_nr][pos].x;
2131 ELY = laser.beamer[beamer_nr][pos].y;
2132 LX = ELX * TILEX + 14;
2133 LY = ELY * TILEY + 14;
2135 if (IS_BEAMER(element))
2137 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
2138 XS = 2 * Step[laser.current_angle].x;
2139 YS = 2 * Step[laser.current_angle].y;
2142 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
2144 AddLaserEdge(LX, LY);
2145 AddDamagedField(ELX, ELY);
2147 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2150 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2152 if (laser.current_angle == (laser.current_angle >> 1) << 1)
2157 LX += step_size * XS;
2158 LY += step_size * YS;
2160 laser.num_beamers++;
2169 static boolean HitOnlyAnEdge(int hit_mask)
2171 // check if the laser hit only the edge of an element and, if so, go on
2174 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
2178 if ((hit_mask == HIT_MASK_TOPLEFT ||
2179 hit_mask == HIT_MASK_TOPRIGHT ||
2180 hit_mask == HIT_MASK_BOTTOMLEFT ||
2181 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
2182 laser.current_angle % 4) // angle is not 90°
2186 if (hit_mask == HIT_MASK_TOPLEFT)
2191 else if (hit_mask == HIT_MASK_TOPRIGHT)
2196 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
2201 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
2207 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
2213 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
2220 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
2226 static boolean HitPolarizer(int element, int hit_mask)
2228 if (HitOnlyAnEdge(hit_mask))
2231 if (IS_DF_GRID(element))
2233 int grid_angle = get_element_angle(element);
2236 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2237 grid_angle, laser.current_angle);
2240 AddLaserEdge(LX, LY);
2241 AddDamagedField(ELX, ELY);
2244 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2246 if (laser.current_angle == grid_angle ||
2247 laser.current_angle == get_opposite_angle(grid_angle))
2249 // skip the whole element before continuing the scan
2255 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2257 if (LX/TILEX > ELX || LY/TILEY > ELY)
2259 /* skipping scan positions to the right and down skips one scan
2260 position too much, because this is only the top left scan position
2261 of totally four scan positions (plus one to the right, one to the
2262 bottom and one to the bottom right) */
2268 AddLaserEdge(LX, LY);
2274 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2276 LX / TILEX, LY / TILEY,
2277 LX % TILEX, LY % TILEY);
2282 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2284 return HitReflectingWalls(element, hit_mask);
2288 return HitAbsorbingWalls(element, hit_mask);
2291 else if (IS_GRID_STEEL(element))
2293 // may be required if graphics for steel grid redefined
2294 AddDamagedField(ELX, ELY);
2296 return HitReflectingWalls(element, hit_mask);
2298 else // IS_GRID_WOOD
2300 // may be required if graphics for wooden grid redefined
2301 AddDamagedField(ELX, ELY);
2303 return HitAbsorbingWalls(element, hit_mask);
2309 static boolean HitBlock(int element, int hit_mask)
2311 boolean check = FALSE;
2313 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2314 game_mm.num_keys == 0)
2317 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2320 int ex = ELX * TILEX + 14;
2321 int ey = ELY * TILEY + 14;
2325 for (i = 1; i < 32; i++)
2330 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2335 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2336 return HitAbsorbingWalls(element, hit_mask);
2340 AddLaserEdge(LX - XS, LY - YS);
2341 AddDamagedField(ELX, ELY);
2344 Box[ELX][ELY] = laser.num_edges;
2346 return HitReflectingWalls(element, hit_mask);
2349 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2351 int xs = XS / 2, ys = YS / 2;
2353 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2354 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2356 laser.overloaded = (element == EL_GATE_STONE);
2361 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2362 (hit_mask == HIT_MASK_TOP ||
2363 hit_mask == HIT_MASK_LEFT ||
2364 hit_mask == HIT_MASK_RIGHT ||
2365 hit_mask == HIT_MASK_BOTTOM))
2366 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2367 hit_mask == HIT_MASK_BOTTOM),
2368 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2369 hit_mask == HIT_MASK_RIGHT));
2370 AddLaserEdge(LX, LY);
2376 if (element == EL_GATE_STONE && Box[ELX][ELY])
2378 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2390 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2392 int xs = XS / 2, ys = YS / 2;
2394 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2395 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2397 laser.overloaded = (element == EL_BLOCK_STONE);
2402 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2403 (hit_mask == HIT_MASK_TOP ||
2404 hit_mask == HIT_MASK_LEFT ||
2405 hit_mask == HIT_MASK_RIGHT ||
2406 hit_mask == HIT_MASK_BOTTOM))
2407 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2408 hit_mask == HIT_MASK_BOTTOM),
2409 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2410 hit_mask == HIT_MASK_RIGHT));
2411 AddDamagedField(ELX, ELY);
2413 LX = ELX * TILEX + 14;
2414 LY = ELY * TILEY + 14;
2416 AddLaserEdge(LX, LY);
2418 laser.stops_inside_element = TRUE;
2426 static boolean HitLaserSource(int element, int hit_mask)
2428 if (HitOnlyAnEdge(hit_mask))
2431 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2433 laser.overloaded = TRUE;
2438 static boolean HitLaserDestination(int element, int hit_mask)
2440 if (HitOnlyAnEdge(hit_mask))
2443 if (element != EL_EXIT_OPEN &&
2444 !(IS_RECEIVER(element) &&
2445 game_mm.kettles_still_needed == 0 &&
2446 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2448 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2453 if (IS_RECEIVER(element) ||
2454 (IS_22_5_ANGLE(laser.current_angle) &&
2455 (ELX != (LX + 6 * XS) / TILEX ||
2456 ELY != (LY + 6 * YS) / TILEY ||
2465 LX = ELX * TILEX + 14;
2466 LY = ELY * TILEY + 14;
2468 laser.stops_inside_element = TRUE;
2471 AddLaserEdge(LX, LY);
2472 AddDamagedField(ELX, ELY);
2474 if (game_mm.lights_still_needed == 0)
2476 game_mm.level_solved = TRUE;
2478 SetTileCursorActive(FALSE);
2484 static boolean HitReflectingWalls(int element, int hit_mask)
2486 // check if laser hits side of a wall with an angle that is not 90°
2487 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2488 hit_mask == HIT_MASK_LEFT ||
2489 hit_mask == HIT_MASK_RIGHT ||
2490 hit_mask == HIT_MASK_BOTTOM))
2492 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2497 if (!IS_DF_GRID(element))
2498 AddLaserEdge(LX, LY);
2500 // check if laser hits wall with an angle of 45°
2501 if (!IS_22_5_ANGLE(laser.current_angle))
2503 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2506 laser.current_angle = get_mirrored_angle(laser.current_angle,
2509 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2512 laser.current_angle = get_mirrored_angle(laser.current_angle,
2516 AddLaserEdge(LX, LY);
2518 XS = 2 * Step[laser.current_angle].x;
2519 YS = 2 * Step[laser.current_angle].y;
2523 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2525 laser.current_angle = get_mirrored_angle(laser.current_angle,
2530 if (!IS_DF_GRID(element))
2531 AddLaserEdge(LX, LY);
2536 if (!IS_DF_GRID(element))
2537 AddLaserEdge(LX, LY + YS / 2);
2540 if (!IS_DF_GRID(element))
2541 AddLaserEdge(LX, LY);
2544 YS = 2 * Step[laser.current_angle].y;
2548 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2550 laser.current_angle = get_mirrored_angle(laser.current_angle,
2555 if (!IS_DF_GRID(element))
2556 AddLaserEdge(LX, LY);
2561 if (!IS_DF_GRID(element))
2562 AddLaserEdge(LX + XS / 2, LY);
2565 if (!IS_DF_GRID(element))
2566 AddLaserEdge(LX, LY);
2569 XS = 2 * Step[laser.current_angle].x;
2575 // reflection at the edge of reflecting DF style wall
2576 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2578 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2579 hit_mask == HIT_MASK_TOPRIGHT) ||
2580 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2581 hit_mask == HIT_MASK_TOPLEFT) ||
2582 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2583 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2584 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2585 hit_mask == HIT_MASK_BOTTOMRIGHT))
2588 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2589 ANG_MIRROR_135 : ANG_MIRROR_45);
2591 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2593 AddDamagedField(ELX, ELY);
2594 AddLaserEdge(LX, LY);
2596 laser.current_angle = get_mirrored_angle(laser.current_angle,
2604 AddLaserEdge(LX, LY);
2610 // reflection inside an edge of reflecting DF style wall
2611 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2613 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2614 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2615 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2616 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2617 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2618 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2619 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2620 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2623 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2624 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2625 ANG_MIRROR_135 : ANG_MIRROR_45);
2627 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2630 AddDamagedField(ELX, ELY);
2633 AddLaserEdge(LX - XS, LY - YS);
2634 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2635 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2637 laser.current_angle = get_mirrored_angle(laser.current_angle,
2645 AddLaserEdge(LX, LY);
2651 // check if laser hits DF style wall with an angle of 90°
2652 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2654 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2655 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2656 (IS_VERT_ANGLE(laser.current_angle) &&
2657 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2659 // laser at last step touched nothing or the same side of the wall
2660 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2662 AddDamagedField(ELX, ELY);
2669 last_hit_mask = hit_mask;
2676 if (!HitOnlyAnEdge(hit_mask))
2678 laser.overloaded = TRUE;
2686 static boolean HitAbsorbingWalls(int element, int hit_mask)
2688 if (HitOnlyAnEdge(hit_mask))
2692 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2694 AddLaserEdge(LX - XS, LY - YS);
2701 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2703 AddLaserEdge(LX - XS, LY - YS);
2709 if (IS_WALL_WOOD(element) ||
2710 IS_DF_WALL_WOOD(element) ||
2711 IS_GRID_WOOD(element) ||
2712 IS_GRID_WOOD_FIXED(element) ||
2713 IS_GRID_WOOD_AUTO(element) ||
2714 element == EL_FUSE_ON ||
2715 element == EL_BLOCK_WOOD ||
2716 element == EL_GATE_WOOD)
2718 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2723 if (IS_WALL_ICE(element))
2729 // check if laser hit adjacent edges of two diagonal tiles
2730 if (ELX != lx / TILEX)
2732 if (ELY != ly / TILEY)
2735 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2736 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2738 // check if laser hits wall with an angle of 90°
2739 if (IS_90_ANGLE(laser.current_angle))
2740 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2742 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2746 for (i = 0; i < 4; i++)
2748 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2749 mask = 15 - (8 >> i);
2750 else if (ABS(XS) == 4 &&
2752 (XS > 0) == (i % 2) &&
2753 (YS < 0) == (i / 2))
2754 mask = 3 + (i / 2) * 9;
2755 else if (ABS(YS) == 4 &&
2757 (XS < 0) == (i % 2) &&
2758 (YS > 0) == (i / 2))
2759 mask = 5 + (i % 2) * 5;
2763 laser.wall_mask = mask;
2765 else if (IS_WALL_AMOEBA(element))
2767 int elx = (LX - 2 * XS) / TILEX;
2768 int ely = (LY - 2 * YS) / TILEY;
2769 int element2 = Tile[elx][ely];
2772 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2774 laser.dest_element = EL_EMPTY;
2782 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2783 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2785 if (IS_90_ANGLE(laser.current_angle))
2786 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2788 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2790 laser.wall_mask = mask;
2796 static void OpenExit(int x, int y)
2800 if (!MovDelay[x][y]) // next animation frame
2801 MovDelay[x][y] = 4 * delay;
2803 if (MovDelay[x][y]) // wait some time before next frame
2808 phase = MovDelay[x][y] / delay;
2810 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2811 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2813 if (!MovDelay[x][y])
2815 Tile[x][y] = EL_EXIT_OPEN;
2821 static void OpenGrayBall(int x, int y)
2825 if (!MovDelay[x][y]) // next animation frame
2827 if (IS_WALL(Store[x][y]))
2829 DrawWalls_MM(x, y, Store[x][y]);
2831 // copy wall tile to spare bitmap for "melting" animation
2832 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2833 TILEX, TILEY, x * TILEX, y * TILEY);
2835 DrawElement_MM(x, y, EL_GRAY_BALL);
2838 MovDelay[x][y] = 50 * delay;
2841 if (MovDelay[x][y]) // wait some time before next frame
2845 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2849 int dx = RND(26), dy = RND(26);
2851 if (IS_WALL(Store[x][y]))
2853 // copy wall tile from spare bitmap for "melting" animation
2854 bitmap = bitmap_db_field;
2860 int graphic = el2gfx(Store[x][y]);
2862 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2865 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2866 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2868 laser.redraw = TRUE;
2870 MarkTileDirty(x, y);
2873 if (!MovDelay[x][y])
2875 Tile[x][y] = Store[x][y];
2876 Store[x][y] = Store2[x][y] = 0;
2877 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2879 InitField(x, y, FALSE);
2882 ScanLaser_FromLastMirror();
2887 static void OpenEnvelope(int x, int y)
2889 int num_frames = 8; // seven frames plus final empty space
2891 if (!MovDelay[x][y]) // next animation frame
2892 MovDelay[x][y] = num_frames;
2894 if (MovDelay[x][y]) // wait some time before next frame
2896 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2900 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2902 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2903 int frame = num_frames - MovDelay[x][y] - 1;
2905 DrawGraphicAnimation_MM(x, y, graphic, frame);
2907 laser.redraw = TRUE;
2910 if (MovDelay[x][y] == 0)
2912 Tile[x][y] = EL_EMPTY;
2923 static void MeltIce(int x, int y)
2928 if (!MovDelay[x][y]) // next animation frame
2929 MovDelay[x][y] = frames * delay;
2931 if (MovDelay[x][y]) // wait some time before next frame
2934 int wall_mask = Store2[x][y];
2935 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2938 phase = frames - MovDelay[x][y] / delay - 1;
2940 if (!MovDelay[x][y])
2942 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2943 Store[x][y] = Store2[x][y] = 0;
2945 DrawWalls_MM(x, y, Tile[x][y]);
2947 if (Tile[x][y] == EL_WALL_ICE_BASE)
2948 Tile[x][y] = EL_EMPTY;
2950 ScanLaser_FromLastMirror();
2952 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2954 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2956 laser.redraw = TRUE;
2961 static void GrowAmoeba(int x, int y)
2966 if (!MovDelay[x][y]) // next animation frame
2967 MovDelay[x][y] = frames * delay;
2969 if (MovDelay[x][y]) // wait some time before next frame
2972 int wall_mask = Store2[x][y];
2973 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2976 phase = MovDelay[x][y] / delay;
2978 if (!MovDelay[x][y])
2980 Tile[x][y] = real_element;
2981 Store[x][y] = Store2[x][y] = 0;
2983 DrawWalls_MM(x, y, Tile[x][y]);
2984 DrawLaser(0, DL_LASER_ENABLED);
2986 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2988 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2993 static void DrawFieldAnimated_MM(int x, int y)
2997 laser.redraw = TRUE;
3000 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
3002 int element = Tile[x][y];
3003 int graphic = el2gfx(element);
3005 if (!getGraphicInfo_NewFrame(x, y, graphic))
3010 laser.redraw = TRUE;
3013 static void DrawFieldTwinkle(int x, int y)
3015 if (MovDelay[x][y] != 0) // wait some time before next frame
3021 if (MovDelay[x][y] != 0)
3023 int graphic = IMG_TWINKLE_WHITE;
3024 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
3026 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
3029 laser.redraw = TRUE;
3033 static void Explode_MM(int x, int y, int phase, int mode)
3035 int num_phase = 9, delay = 2;
3036 int last_phase = num_phase * delay;
3037 int half_phase = (num_phase / 2) * delay;
3040 laser.redraw = TRUE;
3042 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
3044 center_element = Tile[x][y];
3046 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
3048 // put moving element to center field (and let it explode there)
3049 center_element = MovingOrBlocked2Element_MM(x, y);
3050 RemoveMovingField_MM(x, y);
3052 Tile[x][y] = center_element;
3055 if (center_element != EL_GRAY_BALL_ACTIVE)
3056 Store[x][y] = EL_EMPTY;
3057 Store2[x][y] = center_element;
3059 Tile[x][y] = EL_EXPLODING_OPAQUE;
3061 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
3062 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
3063 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
3066 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3068 ExplodePhase[x][y] = 1;
3074 GfxFrame[x][y] = 0; // restart explosion animation
3076 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
3078 center_element = Store2[x][y];
3080 if (phase == half_phase && Store[x][y] == EL_EMPTY)
3082 Tile[x][y] = EL_EXPLODING_TRANSP;
3084 if (x == ELX && y == ELY)
3088 if (phase == last_phase)
3090 if (center_element == EL_BOMB_ACTIVE)
3092 DrawLaser(0, DL_LASER_DISABLED);
3095 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3097 laser.overloaded = FALSE;
3099 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
3101 GameOver_MM(GAME_OVER_BOMB);
3104 Tile[x][y] = Store[x][y];
3106 Store[x][y] = Store2[x][y] = 0;
3107 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3109 InitField(x, y, FALSE);
3112 if (center_element == EL_GRAY_BALL_ACTIVE)
3113 ScanLaser_FromLastMirror();
3115 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
3117 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
3118 int frame = getGraphicAnimationFrameXY(graphic, x, y);
3120 DrawGraphicAnimation_MM(x, y, graphic, frame);
3122 MarkTileDirty(x, y);
3126 static void Bang_MM(int x, int y)
3128 int element = Tile[x][y];
3130 if (IS_PACMAN(element))
3131 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3132 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
3133 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3134 else if (element == EL_KEY)
3135 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3137 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3139 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
3142 static void TurnRound(int x, int y)
3154 { 0, 0 }, { 0, 0 }, { 0, 0 },
3159 int left, right, back;
3163 { MV_DOWN, MV_UP, MV_RIGHT },
3164 { MV_UP, MV_DOWN, MV_LEFT },
3166 { MV_LEFT, MV_RIGHT, MV_DOWN },
3170 { MV_RIGHT, MV_LEFT, MV_UP }
3173 int element = Tile[x][y];
3174 int old_move_dir = MovDir[x][y];
3175 int right_dir = turn[old_move_dir].right;
3176 int back_dir = turn[old_move_dir].back;
3177 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
3178 int right_x = x + right_dx, right_y = y + right_dy;
3180 if (element == EL_PACMAN)
3182 boolean can_turn_right = FALSE;
3184 if (IN_LEV_FIELD(right_x, right_y) &&
3185 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
3186 can_turn_right = TRUE;
3189 MovDir[x][y] = right_dir;
3191 MovDir[x][y] = back_dir;
3197 static void StartMoving_MM(int x, int y)
3199 int element = Tile[x][y];
3204 if (CAN_MOVE(element))
3208 if (MovDelay[x][y]) // wait some time before next movement
3216 // now make next step
3218 Moving2Blocked(x, y, &newx, &newy); // get next screen position
3220 if (element == EL_PACMAN &&
3221 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
3222 !ObjHit(newx, newy, HIT_POS_CENTER))
3224 Store[newx][newy] = Tile[newx][newy];
3225 Tile[newx][newy] = EL_EMPTY;
3227 DrawField_MM(newx, newy);
3229 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3230 ObjHit(newx, newy, HIT_POS_CENTER))
3232 // object was running against a wall
3239 InitMovingField_MM(x, y, MovDir[x][y]);
3243 ContinueMoving_MM(x, y);
3246 static void ContinueMoving_MM(int x, int y)
3248 int element = Tile[x][y];
3249 int direction = MovDir[x][y];
3250 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3251 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3252 int horiz_move = (dx!=0);
3253 int newx = x + dx, newy = y + dy;
3254 int step = (horiz_move ? dx : dy) * TILEX / 8;
3256 MovPos[x][y] += step;
3258 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3260 Tile[x][y] = EL_EMPTY;
3261 Tile[newx][newy] = element;
3263 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3264 MovDelay[newx][newy] = 0;
3266 if (!CAN_MOVE(element))
3267 MovDir[newx][newy] = 0;
3270 DrawField_MM(newx, newy);
3272 Stop[newx][newy] = TRUE;
3274 if (element == EL_PACMAN)
3276 if (Store[newx][newy] == EL_BOMB)
3277 Bang_MM(newx, newy);
3279 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3280 (LX + 2 * XS) / TILEX == newx &&
3281 (LY + 2 * YS) / TILEY == newy)
3288 else // still moving on
3293 laser.redraw = TRUE;
3296 boolean ClickElement(int x, int y, int button)
3298 static DelayCounter click_delay = { CLICK_DELAY };
3299 static boolean new_button = TRUE;
3300 boolean element_clicked = FALSE;
3305 // initialize static variables
3306 click_delay.count = 0;
3307 click_delay.value = CLICK_DELAY;
3313 // do not rotate objects hit by the laser after the game was solved
3314 if (game_mm.level_solved && Hit[x][y])
3317 if (button == MB_RELEASED)
3320 click_delay.value = CLICK_DELAY;
3322 // release eventually hold auto-rotating mirror
3323 RotateMirror(x, y, MB_RELEASED);
3328 if (!FrameReached(&click_delay) && !new_button)
3331 if (button == MB_MIDDLEBUTTON) // middle button has no function
3334 if (!IN_LEV_FIELD(x, y))
3337 if (Tile[x][y] == EL_EMPTY)
3340 element = Tile[x][y];
3342 if (IS_MIRROR(element) ||
3343 IS_BEAMER(element) ||
3344 IS_POLAR(element) ||
3345 IS_POLAR_CROSS(element) ||
3346 IS_DF_MIRROR(element) ||
3347 IS_DF_MIRROR_AUTO(element))
3349 RotateMirror(x, y, button);
3351 element_clicked = TRUE;
3353 else if (IS_MCDUFFIN(element))
3355 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3357 if (has_laser && !laser.fuse_off)
3358 DrawLaser(0, DL_LASER_DISABLED);
3360 element = get_rotated_element(element, BUTTON_ROTATION(button));
3362 Tile[x][y] = element;
3367 laser.start_angle = get_element_angle(element);
3371 if (!laser.fuse_off)
3375 element_clicked = TRUE;
3377 else if (element == EL_FUSE_ON && laser.fuse_off)
3379 if (x != laser.fuse_x || y != laser.fuse_y)
3382 laser.fuse_off = FALSE;
3383 laser.fuse_x = laser.fuse_y = -1;
3385 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3388 element_clicked = TRUE;
3390 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3392 laser.fuse_off = TRUE;
3395 laser.overloaded = FALSE;
3397 DrawLaser(0, DL_LASER_DISABLED);
3398 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3400 element_clicked = TRUE;
3402 else if (element == EL_LIGHTBALL)
3405 RaiseScoreElement_MM(element);
3406 DrawLaser(0, DL_LASER_ENABLED);
3408 element_clicked = TRUE;
3410 else if (IS_ENVELOPE(element))
3412 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3414 element_clicked = TRUE;
3417 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3420 return element_clicked;
3423 static void RotateMirror(int x, int y, int button)
3425 if (button == MB_RELEASED)
3427 // release eventually hold auto-rotating mirror
3434 if (IS_MIRROR(Tile[x][y]) ||
3435 IS_POLAR_CROSS(Tile[x][y]) ||
3436 IS_POLAR(Tile[x][y]) ||
3437 IS_BEAMER(Tile[x][y]) ||
3438 IS_DF_MIRROR(Tile[x][y]) ||
3439 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3440 IS_GRID_WOOD_AUTO(Tile[x][y]))
3442 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3444 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3446 if (button == MB_LEFTBUTTON)
3448 // left mouse button only for manual adjustment, no auto-rotating;
3449 // freeze mirror for until mouse button released
3453 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3455 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3459 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3461 int edge = Hit[x][y];
3467 DrawLaser(edge - 1, DL_LASER_DISABLED);
3471 else if (ObjHit(x, y, HIT_POS_CENTER))
3473 int edge = Hit[x][y];
3477 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3482 DrawLaser(edge - 1, DL_LASER_DISABLED);
3489 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3494 if ((IS_BEAMER(Tile[x][y]) ||
3495 IS_POLAR(Tile[x][y]) ||
3496 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3498 if (IS_BEAMER(Tile[x][y]))
3501 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3502 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3515 DrawLaser(0, DL_LASER_ENABLED);
3519 static void AutoRotateMirrors(void)
3523 if (!FrameReached(&rotate_delay))
3526 for (x = 0; x < lev_fieldx; x++)
3528 for (y = 0; y < lev_fieldy; y++)
3530 int element = Tile[x][y];
3532 // do not rotate objects hit by the laser after the game was solved
3533 if (game_mm.level_solved && Hit[x][y])
3536 if (IS_DF_MIRROR_AUTO(element) ||
3537 IS_GRID_WOOD_AUTO(element) ||
3538 IS_GRID_STEEL_AUTO(element) ||
3539 element == EL_REFRACTOR)
3541 RotateMirror(x, y, MB_RIGHTBUTTON);
3543 laser.redraw = TRUE;
3549 static boolean ObjHit(int obx, int oby, int bits)
3556 if (bits & HIT_POS_CENTER)
3558 if (CheckLaserPixel(cSX + obx + 15,
3563 if (bits & HIT_POS_EDGE)
3565 for (i = 0; i < 4; i++)
3566 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3567 cSY + oby + 31 * (i / 2)))
3571 if (bits & HIT_POS_BETWEEN)
3573 for (i = 0; i < 4; i++)
3574 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3575 cSY + 4 + oby + 22 * (i / 2)))
3582 static void DeletePacMan(int px, int py)
3588 if (game_mm.num_pacman <= 1)
3590 game_mm.num_pacman = 0;
3594 for (i = 0; i < game_mm.num_pacman; i++)
3595 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3598 game_mm.num_pacman--;
3600 for (j = i; j < game_mm.num_pacman; j++)
3602 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3603 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3604 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3608 static void GameActions_MM_Ext(void)
3615 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3618 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3620 element = Tile[x][y];
3622 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3623 StartMoving_MM(x, y);
3624 else if (IS_MOVING(x, y))
3625 ContinueMoving_MM(x, y);
3626 else if (IS_EXPLODING(element))
3627 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3628 else if (element == EL_EXIT_OPENING)
3630 else if (element == EL_GRAY_BALL_OPENING)
3632 else if (IS_ENVELOPE_OPENING(element))
3634 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3636 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3638 else if (IS_MIRROR(element) ||
3639 IS_MIRROR_FIXED(element) ||
3640 element == EL_PRISM)
3641 DrawFieldTwinkle(x, y);
3642 else if (element == EL_GRAY_BALL_ACTIVE ||
3643 element == EL_BOMB_ACTIVE ||
3644 element == EL_MINE_ACTIVE)
3645 DrawFieldAnimated_MM(x, y);
3646 else if (!IS_BLOCKED(x, y))
3647 DrawFieldAnimatedIfNeeded_MM(x, y);
3650 AutoRotateMirrors();
3653 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3655 // redraw after Explode_MM() ...
3657 DrawLaser(0, DL_LASER_ENABLED);
3658 laser.redraw = FALSE;
3663 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3667 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3669 DrawLaser(0, DL_LASER_DISABLED);
3674 // skip all following game actions if game is over
3675 if (game_mm.game_over)
3678 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3682 GameOver_MM(GAME_OVER_NO_ENERGY);
3687 if (FrameReached(&energy_delay))
3689 if (game_mm.energy_left > 0)
3690 game_mm.energy_left--;
3692 // when out of energy, wait another frame to play "out of time" sound
3695 element = laser.dest_element;
3698 if (element != Tile[ELX][ELY])
3700 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3701 element, Tile[ELX][ELY]);
3705 if (!laser.overloaded && laser.overload_value == 0 &&
3706 element != EL_BOMB &&
3707 element != EL_BOMB_ACTIVE &&
3708 element != EL_MINE &&
3709 element != EL_MINE_ACTIVE &&
3710 element != EL_GRAY_BALL &&
3711 element != EL_GRAY_BALL_ACTIVE &&
3712 element != EL_BLOCK_STONE &&
3713 element != EL_BLOCK_WOOD &&
3714 element != EL_FUSE_ON &&
3715 element != EL_FUEL_FULL &&
3716 !IS_WALL_ICE(element) &&
3717 !IS_WALL_AMOEBA(element))
3720 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3722 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3723 (!laser.overloaded && laser.overload_value > 0)) &&
3724 FrameReached(&overload_delay))
3726 if (laser.overloaded)
3727 laser.overload_value++;
3729 laser.overload_value--;
3731 if (game_mm.cheat_no_overload)
3733 laser.overloaded = FALSE;
3734 laser.overload_value = 0;
3737 game_mm.laser_overload_value = laser.overload_value;
3739 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3741 SetLaserColor(0xFF);
3743 DrawLaser(0, DL_LASER_ENABLED);
3746 if (!laser.overloaded)
3747 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3748 else if (setup.sound_loops)
3749 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3751 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3753 if (laser.overload_value == MAX_LASER_OVERLOAD)
3755 UpdateAndDisplayGameControlValues();
3759 GameOver_MM(GAME_OVER_OVERLOADED);
3770 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3772 if (game_mm.cheat_no_explosion)
3777 laser.dest_element = EL_EXPLODING_OPAQUE;
3782 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3784 laser.fuse_off = TRUE;
3788 DrawLaser(0, DL_LASER_DISABLED);
3789 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3792 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3794 if (!Store2[ELX][ELY]) // check if content element not yet determined
3796 int last_anim_random_frame = gfx.anim_random_frame;
3799 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3800 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3802 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3803 native_mm_level.ball_choice_mode, 0,
3804 game_mm.ball_choice_pos);
3806 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3807 gfx.anim_random_frame = last_anim_random_frame;
3809 game_mm.ball_choice_pos++;
3811 int new_element = native_mm_level.ball_content[element_pos];
3812 int new_element_base = map_wall_to_base_element(new_element);
3814 if (IS_WALL(new_element_base))
3816 // always use completely filled wall element
3817 new_element = new_element_base | 0x000f;
3819 else if (native_mm_level.rotate_ball_content &&
3820 get_num_elements(new_element) > 1)
3822 // randomly rotate newly created game element
3823 new_element = get_rotated_element(new_element, RND(16));
3826 Store[ELX][ELY] = new_element;
3827 Store2[ELX][ELY] = TRUE;
3830 if (native_mm_level.explode_ball)
3833 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3835 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3840 if (IS_WALL_ICE(element) && CT > 50)
3842 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3844 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3845 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3846 Store2[ELX][ELY] = laser.wall_mask;
3848 laser.dest_element = Tile[ELX][ELY];
3853 if (IS_WALL_AMOEBA(element) && CT > 60)
3856 int element2 = Tile[ELX][ELY];
3858 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3861 for (i = laser.num_damages - 1; i >= 0; i--)
3862 if (laser.damage[i].is_mirror)
3865 r = laser.num_edges;
3866 d = laser.num_damages;
3873 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3876 DrawLaser(0, DL_LASER_ENABLED);
3879 x = laser.damage[k1].x;
3880 y = laser.damage[k1].y;
3885 for (i = 0; i < 4; i++)
3887 if (laser.wall_mask & (1 << i))
3889 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3890 cSY + ELY * TILEY + 31 * (i / 2)))
3893 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3894 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3901 for (i = 0; i < 4; i++)
3903 if (laser.wall_mask & (1 << i))
3905 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3906 cSY + ELY * TILEY + 31 * (i / 2)))
3913 if (laser.num_beamers > 0 ||
3914 k1 < 1 || k2 < 4 || k3 < 4 ||
3915 CheckLaserPixel(cSX + ELX * TILEX + 14,
3916 cSY + ELY * TILEY + 14))
3918 laser.num_edges = r;
3919 laser.num_damages = d;
3921 DrawLaser(0, DL_LASER_DISABLED);
3924 Tile[ELX][ELY] = element | laser.wall_mask;
3926 int x = ELX, y = ELY;
3927 int wall_mask = laser.wall_mask;
3930 DrawLaser(0, DL_LASER_ENABLED);
3932 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3934 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3935 Store[x][y] = EL_WALL_AMOEBA_BASE;
3936 Store2[x][y] = wall_mask;
3941 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3942 laser.stops_inside_element && CT > native_mm_level.time_block)
3947 if (ABS(XS) > ABS(YS))
3954 for (i = 0; i < 4; i++)
3961 x = ELX + Step[k * 4].x;
3962 y = ELY + Step[k * 4].y;
3964 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3967 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3975 laser.overloaded = (element == EL_BLOCK_STONE);
3980 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3983 Tile[x][y] = element;
3985 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3988 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3990 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3991 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3999 if (element == EL_FUEL_FULL && CT > 10)
4001 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
4002 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
4004 for (i = start; i <= num_init_game_frames; i++)
4006 if (i == num_init_game_frames)
4007 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4008 else if (setup.sound_loops)
4009 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4011 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4013 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
4015 UpdateAndDisplayGameControlValues();
4020 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
4022 DrawField_MM(ELX, ELY);
4024 DrawLaser(0, DL_LASER_ENABLED);
4030 void GameActions_MM(struct MouseActionInfo action)
4032 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
4033 boolean button_released = (action.button == MB_RELEASED);
4035 GameActions_MM_Ext();
4037 CheckSingleStepMode_MM(element_clicked, button_released);
4040 static void MovePacMen(void)
4042 int mx, my, ox, oy, nx, ny;
4046 if (++pacman_nr >= game_mm.num_pacman)
4049 game_mm.pacman[pacman_nr].dir--;
4051 for (l = 1; l < 5; l++)
4053 game_mm.pacman[pacman_nr].dir++;
4055 if (game_mm.pacman[pacman_nr].dir > 4)
4056 game_mm.pacman[pacman_nr].dir = 1;
4058 if (game_mm.pacman[pacman_nr].dir % 2)
4061 my = game_mm.pacman[pacman_nr].dir - 2;
4066 mx = 3 - game_mm.pacman[pacman_nr].dir;
4069 ox = game_mm.pacman[pacman_nr].x;
4070 oy = game_mm.pacman[pacman_nr].y;
4073 element = Tile[nx][ny];
4075 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
4078 if (!IS_EATABLE4PACMAN(element))
4081 if (ObjHit(nx, ny, HIT_POS_CENTER))
4084 Tile[ox][oy] = EL_EMPTY;
4086 EL_PACMAN_RIGHT - 1 +
4087 (game_mm.pacman[pacman_nr].dir - 1 +
4088 (game_mm.pacman[pacman_nr].dir % 2) * 2);
4090 game_mm.pacman[pacman_nr].x = nx;
4091 game_mm.pacman[pacman_nr].y = ny;
4093 DrawGraphic_MM(ox, oy, IMG_EMPTY);
4095 if (element != EL_EMPTY)
4097 int graphic = el2gfx(Tile[nx][ny]);
4102 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
4105 ox = cSX + ox * TILEX;
4106 oy = cSY + oy * TILEY;
4108 for (i = 1; i < 33; i += 2)
4109 BlitBitmap(bitmap, window,
4110 src_x, src_y, TILEX, TILEY,
4111 ox + i * mx, oy + i * my);
4112 Ct = Ct + FrameCounter - CT;
4115 DrawField_MM(nx, ny);
4118 if (!laser.fuse_off)
4120 DrawLaser(0, DL_LASER_ENABLED);
4122 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
4124 AddDamagedField(nx, ny);
4126 laser.damage[laser.num_damages - 1].edge = 0;
4130 if (element == EL_BOMB)
4131 DeletePacMan(nx, ny);
4133 if (IS_WALL_AMOEBA(element) &&
4134 (LX + 2 * XS) / TILEX == nx &&
4135 (LY + 2 * YS) / TILEY == ny)
4145 static void InitMovingField_MM(int x, int y, int direction)
4147 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4148 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4150 MovDir[x][y] = direction;
4151 MovDir[newx][newy] = direction;
4153 if (Tile[newx][newy] == EL_EMPTY)
4154 Tile[newx][newy] = EL_BLOCKED;
4157 static int MovingOrBlocked2Element_MM(int x, int y)
4159 int element = Tile[x][y];
4161 if (element == EL_BLOCKED)
4165 Blocked2Moving(x, y, &oldx, &oldy);
4167 return Tile[oldx][oldy];
4173 static void RemoveMovingField_MM(int x, int y)
4175 int oldx = x, oldy = y, newx = x, newy = y;
4177 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4180 if (IS_MOVING(x, y))
4182 Moving2Blocked(x, y, &newx, &newy);
4183 if (Tile[newx][newy] != EL_BLOCKED)
4186 else if (Tile[x][y] == EL_BLOCKED)
4188 Blocked2Moving(x, y, &oldx, &oldy);
4189 if (!IS_MOVING(oldx, oldy))
4193 Tile[oldx][oldy] = EL_EMPTY;
4194 Tile[newx][newy] = EL_EMPTY;
4195 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4196 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4198 DrawLevelField_MM(oldx, oldy);
4199 DrawLevelField_MM(newx, newy);
4202 static void RaiseScore_MM(int value)
4204 game_mm.score += value;
4207 void RaiseScoreElement_MM(int element)
4212 case EL_PACMAN_RIGHT:
4214 case EL_PACMAN_LEFT:
4215 case EL_PACMAN_DOWN:
4216 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4220 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4225 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4229 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4238 // ----------------------------------------------------------------------------
4239 // Mirror Magic game engine snapshot handling functions
4240 // ----------------------------------------------------------------------------
4242 void SaveEngineSnapshotValues_MM(void)
4246 engine_snapshot_mm.game_mm = game_mm;
4247 engine_snapshot_mm.laser = laser;
4249 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4251 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4253 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4254 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4255 engine_snapshot_mm.Box[x][y] = Box[x][y];
4256 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4260 engine_snapshot_mm.LX = LX;
4261 engine_snapshot_mm.LY = LY;
4262 engine_snapshot_mm.XS = XS;
4263 engine_snapshot_mm.YS = YS;
4264 engine_snapshot_mm.ELX = ELX;
4265 engine_snapshot_mm.ELY = ELY;
4266 engine_snapshot_mm.CT = CT;
4267 engine_snapshot_mm.Ct = Ct;
4269 engine_snapshot_mm.last_LX = last_LX;
4270 engine_snapshot_mm.last_LY = last_LY;
4271 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4272 engine_snapshot_mm.hold_x = hold_x;
4273 engine_snapshot_mm.hold_y = hold_y;
4274 engine_snapshot_mm.pacman_nr = pacman_nr;
4276 engine_snapshot_mm.rotate_delay = rotate_delay;
4277 engine_snapshot_mm.pacman_delay = pacman_delay;
4278 engine_snapshot_mm.energy_delay = energy_delay;
4279 engine_snapshot_mm.overload_delay = overload_delay;
4282 void LoadEngineSnapshotValues_MM(void)
4286 // stored engine snapshot buffers already restored at this point
4288 game_mm = engine_snapshot_mm.game_mm;
4289 laser = engine_snapshot_mm.laser;
4291 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4293 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4295 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4296 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4297 Box[x][y] = engine_snapshot_mm.Box[x][y];
4298 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4302 LX = engine_snapshot_mm.LX;
4303 LY = engine_snapshot_mm.LY;
4304 XS = engine_snapshot_mm.XS;
4305 YS = engine_snapshot_mm.YS;
4306 ELX = engine_snapshot_mm.ELX;
4307 ELY = engine_snapshot_mm.ELY;
4308 CT = engine_snapshot_mm.CT;
4309 Ct = engine_snapshot_mm.Ct;
4311 last_LX = engine_snapshot_mm.last_LX;
4312 last_LY = engine_snapshot_mm.last_LY;
4313 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4314 hold_x = engine_snapshot_mm.hold_x;
4315 hold_y = engine_snapshot_mm.hold_y;
4316 pacman_nr = engine_snapshot_mm.pacman_nr;
4318 rotate_delay = engine_snapshot_mm.rotate_delay;
4319 pacman_delay = engine_snapshot_mm.pacman_delay;
4320 energy_delay = engine_snapshot_mm.energy_delay;
4321 overload_delay = engine_snapshot_mm.overload_delay;
4323 RedrawPlayfield_MM();
4326 static int getAngleFromTouchDelta(int dx, int dy, int base)
4328 double pi = 3.141592653;
4329 double rad = atan2((double)-dy, (double)dx);
4330 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4331 double deg = rad2 * 180.0 / pi;
4333 return (int)(deg * base / 360.0 + 0.5) % base;
4336 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4338 // calculate start (source) position to be at the middle of the tile
4339 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4340 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4341 int dx = dst_mx - src_mx;
4342 int dy = dst_my - src_my;
4351 if (!IN_LEV_FIELD(x, y))
4354 element = Tile[x][y];
4356 if (!IS_MCDUFFIN(element) &&
4357 !IS_MIRROR(element) &&
4358 !IS_BEAMER(element) &&
4359 !IS_POLAR(element) &&
4360 !IS_POLAR_CROSS(element) &&
4361 !IS_DF_MIRROR(element))
4364 angle_old = get_element_angle(element);
4366 if (IS_MCDUFFIN(element))
4368 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4369 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4370 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4371 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4374 else if (IS_MIRROR(element) ||
4375 IS_DF_MIRROR(element))
4377 for (i = 0; i < laser.num_damages; i++)
4379 if (laser.damage[i].x == x &&
4380 laser.damage[i].y == y &&
4381 ObjHit(x, y, HIT_POS_CENTER))
4383 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4384 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4391 if (angle_new == -1)
4393 if (IS_MIRROR(element) ||
4394 IS_DF_MIRROR(element) ||
4398 if (IS_POLAR_CROSS(element))
4401 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4404 button = (angle_new == angle_old ? 0 :
4405 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4406 MB_LEFTBUTTON : MB_RIGHTBUTTON);