1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
101 static void AddLaserEdge(int, int);
102 static void ScanLaser(void);
103 static void DrawLaser(int, int);
104 static boolean HitElement(int, int);
105 static boolean HitOnlyAnEdge(int);
106 static boolean HitPolarizer(int, int);
107 static boolean HitBlock(int, int);
108 static boolean HitLaserSource(int, int);
109 static boolean HitLaserDestination(int, int);
110 static boolean HitReflectingWalls(int, int);
111 static boolean HitAbsorbingWalls(int, int);
112 static void RotateMirror(int, int, int);
113 static boolean ObjHit(int, int, int);
114 static void DeletePacMan(int, int);
115 static void MovePacMen(void);
117 // bitmap for laser beam detection
118 static Bitmap *laser_bitmap = NULL;
120 // variables for laser control
121 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
122 static int hold_x = -1, hold_y = -1;
124 // variables for pacman control
125 static int pacman_nr = -1;
127 // various game engine delay counters
128 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
129 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
130 static DelayCounter energy_delay = { ENERGY_DELAY };
131 static DelayCounter overload_delay = { 0 };
133 // element mask positions for scanning pixels of MM elements
134 #define MM_MASK_MCDUFFIN_RIGHT 0
135 #define MM_MASK_MCDUFFIN_UP 1
136 #define MM_MASK_MCDUFFIN_LEFT 2
137 #define MM_MASK_MCDUFFIN_DOWN 3
138 #define MM_MASK_GRID_1 4
139 #define MM_MASK_GRID_2 5
140 #define MM_MASK_GRID_3 6
141 #define MM_MASK_GRID_4 7
142 #define MM_MASK_SLOPE_1 8
143 #define MM_MASK_SLOPE_2 9
144 #define MM_MASK_SLOPE_3 10
145 #define MM_MASK_SLOPE_4 11
146 #define MM_MASK_RECTANGLE 12
147 #define MM_MASK_CIRCLE 13
149 #define NUM_MM_MASKS 14
151 // element masks for scanning pixels of MM elements
152 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
408 static int get_element_angle(int element)
410 int element_phase = get_element_phase(element);
412 if (IS_MIRROR_FIXED(element) ||
413 IS_MCDUFFIN(element) ||
415 IS_RECEIVER(element))
416 return 4 * element_phase;
417 else if (IS_DF_SLOPE(element))
418 return 4 + (element_phase % 2) * 8;
420 return element_phase;
423 static int get_opposite_angle(int angle)
425 int opposite_angle = angle + ANG_RAY_180;
427 // make sure "opposite_angle" is in valid interval [0, 15]
428 return (opposite_angle + 16) % 16;
431 static int get_mirrored_angle(int laser_angle, int mirror_angle)
433 int reflected_angle = 16 - laser_angle + mirror_angle;
435 // make sure "reflected_angle" is in valid interval [0, 15]
436 return (reflected_angle + 16) % 16;
439 static void DrawLaserLines(struct XY *points, int num_points, int mode)
441 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
442 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
444 DrawLines(drawto_mm, points, num_points, pixel_drawto);
448 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
453 static boolean CheckLaserPixel(int x, int y)
459 pixel = ReadPixel(laser_bitmap, x, y);
463 return (pixel == WHITE_PIXEL);
466 static void CheckExitMM(void)
468 int exit_element = EL_EMPTY;
472 static int xy[4][2] =
480 for (y = 0; y < lev_fieldy; y++)
482 for (x = 0; x < lev_fieldx; x++)
484 if (Tile[x][y] == EL_EXIT_CLOSED)
486 // initiate opening animation of exit door
487 Tile[x][y] = EL_EXIT_OPENING;
489 exit_element = EL_EXIT_OPEN;
493 else if (IS_RECEIVER(Tile[x][y]))
495 // remove field that blocks receiver
496 int phase = Tile[x][y] - EL_RECEIVER_START;
497 int blocking_x, blocking_y;
499 blocking_x = x + xy[phase][0];
500 blocking_y = y + xy[phase][1];
502 if (IN_LEV_FIELD(blocking_x, blocking_y))
504 Tile[blocking_x][blocking_y] = EL_EMPTY;
506 DrawField_MM(blocking_x, blocking_y);
509 exit_element = EL_RECEIVER;
516 if (exit_element != EL_EMPTY)
517 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
520 static void SetLaserColor(int brightness)
522 int color_min = 0x00;
523 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
524 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
525 int color_down = color_max - color_up;
528 GetPixelFromRGB(window,
529 (game_mm.laser_red ? color_max : color_up),
530 (game_mm.laser_green ? color_down : color_min),
531 (game_mm.laser_blue ? color_down : color_min));
534 static void InitMovDir_MM(int x, int y)
536 int element = Tile[x][y];
537 static int direction[3][4] =
539 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
540 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
541 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
546 case EL_PACMAN_RIGHT:
550 Tile[x][y] = EL_PACMAN;
551 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
559 static void InitField(int x, int y, boolean init_game)
561 int element = Tile[x][y];
566 Tile[x][y] = EL_EMPTY;
571 if (init_game && native_mm_level.auto_count_kettles)
572 game_mm.kettles_still_needed++;
575 case EL_LIGHTBULB_OFF:
576 game_mm.lights_still_needed++;
580 if (IS_MIRROR(element) ||
581 IS_BEAMER_OLD(element) ||
582 IS_BEAMER(element) ||
584 IS_POLAR_CROSS(element) ||
585 IS_DF_MIRROR(element) ||
586 IS_DF_MIRROR_AUTO(element) ||
587 IS_GRID_STEEL_AUTO(element) ||
588 IS_GRID_WOOD_AUTO(element) ||
589 IS_FIBRE_OPTIC(element))
591 if (IS_BEAMER_OLD(element))
593 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
594 element = Tile[x][y];
597 if (!IS_FIBRE_OPTIC(element))
599 static int steps_grid_auto = 0;
601 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
602 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
604 if (IS_GRID_STEEL_AUTO(element) ||
605 IS_GRID_WOOD_AUTO(element))
606 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
608 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
610 game_mm.cycle[game_mm.num_cycle].x = x;
611 game_mm.cycle[game_mm.num_cycle].y = y;
615 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
617 int beamer_nr = BEAMER_NR(element);
618 int nr = laser.beamer[beamer_nr][0].num;
620 laser.beamer[beamer_nr][nr].x = x;
621 laser.beamer[beamer_nr][nr].y = y;
622 laser.beamer[beamer_nr][nr].num = 1;
625 else if (IS_PACMAN(element))
629 else if (IS_MCDUFFIN(element) || IS_LASER(element))
633 laser.start_edge.x = x;
634 laser.start_edge.y = y;
635 laser.start_angle = get_element_angle(element);
638 if (IS_MCDUFFIN(element))
640 game_mm.laser_red = native_mm_level.mm_laser_red;
641 game_mm.laser_green = native_mm_level.mm_laser_green;
642 game_mm.laser_blue = native_mm_level.mm_laser_blue;
646 game_mm.laser_red = native_mm_level.df_laser_red;
647 game_mm.laser_green = native_mm_level.df_laser_green;
648 game_mm.laser_blue = native_mm_level.df_laser_blue;
651 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
658 static void InitCycleElements_RotateSingleStep(void)
662 if (game_mm.num_cycle == 0) // no elements to cycle
665 for (i = 0; i < game_mm.num_cycle; i++)
667 int x = game_mm.cycle[i].x;
668 int y = game_mm.cycle[i].y;
669 int step = SIGN(game_mm.cycle[i].steps);
670 int last_element = Tile[x][y];
671 int next_element = get_rotated_element(last_element, step);
673 if (!game_mm.cycle[i].steps)
676 Tile[x][y] = next_element;
678 game_mm.cycle[i].steps -= step;
682 static void InitLaser(void)
684 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
685 int step = (IS_LASER(start_element) ? 4 : 0);
687 LX = laser.start_edge.x * TILEX;
688 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
691 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
693 LY = laser.start_edge.y * TILEY;
694 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
695 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
699 XS = 2 * Step[laser.start_angle].x;
700 YS = 2 * Step[laser.start_angle].y;
702 laser.current_angle = laser.start_angle;
704 laser.num_damages = 0;
706 laser.num_beamers = 0;
707 laser.beamer_edge[0] = 0;
709 laser.dest_element = EL_EMPTY;
712 AddLaserEdge(LX, LY); // set laser starting edge
717 void InitGameEngine_MM(void)
723 // initialize laser bitmap to current playfield (screen) size
724 ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
725 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
729 // set global game control values
730 game_mm.num_cycle = 0;
731 game_mm.num_pacman = 0;
734 game_mm.energy_left = 0; // later set to "native_mm_level.time"
735 game_mm.kettles_still_needed =
736 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
737 game_mm.lights_still_needed = 0;
738 game_mm.num_keys = 0;
739 game_mm.ball_choice_pos = 0;
741 game_mm.laser_red = FALSE;
742 game_mm.laser_green = FALSE;
743 game_mm.laser_blue = TRUE;
744 game_mm.has_mcduffin = TRUE;
746 game_mm.level_solved = FALSE;
747 game_mm.game_over = FALSE;
748 game_mm.game_over_cause = 0;
749 game_mm.game_over_message = NULL;
751 game_mm.laser_overload_value = 0;
752 game_mm.laser_enabled = FALSE;
754 // set global laser control values (must be set before "InitLaser()")
755 laser.start_edge.x = 0;
756 laser.start_edge.y = 0;
757 laser.start_angle = 0;
759 for (i = 0; i < MAX_NUM_BEAMERS; i++)
760 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
762 laser.overloaded = FALSE;
763 laser.overload_value = 0;
764 laser.fuse_off = FALSE;
765 laser.fuse_x = laser.fuse_y = -1;
767 laser.dest_element = EL_EMPTY;
768 laser.dest_element_last = EL_EMPTY;
769 laser.dest_element_last_x = -1;
770 laser.dest_element_last_y = -1;
784 rotate_delay.count = 0;
785 pacman_delay.count = 0;
786 energy_delay.count = 0;
787 overload_delay.count = 0;
789 ClickElement(-1, -1, -1);
791 for (x = 0; x < lev_fieldx; x++)
793 for (y = 0; y < lev_fieldy; y++)
795 Tile[x][y] = Ur[x][y];
796 Hit[x][y] = Box[x][y] = 0;
798 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
799 Store[x][y] = Store2[x][y] = 0;
802 InitField(x, y, TRUE);
809 void InitGameActions_MM(void)
811 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
812 int cycle_steps_done = 0;
817 for (i = 0; i <= num_init_game_frames; i++)
819 if (i == num_init_game_frames)
820 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
821 else if (setup.sound_loops)
822 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
824 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
826 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
828 UpdateAndDisplayGameControlValues();
830 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
832 InitCycleElements_RotateSingleStep();
837 AdvanceFrameCounter();
840 if (PendingEscapeKeyEvent())
844 if (setup.quick_doors)
854 if (setup.quick_doors)
860 if (game_mm.kettles_still_needed == 0)
863 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
864 SetTileCursorActive(TRUE);
866 // restart all delay counters after initially cycling game elements
867 ResetFrameCounter(&rotate_delay);
868 ResetFrameCounter(&pacman_delay);
869 ResetFrameCounter(&energy_delay);
870 ResetFrameCounter(&overload_delay);
873 static void FadeOutLaser(void)
877 for (i = 15; i >= 0; i--)
879 SetLaserColor(0x11 * i);
881 DrawLaser(0, DL_LASER_ENABLED);
884 Delay_WithScreenUpdates(50);
887 DrawLaser(0, DL_LASER_DISABLED);
889 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
892 static void GameOver_MM(int game_over_cause)
894 game_mm.game_over = TRUE;
895 game_mm.game_over_cause = game_over_cause;
896 game_mm.game_over_message = (game_mm.has_mcduffin ?
897 (game_over_cause == GAME_OVER_BOMB ?
898 "Bomb killed Mc Duffin!" :
899 game_over_cause == GAME_OVER_NO_ENERGY ?
900 "Out of magic energy!" :
901 game_over_cause == GAME_OVER_OVERLOADED ?
902 "Magic spell hit Mc Duffin!" :
904 (game_over_cause == GAME_OVER_BOMB ?
905 "Bomb destroyed laser cannon!" :
906 game_over_cause == GAME_OVER_NO_ENERGY ?
907 "Out of laser energy!" :
908 game_over_cause == GAME_OVER_OVERLOADED ?
909 "Laser beam hit laser cannon!" :
912 SetTileCursorActive(FALSE);
915 static void AddLaserEdge(int lx, int ly)
917 int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
918 int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
920 // check if laser is still inside visible playfield area (or inside level)
921 if (cSX + lx < REAL_SX || cSX + lx >= REAL_SX + full_sxsize ||
922 cSY + ly < REAL_SY || cSY + ly >= REAL_SY + full_sysize)
924 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
929 laser.edge[laser.num_edges].x = cSX2 + lx;
930 laser.edge[laser.num_edges].y = cSY2 + ly;
936 static void AddDamagedField(int ex, int ey)
938 // prevent adding the same field position again
939 if (laser.num_damages > 0 &&
940 laser.damage[laser.num_damages - 1].x == ex &&
941 laser.damage[laser.num_damages - 1].y == ey &&
942 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
945 laser.damage[laser.num_damages].is_mirror = FALSE;
946 laser.damage[laser.num_damages].angle = laser.current_angle;
947 laser.damage[laser.num_damages].edge = laser.num_edges;
948 laser.damage[laser.num_damages].x = ex;
949 laser.damage[laser.num_damages].y = ey;
953 static boolean StepBehind(void)
959 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
960 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
962 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
968 static int getMaskFromElement(int element)
970 if (IS_MCDUFFIN(element))
971 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
972 else if (IS_GRID(element))
973 return MM_MASK_GRID_1 + get_element_phase(element);
974 else if (IS_DF_GRID(element))
975 return MM_MASK_RECTANGLE;
976 else if (IS_DF_SLOPE(element))
977 return MM_MASK_SLOPE_1 + get_element_phase(element);
978 else if (IS_RECTANGLE(element))
979 return MM_MASK_RECTANGLE;
981 return MM_MASK_CIRCLE;
984 static int getPixelFromMask(int pos, int dx, int dy)
986 return (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
989 static int getLevelFromLaserX(int x)
991 return x / TILEX - (x < 0 ? 1 : 0); // correct negative values
994 static int getLevelFromLaserY(int y)
996 return y / TILEY - (y < 0 ? 1 : 0); // correct negative values
999 static int ScanPixel(void)
1004 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
1005 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
1008 // follow laser beam until it hits something (at least the screen border)
1009 while (hit_mask == HIT_MASK_NO_HIT)
1015 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
1016 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
1018 Debug("game:mm:ScanPixel", "touched screen border!");
1020 return HIT_MASK_ALL;
1024 // check if laser scan has crossed element boundaries (not just mini tiles)
1025 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1026 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1028 if (cross_x && cross_y)
1030 int elx1 = (LX - XS) / TILEX;
1031 int ely1 = (LY + YS) / TILEY;
1032 int elx2 = (LX + XS) / TILEX;
1033 int ely2 = (LY - YS) / TILEY;
1035 // add element corners left and right from the laser beam to damage list
1037 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
1038 AddDamagedField(elx1, ely1);
1040 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
1041 AddDamagedField(elx2, ely2);
1044 for (i = 0; i < 4; i++)
1046 int px = LX + (i % 2) * 2;
1047 int py = LY + (i / 2) * 2;
1048 int dx = px % TILEX;
1049 int dy = py % TILEY;
1050 int lx = getLevelFromLaserX(px);
1051 int ly = getLevelFromLaserY(py);
1054 if (IN_LEV_FIELD(lx, ly))
1056 int element = Tile[lx][ly];
1058 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
1062 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
1064 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1066 pixel = ((element & (1 << pos)) ? 1 : 0);
1070 int pos = getMaskFromElement(element);
1072 pixel = getPixelFromMask(pos, dx, dy);
1077 // check if laser is still inside visible playfield area
1078 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
1079 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
1082 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
1083 hit_mask |= (1 << i);
1086 if (hit_mask == HIT_MASK_NO_HIT)
1088 // hit nothing -- go on with another step
1097 static void DeactivateLaserTargetElement(void)
1099 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1100 laser.dest_element_last == EL_MINE_ACTIVE ||
1101 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1102 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1104 int x = laser.dest_element_last_x;
1105 int y = laser.dest_element_last_y;
1106 int element = laser.dest_element_last;
1108 if (Tile[x][y] == element)
1109 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1110 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1112 if (Tile[x][y] == EL_GRAY_BALL)
1115 laser.dest_element_last = EL_EMPTY;
1116 laser.dest_element_last_x = -1;
1117 laser.dest_element_last_y = -1;
1121 static void ScanLaser(void)
1123 int element = EL_EMPTY;
1124 int last_element = EL_EMPTY;
1125 int end = 0, rf = laser.num_edges;
1127 // do not scan laser again after the game was lost for whatever reason
1128 if (game_mm.game_over)
1131 // do not scan laser if fuse is off
1135 DeactivateLaserTargetElement();
1137 laser.overloaded = FALSE;
1138 laser.stops_inside_element = FALSE;
1140 DrawLaser(0, DL_LASER_ENABLED);
1143 Debug("game:mm:ScanLaser",
1144 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1152 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1155 laser.overloaded = TRUE;
1160 hit_mask = ScanPixel();
1163 Debug("game:mm:ScanLaser",
1164 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1168 // check if laser scan has hit two diagonally adjacent element corners
1169 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1170 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1172 // check if laser scan has crossed element boundaries (not just mini tiles)
1173 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1174 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1176 if (cross_x || cross_y)
1178 // hit something at next tile -- check out what it was
1179 ELX = getLevelFromLaserX(LX + XS);
1180 ELY = getLevelFromLaserY(LY + YS);
1184 // hit something at same tile -- check out what it was
1185 ELX = getLevelFromLaserX(LX);
1186 ELY = getLevelFromLaserY(LY);
1190 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1191 hit_mask, LX, LY, ELX, ELY);
1194 if (!IN_LEV_FIELD(ELX, ELY))
1196 // laser next step position
1197 int x = cSX + LX + XS;
1198 int y = cSY + LY + YS;
1200 // check if next step of laser is still inside visible playfield area
1201 if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
1202 y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
1204 // go on with another step
1212 laser.dest_element = element;
1217 // handle special case of laser hitting two diagonally adjacent elements
1218 // (with or without a third corner element behind these two elements)
1219 if ((diag_1 || diag_2) && cross_x && cross_y)
1221 // compare the two diagonally adjacent elements
1223 int yoffset = 2 * (diag_1 ? -1 : +1);
1224 int elx1 = (LX - xoffset) / TILEX;
1225 int ely1 = (LY + yoffset) / TILEY;
1226 int elx2 = (LX + xoffset) / TILEX;
1227 int ely2 = (LY - yoffset) / TILEY;
1228 int e1 = Tile[elx1][ely1];
1229 int e2 = Tile[elx2][ely2];
1230 boolean use_element_1 = FALSE;
1232 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1234 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1235 use_element_1 = (RND(2) ? TRUE : FALSE);
1236 else if (IS_WALL_ICE(e1))
1237 use_element_1 = TRUE;
1239 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1241 // if both tiles match, we can just select the first one
1242 if (IS_WALL_AMOEBA(e1))
1243 use_element_1 = TRUE;
1245 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1247 // if both tiles match, we can just select the first one
1248 if (IS_ABSORBING_BLOCK(e1))
1249 use_element_1 = TRUE;
1252 ELX = (use_element_1 ? elx1 : elx2);
1253 ELY = (use_element_1 ? ely1 : ely2);
1257 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1258 hit_mask, LX, LY, ELX, ELY);
1261 last_element = element;
1263 element = Tile[ELX][ELY];
1264 laser.dest_element = element;
1267 Debug("game:mm:ScanLaser",
1268 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1271 LX % TILEX, LY % TILEY,
1276 if (!IN_LEV_FIELD(ELX, ELY))
1277 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1281 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1282 if (element == EL_EMPTY &&
1283 IS_GRID_STEEL(last_element) &&
1284 laser.current_angle % 4) // angle is not 90°
1285 element = last_element;
1287 if (element == EL_EMPTY)
1289 if (!HitOnlyAnEdge(hit_mask))
1292 else if (element == EL_FUSE_ON)
1294 if (HitPolarizer(element, hit_mask))
1297 else if (IS_GRID(element) || IS_DF_GRID(element))
1299 if (HitPolarizer(element, hit_mask))
1302 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1303 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1305 if (HitBlock(element, hit_mask))
1312 else if (IS_MCDUFFIN(element))
1314 if (HitLaserSource(element, hit_mask))
1317 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1318 IS_RECEIVER(element))
1320 if (HitLaserDestination(element, hit_mask))
1323 else if (IS_WALL(element))
1325 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1327 if (HitReflectingWalls(element, hit_mask))
1332 if (HitAbsorbingWalls(element, hit_mask))
1338 if (HitElement(element, hit_mask))
1343 DrawLaser(rf - 1, DL_LASER_ENABLED);
1344 rf = laser.num_edges;
1346 if (!IS_DF_WALL_STEEL(element))
1348 // only used for scanning DF steel walls; reset for all other elements
1356 if (laser.dest_element != Tile[ELX][ELY])
1358 Debug("game:mm:ScanLaser",
1359 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1360 laser.dest_element, Tile[ELX][ELY]);
1364 if (!end && !laser.stops_inside_element && !StepBehind())
1367 Debug("game:mm:ScanLaser", "Go one step back");
1373 AddLaserEdge(LX, LY);
1377 DrawLaser(rf - 1, DL_LASER_ENABLED);
1379 Ct = CT = FrameCounter;
1382 if (!IN_LEV_FIELD(ELX, ELY))
1383 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1387 static void ScanLaser_FromLastMirror(void)
1389 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1392 for (i = start_pos; i >= 0; i--)
1393 if (laser.damage[i].is_mirror)
1396 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1398 DrawLaser(start_edge, DL_LASER_DISABLED);
1403 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1409 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1410 start_edge, num_edges, mode);
1415 Warn("DrawLaserExt: start_edge < 0");
1422 Warn("DrawLaserExt: num_edges < 0");
1428 if (mode == DL_LASER_DISABLED)
1430 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1434 // now draw the laser to the backbuffer and (if enabled) to the screen
1435 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1437 redraw_mask |= REDRAW_FIELD;
1439 if (mode == DL_LASER_ENABLED)
1442 // after the laser was deleted, the "damaged" graphics must be restored
1443 if (laser.num_damages)
1445 int damage_start = 0;
1448 // determine the starting edge, from which graphics need to be restored
1451 for (i = 0; i < laser.num_damages; i++)
1453 if (laser.damage[i].edge == start_edge + 1)
1462 // restore graphics from this starting edge to the end of damage list
1463 for (i = damage_start; i < laser.num_damages; i++)
1465 int lx = laser.damage[i].x;
1466 int ly = laser.damage[i].y;
1467 int element = Tile[lx][ly];
1469 if (Hit[lx][ly] == laser.damage[i].edge)
1470 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1473 if (Box[lx][ly] == laser.damage[i].edge)
1476 if (IS_DRAWABLE(element))
1477 DrawField_MM(lx, ly);
1480 elx = laser.damage[damage_start].x;
1481 ely = laser.damage[damage_start].y;
1482 element = Tile[elx][ely];
1485 if (IS_BEAMER(element))
1489 for (i = 0; i < laser.num_beamers; i++)
1490 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1492 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1493 mode, elx, ely, Hit[elx][ely], start_edge);
1494 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1495 get_element_angle(element), laser.damage[damage_start].angle);
1499 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1500 laser.num_beamers > 0 &&
1501 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1503 // element is outgoing beamer
1504 laser.num_damages = damage_start + 1;
1506 if (IS_BEAMER(element))
1507 laser.current_angle = get_element_angle(element);
1511 // element is incoming beamer or other element
1512 laser.num_damages = damage_start;
1513 laser.current_angle = laser.damage[laser.num_damages].angle;
1518 // no damages but McDuffin himself (who needs to be redrawn anyway)
1520 elx = laser.start_edge.x;
1521 ely = laser.start_edge.y;
1522 element = Tile[elx][ely];
1525 laser.num_edges = start_edge + 1;
1526 if (start_edge == 0)
1527 laser.current_angle = laser.start_angle;
1529 LX = laser.edge[start_edge].x - cSX2;
1530 LY = laser.edge[start_edge].y - cSY2;
1531 XS = 2 * Step[laser.current_angle].x;
1532 YS = 2 * Step[laser.current_angle].y;
1535 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1541 if (IS_BEAMER(element) ||
1542 IS_FIBRE_OPTIC(element) ||
1543 IS_PACMAN(element) ||
1544 IS_POLAR(element) ||
1545 IS_POLAR_CROSS(element) ||
1546 element == EL_FUSE_ON)
1551 Debug("game:mm:DrawLaserExt", "element == %d", element);
1554 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1555 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1559 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1560 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1561 (laser.num_beamers == 0 ||
1562 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1564 // element is incoming beamer or other element
1565 step_size = -step_size;
1570 if (IS_BEAMER(element))
1571 Debug("game:mm:DrawLaserExt",
1572 "start_edge == %d, laser.beamer_edge == %d",
1573 start_edge, laser.beamer_edge);
1576 LX += step_size * XS;
1577 LY += step_size * YS;
1579 else if (element != EL_EMPTY)
1588 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1593 void DrawLaser(int start_edge, int mode)
1595 // do not draw laser if fuse is off
1596 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1599 if (mode == DL_LASER_DISABLED)
1600 DeactivateLaserTargetElement();
1602 if (laser.num_edges - start_edge < 0)
1604 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1609 // check if laser is interrupted by beamer element
1610 if (laser.num_beamers > 0 &&
1611 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1613 if (mode == DL_LASER_ENABLED)
1616 int tmp_start_edge = start_edge;
1618 // draw laser segments forward from the start to the last beamer
1619 for (i = 0; i < laser.num_beamers; i++)
1621 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1623 if (tmp_num_edges <= 0)
1627 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1628 i, laser.beamer_edge[i], tmp_start_edge);
1631 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1633 tmp_start_edge = laser.beamer_edge[i];
1636 // draw last segment from last beamer to the end
1637 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1643 int last_num_edges = laser.num_edges;
1644 int num_beamers = laser.num_beamers;
1646 // delete laser segments backward from the end to the first beamer
1647 for (i = num_beamers - 1; i >= 0; i--)
1649 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1651 if (laser.beamer_edge[i] - start_edge <= 0)
1654 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1656 last_num_edges = laser.beamer_edge[i];
1657 laser.num_beamers--;
1661 if (last_num_edges - start_edge <= 0)
1662 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1663 last_num_edges, start_edge);
1666 // special case when rotating first beamer: delete laser edge on beamer
1667 // (but do not start scanning on previous edge to prevent mirror sound)
1668 if (last_num_edges - start_edge == 1 && start_edge > 0)
1669 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1671 // delete first segment from start to the first beamer
1672 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1677 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1680 game_mm.laser_enabled = mode;
1683 void DrawLaser_MM(void)
1685 DrawLaser(0, game_mm.laser_enabled);
1688 static boolean HitElement(int element, int hit_mask)
1690 if (IS_DF_SLOPE(element))
1692 // check if laser scan has crossed element boundaries (not just mini tiles)
1693 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1694 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1696 // check if wall (horizontal or vertical) side of slope was hit
1697 if (hit_mask == HIT_MASK_LEFT ||
1698 hit_mask == HIT_MASK_RIGHT ||
1699 hit_mask == HIT_MASK_TOP ||
1700 hit_mask == HIT_MASK_BOTTOM)
1702 return HitReflectingWalls(element, hit_mask);
1705 // check if an edge was hit while crossing element borders
1706 if (cross_x && cross_y && get_number_of_bits(hit_mask) == 1)
1708 // check both sides of potentially diagonal side of slope
1709 int dx1 = (LX + XS) % TILEX;
1710 int dy1 = (LY + YS) % TILEY;
1711 int dx2 = (LX + XS + 2) % TILEX;
1712 int dy2 = (LY + YS + 2) % TILEY;
1713 int pos = getMaskFromElement(element);
1715 // check if we are entering empty space area after hitting edge
1716 if (!getPixelFromMask(pos, dx1, dy1) &&
1717 !getPixelFromMask(pos, dx2, dy2))
1719 // we already know that we hit an edge, but use this function to go on
1720 if (HitOnlyAnEdge(hit_mask))
1725 int mirrored_angle = get_mirrored_angle(laser.current_angle,
1726 get_element_angle(element));
1727 int opposite_angle = get_opposite_angle(laser.current_angle);
1729 // check if laser is reflected by slope by 180°
1730 if (mirrored_angle == opposite_angle)
1732 AddDamagedField(LX / TILEX, LY / TILEY);
1734 laser.overloaded = TRUE;
1741 if (HitOnlyAnEdge(hit_mask))
1745 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1746 element = MovingOrBlocked2Element_MM(ELX, ELY);
1749 Debug("game:mm:HitElement", "(1): element == %d", element);
1753 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1754 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1757 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1761 AddDamagedField(ELX, ELY);
1763 boolean through_center = ((ELX * TILEX + 14 - LX) * YS ==
1764 (ELY * TILEY + 14 - LY) * XS);
1766 // this is more precise: check if laser would go through the center
1767 if (!IS_DF_SLOPE(element) && !through_center)
1771 // prevent cutting through laser emitter with laser beam
1772 if (IS_LASER(element))
1775 // skip the whole element before continuing the scan
1783 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1785 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1787 /* skipping scan positions to the right and down skips one scan
1788 position too much, because this is only the top left scan position
1789 of totally four scan positions (plus one to the right, one to the
1790 bottom and one to the bottom right) */
1791 /* ... but only roll back scan position if more than one step done */
1801 Debug("game:mm:HitElement", "(2): element == %d", element);
1804 if (LX + 5 * XS < 0 ||
1814 Debug("game:mm:HitElement", "(3): element == %d", element);
1817 if (IS_POLAR(element) &&
1818 ((element - EL_POLAR_START) % 2 ||
1819 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1821 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1823 laser.num_damages--;
1828 if (IS_POLAR_CROSS(element) &&
1829 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1831 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1833 laser.num_damages--;
1838 if (IS_DF_SLOPE(element) && !through_center)
1842 if (hit_mask == HIT_MASK_ALL)
1844 // laser already inside slope -- go back half step
1851 AddLaserEdge(LX, LY);
1853 LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0);
1854 LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0);
1856 else if (!IS_BEAMER(element) &&
1857 !IS_FIBRE_OPTIC(element) &&
1858 !IS_GRID_WOOD(element) &&
1859 element != EL_FUEL_EMPTY)
1862 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1863 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1865 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1868 LX = ELX * TILEX + 14;
1869 LY = ELY * TILEY + 14;
1871 AddLaserEdge(LX, LY);
1874 if (IS_MIRROR(element) ||
1875 IS_MIRROR_FIXED(element) ||
1876 IS_POLAR(element) ||
1877 IS_POLAR_CROSS(element) ||
1878 IS_DF_MIRROR(element) ||
1879 IS_DF_MIRROR_AUTO(element) ||
1880 IS_DF_MIRROR_FIXED(element) ||
1881 IS_DF_SLOPE(element) ||
1882 element == EL_PRISM ||
1883 element == EL_REFRACTOR)
1885 int current_angle = laser.current_angle;
1888 laser.num_damages--;
1890 AddDamagedField(ELX, ELY);
1892 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1895 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1897 if (IS_MIRROR(element) ||
1898 IS_MIRROR_FIXED(element) ||
1899 IS_DF_MIRROR(element) ||
1900 IS_DF_MIRROR_AUTO(element) ||
1901 IS_DF_MIRROR_FIXED(element) ||
1902 IS_DF_SLOPE(element))
1903 laser.current_angle = get_mirrored_angle(laser.current_angle,
1904 get_element_angle(element));
1906 if (element == EL_PRISM || element == EL_REFRACTOR)
1907 laser.current_angle = RND(16);
1909 XS = 2 * Step[laser.current_angle].x;
1910 YS = 2 * Step[laser.current_angle].y;
1914 // start from center position for all game elements but slope
1915 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1920 LX += step_size * XS;
1921 LY += step_size * YS;
1925 // advance laser position until reaching the next tile (slopes)
1926 while (LX / TILEX == ELX && (LX + 2) / TILEX == ELX &&
1927 LY / TILEY == ELY && (LY + 2) / TILEY == ELY)
1934 // draw sparkles on mirror
1935 if ((IS_MIRROR(element) ||
1936 IS_MIRROR_FIXED(element) ||
1937 element == EL_PRISM) &&
1938 current_angle != laser.current_angle)
1940 MovDelay[ELX][ELY] = 11; // start animation
1943 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1944 current_angle != laser.current_angle)
1945 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1948 (get_opposite_angle(laser.current_angle) ==
1949 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1951 if (IS_DF_SLOPE(element))
1953 // handle special cases for slope element
1955 if (IS_45_ANGLE(laser.current_angle))
1959 elx = getLevelFromLaserX(LX);
1960 ely = getLevelFromLaserY(LY);
1962 if (IN_LEV_FIELD(elx, ely))
1964 int element_next = Tile[elx][ely];
1966 // check if slope is followed by slope with opposite orientation
1967 if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2)
1968 laser.overloaded = TRUE;
1971 int nr = element - EL_DF_SLOPE_START;
1972 int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) :
1973 nr == 1 ? (XS > 0 ? TILEX : 0) :
1974 nr == 2 ? (XS > 0 ? TILEX : 0) :
1975 nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0);
1976 int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) :
1977 nr == 1 ? (YS > 0 ? TILEY - 1 : -1) :
1978 nr == 2 ? (YS > 0 ? TILEY : 0) :
1979 nr == 3 ? (YS > 0 ? TILEY : 0) : 0);
1981 int px = ELX * TILEX + dx;
1982 int py = ELY * TILEY + dy;
1987 elx = getLevelFromLaserX(px);
1988 ely = getLevelFromLaserY(py);
1990 if (IN_LEV_FIELD(elx, ely))
1992 int element_side = Tile[elx][ely];
1994 // check if end of slope is blocked by other element
1995 if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side))
1997 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1999 if (element & (1 << pos))
2000 laser.overloaded = TRUE;
2004 int pos = getMaskFromElement(element_side);
2006 if (getPixelFromMask(pos, dx, dy))
2007 laser.overloaded = TRUE;
2013 return (laser.overloaded ? TRUE : FALSE);
2016 if (element == EL_FUEL_FULL)
2018 laser.stops_inside_element = TRUE;
2023 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
2025 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2027 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
2028 element == EL_MINE ? EL_MINE_ACTIVE :
2029 EL_GRAY_BALL_ACTIVE);
2031 GfxFrame[ELX][ELY] = 0; // restart animation
2033 laser.dest_element_last = Tile[ELX][ELY];
2034 laser.dest_element_last_x = ELX;
2035 laser.dest_element_last_y = ELY;
2037 if (element == EL_MINE)
2038 laser.overloaded = TRUE;
2041 if (element == EL_KETTLE ||
2042 element == EL_CELL ||
2043 element == EL_KEY ||
2044 element == EL_LIGHTBALL ||
2045 element == EL_PACMAN ||
2046 IS_PACMAN(element) ||
2047 IS_ENVELOPE(element))
2049 if (!IS_PACMAN(element) &&
2050 !IS_ENVELOPE(element))
2053 if (element == EL_PACMAN)
2056 if (element == EL_KETTLE || element == EL_CELL)
2058 if (game_mm.kettles_still_needed > 0)
2059 game_mm.kettles_still_needed--;
2061 game.snapshot.collected_item = TRUE;
2063 if (game_mm.kettles_still_needed == 0)
2067 DrawLaser(0, DL_LASER_ENABLED);
2070 else if (element == EL_KEY)
2074 else if (IS_PACMAN(element))
2076 DeletePacMan(ELX, ELY);
2078 else if (IS_ENVELOPE(element))
2080 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
2083 RaiseScoreElement_MM(element);
2088 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
2090 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2092 DrawLaser(0, DL_LASER_ENABLED);
2094 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
2096 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
2097 game_mm.lights_still_needed--;
2101 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
2102 game_mm.lights_still_needed++;
2105 DrawField_MM(ELX, ELY);
2106 DrawLaser(0, DL_LASER_ENABLED);
2111 laser.stops_inside_element = TRUE;
2117 Debug("game:mm:HitElement", "(4): element == %d", element);
2120 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
2121 laser.num_beamers < MAX_NUM_BEAMERS &&
2122 laser.beamer[BEAMER_NR(element)][1].num)
2124 int beamer_angle = get_element_angle(element);
2125 int beamer_nr = BEAMER_NR(element);
2129 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
2132 laser.num_damages--;
2134 if (IS_FIBRE_OPTIC(element) ||
2135 laser.current_angle == get_opposite_angle(beamer_angle))
2139 LX = ELX * TILEX + 14;
2140 LY = ELY * TILEY + 14;
2142 AddLaserEdge(LX, LY);
2143 AddDamagedField(ELX, ELY);
2145 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2148 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2150 pos = (ELX == laser.beamer[beamer_nr][0].x &&
2151 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
2152 ELX = laser.beamer[beamer_nr][pos].x;
2153 ELY = laser.beamer[beamer_nr][pos].y;
2154 LX = ELX * TILEX + 14;
2155 LY = ELY * TILEY + 14;
2157 if (IS_BEAMER(element))
2159 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
2160 XS = 2 * Step[laser.current_angle].x;
2161 YS = 2 * Step[laser.current_angle].y;
2164 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
2166 AddLaserEdge(LX, LY);
2167 AddDamagedField(ELX, ELY);
2169 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2172 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2174 if (laser.current_angle == (laser.current_angle >> 1) << 1)
2179 LX += step_size * XS;
2180 LY += step_size * YS;
2182 laser.num_beamers++;
2191 static boolean HitOnlyAnEdge(int hit_mask)
2193 // check if the laser hit only the edge of an element and, if so, go on
2196 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
2200 if ((hit_mask == HIT_MASK_TOPLEFT ||
2201 hit_mask == HIT_MASK_TOPRIGHT ||
2202 hit_mask == HIT_MASK_BOTTOMLEFT ||
2203 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
2204 laser.current_angle % 4) // angle is not 90°
2208 if (hit_mask == HIT_MASK_TOPLEFT)
2213 else if (hit_mask == HIT_MASK_TOPRIGHT)
2218 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
2223 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
2229 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
2235 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
2242 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
2248 static boolean HitPolarizer(int element, int hit_mask)
2250 if (HitOnlyAnEdge(hit_mask))
2253 if (IS_DF_GRID(element))
2255 int grid_angle = get_element_angle(element);
2258 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2259 grid_angle, laser.current_angle);
2262 AddLaserEdge(LX, LY);
2263 AddDamagedField(ELX, ELY);
2266 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2268 if (laser.current_angle == grid_angle ||
2269 laser.current_angle == get_opposite_angle(grid_angle))
2271 // skip the whole element before continuing the scan
2277 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2279 if (LX/TILEX > ELX || LY/TILEY > ELY)
2281 /* skipping scan positions to the right and down skips one scan
2282 position too much, because this is only the top left scan position
2283 of totally four scan positions (plus one to the right, one to the
2284 bottom and one to the bottom right) */
2290 AddLaserEdge(LX, LY);
2296 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2298 LX / TILEX, LY / TILEY,
2299 LX % TILEX, LY % TILEY);
2304 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2306 return HitReflectingWalls(element, hit_mask);
2310 return HitAbsorbingWalls(element, hit_mask);
2313 else if (IS_GRID_STEEL(element))
2315 // may be required if graphics for steel grid redefined
2316 AddDamagedField(ELX, ELY);
2318 return HitReflectingWalls(element, hit_mask);
2320 else // IS_GRID_WOOD
2322 // may be required if graphics for wooden grid redefined
2323 AddDamagedField(ELX, ELY);
2325 return HitAbsorbingWalls(element, hit_mask);
2331 static boolean HitBlock(int element, int hit_mask)
2333 boolean check = FALSE;
2335 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2336 game_mm.num_keys == 0)
2339 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2342 int ex = ELX * TILEX + 14;
2343 int ey = ELY * TILEY + 14;
2347 for (i = 1; i < 32; i++)
2352 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2357 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2358 return HitAbsorbingWalls(element, hit_mask);
2362 AddLaserEdge(LX - XS, LY - YS);
2363 AddDamagedField(ELX, ELY);
2366 Box[ELX][ELY] = laser.num_edges;
2368 return HitReflectingWalls(element, hit_mask);
2371 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2373 int xs = XS / 2, ys = YS / 2;
2375 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2376 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2378 laser.overloaded = (element == EL_GATE_STONE);
2383 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2384 (hit_mask == HIT_MASK_TOP ||
2385 hit_mask == HIT_MASK_LEFT ||
2386 hit_mask == HIT_MASK_RIGHT ||
2387 hit_mask == HIT_MASK_BOTTOM))
2388 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2389 hit_mask == HIT_MASK_BOTTOM),
2390 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2391 hit_mask == HIT_MASK_RIGHT));
2392 AddLaserEdge(LX, LY);
2398 if (element == EL_GATE_STONE && Box[ELX][ELY])
2400 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2412 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2414 int xs = XS / 2, ys = YS / 2;
2416 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2417 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2419 laser.overloaded = (element == EL_BLOCK_STONE);
2424 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2425 (hit_mask == HIT_MASK_TOP ||
2426 hit_mask == HIT_MASK_LEFT ||
2427 hit_mask == HIT_MASK_RIGHT ||
2428 hit_mask == HIT_MASK_BOTTOM))
2429 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2430 hit_mask == HIT_MASK_BOTTOM),
2431 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2432 hit_mask == HIT_MASK_RIGHT));
2433 AddDamagedField(ELX, ELY);
2435 LX = ELX * TILEX + 14;
2436 LY = ELY * TILEY + 14;
2438 AddLaserEdge(LX, LY);
2440 laser.stops_inside_element = TRUE;
2448 static boolean HitLaserSource(int element, int hit_mask)
2450 if (HitOnlyAnEdge(hit_mask))
2453 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2455 laser.overloaded = TRUE;
2460 static boolean HitLaserDestination(int element, int hit_mask)
2462 if (HitOnlyAnEdge(hit_mask))
2465 if (element != EL_EXIT_OPEN &&
2466 !(IS_RECEIVER(element) &&
2467 game_mm.kettles_still_needed == 0 &&
2468 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2470 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2475 if (IS_RECEIVER(element) ||
2476 (IS_22_5_ANGLE(laser.current_angle) &&
2477 (ELX != (LX + 6 * XS) / TILEX ||
2478 ELY != (LY + 6 * YS) / TILEY ||
2487 LX = ELX * TILEX + 14;
2488 LY = ELY * TILEY + 14;
2490 laser.stops_inside_element = TRUE;
2493 AddLaserEdge(LX, LY);
2494 AddDamagedField(ELX, ELY);
2496 if (game_mm.lights_still_needed == 0)
2498 game_mm.level_solved = TRUE;
2500 SetTileCursorActive(FALSE);
2506 static boolean HitReflectingWalls(int element, int hit_mask)
2508 // check if laser hits side of a wall with an angle that is not 90°
2509 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2510 hit_mask == HIT_MASK_LEFT ||
2511 hit_mask == HIT_MASK_RIGHT ||
2512 hit_mask == HIT_MASK_BOTTOM))
2514 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2519 if (!IS_DF_GRID(element))
2520 AddLaserEdge(LX, LY);
2522 // check if laser hits wall with an angle of 45°
2523 if (!IS_22_5_ANGLE(laser.current_angle))
2525 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2528 laser.current_angle = get_mirrored_angle(laser.current_angle,
2531 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2534 laser.current_angle = get_mirrored_angle(laser.current_angle,
2538 AddLaserEdge(LX, LY);
2540 XS = 2 * Step[laser.current_angle].x;
2541 YS = 2 * Step[laser.current_angle].y;
2545 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2547 laser.current_angle = get_mirrored_angle(laser.current_angle,
2552 if (!IS_DF_GRID(element))
2553 AddLaserEdge(LX, LY);
2558 if (!IS_DF_GRID(element))
2559 AddLaserEdge(LX, LY + YS / 2);
2562 if (!IS_DF_GRID(element))
2563 AddLaserEdge(LX, LY);
2566 YS = 2 * Step[laser.current_angle].y;
2570 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2572 laser.current_angle = get_mirrored_angle(laser.current_angle,
2577 if (!IS_DF_GRID(element))
2578 AddLaserEdge(LX, LY);
2583 if (!IS_DF_GRID(element))
2584 AddLaserEdge(LX + XS / 2, LY);
2587 if (!IS_DF_GRID(element))
2588 AddLaserEdge(LX, LY);
2591 XS = 2 * Step[laser.current_angle].x;
2597 // reflection at the edge of reflecting DF style wall
2598 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2600 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2601 hit_mask == HIT_MASK_TOPRIGHT) ||
2602 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2603 hit_mask == HIT_MASK_TOPLEFT) ||
2604 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2605 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2606 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2607 hit_mask == HIT_MASK_BOTTOMRIGHT))
2610 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2611 ANG_MIRROR_135 : ANG_MIRROR_45);
2613 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2615 AddDamagedField(ELX, ELY);
2616 AddLaserEdge(LX, LY);
2618 laser.current_angle = get_mirrored_angle(laser.current_angle,
2626 AddLaserEdge(LX, LY);
2632 // reflection inside an edge of reflecting DF style wall
2633 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2635 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2636 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2637 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2638 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2639 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2640 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2641 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2642 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2645 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2646 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2647 ANG_MIRROR_135 : ANG_MIRROR_45);
2649 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2652 AddDamagedField(ELX, ELY);
2655 AddLaserEdge(LX - XS, LY - YS);
2656 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2657 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2659 laser.current_angle = get_mirrored_angle(laser.current_angle,
2667 AddLaserEdge(LX, LY);
2673 // check if laser hits DF style wall with an angle of 90°
2674 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2676 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2677 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2678 (IS_VERT_ANGLE(laser.current_angle) &&
2679 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2681 // laser at last step touched nothing or the same side of the wall
2682 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2684 AddDamagedField(ELX, ELY);
2691 last_hit_mask = hit_mask;
2698 if (!HitOnlyAnEdge(hit_mask))
2700 laser.overloaded = TRUE;
2708 static boolean HitAbsorbingWalls(int element, int hit_mask)
2710 if (HitOnlyAnEdge(hit_mask))
2714 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2716 AddLaserEdge(LX - XS, LY - YS);
2723 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2725 AddLaserEdge(LX - XS, LY - YS);
2731 if (IS_WALL_WOOD(element) ||
2732 IS_DF_WALL_WOOD(element) ||
2733 IS_GRID_WOOD(element) ||
2734 IS_GRID_WOOD_FIXED(element) ||
2735 IS_GRID_WOOD_AUTO(element) ||
2736 element == EL_FUSE_ON ||
2737 element == EL_BLOCK_WOOD ||
2738 element == EL_GATE_WOOD)
2740 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2745 if (IS_WALL_ICE(element))
2751 // check if laser hit adjacent edges of two diagonal tiles
2752 if (ELX != lx / TILEX)
2754 if (ELY != ly / TILEY)
2757 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2758 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2760 // check if laser hits wall with an angle of 90°
2761 if (IS_90_ANGLE(laser.current_angle))
2762 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2764 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2768 for (i = 0; i < 4; i++)
2770 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2771 mask = 15 - (8 >> i);
2772 else if (ABS(XS) == 4 &&
2774 (XS > 0) == (i % 2) &&
2775 (YS < 0) == (i / 2))
2776 mask = 3 + (i / 2) * 9;
2777 else if (ABS(YS) == 4 &&
2779 (XS < 0) == (i % 2) &&
2780 (YS > 0) == (i / 2))
2781 mask = 5 + (i % 2) * 5;
2785 laser.wall_mask = mask;
2787 else if (IS_WALL_AMOEBA(element))
2789 int elx = (LX - 2 * XS) / TILEX;
2790 int ely = (LY - 2 * YS) / TILEY;
2791 int element2 = Tile[elx][ely];
2794 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2796 laser.dest_element = EL_EMPTY;
2804 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2805 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2807 if (IS_90_ANGLE(laser.current_angle))
2808 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2810 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2812 laser.wall_mask = mask;
2818 static void OpenExit(int x, int y)
2822 if (!MovDelay[x][y]) // next animation frame
2823 MovDelay[x][y] = 4 * delay;
2825 if (MovDelay[x][y]) // wait some time before next frame
2830 phase = MovDelay[x][y] / delay;
2832 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2833 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2835 if (!MovDelay[x][y])
2837 Tile[x][y] = EL_EXIT_OPEN;
2843 static void OpenGrayBall(int x, int y)
2847 if (!MovDelay[x][y]) // next animation frame
2849 if (IS_WALL(Store[x][y]))
2851 DrawWalls_MM(x, y, Store[x][y]);
2853 // copy wall tile to spare bitmap for "melting" animation
2854 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2855 TILEX, TILEY, x * TILEX, y * TILEY);
2857 DrawElement_MM(x, y, EL_GRAY_BALL);
2860 MovDelay[x][y] = 50 * delay;
2863 if (MovDelay[x][y]) // wait some time before next frame
2867 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2871 int dx = RND(26), dy = RND(26);
2873 if (IS_WALL(Store[x][y]))
2875 // copy wall tile from spare bitmap for "melting" animation
2876 bitmap = bitmap_db_field;
2882 int graphic = el2gfx(Store[x][y]);
2884 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2887 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2888 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2890 laser.redraw = TRUE;
2892 MarkTileDirty(x, y);
2895 if (!MovDelay[x][y])
2897 Tile[x][y] = Store[x][y];
2898 Store[x][y] = Store2[x][y] = 0;
2899 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2901 InitField(x, y, FALSE);
2904 ScanLaser_FromLastMirror();
2909 static void OpenEnvelope(int x, int y)
2911 int num_frames = 8; // seven frames plus final empty space
2913 if (!MovDelay[x][y]) // next animation frame
2914 MovDelay[x][y] = num_frames;
2916 if (MovDelay[x][y]) // wait some time before next frame
2918 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2922 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2924 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2925 int frame = num_frames - MovDelay[x][y] - 1;
2927 DrawGraphicAnimation_MM(x, y, graphic, frame);
2929 laser.redraw = TRUE;
2932 if (MovDelay[x][y] == 0)
2934 Tile[x][y] = EL_EMPTY;
2945 static void MeltIce(int x, int y)
2950 if (!MovDelay[x][y]) // next animation frame
2951 MovDelay[x][y] = frames * delay;
2953 if (MovDelay[x][y]) // wait some time before next frame
2956 int wall_mask = Store2[x][y];
2957 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2960 phase = frames - MovDelay[x][y] / delay - 1;
2962 if (!MovDelay[x][y])
2964 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2965 Store[x][y] = Store2[x][y] = 0;
2967 DrawWalls_MM(x, y, Tile[x][y]);
2969 if (Tile[x][y] == EL_WALL_ICE_BASE)
2970 Tile[x][y] = EL_EMPTY;
2972 ScanLaser_FromLastMirror();
2974 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2976 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2978 laser.redraw = TRUE;
2983 static void GrowAmoeba(int x, int y)
2988 if (!MovDelay[x][y]) // next animation frame
2989 MovDelay[x][y] = frames * delay;
2991 if (MovDelay[x][y]) // wait some time before next frame
2994 int wall_mask = Store2[x][y];
2995 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2998 phase = MovDelay[x][y] / delay;
3000 if (!MovDelay[x][y])
3002 Tile[x][y] = real_element;
3003 Store[x][y] = Store2[x][y] = 0;
3005 DrawWalls_MM(x, y, Tile[x][y]);
3006 DrawLaser(0, DL_LASER_ENABLED);
3008 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
3010 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
3015 static void DrawFieldAnimated_MM(int x, int y)
3019 laser.redraw = TRUE;
3022 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
3024 int element = Tile[x][y];
3025 int graphic = el2gfx(element);
3027 if (!getGraphicInfo_NewFrame(x, y, graphic))
3032 laser.redraw = TRUE;
3035 static void DrawFieldTwinkle(int x, int y)
3037 if (MovDelay[x][y] != 0) // wait some time before next frame
3043 if (MovDelay[x][y] != 0)
3045 int graphic = IMG_TWINKLE_WHITE;
3046 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
3048 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
3051 laser.redraw = TRUE;
3055 static void Explode_MM(int x, int y, int phase, int mode)
3057 int num_phase = 9, delay = 2;
3058 int last_phase = num_phase * delay;
3059 int half_phase = (num_phase / 2) * delay;
3062 laser.redraw = TRUE;
3064 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
3066 center_element = Tile[x][y];
3068 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
3070 // put moving element to center field (and let it explode there)
3071 center_element = MovingOrBlocked2Element_MM(x, y);
3072 RemoveMovingField_MM(x, y);
3074 Tile[x][y] = center_element;
3077 if (center_element != EL_GRAY_BALL_ACTIVE)
3078 Store[x][y] = EL_EMPTY;
3079 Store2[x][y] = center_element;
3081 Tile[x][y] = EL_EXPLODING_OPAQUE;
3083 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
3084 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
3085 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
3088 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3090 ExplodePhase[x][y] = 1;
3096 GfxFrame[x][y] = 0; // restart explosion animation
3098 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
3100 center_element = Store2[x][y];
3102 if (phase == half_phase && Store[x][y] == EL_EMPTY)
3104 Tile[x][y] = EL_EXPLODING_TRANSP;
3106 if (x == ELX && y == ELY)
3110 if (phase == last_phase)
3112 if (center_element == EL_BOMB_ACTIVE)
3114 DrawLaser(0, DL_LASER_DISABLED);
3117 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3119 laser.overloaded = FALSE;
3121 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
3123 GameOver_MM(GAME_OVER_BOMB);
3126 Tile[x][y] = Store[x][y];
3128 Store[x][y] = Store2[x][y] = 0;
3129 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3131 InitField(x, y, FALSE);
3134 if (center_element == EL_GRAY_BALL_ACTIVE)
3135 ScanLaser_FromLastMirror();
3137 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
3139 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
3140 int frame = getGraphicAnimationFrameXY(graphic, x, y);
3142 DrawGraphicAnimation_MM(x, y, graphic, frame);
3144 MarkTileDirty(x, y);
3148 static void Bang_MM(int x, int y)
3150 int element = Tile[x][y];
3152 if (IS_PACMAN(element))
3153 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3154 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
3155 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3156 else if (element == EL_KEY)
3157 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3159 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3161 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
3164 static void TurnRound(int x, int y)
3176 { 0, 0 }, { 0, 0 }, { 0, 0 },
3181 int left, right, back;
3185 { MV_DOWN, MV_UP, MV_RIGHT },
3186 { MV_UP, MV_DOWN, MV_LEFT },
3188 { MV_LEFT, MV_RIGHT, MV_DOWN },
3192 { MV_RIGHT, MV_LEFT, MV_UP }
3195 int element = Tile[x][y];
3196 int old_move_dir = MovDir[x][y];
3197 int right_dir = turn[old_move_dir].right;
3198 int back_dir = turn[old_move_dir].back;
3199 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
3200 int right_x = x + right_dx, right_y = y + right_dy;
3202 if (element == EL_PACMAN)
3204 boolean can_turn_right = FALSE;
3206 if (IN_LEV_FIELD(right_x, right_y) &&
3207 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
3208 can_turn_right = TRUE;
3211 MovDir[x][y] = right_dir;
3213 MovDir[x][y] = back_dir;
3219 static void StartMoving_MM(int x, int y)
3221 int element = Tile[x][y];
3226 if (CAN_MOVE(element))
3230 if (MovDelay[x][y]) // wait some time before next movement
3238 // now make next step
3240 Moving2Blocked(x, y, &newx, &newy); // get next screen position
3242 if (element == EL_PACMAN &&
3243 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
3244 !ObjHit(newx, newy, HIT_POS_CENTER))
3246 Store[newx][newy] = Tile[newx][newy];
3247 Tile[newx][newy] = EL_EMPTY;
3249 DrawField_MM(newx, newy);
3251 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3252 ObjHit(newx, newy, HIT_POS_CENTER))
3254 // object was running against a wall
3261 InitMovingField_MM(x, y, MovDir[x][y]);
3265 ContinueMoving_MM(x, y);
3268 static void ContinueMoving_MM(int x, int y)
3270 int element = Tile[x][y];
3271 int direction = MovDir[x][y];
3272 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3273 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3274 int horiz_move = (dx!=0);
3275 int newx = x + dx, newy = y + dy;
3276 int step = (horiz_move ? dx : dy) * TILEX / 8;
3278 MovPos[x][y] += step;
3280 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3282 Tile[x][y] = EL_EMPTY;
3283 Tile[newx][newy] = element;
3285 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3286 MovDelay[newx][newy] = 0;
3288 if (!CAN_MOVE(element))
3289 MovDir[newx][newy] = 0;
3292 DrawField_MM(newx, newy);
3294 Stop[newx][newy] = TRUE;
3296 if (element == EL_PACMAN)
3298 if (Store[newx][newy] == EL_BOMB)
3299 Bang_MM(newx, newy);
3301 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3302 (LX + 2 * XS) / TILEX == newx &&
3303 (LY + 2 * YS) / TILEY == newy)
3310 else // still moving on
3315 laser.redraw = TRUE;
3318 boolean ClickElement(int x, int y, int button)
3320 static DelayCounter click_delay = { CLICK_DELAY };
3321 static boolean new_button = TRUE;
3322 boolean element_clicked = FALSE;
3327 // initialize static variables
3328 click_delay.count = 0;
3329 click_delay.value = CLICK_DELAY;
3335 // do not rotate objects hit by the laser after the game was solved
3336 if (game_mm.level_solved && Hit[x][y])
3339 if (button == MB_RELEASED)
3342 click_delay.value = CLICK_DELAY;
3344 // release eventually hold auto-rotating mirror
3345 RotateMirror(x, y, MB_RELEASED);
3350 if (!FrameReached(&click_delay) && !new_button)
3353 if (button == MB_MIDDLEBUTTON) // middle button has no function
3356 if (!IN_LEV_FIELD(x, y))
3359 if (Tile[x][y] == EL_EMPTY)
3362 element = Tile[x][y];
3364 if (IS_MIRROR(element) ||
3365 IS_BEAMER(element) ||
3366 IS_POLAR(element) ||
3367 IS_POLAR_CROSS(element) ||
3368 IS_DF_MIRROR(element) ||
3369 IS_DF_MIRROR_AUTO(element))
3371 RotateMirror(x, y, button);
3373 element_clicked = TRUE;
3375 else if (IS_MCDUFFIN(element))
3377 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3379 if (has_laser && !laser.fuse_off)
3380 DrawLaser(0, DL_LASER_DISABLED);
3382 element = get_rotated_element(element, BUTTON_ROTATION(button));
3384 Tile[x][y] = element;
3389 laser.start_angle = get_element_angle(element);
3393 if (!laser.fuse_off)
3397 element_clicked = TRUE;
3399 else if (element == EL_FUSE_ON && laser.fuse_off)
3401 if (x != laser.fuse_x || y != laser.fuse_y)
3404 laser.fuse_off = FALSE;
3405 laser.fuse_x = laser.fuse_y = -1;
3407 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3410 element_clicked = TRUE;
3412 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3414 laser.fuse_off = TRUE;
3417 laser.overloaded = FALSE;
3419 DrawLaser(0, DL_LASER_DISABLED);
3420 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3422 element_clicked = TRUE;
3424 else if (element == EL_LIGHTBALL)
3427 RaiseScoreElement_MM(element);
3428 DrawLaser(0, DL_LASER_ENABLED);
3430 element_clicked = TRUE;
3432 else if (IS_ENVELOPE(element))
3434 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3436 element_clicked = TRUE;
3439 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3442 return element_clicked;
3445 static void RotateMirror(int x, int y, int button)
3447 if (button == MB_RELEASED)
3449 // release eventually hold auto-rotating mirror
3456 if (IS_MIRROR(Tile[x][y]) ||
3457 IS_POLAR_CROSS(Tile[x][y]) ||
3458 IS_POLAR(Tile[x][y]) ||
3459 IS_BEAMER(Tile[x][y]) ||
3460 IS_DF_MIRROR(Tile[x][y]) ||
3461 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3462 IS_GRID_WOOD_AUTO(Tile[x][y]))
3464 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3466 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3468 if (button == MB_LEFTBUTTON)
3470 // left mouse button only for manual adjustment, no auto-rotating;
3471 // freeze mirror for until mouse button released
3475 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3477 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3481 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3483 int edge = Hit[x][y];
3489 DrawLaser(edge - 1, DL_LASER_DISABLED);
3493 else if (ObjHit(x, y, HIT_POS_CENTER))
3495 int edge = Hit[x][y];
3499 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3504 DrawLaser(edge - 1, DL_LASER_DISABLED);
3511 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3516 if ((IS_BEAMER(Tile[x][y]) ||
3517 IS_POLAR(Tile[x][y]) ||
3518 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3520 if (IS_BEAMER(Tile[x][y]))
3523 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3524 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3537 DrawLaser(0, DL_LASER_ENABLED);
3541 static void AutoRotateMirrors(void)
3545 if (!FrameReached(&rotate_delay))
3548 for (x = 0; x < lev_fieldx; x++)
3550 for (y = 0; y < lev_fieldy; y++)
3552 int element = Tile[x][y];
3554 // do not rotate objects hit by the laser after the game was solved
3555 if (game_mm.level_solved && Hit[x][y])
3558 if (IS_DF_MIRROR_AUTO(element) ||
3559 IS_GRID_WOOD_AUTO(element) ||
3560 IS_GRID_STEEL_AUTO(element) ||
3561 element == EL_REFRACTOR)
3563 RotateMirror(x, y, MB_RIGHTBUTTON);
3565 laser.redraw = TRUE;
3571 static boolean ObjHit(int obx, int oby, int bits)
3578 if (bits & HIT_POS_CENTER)
3580 if (CheckLaserPixel(cSX + obx + 15,
3585 if (bits & HIT_POS_EDGE)
3587 for (i = 0; i < 4; i++)
3588 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3589 cSY + oby + 31 * (i / 2)))
3593 if (bits & HIT_POS_BETWEEN)
3595 for (i = 0; i < 4; i++)
3596 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3597 cSY + 4 + oby + 22 * (i / 2)))
3604 static void DeletePacMan(int px, int py)
3610 if (game_mm.num_pacman <= 1)
3612 game_mm.num_pacman = 0;
3616 for (i = 0; i < game_mm.num_pacman; i++)
3617 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3620 game_mm.num_pacman--;
3622 for (j = i; j < game_mm.num_pacman; j++)
3624 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3625 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3626 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3630 static void GameActions_MM_Ext(void)
3637 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3640 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3642 element = Tile[x][y];
3644 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3645 StartMoving_MM(x, y);
3646 else if (IS_MOVING(x, y))
3647 ContinueMoving_MM(x, y);
3648 else if (IS_EXPLODING(element))
3649 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3650 else if (element == EL_EXIT_OPENING)
3652 else if (element == EL_GRAY_BALL_OPENING)
3654 else if (IS_ENVELOPE_OPENING(element))
3656 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3658 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3660 else if (IS_MIRROR(element) ||
3661 IS_MIRROR_FIXED(element) ||
3662 element == EL_PRISM)
3663 DrawFieldTwinkle(x, y);
3664 else if (element == EL_GRAY_BALL_ACTIVE ||
3665 element == EL_BOMB_ACTIVE ||
3666 element == EL_MINE_ACTIVE)
3667 DrawFieldAnimated_MM(x, y);
3668 else if (!IS_BLOCKED(x, y))
3669 DrawFieldAnimatedIfNeeded_MM(x, y);
3672 AutoRotateMirrors();
3675 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3677 // redraw after Explode_MM() ...
3679 DrawLaser(0, DL_LASER_ENABLED);
3680 laser.redraw = FALSE;
3685 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3689 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3691 DrawLaser(0, DL_LASER_DISABLED);
3696 // skip all following game actions if game is over
3697 if (game_mm.game_over)
3700 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3704 GameOver_MM(GAME_OVER_NO_ENERGY);
3709 if (FrameReached(&energy_delay))
3711 if (game_mm.energy_left > 0)
3712 game_mm.energy_left--;
3714 // when out of energy, wait another frame to play "out of time" sound
3717 element = laser.dest_element;
3720 if (element != Tile[ELX][ELY])
3722 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3723 element, Tile[ELX][ELY]);
3727 if (!laser.overloaded && laser.overload_value == 0 &&
3728 element != EL_BOMB &&
3729 element != EL_BOMB_ACTIVE &&
3730 element != EL_MINE &&
3731 element != EL_MINE_ACTIVE &&
3732 element != EL_GRAY_BALL &&
3733 element != EL_GRAY_BALL_ACTIVE &&
3734 element != EL_BLOCK_STONE &&
3735 element != EL_BLOCK_WOOD &&
3736 element != EL_FUSE_ON &&
3737 element != EL_FUEL_FULL &&
3738 !IS_WALL_ICE(element) &&
3739 !IS_WALL_AMOEBA(element))
3742 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3744 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3745 (!laser.overloaded && laser.overload_value > 0)) &&
3746 FrameReached(&overload_delay))
3748 if (laser.overloaded)
3749 laser.overload_value++;
3751 laser.overload_value--;
3753 if (game_mm.cheat_no_overload)
3755 laser.overloaded = FALSE;
3756 laser.overload_value = 0;
3759 game_mm.laser_overload_value = laser.overload_value;
3761 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3763 SetLaserColor(0xFF);
3765 DrawLaser(0, DL_LASER_ENABLED);
3768 if (!laser.overloaded)
3769 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3770 else if (setup.sound_loops)
3771 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3773 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3775 if (laser.overload_value == MAX_LASER_OVERLOAD)
3777 UpdateAndDisplayGameControlValues();
3781 GameOver_MM(GAME_OVER_OVERLOADED);
3792 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3794 if (game_mm.cheat_no_explosion)
3799 laser.dest_element = EL_EXPLODING_OPAQUE;
3804 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3806 laser.fuse_off = TRUE;
3810 DrawLaser(0, DL_LASER_DISABLED);
3811 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3814 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3816 if (!Store2[ELX][ELY]) // check if content element not yet determined
3818 int last_anim_random_frame = gfx.anim_random_frame;
3821 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3822 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3824 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3825 native_mm_level.ball_choice_mode, 0,
3826 game_mm.ball_choice_pos);
3828 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3829 gfx.anim_random_frame = last_anim_random_frame;
3831 game_mm.ball_choice_pos++;
3833 int new_element = native_mm_level.ball_content[element_pos];
3834 int new_element_base = map_wall_to_base_element(new_element);
3836 if (IS_WALL(new_element_base))
3838 // always use completely filled wall element
3839 new_element = new_element_base | 0x000f;
3841 else if (native_mm_level.rotate_ball_content &&
3842 get_num_elements(new_element) > 1)
3844 // randomly rotate newly created game element
3845 new_element = get_rotated_element(new_element, RND(16));
3848 Store[ELX][ELY] = new_element;
3849 Store2[ELX][ELY] = TRUE;
3852 if (native_mm_level.explode_ball)
3855 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3857 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3862 if (IS_WALL_ICE(element) && CT > 50)
3864 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3866 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3867 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3868 Store2[ELX][ELY] = laser.wall_mask;
3870 laser.dest_element = Tile[ELX][ELY];
3875 if (IS_WALL_AMOEBA(element) && CT > 60)
3878 int element2 = Tile[ELX][ELY];
3880 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3883 for (i = laser.num_damages - 1; i >= 0; i--)
3884 if (laser.damage[i].is_mirror)
3887 r = laser.num_edges;
3888 d = laser.num_damages;
3895 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3898 DrawLaser(0, DL_LASER_ENABLED);
3901 x = laser.damage[k1].x;
3902 y = laser.damage[k1].y;
3907 for (i = 0; i < 4; i++)
3909 if (laser.wall_mask & (1 << i))
3911 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3912 cSY + ELY * TILEY + 31 * (i / 2)))
3915 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3916 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3923 for (i = 0; i < 4; i++)
3925 if (laser.wall_mask & (1 << i))
3927 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3928 cSY + ELY * TILEY + 31 * (i / 2)))
3935 if (laser.num_beamers > 0 ||
3936 k1 < 1 || k2 < 4 || k3 < 4 ||
3937 CheckLaserPixel(cSX + ELX * TILEX + 14,
3938 cSY + ELY * TILEY + 14))
3940 laser.num_edges = r;
3941 laser.num_damages = d;
3943 DrawLaser(0, DL_LASER_DISABLED);
3946 Tile[ELX][ELY] = element | laser.wall_mask;
3948 int x = ELX, y = ELY;
3949 int wall_mask = laser.wall_mask;
3952 DrawLaser(0, DL_LASER_ENABLED);
3954 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3956 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3957 Store[x][y] = EL_WALL_AMOEBA_BASE;
3958 Store2[x][y] = wall_mask;
3963 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3964 laser.stops_inside_element && CT > native_mm_level.time_block)
3969 if (ABS(XS) > ABS(YS))
3976 for (i = 0; i < 4; i++)
3983 x = ELX + Step[k * 4].x;
3984 y = ELY + Step[k * 4].y;
3986 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3989 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3997 laser.overloaded = (element == EL_BLOCK_STONE);
4002 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
4005 Tile[x][y] = element;
4007 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
4010 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
4012 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
4013 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
4021 if (element == EL_FUEL_FULL && CT > 10)
4023 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
4024 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
4026 for (i = start; i <= num_init_game_frames; i++)
4028 if (i == num_init_game_frames)
4029 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4030 else if (setup.sound_loops)
4031 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4033 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4035 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
4037 UpdateAndDisplayGameControlValues();
4042 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
4044 DrawField_MM(ELX, ELY);
4046 DrawLaser(0, DL_LASER_ENABLED);
4052 void GameActions_MM(struct MouseActionInfo action)
4054 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
4055 boolean button_released = (action.button == MB_RELEASED);
4057 GameActions_MM_Ext();
4059 CheckSingleStepMode_MM(element_clicked, button_released);
4062 static void MovePacMen(void)
4064 int mx, my, ox, oy, nx, ny;
4068 if (++pacman_nr >= game_mm.num_pacman)
4071 game_mm.pacman[pacman_nr].dir--;
4073 for (l = 1; l < 5; l++)
4075 game_mm.pacman[pacman_nr].dir++;
4077 if (game_mm.pacman[pacman_nr].dir > 4)
4078 game_mm.pacman[pacman_nr].dir = 1;
4080 if (game_mm.pacman[pacman_nr].dir % 2)
4083 my = game_mm.pacman[pacman_nr].dir - 2;
4088 mx = 3 - game_mm.pacman[pacman_nr].dir;
4091 ox = game_mm.pacman[pacman_nr].x;
4092 oy = game_mm.pacman[pacman_nr].y;
4095 element = Tile[nx][ny];
4097 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
4100 if (!IS_EATABLE4PACMAN(element))
4103 if (ObjHit(nx, ny, HIT_POS_CENTER))
4106 Tile[ox][oy] = EL_EMPTY;
4108 EL_PACMAN_RIGHT - 1 +
4109 (game_mm.pacman[pacman_nr].dir - 1 +
4110 (game_mm.pacman[pacman_nr].dir % 2) * 2);
4112 game_mm.pacman[pacman_nr].x = nx;
4113 game_mm.pacman[pacman_nr].y = ny;
4115 DrawGraphic_MM(ox, oy, IMG_EMPTY);
4117 if (element != EL_EMPTY)
4119 int graphic = el2gfx(Tile[nx][ny]);
4124 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
4127 ox = cSX + ox * TILEX;
4128 oy = cSY + oy * TILEY;
4130 for (i = 1; i < 33; i += 2)
4131 BlitBitmap(bitmap, window,
4132 src_x, src_y, TILEX, TILEY,
4133 ox + i * mx, oy + i * my);
4134 Ct = Ct + FrameCounter - CT;
4137 DrawField_MM(nx, ny);
4140 if (!laser.fuse_off)
4142 DrawLaser(0, DL_LASER_ENABLED);
4144 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
4146 AddDamagedField(nx, ny);
4148 laser.damage[laser.num_damages - 1].edge = 0;
4152 if (element == EL_BOMB)
4153 DeletePacMan(nx, ny);
4155 if (IS_WALL_AMOEBA(element) &&
4156 (LX + 2 * XS) / TILEX == nx &&
4157 (LY + 2 * YS) / TILEY == ny)
4167 static void InitMovingField_MM(int x, int y, int direction)
4169 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4170 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4172 MovDir[x][y] = direction;
4173 MovDir[newx][newy] = direction;
4175 if (Tile[newx][newy] == EL_EMPTY)
4176 Tile[newx][newy] = EL_BLOCKED;
4179 static int MovingOrBlocked2Element_MM(int x, int y)
4181 int element = Tile[x][y];
4183 if (element == EL_BLOCKED)
4187 Blocked2Moving(x, y, &oldx, &oldy);
4189 return Tile[oldx][oldy];
4195 static void RemoveMovingField_MM(int x, int y)
4197 int oldx = x, oldy = y, newx = x, newy = y;
4199 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4202 if (IS_MOVING(x, y))
4204 Moving2Blocked(x, y, &newx, &newy);
4205 if (Tile[newx][newy] != EL_BLOCKED)
4208 else if (Tile[x][y] == EL_BLOCKED)
4210 Blocked2Moving(x, y, &oldx, &oldy);
4211 if (!IS_MOVING(oldx, oldy))
4215 Tile[oldx][oldy] = EL_EMPTY;
4216 Tile[newx][newy] = EL_EMPTY;
4217 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4218 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4220 DrawLevelField_MM(oldx, oldy);
4221 DrawLevelField_MM(newx, newy);
4224 static void RaiseScore_MM(int value)
4226 game_mm.score += value;
4229 void RaiseScoreElement_MM(int element)
4234 case EL_PACMAN_RIGHT:
4236 case EL_PACMAN_LEFT:
4237 case EL_PACMAN_DOWN:
4238 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4242 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4247 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4251 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4260 // ----------------------------------------------------------------------------
4261 // Mirror Magic game engine snapshot handling functions
4262 // ----------------------------------------------------------------------------
4264 void SaveEngineSnapshotValues_MM(void)
4268 engine_snapshot_mm.game_mm = game_mm;
4269 engine_snapshot_mm.laser = laser;
4271 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4273 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4275 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4276 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4277 engine_snapshot_mm.Box[x][y] = Box[x][y];
4278 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4282 engine_snapshot_mm.LX = LX;
4283 engine_snapshot_mm.LY = LY;
4284 engine_snapshot_mm.XS = XS;
4285 engine_snapshot_mm.YS = YS;
4286 engine_snapshot_mm.ELX = ELX;
4287 engine_snapshot_mm.ELY = ELY;
4288 engine_snapshot_mm.CT = CT;
4289 engine_snapshot_mm.Ct = Ct;
4291 engine_snapshot_mm.last_LX = last_LX;
4292 engine_snapshot_mm.last_LY = last_LY;
4293 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4294 engine_snapshot_mm.hold_x = hold_x;
4295 engine_snapshot_mm.hold_y = hold_y;
4296 engine_snapshot_mm.pacman_nr = pacman_nr;
4298 engine_snapshot_mm.rotate_delay = rotate_delay;
4299 engine_snapshot_mm.pacman_delay = pacman_delay;
4300 engine_snapshot_mm.energy_delay = energy_delay;
4301 engine_snapshot_mm.overload_delay = overload_delay;
4304 void LoadEngineSnapshotValues_MM(void)
4308 // stored engine snapshot buffers already restored at this point
4310 game_mm = engine_snapshot_mm.game_mm;
4311 laser = engine_snapshot_mm.laser;
4313 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4315 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4317 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4318 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4319 Box[x][y] = engine_snapshot_mm.Box[x][y];
4320 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4324 LX = engine_snapshot_mm.LX;
4325 LY = engine_snapshot_mm.LY;
4326 XS = engine_snapshot_mm.XS;
4327 YS = engine_snapshot_mm.YS;
4328 ELX = engine_snapshot_mm.ELX;
4329 ELY = engine_snapshot_mm.ELY;
4330 CT = engine_snapshot_mm.CT;
4331 Ct = engine_snapshot_mm.Ct;
4333 last_LX = engine_snapshot_mm.last_LX;
4334 last_LY = engine_snapshot_mm.last_LY;
4335 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4336 hold_x = engine_snapshot_mm.hold_x;
4337 hold_y = engine_snapshot_mm.hold_y;
4338 pacman_nr = engine_snapshot_mm.pacman_nr;
4340 rotate_delay = engine_snapshot_mm.rotate_delay;
4341 pacman_delay = engine_snapshot_mm.pacman_delay;
4342 energy_delay = engine_snapshot_mm.energy_delay;
4343 overload_delay = engine_snapshot_mm.overload_delay;
4345 RedrawPlayfield_MM();
4348 static int getAngleFromTouchDelta(int dx, int dy, int base)
4350 double pi = 3.141592653;
4351 double rad = atan2((double)-dy, (double)dx);
4352 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4353 double deg = rad2 * 180.0 / pi;
4355 return (int)(deg * base / 360.0 + 0.5) % base;
4358 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4360 // calculate start (source) position to be at the middle of the tile
4361 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4362 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4363 int dx = dst_mx - src_mx;
4364 int dy = dst_my - src_my;
4373 if (!IN_LEV_FIELD(x, y))
4376 element = Tile[x][y];
4378 if (!IS_MCDUFFIN(element) &&
4379 !IS_MIRROR(element) &&
4380 !IS_BEAMER(element) &&
4381 !IS_POLAR(element) &&
4382 !IS_POLAR_CROSS(element) &&
4383 !IS_DF_MIRROR(element))
4386 angle_old = get_element_angle(element);
4388 if (IS_MCDUFFIN(element))
4390 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4391 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4392 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4393 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4396 else if (IS_MIRROR(element) ||
4397 IS_DF_MIRROR(element))
4399 for (i = 0; i < laser.num_damages; i++)
4401 if (laser.damage[i].x == x &&
4402 laser.damage[i].y == y &&
4403 ObjHit(x, y, HIT_POS_CENTER))
4405 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4406 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4413 if (angle_new == -1)
4415 if (IS_MIRROR(element) ||
4416 IS_DF_MIRROR(element) ||
4420 if (IS_POLAR_CROSS(element))
4423 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4426 button = (angle_new == angle_old ? 0 :
4427 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4428 MB_LEFTBUTTON : MB_RIGHTBUTTON);