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_RECTANGLE 8
143 #define MM_MASK_CIRCLE 9
145 #define NUM_MM_MASKS 10
147 // element masks for scanning pixels of MM elements
148 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
332 static int get_element_angle(int element)
334 int element_phase = get_element_phase(element);
336 if (IS_MIRROR_FIXED(element) ||
337 IS_MCDUFFIN(element) ||
339 IS_RECEIVER(element))
340 return 4 * element_phase;
342 return element_phase;
345 static int get_opposite_angle(int angle)
347 int opposite_angle = angle + ANG_RAY_180;
349 // make sure "opposite_angle" is in valid interval [0, 15]
350 return (opposite_angle + 16) % 16;
353 static int get_mirrored_angle(int laser_angle, int mirror_angle)
355 int reflected_angle = 16 - laser_angle + mirror_angle;
357 // make sure "reflected_angle" is in valid interval [0, 15]
358 return (reflected_angle + 16) % 16;
361 static void DrawLaserLines(struct XY *points, int num_points, int mode)
363 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
364 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
366 DrawLines(drawto_mm, points, num_points, pixel_drawto);
370 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
375 static boolean CheckLaserPixel(int x, int y)
381 pixel = ReadPixel(laser_bitmap, x, y);
385 return (pixel == WHITE_PIXEL);
388 static void CheckExitMM(void)
390 int exit_element = EL_EMPTY;
394 static int xy[4][2] =
402 for (y = 0; y < lev_fieldy; y++)
404 for (x = 0; x < lev_fieldx; x++)
406 if (Tile[x][y] == EL_EXIT_CLOSED)
408 // initiate opening animation of exit door
409 Tile[x][y] = EL_EXIT_OPENING;
411 exit_element = EL_EXIT_OPEN;
415 else if (IS_RECEIVER(Tile[x][y]))
417 // remove field that blocks receiver
418 int phase = Tile[x][y] - EL_RECEIVER_START;
419 int blocking_x, blocking_y;
421 blocking_x = x + xy[phase][0];
422 blocking_y = y + xy[phase][1];
424 if (IN_LEV_FIELD(blocking_x, blocking_y))
426 Tile[blocking_x][blocking_y] = EL_EMPTY;
428 DrawField_MM(blocking_x, blocking_y);
431 exit_element = EL_RECEIVER;
438 if (exit_element != EL_EMPTY)
439 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
442 static void SetLaserColor(int brightness)
444 int color_min = 0x00;
445 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
446 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
447 int color_down = color_max - color_up;
450 GetPixelFromRGB(window,
451 (game_mm.laser_red ? color_max : color_up),
452 (game_mm.laser_green ? color_down : color_min),
453 (game_mm.laser_blue ? color_down : color_min));
456 static void InitMovDir_MM(int x, int y)
458 int element = Tile[x][y];
459 static int direction[3][4] =
461 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
462 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
463 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
468 case EL_PACMAN_RIGHT:
472 Tile[x][y] = EL_PACMAN;
473 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
481 static void InitField(int x, int y, boolean init_game)
483 int element = Tile[x][y];
488 Tile[x][y] = EL_EMPTY;
493 if (init_game && native_mm_level.auto_count_kettles)
494 game_mm.kettles_still_needed++;
497 case EL_LIGHTBULB_OFF:
498 game_mm.lights_still_needed++;
502 if (IS_MIRROR(element) ||
503 IS_BEAMER_OLD(element) ||
504 IS_BEAMER(element) ||
506 IS_POLAR_CROSS(element) ||
507 IS_DF_MIRROR(element) ||
508 IS_DF_MIRROR_AUTO(element) ||
509 IS_GRID_STEEL_AUTO(element) ||
510 IS_GRID_WOOD_AUTO(element) ||
511 IS_FIBRE_OPTIC(element))
513 if (IS_BEAMER_OLD(element))
515 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
516 element = Tile[x][y];
519 if (!IS_FIBRE_OPTIC(element))
521 static int steps_grid_auto = 0;
523 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
524 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
526 if (IS_GRID_STEEL_AUTO(element) ||
527 IS_GRID_WOOD_AUTO(element))
528 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
530 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
532 game_mm.cycle[game_mm.num_cycle].x = x;
533 game_mm.cycle[game_mm.num_cycle].y = y;
537 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
539 int beamer_nr = BEAMER_NR(element);
540 int nr = laser.beamer[beamer_nr][0].num;
542 laser.beamer[beamer_nr][nr].x = x;
543 laser.beamer[beamer_nr][nr].y = y;
544 laser.beamer[beamer_nr][nr].num = 1;
547 else if (IS_PACMAN(element))
551 else if (IS_MCDUFFIN(element) || IS_LASER(element))
555 laser.start_edge.x = x;
556 laser.start_edge.y = y;
557 laser.start_angle = get_element_angle(element);
560 if (IS_MCDUFFIN(element))
562 game_mm.laser_red = native_mm_level.mm_laser_red;
563 game_mm.laser_green = native_mm_level.mm_laser_green;
564 game_mm.laser_blue = native_mm_level.mm_laser_blue;
568 game_mm.laser_red = native_mm_level.df_laser_red;
569 game_mm.laser_green = native_mm_level.df_laser_green;
570 game_mm.laser_blue = native_mm_level.df_laser_blue;
573 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
580 static void InitCycleElements_RotateSingleStep(void)
584 if (game_mm.num_cycle == 0) // no elements to cycle
587 for (i = 0; i < game_mm.num_cycle; i++)
589 int x = game_mm.cycle[i].x;
590 int y = game_mm.cycle[i].y;
591 int step = SIGN(game_mm.cycle[i].steps);
592 int last_element = Tile[x][y];
593 int next_element = get_rotated_element(last_element, step);
595 if (!game_mm.cycle[i].steps)
598 Tile[x][y] = next_element;
600 game_mm.cycle[i].steps -= step;
604 static void InitLaser(void)
606 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
607 int step = (IS_LASER(start_element) ? 4 : 0);
609 LX = laser.start_edge.x * TILEX;
610 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
613 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
615 LY = laser.start_edge.y * TILEY;
616 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
617 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
621 XS = 2 * Step[laser.start_angle].x;
622 YS = 2 * Step[laser.start_angle].y;
624 laser.current_angle = laser.start_angle;
626 laser.num_damages = 0;
628 laser.num_beamers = 0;
629 laser.beamer_edge[0] = 0;
631 laser.dest_element = EL_EMPTY;
634 AddLaserEdge(LX, LY); // set laser starting edge
639 void InitGameEngine_MM(void)
645 // initialize laser bitmap to current playfield (screen) size
646 ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
647 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
651 // set global game control values
652 game_mm.num_cycle = 0;
653 game_mm.num_pacman = 0;
656 game_mm.energy_left = 0; // later set to "native_mm_level.time"
657 game_mm.kettles_still_needed =
658 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
659 game_mm.lights_still_needed = 0;
660 game_mm.num_keys = 0;
661 game_mm.ball_choice_pos = 0;
663 game_mm.laser_red = FALSE;
664 game_mm.laser_green = FALSE;
665 game_mm.laser_blue = TRUE;
666 game_mm.has_mcduffin = TRUE;
668 game_mm.level_solved = FALSE;
669 game_mm.game_over = FALSE;
670 game_mm.game_over_cause = 0;
671 game_mm.game_over_message = NULL;
673 game_mm.laser_overload_value = 0;
674 game_mm.laser_enabled = FALSE;
676 // set global laser control values (must be set before "InitLaser()")
677 laser.start_edge.x = 0;
678 laser.start_edge.y = 0;
679 laser.start_angle = 0;
681 for (i = 0; i < MAX_NUM_BEAMERS; i++)
682 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
684 laser.overloaded = FALSE;
685 laser.overload_value = 0;
686 laser.fuse_off = FALSE;
687 laser.fuse_x = laser.fuse_y = -1;
689 laser.dest_element = EL_EMPTY;
690 laser.dest_element_last = EL_EMPTY;
691 laser.dest_element_last_x = -1;
692 laser.dest_element_last_y = -1;
706 rotate_delay.count = 0;
707 pacman_delay.count = 0;
708 energy_delay.count = 0;
709 overload_delay.count = 0;
711 ClickElement(-1, -1, -1);
713 for (x = 0; x < lev_fieldx; x++)
715 for (y = 0; y < lev_fieldy; y++)
717 Tile[x][y] = Ur[x][y];
718 Hit[x][y] = Box[x][y] = 0;
720 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
721 Store[x][y] = Store2[x][y] = 0;
724 InitField(x, y, TRUE);
731 void InitGameActions_MM(void)
733 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
734 int cycle_steps_done = 0;
739 for (i = 0; i <= num_init_game_frames; i++)
741 if (i == num_init_game_frames)
742 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
743 else if (setup.sound_loops)
744 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
746 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
748 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
750 UpdateAndDisplayGameControlValues();
752 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
754 InitCycleElements_RotateSingleStep();
759 AdvanceFrameCounter();
767 if (setup.quick_doors)
774 if (game_mm.kettles_still_needed == 0)
777 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
778 SetTileCursorActive(TRUE);
780 // restart all delay counters after initially cycling game elements
781 ResetFrameCounter(&rotate_delay);
782 ResetFrameCounter(&pacman_delay);
783 ResetFrameCounter(&energy_delay);
784 ResetFrameCounter(&overload_delay);
787 static void FadeOutLaser(void)
791 for (i = 15; i >= 0; i--)
793 SetLaserColor(0x11 * i);
795 DrawLaser(0, DL_LASER_ENABLED);
798 Delay_WithScreenUpdates(50);
801 DrawLaser(0, DL_LASER_DISABLED);
803 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
806 static void GameOver_MM(int game_over_cause)
808 game_mm.game_over = TRUE;
809 game_mm.game_over_cause = game_over_cause;
810 game_mm.game_over_message = (game_mm.has_mcduffin ?
811 (game_over_cause == GAME_OVER_BOMB ?
812 "Bomb killed Mc Duffin!" :
813 game_over_cause == GAME_OVER_NO_ENERGY ?
814 "Out of magic energy!" :
815 game_over_cause == GAME_OVER_OVERLOADED ?
816 "Magic spell hit Mc Duffin!" :
818 (game_over_cause == GAME_OVER_BOMB ?
819 "Bomb destroyed laser cannon!" :
820 game_over_cause == GAME_OVER_NO_ENERGY ?
821 "Out of laser energy!" :
822 game_over_cause == GAME_OVER_OVERLOADED ?
823 "Laser beam hit laser cannon!" :
826 SetTileCursorActive(FALSE);
829 static void AddLaserEdge(int lx, int ly)
831 int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
832 int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
834 // check if laser is still inside visible playfield area (or inside level)
835 if (cSX + lx < REAL_SX || cSX + lx >= REAL_SX + full_sxsize ||
836 cSY + ly < REAL_SY || cSY + ly >= REAL_SY + full_sysize)
838 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
843 laser.edge[laser.num_edges].x = cSX2 + lx;
844 laser.edge[laser.num_edges].y = cSY2 + ly;
850 static void AddDamagedField(int ex, int ey)
852 // prevent adding the same field position again
853 if (laser.num_damages > 0 &&
854 laser.damage[laser.num_damages - 1].x == ex &&
855 laser.damage[laser.num_damages - 1].y == ey &&
856 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
859 laser.damage[laser.num_damages].is_mirror = FALSE;
860 laser.damage[laser.num_damages].angle = laser.current_angle;
861 laser.damage[laser.num_damages].edge = laser.num_edges;
862 laser.damage[laser.num_damages].x = ex;
863 laser.damage[laser.num_damages].y = ey;
867 static boolean StepBehind(void)
873 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
874 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
876 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
882 static int getMaskFromElement(int element)
884 if (IS_MCDUFFIN(element))
885 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
886 else if (IS_GRID(element))
887 return MM_MASK_GRID_1 + get_element_phase(element);
888 else if (IS_DF_GRID(element))
889 return MM_MASK_RECTANGLE;
890 else if (IS_RECTANGLE(element))
891 return MM_MASK_RECTANGLE;
893 return MM_MASK_CIRCLE;
896 static int getLevelFromLaserX(int x)
898 return x / TILEX - (x < 0 ? 1 : 0); // correct negative values
901 static int getLevelFromLaserY(int y)
903 return y / TILEY - (y < 0 ? 1 : 0); // correct negative values
906 static int ScanPixel(void)
911 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
912 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
915 // follow laser beam until it hits something (at least the screen border)
916 while (hit_mask == HIT_MASK_NO_HIT)
922 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
923 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
925 Debug("game:mm:ScanPixel", "touched screen border!");
931 // check if laser scan has crossed element boundaries (not just mini tiles)
932 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
933 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
935 if (cross_x && cross_y)
937 int elx1 = (LX - XS) / TILEX;
938 int ely1 = (LY + YS) / TILEY;
939 int elx2 = (LX + XS) / TILEX;
940 int ely2 = (LY - YS) / TILEY;
942 // add element corners left and right from the laser beam to damage list
944 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
945 AddDamagedField(elx1, ely1);
947 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
948 AddDamagedField(elx2, ely2);
951 for (i = 0; i < 4; i++)
953 int px = LX + (i % 2) * 2;
954 int py = LY + (i / 2) * 2;
957 int lx = getLevelFromLaserX(px);
958 int ly = getLevelFromLaserY(py);
961 if (IN_LEV_FIELD(lx, ly))
963 int element = Tile[lx][ly];
965 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
969 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
971 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
973 pixel = ((element & (1 << pos)) ? 1 : 0);
977 int pos = getMaskFromElement(element);
979 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
984 // check if laser is still inside visible playfield area
985 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
986 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
989 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
990 hit_mask |= (1 << i);
993 if (hit_mask == HIT_MASK_NO_HIT)
995 // hit nothing -- go on with another step
1004 static void DeactivateLaserTargetElement(void)
1006 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1007 laser.dest_element_last == EL_MINE_ACTIVE ||
1008 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1009 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1011 int x = laser.dest_element_last_x;
1012 int y = laser.dest_element_last_y;
1013 int element = laser.dest_element_last;
1015 if (Tile[x][y] == element)
1016 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1017 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1019 if (Tile[x][y] == EL_GRAY_BALL)
1022 laser.dest_element_last = EL_EMPTY;
1023 laser.dest_element_last_x = -1;
1024 laser.dest_element_last_y = -1;
1028 static void ScanLaser(void)
1030 int element = EL_EMPTY;
1031 int last_element = EL_EMPTY;
1032 int end = 0, rf = laser.num_edges;
1034 // do not scan laser again after the game was lost for whatever reason
1035 if (game_mm.game_over)
1038 // do not scan laser if fuse is off
1042 DeactivateLaserTargetElement();
1044 laser.overloaded = FALSE;
1045 laser.stops_inside_element = FALSE;
1047 DrawLaser(0, DL_LASER_ENABLED);
1050 Debug("game:mm:ScanLaser",
1051 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1059 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1062 laser.overloaded = TRUE;
1067 hit_mask = ScanPixel();
1070 Debug("game:mm:ScanLaser",
1071 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1075 // hit something -- check out what it was
1076 ELX = getLevelFromLaserX(LX + XS);
1077 ELY = getLevelFromLaserY(LY + YS);
1080 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1081 hit_mask, LX, LY, ELX, ELY);
1084 if (!IN_LEV_FIELD(ELX, ELY))
1086 // laser next step position
1087 int x = cSX + LX + XS;
1088 int y = cSY + LY + YS;
1090 // check if next step of laser is still inside visible playfield area
1091 if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
1092 y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
1094 // go on with another step
1102 laser.dest_element = element;
1107 // check if laser scan has hit two diagonally adjacent element corners
1108 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1109 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1111 // check if laser scan has crossed element boundaries (not just mini tiles)
1112 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1113 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1115 // handle special case of laser hitting two diagonally adjacent elements
1116 // (with or without a third corner element behind these two elements)
1117 if ((diag_1 || diag_2) && cross_x && cross_y)
1119 // compare the two diagonally adjacent elements
1121 int yoffset = 2 * (diag_1 ? -1 : +1);
1122 int elx1 = (LX - xoffset) / TILEX;
1123 int ely1 = (LY + yoffset) / TILEY;
1124 int elx2 = (LX + xoffset) / TILEX;
1125 int ely2 = (LY - yoffset) / TILEY;
1126 int e1 = Tile[elx1][ely1];
1127 int e2 = Tile[elx2][ely2];
1128 boolean use_element_1 = FALSE;
1130 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1132 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1133 use_element_1 = (RND(2) ? TRUE : FALSE);
1134 else if (IS_WALL_ICE(e1))
1135 use_element_1 = TRUE;
1137 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1139 // if both tiles match, we can just select the first one
1140 if (IS_WALL_AMOEBA(e1))
1141 use_element_1 = TRUE;
1143 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1145 // if both tiles match, we can just select the first one
1146 if (IS_ABSORBING_BLOCK(e1))
1147 use_element_1 = TRUE;
1150 ELX = (use_element_1 ? elx1 : elx2);
1151 ELY = (use_element_1 ? ely1 : ely2);
1155 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1156 hit_mask, LX, LY, ELX, ELY);
1159 last_element = element;
1161 element = Tile[ELX][ELY];
1162 laser.dest_element = element;
1165 Debug("game:mm:ScanLaser",
1166 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1169 LX % TILEX, LY % TILEY,
1174 if (!IN_LEV_FIELD(ELX, ELY))
1175 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1179 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1180 if (element == EL_EMPTY &&
1181 IS_GRID_STEEL(last_element) &&
1182 laser.current_angle % 4) // angle is not 90°
1183 element = last_element;
1185 if (element == EL_EMPTY)
1187 if (!HitOnlyAnEdge(hit_mask))
1190 else if (element == EL_FUSE_ON)
1192 if (HitPolarizer(element, hit_mask))
1195 else if (IS_GRID(element) || IS_DF_GRID(element))
1197 if (HitPolarizer(element, hit_mask))
1200 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1201 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1203 if (HitBlock(element, hit_mask))
1210 else if (IS_MCDUFFIN(element))
1212 if (HitLaserSource(element, hit_mask))
1215 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1216 IS_RECEIVER(element))
1218 if (HitLaserDestination(element, hit_mask))
1221 else if (IS_WALL(element))
1223 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1225 if (HitReflectingWalls(element, hit_mask))
1230 if (HitAbsorbingWalls(element, hit_mask))
1236 if (HitElement(element, hit_mask))
1241 DrawLaser(rf - 1, DL_LASER_ENABLED);
1242 rf = laser.num_edges;
1244 if (!IS_DF_WALL_STEEL(element))
1246 // only used for scanning DF steel walls; reset for all other elements
1254 if (laser.dest_element != Tile[ELX][ELY])
1256 Debug("game:mm:ScanLaser",
1257 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1258 laser.dest_element, Tile[ELX][ELY]);
1262 if (!end && !laser.stops_inside_element && !StepBehind())
1265 Debug("game:mm:ScanLaser", "Go one step back");
1271 AddLaserEdge(LX, LY);
1275 DrawLaser(rf - 1, DL_LASER_ENABLED);
1277 Ct = CT = FrameCounter;
1280 if (!IN_LEV_FIELD(ELX, ELY))
1281 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1285 static void ScanLaser_FromLastMirror(void)
1287 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1290 for (i = start_pos; i >= 0; i--)
1291 if (laser.damage[i].is_mirror)
1294 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1296 DrawLaser(start_edge, DL_LASER_DISABLED);
1301 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1307 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1308 start_edge, num_edges, mode);
1313 Warn("DrawLaserExt: start_edge < 0");
1320 Warn("DrawLaserExt: num_edges < 0");
1326 if (mode == DL_LASER_DISABLED)
1328 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1332 // now draw the laser to the backbuffer and (if enabled) to the screen
1333 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1335 redraw_mask |= REDRAW_FIELD;
1337 if (mode == DL_LASER_ENABLED)
1340 // after the laser was deleted, the "damaged" graphics must be restored
1341 if (laser.num_damages)
1343 int damage_start = 0;
1346 // determine the starting edge, from which graphics need to be restored
1349 for (i = 0; i < laser.num_damages; i++)
1351 if (laser.damage[i].edge == start_edge + 1)
1360 // restore graphics from this starting edge to the end of damage list
1361 for (i = damage_start; i < laser.num_damages; i++)
1363 int lx = laser.damage[i].x;
1364 int ly = laser.damage[i].y;
1365 int element = Tile[lx][ly];
1367 if (Hit[lx][ly] == laser.damage[i].edge)
1368 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1371 if (Box[lx][ly] == laser.damage[i].edge)
1374 if (IS_DRAWABLE(element))
1375 DrawField_MM(lx, ly);
1378 elx = laser.damage[damage_start].x;
1379 ely = laser.damage[damage_start].y;
1380 element = Tile[elx][ely];
1383 if (IS_BEAMER(element))
1387 for (i = 0; i < laser.num_beamers; i++)
1388 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1390 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1391 mode, elx, ely, Hit[elx][ely], start_edge);
1392 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1393 get_element_angle(element), laser.damage[damage_start].angle);
1397 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1398 laser.num_beamers > 0 &&
1399 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1401 // element is outgoing beamer
1402 laser.num_damages = damage_start + 1;
1404 if (IS_BEAMER(element))
1405 laser.current_angle = get_element_angle(element);
1409 // element is incoming beamer or other element
1410 laser.num_damages = damage_start;
1411 laser.current_angle = laser.damage[laser.num_damages].angle;
1416 // no damages but McDuffin himself (who needs to be redrawn anyway)
1418 elx = laser.start_edge.x;
1419 ely = laser.start_edge.y;
1420 element = Tile[elx][ely];
1423 laser.num_edges = start_edge + 1;
1424 if (start_edge == 0)
1425 laser.current_angle = laser.start_angle;
1427 LX = laser.edge[start_edge].x - cSX2;
1428 LY = laser.edge[start_edge].y - cSY2;
1429 XS = 2 * Step[laser.current_angle].x;
1430 YS = 2 * Step[laser.current_angle].y;
1433 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1439 if (IS_BEAMER(element) ||
1440 IS_FIBRE_OPTIC(element) ||
1441 IS_PACMAN(element) ||
1442 IS_POLAR(element) ||
1443 IS_POLAR_CROSS(element) ||
1444 element == EL_FUSE_ON)
1449 Debug("game:mm:DrawLaserExt", "element == %d", element);
1452 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1453 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1457 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1458 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1459 (laser.num_beamers == 0 ||
1460 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1462 // element is incoming beamer or other element
1463 step_size = -step_size;
1468 if (IS_BEAMER(element))
1469 Debug("game:mm:DrawLaserExt",
1470 "start_edge == %d, laser.beamer_edge == %d",
1471 start_edge, laser.beamer_edge);
1474 LX += step_size * XS;
1475 LY += step_size * YS;
1477 else if (element != EL_EMPTY)
1486 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1491 void DrawLaser(int start_edge, int mode)
1493 // do not draw laser if fuse is off
1494 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1497 if (mode == DL_LASER_DISABLED)
1498 DeactivateLaserTargetElement();
1500 if (laser.num_edges - start_edge < 0)
1502 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1507 // check if laser is interrupted by beamer element
1508 if (laser.num_beamers > 0 &&
1509 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1511 if (mode == DL_LASER_ENABLED)
1514 int tmp_start_edge = start_edge;
1516 // draw laser segments forward from the start to the last beamer
1517 for (i = 0; i < laser.num_beamers; i++)
1519 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1521 if (tmp_num_edges <= 0)
1525 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1526 i, laser.beamer_edge[i], tmp_start_edge);
1529 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1531 tmp_start_edge = laser.beamer_edge[i];
1534 // draw last segment from last beamer to the end
1535 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1541 int last_num_edges = laser.num_edges;
1542 int num_beamers = laser.num_beamers;
1544 // delete laser segments backward from the end to the first beamer
1545 for (i = num_beamers - 1; i >= 0; i--)
1547 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1549 if (laser.beamer_edge[i] - start_edge <= 0)
1552 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1554 last_num_edges = laser.beamer_edge[i];
1555 laser.num_beamers--;
1559 if (last_num_edges - start_edge <= 0)
1560 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1561 last_num_edges, start_edge);
1564 // special case when rotating first beamer: delete laser edge on beamer
1565 // (but do not start scanning on previous edge to prevent mirror sound)
1566 if (last_num_edges - start_edge == 1 && start_edge > 0)
1567 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1569 // delete first segment from start to the first beamer
1570 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1575 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1578 game_mm.laser_enabled = mode;
1581 void DrawLaser_MM(void)
1583 DrawLaser(0, game_mm.laser_enabled);
1586 static boolean HitElement(int element, int hit_mask)
1588 if (HitOnlyAnEdge(hit_mask))
1591 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1592 element = MovingOrBlocked2Element_MM(ELX, ELY);
1595 Debug("game:mm:HitElement", "(1): element == %d", element);
1599 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1600 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1603 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1607 AddDamagedField(ELX, ELY);
1609 // this is more precise: check if laser would go through the center
1610 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1614 // prevent cutting through laser emitter with laser beam
1615 if (IS_LASER(element))
1618 // skip the whole element before continuing the scan
1626 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1628 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1630 /* skipping scan positions to the right and down skips one scan
1631 position too much, because this is only the top left scan position
1632 of totally four scan positions (plus one to the right, one to the
1633 bottom and one to the bottom right) */
1634 /* ... but only roll back scan position if more than one step done */
1644 Debug("game:mm:HitElement", "(2): element == %d", element);
1647 if (LX + 5 * XS < 0 ||
1657 Debug("game:mm:HitElement", "(3): element == %d", element);
1660 if (IS_POLAR(element) &&
1661 ((element - EL_POLAR_START) % 2 ||
1662 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1664 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1666 laser.num_damages--;
1671 if (IS_POLAR_CROSS(element) &&
1672 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1674 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1676 laser.num_damages--;
1681 if (!IS_BEAMER(element) &&
1682 !IS_FIBRE_OPTIC(element) &&
1683 !IS_GRID_WOOD(element) &&
1684 element != EL_FUEL_EMPTY)
1687 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1688 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1690 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1693 LX = ELX * TILEX + 14;
1694 LY = ELY * TILEY + 14;
1696 AddLaserEdge(LX, LY);
1699 if (IS_MIRROR(element) ||
1700 IS_MIRROR_FIXED(element) ||
1701 IS_POLAR(element) ||
1702 IS_POLAR_CROSS(element) ||
1703 IS_DF_MIRROR(element) ||
1704 IS_DF_MIRROR_AUTO(element) ||
1705 IS_DF_MIRROR_FIXED(element) ||
1706 element == EL_PRISM ||
1707 element == EL_REFRACTOR)
1709 int current_angle = laser.current_angle;
1712 laser.num_damages--;
1714 AddDamagedField(ELX, ELY);
1716 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1719 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1721 if (IS_MIRROR(element) ||
1722 IS_MIRROR_FIXED(element) ||
1723 IS_DF_MIRROR(element) ||
1724 IS_DF_MIRROR_AUTO(element) ||
1725 IS_DF_MIRROR_FIXED(element))
1726 laser.current_angle = get_mirrored_angle(laser.current_angle,
1727 get_element_angle(element));
1729 if (element == EL_PRISM || element == EL_REFRACTOR)
1730 laser.current_angle = RND(16);
1732 XS = 2 * Step[laser.current_angle].x;
1733 YS = 2 * Step[laser.current_angle].y;
1735 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1740 LX += step_size * XS;
1741 LY += step_size * YS;
1743 // draw sparkles on mirror
1744 if ((IS_MIRROR(element) ||
1745 IS_MIRROR_FIXED(element) ||
1746 element == EL_PRISM) &&
1747 current_angle != laser.current_angle)
1749 MovDelay[ELX][ELY] = 11; // start animation
1752 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1753 current_angle != laser.current_angle)
1754 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1757 (get_opposite_angle(laser.current_angle) ==
1758 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1760 return (laser.overloaded ? TRUE : FALSE);
1763 if (element == EL_FUEL_FULL)
1765 laser.stops_inside_element = TRUE;
1770 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1772 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1774 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1775 element == EL_MINE ? EL_MINE_ACTIVE :
1776 EL_GRAY_BALL_ACTIVE);
1778 GfxFrame[ELX][ELY] = 0; // restart animation
1780 laser.dest_element_last = Tile[ELX][ELY];
1781 laser.dest_element_last_x = ELX;
1782 laser.dest_element_last_y = ELY;
1784 if (element == EL_MINE)
1785 laser.overloaded = TRUE;
1788 if (element == EL_KETTLE ||
1789 element == EL_CELL ||
1790 element == EL_KEY ||
1791 element == EL_LIGHTBALL ||
1792 element == EL_PACMAN ||
1793 IS_PACMAN(element) ||
1794 IS_ENVELOPE(element))
1796 if (!IS_PACMAN(element) &&
1797 !IS_ENVELOPE(element))
1800 if (element == EL_PACMAN)
1803 if (element == EL_KETTLE || element == EL_CELL)
1805 if (game_mm.kettles_still_needed > 0)
1806 game_mm.kettles_still_needed--;
1808 game.snapshot.collected_item = TRUE;
1810 if (game_mm.kettles_still_needed == 0)
1814 DrawLaser(0, DL_LASER_ENABLED);
1817 else if (element == EL_KEY)
1821 else if (IS_PACMAN(element))
1823 DeletePacMan(ELX, ELY);
1825 else if (IS_ENVELOPE(element))
1827 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1830 RaiseScoreElement_MM(element);
1835 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1837 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1839 DrawLaser(0, DL_LASER_ENABLED);
1841 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1843 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1844 game_mm.lights_still_needed--;
1848 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1849 game_mm.lights_still_needed++;
1852 DrawField_MM(ELX, ELY);
1853 DrawLaser(0, DL_LASER_ENABLED);
1858 laser.stops_inside_element = TRUE;
1864 Debug("game:mm:HitElement", "(4): element == %d", element);
1867 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1868 laser.num_beamers < MAX_NUM_BEAMERS &&
1869 laser.beamer[BEAMER_NR(element)][1].num)
1871 int beamer_angle = get_element_angle(element);
1872 int beamer_nr = BEAMER_NR(element);
1876 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1879 laser.num_damages--;
1881 if (IS_FIBRE_OPTIC(element) ||
1882 laser.current_angle == get_opposite_angle(beamer_angle))
1886 LX = ELX * TILEX + 14;
1887 LY = ELY * TILEY + 14;
1889 AddLaserEdge(LX, LY);
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 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1898 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1899 ELX = laser.beamer[beamer_nr][pos].x;
1900 ELY = laser.beamer[beamer_nr][pos].y;
1901 LX = ELX * TILEX + 14;
1902 LY = ELY * TILEY + 14;
1904 if (IS_BEAMER(element))
1906 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1907 XS = 2 * Step[laser.current_angle].x;
1908 YS = 2 * Step[laser.current_angle].y;
1911 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1913 AddLaserEdge(LX, LY);
1914 AddDamagedField(ELX, ELY);
1916 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1919 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1921 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1926 LX += step_size * XS;
1927 LY += step_size * YS;
1929 laser.num_beamers++;
1938 static boolean HitOnlyAnEdge(int hit_mask)
1940 // check if the laser hit only the edge of an element and, if so, go on
1943 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1947 if ((hit_mask == HIT_MASK_TOPLEFT ||
1948 hit_mask == HIT_MASK_TOPRIGHT ||
1949 hit_mask == HIT_MASK_BOTTOMLEFT ||
1950 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1951 laser.current_angle % 4) // angle is not 90°
1955 if (hit_mask == HIT_MASK_TOPLEFT)
1960 else if (hit_mask == HIT_MASK_TOPRIGHT)
1965 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1970 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1976 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1982 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1989 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1995 static boolean HitPolarizer(int element, int hit_mask)
1997 if (HitOnlyAnEdge(hit_mask))
2000 if (IS_DF_GRID(element))
2002 int grid_angle = get_element_angle(element);
2005 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2006 grid_angle, laser.current_angle);
2009 AddLaserEdge(LX, LY);
2010 AddDamagedField(ELX, ELY);
2013 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2015 if (laser.current_angle == grid_angle ||
2016 laser.current_angle == get_opposite_angle(grid_angle))
2018 // skip the whole element before continuing the scan
2024 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2026 if (LX/TILEX > ELX || LY/TILEY > ELY)
2028 /* skipping scan positions to the right and down skips one scan
2029 position too much, because this is only the top left scan position
2030 of totally four scan positions (plus one to the right, one to the
2031 bottom and one to the bottom right) */
2037 AddLaserEdge(LX, LY);
2043 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2045 LX / TILEX, LY / TILEY,
2046 LX % TILEX, LY % TILEY);
2051 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2053 return HitReflectingWalls(element, hit_mask);
2057 return HitAbsorbingWalls(element, hit_mask);
2060 else if (IS_GRID_STEEL(element))
2062 // may be required if graphics for steel grid redefined
2063 AddDamagedField(ELX, ELY);
2065 return HitReflectingWalls(element, hit_mask);
2067 else // IS_GRID_WOOD
2069 // may be required if graphics for wooden grid redefined
2070 AddDamagedField(ELX, ELY);
2072 return HitAbsorbingWalls(element, hit_mask);
2078 static boolean HitBlock(int element, int hit_mask)
2080 boolean check = FALSE;
2082 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2083 game_mm.num_keys == 0)
2086 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2089 int ex = ELX * TILEX + 14;
2090 int ey = ELY * TILEY + 14;
2094 for (i = 1; i < 32; i++)
2099 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2104 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2105 return HitAbsorbingWalls(element, hit_mask);
2109 AddLaserEdge(LX - XS, LY - YS);
2110 AddDamagedField(ELX, ELY);
2113 Box[ELX][ELY] = laser.num_edges;
2115 return HitReflectingWalls(element, hit_mask);
2118 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2120 int xs = XS / 2, ys = YS / 2;
2122 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2123 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2125 laser.overloaded = (element == EL_GATE_STONE);
2130 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2131 (hit_mask == HIT_MASK_TOP ||
2132 hit_mask == HIT_MASK_LEFT ||
2133 hit_mask == HIT_MASK_RIGHT ||
2134 hit_mask == HIT_MASK_BOTTOM))
2135 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2136 hit_mask == HIT_MASK_BOTTOM),
2137 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2138 hit_mask == HIT_MASK_RIGHT));
2139 AddLaserEdge(LX, LY);
2145 if (element == EL_GATE_STONE && Box[ELX][ELY])
2147 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2159 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2161 int xs = XS / 2, ys = YS / 2;
2163 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2164 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2166 laser.overloaded = (element == EL_BLOCK_STONE);
2171 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2172 (hit_mask == HIT_MASK_TOP ||
2173 hit_mask == HIT_MASK_LEFT ||
2174 hit_mask == HIT_MASK_RIGHT ||
2175 hit_mask == HIT_MASK_BOTTOM))
2176 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2177 hit_mask == HIT_MASK_BOTTOM),
2178 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2179 hit_mask == HIT_MASK_RIGHT));
2180 AddDamagedField(ELX, ELY);
2182 LX = ELX * TILEX + 14;
2183 LY = ELY * TILEY + 14;
2185 AddLaserEdge(LX, LY);
2187 laser.stops_inside_element = TRUE;
2195 static boolean HitLaserSource(int element, int hit_mask)
2197 if (HitOnlyAnEdge(hit_mask))
2200 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2202 laser.overloaded = TRUE;
2207 static boolean HitLaserDestination(int element, int hit_mask)
2209 if (HitOnlyAnEdge(hit_mask))
2212 if (element != EL_EXIT_OPEN &&
2213 !(IS_RECEIVER(element) &&
2214 game_mm.kettles_still_needed == 0 &&
2215 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2217 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2222 if (IS_RECEIVER(element) ||
2223 (IS_22_5_ANGLE(laser.current_angle) &&
2224 (ELX != (LX + 6 * XS) / TILEX ||
2225 ELY != (LY + 6 * YS) / TILEY ||
2234 LX = ELX * TILEX + 14;
2235 LY = ELY * TILEY + 14;
2237 laser.stops_inside_element = TRUE;
2240 AddLaserEdge(LX, LY);
2241 AddDamagedField(ELX, ELY);
2243 if (game_mm.lights_still_needed == 0)
2245 game_mm.level_solved = TRUE;
2247 SetTileCursorActive(FALSE);
2253 static boolean HitReflectingWalls(int element, int hit_mask)
2255 // check if laser hits side of a wall with an angle that is not 90°
2256 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2257 hit_mask == HIT_MASK_LEFT ||
2258 hit_mask == HIT_MASK_RIGHT ||
2259 hit_mask == HIT_MASK_BOTTOM))
2261 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2266 if (!IS_DF_GRID(element))
2267 AddLaserEdge(LX, LY);
2269 // check if laser hits wall with an angle of 45°
2270 if (!IS_22_5_ANGLE(laser.current_angle))
2272 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2275 laser.current_angle = get_mirrored_angle(laser.current_angle,
2278 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2281 laser.current_angle = get_mirrored_angle(laser.current_angle,
2285 AddLaserEdge(LX, LY);
2287 XS = 2 * Step[laser.current_angle].x;
2288 YS = 2 * Step[laser.current_angle].y;
2292 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2294 laser.current_angle = get_mirrored_angle(laser.current_angle,
2299 if (!IS_DF_GRID(element))
2300 AddLaserEdge(LX, LY);
2305 if (!IS_DF_GRID(element))
2306 AddLaserEdge(LX, LY + YS / 2);
2309 if (!IS_DF_GRID(element))
2310 AddLaserEdge(LX, LY);
2313 YS = 2 * Step[laser.current_angle].y;
2317 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2319 laser.current_angle = get_mirrored_angle(laser.current_angle,
2324 if (!IS_DF_GRID(element))
2325 AddLaserEdge(LX, LY);
2330 if (!IS_DF_GRID(element))
2331 AddLaserEdge(LX + XS / 2, LY);
2334 if (!IS_DF_GRID(element))
2335 AddLaserEdge(LX, LY);
2338 XS = 2 * Step[laser.current_angle].x;
2344 // reflection at the edge of reflecting DF style wall
2345 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2347 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2348 hit_mask == HIT_MASK_TOPRIGHT) ||
2349 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2350 hit_mask == HIT_MASK_TOPLEFT) ||
2351 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2352 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2353 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2354 hit_mask == HIT_MASK_BOTTOMRIGHT))
2357 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2358 ANG_MIRROR_135 : ANG_MIRROR_45);
2360 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2362 AddDamagedField(ELX, ELY);
2363 AddLaserEdge(LX, LY);
2365 laser.current_angle = get_mirrored_angle(laser.current_angle,
2373 AddLaserEdge(LX, LY);
2379 // reflection inside an edge of reflecting DF style wall
2380 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2382 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2383 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2384 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2385 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2386 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2387 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2388 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2389 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2392 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2393 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2394 ANG_MIRROR_135 : ANG_MIRROR_45);
2396 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2399 AddDamagedField(ELX, ELY);
2402 AddLaserEdge(LX - XS, LY - YS);
2403 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2404 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2406 laser.current_angle = get_mirrored_angle(laser.current_angle,
2414 AddLaserEdge(LX, LY);
2420 // check if laser hits DF style wall with an angle of 90°
2421 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2423 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2424 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2425 (IS_VERT_ANGLE(laser.current_angle) &&
2426 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2428 // laser at last step touched nothing or the same side of the wall
2429 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2431 AddDamagedField(ELX, ELY);
2438 last_hit_mask = hit_mask;
2445 if (!HitOnlyAnEdge(hit_mask))
2447 laser.overloaded = TRUE;
2455 static boolean HitAbsorbingWalls(int element, int hit_mask)
2457 if (HitOnlyAnEdge(hit_mask))
2461 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2463 AddLaserEdge(LX - XS, LY - YS);
2470 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2472 AddLaserEdge(LX - XS, LY - YS);
2478 if (IS_WALL_WOOD(element) ||
2479 IS_DF_WALL_WOOD(element) ||
2480 IS_GRID_WOOD(element) ||
2481 IS_GRID_WOOD_FIXED(element) ||
2482 IS_GRID_WOOD_AUTO(element) ||
2483 element == EL_FUSE_ON ||
2484 element == EL_BLOCK_WOOD ||
2485 element == EL_GATE_WOOD)
2487 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2492 if (IS_WALL_ICE(element))
2498 // check if laser hit adjacent edges of two diagonal tiles
2499 if (ELX != lx / TILEX)
2501 if (ELY != ly / TILEY)
2504 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2505 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2507 // check if laser hits wall with an angle of 90°
2508 if (IS_90_ANGLE(laser.current_angle))
2509 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2511 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2515 for (i = 0; i < 4; i++)
2517 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2518 mask = 15 - (8 >> i);
2519 else if (ABS(XS) == 4 &&
2521 (XS > 0) == (i % 2) &&
2522 (YS < 0) == (i / 2))
2523 mask = 3 + (i / 2) * 9;
2524 else if (ABS(YS) == 4 &&
2526 (XS < 0) == (i % 2) &&
2527 (YS > 0) == (i / 2))
2528 mask = 5 + (i % 2) * 5;
2532 laser.wall_mask = mask;
2534 else if (IS_WALL_AMOEBA(element))
2536 int elx = (LX - 2 * XS) / TILEX;
2537 int ely = (LY - 2 * YS) / TILEY;
2538 int element2 = Tile[elx][ely];
2541 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2543 laser.dest_element = EL_EMPTY;
2551 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2552 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2554 if (IS_90_ANGLE(laser.current_angle))
2555 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2557 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2559 laser.wall_mask = mask;
2565 static void OpenExit(int x, int y)
2569 if (!MovDelay[x][y]) // next animation frame
2570 MovDelay[x][y] = 4 * delay;
2572 if (MovDelay[x][y]) // wait some time before next frame
2577 phase = MovDelay[x][y] / delay;
2579 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2580 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2582 if (!MovDelay[x][y])
2584 Tile[x][y] = EL_EXIT_OPEN;
2590 static void OpenGrayBall(int x, int y)
2594 if (!MovDelay[x][y]) // next animation frame
2596 if (IS_WALL(Store[x][y]))
2598 DrawWalls_MM(x, y, Store[x][y]);
2600 // copy wall tile to spare bitmap for "melting" animation
2601 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2602 TILEX, TILEY, x * TILEX, y * TILEY);
2604 DrawElement_MM(x, y, EL_GRAY_BALL);
2607 MovDelay[x][y] = 50 * delay;
2610 if (MovDelay[x][y]) // wait some time before next frame
2614 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2618 int dx = RND(26), dy = RND(26);
2620 if (IS_WALL(Store[x][y]))
2622 // copy wall tile from spare bitmap for "melting" animation
2623 bitmap = bitmap_db_field;
2629 int graphic = el2gfx(Store[x][y]);
2631 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2634 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2635 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2637 laser.redraw = TRUE;
2639 MarkTileDirty(x, y);
2642 if (!MovDelay[x][y])
2644 Tile[x][y] = Store[x][y];
2645 Store[x][y] = Store2[x][y] = 0;
2646 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2648 InitField(x, y, FALSE);
2651 ScanLaser_FromLastMirror();
2656 static void OpenEnvelope(int x, int y)
2658 int num_frames = 8; // seven frames plus final empty space
2660 if (!MovDelay[x][y]) // next animation frame
2661 MovDelay[x][y] = num_frames;
2663 if (MovDelay[x][y]) // wait some time before next frame
2665 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2669 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2671 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2672 int frame = num_frames - MovDelay[x][y] - 1;
2674 DrawGraphicAnimation_MM(x, y, graphic, frame);
2676 laser.redraw = TRUE;
2679 if (MovDelay[x][y] == 0)
2681 Tile[x][y] = EL_EMPTY;
2692 static void MeltIce(int x, int y)
2697 if (!MovDelay[x][y]) // next animation frame
2698 MovDelay[x][y] = frames * delay;
2700 if (MovDelay[x][y]) // wait some time before next frame
2703 int wall_mask = Store2[x][y];
2704 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2707 phase = frames - MovDelay[x][y] / delay - 1;
2709 if (!MovDelay[x][y])
2711 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2712 Store[x][y] = Store2[x][y] = 0;
2714 DrawWalls_MM(x, y, Tile[x][y]);
2716 if (Tile[x][y] == EL_WALL_ICE_BASE)
2717 Tile[x][y] = EL_EMPTY;
2719 ScanLaser_FromLastMirror();
2721 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2723 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2725 laser.redraw = TRUE;
2730 static void GrowAmoeba(int x, int y)
2735 if (!MovDelay[x][y]) // next animation frame
2736 MovDelay[x][y] = frames * delay;
2738 if (MovDelay[x][y]) // wait some time before next frame
2741 int wall_mask = Store2[x][y];
2742 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2745 phase = MovDelay[x][y] / delay;
2747 if (!MovDelay[x][y])
2749 Tile[x][y] = real_element;
2750 Store[x][y] = Store2[x][y] = 0;
2752 DrawWalls_MM(x, y, Tile[x][y]);
2753 DrawLaser(0, DL_LASER_ENABLED);
2755 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2757 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2762 static void DrawFieldAnimated_MM(int x, int y)
2766 laser.redraw = TRUE;
2769 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2771 int element = Tile[x][y];
2772 int graphic = el2gfx(element);
2774 if (!getGraphicInfo_NewFrame(x, y, graphic))
2779 laser.redraw = TRUE;
2782 static void DrawFieldTwinkle(int x, int y)
2784 if (MovDelay[x][y] != 0) // wait some time before next frame
2790 if (MovDelay[x][y] != 0)
2792 int graphic = IMG_TWINKLE_WHITE;
2793 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2795 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2798 laser.redraw = TRUE;
2802 static void Explode_MM(int x, int y, int phase, int mode)
2804 int num_phase = 9, delay = 2;
2805 int last_phase = num_phase * delay;
2806 int half_phase = (num_phase / 2) * delay;
2809 laser.redraw = TRUE;
2811 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2813 center_element = Tile[x][y];
2815 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2817 // put moving element to center field (and let it explode there)
2818 center_element = MovingOrBlocked2Element_MM(x, y);
2819 RemoveMovingField_MM(x, y);
2821 Tile[x][y] = center_element;
2824 if (center_element != EL_GRAY_BALL_ACTIVE)
2825 Store[x][y] = EL_EMPTY;
2826 Store2[x][y] = center_element;
2828 Tile[x][y] = EL_EXPLODING_OPAQUE;
2830 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2831 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2832 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2835 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2837 ExplodePhase[x][y] = 1;
2843 GfxFrame[x][y] = 0; // restart explosion animation
2845 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2847 center_element = Store2[x][y];
2849 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2851 Tile[x][y] = EL_EXPLODING_TRANSP;
2853 if (x == ELX && y == ELY)
2857 if (phase == last_phase)
2859 if (center_element == EL_BOMB_ACTIVE)
2861 DrawLaser(0, DL_LASER_DISABLED);
2864 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2866 laser.overloaded = FALSE;
2868 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2870 GameOver_MM(GAME_OVER_BOMB);
2873 Tile[x][y] = Store[x][y];
2875 Store[x][y] = Store2[x][y] = 0;
2876 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2878 InitField(x, y, FALSE);
2881 if (center_element == EL_GRAY_BALL_ACTIVE)
2882 ScanLaser_FromLastMirror();
2884 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2886 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2887 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2889 DrawGraphicAnimation_MM(x, y, graphic, frame);
2891 MarkTileDirty(x, y);
2895 static void Bang_MM(int x, int y)
2897 int element = Tile[x][y];
2899 if (IS_PACMAN(element))
2900 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2901 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2902 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2903 else if (element == EL_KEY)
2904 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2906 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2908 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2911 static void TurnRound(int x, int y)
2923 { 0, 0 }, { 0, 0 }, { 0, 0 },
2928 int left, right, back;
2932 { MV_DOWN, MV_UP, MV_RIGHT },
2933 { MV_UP, MV_DOWN, MV_LEFT },
2935 { MV_LEFT, MV_RIGHT, MV_DOWN },
2939 { MV_RIGHT, MV_LEFT, MV_UP }
2942 int element = Tile[x][y];
2943 int old_move_dir = MovDir[x][y];
2944 int right_dir = turn[old_move_dir].right;
2945 int back_dir = turn[old_move_dir].back;
2946 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2947 int right_x = x + right_dx, right_y = y + right_dy;
2949 if (element == EL_PACMAN)
2951 boolean can_turn_right = FALSE;
2953 if (IN_LEV_FIELD(right_x, right_y) &&
2954 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2955 can_turn_right = TRUE;
2958 MovDir[x][y] = right_dir;
2960 MovDir[x][y] = back_dir;
2966 static void StartMoving_MM(int x, int y)
2968 int element = Tile[x][y];
2973 if (CAN_MOVE(element))
2977 if (MovDelay[x][y]) // wait some time before next movement
2985 // now make next step
2987 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2989 if (element == EL_PACMAN &&
2990 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2991 !ObjHit(newx, newy, HIT_POS_CENTER))
2993 Store[newx][newy] = Tile[newx][newy];
2994 Tile[newx][newy] = EL_EMPTY;
2996 DrawField_MM(newx, newy);
2998 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2999 ObjHit(newx, newy, HIT_POS_CENTER))
3001 // object was running against a wall
3008 InitMovingField_MM(x, y, MovDir[x][y]);
3012 ContinueMoving_MM(x, y);
3015 static void ContinueMoving_MM(int x, int y)
3017 int element = Tile[x][y];
3018 int direction = MovDir[x][y];
3019 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3020 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3021 int horiz_move = (dx!=0);
3022 int newx = x + dx, newy = y + dy;
3023 int step = (horiz_move ? dx : dy) * TILEX / 8;
3025 MovPos[x][y] += step;
3027 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3029 Tile[x][y] = EL_EMPTY;
3030 Tile[newx][newy] = element;
3032 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3033 MovDelay[newx][newy] = 0;
3035 if (!CAN_MOVE(element))
3036 MovDir[newx][newy] = 0;
3039 DrawField_MM(newx, newy);
3041 Stop[newx][newy] = TRUE;
3043 if (element == EL_PACMAN)
3045 if (Store[newx][newy] == EL_BOMB)
3046 Bang_MM(newx, newy);
3048 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3049 (LX + 2 * XS) / TILEX == newx &&
3050 (LY + 2 * YS) / TILEY == newy)
3057 else // still moving on
3062 laser.redraw = TRUE;
3065 boolean ClickElement(int x, int y, int button)
3067 static DelayCounter click_delay = { CLICK_DELAY };
3068 static boolean new_button = TRUE;
3069 boolean element_clicked = FALSE;
3074 // initialize static variables
3075 click_delay.count = 0;
3076 click_delay.value = CLICK_DELAY;
3082 // do not rotate objects hit by the laser after the game was solved
3083 if (game_mm.level_solved && Hit[x][y])
3086 if (button == MB_RELEASED)
3089 click_delay.value = CLICK_DELAY;
3091 // release eventually hold auto-rotating mirror
3092 RotateMirror(x, y, MB_RELEASED);
3097 if (!FrameReached(&click_delay) && !new_button)
3100 if (button == MB_MIDDLEBUTTON) // middle button has no function
3103 if (!IN_LEV_FIELD(x, y))
3106 if (Tile[x][y] == EL_EMPTY)
3109 element = Tile[x][y];
3111 if (IS_MIRROR(element) ||
3112 IS_BEAMER(element) ||
3113 IS_POLAR(element) ||
3114 IS_POLAR_CROSS(element) ||
3115 IS_DF_MIRROR(element) ||
3116 IS_DF_MIRROR_AUTO(element))
3118 RotateMirror(x, y, button);
3120 element_clicked = TRUE;
3122 else if (IS_MCDUFFIN(element))
3124 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3126 if (has_laser && !laser.fuse_off)
3127 DrawLaser(0, DL_LASER_DISABLED);
3129 element = get_rotated_element(element, BUTTON_ROTATION(button));
3131 Tile[x][y] = element;
3136 laser.start_angle = get_element_angle(element);
3140 if (!laser.fuse_off)
3144 element_clicked = TRUE;
3146 else if (element == EL_FUSE_ON && laser.fuse_off)
3148 if (x != laser.fuse_x || y != laser.fuse_y)
3151 laser.fuse_off = FALSE;
3152 laser.fuse_x = laser.fuse_y = -1;
3154 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3157 element_clicked = TRUE;
3159 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3161 laser.fuse_off = TRUE;
3164 laser.overloaded = FALSE;
3166 DrawLaser(0, DL_LASER_DISABLED);
3167 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3169 element_clicked = TRUE;
3171 else if (element == EL_LIGHTBALL)
3174 RaiseScoreElement_MM(element);
3175 DrawLaser(0, DL_LASER_ENABLED);
3177 element_clicked = TRUE;
3179 else if (IS_ENVELOPE(element))
3181 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3183 element_clicked = TRUE;
3186 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3189 return element_clicked;
3192 static void RotateMirror(int x, int y, int button)
3194 if (button == MB_RELEASED)
3196 // release eventually hold auto-rotating mirror
3203 if (IS_MIRROR(Tile[x][y]) ||
3204 IS_POLAR_CROSS(Tile[x][y]) ||
3205 IS_POLAR(Tile[x][y]) ||
3206 IS_BEAMER(Tile[x][y]) ||
3207 IS_DF_MIRROR(Tile[x][y]) ||
3208 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3209 IS_GRID_WOOD_AUTO(Tile[x][y]))
3211 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3213 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3215 if (button == MB_LEFTBUTTON)
3217 // left mouse button only for manual adjustment, no auto-rotating;
3218 // freeze mirror for until mouse button released
3222 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3224 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3228 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3230 int edge = Hit[x][y];
3236 DrawLaser(edge - 1, DL_LASER_DISABLED);
3240 else if (ObjHit(x, y, HIT_POS_CENTER))
3242 int edge = Hit[x][y];
3246 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3251 DrawLaser(edge - 1, DL_LASER_DISABLED);
3258 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3263 if ((IS_BEAMER(Tile[x][y]) ||
3264 IS_POLAR(Tile[x][y]) ||
3265 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3267 if (IS_BEAMER(Tile[x][y]))
3270 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3271 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3284 DrawLaser(0, DL_LASER_ENABLED);
3288 static void AutoRotateMirrors(void)
3292 if (!FrameReached(&rotate_delay))
3295 for (x = 0; x < lev_fieldx; x++)
3297 for (y = 0; y < lev_fieldy; y++)
3299 int element = Tile[x][y];
3301 // do not rotate objects hit by the laser after the game was solved
3302 if (game_mm.level_solved && Hit[x][y])
3305 if (IS_DF_MIRROR_AUTO(element) ||
3306 IS_GRID_WOOD_AUTO(element) ||
3307 IS_GRID_STEEL_AUTO(element) ||
3308 element == EL_REFRACTOR)
3310 RotateMirror(x, y, MB_RIGHTBUTTON);
3312 laser.redraw = TRUE;
3318 static boolean ObjHit(int obx, int oby, int bits)
3325 if (bits & HIT_POS_CENTER)
3327 if (CheckLaserPixel(cSX + obx + 15,
3332 if (bits & HIT_POS_EDGE)
3334 for (i = 0; i < 4; i++)
3335 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3336 cSY + oby + 31 * (i / 2)))
3340 if (bits & HIT_POS_BETWEEN)
3342 for (i = 0; i < 4; i++)
3343 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3344 cSY + 4 + oby + 22 * (i / 2)))
3351 static void DeletePacMan(int px, int py)
3357 if (game_mm.num_pacman <= 1)
3359 game_mm.num_pacman = 0;
3363 for (i = 0; i < game_mm.num_pacman; i++)
3364 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3367 game_mm.num_pacman--;
3369 for (j = i; j < game_mm.num_pacman; j++)
3371 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3372 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3373 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3377 static void GameActions_MM_Ext(void)
3384 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3387 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3389 element = Tile[x][y];
3391 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3392 StartMoving_MM(x, y);
3393 else if (IS_MOVING(x, y))
3394 ContinueMoving_MM(x, y);
3395 else if (IS_EXPLODING(element))
3396 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3397 else if (element == EL_EXIT_OPENING)
3399 else if (element == EL_GRAY_BALL_OPENING)
3401 else if (IS_ENVELOPE_OPENING(element))
3403 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3405 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3407 else if (IS_MIRROR(element) ||
3408 IS_MIRROR_FIXED(element) ||
3409 element == EL_PRISM)
3410 DrawFieldTwinkle(x, y);
3411 else if (element == EL_GRAY_BALL_ACTIVE ||
3412 element == EL_BOMB_ACTIVE ||
3413 element == EL_MINE_ACTIVE)
3414 DrawFieldAnimated_MM(x, y);
3415 else if (!IS_BLOCKED(x, y))
3416 DrawFieldAnimatedIfNeeded_MM(x, y);
3419 AutoRotateMirrors();
3422 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3424 // redraw after Explode_MM() ...
3426 DrawLaser(0, DL_LASER_ENABLED);
3427 laser.redraw = FALSE;
3432 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3436 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3438 DrawLaser(0, DL_LASER_DISABLED);
3443 // skip all following game actions if game is over
3444 if (game_mm.game_over)
3447 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3451 GameOver_MM(GAME_OVER_NO_ENERGY);
3456 if (FrameReached(&energy_delay))
3458 if (game_mm.energy_left > 0)
3459 game_mm.energy_left--;
3461 // when out of energy, wait another frame to play "out of time" sound
3464 element = laser.dest_element;
3467 if (element != Tile[ELX][ELY])
3469 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3470 element, Tile[ELX][ELY]);
3474 if (!laser.overloaded && laser.overload_value == 0 &&
3475 element != EL_BOMB &&
3476 element != EL_BOMB_ACTIVE &&
3477 element != EL_MINE &&
3478 element != EL_MINE_ACTIVE &&
3479 element != EL_GRAY_BALL &&
3480 element != EL_GRAY_BALL_ACTIVE &&
3481 element != EL_BLOCK_STONE &&
3482 element != EL_BLOCK_WOOD &&
3483 element != EL_FUSE_ON &&
3484 element != EL_FUEL_FULL &&
3485 !IS_WALL_ICE(element) &&
3486 !IS_WALL_AMOEBA(element))
3489 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3491 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3492 (!laser.overloaded && laser.overload_value > 0)) &&
3493 FrameReached(&overload_delay))
3495 if (laser.overloaded)
3496 laser.overload_value++;
3498 laser.overload_value--;
3500 if (game_mm.cheat_no_overload)
3502 laser.overloaded = FALSE;
3503 laser.overload_value = 0;
3506 game_mm.laser_overload_value = laser.overload_value;
3508 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3510 SetLaserColor(0xFF);
3512 DrawLaser(0, DL_LASER_ENABLED);
3515 if (!laser.overloaded)
3516 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3517 else if (setup.sound_loops)
3518 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3520 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3522 if (laser.overload_value == MAX_LASER_OVERLOAD)
3524 UpdateAndDisplayGameControlValues();
3528 GameOver_MM(GAME_OVER_OVERLOADED);
3539 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3541 if (game_mm.cheat_no_explosion)
3546 laser.dest_element = EL_EXPLODING_OPAQUE;
3551 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3553 laser.fuse_off = TRUE;
3557 DrawLaser(0, DL_LASER_DISABLED);
3558 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3561 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3563 if (!Store2[ELX][ELY]) // check if content element not yet determined
3565 int last_anim_random_frame = gfx.anim_random_frame;
3568 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3569 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3571 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3572 native_mm_level.ball_choice_mode, 0,
3573 game_mm.ball_choice_pos);
3575 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3576 gfx.anim_random_frame = last_anim_random_frame;
3578 game_mm.ball_choice_pos++;
3580 int new_element = native_mm_level.ball_content[element_pos];
3581 int new_element_base = map_wall_to_base_element(new_element);
3583 if (IS_WALL(new_element_base))
3585 // always use completely filled wall element
3586 new_element = new_element_base | 0x000f;
3588 else if (native_mm_level.rotate_ball_content &&
3589 get_num_elements(new_element) > 1)
3591 // randomly rotate newly created game element
3592 new_element = get_rotated_element(new_element, RND(16));
3595 Store[ELX][ELY] = new_element;
3596 Store2[ELX][ELY] = TRUE;
3599 if (native_mm_level.explode_ball)
3602 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3604 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3609 if (IS_WALL_ICE(element) && CT > 50)
3611 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3613 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3614 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3615 Store2[ELX][ELY] = laser.wall_mask;
3617 laser.dest_element = Tile[ELX][ELY];
3622 if (IS_WALL_AMOEBA(element) && CT > 60)
3625 int element2 = Tile[ELX][ELY];
3627 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3630 for (i = laser.num_damages - 1; i >= 0; i--)
3631 if (laser.damage[i].is_mirror)
3634 r = laser.num_edges;
3635 d = laser.num_damages;
3642 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3645 DrawLaser(0, DL_LASER_ENABLED);
3648 x = laser.damage[k1].x;
3649 y = laser.damage[k1].y;
3654 for (i = 0; i < 4; i++)
3656 if (laser.wall_mask & (1 << i))
3658 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3659 cSY + ELY * TILEY + 31 * (i / 2)))
3662 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3663 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3670 for (i = 0; i < 4; i++)
3672 if (laser.wall_mask & (1 << i))
3674 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3675 cSY + ELY * TILEY + 31 * (i / 2)))
3682 if (laser.num_beamers > 0 ||
3683 k1 < 1 || k2 < 4 || k3 < 4 ||
3684 CheckLaserPixel(cSX + ELX * TILEX + 14,
3685 cSY + ELY * TILEY + 14))
3687 laser.num_edges = r;
3688 laser.num_damages = d;
3690 DrawLaser(0, DL_LASER_DISABLED);
3693 Tile[ELX][ELY] = element | laser.wall_mask;
3695 int x = ELX, y = ELY;
3696 int wall_mask = laser.wall_mask;
3699 DrawLaser(0, DL_LASER_ENABLED);
3701 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3703 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3704 Store[x][y] = EL_WALL_AMOEBA_BASE;
3705 Store2[x][y] = wall_mask;
3710 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3711 laser.stops_inside_element && CT > native_mm_level.time_block)
3716 if (ABS(XS) > ABS(YS))
3723 for (i = 0; i < 4; i++)
3730 x = ELX + Step[k * 4].x;
3731 y = ELY + Step[k * 4].y;
3733 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3736 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3744 laser.overloaded = (element == EL_BLOCK_STONE);
3749 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3752 Tile[x][y] = element;
3754 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3757 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3759 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3760 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3768 if (element == EL_FUEL_FULL && CT > 10)
3770 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3771 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3773 for (i = start; i <= num_init_game_frames; i++)
3775 if (i == num_init_game_frames)
3776 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3777 else if (setup.sound_loops)
3778 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3780 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3782 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3784 UpdateAndDisplayGameControlValues();
3789 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3791 DrawField_MM(ELX, ELY);
3793 DrawLaser(0, DL_LASER_ENABLED);
3799 void GameActions_MM(struct MouseActionInfo action)
3801 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3802 boolean button_released = (action.button == MB_RELEASED);
3804 GameActions_MM_Ext();
3806 CheckSingleStepMode_MM(element_clicked, button_released);
3809 static void MovePacMen(void)
3811 int mx, my, ox, oy, nx, ny;
3815 if (++pacman_nr >= game_mm.num_pacman)
3818 game_mm.pacman[pacman_nr].dir--;
3820 for (l = 1; l < 5; l++)
3822 game_mm.pacman[pacman_nr].dir++;
3824 if (game_mm.pacman[pacman_nr].dir > 4)
3825 game_mm.pacman[pacman_nr].dir = 1;
3827 if (game_mm.pacman[pacman_nr].dir % 2)
3830 my = game_mm.pacman[pacman_nr].dir - 2;
3835 mx = 3 - game_mm.pacman[pacman_nr].dir;
3838 ox = game_mm.pacman[pacman_nr].x;
3839 oy = game_mm.pacman[pacman_nr].y;
3842 element = Tile[nx][ny];
3844 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3847 if (!IS_EATABLE4PACMAN(element))
3850 if (ObjHit(nx, ny, HIT_POS_CENTER))
3853 Tile[ox][oy] = EL_EMPTY;
3855 EL_PACMAN_RIGHT - 1 +
3856 (game_mm.pacman[pacman_nr].dir - 1 +
3857 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3859 game_mm.pacman[pacman_nr].x = nx;
3860 game_mm.pacman[pacman_nr].y = ny;
3862 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3864 if (element != EL_EMPTY)
3866 int graphic = el2gfx(Tile[nx][ny]);
3871 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3874 ox = cSX + ox * TILEX;
3875 oy = cSY + oy * TILEY;
3877 for (i = 1; i < 33; i += 2)
3878 BlitBitmap(bitmap, window,
3879 src_x, src_y, TILEX, TILEY,
3880 ox + i * mx, oy + i * my);
3881 Ct = Ct + FrameCounter - CT;
3884 DrawField_MM(nx, ny);
3887 if (!laser.fuse_off)
3889 DrawLaser(0, DL_LASER_ENABLED);
3891 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3893 AddDamagedField(nx, ny);
3895 laser.damage[laser.num_damages - 1].edge = 0;
3899 if (element == EL_BOMB)
3900 DeletePacMan(nx, ny);
3902 if (IS_WALL_AMOEBA(element) &&
3903 (LX + 2 * XS) / TILEX == nx &&
3904 (LY + 2 * YS) / TILEY == ny)
3914 static void InitMovingField_MM(int x, int y, int direction)
3916 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3917 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3919 MovDir[x][y] = direction;
3920 MovDir[newx][newy] = direction;
3922 if (Tile[newx][newy] == EL_EMPTY)
3923 Tile[newx][newy] = EL_BLOCKED;
3926 static int MovingOrBlocked2Element_MM(int x, int y)
3928 int element = Tile[x][y];
3930 if (element == EL_BLOCKED)
3934 Blocked2Moving(x, y, &oldx, &oldy);
3936 return Tile[oldx][oldy];
3942 static void RemoveMovingField_MM(int x, int y)
3944 int oldx = x, oldy = y, newx = x, newy = y;
3946 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3949 if (IS_MOVING(x, y))
3951 Moving2Blocked(x, y, &newx, &newy);
3952 if (Tile[newx][newy] != EL_BLOCKED)
3955 else if (Tile[x][y] == EL_BLOCKED)
3957 Blocked2Moving(x, y, &oldx, &oldy);
3958 if (!IS_MOVING(oldx, oldy))
3962 Tile[oldx][oldy] = EL_EMPTY;
3963 Tile[newx][newy] = EL_EMPTY;
3964 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3965 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3967 DrawLevelField_MM(oldx, oldy);
3968 DrawLevelField_MM(newx, newy);
3971 static void RaiseScore_MM(int value)
3973 game_mm.score += value;
3976 void RaiseScoreElement_MM(int element)
3981 case EL_PACMAN_RIGHT:
3983 case EL_PACMAN_LEFT:
3984 case EL_PACMAN_DOWN:
3985 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3989 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3994 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3998 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4007 // ----------------------------------------------------------------------------
4008 // Mirror Magic game engine snapshot handling functions
4009 // ----------------------------------------------------------------------------
4011 void SaveEngineSnapshotValues_MM(void)
4015 engine_snapshot_mm.game_mm = game_mm;
4016 engine_snapshot_mm.laser = laser;
4018 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4020 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4022 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4023 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4024 engine_snapshot_mm.Box[x][y] = Box[x][y];
4025 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4029 engine_snapshot_mm.LX = LX;
4030 engine_snapshot_mm.LY = LY;
4031 engine_snapshot_mm.XS = XS;
4032 engine_snapshot_mm.YS = YS;
4033 engine_snapshot_mm.ELX = ELX;
4034 engine_snapshot_mm.ELY = ELY;
4035 engine_snapshot_mm.CT = CT;
4036 engine_snapshot_mm.Ct = Ct;
4038 engine_snapshot_mm.last_LX = last_LX;
4039 engine_snapshot_mm.last_LY = last_LY;
4040 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4041 engine_snapshot_mm.hold_x = hold_x;
4042 engine_snapshot_mm.hold_y = hold_y;
4043 engine_snapshot_mm.pacman_nr = pacman_nr;
4045 engine_snapshot_mm.rotate_delay = rotate_delay;
4046 engine_snapshot_mm.pacman_delay = pacman_delay;
4047 engine_snapshot_mm.energy_delay = energy_delay;
4048 engine_snapshot_mm.overload_delay = overload_delay;
4051 void LoadEngineSnapshotValues_MM(void)
4055 // stored engine snapshot buffers already restored at this point
4057 game_mm = engine_snapshot_mm.game_mm;
4058 laser = engine_snapshot_mm.laser;
4060 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4062 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4064 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4065 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4066 Box[x][y] = engine_snapshot_mm.Box[x][y];
4067 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4071 LX = engine_snapshot_mm.LX;
4072 LY = engine_snapshot_mm.LY;
4073 XS = engine_snapshot_mm.XS;
4074 YS = engine_snapshot_mm.YS;
4075 ELX = engine_snapshot_mm.ELX;
4076 ELY = engine_snapshot_mm.ELY;
4077 CT = engine_snapshot_mm.CT;
4078 Ct = engine_snapshot_mm.Ct;
4080 last_LX = engine_snapshot_mm.last_LX;
4081 last_LY = engine_snapshot_mm.last_LY;
4082 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4083 hold_x = engine_snapshot_mm.hold_x;
4084 hold_y = engine_snapshot_mm.hold_y;
4085 pacman_nr = engine_snapshot_mm.pacman_nr;
4087 rotate_delay = engine_snapshot_mm.rotate_delay;
4088 pacman_delay = engine_snapshot_mm.pacman_delay;
4089 energy_delay = engine_snapshot_mm.energy_delay;
4090 overload_delay = engine_snapshot_mm.overload_delay;
4092 RedrawPlayfield_MM();
4095 static int getAngleFromTouchDelta(int dx, int dy, int base)
4097 double pi = 3.141592653;
4098 double rad = atan2((double)-dy, (double)dx);
4099 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4100 double deg = rad2 * 180.0 / pi;
4102 return (int)(deg * base / 360.0 + 0.5) % base;
4105 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4107 // calculate start (source) position to be at the middle of the tile
4108 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4109 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4110 int dx = dst_mx - src_mx;
4111 int dy = dst_my - src_my;
4120 if (!IN_LEV_FIELD(x, y))
4123 element = Tile[x][y];
4125 if (!IS_MCDUFFIN(element) &&
4126 !IS_MIRROR(element) &&
4127 !IS_BEAMER(element) &&
4128 !IS_POLAR(element) &&
4129 !IS_POLAR_CROSS(element) &&
4130 !IS_DF_MIRROR(element))
4133 angle_old = get_element_angle(element);
4135 if (IS_MCDUFFIN(element))
4137 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4138 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4139 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4140 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4143 else if (IS_MIRROR(element) ||
4144 IS_DF_MIRROR(element))
4146 for (i = 0; i < laser.num_damages; i++)
4148 if (laser.damage[i].x == x &&
4149 laser.damage[i].y == y &&
4150 ObjHit(x, y, HIT_POS_CENTER))
4152 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4153 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4160 if (angle_new == -1)
4162 if (IS_MIRROR(element) ||
4163 IS_DF_MIRROR(element) ||
4167 if (IS_POLAR_CROSS(element))
4170 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4173 button = (angle_new == angle_old ? 0 :
4174 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4175 MB_LEFTBUTTON : MB_RIGHTBUTTON);