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 ScanPixel(void)
901 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
902 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
905 // follow laser beam until it hits something (at least the screen border)
906 while (hit_mask == HIT_MASK_NO_HIT)
912 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
913 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
915 Debug("game:mm:ScanPixel", "touched screen border!");
921 // check if laser scan has crossed element boundaries (not just mini tiles)
922 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
923 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
925 if (cross_x && cross_y)
927 int elx1 = (LX - XS) / TILEX;
928 int ely1 = (LY + YS) / TILEY;
929 int elx2 = (LX + XS) / TILEX;
930 int ely2 = (LY - YS) / TILEY;
932 // add element corners left and right from the laser beam to damage list
934 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
935 AddDamagedField(elx1, ely1);
937 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
938 AddDamagedField(elx2, ely2);
941 for (i = 0; i < 4; i++)
943 int px = LX + (i % 2) * 2;
944 int py = LY + (i / 2) * 2;
947 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
948 int ly = (py + TILEY) / TILEY - 1; // negative values!
951 if (IN_LEV_FIELD(lx, ly))
953 int element = Tile[lx][ly];
955 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
959 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
961 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
963 pixel = ((element & (1 << pos)) ? 1 : 0);
967 int pos = getMaskFromElement(element);
969 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
974 // check if laser is still inside visible playfield area
975 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
976 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
979 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
980 hit_mask |= (1 << i);
983 if (hit_mask == HIT_MASK_NO_HIT)
985 // hit nothing -- go on with another step
994 static void DeactivateLaserTargetElement(void)
996 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
997 laser.dest_element_last == EL_MINE_ACTIVE ||
998 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
999 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1001 int x = laser.dest_element_last_x;
1002 int y = laser.dest_element_last_y;
1003 int element = laser.dest_element_last;
1005 if (Tile[x][y] == element)
1006 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1007 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1009 if (Tile[x][y] == EL_GRAY_BALL)
1012 laser.dest_element_last = EL_EMPTY;
1013 laser.dest_element_last_x = -1;
1014 laser.dest_element_last_y = -1;
1018 static void ScanLaser(void)
1020 int element = EL_EMPTY;
1021 int last_element = EL_EMPTY;
1022 int end = 0, rf = laser.num_edges;
1024 // do not scan laser again after the game was lost for whatever reason
1025 if (game_mm.game_over)
1028 // do not scan laser if fuse is off
1032 DeactivateLaserTargetElement();
1034 laser.overloaded = FALSE;
1035 laser.stops_inside_element = FALSE;
1037 DrawLaser(0, DL_LASER_ENABLED);
1040 Debug("game:mm:ScanLaser",
1041 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1049 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1052 laser.overloaded = TRUE;
1057 hit_mask = ScanPixel();
1060 Debug("game:mm:ScanLaser",
1061 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1065 // hit something -- check out what it was
1066 ELX = (LX + XS + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
1067 ELY = (LY + YS + TILEY) / TILEY - 1; // negative values!
1070 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1071 hit_mask, LX, LY, ELX, ELY);
1074 if (!IN_LEV_FIELD(ELX, ELY))
1076 // check if laser is still inside visible playfield area
1077 if (cSX + LX >= REAL_SX && cSX + LX < REAL_SX + FULL_SXSIZE &&
1078 cSY + LY >= REAL_SY && cSY + LY < REAL_SY + FULL_SYSIZE)
1080 // go on with another step
1088 laser.dest_element = element;
1093 // check if laser scan has hit two diagonally adjacent element corners
1094 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1095 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1097 // check if laser scan has crossed element boundaries (not just mini tiles)
1098 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1099 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1101 // handle special case of laser hitting two diagonally adjacent elements
1102 // (with or without a third corner element behind these two elements)
1103 if ((diag_1 || diag_2) && cross_x && cross_y)
1105 // compare the two diagonally adjacent elements
1107 int yoffset = 2 * (diag_1 ? -1 : +1);
1108 int elx1 = (LX - xoffset) / TILEX;
1109 int ely1 = (LY + yoffset) / TILEY;
1110 int elx2 = (LX + xoffset) / TILEX;
1111 int ely2 = (LY - yoffset) / TILEY;
1112 int e1 = Tile[elx1][ely1];
1113 int e2 = Tile[elx2][ely2];
1114 boolean use_element_1 = FALSE;
1116 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1118 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1119 use_element_1 = (RND(2) ? TRUE : FALSE);
1120 else if (IS_WALL_ICE(e1))
1121 use_element_1 = TRUE;
1123 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1125 // if both tiles match, we can just select the first one
1126 if (IS_WALL_AMOEBA(e1))
1127 use_element_1 = TRUE;
1129 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1131 // if both tiles match, we can just select the first one
1132 if (IS_ABSORBING_BLOCK(e1))
1133 use_element_1 = TRUE;
1136 ELX = (use_element_1 ? elx1 : elx2);
1137 ELY = (use_element_1 ? ely1 : ely2);
1141 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1142 hit_mask, LX, LY, ELX, ELY);
1145 last_element = element;
1147 element = Tile[ELX][ELY];
1148 laser.dest_element = element;
1151 Debug("game:mm:ScanLaser",
1152 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1155 LX % TILEX, LY % TILEY,
1160 if (!IN_LEV_FIELD(ELX, ELY))
1161 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1165 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1166 if (element == EL_EMPTY &&
1167 IS_GRID_STEEL(last_element) &&
1168 laser.current_angle % 4) // angle is not 90°
1169 element = last_element;
1171 if (element == EL_EMPTY)
1173 if (!HitOnlyAnEdge(hit_mask))
1176 else if (element == EL_FUSE_ON)
1178 if (HitPolarizer(element, hit_mask))
1181 else if (IS_GRID(element) || IS_DF_GRID(element))
1183 if (HitPolarizer(element, hit_mask))
1186 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1187 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1189 if (HitBlock(element, hit_mask))
1196 else if (IS_MCDUFFIN(element))
1198 if (HitLaserSource(element, hit_mask))
1201 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1202 IS_RECEIVER(element))
1204 if (HitLaserDestination(element, hit_mask))
1207 else if (IS_WALL(element))
1209 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1211 if (HitReflectingWalls(element, hit_mask))
1216 if (HitAbsorbingWalls(element, hit_mask))
1222 if (HitElement(element, hit_mask))
1227 DrawLaser(rf - 1, DL_LASER_ENABLED);
1228 rf = laser.num_edges;
1230 if (!IS_DF_WALL_STEEL(element))
1232 // only used for scanning DF steel walls; reset for all other elements
1240 if (laser.dest_element != Tile[ELX][ELY])
1242 Debug("game:mm:ScanLaser",
1243 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1244 laser.dest_element, Tile[ELX][ELY]);
1248 if (!end && !laser.stops_inside_element && !StepBehind())
1251 Debug("game:mm:ScanLaser", "Go one step back");
1257 AddLaserEdge(LX, LY);
1261 DrawLaser(rf - 1, DL_LASER_ENABLED);
1263 Ct = CT = FrameCounter;
1266 if (!IN_LEV_FIELD(ELX, ELY))
1267 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1271 static void ScanLaser_FromLastMirror(void)
1273 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1276 for (i = start_pos; i >= 0; i--)
1277 if (laser.damage[i].is_mirror)
1280 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1282 DrawLaser(start_edge, DL_LASER_DISABLED);
1287 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1293 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1294 start_edge, num_edges, mode);
1299 Warn("DrawLaserExt: start_edge < 0");
1306 Warn("DrawLaserExt: num_edges < 0");
1312 if (mode == DL_LASER_DISABLED)
1314 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1318 // now draw the laser to the backbuffer and (if enabled) to the screen
1319 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1321 redraw_mask |= REDRAW_FIELD;
1323 if (mode == DL_LASER_ENABLED)
1326 // after the laser was deleted, the "damaged" graphics must be restored
1327 if (laser.num_damages)
1329 int damage_start = 0;
1332 // determine the starting edge, from which graphics need to be restored
1335 for (i = 0; i < laser.num_damages; i++)
1337 if (laser.damage[i].edge == start_edge + 1)
1346 // restore graphics from this starting edge to the end of damage list
1347 for (i = damage_start; i < laser.num_damages; i++)
1349 int lx = laser.damage[i].x;
1350 int ly = laser.damage[i].y;
1351 int element = Tile[lx][ly];
1353 if (Hit[lx][ly] == laser.damage[i].edge)
1354 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1357 if (Box[lx][ly] == laser.damage[i].edge)
1360 if (IS_DRAWABLE(element))
1361 DrawField_MM(lx, ly);
1364 elx = laser.damage[damage_start].x;
1365 ely = laser.damage[damage_start].y;
1366 element = Tile[elx][ely];
1369 if (IS_BEAMER(element))
1373 for (i = 0; i < laser.num_beamers; i++)
1374 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1376 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1377 mode, elx, ely, Hit[elx][ely], start_edge);
1378 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1379 get_element_angle(element), laser.damage[damage_start].angle);
1383 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1384 laser.num_beamers > 0 &&
1385 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1387 // element is outgoing beamer
1388 laser.num_damages = damage_start + 1;
1390 if (IS_BEAMER(element))
1391 laser.current_angle = get_element_angle(element);
1395 // element is incoming beamer or other element
1396 laser.num_damages = damage_start;
1397 laser.current_angle = laser.damage[laser.num_damages].angle;
1402 // no damages but McDuffin himself (who needs to be redrawn anyway)
1404 elx = laser.start_edge.x;
1405 ely = laser.start_edge.y;
1406 element = Tile[elx][ely];
1409 laser.num_edges = start_edge + 1;
1410 if (start_edge == 0)
1411 laser.current_angle = laser.start_angle;
1413 LX = laser.edge[start_edge].x - cSX2;
1414 LY = laser.edge[start_edge].y - cSY2;
1415 XS = 2 * Step[laser.current_angle].x;
1416 YS = 2 * Step[laser.current_angle].y;
1419 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1425 if (IS_BEAMER(element) ||
1426 IS_FIBRE_OPTIC(element) ||
1427 IS_PACMAN(element) ||
1428 IS_POLAR(element) ||
1429 IS_POLAR_CROSS(element) ||
1430 element == EL_FUSE_ON)
1435 Debug("game:mm:DrawLaserExt", "element == %d", element);
1438 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1439 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1443 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1444 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1445 (laser.num_beamers == 0 ||
1446 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1448 // element is incoming beamer or other element
1449 step_size = -step_size;
1454 if (IS_BEAMER(element))
1455 Debug("game:mm:DrawLaserExt",
1456 "start_edge == %d, laser.beamer_edge == %d",
1457 start_edge, laser.beamer_edge);
1460 LX += step_size * XS;
1461 LY += step_size * YS;
1463 else if (element != EL_EMPTY)
1472 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1477 void DrawLaser(int start_edge, int mode)
1479 // do not draw laser if fuse is off
1480 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1483 if (mode == DL_LASER_DISABLED)
1484 DeactivateLaserTargetElement();
1486 if (laser.num_edges - start_edge < 0)
1488 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1493 // check if laser is interrupted by beamer element
1494 if (laser.num_beamers > 0 &&
1495 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1497 if (mode == DL_LASER_ENABLED)
1500 int tmp_start_edge = start_edge;
1502 // draw laser segments forward from the start to the last beamer
1503 for (i = 0; i < laser.num_beamers; i++)
1505 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1507 if (tmp_num_edges <= 0)
1511 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1512 i, laser.beamer_edge[i], tmp_start_edge);
1515 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1517 tmp_start_edge = laser.beamer_edge[i];
1520 // draw last segment from last beamer to the end
1521 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1527 int last_num_edges = laser.num_edges;
1528 int num_beamers = laser.num_beamers;
1530 // delete laser segments backward from the end to the first beamer
1531 for (i = num_beamers - 1; i >= 0; i--)
1533 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1535 if (laser.beamer_edge[i] - start_edge <= 0)
1538 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1540 last_num_edges = laser.beamer_edge[i];
1541 laser.num_beamers--;
1545 if (last_num_edges - start_edge <= 0)
1546 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1547 last_num_edges, start_edge);
1550 // special case when rotating first beamer: delete laser edge on beamer
1551 // (but do not start scanning on previous edge to prevent mirror sound)
1552 if (last_num_edges - start_edge == 1 && start_edge > 0)
1553 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1555 // delete first segment from start to the first beamer
1556 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1561 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1564 game_mm.laser_enabled = mode;
1567 void DrawLaser_MM(void)
1569 DrawLaser(0, game_mm.laser_enabled);
1572 static boolean HitElement(int element, int hit_mask)
1574 if (HitOnlyAnEdge(hit_mask))
1577 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1578 element = MovingOrBlocked2Element_MM(ELX, ELY);
1581 Debug("game:mm:HitElement", "(1): element == %d", element);
1585 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1586 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1589 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1593 AddDamagedField(ELX, ELY);
1595 // this is more precise: check if laser would go through the center
1596 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1600 // prevent cutting through laser emitter with laser beam
1601 if (IS_LASER(element))
1604 // skip the whole element before continuing the scan
1612 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1614 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1616 /* skipping scan positions to the right and down skips one scan
1617 position too much, because this is only the top left scan position
1618 of totally four scan positions (plus one to the right, one to the
1619 bottom and one to the bottom right) */
1620 /* ... but only roll back scan position if more than one step done */
1630 Debug("game:mm:HitElement", "(2): element == %d", element);
1633 if (LX + 5 * XS < 0 ||
1643 Debug("game:mm:HitElement", "(3): element == %d", element);
1646 if (IS_POLAR(element) &&
1647 ((element - EL_POLAR_START) % 2 ||
1648 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1650 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1652 laser.num_damages--;
1657 if (IS_POLAR_CROSS(element) &&
1658 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1660 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1662 laser.num_damages--;
1667 if (!IS_BEAMER(element) &&
1668 !IS_FIBRE_OPTIC(element) &&
1669 !IS_GRID_WOOD(element) &&
1670 element != EL_FUEL_EMPTY)
1673 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1674 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1676 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1679 LX = ELX * TILEX + 14;
1680 LY = ELY * TILEY + 14;
1682 AddLaserEdge(LX, LY);
1685 if (IS_MIRROR(element) ||
1686 IS_MIRROR_FIXED(element) ||
1687 IS_POLAR(element) ||
1688 IS_POLAR_CROSS(element) ||
1689 IS_DF_MIRROR(element) ||
1690 IS_DF_MIRROR_AUTO(element) ||
1691 element == EL_PRISM ||
1692 element == EL_REFRACTOR)
1694 int current_angle = laser.current_angle;
1697 laser.num_damages--;
1699 AddDamagedField(ELX, ELY);
1701 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1704 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1706 if (IS_MIRROR(element) ||
1707 IS_MIRROR_FIXED(element) ||
1708 IS_DF_MIRROR(element) ||
1709 IS_DF_MIRROR_AUTO(element))
1710 laser.current_angle = get_mirrored_angle(laser.current_angle,
1711 get_element_angle(element));
1713 if (element == EL_PRISM || element == EL_REFRACTOR)
1714 laser.current_angle = RND(16);
1716 XS = 2 * Step[laser.current_angle].x;
1717 YS = 2 * Step[laser.current_angle].y;
1719 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1724 LX += step_size * XS;
1725 LY += step_size * YS;
1727 // draw sparkles on mirror
1728 if ((IS_MIRROR(element) ||
1729 IS_MIRROR_FIXED(element) ||
1730 element == EL_PRISM) &&
1731 current_angle != laser.current_angle)
1733 MovDelay[ELX][ELY] = 11; // start animation
1736 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1737 current_angle != laser.current_angle)
1738 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1741 (get_opposite_angle(laser.current_angle) ==
1742 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1744 return (laser.overloaded ? TRUE : FALSE);
1747 if (element == EL_FUEL_FULL)
1749 laser.stops_inside_element = TRUE;
1754 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1756 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1758 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1759 element == EL_MINE ? EL_MINE_ACTIVE :
1760 EL_GRAY_BALL_ACTIVE);
1762 GfxFrame[ELX][ELY] = 0; // restart animation
1764 laser.dest_element_last = Tile[ELX][ELY];
1765 laser.dest_element_last_x = ELX;
1766 laser.dest_element_last_y = ELY;
1768 if (element == EL_MINE)
1769 laser.overloaded = TRUE;
1772 if (element == EL_KETTLE ||
1773 element == EL_CELL ||
1774 element == EL_KEY ||
1775 element == EL_LIGHTBALL ||
1776 element == EL_PACMAN ||
1777 IS_PACMAN(element) ||
1778 IS_ENVELOPE(element))
1780 if (!IS_PACMAN(element) &&
1781 !IS_ENVELOPE(element))
1784 if (element == EL_PACMAN)
1787 if (element == EL_KETTLE || element == EL_CELL)
1789 if (game_mm.kettles_still_needed > 0)
1790 game_mm.kettles_still_needed--;
1792 game.snapshot.collected_item = TRUE;
1794 if (game_mm.kettles_still_needed == 0)
1798 DrawLaser(0, DL_LASER_ENABLED);
1801 else if (element == EL_KEY)
1805 else if (IS_PACMAN(element))
1807 DeletePacMan(ELX, ELY);
1809 else if (IS_ENVELOPE(element))
1811 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1814 RaiseScoreElement_MM(element);
1819 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1821 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1823 DrawLaser(0, DL_LASER_ENABLED);
1825 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1827 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1828 game_mm.lights_still_needed--;
1832 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1833 game_mm.lights_still_needed++;
1836 DrawField_MM(ELX, ELY);
1837 DrawLaser(0, DL_LASER_ENABLED);
1842 laser.stops_inside_element = TRUE;
1848 Debug("game:mm:HitElement", "(4): element == %d", element);
1851 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1852 laser.num_beamers < MAX_NUM_BEAMERS &&
1853 laser.beamer[BEAMER_NR(element)][1].num)
1855 int beamer_angle = get_element_angle(element);
1856 int beamer_nr = BEAMER_NR(element);
1860 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1863 laser.num_damages--;
1865 if (IS_FIBRE_OPTIC(element) ||
1866 laser.current_angle == get_opposite_angle(beamer_angle))
1870 LX = ELX * TILEX + 14;
1871 LY = ELY * TILEY + 14;
1873 AddLaserEdge(LX, LY);
1874 AddDamagedField(ELX, ELY);
1876 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1879 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1881 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1882 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1883 ELX = laser.beamer[beamer_nr][pos].x;
1884 ELY = laser.beamer[beamer_nr][pos].y;
1885 LX = ELX * TILEX + 14;
1886 LY = ELY * TILEY + 14;
1888 if (IS_BEAMER(element))
1890 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1891 XS = 2 * Step[laser.current_angle].x;
1892 YS = 2 * Step[laser.current_angle].y;
1895 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1897 AddLaserEdge(LX, LY);
1898 AddDamagedField(ELX, ELY);
1900 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1903 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1905 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1910 LX += step_size * XS;
1911 LY += step_size * YS;
1913 laser.num_beamers++;
1922 static boolean HitOnlyAnEdge(int hit_mask)
1924 // check if the laser hit only the edge of an element and, if so, go on
1927 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1931 if ((hit_mask == HIT_MASK_TOPLEFT ||
1932 hit_mask == HIT_MASK_TOPRIGHT ||
1933 hit_mask == HIT_MASK_BOTTOMLEFT ||
1934 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1935 laser.current_angle % 4) // angle is not 90°
1939 if (hit_mask == HIT_MASK_TOPLEFT)
1944 else if (hit_mask == HIT_MASK_TOPRIGHT)
1949 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1954 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1960 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1966 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1973 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1979 static boolean HitPolarizer(int element, int hit_mask)
1981 if (HitOnlyAnEdge(hit_mask))
1984 if (IS_DF_GRID(element))
1986 int grid_angle = get_element_angle(element);
1989 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1990 grid_angle, laser.current_angle);
1993 AddLaserEdge(LX, LY);
1994 AddDamagedField(ELX, ELY);
1997 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1999 if (laser.current_angle == grid_angle ||
2000 laser.current_angle == get_opposite_angle(grid_angle))
2002 // skip the whole element before continuing the scan
2008 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2010 if (LX/TILEX > ELX || LY/TILEY > ELY)
2012 /* skipping scan positions to the right and down skips one scan
2013 position too much, because this is only the top left scan position
2014 of totally four scan positions (plus one to the right, one to the
2015 bottom and one to the bottom right) */
2021 AddLaserEdge(LX, LY);
2027 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2029 LX / TILEX, LY / TILEY,
2030 LX % TILEX, LY % TILEY);
2035 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2037 return HitReflectingWalls(element, hit_mask);
2041 return HitAbsorbingWalls(element, hit_mask);
2044 else if (IS_GRID_STEEL(element))
2046 // may be required if graphics for steel grid redefined
2047 AddDamagedField(ELX, ELY);
2049 return HitReflectingWalls(element, hit_mask);
2051 else // IS_GRID_WOOD
2053 // may be required if graphics for wooden grid redefined
2054 AddDamagedField(ELX, ELY);
2056 return HitAbsorbingWalls(element, hit_mask);
2062 static boolean HitBlock(int element, int hit_mask)
2064 boolean check = FALSE;
2066 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2067 game_mm.num_keys == 0)
2070 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2073 int ex = ELX * TILEX + 14;
2074 int ey = ELY * TILEY + 14;
2078 for (i = 1; i < 32; i++)
2083 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2088 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2089 return HitAbsorbingWalls(element, hit_mask);
2093 AddLaserEdge(LX - XS, LY - YS);
2094 AddDamagedField(ELX, ELY);
2097 Box[ELX][ELY] = laser.num_edges;
2099 return HitReflectingWalls(element, hit_mask);
2102 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2104 int xs = XS / 2, ys = YS / 2;
2106 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2107 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2109 laser.overloaded = (element == EL_GATE_STONE);
2114 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2115 (hit_mask == HIT_MASK_TOP ||
2116 hit_mask == HIT_MASK_LEFT ||
2117 hit_mask == HIT_MASK_RIGHT ||
2118 hit_mask == HIT_MASK_BOTTOM))
2119 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2120 hit_mask == HIT_MASK_BOTTOM),
2121 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2122 hit_mask == HIT_MASK_RIGHT));
2123 AddLaserEdge(LX, LY);
2129 if (element == EL_GATE_STONE && Box[ELX][ELY])
2131 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2143 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2145 int xs = XS / 2, ys = YS / 2;
2147 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2148 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2150 laser.overloaded = (element == EL_BLOCK_STONE);
2155 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2156 (hit_mask == HIT_MASK_TOP ||
2157 hit_mask == HIT_MASK_LEFT ||
2158 hit_mask == HIT_MASK_RIGHT ||
2159 hit_mask == HIT_MASK_BOTTOM))
2160 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2161 hit_mask == HIT_MASK_BOTTOM),
2162 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2163 hit_mask == HIT_MASK_RIGHT));
2164 AddDamagedField(ELX, ELY);
2166 LX = ELX * TILEX + 14;
2167 LY = ELY * TILEY + 14;
2169 AddLaserEdge(LX, LY);
2171 laser.stops_inside_element = TRUE;
2179 static boolean HitLaserSource(int element, int hit_mask)
2181 if (HitOnlyAnEdge(hit_mask))
2184 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2186 laser.overloaded = TRUE;
2191 static boolean HitLaserDestination(int element, int hit_mask)
2193 if (HitOnlyAnEdge(hit_mask))
2196 if (element != EL_EXIT_OPEN &&
2197 !(IS_RECEIVER(element) &&
2198 game_mm.kettles_still_needed == 0 &&
2199 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2201 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2206 if (IS_RECEIVER(element) ||
2207 (IS_22_5_ANGLE(laser.current_angle) &&
2208 (ELX != (LX + 6 * XS) / TILEX ||
2209 ELY != (LY + 6 * YS) / TILEY ||
2218 LX = ELX * TILEX + 14;
2219 LY = ELY * TILEY + 14;
2221 laser.stops_inside_element = TRUE;
2224 AddLaserEdge(LX, LY);
2225 AddDamagedField(ELX, ELY);
2227 if (game_mm.lights_still_needed == 0)
2229 game_mm.level_solved = TRUE;
2231 SetTileCursorActive(FALSE);
2237 static boolean HitReflectingWalls(int element, int hit_mask)
2239 // check if laser hits side of a wall with an angle that is not 90°
2240 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2241 hit_mask == HIT_MASK_LEFT ||
2242 hit_mask == HIT_MASK_RIGHT ||
2243 hit_mask == HIT_MASK_BOTTOM))
2245 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2250 if (!IS_DF_GRID(element))
2251 AddLaserEdge(LX, LY);
2253 // check if laser hits wall with an angle of 45°
2254 if (!IS_22_5_ANGLE(laser.current_angle))
2256 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2259 laser.current_angle = get_mirrored_angle(laser.current_angle,
2262 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2265 laser.current_angle = get_mirrored_angle(laser.current_angle,
2269 AddLaserEdge(LX, LY);
2271 XS = 2 * Step[laser.current_angle].x;
2272 YS = 2 * Step[laser.current_angle].y;
2276 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2278 laser.current_angle = get_mirrored_angle(laser.current_angle,
2283 if (!IS_DF_GRID(element))
2284 AddLaserEdge(LX, LY);
2289 if (!IS_DF_GRID(element))
2290 AddLaserEdge(LX, LY + YS / 2);
2293 if (!IS_DF_GRID(element))
2294 AddLaserEdge(LX, LY);
2297 YS = 2 * Step[laser.current_angle].y;
2301 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
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 + XS / 2, LY);
2318 if (!IS_DF_GRID(element))
2319 AddLaserEdge(LX, LY);
2322 XS = 2 * Step[laser.current_angle].x;
2328 // reflection at the edge of reflecting DF style wall
2329 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2331 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2332 hit_mask == HIT_MASK_TOPRIGHT) ||
2333 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2334 hit_mask == HIT_MASK_TOPLEFT) ||
2335 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2336 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2337 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2338 hit_mask == HIT_MASK_BOTTOMRIGHT))
2341 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2342 ANG_MIRROR_135 : ANG_MIRROR_45);
2344 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2346 AddDamagedField(ELX, ELY);
2347 AddLaserEdge(LX, LY);
2349 laser.current_angle = get_mirrored_angle(laser.current_angle,
2357 AddLaserEdge(LX, LY);
2363 // reflection inside an edge of reflecting DF style wall
2364 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2366 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2367 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2368 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2369 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2370 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2371 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2372 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2373 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2376 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2377 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2378 ANG_MIRROR_135 : ANG_MIRROR_45);
2380 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2383 AddDamagedField(ELX, ELY);
2386 AddLaserEdge(LX - XS, LY - YS);
2387 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2388 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2390 laser.current_angle = get_mirrored_angle(laser.current_angle,
2398 AddLaserEdge(LX, LY);
2404 // check if laser hits DF style wall with an angle of 90°
2405 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2407 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2408 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2409 (IS_VERT_ANGLE(laser.current_angle) &&
2410 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2412 // laser at last step touched nothing or the same side of the wall
2413 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2415 AddDamagedField(ELX, ELY);
2422 last_hit_mask = hit_mask;
2429 if (!HitOnlyAnEdge(hit_mask))
2431 laser.overloaded = TRUE;
2439 static boolean HitAbsorbingWalls(int element, int hit_mask)
2441 if (HitOnlyAnEdge(hit_mask))
2445 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2447 AddLaserEdge(LX - XS, LY - YS);
2454 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2456 AddLaserEdge(LX - XS, LY - YS);
2462 if (IS_WALL_WOOD(element) ||
2463 IS_DF_WALL_WOOD(element) ||
2464 IS_GRID_WOOD(element) ||
2465 IS_GRID_WOOD_FIXED(element) ||
2466 IS_GRID_WOOD_AUTO(element) ||
2467 element == EL_FUSE_ON ||
2468 element == EL_BLOCK_WOOD ||
2469 element == EL_GATE_WOOD)
2471 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2476 if (IS_WALL_ICE(element))
2482 // check if laser hit adjacent edges of two diagonal tiles
2483 if (ELX != lx / TILEX)
2485 if (ELY != ly / TILEY)
2488 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2489 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2491 // check if laser hits wall with an angle of 90°
2492 if (IS_90_ANGLE(laser.current_angle))
2493 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2495 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2499 for (i = 0; i < 4; i++)
2501 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2502 mask = 15 - (8 >> i);
2503 else if (ABS(XS) == 4 &&
2505 (XS > 0) == (i % 2) &&
2506 (YS < 0) == (i / 2))
2507 mask = 3 + (i / 2) * 9;
2508 else if (ABS(YS) == 4 &&
2510 (XS < 0) == (i % 2) &&
2511 (YS > 0) == (i / 2))
2512 mask = 5 + (i % 2) * 5;
2516 laser.wall_mask = mask;
2518 else if (IS_WALL_AMOEBA(element))
2520 int elx = (LX - 2 * XS) / TILEX;
2521 int ely = (LY - 2 * YS) / TILEY;
2522 int element2 = Tile[elx][ely];
2525 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2527 laser.dest_element = EL_EMPTY;
2535 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2536 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2538 if (IS_90_ANGLE(laser.current_angle))
2539 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2541 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2543 laser.wall_mask = mask;
2549 static void OpenExit(int x, int y)
2553 if (!MovDelay[x][y]) // next animation frame
2554 MovDelay[x][y] = 4 * delay;
2556 if (MovDelay[x][y]) // wait some time before next frame
2561 phase = MovDelay[x][y] / delay;
2563 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2564 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2566 if (!MovDelay[x][y])
2568 Tile[x][y] = EL_EXIT_OPEN;
2574 static void OpenGrayBall(int x, int y)
2578 if (!MovDelay[x][y]) // next animation frame
2580 if (IS_WALL(Store[x][y]))
2582 DrawWalls_MM(x, y, Store[x][y]);
2584 // copy wall tile to spare bitmap for "melting" animation
2585 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2586 TILEX, TILEY, x * TILEX, y * TILEY);
2588 DrawElement_MM(x, y, EL_GRAY_BALL);
2591 MovDelay[x][y] = 50 * delay;
2594 if (MovDelay[x][y]) // wait some time before next frame
2598 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2602 int dx = RND(26), dy = RND(26);
2604 if (IS_WALL(Store[x][y]))
2606 // copy wall tile from spare bitmap for "melting" animation
2607 bitmap = bitmap_db_field;
2613 int graphic = el2gfx(Store[x][y]);
2615 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2618 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2619 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2621 laser.redraw = TRUE;
2623 MarkTileDirty(x, y);
2626 if (!MovDelay[x][y])
2628 Tile[x][y] = Store[x][y];
2629 Store[x][y] = Store2[x][y] = 0;
2630 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2632 InitField(x, y, FALSE);
2635 ScanLaser_FromLastMirror();
2640 static void OpenEnvelope(int x, int y)
2642 int num_frames = 8; // seven frames plus final empty space
2644 if (!MovDelay[x][y]) // next animation frame
2645 MovDelay[x][y] = num_frames;
2647 if (MovDelay[x][y]) // wait some time before next frame
2649 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2653 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2655 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2656 int frame = num_frames - MovDelay[x][y] - 1;
2658 DrawGraphicAnimation_MM(x, y, graphic, frame);
2660 laser.redraw = TRUE;
2663 if (MovDelay[x][y] == 0)
2665 Tile[x][y] = EL_EMPTY;
2676 static void MeltIce(int x, int y)
2681 if (!MovDelay[x][y]) // next animation frame
2682 MovDelay[x][y] = frames * delay;
2684 if (MovDelay[x][y]) // wait some time before next frame
2687 int wall_mask = Store2[x][y];
2688 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2691 phase = frames - MovDelay[x][y] / delay - 1;
2693 if (!MovDelay[x][y])
2695 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2696 Store[x][y] = Store2[x][y] = 0;
2698 DrawWalls_MM(x, y, Tile[x][y]);
2700 if (Tile[x][y] == EL_WALL_ICE_BASE)
2701 Tile[x][y] = EL_EMPTY;
2703 ScanLaser_FromLastMirror();
2705 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2707 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2709 laser.redraw = TRUE;
2714 static void GrowAmoeba(int x, int y)
2719 if (!MovDelay[x][y]) // next animation frame
2720 MovDelay[x][y] = frames * delay;
2722 if (MovDelay[x][y]) // wait some time before next frame
2725 int wall_mask = Store2[x][y];
2726 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2729 phase = MovDelay[x][y] / delay;
2731 if (!MovDelay[x][y])
2733 Tile[x][y] = real_element;
2734 Store[x][y] = Store2[x][y] = 0;
2736 DrawWalls_MM(x, y, Tile[x][y]);
2737 DrawLaser(0, DL_LASER_ENABLED);
2739 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2741 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2746 static void DrawFieldAnimated_MM(int x, int y)
2750 laser.redraw = TRUE;
2753 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2755 int element = Tile[x][y];
2756 int graphic = el2gfx(element);
2758 if (!getGraphicInfo_NewFrame(x, y, graphic))
2763 laser.redraw = TRUE;
2766 static void DrawFieldTwinkle(int x, int y)
2768 if (MovDelay[x][y] != 0) // wait some time before next frame
2774 if (MovDelay[x][y] != 0)
2776 int graphic = IMG_TWINKLE_WHITE;
2777 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2779 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2782 laser.redraw = TRUE;
2786 static void Explode_MM(int x, int y, int phase, int mode)
2788 int num_phase = 9, delay = 2;
2789 int last_phase = num_phase * delay;
2790 int half_phase = (num_phase / 2) * delay;
2793 laser.redraw = TRUE;
2795 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2797 center_element = Tile[x][y];
2799 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2801 // put moving element to center field (and let it explode there)
2802 center_element = MovingOrBlocked2Element_MM(x, y);
2803 RemoveMovingField_MM(x, y);
2805 Tile[x][y] = center_element;
2808 if (center_element != EL_GRAY_BALL_ACTIVE)
2809 Store[x][y] = EL_EMPTY;
2810 Store2[x][y] = center_element;
2812 Tile[x][y] = EL_EXPLODING_OPAQUE;
2814 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2815 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2816 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2819 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2821 ExplodePhase[x][y] = 1;
2827 GfxFrame[x][y] = 0; // restart explosion animation
2829 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2831 center_element = Store2[x][y];
2833 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2835 Tile[x][y] = EL_EXPLODING_TRANSP;
2837 if (x == ELX && y == ELY)
2841 if (phase == last_phase)
2843 if (center_element == EL_BOMB_ACTIVE)
2845 DrawLaser(0, DL_LASER_DISABLED);
2848 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2850 laser.overloaded = FALSE;
2852 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2854 GameOver_MM(GAME_OVER_BOMB);
2857 Tile[x][y] = Store[x][y];
2859 Store[x][y] = Store2[x][y] = 0;
2860 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2862 InitField(x, y, FALSE);
2865 if (center_element == EL_GRAY_BALL_ACTIVE)
2866 ScanLaser_FromLastMirror();
2868 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2870 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2871 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2873 DrawGraphicAnimation_MM(x, y, graphic, frame);
2875 MarkTileDirty(x, y);
2879 static void Bang_MM(int x, int y)
2881 int element = Tile[x][y];
2883 if (IS_PACMAN(element))
2884 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2885 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2886 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2887 else if (element == EL_KEY)
2888 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2890 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2892 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2895 static void TurnRound(int x, int y)
2907 { 0, 0 }, { 0, 0 }, { 0, 0 },
2912 int left, right, back;
2916 { MV_DOWN, MV_UP, MV_RIGHT },
2917 { MV_UP, MV_DOWN, MV_LEFT },
2919 { MV_LEFT, MV_RIGHT, MV_DOWN },
2923 { MV_RIGHT, MV_LEFT, MV_UP }
2926 int element = Tile[x][y];
2927 int old_move_dir = MovDir[x][y];
2928 int right_dir = turn[old_move_dir].right;
2929 int back_dir = turn[old_move_dir].back;
2930 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2931 int right_x = x + right_dx, right_y = y + right_dy;
2933 if (element == EL_PACMAN)
2935 boolean can_turn_right = FALSE;
2937 if (IN_LEV_FIELD(right_x, right_y) &&
2938 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2939 can_turn_right = TRUE;
2942 MovDir[x][y] = right_dir;
2944 MovDir[x][y] = back_dir;
2950 static void StartMoving_MM(int x, int y)
2952 int element = Tile[x][y];
2957 if (CAN_MOVE(element))
2961 if (MovDelay[x][y]) // wait some time before next movement
2969 // now make next step
2971 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2973 if (element == EL_PACMAN &&
2974 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2975 !ObjHit(newx, newy, HIT_POS_CENTER))
2977 Store[newx][newy] = Tile[newx][newy];
2978 Tile[newx][newy] = EL_EMPTY;
2980 DrawField_MM(newx, newy);
2982 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2983 ObjHit(newx, newy, HIT_POS_CENTER))
2985 // object was running against a wall
2992 InitMovingField_MM(x, y, MovDir[x][y]);
2996 ContinueMoving_MM(x, y);
2999 static void ContinueMoving_MM(int x, int y)
3001 int element = Tile[x][y];
3002 int direction = MovDir[x][y];
3003 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3004 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3005 int horiz_move = (dx!=0);
3006 int newx = x + dx, newy = y + dy;
3007 int step = (horiz_move ? dx : dy) * TILEX / 8;
3009 MovPos[x][y] += step;
3011 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3013 Tile[x][y] = EL_EMPTY;
3014 Tile[newx][newy] = element;
3016 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3017 MovDelay[newx][newy] = 0;
3019 if (!CAN_MOVE(element))
3020 MovDir[newx][newy] = 0;
3023 DrawField_MM(newx, newy);
3025 Stop[newx][newy] = TRUE;
3027 if (element == EL_PACMAN)
3029 if (Store[newx][newy] == EL_BOMB)
3030 Bang_MM(newx, newy);
3032 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3033 (LX + 2 * XS) / TILEX == newx &&
3034 (LY + 2 * YS) / TILEY == newy)
3041 else // still moving on
3046 laser.redraw = TRUE;
3049 boolean ClickElement(int x, int y, int button)
3051 static DelayCounter click_delay = { CLICK_DELAY };
3052 static boolean new_button = TRUE;
3053 boolean element_clicked = FALSE;
3058 // initialize static variables
3059 click_delay.count = 0;
3060 click_delay.value = CLICK_DELAY;
3066 // do not rotate objects hit by the laser after the game was solved
3067 if (game_mm.level_solved && Hit[x][y])
3070 if (button == MB_RELEASED)
3073 click_delay.value = CLICK_DELAY;
3075 // release eventually hold auto-rotating mirror
3076 RotateMirror(x, y, MB_RELEASED);
3081 if (!FrameReached(&click_delay) && !new_button)
3084 if (button == MB_MIDDLEBUTTON) // middle button has no function
3087 if (!IN_LEV_FIELD(x, y))
3090 if (Tile[x][y] == EL_EMPTY)
3093 element = Tile[x][y];
3095 if (IS_MIRROR(element) ||
3096 IS_BEAMER(element) ||
3097 IS_POLAR(element) ||
3098 IS_POLAR_CROSS(element) ||
3099 IS_DF_MIRROR(element) ||
3100 IS_DF_MIRROR_AUTO(element))
3102 RotateMirror(x, y, button);
3104 element_clicked = TRUE;
3106 else if (IS_MCDUFFIN(element))
3108 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3110 if (has_laser && !laser.fuse_off)
3111 DrawLaser(0, DL_LASER_DISABLED);
3113 element = get_rotated_element(element, BUTTON_ROTATION(button));
3115 Tile[x][y] = element;
3120 laser.start_angle = get_element_angle(element);
3124 if (!laser.fuse_off)
3128 element_clicked = TRUE;
3130 else if (element == EL_FUSE_ON && laser.fuse_off)
3132 if (x != laser.fuse_x || y != laser.fuse_y)
3135 laser.fuse_off = FALSE;
3136 laser.fuse_x = laser.fuse_y = -1;
3138 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3141 element_clicked = TRUE;
3143 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3145 laser.fuse_off = TRUE;
3148 laser.overloaded = FALSE;
3150 DrawLaser(0, DL_LASER_DISABLED);
3151 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3153 element_clicked = TRUE;
3155 else if (element == EL_LIGHTBALL)
3158 RaiseScoreElement_MM(element);
3159 DrawLaser(0, DL_LASER_ENABLED);
3161 element_clicked = TRUE;
3163 else if (IS_ENVELOPE(element))
3165 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3167 element_clicked = TRUE;
3170 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3173 return element_clicked;
3176 static void RotateMirror(int x, int y, int button)
3178 if (button == MB_RELEASED)
3180 // release eventually hold auto-rotating mirror
3187 if (IS_MIRROR(Tile[x][y]) ||
3188 IS_POLAR_CROSS(Tile[x][y]) ||
3189 IS_POLAR(Tile[x][y]) ||
3190 IS_BEAMER(Tile[x][y]) ||
3191 IS_DF_MIRROR(Tile[x][y]) ||
3192 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3193 IS_GRID_WOOD_AUTO(Tile[x][y]))
3195 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3197 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3199 if (button == MB_LEFTBUTTON)
3201 // left mouse button only for manual adjustment, no auto-rotating;
3202 // freeze mirror for until mouse button released
3206 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3208 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3212 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3214 int edge = Hit[x][y];
3220 DrawLaser(edge - 1, DL_LASER_DISABLED);
3224 else if (ObjHit(x, y, HIT_POS_CENTER))
3226 int edge = Hit[x][y];
3230 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3235 DrawLaser(edge - 1, DL_LASER_DISABLED);
3242 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3247 if ((IS_BEAMER(Tile[x][y]) ||
3248 IS_POLAR(Tile[x][y]) ||
3249 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3251 if (IS_BEAMER(Tile[x][y]))
3254 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3255 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3268 DrawLaser(0, DL_LASER_ENABLED);
3272 static void AutoRotateMirrors(void)
3276 if (!FrameReached(&rotate_delay))
3279 for (x = 0; x < lev_fieldx; x++)
3281 for (y = 0; y < lev_fieldy; y++)
3283 int element = Tile[x][y];
3285 // do not rotate objects hit by the laser after the game was solved
3286 if (game_mm.level_solved && Hit[x][y])
3289 if (IS_DF_MIRROR_AUTO(element) ||
3290 IS_GRID_WOOD_AUTO(element) ||
3291 IS_GRID_STEEL_AUTO(element) ||
3292 element == EL_REFRACTOR)
3294 RotateMirror(x, y, MB_RIGHTBUTTON);
3296 laser.redraw = TRUE;
3302 static boolean ObjHit(int obx, int oby, int bits)
3309 if (bits & HIT_POS_CENTER)
3311 if (CheckLaserPixel(cSX + obx + 15,
3316 if (bits & HIT_POS_EDGE)
3318 for (i = 0; i < 4; i++)
3319 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3320 cSY + oby + 31 * (i / 2)))
3324 if (bits & HIT_POS_BETWEEN)
3326 for (i = 0; i < 4; i++)
3327 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3328 cSY + 4 + oby + 22 * (i / 2)))
3335 static void DeletePacMan(int px, int py)
3341 if (game_mm.num_pacman <= 1)
3343 game_mm.num_pacman = 0;
3347 for (i = 0; i < game_mm.num_pacman; i++)
3348 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3351 game_mm.num_pacman--;
3353 for (j = i; j < game_mm.num_pacman; j++)
3355 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3356 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3357 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3361 static void GameActions_MM_Ext(void)
3368 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3371 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3373 element = Tile[x][y];
3375 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3376 StartMoving_MM(x, y);
3377 else if (IS_MOVING(x, y))
3378 ContinueMoving_MM(x, y);
3379 else if (IS_EXPLODING(element))
3380 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3381 else if (element == EL_EXIT_OPENING)
3383 else if (element == EL_GRAY_BALL_OPENING)
3385 else if (IS_ENVELOPE_OPENING(element))
3387 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3389 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3391 else if (IS_MIRROR(element) ||
3392 IS_MIRROR_FIXED(element) ||
3393 element == EL_PRISM)
3394 DrawFieldTwinkle(x, y);
3395 else if (element == EL_GRAY_BALL_ACTIVE ||
3396 element == EL_BOMB_ACTIVE ||
3397 element == EL_MINE_ACTIVE)
3398 DrawFieldAnimated_MM(x, y);
3399 else if (!IS_BLOCKED(x, y))
3400 DrawFieldAnimatedIfNeeded_MM(x, y);
3403 AutoRotateMirrors();
3406 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3408 // redraw after Explode_MM() ...
3410 DrawLaser(0, DL_LASER_ENABLED);
3411 laser.redraw = FALSE;
3416 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3420 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3422 DrawLaser(0, DL_LASER_DISABLED);
3427 // skip all following game actions if game is over
3428 if (game_mm.game_over)
3431 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3435 GameOver_MM(GAME_OVER_NO_ENERGY);
3440 if (FrameReached(&energy_delay))
3442 if (game_mm.energy_left > 0)
3443 game_mm.energy_left--;
3445 // when out of energy, wait another frame to play "out of time" sound
3448 element = laser.dest_element;
3451 if (element != Tile[ELX][ELY])
3453 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3454 element, Tile[ELX][ELY]);
3458 if (!laser.overloaded && laser.overload_value == 0 &&
3459 element != EL_BOMB &&
3460 element != EL_BOMB_ACTIVE &&
3461 element != EL_MINE &&
3462 element != EL_MINE_ACTIVE &&
3463 element != EL_GRAY_BALL &&
3464 element != EL_GRAY_BALL_ACTIVE &&
3465 element != EL_BLOCK_STONE &&
3466 element != EL_BLOCK_WOOD &&
3467 element != EL_FUSE_ON &&
3468 element != EL_FUEL_FULL &&
3469 !IS_WALL_ICE(element) &&
3470 !IS_WALL_AMOEBA(element))
3473 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3475 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3476 (!laser.overloaded && laser.overload_value > 0)) &&
3477 FrameReached(&overload_delay))
3479 if (laser.overloaded)
3480 laser.overload_value++;
3482 laser.overload_value--;
3484 if (game_mm.cheat_no_overload)
3486 laser.overloaded = FALSE;
3487 laser.overload_value = 0;
3490 game_mm.laser_overload_value = laser.overload_value;
3492 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3494 SetLaserColor(0xFF);
3496 DrawLaser(0, DL_LASER_ENABLED);
3499 if (!laser.overloaded)
3500 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3501 else if (setup.sound_loops)
3502 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3504 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3506 if (laser.overload_value == MAX_LASER_OVERLOAD)
3508 UpdateAndDisplayGameControlValues();
3512 GameOver_MM(GAME_OVER_OVERLOADED);
3523 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3525 if (game_mm.cheat_no_explosion)
3530 laser.dest_element = EL_EXPLODING_OPAQUE;
3535 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3537 laser.fuse_off = TRUE;
3541 DrawLaser(0, DL_LASER_DISABLED);
3542 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3545 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3547 if (!Store2[ELX][ELY]) // check if content element not yet determined
3549 int last_anim_random_frame = gfx.anim_random_frame;
3552 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3553 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3555 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3556 native_mm_level.ball_choice_mode, 0,
3557 game_mm.ball_choice_pos);
3559 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3560 gfx.anim_random_frame = last_anim_random_frame;
3562 game_mm.ball_choice_pos++;
3564 int new_element = native_mm_level.ball_content[element_pos];
3565 int new_element_base = map_wall_to_base_element(new_element);
3567 if (IS_WALL(new_element_base))
3569 // always use completely filled wall element
3570 new_element = new_element_base | 0x000f;
3572 else if (native_mm_level.rotate_ball_content &&
3573 get_num_elements(new_element) > 1)
3575 // randomly rotate newly created game element
3576 new_element = get_rotated_element(new_element, RND(16));
3579 Store[ELX][ELY] = new_element;
3580 Store2[ELX][ELY] = TRUE;
3583 if (native_mm_level.explode_ball)
3586 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3588 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3593 if (IS_WALL_ICE(element) && CT > 50)
3595 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3597 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3598 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3599 Store2[ELX][ELY] = laser.wall_mask;
3601 laser.dest_element = Tile[ELX][ELY];
3606 if (IS_WALL_AMOEBA(element) && CT > 60)
3609 int element2 = Tile[ELX][ELY];
3611 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3614 for (i = laser.num_damages - 1; i >= 0; i--)
3615 if (laser.damage[i].is_mirror)
3618 r = laser.num_edges;
3619 d = laser.num_damages;
3626 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3629 DrawLaser(0, DL_LASER_ENABLED);
3632 x = laser.damage[k1].x;
3633 y = laser.damage[k1].y;
3638 for (i = 0; i < 4; i++)
3640 if (laser.wall_mask & (1 << i))
3642 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3643 cSY + ELY * TILEY + 31 * (i / 2)))
3646 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3647 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3654 for (i = 0; i < 4; i++)
3656 if (laser.wall_mask & (1 << i))
3658 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3659 cSY + ELY * TILEY + 31 * (i / 2)))
3666 if (laser.num_beamers > 0 ||
3667 k1 < 1 || k2 < 4 || k3 < 4 ||
3668 CheckLaserPixel(cSX + ELX * TILEX + 14,
3669 cSY + ELY * TILEY + 14))
3671 laser.num_edges = r;
3672 laser.num_damages = d;
3674 DrawLaser(0, DL_LASER_DISABLED);
3677 Tile[ELX][ELY] = element | laser.wall_mask;
3679 int x = ELX, y = ELY;
3680 int wall_mask = laser.wall_mask;
3683 DrawLaser(0, DL_LASER_ENABLED);
3685 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3687 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3688 Store[x][y] = EL_WALL_AMOEBA_BASE;
3689 Store2[x][y] = wall_mask;
3694 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3695 laser.stops_inside_element && CT > native_mm_level.time_block)
3700 if (ABS(XS) > ABS(YS))
3707 for (i = 0; i < 4; i++)
3714 x = ELX + Step[k * 4].x;
3715 y = ELY + Step[k * 4].y;
3717 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3720 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3728 laser.overloaded = (element == EL_BLOCK_STONE);
3733 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3736 Tile[x][y] = element;
3738 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3741 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3743 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3744 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3752 if (element == EL_FUEL_FULL && CT > 10)
3754 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3755 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3757 for (i = start; i <= num_init_game_frames; i++)
3759 if (i == num_init_game_frames)
3760 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3761 else if (setup.sound_loops)
3762 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3764 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3766 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3768 UpdateAndDisplayGameControlValues();
3773 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3775 DrawField_MM(ELX, ELY);
3777 DrawLaser(0, DL_LASER_ENABLED);
3783 void GameActions_MM(struct MouseActionInfo action)
3785 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3786 boolean button_released = (action.button == MB_RELEASED);
3788 GameActions_MM_Ext();
3790 CheckSingleStepMode_MM(element_clicked, button_released);
3793 static void MovePacMen(void)
3795 int mx, my, ox, oy, nx, ny;
3799 if (++pacman_nr >= game_mm.num_pacman)
3802 game_mm.pacman[pacman_nr].dir--;
3804 for (l = 1; l < 5; l++)
3806 game_mm.pacman[pacman_nr].dir++;
3808 if (game_mm.pacman[pacman_nr].dir > 4)
3809 game_mm.pacman[pacman_nr].dir = 1;
3811 if (game_mm.pacman[pacman_nr].dir % 2)
3814 my = game_mm.pacman[pacman_nr].dir - 2;
3819 mx = 3 - game_mm.pacman[pacman_nr].dir;
3822 ox = game_mm.pacman[pacman_nr].x;
3823 oy = game_mm.pacman[pacman_nr].y;
3826 element = Tile[nx][ny];
3828 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3831 if (!IS_EATABLE4PACMAN(element))
3834 if (ObjHit(nx, ny, HIT_POS_CENTER))
3837 Tile[ox][oy] = EL_EMPTY;
3839 EL_PACMAN_RIGHT - 1 +
3840 (game_mm.pacman[pacman_nr].dir - 1 +
3841 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3843 game_mm.pacman[pacman_nr].x = nx;
3844 game_mm.pacman[pacman_nr].y = ny;
3846 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3848 if (element != EL_EMPTY)
3850 int graphic = el2gfx(Tile[nx][ny]);
3855 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3858 ox = cSX + ox * TILEX;
3859 oy = cSY + oy * TILEY;
3861 for (i = 1; i < 33; i += 2)
3862 BlitBitmap(bitmap, window,
3863 src_x, src_y, TILEX, TILEY,
3864 ox + i * mx, oy + i * my);
3865 Ct = Ct + FrameCounter - CT;
3868 DrawField_MM(nx, ny);
3871 if (!laser.fuse_off)
3873 DrawLaser(0, DL_LASER_ENABLED);
3875 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3877 AddDamagedField(nx, ny);
3879 laser.damage[laser.num_damages - 1].edge = 0;
3883 if (element == EL_BOMB)
3884 DeletePacMan(nx, ny);
3886 if (IS_WALL_AMOEBA(element) &&
3887 (LX + 2 * XS) / TILEX == nx &&
3888 (LY + 2 * YS) / TILEY == ny)
3898 static void InitMovingField_MM(int x, int y, int direction)
3900 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3901 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3903 MovDir[x][y] = direction;
3904 MovDir[newx][newy] = direction;
3906 if (Tile[newx][newy] == EL_EMPTY)
3907 Tile[newx][newy] = EL_BLOCKED;
3910 static int MovingOrBlocked2Element_MM(int x, int y)
3912 int element = Tile[x][y];
3914 if (element == EL_BLOCKED)
3918 Blocked2Moving(x, y, &oldx, &oldy);
3920 return Tile[oldx][oldy];
3926 static void RemoveMovingField_MM(int x, int y)
3928 int oldx = x, oldy = y, newx = x, newy = y;
3930 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3933 if (IS_MOVING(x, y))
3935 Moving2Blocked(x, y, &newx, &newy);
3936 if (Tile[newx][newy] != EL_BLOCKED)
3939 else if (Tile[x][y] == EL_BLOCKED)
3941 Blocked2Moving(x, y, &oldx, &oldy);
3942 if (!IS_MOVING(oldx, oldy))
3946 Tile[oldx][oldy] = EL_EMPTY;
3947 Tile[newx][newy] = EL_EMPTY;
3948 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3949 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3951 DrawLevelField_MM(oldx, oldy);
3952 DrawLevelField_MM(newx, newy);
3955 static void RaiseScore_MM(int value)
3957 game_mm.score += value;
3960 void RaiseScoreElement_MM(int element)
3965 case EL_PACMAN_RIGHT:
3967 case EL_PACMAN_LEFT:
3968 case EL_PACMAN_DOWN:
3969 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3973 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3978 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3982 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3991 // ----------------------------------------------------------------------------
3992 // Mirror Magic game engine snapshot handling functions
3993 // ----------------------------------------------------------------------------
3995 void SaveEngineSnapshotValues_MM(void)
3999 engine_snapshot_mm.game_mm = game_mm;
4000 engine_snapshot_mm.laser = laser;
4002 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4004 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4006 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4007 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4008 engine_snapshot_mm.Box[x][y] = Box[x][y];
4009 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4013 engine_snapshot_mm.LX = LX;
4014 engine_snapshot_mm.LY = LY;
4015 engine_snapshot_mm.XS = XS;
4016 engine_snapshot_mm.YS = YS;
4017 engine_snapshot_mm.ELX = ELX;
4018 engine_snapshot_mm.ELY = ELY;
4019 engine_snapshot_mm.CT = CT;
4020 engine_snapshot_mm.Ct = Ct;
4022 engine_snapshot_mm.last_LX = last_LX;
4023 engine_snapshot_mm.last_LY = last_LY;
4024 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4025 engine_snapshot_mm.hold_x = hold_x;
4026 engine_snapshot_mm.hold_y = hold_y;
4027 engine_snapshot_mm.pacman_nr = pacman_nr;
4029 engine_snapshot_mm.rotate_delay = rotate_delay;
4030 engine_snapshot_mm.pacman_delay = pacman_delay;
4031 engine_snapshot_mm.energy_delay = energy_delay;
4032 engine_snapshot_mm.overload_delay = overload_delay;
4035 void LoadEngineSnapshotValues_MM(void)
4039 // stored engine snapshot buffers already restored at this point
4041 game_mm = engine_snapshot_mm.game_mm;
4042 laser = engine_snapshot_mm.laser;
4044 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4046 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4048 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4049 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4050 Box[x][y] = engine_snapshot_mm.Box[x][y];
4051 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4055 LX = engine_snapshot_mm.LX;
4056 LY = engine_snapshot_mm.LY;
4057 XS = engine_snapshot_mm.XS;
4058 YS = engine_snapshot_mm.YS;
4059 ELX = engine_snapshot_mm.ELX;
4060 ELY = engine_snapshot_mm.ELY;
4061 CT = engine_snapshot_mm.CT;
4062 Ct = engine_snapshot_mm.Ct;
4064 last_LX = engine_snapshot_mm.last_LX;
4065 last_LY = engine_snapshot_mm.last_LY;
4066 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4067 hold_x = engine_snapshot_mm.hold_x;
4068 hold_y = engine_snapshot_mm.hold_y;
4069 pacman_nr = engine_snapshot_mm.pacman_nr;
4071 rotate_delay = engine_snapshot_mm.rotate_delay;
4072 pacman_delay = engine_snapshot_mm.pacman_delay;
4073 energy_delay = engine_snapshot_mm.energy_delay;
4074 overload_delay = engine_snapshot_mm.overload_delay;
4076 RedrawPlayfield_MM();
4079 static int getAngleFromTouchDelta(int dx, int dy, int base)
4081 double pi = 3.141592653;
4082 double rad = atan2((double)-dy, (double)dx);
4083 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4084 double deg = rad2 * 180.0 / pi;
4086 return (int)(deg * base / 360.0 + 0.5) % base;
4089 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4091 // calculate start (source) position to be at the middle of the tile
4092 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4093 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4094 int dx = dst_mx - src_mx;
4095 int dy = dst_my - src_my;
4104 if (!IN_LEV_FIELD(x, y))
4107 element = Tile[x][y];
4109 if (!IS_MCDUFFIN(element) &&
4110 !IS_MIRROR(element) &&
4111 !IS_BEAMER(element) &&
4112 !IS_POLAR(element) &&
4113 !IS_POLAR_CROSS(element) &&
4114 !IS_DF_MIRROR(element))
4117 angle_old = get_element_angle(element);
4119 if (IS_MCDUFFIN(element))
4121 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4122 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4123 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4124 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4127 else if (IS_MIRROR(element) ||
4128 IS_DF_MIRROR(element))
4130 for (i = 0; i < laser.num_damages; i++)
4132 if (laser.damage[i].x == x &&
4133 laser.damage[i].y == y &&
4134 ObjHit(x, y, HIT_POS_CENTER))
4136 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4137 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4144 if (angle_new == -1)
4146 if (IS_MIRROR(element) ||
4147 IS_DF_MIRROR(element) ||
4151 if (IS_POLAR_CROSS(element))
4154 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4157 button = (angle_new == angle_old ? 0 :
4158 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4159 MB_LEFTBUTTON : MB_RIGHTBUTTON);