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 // check if laser scan has hit two diagonally adjacent element corners
1076 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1077 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1079 // check if laser scan has crossed element boundaries (not just mini tiles)
1080 boolean cross_x = (getLevelFromLaserX(LX) != getLevelFromLaserX(LX + 2));
1081 boolean cross_y = (getLevelFromLaserY(LY) != getLevelFromLaserY(LY + 2));
1083 if (cross_x || cross_y)
1085 // hit something at next tile -- check out what it was
1086 ELX = getLevelFromLaserX(LX + XS);
1087 ELY = getLevelFromLaserY(LY + YS);
1091 // hit something at same tile -- check out what it was
1092 ELX = getLevelFromLaserX(LX);
1093 ELY = getLevelFromLaserY(LY);
1097 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1098 hit_mask, LX, LY, ELX, ELY);
1101 if (!IN_LEV_FIELD(ELX, ELY))
1103 // laser next step position
1104 int x = cSX + LX + XS;
1105 int y = cSY + LY + YS;
1107 // check if next step of laser is still inside visible playfield area
1108 if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
1109 y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
1111 // go on with another step
1119 laser.dest_element = element;
1124 // handle special case of laser hitting two diagonally adjacent elements
1125 // (with or without a third corner element behind these two elements)
1126 if ((diag_1 || diag_2) && cross_x && cross_y)
1128 // compare the two diagonally adjacent elements
1130 int yoffset = 2 * (diag_1 ? -1 : +1);
1131 int elx1 = (LX - xoffset) / TILEX;
1132 int ely1 = (LY + yoffset) / TILEY;
1133 int elx2 = (LX + xoffset) / TILEX;
1134 int ely2 = (LY - yoffset) / TILEY;
1135 int e1 = Tile[elx1][ely1];
1136 int e2 = Tile[elx2][ely2];
1137 boolean use_element_1 = FALSE;
1139 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1141 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1142 use_element_1 = (RND(2) ? TRUE : FALSE);
1143 else if (IS_WALL_ICE(e1))
1144 use_element_1 = TRUE;
1146 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1148 // if both tiles match, we can just select the first one
1149 if (IS_WALL_AMOEBA(e1))
1150 use_element_1 = TRUE;
1152 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1154 // if both tiles match, we can just select the first one
1155 if (IS_ABSORBING_BLOCK(e1))
1156 use_element_1 = TRUE;
1159 ELX = (use_element_1 ? elx1 : elx2);
1160 ELY = (use_element_1 ? ely1 : ely2);
1164 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1165 hit_mask, LX, LY, ELX, ELY);
1168 last_element = element;
1170 element = Tile[ELX][ELY];
1171 laser.dest_element = element;
1174 Debug("game:mm:ScanLaser",
1175 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1178 LX % TILEX, LY % TILEY,
1183 if (!IN_LEV_FIELD(ELX, ELY))
1184 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1188 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1189 if (element == EL_EMPTY &&
1190 IS_GRID_STEEL(last_element) &&
1191 laser.current_angle % 4) // angle is not 90°
1192 element = last_element;
1194 if (element == EL_EMPTY)
1196 if (!HitOnlyAnEdge(hit_mask))
1199 else if (element == EL_FUSE_ON)
1201 if (HitPolarizer(element, hit_mask))
1204 else if (IS_GRID(element) || IS_DF_GRID(element))
1206 if (HitPolarizer(element, hit_mask))
1209 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1210 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1212 if (HitBlock(element, hit_mask))
1219 else if (IS_MCDUFFIN(element))
1221 if (HitLaserSource(element, hit_mask))
1224 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1225 IS_RECEIVER(element))
1227 if (HitLaserDestination(element, hit_mask))
1230 else if (IS_WALL(element))
1232 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1234 if (HitReflectingWalls(element, hit_mask))
1239 if (HitAbsorbingWalls(element, hit_mask))
1245 if (HitElement(element, hit_mask))
1250 DrawLaser(rf - 1, DL_LASER_ENABLED);
1251 rf = laser.num_edges;
1253 if (!IS_DF_WALL_STEEL(element))
1255 // only used for scanning DF steel walls; reset for all other elements
1263 if (laser.dest_element != Tile[ELX][ELY])
1265 Debug("game:mm:ScanLaser",
1266 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1267 laser.dest_element, Tile[ELX][ELY]);
1271 if (!end && !laser.stops_inside_element && !StepBehind())
1274 Debug("game:mm:ScanLaser", "Go one step back");
1280 AddLaserEdge(LX, LY);
1284 DrawLaser(rf - 1, DL_LASER_ENABLED);
1286 Ct = CT = FrameCounter;
1289 if (!IN_LEV_FIELD(ELX, ELY))
1290 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1294 static void ScanLaser_FromLastMirror(void)
1296 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1299 for (i = start_pos; i >= 0; i--)
1300 if (laser.damage[i].is_mirror)
1303 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1305 DrawLaser(start_edge, DL_LASER_DISABLED);
1310 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1316 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1317 start_edge, num_edges, mode);
1322 Warn("DrawLaserExt: start_edge < 0");
1329 Warn("DrawLaserExt: num_edges < 0");
1335 if (mode == DL_LASER_DISABLED)
1337 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1341 // now draw the laser to the backbuffer and (if enabled) to the screen
1342 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1344 redraw_mask |= REDRAW_FIELD;
1346 if (mode == DL_LASER_ENABLED)
1349 // after the laser was deleted, the "damaged" graphics must be restored
1350 if (laser.num_damages)
1352 int damage_start = 0;
1355 // determine the starting edge, from which graphics need to be restored
1358 for (i = 0; i < laser.num_damages; i++)
1360 if (laser.damage[i].edge == start_edge + 1)
1369 // restore graphics from this starting edge to the end of damage list
1370 for (i = damage_start; i < laser.num_damages; i++)
1372 int lx = laser.damage[i].x;
1373 int ly = laser.damage[i].y;
1374 int element = Tile[lx][ly];
1376 if (Hit[lx][ly] == laser.damage[i].edge)
1377 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1380 if (Box[lx][ly] == laser.damage[i].edge)
1383 if (IS_DRAWABLE(element))
1384 DrawField_MM(lx, ly);
1387 elx = laser.damage[damage_start].x;
1388 ely = laser.damage[damage_start].y;
1389 element = Tile[elx][ely];
1392 if (IS_BEAMER(element))
1396 for (i = 0; i < laser.num_beamers; i++)
1397 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1399 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1400 mode, elx, ely, Hit[elx][ely], start_edge);
1401 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1402 get_element_angle(element), laser.damage[damage_start].angle);
1406 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1407 laser.num_beamers > 0 &&
1408 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1410 // element is outgoing beamer
1411 laser.num_damages = damage_start + 1;
1413 if (IS_BEAMER(element))
1414 laser.current_angle = get_element_angle(element);
1418 // element is incoming beamer or other element
1419 laser.num_damages = damage_start;
1420 laser.current_angle = laser.damage[laser.num_damages].angle;
1425 // no damages but McDuffin himself (who needs to be redrawn anyway)
1427 elx = laser.start_edge.x;
1428 ely = laser.start_edge.y;
1429 element = Tile[elx][ely];
1432 laser.num_edges = start_edge + 1;
1433 if (start_edge == 0)
1434 laser.current_angle = laser.start_angle;
1436 LX = laser.edge[start_edge].x - cSX2;
1437 LY = laser.edge[start_edge].y - cSY2;
1438 XS = 2 * Step[laser.current_angle].x;
1439 YS = 2 * Step[laser.current_angle].y;
1442 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1448 if (IS_BEAMER(element) ||
1449 IS_FIBRE_OPTIC(element) ||
1450 IS_PACMAN(element) ||
1451 IS_POLAR(element) ||
1452 IS_POLAR_CROSS(element) ||
1453 element == EL_FUSE_ON)
1458 Debug("game:mm:DrawLaserExt", "element == %d", element);
1461 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1462 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1466 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1467 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1468 (laser.num_beamers == 0 ||
1469 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1471 // element is incoming beamer or other element
1472 step_size = -step_size;
1477 if (IS_BEAMER(element))
1478 Debug("game:mm:DrawLaserExt",
1479 "start_edge == %d, laser.beamer_edge == %d",
1480 start_edge, laser.beamer_edge);
1483 LX += step_size * XS;
1484 LY += step_size * YS;
1486 else if (element != EL_EMPTY)
1495 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1500 void DrawLaser(int start_edge, int mode)
1502 // do not draw laser if fuse is off
1503 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1506 if (mode == DL_LASER_DISABLED)
1507 DeactivateLaserTargetElement();
1509 if (laser.num_edges - start_edge < 0)
1511 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1516 // check if laser is interrupted by beamer element
1517 if (laser.num_beamers > 0 &&
1518 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1520 if (mode == DL_LASER_ENABLED)
1523 int tmp_start_edge = start_edge;
1525 // draw laser segments forward from the start to the last beamer
1526 for (i = 0; i < laser.num_beamers; i++)
1528 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1530 if (tmp_num_edges <= 0)
1534 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1535 i, laser.beamer_edge[i], tmp_start_edge);
1538 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1540 tmp_start_edge = laser.beamer_edge[i];
1543 // draw last segment from last beamer to the end
1544 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1550 int last_num_edges = laser.num_edges;
1551 int num_beamers = laser.num_beamers;
1553 // delete laser segments backward from the end to the first beamer
1554 for (i = num_beamers - 1; i >= 0; i--)
1556 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1558 if (laser.beamer_edge[i] - start_edge <= 0)
1561 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1563 last_num_edges = laser.beamer_edge[i];
1564 laser.num_beamers--;
1568 if (last_num_edges - start_edge <= 0)
1569 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1570 last_num_edges, start_edge);
1573 // special case when rotating first beamer: delete laser edge on beamer
1574 // (but do not start scanning on previous edge to prevent mirror sound)
1575 if (last_num_edges - start_edge == 1 && start_edge > 0)
1576 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1578 // delete first segment from start to the first beamer
1579 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1584 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1587 game_mm.laser_enabled = mode;
1590 void DrawLaser_MM(void)
1592 DrawLaser(0, game_mm.laser_enabled);
1595 static boolean HitElement(int element, int hit_mask)
1597 if (HitOnlyAnEdge(hit_mask))
1600 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1601 element = MovingOrBlocked2Element_MM(ELX, ELY);
1604 Debug("game:mm:HitElement", "(1): element == %d", element);
1608 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1609 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1612 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1616 AddDamagedField(ELX, ELY);
1618 // this is more precise: check if laser would go through the center
1619 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1623 // prevent cutting through laser emitter with laser beam
1624 if (IS_LASER(element))
1627 // skip the whole element before continuing the scan
1635 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1637 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1639 /* skipping scan positions to the right and down skips one scan
1640 position too much, because this is only the top left scan position
1641 of totally four scan positions (plus one to the right, one to the
1642 bottom and one to the bottom right) */
1643 /* ... but only roll back scan position if more than one step done */
1653 Debug("game:mm:HitElement", "(2): element == %d", element);
1656 if (LX + 5 * XS < 0 ||
1666 Debug("game:mm:HitElement", "(3): element == %d", element);
1669 if (IS_POLAR(element) &&
1670 ((element - EL_POLAR_START) % 2 ||
1671 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1673 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1675 laser.num_damages--;
1680 if (IS_POLAR_CROSS(element) &&
1681 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1683 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1685 laser.num_damages--;
1690 if (!IS_BEAMER(element) &&
1691 !IS_FIBRE_OPTIC(element) &&
1692 !IS_GRID_WOOD(element) &&
1693 element != EL_FUEL_EMPTY)
1696 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1697 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1699 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1702 LX = ELX * TILEX + 14;
1703 LY = ELY * TILEY + 14;
1705 AddLaserEdge(LX, LY);
1708 if (IS_MIRROR(element) ||
1709 IS_MIRROR_FIXED(element) ||
1710 IS_POLAR(element) ||
1711 IS_POLAR_CROSS(element) ||
1712 IS_DF_MIRROR(element) ||
1713 IS_DF_MIRROR_AUTO(element) ||
1714 IS_DF_MIRROR_FIXED(element) ||
1715 element == EL_PRISM ||
1716 element == EL_REFRACTOR)
1718 int current_angle = laser.current_angle;
1721 laser.num_damages--;
1723 AddDamagedField(ELX, ELY);
1725 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1728 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1730 if (IS_MIRROR(element) ||
1731 IS_MIRROR_FIXED(element) ||
1732 IS_DF_MIRROR(element) ||
1733 IS_DF_MIRROR_AUTO(element) ||
1734 IS_DF_MIRROR_FIXED(element))
1735 laser.current_angle = get_mirrored_angle(laser.current_angle,
1736 get_element_angle(element));
1738 if (element == EL_PRISM || element == EL_REFRACTOR)
1739 laser.current_angle = RND(16);
1741 XS = 2 * Step[laser.current_angle].x;
1742 YS = 2 * Step[laser.current_angle].y;
1744 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1749 LX += step_size * XS;
1750 LY += step_size * YS;
1752 // draw sparkles on mirror
1753 if ((IS_MIRROR(element) ||
1754 IS_MIRROR_FIXED(element) ||
1755 element == EL_PRISM) &&
1756 current_angle != laser.current_angle)
1758 MovDelay[ELX][ELY] = 11; // start animation
1761 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1762 current_angle != laser.current_angle)
1763 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1766 (get_opposite_angle(laser.current_angle) ==
1767 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1769 return (laser.overloaded ? TRUE : FALSE);
1772 if (element == EL_FUEL_FULL)
1774 laser.stops_inside_element = TRUE;
1779 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1781 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1783 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1784 element == EL_MINE ? EL_MINE_ACTIVE :
1785 EL_GRAY_BALL_ACTIVE);
1787 GfxFrame[ELX][ELY] = 0; // restart animation
1789 laser.dest_element_last = Tile[ELX][ELY];
1790 laser.dest_element_last_x = ELX;
1791 laser.dest_element_last_y = ELY;
1793 if (element == EL_MINE)
1794 laser.overloaded = TRUE;
1797 if (element == EL_KETTLE ||
1798 element == EL_CELL ||
1799 element == EL_KEY ||
1800 element == EL_LIGHTBALL ||
1801 element == EL_PACMAN ||
1802 IS_PACMAN(element) ||
1803 IS_ENVELOPE(element))
1805 if (!IS_PACMAN(element) &&
1806 !IS_ENVELOPE(element))
1809 if (element == EL_PACMAN)
1812 if (element == EL_KETTLE || element == EL_CELL)
1814 if (game_mm.kettles_still_needed > 0)
1815 game_mm.kettles_still_needed--;
1817 game.snapshot.collected_item = TRUE;
1819 if (game_mm.kettles_still_needed == 0)
1823 DrawLaser(0, DL_LASER_ENABLED);
1826 else if (element == EL_KEY)
1830 else if (IS_PACMAN(element))
1832 DeletePacMan(ELX, ELY);
1834 else if (IS_ENVELOPE(element))
1836 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1839 RaiseScoreElement_MM(element);
1844 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1846 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1848 DrawLaser(0, DL_LASER_ENABLED);
1850 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1852 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1853 game_mm.lights_still_needed--;
1857 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1858 game_mm.lights_still_needed++;
1861 DrawField_MM(ELX, ELY);
1862 DrawLaser(0, DL_LASER_ENABLED);
1867 laser.stops_inside_element = TRUE;
1873 Debug("game:mm:HitElement", "(4): element == %d", element);
1876 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1877 laser.num_beamers < MAX_NUM_BEAMERS &&
1878 laser.beamer[BEAMER_NR(element)][1].num)
1880 int beamer_angle = get_element_angle(element);
1881 int beamer_nr = BEAMER_NR(element);
1885 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1888 laser.num_damages--;
1890 if (IS_FIBRE_OPTIC(element) ||
1891 laser.current_angle == get_opposite_angle(beamer_angle))
1895 LX = ELX * TILEX + 14;
1896 LY = ELY * TILEY + 14;
1898 AddLaserEdge(LX, LY);
1899 AddDamagedField(ELX, ELY);
1901 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1904 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1906 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1907 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1908 ELX = laser.beamer[beamer_nr][pos].x;
1909 ELY = laser.beamer[beamer_nr][pos].y;
1910 LX = ELX * TILEX + 14;
1911 LY = ELY * TILEY + 14;
1913 if (IS_BEAMER(element))
1915 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1916 XS = 2 * Step[laser.current_angle].x;
1917 YS = 2 * Step[laser.current_angle].y;
1920 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1922 AddLaserEdge(LX, LY);
1923 AddDamagedField(ELX, ELY);
1925 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1928 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1930 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1935 LX += step_size * XS;
1936 LY += step_size * YS;
1938 laser.num_beamers++;
1947 static boolean HitOnlyAnEdge(int hit_mask)
1949 // check if the laser hit only the edge of an element and, if so, go on
1952 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1956 if ((hit_mask == HIT_MASK_TOPLEFT ||
1957 hit_mask == HIT_MASK_TOPRIGHT ||
1958 hit_mask == HIT_MASK_BOTTOMLEFT ||
1959 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1960 laser.current_angle % 4) // angle is not 90°
1964 if (hit_mask == HIT_MASK_TOPLEFT)
1969 else if (hit_mask == HIT_MASK_TOPRIGHT)
1974 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1979 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1985 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1991 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1998 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
2004 static boolean HitPolarizer(int element, int hit_mask)
2006 if (HitOnlyAnEdge(hit_mask))
2009 if (IS_DF_GRID(element))
2011 int grid_angle = get_element_angle(element);
2014 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2015 grid_angle, laser.current_angle);
2018 AddLaserEdge(LX, LY);
2019 AddDamagedField(ELX, ELY);
2022 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2024 if (laser.current_angle == grid_angle ||
2025 laser.current_angle == get_opposite_angle(grid_angle))
2027 // skip the whole element before continuing the scan
2033 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2035 if (LX/TILEX > ELX || LY/TILEY > ELY)
2037 /* skipping scan positions to the right and down skips one scan
2038 position too much, because this is only the top left scan position
2039 of totally four scan positions (plus one to the right, one to the
2040 bottom and one to the bottom right) */
2046 AddLaserEdge(LX, LY);
2052 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2054 LX / TILEX, LY / TILEY,
2055 LX % TILEX, LY % TILEY);
2060 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2062 return HitReflectingWalls(element, hit_mask);
2066 return HitAbsorbingWalls(element, hit_mask);
2069 else if (IS_GRID_STEEL(element))
2071 // may be required if graphics for steel grid redefined
2072 AddDamagedField(ELX, ELY);
2074 return HitReflectingWalls(element, hit_mask);
2076 else // IS_GRID_WOOD
2078 // may be required if graphics for wooden grid redefined
2079 AddDamagedField(ELX, ELY);
2081 return HitAbsorbingWalls(element, hit_mask);
2087 static boolean HitBlock(int element, int hit_mask)
2089 boolean check = FALSE;
2091 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2092 game_mm.num_keys == 0)
2095 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2098 int ex = ELX * TILEX + 14;
2099 int ey = ELY * TILEY + 14;
2103 for (i = 1; i < 32; i++)
2108 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2113 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2114 return HitAbsorbingWalls(element, hit_mask);
2118 AddLaserEdge(LX - XS, LY - YS);
2119 AddDamagedField(ELX, ELY);
2122 Box[ELX][ELY] = laser.num_edges;
2124 return HitReflectingWalls(element, hit_mask);
2127 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2129 int xs = XS / 2, ys = YS / 2;
2131 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2132 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2134 laser.overloaded = (element == EL_GATE_STONE);
2139 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2140 (hit_mask == HIT_MASK_TOP ||
2141 hit_mask == HIT_MASK_LEFT ||
2142 hit_mask == HIT_MASK_RIGHT ||
2143 hit_mask == HIT_MASK_BOTTOM))
2144 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2145 hit_mask == HIT_MASK_BOTTOM),
2146 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2147 hit_mask == HIT_MASK_RIGHT));
2148 AddLaserEdge(LX, LY);
2154 if (element == EL_GATE_STONE && Box[ELX][ELY])
2156 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2168 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2170 int xs = XS / 2, ys = YS / 2;
2172 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2173 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2175 laser.overloaded = (element == EL_BLOCK_STONE);
2180 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2181 (hit_mask == HIT_MASK_TOP ||
2182 hit_mask == HIT_MASK_LEFT ||
2183 hit_mask == HIT_MASK_RIGHT ||
2184 hit_mask == HIT_MASK_BOTTOM))
2185 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2186 hit_mask == HIT_MASK_BOTTOM),
2187 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2188 hit_mask == HIT_MASK_RIGHT));
2189 AddDamagedField(ELX, ELY);
2191 LX = ELX * TILEX + 14;
2192 LY = ELY * TILEY + 14;
2194 AddLaserEdge(LX, LY);
2196 laser.stops_inside_element = TRUE;
2204 static boolean HitLaserSource(int element, int hit_mask)
2206 if (HitOnlyAnEdge(hit_mask))
2209 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2211 laser.overloaded = TRUE;
2216 static boolean HitLaserDestination(int element, int hit_mask)
2218 if (HitOnlyAnEdge(hit_mask))
2221 if (element != EL_EXIT_OPEN &&
2222 !(IS_RECEIVER(element) &&
2223 game_mm.kettles_still_needed == 0 &&
2224 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2226 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2231 if (IS_RECEIVER(element) ||
2232 (IS_22_5_ANGLE(laser.current_angle) &&
2233 (ELX != (LX + 6 * XS) / TILEX ||
2234 ELY != (LY + 6 * YS) / TILEY ||
2243 LX = ELX * TILEX + 14;
2244 LY = ELY * TILEY + 14;
2246 laser.stops_inside_element = TRUE;
2249 AddLaserEdge(LX, LY);
2250 AddDamagedField(ELX, ELY);
2252 if (game_mm.lights_still_needed == 0)
2254 game_mm.level_solved = TRUE;
2256 SetTileCursorActive(FALSE);
2262 static boolean HitReflectingWalls(int element, int hit_mask)
2264 // check if laser hits side of a wall with an angle that is not 90°
2265 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2266 hit_mask == HIT_MASK_LEFT ||
2267 hit_mask == HIT_MASK_RIGHT ||
2268 hit_mask == HIT_MASK_BOTTOM))
2270 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2275 if (!IS_DF_GRID(element))
2276 AddLaserEdge(LX, LY);
2278 // check if laser hits wall with an angle of 45°
2279 if (!IS_22_5_ANGLE(laser.current_angle))
2281 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2284 laser.current_angle = get_mirrored_angle(laser.current_angle,
2287 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2290 laser.current_angle = get_mirrored_angle(laser.current_angle,
2294 AddLaserEdge(LX, LY);
2296 XS = 2 * Step[laser.current_angle].x;
2297 YS = 2 * Step[laser.current_angle].y;
2301 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2303 laser.current_angle = get_mirrored_angle(laser.current_angle,
2308 if (!IS_DF_GRID(element))
2309 AddLaserEdge(LX, LY);
2314 if (!IS_DF_GRID(element))
2315 AddLaserEdge(LX, LY + YS / 2);
2318 if (!IS_DF_GRID(element))
2319 AddLaserEdge(LX, LY);
2322 YS = 2 * Step[laser.current_angle].y;
2326 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2328 laser.current_angle = get_mirrored_angle(laser.current_angle,
2333 if (!IS_DF_GRID(element))
2334 AddLaserEdge(LX, LY);
2339 if (!IS_DF_GRID(element))
2340 AddLaserEdge(LX + XS / 2, LY);
2343 if (!IS_DF_GRID(element))
2344 AddLaserEdge(LX, LY);
2347 XS = 2 * Step[laser.current_angle].x;
2353 // reflection at the edge of reflecting DF style wall
2354 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2356 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2357 hit_mask == HIT_MASK_TOPRIGHT) ||
2358 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2359 hit_mask == HIT_MASK_TOPLEFT) ||
2360 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2361 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2362 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2363 hit_mask == HIT_MASK_BOTTOMRIGHT))
2366 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2367 ANG_MIRROR_135 : ANG_MIRROR_45);
2369 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2371 AddDamagedField(ELX, ELY);
2372 AddLaserEdge(LX, LY);
2374 laser.current_angle = get_mirrored_angle(laser.current_angle,
2382 AddLaserEdge(LX, LY);
2388 // reflection inside an edge of reflecting DF style wall
2389 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2391 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2392 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2393 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2394 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2395 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2396 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2397 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2398 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2401 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2402 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2403 ANG_MIRROR_135 : ANG_MIRROR_45);
2405 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2408 AddDamagedField(ELX, ELY);
2411 AddLaserEdge(LX - XS, LY - YS);
2412 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2413 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2415 laser.current_angle = get_mirrored_angle(laser.current_angle,
2423 AddLaserEdge(LX, LY);
2429 // check if laser hits DF style wall with an angle of 90°
2430 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2432 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2433 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2434 (IS_VERT_ANGLE(laser.current_angle) &&
2435 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2437 // laser at last step touched nothing or the same side of the wall
2438 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2440 AddDamagedField(ELX, ELY);
2447 last_hit_mask = hit_mask;
2454 if (!HitOnlyAnEdge(hit_mask))
2456 laser.overloaded = TRUE;
2464 static boolean HitAbsorbingWalls(int element, int hit_mask)
2466 if (HitOnlyAnEdge(hit_mask))
2470 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2472 AddLaserEdge(LX - XS, LY - YS);
2479 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2481 AddLaserEdge(LX - XS, LY - YS);
2487 if (IS_WALL_WOOD(element) ||
2488 IS_DF_WALL_WOOD(element) ||
2489 IS_GRID_WOOD(element) ||
2490 IS_GRID_WOOD_FIXED(element) ||
2491 IS_GRID_WOOD_AUTO(element) ||
2492 element == EL_FUSE_ON ||
2493 element == EL_BLOCK_WOOD ||
2494 element == EL_GATE_WOOD)
2496 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2501 if (IS_WALL_ICE(element))
2507 // check if laser hit adjacent edges of two diagonal tiles
2508 if (ELX != lx / TILEX)
2510 if (ELY != ly / TILEY)
2513 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2514 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2516 // check if laser hits wall with an angle of 90°
2517 if (IS_90_ANGLE(laser.current_angle))
2518 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2520 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2524 for (i = 0; i < 4; i++)
2526 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2527 mask = 15 - (8 >> i);
2528 else if (ABS(XS) == 4 &&
2530 (XS > 0) == (i % 2) &&
2531 (YS < 0) == (i / 2))
2532 mask = 3 + (i / 2) * 9;
2533 else if (ABS(YS) == 4 &&
2535 (XS < 0) == (i % 2) &&
2536 (YS > 0) == (i / 2))
2537 mask = 5 + (i % 2) * 5;
2541 laser.wall_mask = mask;
2543 else if (IS_WALL_AMOEBA(element))
2545 int elx = (LX - 2 * XS) / TILEX;
2546 int ely = (LY - 2 * YS) / TILEY;
2547 int element2 = Tile[elx][ely];
2550 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2552 laser.dest_element = EL_EMPTY;
2560 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2561 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2563 if (IS_90_ANGLE(laser.current_angle))
2564 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2566 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2568 laser.wall_mask = mask;
2574 static void OpenExit(int x, int y)
2578 if (!MovDelay[x][y]) // next animation frame
2579 MovDelay[x][y] = 4 * delay;
2581 if (MovDelay[x][y]) // wait some time before next frame
2586 phase = MovDelay[x][y] / delay;
2588 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2589 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2591 if (!MovDelay[x][y])
2593 Tile[x][y] = EL_EXIT_OPEN;
2599 static void OpenGrayBall(int x, int y)
2603 if (!MovDelay[x][y]) // next animation frame
2605 if (IS_WALL(Store[x][y]))
2607 DrawWalls_MM(x, y, Store[x][y]);
2609 // copy wall tile to spare bitmap for "melting" animation
2610 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2611 TILEX, TILEY, x * TILEX, y * TILEY);
2613 DrawElement_MM(x, y, EL_GRAY_BALL);
2616 MovDelay[x][y] = 50 * delay;
2619 if (MovDelay[x][y]) // wait some time before next frame
2623 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2627 int dx = RND(26), dy = RND(26);
2629 if (IS_WALL(Store[x][y]))
2631 // copy wall tile from spare bitmap for "melting" animation
2632 bitmap = bitmap_db_field;
2638 int graphic = el2gfx(Store[x][y]);
2640 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2643 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2644 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2646 laser.redraw = TRUE;
2648 MarkTileDirty(x, y);
2651 if (!MovDelay[x][y])
2653 Tile[x][y] = Store[x][y];
2654 Store[x][y] = Store2[x][y] = 0;
2655 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2657 InitField(x, y, FALSE);
2660 ScanLaser_FromLastMirror();
2665 static void OpenEnvelope(int x, int y)
2667 int num_frames = 8; // seven frames plus final empty space
2669 if (!MovDelay[x][y]) // next animation frame
2670 MovDelay[x][y] = num_frames;
2672 if (MovDelay[x][y]) // wait some time before next frame
2674 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2678 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2680 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2681 int frame = num_frames - MovDelay[x][y] - 1;
2683 DrawGraphicAnimation_MM(x, y, graphic, frame);
2685 laser.redraw = TRUE;
2688 if (MovDelay[x][y] == 0)
2690 Tile[x][y] = EL_EMPTY;
2701 static void MeltIce(int x, int y)
2706 if (!MovDelay[x][y]) // next animation frame
2707 MovDelay[x][y] = frames * delay;
2709 if (MovDelay[x][y]) // wait some time before next frame
2712 int wall_mask = Store2[x][y];
2713 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2716 phase = frames - MovDelay[x][y] / delay - 1;
2718 if (!MovDelay[x][y])
2720 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2721 Store[x][y] = Store2[x][y] = 0;
2723 DrawWalls_MM(x, y, Tile[x][y]);
2725 if (Tile[x][y] == EL_WALL_ICE_BASE)
2726 Tile[x][y] = EL_EMPTY;
2728 ScanLaser_FromLastMirror();
2730 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2732 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2734 laser.redraw = TRUE;
2739 static void GrowAmoeba(int x, int y)
2744 if (!MovDelay[x][y]) // next animation frame
2745 MovDelay[x][y] = frames * delay;
2747 if (MovDelay[x][y]) // wait some time before next frame
2750 int wall_mask = Store2[x][y];
2751 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2754 phase = MovDelay[x][y] / delay;
2756 if (!MovDelay[x][y])
2758 Tile[x][y] = real_element;
2759 Store[x][y] = Store2[x][y] = 0;
2761 DrawWalls_MM(x, y, Tile[x][y]);
2762 DrawLaser(0, DL_LASER_ENABLED);
2764 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2766 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2771 static void DrawFieldAnimated_MM(int x, int y)
2775 laser.redraw = TRUE;
2778 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2780 int element = Tile[x][y];
2781 int graphic = el2gfx(element);
2783 if (!getGraphicInfo_NewFrame(x, y, graphic))
2788 laser.redraw = TRUE;
2791 static void DrawFieldTwinkle(int x, int y)
2793 if (MovDelay[x][y] != 0) // wait some time before next frame
2799 if (MovDelay[x][y] != 0)
2801 int graphic = IMG_TWINKLE_WHITE;
2802 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2804 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2807 laser.redraw = TRUE;
2811 static void Explode_MM(int x, int y, int phase, int mode)
2813 int num_phase = 9, delay = 2;
2814 int last_phase = num_phase * delay;
2815 int half_phase = (num_phase / 2) * delay;
2818 laser.redraw = TRUE;
2820 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2822 center_element = Tile[x][y];
2824 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2826 // put moving element to center field (and let it explode there)
2827 center_element = MovingOrBlocked2Element_MM(x, y);
2828 RemoveMovingField_MM(x, y);
2830 Tile[x][y] = center_element;
2833 if (center_element != EL_GRAY_BALL_ACTIVE)
2834 Store[x][y] = EL_EMPTY;
2835 Store2[x][y] = center_element;
2837 Tile[x][y] = EL_EXPLODING_OPAQUE;
2839 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2840 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2841 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2844 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2846 ExplodePhase[x][y] = 1;
2852 GfxFrame[x][y] = 0; // restart explosion animation
2854 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2856 center_element = Store2[x][y];
2858 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2860 Tile[x][y] = EL_EXPLODING_TRANSP;
2862 if (x == ELX && y == ELY)
2866 if (phase == last_phase)
2868 if (center_element == EL_BOMB_ACTIVE)
2870 DrawLaser(0, DL_LASER_DISABLED);
2873 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2875 laser.overloaded = FALSE;
2877 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2879 GameOver_MM(GAME_OVER_BOMB);
2882 Tile[x][y] = Store[x][y];
2884 Store[x][y] = Store2[x][y] = 0;
2885 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2887 InitField(x, y, FALSE);
2890 if (center_element == EL_GRAY_BALL_ACTIVE)
2891 ScanLaser_FromLastMirror();
2893 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2895 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2896 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2898 DrawGraphicAnimation_MM(x, y, graphic, frame);
2900 MarkTileDirty(x, y);
2904 static void Bang_MM(int x, int y)
2906 int element = Tile[x][y];
2908 if (IS_PACMAN(element))
2909 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2910 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2911 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2912 else if (element == EL_KEY)
2913 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2915 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2917 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2920 static void TurnRound(int x, int y)
2932 { 0, 0 }, { 0, 0 }, { 0, 0 },
2937 int left, right, back;
2941 { MV_DOWN, MV_UP, MV_RIGHT },
2942 { MV_UP, MV_DOWN, MV_LEFT },
2944 { MV_LEFT, MV_RIGHT, MV_DOWN },
2948 { MV_RIGHT, MV_LEFT, MV_UP }
2951 int element = Tile[x][y];
2952 int old_move_dir = MovDir[x][y];
2953 int right_dir = turn[old_move_dir].right;
2954 int back_dir = turn[old_move_dir].back;
2955 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2956 int right_x = x + right_dx, right_y = y + right_dy;
2958 if (element == EL_PACMAN)
2960 boolean can_turn_right = FALSE;
2962 if (IN_LEV_FIELD(right_x, right_y) &&
2963 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2964 can_turn_right = TRUE;
2967 MovDir[x][y] = right_dir;
2969 MovDir[x][y] = back_dir;
2975 static void StartMoving_MM(int x, int y)
2977 int element = Tile[x][y];
2982 if (CAN_MOVE(element))
2986 if (MovDelay[x][y]) // wait some time before next movement
2994 // now make next step
2996 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2998 if (element == EL_PACMAN &&
2999 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
3000 !ObjHit(newx, newy, HIT_POS_CENTER))
3002 Store[newx][newy] = Tile[newx][newy];
3003 Tile[newx][newy] = EL_EMPTY;
3005 DrawField_MM(newx, newy);
3007 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3008 ObjHit(newx, newy, HIT_POS_CENTER))
3010 // object was running against a wall
3017 InitMovingField_MM(x, y, MovDir[x][y]);
3021 ContinueMoving_MM(x, y);
3024 static void ContinueMoving_MM(int x, int y)
3026 int element = Tile[x][y];
3027 int direction = MovDir[x][y];
3028 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3029 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3030 int horiz_move = (dx!=0);
3031 int newx = x + dx, newy = y + dy;
3032 int step = (horiz_move ? dx : dy) * TILEX / 8;
3034 MovPos[x][y] += step;
3036 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3038 Tile[x][y] = EL_EMPTY;
3039 Tile[newx][newy] = element;
3041 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3042 MovDelay[newx][newy] = 0;
3044 if (!CAN_MOVE(element))
3045 MovDir[newx][newy] = 0;
3048 DrawField_MM(newx, newy);
3050 Stop[newx][newy] = TRUE;
3052 if (element == EL_PACMAN)
3054 if (Store[newx][newy] == EL_BOMB)
3055 Bang_MM(newx, newy);
3057 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3058 (LX + 2 * XS) / TILEX == newx &&
3059 (LY + 2 * YS) / TILEY == newy)
3066 else // still moving on
3071 laser.redraw = TRUE;
3074 boolean ClickElement(int x, int y, int button)
3076 static DelayCounter click_delay = { CLICK_DELAY };
3077 static boolean new_button = TRUE;
3078 boolean element_clicked = FALSE;
3083 // initialize static variables
3084 click_delay.count = 0;
3085 click_delay.value = CLICK_DELAY;
3091 // do not rotate objects hit by the laser after the game was solved
3092 if (game_mm.level_solved && Hit[x][y])
3095 if (button == MB_RELEASED)
3098 click_delay.value = CLICK_DELAY;
3100 // release eventually hold auto-rotating mirror
3101 RotateMirror(x, y, MB_RELEASED);
3106 if (!FrameReached(&click_delay) && !new_button)
3109 if (button == MB_MIDDLEBUTTON) // middle button has no function
3112 if (!IN_LEV_FIELD(x, y))
3115 if (Tile[x][y] == EL_EMPTY)
3118 element = Tile[x][y];
3120 if (IS_MIRROR(element) ||
3121 IS_BEAMER(element) ||
3122 IS_POLAR(element) ||
3123 IS_POLAR_CROSS(element) ||
3124 IS_DF_MIRROR(element) ||
3125 IS_DF_MIRROR_AUTO(element))
3127 RotateMirror(x, y, button);
3129 element_clicked = TRUE;
3131 else if (IS_MCDUFFIN(element))
3133 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3135 if (has_laser && !laser.fuse_off)
3136 DrawLaser(0, DL_LASER_DISABLED);
3138 element = get_rotated_element(element, BUTTON_ROTATION(button));
3140 Tile[x][y] = element;
3145 laser.start_angle = get_element_angle(element);
3149 if (!laser.fuse_off)
3153 element_clicked = TRUE;
3155 else if (element == EL_FUSE_ON && laser.fuse_off)
3157 if (x != laser.fuse_x || y != laser.fuse_y)
3160 laser.fuse_off = FALSE;
3161 laser.fuse_x = laser.fuse_y = -1;
3163 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3166 element_clicked = TRUE;
3168 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3170 laser.fuse_off = TRUE;
3173 laser.overloaded = FALSE;
3175 DrawLaser(0, DL_LASER_DISABLED);
3176 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3178 element_clicked = TRUE;
3180 else if (element == EL_LIGHTBALL)
3183 RaiseScoreElement_MM(element);
3184 DrawLaser(0, DL_LASER_ENABLED);
3186 element_clicked = TRUE;
3188 else if (IS_ENVELOPE(element))
3190 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3192 element_clicked = TRUE;
3195 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3198 return element_clicked;
3201 static void RotateMirror(int x, int y, int button)
3203 if (button == MB_RELEASED)
3205 // release eventually hold auto-rotating mirror
3212 if (IS_MIRROR(Tile[x][y]) ||
3213 IS_POLAR_CROSS(Tile[x][y]) ||
3214 IS_POLAR(Tile[x][y]) ||
3215 IS_BEAMER(Tile[x][y]) ||
3216 IS_DF_MIRROR(Tile[x][y]) ||
3217 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3218 IS_GRID_WOOD_AUTO(Tile[x][y]))
3220 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3222 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3224 if (button == MB_LEFTBUTTON)
3226 // left mouse button only for manual adjustment, no auto-rotating;
3227 // freeze mirror for until mouse button released
3231 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3233 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3237 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3239 int edge = Hit[x][y];
3245 DrawLaser(edge - 1, DL_LASER_DISABLED);
3249 else if (ObjHit(x, y, HIT_POS_CENTER))
3251 int edge = Hit[x][y];
3255 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3260 DrawLaser(edge - 1, DL_LASER_DISABLED);
3267 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3272 if ((IS_BEAMER(Tile[x][y]) ||
3273 IS_POLAR(Tile[x][y]) ||
3274 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3276 if (IS_BEAMER(Tile[x][y]))
3279 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3280 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3293 DrawLaser(0, DL_LASER_ENABLED);
3297 static void AutoRotateMirrors(void)
3301 if (!FrameReached(&rotate_delay))
3304 for (x = 0; x < lev_fieldx; x++)
3306 for (y = 0; y < lev_fieldy; y++)
3308 int element = Tile[x][y];
3310 // do not rotate objects hit by the laser after the game was solved
3311 if (game_mm.level_solved && Hit[x][y])
3314 if (IS_DF_MIRROR_AUTO(element) ||
3315 IS_GRID_WOOD_AUTO(element) ||
3316 IS_GRID_STEEL_AUTO(element) ||
3317 element == EL_REFRACTOR)
3319 RotateMirror(x, y, MB_RIGHTBUTTON);
3321 laser.redraw = TRUE;
3327 static boolean ObjHit(int obx, int oby, int bits)
3334 if (bits & HIT_POS_CENTER)
3336 if (CheckLaserPixel(cSX + obx + 15,
3341 if (bits & HIT_POS_EDGE)
3343 for (i = 0; i < 4; i++)
3344 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3345 cSY + oby + 31 * (i / 2)))
3349 if (bits & HIT_POS_BETWEEN)
3351 for (i = 0; i < 4; i++)
3352 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3353 cSY + 4 + oby + 22 * (i / 2)))
3360 static void DeletePacMan(int px, int py)
3366 if (game_mm.num_pacman <= 1)
3368 game_mm.num_pacman = 0;
3372 for (i = 0; i < game_mm.num_pacman; i++)
3373 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3376 game_mm.num_pacman--;
3378 for (j = i; j < game_mm.num_pacman; j++)
3380 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3381 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3382 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3386 static void GameActions_MM_Ext(void)
3393 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3396 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3398 element = Tile[x][y];
3400 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3401 StartMoving_MM(x, y);
3402 else if (IS_MOVING(x, y))
3403 ContinueMoving_MM(x, y);
3404 else if (IS_EXPLODING(element))
3405 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3406 else if (element == EL_EXIT_OPENING)
3408 else if (element == EL_GRAY_BALL_OPENING)
3410 else if (IS_ENVELOPE_OPENING(element))
3412 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3414 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3416 else if (IS_MIRROR(element) ||
3417 IS_MIRROR_FIXED(element) ||
3418 element == EL_PRISM)
3419 DrawFieldTwinkle(x, y);
3420 else if (element == EL_GRAY_BALL_ACTIVE ||
3421 element == EL_BOMB_ACTIVE ||
3422 element == EL_MINE_ACTIVE)
3423 DrawFieldAnimated_MM(x, y);
3424 else if (!IS_BLOCKED(x, y))
3425 DrawFieldAnimatedIfNeeded_MM(x, y);
3428 AutoRotateMirrors();
3431 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3433 // redraw after Explode_MM() ...
3435 DrawLaser(0, DL_LASER_ENABLED);
3436 laser.redraw = FALSE;
3441 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3445 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3447 DrawLaser(0, DL_LASER_DISABLED);
3452 // skip all following game actions if game is over
3453 if (game_mm.game_over)
3456 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3460 GameOver_MM(GAME_OVER_NO_ENERGY);
3465 if (FrameReached(&energy_delay))
3467 if (game_mm.energy_left > 0)
3468 game_mm.energy_left--;
3470 // when out of energy, wait another frame to play "out of time" sound
3473 element = laser.dest_element;
3476 if (element != Tile[ELX][ELY])
3478 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3479 element, Tile[ELX][ELY]);
3483 if (!laser.overloaded && laser.overload_value == 0 &&
3484 element != EL_BOMB &&
3485 element != EL_BOMB_ACTIVE &&
3486 element != EL_MINE &&
3487 element != EL_MINE_ACTIVE &&
3488 element != EL_GRAY_BALL &&
3489 element != EL_GRAY_BALL_ACTIVE &&
3490 element != EL_BLOCK_STONE &&
3491 element != EL_BLOCK_WOOD &&
3492 element != EL_FUSE_ON &&
3493 element != EL_FUEL_FULL &&
3494 !IS_WALL_ICE(element) &&
3495 !IS_WALL_AMOEBA(element))
3498 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3500 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3501 (!laser.overloaded && laser.overload_value > 0)) &&
3502 FrameReached(&overload_delay))
3504 if (laser.overloaded)
3505 laser.overload_value++;
3507 laser.overload_value--;
3509 if (game_mm.cheat_no_overload)
3511 laser.overloaded = FALSE;
3512 laser.overload_value = 0;
3515 game_mm.laser_overload_value = laser.overload_value;
3517 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3519 SetLaserColor(0xFF);
3521 DrawLaser(0, DL_LASER_ENABLED);
3524 if (!laser.overloaded)
3525 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3526 else if (setup.sound_loops)
3527 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3529 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3531 if (laser.overload_value == MAX_LASER_OVERLOAD)
3533 UpdateAndDisplayGameControlValues();
3537 GameOver_MM(GAME_OVER_OVERLOADED);
3548 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3550 if (game_mm.cheat_no_explosion)
3555 laser.dest_element = EL_EXPLODING_OPAQUE;
3560 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3562 laser.fuse_off = TRUE;
3566 DrawLaser(0, DL_LASER_DISABLED);
3567 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3570 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3572 if (!Store2[ELX][ELY]) // check if content element not yet determined
3574 int last_anim_random_frame = gfx.anim_random_frame;
3577 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3578 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3580 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3581 native_mm_level.ball_choice_mode, 0,
3582 game_mm.ball_choice_pos);
3584 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3585 gfx.anim_random_frame = last_anim_random_frame;
3587 game_mm.ball_choice_pos++;
3589 int new_element = native_mm_level.ball_content[element_pos];
3590 int new_element_base = map_wall_to_base_element(new_element);
3592 if (IS_WALL(new_element_base))
3594 // always use completely filled wall element
3595 new_element = new_element_base | 0x000f;
3597 else if (native_mm_level.rotate_ball_content &&
3598 get_num_elements(new_element) > 1)
3600 // randomly rotate newly created game element
3601 new_element = get_rotated_element(new_element, RND(16));
3604 Store[ELX][ELY] = new_element;
3605 Store2[ELX][ELY] = TRUE;
3608 if (native_mm_level.explode_ball)
3611 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3613 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3618 if (IS_WALL_ICE(element) && CT > 50)
3620 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3622 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3623 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3624 Store2[ELX][ELY] = laser.wall_mask;
3626 laser.dest_element = Tile[ELX][ELY];
3631 if (IS_WALL_AMOEBA(element) && CT > 60)
3634 int element2 = Tile[ELX][ELY];
3636 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3639 for (i = laser.num_damages - 1; i >= 0; i--)
3640 if (laser.damage[i].is_mirror)
3643 r = laser.num_edges;
3644 d = laser.num_damages;
3651 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3654 DrawLaser(0, DL_LASER_ENABLED);
3657 x = laser.damage[k1].x;
3658 y = laser.damage[k1].y;
3663 for (i = 0; i < 4; i++)
3665 if (laser.wall_mask & (1 << i))
3667 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3668 cSY + ELY * TILEY + 31 * (i / 2)))
3671 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3672 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3679 for (i = 0; i < 4; i++)
3681 if (laser.wall_mask & (1 << i))
3683 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3684 cSY + ELY * TILEY + 31 * (i / 2)))
3691 if (laser.num_beamers > 0 ||
3692 k1 < 1 || k2 < 4 || k3 < 4 ||
3693 CheckLaserPixel(cSX + ELX * TILEX + 14,
3694 cSY + ELY * TILEY + 14))
3696 laser.num_edges = r;
3697 laser.num_damages = d;
3699 DrawLaser(0, DL_LASER_DISABLED);
3702 Tile[ELX][ELY] = element | laser.wall_mask;
3704 int x = ELX, y = ELY;
3705 int wall_mask = laser.wall_mask;
3708 DrawLaser(0, DL_LASER_ENABLED);
3710 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3712 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3713 Store[x][y] = EL_WALL_AMOEBA_BASE;
3714 Store2[x][y] = wall_mask;
3719 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3720 laser.stops_inside_element && CT > native_mm_level.time_block)
3725 if (ABS(XS) > ABS(YS))
3732 for (i = 0; i < 4; i++)
3739 x = ELX + Step[k * 4].x;
3740 y = ELY + Step[k * 4].y;
3742 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3745 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3753 laser.overloaded = (element == EL_BLOCK_STONE);
3758 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3761 Tile[x][y] = element;
3763 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3766 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3768 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3769 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3777 if (element == EL_FUEL_FULL && CT > 10)
3779 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3780 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3782 for (i = start; i <= num_init_game_frames; i++)
3784 if (i == num_init_game_frames)
3785 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3786 else if (setup.sound_loops)
3787 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3789 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3791 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3793 UpdateAndDisplayGameControlValues();
3798 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3800 DrawField_MM(ELX, ELY);
3802 DrawLaser(0, DL_LASER_ENABLED);
3808 void GameActions_MM(struct MouseActionInfo action)
3810 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3811 boolean button_released = (action.button == MB_RELEASED);
3813 GameActions_MM_Ext();
3815 CheckSingleStepMode_MM(element_clicked, button_released);
3818 static void MovePacMen(void)
3820 int mx, my, ox, oy, nx, ny;
3824 if (++pacman_nr >= game_mm.num_pacman)
3827 game_mm.pacman[pacman_nr].dir--;
3829 for (l = 1; l < 5; l++)
3831 game_mm.pacman[pacman_nr].dir++;
3833 if (game_mm.pacman[pacman_nr].dir > 4)
3834 game_mm.pacman[pacman_nr].dir = 1;
3836 if (game_mm.pacman[pacman_nr].dir % 2)
3839 my = game_mm.pacman[pacman_nr].dir - 2;
3844 mx = 3 - game_mm.pacman[pacman_nr].dir;
3847 ox = game_mm.pacman[pacman_nr].x;
3848 oy = game_mm.pacman[pacman_nr].y;
3851 element = Tile[nx][ny];
3853 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3856 if (!IS_EATABLE4PACMAN(element))
3859 if (ObjHit(nx, ny, HIT_POS_CENTER))
3862 Tile[ox][oy] = EL_EMPTY;
3864 EL_PACMAN_RIGHT - 1 +
3865 (game_mm.pacman[pacman_nr].dir - 1 +
3866 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3868 game_mm.pacman[pacman_nr].x = nx;
3869 game_mm.pacman[pacman_nr].y = ny;
3871 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3873 if (element != EL_EMPTY)
3875 int graphic = el2gfx(Tile[nx][ny]);
3880 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3883 ox = cSX + ox * TILEX;
3884 oy = cSY + oy * TILEY;
3886 for (i = 1; i < 33; i += 2)
3887 BlitBitmap(bitmap, window,
3888 src_x, src_y, TILEX, TILEY,
3889 ox + i * mx, oy + i * my);
3890 Ct = Ct + FrameCounter - CT;
3893 DrawField_MM(nx, ny);
3896 if (!laser.fuse_off)
3898 DrawLaser(0, DL_LASER_ENABLED);
3900 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3902 AddDamagedField(nx, ny);
3904 laser.damage[laser.num_damages - 1].edge = 0;
3908 if (element == EL_BOMB)
3909 DeletePacMan(nx, ny);
3911 if (IS_WALL_AMOEBA(element) &&
3912 (LX + 2 * XS) / TILEX == nx &&
3913 (LY + 2 * YS) / TILEY == ny)
3923 static void InitMovingField_MM(int x, int y, int direction)
3925 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3926 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3928 MovDir[x][y] = direction;
3929 MovDir[newx][newy] = direction;
3931 if (Tile[newx][newy] == EL_EMPTY)
3932 Tile[newx][newy] = EL_BLOCKED;
3935 static int MovingOrBlocked2Element_MM(int x, int y)
3937 int element = Tile[x][y];
3939 if (element == EL_BLOCKED)
3943 Blocked2Moving(x, y, &oldx, &oldy);
3945 return Tile[oldx][oldy];
3951 static void RemoveMovingField_MM(int x, int y)
3953 int oldx = x, oldy = y, newx = x, newy = y;
3955 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3958 if (IS_MOVING(x, y))
3960 Moving2Blocked(x, y, &newx, &newy);
3961 if (Tile[newx][newy] != EL_BLOCKED)
3964 else if (Tile[x][y] == EL_BLOCKED)
3966 Blocked2Moving(x, y, &oldx, &oldy);
3967 if (!IS_MOVING(oldx, oldy))
3971 Tile[oldx][oldy] = EL_EMPTY;
3972 Tile[newx][newy] = EL_EMPTY;
3973 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3974 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3976 DrawLevelField_MM(oldx, oldy);
3977 DrawLevelField_MM(newx, newy);
3980 static void RaiseScore_MM(int value)
3982 game_mm.score += value;
3985 void RaiseScoreElement_MM(int element)
3990 case EL_PACMAN_RIGHT:
3992 case EL_PACMAN_LEFT:
3993 case EL_PACMAN_DOWN:
3994 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3998 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4003 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4007 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4016 // ----------------------------------------------------------------------------
4017 // Mirror Magic game engine snapshot handling functions
4018 // ----------------------------------------------------------------------------
4020 void SaveEngineSnapshotValues_MM(void)
4024 engine_snapshot_mm.game_mm = game_mm;
4025 engine_snapshot_mm.laser = laser;
4027 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4029 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4031 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4032 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4033 engine_snapshot_mm.Box[x][y] = Box[x][y];
4034 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4038 engine_snapshot_mm.LX = LX;
4039 engine_snapshot_mm.LY = LY;
4040 engine_snapshot_mm.XS = XS;
4041 engine_snapshot_mm.YS = YS;
4042 engine_snapshot_mm.ELX = ELX;
4043 engine_snapshot_mm.ELY = ELY;
4044 engine_snapshot_mm.CT = CT;
4045 engine_snapshot_mm.Ct = Ct;
4047 engine_snapshot_mm.last_LX = last_LX;
4048 engine_snapshot_mm.last_LY = last_LY;
4049 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4050 engine_snapshot_mm.hold_x = hold_x;
4051 engine_snapshot_mm.hold_y = hold_y;
4052 engine_snapshot_mm.pacman_nr = pacman_nr;
4054 engine_snapshot_mm.rotate_delay = rotate_delay;
4055 engine_snapshot_mm.pacman_delay = pacman_delay;
4056 engine_snapshot_mm.energy_delay = energy_delay;
4057 engine_snapshot_mm.overload_delay = overload_delay;
4060 void LoadEngineSnapshotValues_MM(void)
4064 // stored engine snapshot buffers already restored at this point
4066 game_mm = engine_snapshot_mm.game_mm;
4067 laser = engine_snapshot_mm.laser;
4069 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4071 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4073 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4074 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4075 Box[x][y] = engine_snapshot_mm.Box[x][y];
4076 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4080 LX = engine_snapshot_mm.LX;
4081 LY = engine_snapshot_mm.LY;
4082 XS = engine_snapshot_mm.XS;
4083 YS = engine_snapshot_mm.YS;
4084 ELX = engine_snapshot_mm.ELX;
4085 ELY = engine_snapshot_mm.ELY;
4086 CT = engine_snapshot_mm.CT;
4087 Ct = engine_snapshot_mm.Ct;
4089 last_LX = engine_snapshot_mm.last_LX;
4090 last_LY = engine_snapshot_mm.last_LY;
4091 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4092 hold_x = engine_snapshot_mm.hold_x;
4093 hold_y = engine_snapshot_mm.hold_y;
4094 pacman_nr = engine_snapshot_mm.pacman_nr;
4096 rotate_delay = engine_snapshot_mm.rotate_delay;
4097 pacman_delay = engine_snapshot_mm.pacman_delay;
4098 energy_delay = engine_snapshot_mm.energy_delay;
4099 overload_delay = engine_snapshot_mm.overload_delay;
4101 RedrawPlayfield_MM();
4104 static int getAngleFromTouchDelta(int dx, int dy, int base)
4106 double pi = 3.141592653;
4107 double rad = atan2((double)-dy, (double)dx);
4108 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4109 double deg = rad2 * 180.0 / pi;
4111 return (int)(deg * base / 360.0 + 0.5) % base;
4114 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4116 // calculate start (source) position to be at the middle of the tile
4117 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4118 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4119 int dx = dst_mx - src_mx;
4120 int dy = dst_my - src_my;
4129 if (!IN_LEV_FIELD(x, y))
4132 element = Tile[x][y];
4134 if (!IS_MCDUFFIN(element) &&
4135 !IS_MIRROR(element) &&
4136 !IS_BEAMER(element) &&
4137 !IS_POLAR(element) &&
4138 !IS_POLAR_CROSS(element) &&
4139 !IS_DF_MIRROR(element))
4142 angle_old = get_element_angle(element);
4144 if (IS_MCDUFFIN(element))
4146 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4147 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4148 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4149 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4152 else if (IS_MIRROR(element) ||
4153 IS_DF_MIRROR(element))
4155 for (i = 0; i < laser.num_damages; i++)
4157 if (laser.damage[i].x == x &&
4158 laser.damage[i].y == y &&
4159 ObjHit(x, y, HIT_POS_CENTER))
4161 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4162 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4169 if (angle_new == -1)
4171 if (IS_MIRROR(element) ||
4172 IS_DF_MIRROR(element) ||
4176 if (IS_POLAR_CROSS(element))
4179 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4182 button = (angle_new == angle_old ? 0 :
4183 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4184 MB_LEFTBUTTON : MB_RIGHTBUTTON);