1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
101 static void AddLaserEdge(int, int);
102 static void ScanLaser(void);
103 static void DrawLaser(int, int);
104 static boolean HitElement(int, int);
105 static boolean HitOnlyAnEdge(int);
106 static boolean HitPolarizer(int, int);
107 static boolean HitBlock(int, int);
108 static boolean HitLaserSource(int, int);
109 static boolean HitLaserDestination(int, int);
110 static boolean HitReflectingWalls(int, int);
111 static boolean HitAbsorbingWalls(int, int);
112 static void RotateMirror(int, int, int);
113 static boolean ObjHit(int, int, int);
114 static void DeletePacMan(int, int);
115 static void MovePacMen(void);
117 // bitmap for laser beam detection
118 static Bitmap *laser_bitmap = NULL;
120 // variables for laser control
121 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
122 static int hold_x = -1, hold_y = -1;
124 // variables for pacman control
125 static int pacman_nr = -1;
127 // various game engine delay counters
128 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
129 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
130 static DelayCounter energy_delay = { ENERGY_DELAY };
131 static DelayCounter overload_delay = { 0 };
133 // element mask positions for scanning pixels of MM elements
134 #define MM_MASK_MCDUFFIN_RIGHT 0
135 #define MM_MASK_MCDUFFIN_UP 1
136 #define MM_MASK_MCDUFFIN_LEFT 2
137 #define MM_MASK_MCDUFFIN_DOWN 3
138 #define MM_MASK_GRID_1 4
139 #define MM_MASK_GRID_2 5
140 #define MM_MASK_GRID_3 6
141 #define MM_MASK_GRID_4 7
142 #define MM_MASK_SLOPE_1 8
143 #define MM_MASK_SLOPE_2 9
144 #define MM_MASK_SLOPE_3 10
145 #define MM_MASK_SLOPE_4 11
146 #define MM_MASK_RECTANGLE 12
147 #define MM_MASK_CIRCLE 13
149 #define NUM_MM_MASKS 14
151 // element masks for scanning pixels of MM elements
152 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
408 static int get_element_angle(int element)
410 int element_phase = get_element_phase(element);
412 if (IS_MIRROR_FIXED(element) ||
413 IS_MCDUFFIN(element) ||
415 IS_RECEIVER(element))
416 return 4 * element_phase;
417 else if (IS_DF_SLOPE(element))
418 return 4 + (element_phase % 2) * 8;
420 return element_phase;
423 static int get_opposite_angle(int angle)
425 int opposite_angle = angle + ANG_RAY_180;
427 // make sure "opposite_angle" is in valid interval [0, 15]
428 return (opposite_angle + 16) % 16;
431 static int get_mirrored_angle(int laser_angle, int mirror_angle)
433 int reflected_angle = 16 - laser_angle + mirror_angle;
435 // make sure "reflected_angle" is in valid interval [0, 15]
436 return (reflected_angle + 16) % 16;
439 static void DrawLaserLines(struct XY *points, int num_points, int mode)
441 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
442 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
444 DrawLines(drawto_mm, points, num_points, pixel_drawto);
448 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
453 static boolean CheckLaserPixel(int x, int y)
459 pixel = ReadPixel(laser_bitmap, x, y);
463 return (pixel == WHITE_PIXEL);
466 static void CheckExitMM(void)
468 int exit_element = EL_EMPTY;
472 static int xy[4][2] =
480 for (y = 0; y < lev_fieldy; y++)
482 for (x = 0; x < lev_fieldx; x++)
484 if (Tile[x][y] == EL_EXIT_CLOSED)
486 // initiate opening animation of exit door
487 Tile[x][y] = EL_EXIT_OPENING;
489 exit_element = EL_EXIT_OPEN;
493 else if (IS_RECEIVER(Tile[x][y]))
495 // remove field that blocks receiver
496 int phase = Tile[x][y] - EL_RECEIVER_START;
497 int blocking_x, blocking_y;
499 blocking_x = x + xy[phase][0];
500 blocking_y = y + xy[phase][1];
502 if (IN_LEV_FIELD(blocking_x, blocking_y))
504 Tile[blocking_x][blocking_y] = EL_EMPTY;
506 DrawField_MM(blocking_x, blocking_y);
509 exit_element = EL_RECEIVER;
516 if (exit_element != EL_EMPTY)
517 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
520 static void SetLaserColor(int brightness)
522 int color_min = 0x00;
523 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
524 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
525 int color_down = color_max - color_up;
528 GetPixelFromRGB(window,
529 (game_mm.laser_red ? color_max : color_up),
530 (game_mm.laser_green ? color_down : color_min),
531 (game_mm.laser_blue ? color_down : color_min));
534 static void InitMovDir_MM(int x, int y)
536 int element = Tile[x][y];
537 static int direction[3][4] =
539 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
540 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
541 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
546 case EL_PACMAN_RIGHT:
550 Tile[x][y] = EL_PACMAN;
551 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
559 static void InitField(int x, int y, boolean init_game)
561 int element = Tile[x][y];
566 Tile[x][y] = EL_EMPTY;
571 if (init_game && native_mm_level.auto_count_kettles)
572 game_mm.kettles_still_needed++;
575 case EL_LIGHTBULB_OFF:
576 game_mm.lights_still_needed++;
580 if (IS_MIRROR(element) ||
581 IS_BEAMER_OLD(element) ||
582 IS_BEAMER(element) ||
584 IS_POLAR_CROSS(element) ||
585 IS_DF_MIRROR(element) ||
586 IS_DF_MIRROR_AUTO(element) ||
587 IS_GRID_STEEL_AUTO(element) ||
588 IS_GRID_WOOD_AUTO(element) ||
589 IS_FIBRE_OPTIC(element))
591 if (IS_BEAMER_OLD(element))
593 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
594 element = Tile[x][y];
597 if (!IS_FIBRE_OPTIC(element))
599 static int steps_grid_auto = 0;
601 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
602 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
604 if (IS_GRID_STEEL_AUTO(element) ||
605 IS_GRID_WOOD_AUTO(element))
606 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
608 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
610 game_mm.cycle[game_mm.num_cycle].x = x;
611 game_mm.cycle[game_mm.num_cycle].y = y;
615 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
617 int beamer_nr = BEAMER_NR(element);
618 int nr = laser.beamer[beamer_nr][0].num;
620 laser.beamer[beamer_nr][nr].x = x;
621 laser.beamer[beamer_nr][nr].y = y;
622 laser.beamer[beamer_nr][nr].num = 1;
625 else if (IS_PACMAN(element))
629 else if (IS_MCDUFFIN(element) || IS_LASER(element))
633 laser.start_edge.x = x;
634 laser.start_edge.y = y;
635 laser.start_angle = get_element_angle(element);
638 if (IS_MCDUFFIN(element))
640 game_mm.laser_red = native_mm_level.mm_laser_red;
641 game_mm.laser_green = native_mm_level.mm_laser_green;
642 game_mm.laser_blue = native_mm_level.mm_laser_blue;
646 game_mm.laser_red = native_mm_level.df_laser_red;
647 game_mm.laser_green = native_mm_level.df_laser_green;
648 game_mm.laser_blue = native_mm_level.df_laser_blue;
651 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
658 static void InitCycleElements_RotateSingleStep(void)
662 if (game_mm.num_cycle == 0) // no elements to cycle
665 for (i = 0; i < game_mm.num_cycle; i++)
667 int x = game_mm.cycle[i].x;
668 int y = game_mm.cycle[i].y;
669 int step = SIGN(game_mm.cycle[i].steps);
670 int last_element = Tile[x][y];
671 int next_element = get_rotated_element(last_element, step);
673 if (!game_mm.cycle[i].steps)
676 Tile[x][y] = next_element;
678 game_mm.cycle[i].steps -= step;
682 static void InitLaser(void)
684 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
685 int step = (IS_LASER(start_element) ? 4 : 0);
687 LX = laser.start_edge.x * TILEX;
688 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
691 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
693 LY = laser.start_edge.y * TILEY;
694 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
695 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
699 XS = 2 * Step[laser.start_angle].x;
700 YS = 2 * Step[laser.start_angle].y;
702 laser.current_angle = laser.start_angle;
704 laser.num_damages = 0;
706 laser.num_beamers = 0;
707 laser.beamer_edge[0] = 0;
709 laser.dest_element = EL_EMPTY;
712 AddLaserEdge(LX, LY); // set laser starting edge
717 void InitGameEngine_MM(void)
723 // initialize laser bitmap to current playfield (screen) size
724 ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
725 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
729 // set global game control values
730 game_mm.num_cycle = 0;
731 game_mm.num_pacman = 0;
734 game_mm.energy_left = 0; // later set to "native_mm_level.time"
735 game_mm.kettles_still_needed =
736 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
737 game_mm.lights_still_needed = 0;
738 game_mm.num_keys = 0;
739 game_mm.ball_choice_pos = 0;
741 game_mm.laser_red = FALSE;
742 game_mm.laser_green = FALSE;
743 game_mm.laser_blue = TRUE;
744 game_mm.has_mcduffin = TRUE;
746 game_mm.level_solved = FALSE;
747 game_mm.game_over = FALSE;
748 game_mm.game_over_cause = 0;
749 game_mm.game_over_message = NULL;
751 game_mm.laser_overload_value = 0;
752 game_mm.laser_enabled = FALSE;
754 // set global laser control values (must be set before "InitLaser()")
755 laser.start_edge.x = 0;
756 laser.start_edge.y = 0;
757 laser.start_angle = 0;
759 for (i = 0; i < MAX_NUM_BEAMERS; i++)
760 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
762 laser.overloaded = FALSE;
763 laser.overload_value = 0;
764 laser.fuse_off = FALSE;
765 laser.fuse_x = laser.fuse_y = -1;
767 laser.dest_element = EL_EMPTY;
768 laser.dest_element_last = EL_EMPTY;
769 laser.dest_element_last_x = -1;
770 laser.dest_element_last_y = -1;
784 rotate_delay.count = 0;
785 pacman_delay.count = 0;
786 energy_delay.count = 0;
787 overload_delay.count = 0;
789 ClickElement(-1, -1, -1);
791 for (x = 0; x < lev_fieldx; x++)
793 for (y = 0; y < lev_fieldy; y++)
795 Tile[x][y] = Ur[x][y];
796 Hit[x][y] = Box[x][y] = 0;
798 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
799 Store[x][y] = Store2[x][y] = 0;
802 InitField(x, y, TRUE);
809 void InitGameActions_MM(void)
811 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
812 int cycle_steps_done = 0;
817 for (i = 0; i <= num_init_game_frames; i++)
819 if (i == num_init_game_frames)
820 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
821 else if (setup.sound_loops)
822 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
824 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
826 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
828 UpdateAndDisplayGameControlValues();
830 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
832 InitCycleElements_RotateSingleStep();
837 AdvanceFrameCounter();
840 if (PendingEscapeKeyEvent())
844 if (setup.quick_doors)
854 if (setup.quick_doors)
860 if (game_mm.kettles_still_needed == 0)
863 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
864 SetTileCursorActive(TRUE);
866 // restart all delay counters after initially cycling game elements
867 ResetFrameCounter(&rotate_delay);
868 ResetFrameCounter(&pacman_delay);
869 ResetFrameCounter(&energy_delay);
870 ResetFrameCounter(&overload_delay);
873 static void FadeOutLaser(void)
877 for (i = 15; i >= 0; i--)
879 SetLaserColor(0x11 * i);
881 DrawLaser(0, DL_LASER_ENABLED);
884 Delay_WithScreenUpdates(50);
887 DrawLaser(0, DL_LASER_DISABLED);
889 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
892 static void GameOver_MM(int game_over_cause)
894 game_mm.game_over = TRUE;
895 game_mm.game_over_cause = game_over_cause;
896 game_mm.game_over_message = (game_mm.has_mcduffin ?
897 (game_over_cause == GAME_OVER_BOMB ?
898 "Bomb killed Mc Duffin!" :
899 game_over_cause == GAME_OVER_NO_ENERGY ?
900 "Out of magic energy!" :
901 game_over_cause == GAME_OVER_OVERLOADED ?
902 "Magic spell hit Mc Duffin!" :
904 (game_over_cause == GAME_OVER_BOMB ?
905 "Bomb destroyed laser cannon!" :
906 game_over_cause == GAME_OVER_NO_ENERGY ?
907 "Out of laser energy!" :
908 game_over_cause == GAME_OVER_OVERLOADED ?
909 "Laser beam hit laser cannon!" :
912 SetTileCursorActive(FALSE);
915 static void AddLaserEdge(int lx, int ly)
917 int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
918 int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
920 // check if laser is still inside visible playfield area (or inside level)
921 if (cSX + lx < REAL_SX || cSX + lx >= REAL_SX + full_sxsize ||
922 cSY + ly < REAL_SY || cSY + ly >= REAL_SY + full_sysize)
924 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
929 laser.edge[laser.num_edges].x = cSX2 + lx;
930 laser.edge[laser.num_edges].y = cSY2 + ly;
936 static void AddDamagedField(int ex, int ey)
938 // prevent adding the same field position again
939 if (laser.num_damages > 0 &&
940 laser.damage[laser.num_damages - 1].x == ex &&
941 laser.damage[laser.num_damages - 1].y == ey &&
942 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
945 laser.damage[laser.num_damages].is_mirror = FALSE;
946 laser.damage[laser.num_damages].angle = laser.current_angle;
947 laser.damage[laser.num_damages].edge = laser.num_edges;
948 laser.damage[laser.num_damages].x = ex;
949 laser.damage[laser.num_damages].y = ey;
953 static boolean StepBehind(void)
959 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
960 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
962 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
968 static int getMaskFromElement(int element)
970 if (IS_MCDUFFIN(element))
971 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
972 else if (IS_GRID(element))
973 return MM_MASK_GRID_1 + get_element_phase(element);
974 else if (IS_DF_GRID(element))
975 return MM_MASK_RECTANGLE;
976 else if (IS_DF_SLOPE(element))
977 return MM_MASK_SLOPE_1 + get_element_phase(element);
978 else if (IS_RECTANGLE(element))
979 return MM_MASK_RECTANGLE;
981 return MM_MASK_CIRCLE;
984 static int getPixelFromMask(int pos, int dx, int dy)
986 return (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
989 static int getLevelFromLaserX(int x)
991 return x / TILEX - (x < 0 ? 1 : 0); // correct negative values
994 static int getLevelFromLaserY(int y)
996 return y / TILEY - (y < 0 ? 1 : 0); // correct negative values
999 static int ScanPixel(void)
1004 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
1005 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
1008 // follow laser beam until it hits something (at least the screen border)
1009 while (hit_mask == HIT_MASK_NO_HIT)
1015 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
1016 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
1018 Debug("game:mm:ScanPixel", "touched screen border!");
1020 return HIT_MASK_ALL;
1024 // check if laser scan has crossed element boundaries (not just mini tiles)
1025 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1026 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1028 if (cross_x && cross_y)
1030 int elx1 = (LX - XS) / TILEX;
1031 int ely1 = (LY + YS) / TILEY;
1032 int elx2 = (LX + XS) / TILEX;
1033 int ely2 = (LY - YS) / TILEY;
1035 // add element corners left and right from the laser beam to damage list
1037 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
1038 AddDamagedField(elx1, ely1);
1040 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
1041 AddDamagedField(elx2, ely2);
1044 for (i = 0; i < 4; i++)
1046 int px = LX + (i % 2) * 2;
1047 int py = LY + (i / 2) * 2;
1048 int dx = px % TILEX;
1049 int dy = py % TILEY;
1050 int lx = getLevelFromLaserX(px);
1051 int ly = getLevelFromLaserY(py);
1054 if (IN_LEV_FIELD(lx, ly))
1056 int element = Tile[lx][ly];
1058 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
1062 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
1064 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1066 pixel = ((element & (1 << pos)) ? 1 : 0);
1070 int pos = getMaskFromElement(element);
1072 pixel = getPixelFromMask(pos, dx, dy);
1077 // check if laser is still inside visible playfield area
1078 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
1079 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
1082 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
1083 hit_mask |= (1 << i);
1086 if (hit_mask == HIT_MASK_NO_HIT)
1088 // hit nothing -- go on with another step
1097 static void DeactivateLaserTargetElement(void)
1099 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1100 laser.dest_element_last == EL_MINE_ACTIVE ||
1101 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1102 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1104 int x = laser.dest_element_last_x;
1105 int y = laser.dest_element_last_y;
1106 int element = laser.dest_element_last;
1108 if (Tile[x][y] == element)
1109 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1110 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1112 if (Tile[x][y] == EL_GRAY_BALL)
1115 laser.dest_element_last = EL_EMPTY;
1116 laser.dest_element_last_x = -1;
1117 laser.dest_element_last_y = -1;
1121 static void ScanLaser(void)
1123 int element = EL_EMPTY;
1124 int last_element = EL_EMPTY;
1125 int end = 0, rf = laser.num_edges;
1127 // do not scan laser again after the game was lost for whatever reason
1128 if (game_mm.game_over)
1131 // do not scan laser if fuse is off
1135 DeactivateLaserTargetElement();
1137 laser.overloaded = FALSE;
1138 laser.stops_inside_element = FALSE;
1140 DrawLaser(0, DL_LASER_ENABLED);
1143 Debug("game:mm:ScanLaser",
1144 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1152 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1155 laser.overloaded = TRUE;
1160 hit_mask = ScanPixel();
1163 Debug("game:mm:ScanLaser",
1164 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1168 // check if laser scan has hit two diagonally adjacent element corners
1169 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1170 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1172 // check if laser scan has crossed element boundaries (not just mini tiles)
1173 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1174 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1176 if (cross_x || cross_y)
1178 // hit something at next tile -- check out what it was
1179 ELX = getLevelFromLaserX(LX + XS);
1180 ELY = getLevelFromLaserY(LY + YS);
1184 // hit something at same tile -- check out what it was
1185 ELX = getLevelFromLaserX(LX);
1186 ELY = getLevelFromLaserY(LY);
1190 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1191 hit_mask, LX, LY, ELX, ELY);
1194 if (!IN_LEV_FIELD(ELX, ELY))
1196 // laser next step position
1197 int x = cSX + LX + XS;
1198 int y = cSY + LY + YS;
1200 // check if next step of laser is still inside visible playfield area
1201 if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
1202 y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
1204 // go on with another step
1212 laser.dest_element = element;
1217 // handle special case of laser hitting two diagonally adjacent elements
1218 // (with or without a third corner element behind these two elements)
1219 if ((diag_1 || diag_2) && cross_x && cross_y)
1221 // compare the two diagonally adjacent elements
1223 int yoffset = 2 * (diag_1 ? -1 : +1);
1224 int elx1 = (LX - xoffset) / TILEX;
1225 int ely1 = (LY + yoffset) / TILEY;
1226 int elx2 = (LX + xoffset) / TILEX;
1227 int ely2 = (LY - yoffset) / TILEY;
1228 int e1 = Tile[elx1][ely1];
1229 int e2 = Tile[elx2][ely2];
1230 boolean use_element_1 = FALSE;
1232 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1234 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1235 use_element_1 = (RND(2) ? TRUE : FALSE);
1236 else if (IS_WALL_ICE(e1))
1237 use_element_1 = TRUE;
1239 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1241 // if both tiles match, we can just select the first one
1242 if (IS_WALL_AMOEBA(e1))
1243 use_element_1 = TRUE;
1245 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1247 // if both tiles match, we can just select the first one
1248 if (IS_ABSORBING_BLOCK(e1))
1249 use_element_1 = TRUE;
1252 ELX = (use_element_1 ? elx1 : elx2);
1253 ELY = (use_element_1 ? ely1 : ely2);
1257 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1258 hit_mask, LX, LY, ELX, ELY);
1261 last_element = element;
1263 element = Tile[ELX][ELY];
1264 laser.dest_element = element;
1267 Debug("game:mm:ScanLaser",
1268 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1271 LX % TILEX, LY % TILEY,
1276 if (!IN_LEV_FIELD(ELX, ELY))
1277 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1281 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1282 if (element == EL_EMPTY &&
1283 IS_GRID_STEEL(last_element) &&
1284 laser.current_angle % 4) // angle is not 90°
1285 element = last_element;
1287 if (element == EL_EMPTY)
1289 if (!HitOnlyAnEdge(hit_mask))
1292 else if (element == EL_FUSE_ON)
1294 if (HitPolarizer(element, hit_mask))
1297 else if (IS_GRID(element) || IS_DF_GRID(element))
1299 if (HitPolarizer(element, hit_mask))
1302 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1303 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1305 if (HitBlock(element, hit_mask))
1312 else if (IS_MCDUFFIN(element))
1314 if (HitLaserSource(element, hit_mask))
1317 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1318 IS_RECEIVER(element))
1320 if (HitLaserDestination(element, hit_mask))
1323 else if (IS_WALL(element))
1325 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1327 if (HitReflectingWalls(element, hit_mask))
1332 if (HitAbsorbingWalls(element, hit_mask))
1338 if (HitElement(element, hit_mask))
1343 DrawLaser(rf - 1, DL_LASER_ENABLED);
1344 rf = laser.num_edges;
1346 if (!IS_DF_WALL_STEEL(element))
1348 // only used for scanning DF steel walls; reset for all other elements
1356 if (laser.dest_element != Tile[ELX][ELY])
1358 Debug("game:mm:ScanLaser",
1359 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1360 laser.dest_element, Tile[ELX][ELY]);
1364 if (!end && !laser.stops_inside_element && !StepBehind())
1367 Debug("game:mm:ScanLaser", "Go one step back");
1373 AddLaserEdge(LX, LY);
1377 DrawLaser(rf - 1, DL_LASER_ENABLED);
1379 Ct = CT = FrameCounter;
1382 if (!IN_LEV_FIELD(ELX, ELY))
1383 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1387 static void ScanLaser_FromLastMirror(void)
1389 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1392 for (i = start_pos; i >= 0; i--)
1393 if (laser.damage[i].is_mirror)
1396 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1398 DrawLaser(start_edge, DL_LASER_DISABLED);
1403 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1409 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1410 start_edge, num_edges, mode);
1415 Warn("DrawLaserExt: start_edge < 0");
1422 Warn("DrawLaserExt: num_edges < 0");
1428 if (mode == DL_LASER_DISABLED)
1430 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1434 // now draw the laser to the backbuffer and (if enabled) to the screen
1435 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1437 redraw_mask |= REDRAW_FIELD;
1439 if (mode == DL_LASER_ENABLED)
1442 // after the laser was deleted, the "damaged" graphics must be restored
1443 if (laser.num_damages)
1445 int damage_start = 0;
1448 // determine the starting edge, from which graphics need to be restored
1451 for (i = 0; i < laser.num_damages; i++)
1453 if (laser.damage[i].edge == start_edge + 1)
1462 // restore graphics from this starting edge to the end of damage list
1463 for (i = damage_start; i < laser.num_damages; i++)
1465 int lx = laser.damage[i].x;
1466 int ly = laser.damage[i].y;
1467 int element = Tile[lx][ly];
1469 if (Hit[lx][ly] == laser.damage[i].edge)
1470 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1473 if (Box[lx][ly] == laser.damage[i].edge)
1476 if (IS_DRAWABLE(element))
1477 DrawField_MM(lx, ly);
1480 elx = laser.damage[damage_start].x;
1481 ely = laser.damage[damage_start].y;
1482 element = Tile[elx][ely];
1485 if (IS_BEAMER(element))
1489 for (i = 0; i < laser.num_beamers; i++)
1490 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1492 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1493 mode, elx, ely, Hit[elx][ely], start_edge);
1494 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1495 get_element_angle(element), laser.damage[damage_start].angle);
1499 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1500 laser.num_beamers > 0 &&
1501 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1503 // element is outgoing beamer
1504 laser.num_damages = damage_start + 1;
1506 if (IS_BEAMER(element))
1507 laser.current_angle = get_element_angle(element);
1511 // element is incoming beamer or other element
1512 laser.num_damages = damage_start;
1513 laser.current_angle = laser.damage[laser.num_damages].angle;
1518 // no damages but McDuffin himself (who needs to be redrawn anyway)
1520 elx = laser.start_edge.x;
1521 ely = laser.start_edge.y;
1522 element = Tile[elx][ely];
1525 laser.num_edges = start_edge + 1;
1526 if (start_edge == 0)
1527 laser.current_angle = laser.start_angle;
1529 LX = laser.edge[start_edge].x - cSX2;
1530 LY = laser.edge[start_edge].y - cSY2;
1531 XS = 2 * Step[laser.current_angle].x;
1532 YS = 2 * Step[laser.current_angle].y;
1535 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1541 if (IS_BEAMER(element) ||
1542 IS_FIBRE_OPTIC(element) ||
1543 IS_PACMAN(element) ||
1544 IS_POLAR(element) ||
1545 IS_POLAR_CROSS(element) ||
1546 element == EL_FUSE_ON)
1551 Debug("game:mm:DrawLaserExt", "element == %d", element);
1554 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1555 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1559 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1560 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1561 (laser.num_beamers == 0 ||
1562 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1564 // element is incoming beamer or other element
1565 step_size = -step_size;
1570 if (IS_BEAMER(element))
1571 Debug("game:mm:DrawLaserExt",
1572 "start_edge == %d, laser.beamer_edge == %d",
1573 start_edge, laser.beamer_edge);
1576 LX += step_size * XS;
1577 LY += step_size * YS;
1579 else if (element != EL_EMPTY)
1588 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1593 void DrawLaser(int start_edge, int mode)
1595 // do not draw laser if fuse is off
1596 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1599 if (mode == DL_LASER_DISABLED)
1600 DeactivateLaserTargetElement();
1602 if (laser.num_edges - start_edge < 0)
1604 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1609 // check if laser is interrupted by beamer element
1610 if (laser.num_beamers > 0 &&
1611 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1613 if (mode == DL_LASER_ENABLED)
1616 int tmp_start_edge = start_edge;
1618 // draw laser segments forward from the start to the last beamer
1619 for (i = 0; i < laser.num_beamers; i++)
1621 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1623 if (tmp_num_edges <= 0)
1627 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1628 i, laser.beamer_edge[i], tmp_start_edge);
1631 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1633 tmp_start_edge = laser.beamer_edge[i];
1636 // draw last segment from last beamer to the end
1637 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1643 int last_num_edges = laser.num_edges;
1644 int num_beamers = laser.num_beamers;
1646 // delete laser segments backward from the end to the first beamer
1647 for (i = num_beamers - 1; i >= 0; i--)
1649 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1651 if (laser.beamer_edge[i] - start_edge <= 0)
1654 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1656 last_num_edges = laser.beamer_edge[i];
1657 laser.num_beamers--;
1661 if (last_num_edges - start_edge <= 0)
1662 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1663 last_num_edges, start_edge);
1666 // special case when rotating first beamer: delete laser edge on beamer
1667 // (but do not start scanning on previous edge to prevent mirror sound)
1668 if (last_num_edges - start_edge == 1 && start_edge > 0)
1669 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1671 // delete first segment from start to the first beamer
1672 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1677 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1680 game_mm.laser_enabled = mode;
1683 void DrawLaser_MM(void)
1685 DrawLaser(0, game_mm.laser_enabled);
1688 static boolean HitElement(int element, int hit_mask)
1690 if (IS_DF_SLOPE(element))
1692 // check if laser scan has crossed element boundaries (not just mini tiles)
1693 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1694 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1695 int element_angle = get_element_angle(element);
1696 int mirrored_angle = get_mirrored_angle(laser.current_angle, element_angle);
1697 int opposite_angle = get_opposite_angle(laser.current_angle);
1699 // check if wall (horizontal or vertical) side of slope was hit
1700 if (hit_mask == HIT_MASK_LEFT ||
1701 hit_mask == HIT_MASK_RIGHT ||
1702 hit_mask == HIT_MASK_TOP ||
1703 hit_mask == HIT_MASK_BOTTOM)
1705 return HitReflectingWalls(element, hit_mask);
1708 // check if an edge was hit while crossing element borders
1709 if (cross_x && cross_y && get_number_of_bits(hit_mask) == 1)
1711 // check both sides of potentially diagonal side of slope
1712 int dx1 = (LX + XS) % TILEX;
1713 int dy1 = (LY + YS) % TILEY;
1714 int dx2 = (LX + XS + 2) % TILEX;
1715 int dy2 = (LY + YS + 2) % TILEY;
1716 int pos = getMaskFromElement(element);
1718 // check if we are entering empty space area after hitting edge
1719 if (!getPixelFromMask(pos, dx1, dy1) &&
1720 !getPixelFromMask(pos, dx2, dy2))
1722 // we already know that we hit an edge, but use this function to go on
1723 if (HitOnlyAnEdge(hit_mask))
1728 // check if laser is reflected by slope by 180°
1729 if (mirrored_angle == opposite_angle)
1731 AddDamagedField(LX / TILEX, LY / TILEY);
1733 laser.overloaded = TRUE;
1740 if (HitOnlyAnEdge(hit_mask))
1744 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1745 element = MovingOrBlocked2Element_MM(ELX, ELY);
1748 Debug("game:mm:HitElement", "(1): element == %d", element);
1752 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1753 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1756 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1760 AddDamagedField(ELX, ELY);
1762 boolean through_center = ((ELX * TILEX + 14 - LX) * YS ==
1763 (ELY * TILEY + 14 - LY) * XS);
1765 // this is more precise: check if laser would go through the center
1766 if (!IS_DF_SLOPE(element) && !through_center)
1770 // prevent cutting through laser emitter with laser beam
1771 if (IS_LASER(element))
1774 // skip the whole element before continuing the scan
1782 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1784 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1786 /* skipping scan positions to the right and down skips one scan
1787 position too much, because this is only the top left scan position
1788 of totally four scan positions (plus one to the right, one to the
1789 bottom and one to the bottom right) */
1790 /* ... but only roll back scan position if more than one step done */
1800 Debug("game:mm:HitElement", "(2): element == %d", element);
1803 if (LX + 5 * XS < 0 ||
1813 Debug("game:mm:HitElement", "(3): element == %d", element);
1816 if (IS_POLAR(element) &&
1817 ((element - EL_POLAR_START) % 2 ||
1818 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1820 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1822 laser.num_damages--;
1827 if (IS_POLAR_CROSS(element) &&
1828 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1830 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1832 laser.num_damages--;
1837 if (IS_DF_SLOPE(element) && !through_center)
1841 if (hit_mask == HIT_MASK_ALL)
1843 // laser already inside slope -- go back half step
1850 AddLaserEdge(LX, LY);
1852 LX -= (ABS(XS) < ABS(YS) ? correction * SIGN(XS) : 0);
1853 LY -= (ABS(YS) < ABS(XS) ? correction * SIGN(YS) : 0);
1855 else if (!IS_BEAMER(element) &&
1856 !IS_FIBRE_OPTIC(element) &&
1857 !IS_GRID_WOOD(element) &&
1858 element != EL_FUEL_EMPTY)
1861 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1862 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1864 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1867 LX = ELX * TILEX + 14;
1868 LY = ELY * TILEY + 14;
1870 AddLaserEdge(LX, LY);
1873 if (IS_MIRROR(element) ||
1874 IS_MIRROR_FIXED(element) ||
1875 IS_POLAR(element) ||
1876 IS_POLAR_CROSS(element) ||
1877 IS_DF_MIRROR(element) ||
1878 IS_DF_MIRROR_AUTO(element) ||
1879 IS_DF_MIRROR_FIXED(element) ||
1880 IS_DF_SLOPE(element) ||
1881 element == EL_PRISM ||
1882 element == EL_REFRACTOR)
1884 int current_angle = laser.current_angle;
1887 laser.num_damages--;
1889 AddDamagedField(ELX, ELY);
1891 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1894 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1896 if (IS_MIRROR(element) ||
1897 IS_MIRROR_FIXED(element) ||
1898 IS_DF_MIRROR(element) ||
1899 IS_DF_MIRROR_AUTO(element) ||
1900 IS_DF_MIRROR_FIXED(element) ||
1901 IS_DF_SLOPE(element))
1902 laser.current_angle = get_mirrored_angle(laser.current_angle,
1903 get_element_angle(element));
1905 if (element == EL_PRISM || element == EL_REFRACTOR)
1906 laser.current_angle = RND(16);
1908 XS = 2 * Step[laser.current_angle].x;
1909 YS = 2 * Step[laser.current_angle].y;
1913 // start from center position for all game elements but slope
1914 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1919 LX += step_size * XS;
1920 LY += step_size * YS;
1924 // advance laser position until reaching the next tile (slopes)
1925 while (LX / TILEX == ELX && (LX + 2) / TILEX == ELX &&
1926 LY / TILEY == ELY && (LY + 2) / TILEY == ELY)
1933 // draw sparkles on mirror
1934 if ((IS_MIRROR(element) ||
1935 IS_MIRROR_FIXED(element) ||
1936 element == EL_PRISM) &&
1937 current_angle != laser.current_angle)
1939 MovDelay[ELX][ELY] = 11; // start animation
1942 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1943 current_angle != laser.current_angle)
1944 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1947 (get_opposite_angle(laser.current_angle) ==
1948 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1950 if (IS_DF_SLOPE(element))
1952 // handle special cases for slope element
1954 if (IS_45_ANGLE(laser.current_angle))
1958 elx = getLevelFromLaserX(LX + XS);
1959 ely = getLevelFromLaserY(LY + YS);
1961 if (IN_LEV_FIELD(elx, ely))
1963 int element_next = Tile[elx][ely];
1965 // check if slope is followed by slope with opposite orientation
1966 if (IS_DF_SLOPE(element_next) && ABS(element - element_next) == 2)
1967 laser.overloaded = TRUE;
1970 int nr = element - EL_DF_SLOPE_START;
1971 int dx = (nr == 0 ? (XS > 0 ? TILEX - 1 : -1) :
1972 nr == 1 ? (XS > 0 ? TILEX : 0) :
1973 nr == 2 ? (XS > 0 ? TILEX : 0) :
1974 nr == 3 ? (XS > 0 ? TILEX - 1 : -1) : 0);
1975 int dy = (nr == 0 ? (YS > 0 ? TILEY - 1 : -1) :
1976 nr == 1 ? (YS > 0 ? TILEY - 1 : -1) :
1977 nr == 2 ? (YS > 0 ? TILEY : 0) :
1978 nr == 3 ? (YS > 0 ? TILEY : 0) : 0);
1980 int px = ELX * TILEX + dx;
1981 int py = ELY * TILEY + dy;
1986 elx = getLevelFromLaserX(px);
1987 ely = getLevelFromLaserY(py);
1989 if (IN_LEV_FIELD(elx, ely))
1991 int element_side = Tile[elx][ely];
1993 // check if end of slope is blocked by other element
1994 if (IS_WALL(element_side) || IS_WALL_CHANGING(element_side))
1996 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
1998 if (element & (1 << pos))
1999 laser.overloaded = TRUE;
2003 int pos = getMaskFromElement(element_side);
2005 if (getPixelFromMask(pos, dx, dy))
2006 laser.overloaded = TRUE;
2012 return (laser.overloaded ? TRUE : FALSE);
2015 if (element == EL_FUEL_FULL)
2017 laser.stops_inside_element = TRUE;
2022 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
2024 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2026 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
2027 element == EL_MINE ? EL_MINE_ACTIVE :
2028 EL_GRAY_BALL_ACTIVE);
2030 GfxFrame[ELX][ELY] = 0; // restart animation
2032 laser.dest_element_last = Tile[ELX][ELY];
2033 laser.dest_element_last_x = ELX;
2034 laser.dest_element_last_y = ELY;
2036 if (element == EL_MINE)
2037 laser.overloaded = TRUE;
2040 if (element == EL_KETTLE ||
2041 element == EL_CELL ||
2042 element == EL_KEY ||
2043 element == EL_LIGHTBALL ||
2044 element == EL_PACMAN ||
2045 IS_PACMAN(element) ||
2046 IS_ENVELOPE(element))
2048 if (!IS_PACMAN(element) &&
2049 !IS_ENVELOPE(element))
2052 if (element == EL_PACMAN)
2055 if (element == EL_KETTLE || element == EL_CELL)
2057 if (game_mm.kettles_still_needed > 0)
2058 game_mm.kettles_still_needed--;
2060 game.snapshot.collected_item = TRUE;
2062 if (game_mm.kettles_still_needed == 0)
2066 DrawLaser(0, DL_LASER_ENABLED);
2069 else if (element == EL_KEY)
2073 else if (IS_PACMAN(element))
2075 DeletePacMan(ELX, ELY);
2077 else if (IS_ENVELOPE(element))
2079 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
2082 RaiseScoreElement_MM(element);
2087 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
2089 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2091 DrawLaser(0, DL_LASER_ENABLED);
2093 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
2095 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
2096 game_mm.lights_still_needed--;
2100 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
2101 game_mm.lights_still_needed++;
2104 DrawField_MM(ELX, ELY);
2105 DrawLaser(0, DL_LASER_ENABLED);
2110 laser.stops_inside_element = TRUE;
2116 Debug("game:mm:HitElement", "(4): element == %d", element);
2119 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
2120 laser.num_beamers < MAX_NUM_BEAMERS &&
2121 laser.beamer[BEAMER_NR(element)][1].num)
2123 int beamer_angle = get_element_angle(element);
2124 int beamer_nr = BEAMER_NR(element);
2128 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
2131 laser.num_damages--;
2133 if (IS_FIBRE_OPTIC(element) ||
2134 laser.current_angle == get_opposite_angle(beamer_angle))
2138 LX = ELX * TILEX + 14;
2139 LY = ELY * TILEY + 14;
2141 AddLaserEdge(LX, LY);
2142 AddDamagedField(ELX, ELY);
2144 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2147 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2149 pos = (ELX == laser.beamer[beamer_nr][0].x &&
2150 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
2151 ELX = laser.beamer[beamer_nr][pos].x;
2152 ELY = laser.beamer[beamer_nr][pos].y;
2153 LX = ELX * TILEX + 14;
2154 LY = ELY * TILEY + 14;
2156 if (IS_BEAMER(element))
2158 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
2159 XS = 2 * Step[laser.current_angle].x;
2160 YS = 2 * Step[laser.current_angle].y;
2163 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
2165 AddLaserEdge(LX, LY);
2166 AddDamagedField(ELX, ELY);
2168 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
2171 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2173 if (laser.current_angle == (laser.current_angle >> 1) << 1)
2178 LX += step_size * XS;
2179 LY += step_size * YS;
2181 laser.num_beamers++;
2190 static boolean HitOnlyAnEdge(int hit_mask)
2192 // check if the laser hit only the edge of an element and, if so, go on
2195 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
2199 if ((hit_mask == HIT_MASK_TOPLEFT ||
2200 hit_mask == HIT_MASK_TOPRIGHT ||
2201 hit_mask == HIT_MASK_BOTTOMLEFT ||
2202 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
2203 laser.current_angle % 4) // angle is not 90°
2207 if (hit_mask == HIT_MASK_TOPLEFT)
2212 else if (hit_mask == HIT_MASK_TOPRIGHT)
2217 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
2222 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
2228 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
2234 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
2241 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
2247 static boolean HitPolarizer(int element, int hit_mask)
2249 if (HitOnlyAnEdge(hit_mask))
2252 if (IS_DF_GRID(element))
2254 int grid_angle = get_element_angle(element);
2257 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2258 grid_angle, laser.current_angle);
2261 AddLaserEdge(LX, LY);
2262 AddDamagedField(ELX, ELY);
2265 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2267 if (laser.current_angle == grid_angle ||
2268 laser.current_angle == get_opposite_angle(grid_angle))
2270 // skip the whole element before continuing the scan
2276 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2278 if (LX/TILEX > ELX || LY/TILEY > ELY)
2280 /* skipping scan positions to the right and down skips one scan
2281 position too much, because this is only the top left scan position
2282 of totally four scan positions (plus one to the right, one to the
2283 bottom and one to the bottom right) */
2289 AddLaserEdge(LX, LY);
2295 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2297 LX / TILEX, LY / TILEY,
2298 LX % TILEX, LY % TILEY);
2303 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2305 return HitReflectingWalls(element, hit_mask);
2309 return HitAbsorbingWalls(element, hit_mask);
2312 else if (IS_GRID_STEEL(element))
2314 // may be required if graphics for steel grid redefined
2315 AddDamagedField(ELX, ELY);
2317 return HitReflectingWalls(element, hit_mask);
2319 else // IS_GRID_WOOD
2321 // may be required if graphics for wooden grid redefined
2322 AddDamagedField(ELX, ELY);
2324 return HitAbsorbingWalls(element, hit_mask);
2330 static boolean HitBlock(int element, int hit_mask)
2332 boolean check = FALSE;
2334 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2335 game_mm.num_keys == 0)
2338 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2341 int ex = ELX * TILEX + 14;
2342 int ey = ELY * TILEY + 14;
2346 for (i = 1; i < 32; i++)
2351 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2356 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2357 return HitAbsorbingWalls(element, hit_mask);
2361 AddLaserEdge(LX - XS, LY - YS);
2362 AddDamagedField(ELX, ELY);
2365 Box[ELX][ELY] = laser.num_edges;
2367 return HitReflectingWalls(element, hit_mask);
2370 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2372 int xs = XS / 2, ys = YS / 2;
2374 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2375 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2377 laser.overloaded = (element == EL_GATE_STONE);
2382 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2383 (hit_mask == HIT_MASK_TOP ||
2384 hit_mask == HIT_MASK_LEFT ||
2385 hit_mask == HIT_MASK_RIGHT ||
2386 hit_mask == HIT_MASK_BOTTOM))
2387 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2388 hit_mask == HIT_MASK_BOTTOM),
2389 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2390 hit_mask == HIT_MASK_RIGHT));
2391 AddLaserEdge(LX, LY);
2397 if (element == EL_GATE_STONE && Box[ELX][ELY])
2399 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2411 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2413 int xs = XS / 2, ys = YS / 2;
2415 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2416 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2418 laser.overloaded = (element == EL_BLOCK_STONE);
2423 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2424 (hit_mask == HIT_MASK_TOP ||
2425 hit_mask == HIT_MASK_LEFT ||
2426 hit_mask == HIT_MASK_RIGHT ||
2427 hit_mask == HIT_MASK_BOTTOM))
2428 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2429 hit_mask == HIT_MASK_BOTTOM),
2430 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2431 hit_mask == HIT_MASK_RIGHT));
2432 AddDamagedField(ELX, ELY);
2434 LX = ELX * TILEX + 14;
2435 LY = ELY * TILEY + 14;
2437 AddLaserEdge(LX, LY);
2439 laser.stops_inside_element = TRUE;
2447 static boolean HitLaserSource(int element, int hit_mask)
2449 if (HitOnlyAnEdge(hit_mask))
2452 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2454 laser.overloaded = TRUE;
2459 static boolean HitLaserDestination(int element, int hit_mask)
2461 if (HitOnlyAnEdge(hit_mask))
2464 if (element != EL_EXIT_OPEN &&
2465 !(IS_RECEIVER(element) &&
2466 game_mm.kettles_still_needed == 0 &&
2467 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2469 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2474 if (IS_RECEIVER(element) ||
2475 (IS_22_5_ANGLE(laser.current_angle) &&
2476 (ELX != (LX + 6 * XS) / TILEX ||
2477 ELY != (LY + 6 * YS) / TILEY ||
2486 LX = ELX * TILEX + 14;
2487 LY = ELY * TILEY + 14;
2489 laser.stops_inside_element = TRUE;
2492 AddLaserEdge(LX, LY);
2493 AddDamagedField(ELX, ELY);
2495 if (game_mm.lights_still_needed == 0)
2497 game_mm.level_solved = TRUE;
2499 SetTileCursorActive(FALSE);
2505 static boolean HitReflectingWalls(int element, int hit_mask)
2507 // check if laser hits side of a wall with an angle that is not 90°
2508 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2509 hit_mask == HIT_MASK_LEFT ||
2510 hit_mask == HIT_MASK_RIGHT ||
2511 hit_mask == HIT_MASK_BOTTOM))
2513 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2518 if (!IS_DF_GRID(element))
2519 AddLaserEdge(LX, LY);
2521 // check if laser hits wall with an angle of 45°
2522 if (!IS_22_5_ANGLE(laser.current_angle))
2524 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2527 laser.current_angle = get_mirrored_angle(laser.current_angle,
2530 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2533 laser.current_angle = get_mirrored_angle(laser.current_angle,
2537 AddLaserEdge(LX, LY);
2539 XS = 2 * Step[laser.current_angle].x;
2540 YS = 2 * Step[laser.current_angle].y;
2544 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2546 laser.current_angle = get_mirrored_angle(laser.current_angle,
2551 if (!IS_DF_GRID(element))
2552 AddLaserEdge(LX, LY);
2557 if (!IS_DF_GRID(element))
2558 AddLaserEdge(LX, LY + YS / 2);
2561 if (!IS_DF_GRID(element))
2562 AddLaserEdge(LX, LY);
2565 YS = 2 * Step[laser.current_angle].y;
2569 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2571 laser.current_angle = get_mirrored_angle(laser.current_angle,
2576 if (!IS_DF_GRID(element))
2577 AddLaserEdge(LX, LY);
2582 if (!IS_DF_GRID(element))
2583 AddLaserEdge(LX + XS / 2, LY);
2586 if (!IS_DF_GRID(element))
2587 AddLaserEdge(LX, LY);
2590 XS = 2 * Step[laser.current_angle].x;
2596 // reflection at the edge of reflecting DF style wall
2597 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2599 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2600 hit_mask == HIT_MASK_TOPRIGHT) ||
2601 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2602 hit_mask == HIT_MASK_TOPLEFT) ||
2603 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2604 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2605 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2606 hit_mask == HIT_MASK_BOTTOMRIGHT))
2609 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2610 ANG_MIRROR_135 : ANG_MIRROR_45);
2612 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2614 AddDamagedField(ELX, ELY);
2615 AddLaserEdge(LX, LY);
2617 laser.current_angle = get_mirrored_angle(laser.current_angle,
2625 AddLaserEdge(LX, LY);
2631 // reflection inside an edge of reflecting DF style wall
2632 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2634 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2635 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2636 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2637 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2638 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2639 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2640 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2641 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2644 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2645 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2646 ANG_MIRROR_135 : ANG_MIRROR_45);
2648 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2651 AddDamagedField(ELX, ELY);
2654 AddLaserEdge(LX - XS, LY - YS);
2655 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2656 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2658 laser.current_angle = get_mirrored_angle(laser.current_angle,
2666 AddLaserEdge(LX, LY);
2672 // check if laser hits DF style wall with an angle of 90°
2673 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2675 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2676 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2677 (IS_VERT_ANGLE(laser.current_angle) &&
2678 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2680 // laser at last step touched nothing or the same side of the wall
2681 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2683 AddDamagedField(ELX, ELY);
2690 last_hit_mask = hit_mask;
2697 if (!HitOnlyAnEdge(hit_mask))
2699 laser.overloaded = TRUE;
2707 static boolean HitAbsorbingWalls(int element, int hit_mask)
2709 if (HitOnlyAnEdge(hit_mask))
2713 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2715 AddLaserEdge(LX - XS, LY - YS);
2722 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2724 AddLaserEdge(LX - XS, LY - YS);
2730 if (IS_WALL_WOOD(element) ||
2731 IS_DF_WALL_WOOD(element) ||
2732 IS_GRID_WOOD(element) ||
2733 IS_GRID_WOOD_FIXED(element) ||
2734 IS_GRID_WOOD_AUTO(element) ||
2735 element == EL_FUSE_ON ||
2736 element == EL_BLOCK_WOOD ||
2737 element == EL_GATE_WOOD)
2739 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2744 if (IS_WALL_ICE(element))
2750 // check if laser hit adjacent edges of two diagonal tiles
2751 if (ELX != lx / TILEX)
2753 if (ELY != ly / TILEY)
2756 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2757 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2759 // check if laser hits wall with an angle of 90°
2760 if (IS_90_ANGLE(laser.current_angle))
2761 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2763 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2767 for (i = 0; i < 4; i++)
2769 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2770 mask = 15 - (8 >> i);
2771 else if (ABS(XS) == 4 &&
2773 (XS > 0) == (i % 2) &&
2774 (YS < 0) == (i / 2))
2775 mask = 3 + (i / 2) * 9;
2776 else if (ABS(YS) == 4 &&
2778 (XS < 0) == (i % 2) &&
2779 (YS > 0) == (i / 2))
2780 mask = 5 + (i % 2) * 5;
2784 laser.wall_mask = mask;
2786 else if (IS_WALL_AMOEBA(element))
2788 int elx = (LX - 2 * XS) / TILEX;
2789 int ely = (LY - 2 * YS) / TILEY;
2790 int element2 = Tile[elx][ely];
2793 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2795 laser.dest_element = EL_EMPTY;
2803 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2804 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2806 if (IS_90_ANGLE(laser.current_angle))
2807 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2809 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2811 laser.wall_mask = mask;
2817 static void OpenExit(int x, int y)
2821 if (!MovDelay[x][y]) // next animation frame
2822 MovDelay[x][y] = 4 * delay;
2824 if (MovDelay[x][y]) // wait some time before next frame
2829 phase = MovDelay[x][y] / delay;
2831 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2832 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2834 if (!MovDelay[x][y])
2836 Tile[x][y] = EL_EXIT_OPEN;
2842 static void OpenGrayBall(int x, int y)
2846 if (!MovDelay[x][y]) // next animation frame
2848 if (IS_WALL(Store[x][y]))
2850 DrawWalls_MM(x, y, Store[x][y]);
2852 // copy wall tile to spare bitmap for "melting" animation
2853 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2854 TILEX, TILEY, x * TILEX, y * TILEY);
2856 DrawElement_MM(x, y, EL_GRAY_BALL);
2859 MovDelay[x][y] = 50 * delay;
2862 if (MovDelay[x][y]) // wait some time before next frame
2866 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2870 int dx = RND(26), dy = RND(26);
2872 if (IS_WALL(Store[x][y]))
2874 // copy wall tile from spare bitmap for "melting" animation
2875 bitmap = bitmap_db_field;
2881 int graphic = el2gfx(Store[x][y]);
2883 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2886 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2887 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2889 laser.redraw = TRUE;
2891 MarkTileDirty(x, y);
2894 if (!MovDelay[x][y])
2896 Tile[x][y] = Store[x][y];
2897 Store[x][y] = Store2[x][y] = 0;
2898 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2900 InitField(x, y, FALSE);
2903 ScanLaser_FromLastMirror();
2908 static void OpenEnvelope(int x, int y)
2910 int num_frames = 8; // seven frames plus final empty space
2912 if (!MovDelay[x][y]) // next animation frame
2913 MovDelay[x][y] = num_frames;
2915 if (MovDelay[x][y]) // wait some time before next frame
2917 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2921 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2923 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2924 int frame = num_frames - MovDelay[x][y] - 1;
2926 DrawGraphicAnimation_MM(x, y, graphic, frame);
2928 laser.redraw = TRUE;
2931 if (MovDelay[x][y] == 0)
2933 Tile[x][y] = EL_EMPTY;
2944 static void MeltIce(int x, int y)
2949 if (!MovDelay[x][y]) // next animation frame
2950 MovDelay[x][y] = frames * delay;
2952 if (MovDelay[x][y]) // wait some time before next frame
2955 int wall_mask = Store2[x][y];
2956 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2959 phase = frames - MovDelay[x][y] / delay - 1;
2961 if (!MovDelay[x][y])
2963 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2964 Store[x][y] = Store2[x][y] = 0;
2966 DrawWalls_MM(x, y, Tile[x][y]);
2968 if (Tile[x][y] == EL_WALL_ICE_BASE)
2969 Tile[x][y] = EL_EMPTY;
2971 ScanLaser_FromLastMirror();
2973 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2975 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2977 laser.redraw = TRUE;
2982 static void GrowAmoeba(int x, int y)
2987 if (!MovDelay[x][y]) // next animation frame
2988 MovDelay[x][y] = frames * delay;
2990 if (MovDelay[x][y]) // wait some time before next frame
2993 int wall_mask = Store2[x][y];
2994 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2997 phase = MovDelay[x][y] / delay;
2999 if (!MovDelay[x][y])
3001 Tile[x][y] = real_element;
3002 Store[x][y] = Store2[x][y] = 0;
3004 DrawWalls_MM(x, y, Tile[x][y]);
3005 DrawLaser(0, DL_LASER_ENABLED);
3007 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
3009 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
3014 static void DrawFieldAnimated_MM(int x, int y)
3018 laser.redraw = TRUE;
3021 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
3023 int element = Tile[x][y];
3024 int graphic = el2gfx(element);
3026 if (!getGraphicInfo_NewFrame(x, y, graphic))
3031 laser.redraw = TRUE;
3034 static void DrawFieldTwinkle(int x, int y)
3036 if (MovDelay[x][y] != 0) // wait some time before next frame
3042 if (MovDelay[x][y] != 0)
3044 int graphic = IMG_TWINKLE_WHITE;
3045 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
3047 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
3050 laser.redraw = TRUE;
3054 static void Explode_MM(int x, int y, int phase, int mode)
3056 int num_phase = 9, delay = 2;
3057 int last_phase = num_phase * delay;
3058 int half_phase = (num_phase / 2) * delay;
3061 laser.redraw = TRUE;
3063 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
3065 center_element = Tile[x][y];
3067 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
3069 // put moving element to center field (and let it explode there)
3070 center_element = MovingOrBlocked2Element_MM(x, y);
3071 RemoveMovingField_MM(x, y);
3073 Tile[x][y] = center_element;
3076 if (center_element != EL_GRAY_BALL_ACTIVE)
3077 Store[x][y] = EL_EMPTY;
3078 Store2[x][y] = center_element;
3080 Tile[x][y] = EL_EXPLODING_OPAQUE;
3082 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
3083 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
3084 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
3087 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3089 ExplodePhase[x][y] = 1;
3095 GfxFrame[x][y] = 0; // restart explosion animation
3097 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
3099 center_element = Store2[x][y];
3101 if (phase == half_phase && Store[x][y] == EL_EMPTY)
3103 Tile[x][y] = EL_EXPLODING_TRANSP;
3105 if (x == ELX && y == ELY)
3109 if (phase == last_phase)
3111 if (center_element == EL_BOMB_ACTIVE)
3113 DrawLaser(0, DL_LASER_DISABLED);
3116 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3118 laser.overloaded = FALSE;
3120 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
3122 GameOver_MM(GAME_OVER_BOMB);
3125 Tile[x][y] = Store[x][y];
3127 Store[x][y] = Store2[x][y] = 0;
3128 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
3130 InitField(x, y, FALSE);
3133 if (center_element == EL_GRAY_BALL_ACTIVE)
3134 ScanLaser_FromLastMirror();
3136 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
3138 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
3139 int frame = getGraphicAnimationFrameXY(graphic, x, y);
3141 DrawGraphicAnimation_MM(x, y, graphic, frame);
3143 MarkTileDirty(x, y);
3147 static void Bang_MM(int x, int y)
3149 int element = Tile[x][y];
3151 if (IS_PACMAN(element))
3152 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3153 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
3154 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3155 else if (element == EL_KEY)
3156 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3158 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
3160 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
3163 static void TurnRound(int x, int y)
3175 { 0, 0 }, { 0, 0 }, { 0, 0 },
3180 int left, right, back;
3184 { MV_DOWN, MV_UP, MV_RIGHT },
3185 { MV_UP, MV_DOWN, MV_LEFT },
3187 { MV_LEFT, MV_RIGHT, MV_DOWN },
3191 { MV_RIGHT, MV_LEFT, MV_UP }
3194 int element = Tile[x][y];
3195 int old_move_dir = MovDir[x][y];
3196 int right_dir = turn[old_move_dir].right;
3197 int back_dir = turn[old_move_dir].back;
3198 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
3199 int right_x = x + right_dx, right_y = y + right_dy;
3201 if (element == EL_PACMAN)
3203 boolean can_turn_right = FALSE;
3205 if (IN_LEV_FIELD(right_x, right_y) &&
3206 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
3207 can_turn_right = TRUE;
3210 MovDir[x][y] = right_dir;
3212 MovDir[x][y] = back_dir;
3218 static void StartMoving_MM(int x, int y)
3220 int element = Tile[x][y];
3225 if (CAN_MOVE(element))
3229 if (MovDelay[x][y]) // wait some time before next movement
3237 // now make next step
3239 Moving2Blocked(x, y, &newx, &newy); // get next screen position
3241 if (element == EL_PACMAN &&
3242 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
3243 !ObjHit(newx, newy, HIT_POS_CENTER))
3245 Store[newx][newy] = Tile[newx][newy];
3246 Tile[newx][newy] = EL_EMPTY;
3248 DrawField_MM(newx, newy);
3250 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3251 ObjHit(newx, newy, HIT_POS_CENTER))
3253 // object was running against a wall
3260 InitMovingField_MM(x, y, MovDir[x][y]);
3264 ContinueMoving_MM(x, y);
3267 static void ContinueMoving_MM(int x, int y)
3269 int element = Tile[x][y];
3270 int direction = MovDir[x][y];
3271 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3272 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3273 int horiz_move = (dx!=0);
3274 int newx = x + dx, newy = y + dy;
3275 int step = (horiz_move ? dx : dy) * TILEX / 8;
3277 MovPos[x][y] += step;
3279 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3281 Tile[x][y] = EL_EMPTY;
3282 Tile[newx][newy] = element;
3284 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3285 MovDelay[newx][newy] = 0;
3287 if (!CAN_MOVE(element))
3288 MovDir[newx][newy] = 0;
3291 DrawField_MM(newx, newy);
3293 Stop[newx][newy] = TRUE;
3295 if (element == EL_PACMAN)
3297 if (Store[newx][newy] == EL_BOMB)
3298 Bang_MM(newx, newy);
3300 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3301 (LX + 2 * XS) / TILEX == newx &&
3302 (LY + 2 * YS) / TILEY == newy)
3309 else // still moving on
3314 laser.redraw = TRUE;
3317 boolean ClickElement(int x, int y, int button)
3319 static DelayCounter click_delay = { CLICK_DELAY };
3320 static boolean new_button = TRUE;
3321 boolean element_clicked = FALSE;
3326 // initialize static variables
3327 click_delay.count = 0;
3328 click_delay.value = CLICK_DELAY;
3334 // do not rotate objects hit by the laser after the game was solved
3335 if (game_mm.level_solved && Hit[x][y])
3338 if (button == MB_RELEASED)
3341 click_delay.value = CLICK_DELAY;
3343 // release eventually hold auto-rotating mirror
3344 RotateMirror(x, y, MB_RELEASED);
3349 if (!FrameReached(&click_delay) && !new_button)
3352 if (button == MB_MIDDLEBUTTON) // middle button has no function
3355 if (!IN_LEV_FIELD(x, y))
3358 if (Tile[x][y] == EL_EMPTY)
3361 element = Tile[x][y];
3363 if (IS_MIRROR(element) ||
3364 IS_BEAMER(element) ||
3365 IS_POLAR(element) ||
3366 IS_POLAR_CROSS(element) ||
3367 IS_DF_MIRROR(element) ||
3368 IS_DF_MIRROR_AUTO(element))
3370 RotateMirror(x, y, button);
3372 element_clicked = TRUE;
3374 else if (IS_MCDUFFIN(element))
3376 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3378 if (has_laser && !laser.fuse_off)
3379 DrawLaser(0, DL_LASER_DISABLED);
3381 element = get_rotated_element(element, BUTTON_ROTATION(button));
3383 Tile[x][y] = element;
3388 laser.start_angle = get_element_angle(element);
3392 if (!laser.fuse_off)
3396 element_clicked = TRUE;
3398 else if (element == EL_FUSE_ON && laser.fuse_off)
3400 if (x != laser.fuse_x || y != laser.fuse_y)
3403 laser.fuse_off = FALSE;
3404 laser.fuse_x = laser.fuse_y = -1;
3406 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3409 element_clicked = TRUE;
3411 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3413 laser.fuse_off = TRUE;
3416 laser.overloaded = FALSE;
3418 DrawLaser(0, DL_LASER_DISABLED);
3419 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3421 element_clicked = TRUE;
3423 else if (element == EL_LIGHTBALL)
3426 RaiseScoreElement_MM(element);
3427 DrawLaser(0, DL_LASER_ENABLED);
3429 element_clicked = TRUE;
3431 else if (IS_ENVELOPE(element))
3433 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3435 element_clicked = TRUE;
3438 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3441 return element_clicked;
3444 static void RotateMirror(int x, int y, int button)
3446 if (button == MB_RELEASED)
3448 // release eventually hold auto-rotating mirror
3455 if (IS_MIRROR(Tile[x][y]) ||
3456 IS_POLAR_CROSS(Tile[x][y]) ||
3457 IS_POLAR(Tile[x][y]) ||
3458 IS_BEAMER(Tile[x][y]) ||
3459 IS_DF_MIRROR(Tile[x][y]) ||
3460 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3461 IS_GRID_WOOD_AUTO(Tile[x][y]))
3463 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3465 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3467 if (button == MB_LEFTBUTTON)
3469 // left mouse button only for manual adjustment, no auto-rotating;
3470 // freeze mirror for until mouse button released
3474 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3476 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3480 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3482 int edge = Hit[x][y];
3488 DrawLaser(edge - 1, DL_LASER_DISABLED);
3492 else if (ObjHit(x, y, HIT_POS_CENTER))
3494 int edge = Hit[x][y];
3498 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3503 DrawLaser(edge - 1, DL_LASER_DISABLED);
3510 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3515 if ((IS_BEAMER(Tile[x][y]) ||
3516 IS_POLAR(Tile[x][y]) ||
3517 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3519 if (IS_BEAMER(Tile[x][y]))
3522 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3523 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3536 DrawLaser(0, DL_LASER_ENABLED);
3540 static void AutoRotateMirrors(void)
3544 if (!FrameReached(&rotate_delay))
3547 for (x = 0; x < lev_fieldx; x++)
3549 for (y = 0; y < lev_fieldy; y++)
3551 int element = Tile[x][y];
3553 // do not rotate objects hit by the laser after the game was solved
3554 if (game_mm.level_solved && Hit[x][y])
3557 if (IS_DF_MIRROR_AUTO(element) ||
3558 IS_GRID_WOOD_AUTO(element) ||
3559 IS_GRID_STEEL_AUTO(element) ||
3560 element == EL_REFRACTOR)
3562 RotateMirror(x, y, MB_RIGHTBUTTON);
3564 laser.redraw = TRUE;
3570 static boolean ObjHit(int obx, int oby, int bits)
3577 if (bits & HIT_POS_CENTER)
3579 if (CheckLaserPixel(cSX + obx + 15,
3584 if (bits & HIT_POS_EDGE)
3586 for (i = 0; i < 4; i++)
3587 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3588 cSY + oby + 31 * (i / 2)))
3592 if (bits & HIT_POS_BETWEEN)
3594 for (i = 0; i < 4; i++)
3595 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3596 cSY + 4 + oby + 22 * (i / 2)))
3603 static void DeletePacMan(int px, int py)
3609 if (game_mm.num_pacman <= 1)
3611 game_mm.num_pacman = 0;
3615 for (i = 0; i < game_mm.num_pacman; i++)
3616 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3619 game_mm.num_pacman--;
3621 for (j = i; j < game_mm.num_pacman; j++)
3623 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3624 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3625 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3629 static void GameActions_MM_Ext(void)
3636 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3639 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3641 element = Tile[x][y];
3643 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3644 StartMoving_MM(x, y);
3645 else if (IS_MOVING(x, y))
3646 ContinueMoving_MM(x, y);
3647 else if (IS_EXPLODING(element))
3648 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3649 else if (element == EL_EXIT_OPENING)
3651 else if (element == EL_GRAY_BALL_OPENING)
3653 else if (IS_ENVELOPE_OPENING(element))
3655 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3657 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3659 else if (IS_MIRROR(element) ||
3660 IS_MIRROR_FIXED(element) ||
3661 element == EL_PRISM)
3662 DrawFieldTwinkle(x, y);
3663 else if (element == EL_GRAY_BALL_ACTIVE ||
3664 element == EL_BOMB_ACTIVE ||
3665 element == EL_MINE_ACTIVE)
3666 DrawFieldAnimated_MM(x, y);
3667 else if (!IS_BLOCKED(x, y))
3668 DrawFieldAnimatedIfNeeded_MM(x, y);
3671 AutoRotateMirrors();
3674 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3676 // redraw after Explode_MM() ...
3678 DrawLaser(0, DL_LASER_ENABLED);
3679 laser.redraw = FALSE;
3684 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3688 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3690 DrawLaser(0, DL_LASER_DISABLED);
3695 // skip all following game actions if game is over
3696 if (game_mm.game_over)
3699 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3703 GameOver_MM(GAME_OVER_NO_ENERGY);
3708 if (FrameReached(&energy_delay))
3710 if (game_mm.energy_left > 0)
3711 game_mm.energy_left--;
3713 // when out of energy, wait another frame to play "out of time" sound
3716 element = laser.dest_element;
3719 if (element != Tile[ELX][ELY])
3721 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3722 element, Tile[ELX][ELY]);
3726 if (!laser.overloaded && laser.overload_value == 0 &&
3727 element != EL_BOMB &&
3728 element != EL_BOMB_ACTIVE &&
3729 element != EL_MINE &&
3730 element != EL_MINE_ACTIVE &&
3731 element != EL_GRAY_BALL &&
3732 element != EL_GRAY_BALL_ACTIVE &&
3733 element != EL_BLOCK_STONE &&
3734 element != EL_BLOCK_WOOD &&
3735 element != EL_FUSE_ON &&
3736 element != EL_FUEL_FULL &&
3737 !IS_WALL_ICE(element) &&
3738 !IS_WALL_AMOEBA(element))
3741 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3743 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3744 (!laser.overloaded && laser.overload_value > 0)) &&
3745 FrameReached(&overload_delay))
3747 if (laser.overloaded)
3748 laser.overload_value++;
3750 laser.overload_value--;
3752 if (game_mm.cheat_no_overload)
3754 laser.overloaded = FALSE;
3755 laser.overload_value = 0;
3758 game_mm.laser_overload_value = laser.overload_value;
3760 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3762 SetLaserColor(0xFF);
3764 DrawLaser(0, DL_LASER_ENABLED);
3767 if (!laser.overloaded)
3768 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3769 else if (setup.sound_loops)
3770 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3772 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3774 if (laser.overload_value == MAX_LASER_OVERLOAD)
3776 UpdateAndDisplayGameControlValues();
3780 GameOver_MM(GAME_OVER_OVERLOADED);
3791 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3793 if (game_mm.cheat_no_explosion)
3798 laser.dest_element = EL_EXPLODING_OPAQUE;
3803 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3805 laser.fuse_off = TRUE;
3809 DrawLaser(0, DL_LASER_DISABLED);
3810 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3813 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3815 if (!Store2[ELX][ELY]) // check if content element not yet determined
3817 int last_anim_random_frame = gfx.anim_random_frame;
3820 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3821 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3823 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3824 native_mm_level.ball_choice_mode, 0,
3825 game_mm.ball_choice_pos);
3827 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3828 gfx.anim_random_frame = last_anim_random_frame;
3830 game_mm.ball_choice_pos++;
3832 int new_element = native_mm_level.ball_content[element_pos];
3833 int new_element_base = map_wall_to_base_element(new_element);
3835 if (IS_WALL(new_element_base))
3837 // always use completely filled wall element
3838 new_element = new_element_base | 0x000f;
3840 else if (native_mm_level.rotate_ball_content &&
3841 get_num_elements(new_element) > 1)
3843 // randomly rotate newly created game element
3844 new_element = get_rotated_element(new_element, RND(16));
3847 Store[ELX][ELY] = new_element;
3848 Store2[ELX][ELY] = TRUE;
3851 if (native_mm_level.explode_ball)
3854 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3856 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3861 if (IS_WALL_ICE(element) && CT > 50)
3863 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3865 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3866 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3867 Store2[ELX][ELY] = laser.wall_mask;
3869 laser.dest_element = Tile[ELX][ELY];
3874 if (IS_WALL_AMOEBA(element) && CT > 60)
3877 int element2 = Tile[ELX][ELY];
3879 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3882 for (i = laser.num_damages - 1; i >= 0; i--)
3883 if (laser.damage[i].is_mirror)
3886 r = laser.num_edges;
3887 d = laser.num_damages;
3894 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3897 DrawLaser(0, DL_LASER_ENABLED);
3900 x = laser.damage[k1].x;
3901 y = laser.damage[k1].y;
3906 for (i = 0; i < 4; i++)
3908 if (laser.wall_mask & (1 << i))
3910 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3911 cSY + ELY * TILEY + 31 * (i / 2)))
3914 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3915 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3922 for (i = 0; i < 4; i++)
3924 if (laser.wall_mask & (1 << i))
3926 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3927 cSY + ELY * TILEY + 31 * (i / 2)))
3934 if (laser.num_beamers > 0 ||
3935 k1 < 1 || k2 < 4 || k3 < 4 ||
3936 CheckLaserPixel(cSX + ELX * TILEX + 14,
3937 cSY + ELY * TILEY + 14))
3939 laser.num_edges = r;
3940 laser.num_damages = d;
3942 DrawLaser(0, DL_LASER_DISABLED);
3945 Tile[ELX][ELY] = element | laser.wall_mask;
3947 int x = ELX, y = ELY;
3948 int wall_mask = laser.wall_mask;
3951 DrawLaser(0, DL_LASER_ENABLED);
3953 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3955 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3956 Store[x][y] = EL_WALL_AMOEBA_BASE;
3957 Store2[x][y] = wall_mask;
3962 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3963 laser.stops_inside_element && CT > native_mm_level.time_block)
3968 if (ABS(XS) > ABS(YS))
3975 for (i = 0; i < 4; i++)
3982 x = ELX + Step[k * 4].x;
3983 y = ELY + Step[k * 4].y;
3985 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3988 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3996 laser.overloaded = (element == EL_BLOCK_STONE);
4001 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
4004 Tile[x][y] = element;
4006 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
4009 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
4011 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
4012 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
4020 if (element == EL_FUEL_FULL && CT > 10)
4022 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
4023 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
4025 for (i = start; i <= num_init_game_frames; i++)
4027 if (i == num_init_game_frames)
4028 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4029 else if (setup.sound_loops)
4030 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4032 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
4034 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
4036 UpdateAndDisplayGameControlValues();
4041 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
4043 DrawField_MM(ELX, ELY);
4045 DrawLaser(0, DL_LASER_ENABLED);
4051 void GameActions_MM(struct MouseActionInfo action)
4053 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
4054 boolean button_released = (action.button == MB_RELEASED);
4056 GameActions_MM_Ext();
4058 CheckSingleStepMode_MM(element_clicked, button_released);
4061 static void MovePacMen(void)
4063 int mx, my, ox, oy, nx, ny;
4067 if (++pacman_nr >= game_mm.num_pacman)
4070 game_mm.pacman[pacman_nr].dir--;
4072 for (l = 1; l < 5; l++)
4074 game_mm.pacman[pacman_nr].dir++;
4076 if (game_mm.pacman[pacman_nr].dir > 4)
4077 game_mm.pacman[pacman_nr].dir = 1;
4079 if (game_mm.pacman[pacman_nr].dir % 2)
4082 my = game_mm.pacman[pacman_nr].dir - 2;
4087 mx = 3 - game_mm.pacman[pacman_nr].dir;
4090 ox = game_mm.pacman[pacman_nr].x;
4091 oy = game_mm.pacman[pacman_nr].y;
4094 element = Tile[nx][ny];
4096 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
4099 if (!IS_EATABLE4PACMAN(element))
4102 if (ObjHit(nx, ny, HIT_POS_CENTER))
4105 Tile[ox][oy] = EL_EMPTY;
4107 EL_PACMAN_RIGHT - 1 +
4108 (game_mm.pacman[pacman_nr].dir - 1 +
4109 (game_mm.pacman[pacman_nr].dir % 2) * 2);
4111 game_mm.pacman[pacman_nr].x = nx;
4112 game_mm.pacman[pacman_nr].y = ny;
4114 DrawGraphic_MM(ox, oy, IMG_EMPTY);
4116 if (element != EL_EMPTY)
4118 int graphic = el2gfx(Tile[nx][ny]);
4123 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
4126 ox = cSX + ox * TILEX;
4127 oy = cSY + oy * TILEY;
4129 for (i = 1; i < 33; i += 2)
4130 BlitBitmap(bitmap, window,
4131 src_x, src_y, TILEX, TILEY,
4132 ox + i * mx, oy + i * my);
4133 Ct = Ct + FrameCounter - CT;
4136 DrawField_MM(nx, ny);
4139 if (!laser.fuse_off)
4141 DrawLaser(0, DL_LASER_ENABLED);
4143 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
4145 AddDamagedField(nx, ny);
4147 laser.damage[laser.num_damages - 1].edge = 0;
4151 if (element == EL_BOMB)
4152 DeletePacMan(nx, ny);
4154 if (IS_WALL_AMOEBA(element) &&
4155 (LX + 2 * XS) / TILEX == nx &&
4156 (LY + 2 * YS) / TILEY == ny)
4166 static void InitMovingField_MM(int x, int y, int direction)
4168 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4169 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4171 MovDir[x][y] = direction;
4172 MovDir[newx][newy] = direction;
4174 if (Tile[newx][newy] == EL_EMPTY)
4175 Tile[newx][newy] = EL_BLOCKED;
4178 static int MovingOrBlocked2Element_MM(int x, int y)
4180 int element = Tile[x][y];
4182 if (element == EL_BLOCKED)
4186 Blocked2Moving(x, y, &oldx, &oldy);
4188 return Tile[oldx][oldy];
4194 static void RemoveMovingField_MM(int x, int y)
4196 int oldx = x, oldy = y, newx = x, newy = y;
4198 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4201 if (IS_MOVING(x, y))
4203 Moving2Blocked(x, y, &newx, &newy);
4204 if (Tile[newx][newy] != EL_BLOCKED)
4207 else if (Tile[x][y] == EL_BLOCKED)
4209 Blocked2Moving(x, y, &oldx, &oldy);
4210 if (!IS_MOVING(oldx, oldy))
4214 Tile[oldx][oldy] = EL_EMPTY;
4215 Tile[newx][newy] = EL_EMPTY;
4216 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4217 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4219 DrawLevelField_MM(oldx, oldy);
4220 DrawLevelField_MM(newx, newy);
4223 static void RaiseScore_MM(int value)
4225 game_mm.score += value;
4228 void RaiseScoreElement_MM(int element)
4233 case EL_PACMAN_RIGHT:
4235 case EL_PACMAN_LEFT:
4236 case EL_PACMAN_DOWN:
4237 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4241 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4246 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4250 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4259 // ----------------------------------------------------------------------------
4260 // Mirror Magic game engine snapshot handling functions
4261 // ----------------------------------------------------------------------------
4263 void SaveEngineSnapshotValues_MM(void)
4267 engine_snapshot_mm.game_mm = game_mm;
4268 engine_snapshot_mm.laser = laser;
4270 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4272 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4274 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4275 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4276 engine_snapshot_mm.Box[x][y] = Box[x][y];
4277 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4281 engine_snapshot_mm.LX = LX;
4282 engine_snapshot_mm.LY = LY;
4283 engine_snapshot_mm.XS = XS;
4284 engine_snapshot_mm.YS = YS;
4285 engine_snapshot_mm.ELX = ELX;
4286 engine_snapshot_mm.ELY = ELY;
4287 engine_snapshot_mm.CT = CT;
4288 engine_snapshot_mm.Ct = Ct;
4290 engine_snapshot_mm.last_LX = last_LX;
4291 engine_snapshot_mm.last_LY = last_LY;
4292 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4293 engine_snapshot_mm.hold_x = hold_x;
4294 engine_snapshot_mm.hold_y = hold_y;
4295 engine_snapshot_mm.pacman_nr = pacman_nr;
4297 engine_snapshot_mm.rotate_delay = rotate_delay;
4298 engine_snapshot_mm.pacman_delay = pacman_delay;
4299 engine_snapshot_mm.energy_delay = energy_delay;
4300 engine_snapshot_mm.overload_delay = overload_delay;
4303 void LoadEngineSnapshotValues_MM(void)
4307 // stored engine snapshot buffers already restored at this point
4309 game_mm = engine_snapshot_mm.game_mm;
4310 laser = engine_snapshot_mm.laser;
4312 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4314 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4316 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4317 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4318 Box[x][y] = engine_snapshot_mm.Box[x][y];
4319 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4323 LX = engine_snapshot_mm.LX;
4324 LY = engine_snapshot_mm.LY;
4325 XS = engine_snapshot_mm.XS;
4326 YS = engine_snapshot_mm.YS;
4327 ELX = engine_snapshot_mm.ELX;
4328 ELY = engine_snapshot_mm.ELY;
4329 CT = engine_snapshot_mm.CT;
4330 Ct = engine_snapshot_mm.Ct;
4332 last_LX = engine_snapshot_mm.last_LX;
4333 last_LY = engine_snapshot_mm.last_LY;
4334 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4335 hold_x = engine_snapshot_mm.hold_x;
4336 hold_y = engine_snapshot_mm.hold_y;
4337 pacman_nr = engine_snapshot_mm.pacman_nr;
4339 rotate_delay = engine_snapshot_mm.rotate_delay;
4340 pacman_delay = engine_snapshot_mm.pacman_delay;
4341 energy_delay = engine_snapshot_mm.energy_delay;
4342 overload_delay = engine_snapshot_mm.overload_delay;
4344 RedrawPlayfield_MM();
4347 static int getAngleFromTouchDelta(int dx, int dy, int base)
4349 double pi = 3.141592653;
4350 double rad = atan2((double)-dy, (double)dx);
4351 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4352 double deg = rad2 * 180.0 / pi;
4354 return (int)(deg * base / 360.0 + 0.5) % base;
4357 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4359 // calculate start (source) position to be at the middle of the tile
4360 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4361 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4362 int dx = dst_mx - src_mx;
4363 int dy = dst_my - src_my;
4372 if (!IN_LEV_FIELD(x, y))
4375 element = Tile[x][y];
4377 if (!IS_MCDUFFIN(element) &&
4378 !IS_MIRROR(element) &&
4379 !IS_BEAMER(element) &&
4380 !IS_POLAR(element) &&
4381 !IS_POLAR_CROSS(element) &&
4382 !IS_DF_MIRROR(element))
4385 angle_old = get_element_angle(element);
4387 if (IS_MCDUFFIN(element))
4389 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4390 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4391 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4392 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4395 else if (IS_MIRROR(element) ||
4396 IS_DF_MIRROR(element))
4398 for (i = 0; i < laser.num_damages; i++)
4400 if (laser.damage[i].x == x &&
4401 laser.damage[i].y == y &&
4402 ObjHit(x, y, HIT_POS_CENTER))
4404 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4405 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4412 if (angle_new == -1)
4414 if (IS_MIRROR(element) ||
4415 IS_DF_MIRROR(element) ||
4419 if (IS_POLAR_CROSS(element))
4422 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4425 button = (angle_new == angle_old ? 0 :
4426 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4427 MB_LEFTBUTTON : MB_RIGHTBUTTON);