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 // laser next step position
1077 int x = cSX + LX + XS;
1078 int y = cSY + LY + YS;
1080 // check if next step of laser is still inside visible playfield area
1081 if (x >= REAL_SX && x < REAL_SX + FULL_SXSIZE &&
1082 y >= REAL_SY && y < REAL_SY + FULL_SYSIZE)
1084 // go on with another step
1092 laser.dest_element = element;
1097 // check if laser scan has hit two diagonally adjacent element corners
1098 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1099 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1101 // check if laser scan has crossed element boundaries (not just mini tiles)
1102 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1103 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1105 // handle special case of laser hitting two diagonally adjacent elements
1106 // (with or without a third corner element behind these two elements)
1107 if ((diag_1 || diag_2) && cross_x && cross_y)
1109 // compare the two diagonally adjacent elements
1111 int yoffset = 2 * (diag_1 ? -1 : +1);
1112 int elx1 = (LX - xoffset) / TILEX;
1113 int ely1 = (LY + yoffset) / TILEY;
1114 int elx2 = (LX + xoffset) / TILEX;
1115 int ely2 = (LY - yoffset) / TILEY;
1116 int e1 = Tile[elx1][ely1];
1117 int e2 = Tile[elx2][ely2];
1118 boolean use_element_1 = FALSE;
1120 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1122 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1123 use_element_1 = (RND(2) ? TRUE : FALSE);
1124 else if (IS_WALL_ICE(e1))
1125 use_element_1 = TRUE;
1127 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1129 // if both tiles match, we can just select the first one
1130 if (IS_WALL_AMOEBA(e1))
1131 use_element_1 = TRUE;
1133 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1135 // if both tiles match, we can just select the first one
1136 if (IS_ABSORBING_BLOCK(e1))
1137 use_element_1 = TRUE;
1140 ELX = (use_element_1 ? elx1 : elx2);
1141 ELY = (use_element_1 ? ely1 : ely2);
1145 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1146 hit_mask, LX, LY, ELX, ELY);
1149 last_element = element;
1151 element = Tile[ELX][ELY];
1152 laser.dest_element = element;
1155 Debug("game:mm:ScanLaser",
1156 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1159 LX % TILEX, LY % TILEY,
1164 if (!IN_LEV_FIELD(ELX, ELY))
1165 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1169 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1170 if (element == EL_EMPTY &&
1171 IS_GRID_STEEL(last_element) &&
1172 laser.current_angle % 4) // angle is not 90°
1173 element = last_element;
1175 if (element == EL_EMPTY)
1177 if (!HitOnlyAnEdge(hit_mask))
1180 else if (element == EL_FUSE_ON)
1182 if (HitPolarizer(element, hit_mask))
1185 else if (IS_GRID(element) || IS_DF_GRID(element))
1187 if (HitPolarizer(element, hit_mask))
1190 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1191 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1193 if (HitBlock(element, hit_mask))
1200 else if (IS_MCDUFFIN(element))
1202 if (HitLaserSource(element, hit_mask))
1205 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1206 IS_RECEIVER(element))
1208 if (HitLaserDestination(element, hit_mask))
1211 else if (IS_WALL(element))
1213 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1215 if (HitReflectingWalls(element, hit_mask))
1220 if (HitAbsorbingWalls(element, hit_mask))
1226 if (HitElement(element, hit_mask))
1231 DrawLaser(rf - 1, DL_LASER_ENABLED);
1232 rf = laser.num_edges;
1234 if (!IS_DF_WALL_STEEL(element))
1236 // only used for scanning DF steel walls; reset for all other elements
1244 if (laser.dest_element != Tile[ELX][ELY])
1246 Debug("game:mm:ScanLaser",
1247 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1248 laser.dest_element, Tile[ELX][ELY]);
1252 if (!end && !laser.stops_inside_element && !StepBehind())
1255 Debug("game:mm:ScanLaser", "Go one step back");
1261 AddLaserEdge(LX, LY);
1265 DrawLaser(rf - 1, DL_LASER_ENABLED);
1267 Ct = CT = FrameCounter;
1270 if (!IN_LEV_FIELD(ELX, ELY))
1271 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1275 static void ScanLaser_FromLastMirror(void)
1277 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1280 for (i = start_pos; i >= 0; i--)
1281 if (laser.damage[i].is_mirror)
1284 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1286 DrawLaser(start_edge, DL_LASER_DISABLED);
1291 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1297 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1298 start_edge, num_edges, mode);
1303 Warn("DrawLaserExt: start_edge < 0");
1310 Warn("DrawLaserExt: num_edges < 0");
1316 if (mode == DL_LASER_DISABLED)
1318 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1322 // now draw the laser to the backbuffer and (if enabled) to the screen
1323 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1325 redraw_mask |= REDRAW_FIELD;
1327 if (mode == DL_LASER_ENABLED)
1330 // after the laser was deleted, the "damaged" graphics must be restored
1331 if (laser.num_damages)
1333 int damage_start = 0;
1336 // determine the starting edge, from which graphics need to be restored
1339 for (i = 0; i < laser.num_damages; i++)
1341 if (laser.damage[i].edge == start_edge + 1)
1350 // restore graphics from this starting edge to the end of damage list
1351 for (i = damage_start; i < laser.num_damages; i++)
1353 int lx = laser.damage[i].x;
1354 int ly = laser.damage[i].y;
1355 int element = Tile[lx][ly];
1357 if (Hit[lx][ly] == laser.damage[i].edge)
1358 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1361 if (Box[lx][ly] == laser.damage[i].edge)
1364 if (IS_DRAWABLE(element))
1365 DrawField_MM(lx, ly);
1368 elx = laser.damage[damage_start].x;
1369 ely = laser.damage[damage_start].y;
1370 element = Tile[elx][ely];
1373 if (IS_BEAMER(element))
1377 for (i = 0; i < laser.num_beamers; i++)
1378 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1380 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1381 mode, elx, ely, Hit[elx][ely], start_edge);
1382 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1383 get_element_angle(element), laser.damage[damage_start].angle);
1387 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1388 laser.num_beamers > 0 &&
1389 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1391 // element is outgoing beamer
1392 laser.num_damages = damage_start + 1;
1394 if (IS_BEAMER(element))
1395 laser.current_angle = get_element_angle(element);
1399 // element is incoming beamer or other element
1400 laser.num_damages = damage_start;
1401 laser.current_angle = laser.damage[laser.num_damages].angle;
1406 // no damages but McDuffin himself (who needs to be redrawn anyway)
1408 elx = laser.start_edge.x;
1409 ely = laser.start_edge.y;
1410 element = Tile[elx][ely];
1413 laser.num_edges = start_edge + 1;
1414 if (start_edge == 0)
1415 laser.current_angle = laser.start_angle;
1417 LX = laser.edge[start_edge].x - cSX2;
1418 LY = laser.edge[start_edge].y - cSY2;
1419 XS = 2 * Step[laser.current_angle].x;
1420 YS = 2 * Step[laser.current_angle].y;
1423 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1429 if (IS_BEAMER(element) ||
1430 IS_FIBRE_OPTIC(element) ||
1431 IS_PACMAN(element) ||
1432 IS_POLAR(element) ||
1433 IS_POLAR_CROSS(element) ||
1434 element == EL_FUSE_ON)
1439 Debug("game:mm:DrawLaserExt", "element == %d", element);
1442 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1443 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1447 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1448 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1449 (laser.num_beamers == 0 ||
1450 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1452 // element is incoming beamer or other element
1453 step_size = -step_size;
1458 if (IS_BEAMER(element))
1459 Debug("game:mm:DrawLaserExt",
1460 "start_edge == %d, laser.beamer_edge == %d",
1461 start_edge, laser.beamer_edge);
1464 LX += step_size * XS;
1465 LY += step_size * YS;
1467 else if (element != EL_EMPTY)
1476 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1481 void DrawLaser(int start_edge, int mode)
1483 // do not draw laser if fuse is off
1484 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1487 if (mode == DL_LASER_DISABLED)
1488 DeactivateLaserTargetElement();
1490 if (laser.num_edges - start_edge < 0)
1492 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1497 // check if laser is interrupted by beamer element
1498 if (laser.num_beamers > 0 &&
1499 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1501 if (mode == DL_LASER_ENABLED)
1504 int tmp_start_edge = start_edge;
1506 // draw laser segments forward from the start to the last beamer
1507 for (i = 0; i < laser.num_beamers; i++)
1509 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1511 if (tmp_num_edges <= 0)
1515 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1516 i, laser.beamer_edge[i], tmp_start_edge);
1519 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1521 tmp_start_edge = laser.beamer_edge[i];
1524 // draw last segment from last beamer to the end
1525 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1531 int last_num_edges = laser.num_edges;
1532 int num_beamers = laser.num_beamers;
1534 // delete laser segments backward from the end to the first beamer
1535 for (i = num_beamers - 1; i >= 0; i--)
1537 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1539 if (laser.beamer_edge[i] - start_edge <= 0)
1542 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1544 last_num_edges = laser.beamer_edge[i];
1545 laser.num_beamers--;
1549 if (last_num_edges - start_edge <= 0)
1550 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1551 last_num_edges, start_edge);
1554 // special case when rotating first beamer: delete laser edge on beamer
1555 // (but do not start scanning on previous edge to prevent mirror sound)
1556 if (last_num_edges - start_edge == 1 && start_edge > 0)
1557 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1559 // delete first segment from start to the first beamer
1560 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1565 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1568 game_mm.laser_enabled = mode;
1571 void DrawLaser_MM(void)
1573 DrawLaser(0, game_mm.laser_enabled);
1576 static boolean HitElement(int element, int hit_mask)
1578 if (HitOnlyAnEdge(hit_mask))
1581 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1582 element = MovingOrBlocked2Element_MM(ELX, ELY);
1585 Debug("game:mm:HitElement", "(1): element == %d", element);
1589 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1590 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1593 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1597 AddDamagedField(ELX, ELY);
1599 // this is more precise: check if laser would go through the center
1600 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1604 // prevent cutting through laser emitter with laser beam
1605 if (IS_LASER(element))
1608 // skip the whole element before continuing the scan
1616 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1618 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1620 /* skipping scan positions to the right and down skips one scan
1621 position too much, because this is only the top left scan position
1622 of totally four scan positions (plus one to the right, one to the
1623 bottom and one to the bottom right) */
1624 /* ... but only roll back scan position if more than one step done */
1634 Debug("game:mm:HitElement", "(2): element == %d", element);
1637 if (LX + 5 * XS < 0 ||
1647 Debug("game:mm:HitElement", "(3): element == %d", element);
1650 if (IS_POLAR(element) &&
1651 ((element - EL_POLAR_START) % 2 ||
1652 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1654 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1656 laser.num_damages--;
1661 if (IS_POLAR_CROSS(element) &&
1662 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1664 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1666 laser.num_damages--;
1671 if (!IS_BEAMER(element) &&
1672 !IS_FIBRE_OPTIC(element) &&
1673 !IS_GRID_WOOD(element) &&
1674 element != EL_FUEL_EMPTY)
1677 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1678 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1680 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1683 LX = ELX * TILEX + 14;
1684 LY = ELY * TILEY + 14;
1686 AddLaserEdge(LX, LY);
1689 if (IS_MIRROR(element) ||
1690 IS_MIRROR_FIXED(element) ||
1691 IS_POLAR(element) ||
1692 IS_POLAR_CROSS(element) ||
1693 IS_DF_MIRROR(element) ||
1694 IS_DF_MIRROR_AUTO(element) ||
1695 IS_DF_MIRROR_FIXED(element) ||
1696 element == EL_PRISM ||
1697 element == EL_REFRACTOR)
1699 int current_angle = laser.current_angle;
1702 laser.num_damages--;
1704 AddDamagedField(ELX, ELY);
1706 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1709 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1711 if (IS_MIRROR(element) ||
1712 IS_MIRROR_FIXED(element) ||
1713 IS_DF_MIRROR(element) ||
1714 IS_DF_MIRROR_AUTO(element) ||
1715 IS_DF_MIRROR_FIXED(element))
1716 laser.current_angle = get_mirrored_angle(laser.current_angle,
1717 get_element_angle(element));
1719 if (element == EL_PRISM || element == EL_REFRACTOR)
1720 laser.current_angle = RND(16);
1722 XS = 2 * Step[laser.current_angle].x;
1723 YS = 2 * Step[laser.current_angle].y;
1725 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1730 LX += step_size * XS;
1731 LY += step_size * YS;
1733 // draw sparkles on mirror
1734 if ((IS_MIRROR(element) ||
1735 IS_MIRROR_FIXED(element) ||
1736 element == EL_PRISM) &&
1737 current_angle != laser.current_angle)
1739 MovDelay[ELX][ELY] = 11; // start animation
1742 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1743 current_angle != laser.current_angle)
1744 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1747 (get_opposite_angle(laser.current_angle) ==
1748 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1750 return (laser.overloaded ? TRUE : FALSE);
1753 if (element == EL_FUEL_FULL)
1755 laser.stops_inside_element = TRUE;
1760 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1762 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1764 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1765 element == EL_MINE ? EL_MINE_ACTIVE :
1766 EL_GRAY_BALL_ACTIVE);
1768 GfxFrame[ELX][ELY] = 0; // restart animation
1770 laser.dest_element_last = Tile[ELX][ELY];
1771 laser.dest_element_last_x = ELX;
1772 laser.dest_element_last_y = ELY;
1774 if (element == EL_MINE)
1775 laser.overloaded = TRUE;
1778 if (element == EL_KETTLE ||
1779 element == EL_CELL ||
1780 element == EL_KEY ||
1781 element == EL_LIGHTBALL ||
1782 element == EL_PACMAN ||
1783 IS_PACMAN(element) ||
1784 IS_ENVELOPE(element))
1786 if (!IS_PACMAN(element) &&
1787 !IS_ENVELOPE(element))
1790 if (element == EL_PACMAN)
1793 if (element == EL_KETTLE || element == EL_CELL)
1795 if (game_mm.kettles_still_needed > 0)
1796 game_mm.kettles_still_needed--;
1798 game.snapshot.collected_item = TRUE;
1800 if (game_mm.kettles_still_needed == 0)
1804 DrawLaser(0, DL_LASER_ENABLED);
1807 else if (element == EL_KEY)
1811 else if (IS_PACMAN(element))
1813 DeletePacMan(ELX, ELY);
1815 else if (IS_ENVELOPE(element))
1817 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1820 RaiseScoreElement_MM(element);
1825 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1827 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1829 DrawLaser(0, DL_LASER_ENABLED);
1831 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1833 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1834 game_mm.lights_still_needed--;
1838 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1839 game_mm.lights_still_needed++;
1842 DrawField_MM(ELX, ELY);
1843 DrawLaser(0, DL_LASER_ENABLED);
1848 laser.stops_inside_element = TRUE;
1854 Debug("game:mm:HitElement", "(4): element == %d", element);
1857 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1858 laser.num_beamers < MAX_NUM_BEAMERS &&
1859 laser.beamer[BEAMER_NR(element)][1].num)
1861 int beamer_angle = get_element_angle(element);
1862 int beamer_nr = BEAMER_NR(element);
1866 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1869 laser.num_damages--;
1871 if (IS_FIBRE_OPTIC(element) ||
1872 laser.current_angle == get_opposite_angle(beamer_angle))
1876 LX = ELX * TILEX + 14;
1877 LY = ELY * TILEY + 14;
1879 AddLaserEdge(LX, LY);
1880 AddDamagedField(ELX, ELY);
1882 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1885 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1887 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1888 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1889 ELX = laser.beamer[beamer_nr][pos].x;
1890 ELY = laser.beamer[beamer_nr][pos].y;
1891 LX = ELX * TILEX + 14;
1892 LY = ELY * TILEY + 14;
1894 if (IS_BEAMER(element))
1896 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1897 XS = 2 * Step[laser.current_angle].x;
1898 YS = 2 * Step[laser.current_angle].y;
1901 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1903 AddLaserEdge(LX, LY);
1904 AddDamagedField(ELX, ELY);
1906 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1909 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1911 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1916 LX += step_size * XS;
1917 LY += step_size * YS;
1919 laser.num_beamers++;
1928 static boolean HitOnlyAnEdge(int hit_mask)
1930 // check if the laser hit only the edge of an element and, if so, go on
1933 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1937 if ((hit_mask == HIT_MASK_TOPLEFT ||
1938 hit_mask == HIT_MASK_TOPRIGHT ||
1939 hit_mask == HIT_MASK_BOTTOMLEFT ||
1940 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1941 laser.current_angle % 4) // angle is not 90°
1945 if (hit_mask == HIT_MASK_TOPLEFT)
1950 else if (hit_mask == HIT_MASK_TOPRIGHT)
1955 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1960 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1966 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1972 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1979 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1985 static boolean HitPolarizer(int element, int hit_mask)
1987 if (HitOnlyAnEdge(hit_mask))
1990 if (IS_DF_GRID(element))
1992 int grid_angle = get_element_angle(element);
1995 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1996 grid_angle, laser.current_angle);
1999 AddLaserEdge(LX, LY);
2000 AddDamagedField(ELX, ELY);
2003 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2005 if (laser.current_angle == grid_angle ||
2006 laser.current_angle == get_opposite_angle(grid_angle))
2008 // skip the whole element before continuing the scan
2014 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2016 if (LX/TILEX > ELX || LY/TILEY > ELY)
2018 /* skipping scan positions to the right and down skips one scan
2019 position too much, because this is only the top left scan position
2020 of totally four scan positions (plus one to the right, one to the
2021 bottom and one to the bottom right) */
2027 AddLaserEdge(LX, LY);
2033 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2035 LX / TILEX, LY / TILEY,
2036 LX % TILEX, LY % TILEY);
2041 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2043 return HitReflectingWalls(element, hit_mask);
2047 return HitAbsorbingWalls(element, hit_mask);
2050 else if (IS_GRID_STEEL(element))
2052 // may be required if graphics for steel grid redefined
2053 AddDamagedField(ELX, ELY);
2055 return HitReflectingWalls(element, hit_mask);
2057 else // IS_GRID_WOOD
2059 // may be required if graphics for wooden grid redefined
2060 AddDamagedField(ELX, ELY);
2062 return HitAbsorbingWalls(element, hit_mask);
2068 static boolean HitBlock(int element, int hit_mask)
2070 boolean check = FALSE;
2072 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2073 game_mm.num_keys == 0)
2076 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2079 int ex = ELX * TILEX + 14;
2080 int ey = ELY * TILEY + 14;
2084 for (i = 1; i < 32; i++)
2089 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2094 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2095 return HitAbsorbingWalls(element, hit_mask);
2099 AddLaserEdge(LX - XS, LY - YS);
2100 AddDamagedField(ELX, ELY);
2103 Box[ELX][ELY] = laser.num_edges;
2105 return HitReflectingWalls(element, hit_mask);
2108 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2110 int xs = XS / 2, ys = YS / 2;
2112 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2113 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2115 laser.overloaded = (element == EL_GATE_STONE);
2120 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2121 (hit_mask == HIT_MASK_TOP ||
2122 hit_mask == HIT_MASK_LEFT ||
2123 hit_mask == HIT_MASK_RIGHT ||
2124 hit_mask == HIT_MASK_BOTTOM))
2125 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2126 hit_mask == HIT_MASK_BOTTOM),
2127 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2128 hit_mask == HIT_MASK_RIGHT));
2129 AddLaserEdge(LX, LY);
2135 if (element == EL_GATE_STONE && Box[ELX][ELY])
2137 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2149 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2151 int xs = XS / 2, ys = YS / 2;
2153 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2154 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2156 laser.overloaded = (element == EL_BLOCK_STONE);
2161 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2162 (hit_mask == HIT_MASK_TOP ||
2163 hit_mask == HIT_MASK_LEFT ||
2164 hit_mask == HIT_MASK_RIGHT ||
2165 hit_mask == HIT_MASK_BOTTOM))
2166 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2167 hit_mask == HIT_MASK_BOTTOM),
2168 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2169 hit_mask == HIT_MASK_RIGHT));
2170 AddDamagedField(ELX, ELY);
2172 LX = ELX * TILEX + 14;
2173 LY = ELY * TILEY + 14;
2175 AddLaserEdge(LX, LY);
2177 laser.stops_inside_element = TRUE;
2185 static boolean HitLaserSource(int element, int hit_mask)
2187 if (HitOnlyAnEdge(hit_mask))
2190 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2192 laser.overloaded = TRUE;
2197 static boolean HitLaserDestination(int element, int hit_mask)
2199 if (HitOnlyAnEdge(hit_mask))
2202 if (element != EL_EXIT_OPEN &&
2203 !(IS_RECEIVER(element) &&
2204 game_mm.kettles_still_needed == 0 &&
2205 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2207 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2212 if (IS_RECEIVER(element) ||
2213 (IS_22_5_ANGLE(laser.current_angle) &&
2214 (ELX != (LX + 6 * XS) / TILEX ||
2215 ELY != (LY + 6 * YS) / TILEY ||
2224 LX = ELX * TILEX + 14;
2225 LY = ELY * TILEY + 14;
2227 laser.stops_inside_element = TRUE;
2230 AddLaserEdge(LX, LY);
2231 AddDamagedField(ELX, ELY);
2233 if (game_mm.lights_still_needed == 0)
2235 game_mm.level_solved = TRUE;
2237 SetTileCursorActive(FALSE);
2243 static boolean HitReflectingWalls(int element, int hit_mask)
2245 // check if laser hits side of a wall with an angle that is not 90°
2246 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2247 hit_mask == HIT_MASK_LEFT ||
2248 hit_mask == HIT_MASK_RIGHT ||
2249 hit_mask == HIT_MASK_BOTTOM))
2251 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2256 if (!IS_DF_GRID(element))
2257 AddLaserEdge(LX, LY);
2259 // check if laser hits wall with an angle of 45°
2260 if (!IS_22_5_ANGLE(laser.current_angle))
2262 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2265 laser.current_angle = get_mirrored_angle(laser.current_angle,
2268 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2271 laser.current_angle = get_mirrored_angle(laser.current_angle,
2275 AddLaserEdge(LX, LY);
2277 XS = 2 * Step[laser.current_angle].x;
2278 YS = 2 * Step[laser.current_angle].y;
2282 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2284 laser.current_angle = get_mirrored_angle(laser.current_angle,
2289 if (!IS_DF_GRID(element))
2290 AddLaserEdge(LX, LY);
2295 if (!IS_DF_GRID(element))
2296 AddLaserEdge(LX, LY + YS / 2);
2299 if (!IS_DF_GRID(element))
2300 AddLaserEdge(LX, LY);
2303 YS = 2 * Step[laser.current_angle].y;
2307 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2309 laser.current_angle = get_mirrored_angle(laser.current_angle,
2314 if (!IS_DF_GRID(element))
2315 AddLaserEdge(LX, LY);
2320 if (!IS_DF_GRID(element))
2321 AddLaserEdge(LX + XS / 2, LY);
2324 if (!IS_DF_GRID(element))
2325 AddLaserEdge(LX, LY);
2328 XS = 2 * Step[laser.current_angle].x;
2334 // reflection at the edge of reflecting DF style wall
2335 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2337 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2338 hit_mask == HIT_MASK_TOPRIGHT) ||
2339 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2340 hit_mask == HIT_MASK_TOPLEFT) ||
2341 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2342 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2343 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2344 hit_mask == HIT_MASK_BOTTOMRIGHT))
2347 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2348 ANG_MIRROR_135 : ANG_MIRROR_45);
2350 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2352 AddDamagedField(ELX, ELY);
2353 AddLaserEdge(LX, LY);
2355 laser.current_angle = get_mirrored_angle(laser.current_angle,
2363 AddLaserEdge(LX, LY);
2369 // reflection inside an edge of reflecting DF style wall
2370 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2372 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2373 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2374 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2375 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2376 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2377 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2378 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2379 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2382 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2383 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2384 ANG_MIRROR_135 : ANG_MIRROR_45);
2386 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2389 AddDamagedField(ELX, ELY);
2392 AddLaserEdge(LX - XS, LY - YS);
2393 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2394 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2396 laser.current_angle = get_mirrored_angle(laser.current_angle,
2404 AddLaserEdge(LX, LY);
2410 // check if laser hits DF style wall with an angle of 90°
2411 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2413 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2414 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2415 (IS_VERT_ANGLE(laser.current_angle) &&
2416 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2418 // laser at last step touched nothing or the same side of the wall
2419 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2421 AddDamagedField(ELX, ELY);
2428 last_hit_mask = hit_mask;
2435 if (!HitOnlyAnEdge(hit_mask))
2437 laser.overloaded = TRUE;
2445 static boolean HitAbsorbingWalls(int element, int hit_mask)
2447 if (HitOnlyAnEdge(hit_mask))
2451 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2453 AddLaserEdge(LX - XS, LY - YS);
2460 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2462 AddLaserEdge(LX - XS, LY - YS);
2468 if (IS_WALL_WOOD(element) ||
2469 IS_DF_WALL_WOOD(element) ||
2470 IS_GRID_WOOD(element) ||
2471 IS_GRID_WOOD_FIXED(element) ||
2472 IS_GRID_WOOD_AUTO(element) ||
2473 element == EL_FUSE_ON ||
2474 element == EL_BLOCK_WOOD ||
2475 element == EL_GATE_WOOD)
2477 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2482 if (IS_WALL_ICE(element))
2488 // check if laser hit adjacent edges of two diagonal tiles
2489 if (ELX != lx / TILEX)
2491 if (ELY != ly / TILEY)
2494 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2495 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2497 // check if laser hits wall with an angle of 90°
2498 if (IS_90_ANGLE(laser.current_angle))
2499 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2501 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2505 for (i = 0; i < 4; i++)
2507 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2508 mask = 15 - (8 >> i);
2509 else if (ABS(XS) == 4 &&
2511 (XS > 0) == (i % 2) &&
2512 (YS < 0) == (i / 2))
2513 mask = 3 + (i / 2) * 9;
2514 else if (ABS(YS) == 4 &&
2516 (XS < 0) == (i % 2) &&
2517 (YS > 0) == (i / 2))
2518 mask = 5 + (i % 2) * 5;
2522 laser.wall_mask = mask;
2524 else if (IS_WALL_AMOEBA(element))
2526 int elx = (LX - 2 * XS) / TILEX;
2527 int ely = (LY - 2 * YS) / TILEY;
2528 int element2 = Tile[elx][ely];
2531 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2533 laser.dest_element = EL_EMPTY;
2541 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2542 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2544 if (IS_90_ANGLE(laser.current_angle))
2545 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2547 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2549 laser.wall_mask = mask;
2555 static void OpenExit(int x, int y)
2559 if (!MovDelay[x][y]) // next animation frame
2560 MovDelay[x][y] = 4 * delay;
2562 if (MovDelay[x][y]) // wait some time before next frame
2567 phase = MovDelay[x][y] / delay;
2569 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2570 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2572 if (!MovDelay[x][y])
2574 Tile[x][y] = EL_EXIT_OPEN;
2580 static void OpenGrayBall(int x, int y)
2584 if (!MovDelay[x][y]) // next animation frame
2586 if (IS_WALL(Store[x][y]))
2588 DrawWalls_MM(x, y, Store[x][y]);
2590 // copy wall tile to spare bitmap for "melting" animation
2591 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2592 TILEX, TILEY, x * TILEX, y * TILEY);
2594 DrawElement_MM(x, y, EL_GRAY_BALL);
2597 MovDelay[x][y] = 50 * delay;
2600 if (MovDelay[x][y]) // wait some time before next frame
2604 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2608 int dx = RND(26), dy = RND(26);
2610 if (IS_WALL(Store[x][y]))
2612 // copy wall tile from spare bitmap for "melting" animation
2613 bitmap = bitmap_db_field;
2619 int graphic = el2gfx(Store[x][y]);
2621 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2624 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2625 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2627 laser.redraw = TRUE;
2629 MarkTileDirty(x, y);
2632 if (!MovDelay[x][y])
2634 Tile[x][y] = Store[x][y];
2635 Store[x][y] = Store2[x][y] = 0;
2636 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2638 InitField(x, y, FALSE);
2641 ScanLaser_FromLastMirror();
2646 static void OpenEnvelope(int x, int y)
2648 int num_frames = 8; // seven frames plus final empty space
2650 if (!MovDelay[x][y]) // next animation frame
2651 MovDelay[x][y] = num_frames;
2653 if (MovDelay[x][y]) // wait some time before next frame
2655 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2659 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2661 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2662 int frame = num_frames - MovDelay[x][y] - 1;
2664 DrawGraphicAnimation_MM(x, y, graphic, frame);
2666 laser.redraw = TRUE;
2669 if (MovDelay[x][y] == 0)
2671 Tile[x][y] = EL_EMPTY;
2682 static void MeltIce(int x, int y)
2687 if (!MovDelay[x][y]) // next animation frame
2688 MovDelay[x][y] = frames * delay;
2690 if (MovDelay[x][y]) // wait some time before next frame
2693 int wall_mask = Store2[x][y];
2694 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2697 phase = frames - MovDelay[x][y] / delay - 1;
2699 if (!MovDelay[x][y])
2701 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2702 Store[x][y] = Store2[x][y] = 0;
2704 DrawWalls_MM(x, y, Tile[x][y]);
2706 if (Tile[x][y] == EL_WALL_ICE_BASE)
2707 Tile[x][y] = EL_EMPTY;
2709 ScanLaser_FromLastMirror();
2711 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2713 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2715 laser.redraw = TRUE;
2720 static void GrowAmoeba(int x, int y)
2725 if (!MovDelay[x][y]) // next animation frame
2726 MovDelay[x][y] = frames * delay;
2728 if (MovDelay[x][y]) // wait some time before next frame
2731 int wall_mask = Store2[x][y];
2732 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2735 phase = MovDelay[x][y] / delay;
2737 if (!MovDelay[x][y])
2739 Tile[x][y] = real_element;
2740 Store[x][y] = Store2[x][y] = 0;
2742 DrawWalls_MM(x, y, Tile[x][y]);
2743 DrawLaser(0, DL_LASER_ENABLED);
2745 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2747 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2752 static void DrawFieldAnimated_MM(int x, int y)
2756 laser.redraw = TRUE;
2759 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2761 int element = Tile[x][y];
2762 int graphic = el2gfx(element);
2764 if (!getGraphicInfo_NewFrame(x, y, graphic))
2769 laser.redraw = TRUE;
2772 static void DrawFieldTwinkle(int x, int y)
2774 if (MovDelay[x][y] != 0) // wait some time before next frame
2780 if (MovDelay[x][y] != 0)
2782 int graphic = IMG_TWINKLE_WHITE;
2783 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2785 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2788 laser.redraw = TRUE;
2792 static void Explode_MM(int x, int y, int phase, int mode)
2794 int num_phase = 9, delay = 2;
2795 int last_phase = num_phase * delay;
2796 int half_phase = (num_phase / 2) * delay;
2799 laser.redraw = TRUE;
2801 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2803 center_element = Tile[x][y];
2805 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2807 // put moving element to center field (and let it explode there)
2808 center_element = MovingOrBlocked2Element_MM(x, y);
2809 RemoveMovingField_MM(x, y);
2811 Tile[x][y] = center_element;
2814 if (center_element != EL_GRAY_BALL_ACTIVE)
2815 Store[x][y] = EL_EMPTY;
2816 Store2[x][y] = center_element;
2818 Tile[x][y] = EL_EXPLODING_OPAQUE;
2820 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2821 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2822 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2825 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2827 ExplodePhase[x][y] = 1;
2833 GfxFrame[x][y] = 0; // restart explosion animation
2835 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2837 center_element = Store2[x][y];
2839 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2841 Tile[x][y] = EL_EXPLODING_TRANSP;
2843 if (x == ELX && y == ELY)
2847 if (phase == last_phase)
2849 if (center_element == EL_BOMB_ACTIVE)
2851 DrawLaser(0, DL_LASER_DISABLED);
2854 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2856 laser.overloaded = FALSE;
2858 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2860 GameOver_MM(GAME_OVER_BOMB);
2863 Tile[x][y] = Store[x][y];
2865 Store[x][y] = Store2[x][y] = 0;
2866 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2868 InitField(x, y, FALSE);
2871 if (center_element == EL_GRAY_BALL_ACTIVE)
2872 ScanLaser_FromLastMirror();
2874 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2876 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2877 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2879 DrawGraphicAnimation_MM(x, y, graphic, frame);
2881 MarkTileDirty(x, y);
2885 static void Bang_MM(int x, int y)
2887 int element = Tile[x][y];
2889 if (IS_PACMAN(element))
2890 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2891 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2892 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2893 else if (element == EL_KEY)
2894 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2896 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2898 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2901 static void TurnRound(int x, int y)
2913 { 0, 0 }, { 0, 0 }, { 0, 0 },
2918 int left, right, back;
2922 { MV_DOWN, MV_UP, MV_RIGHT },
2923 { MV_UP, MV_DOWN, MV_LEFT },
2925 { MV_LEFT, MV_RIGHT, MV_DOWN },
2929 { MV_RIGHT, MV_LEFT, MV_UP }
2932 int element = Tile[x][y];
2933 int old_move_dir = MovDir[x][y];
2934 int right_dir = turn[old_move_dir].right;
2935 int back_dir = turn[old_move_dir].back;
2936 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2937 int right_x = x + right_dx, right_y = y + right_dy;
2939 if (element == EL_PACMAN)
2941 boolean can_turn_right = FALSE;
2943 if (IN_LEV_FIELD(right_x, right_y) &&
2944 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2945 can_turn_right = TRUE;
2948 MovDir[x][y] = right_dir;
2950 MovDir[x][y] = back_dir;
2956 static void StartMoving_MM(int x, int y)
2958 int element = Tile[x][y];
2963 if (CAN_MOVE(element))
2967 if (MovDelay[x][y]) // wait some time before next movement
2975 // now make next step
2977 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2979 if (element == EL_PACMAN &&
2980 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2981 !ObjHit(newx, newy, HIT_POS_CENTER))
2983 Store[newx][newy] = Tile[newx][newy];
2984 Tile[newx][newy] = EL_EMPTY;
2986 DrawField_MM(newx, newy);
2988 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2989 ObjHit(newx, newy, HIT_POS_CENTER))
2991 // object was running against a wall
2998 InitMovingField_MM(x, y, MovDir[x][y]);
3002 ContinueMoving_MM(x, y);
3005 static void ContinueMoving_MM(int x, int y)
3007 int element = Tile[x][y];
3008 int direction = MovDir[x][y];
3009 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3010 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3011 int horiz_move = (dx!=0);
3012 int newx = x + dx, newy = y + dy;
3013 int step = (horiz_move ? dx : dy) * TILEX / 8;
3015 MovPos[x][y] += step;
3017 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3019 Tile[x][y] = EL_EMPTY;
3020 Tile[newx][newy] = element;
3022 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3023 MovDelay[newx][newy] = 0;
3025 if (!CAN_MOVE(element))
3026 MovDir[newx][newy] = 0;
3029 DrawField_MM(newx, newy);
3031 Stop[newx][newy] = TRUE;
3033 if (element == EL_PACMAN)
3035 if (Store[newx][newy] == EL_BOMB)
3036 Bang_MM(newx, newy);
3038 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3039 (LX + 2 * XS) / TILEX == newx &&
3040 (LY + 2 * YS) / TILEY == newy)
3047 else // still moving on
3052 laser.redraw = TRUE;
3055 boolean ClickElement(int x, int y, int button)
3057 static DelayCounter click_delay = { CLICK_DELAY };
3058 static boolean new_button = TRUE;
3059 boolean element_clicked = FALSE;
3064 // initialize static variables
3065 click_delay.count = 0;
3066 click_delay.value = CLICK_DELAY;
3072 // do not rotate objects hit by the laser after the game was solved
3073 if (game_mm.level_solved && Hit[x][y])
3076 if (button == MB_RELEASED)
3079 click_delay.value = CLICK_DELAY;
3081 // release eventually hold auto-rotating mirror
3082 RotateMirror(x, y, MB_RELEASED);
3087 if (!FrameReached(&click_delay) && !new_button)
3090 if (button == MB_MIDDLEBUTTON) // middle button has no function
3093 if (!IN_LEV_FIELD(x, y))
3096 if (Tile[x][y] == EL_EMPTY)
3099 element = Tile[x][y];
3101 if (IS_MIRROR(element) ||
3102 IS_BEAMER(element) ||
3103 IS_POLAR(element) ||
3104 IS_POLAR_CROSS(element) ||
3105 IS_DF_MIRROR(element) ||
3106 IS_DF_MIRROR_AUTO(element))
3108 RotateMirror(x, y, button);
3110 element_clicked = TRUE;
3112 else if (IS_MCDUFFIN(element))
3114 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3116 if (has_laser && !laser.fuse_off)
3117 DrawLaser(0, DL_LASER_DISABLED);
3119 element = get_rotated_element(element, BUTTON_ROTATION(button));
3121 Tile[x][y] = element;
3126 laser.start_angle = get_element_angle(element);
3130 if (!laser.fuse_off)
3134 element_clicked = TRUE;
3136 else if (element == EL_FUSE_ON && laser.fuse_off)
3138 if (x != laser.fuse_x || y != laser.fuse_y)
3141 laser.fuse_off = FALSE;
3142 laser.fuse_x = laser.fuse_y = -1;
3144 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3147 element_clicked = TRUE;
3149 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3151 laser.fuse_off = TRUE;
3154 laser.overloaded = FALSE;
3156 DrawLaser(0, DL_LASER_DISABLED);
3157 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3159 element_clicked = TRUE;
3161 else if (element == EL_LIGHTBALL)
3164 RaiseScoreElement_MM(element);
3165 DrawLaser(0, DL_LASER_ENABLED);
3167 element_clicked = TRUE;
3169 else if (IS_ENVELOPE(element))
3171 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3173 element_clicked = TRUE;
3176 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3179 return element_clicked;
3182 static void RotateMirror(int x, int y, int button)
3184 if (button == MB_RELEASED)
3186 // release eventually hold auto-rotating mirror
3193 if (IS_MIRROR(Tile[x][y]) ||
3194 IS_POLAR_CROSS(Tile[x][y]) ||
3195 IS_POLAR(Tile[x][y]) ||
3196 IS_BEAMER(Tile[x][y]) ||
3197 IS_DF_MIRROR(Tile[x][y]) ||
3198 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3199 IS_GRID_WOOD_AUTO(Tile[x][y]))
3201 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3203 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3205 if (button == MB_LEFTBUTTON)
3207 // left mouse button only for manual adjustment, no auto-rotating;
3208 // freeze mirror for until mouse button released
3212 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3214 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3218 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3220 int edge = Hit[x][y];
3226 DrawLaser(edge - 1, DL_LASER_DISABLED);
3230 else if (ObjHit(x, y, HIT_POS_CENTER))
3232 int edge = Hit[x][y];
3236 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3241 DrawLaser(edge - 1, DL_LASER_DISABLED);
3248 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3253 if ((IS_BEAMER(Tile[x][y]) ||
3254 IS_POLAR(Tile[x][y]) ||
3255 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3257 if (IS_BEAMER(Tile[x][y]))
3260 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3261 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3274 DrawLaser(0, DL_LASER_ENABLED);
3278 static void AutoRotateMirrors(void)
3282 if (!FrameReached(&rotate_delay))
3285 for (x = 0; x < lev_fieldx; x++)
3287 for (y = 0; y < lev_fieldy; y++)
3289 int element = Tile[x][y];
3291 // do not rotate objects hit by the laser after the game was solved
3292 if (game_mm.level_solved && Hit[x][y])
3295 if (IS_DF_MIRROR_AUTO(element) ||
3296 IS_GRID_WOOD_AUTO(element) ||
3297 IS_GRID_STEEL_AUTO(element) ||
3298 element == EL_REFRACTOR)
3300 RotateMirror(x, y, MB_RIGHTBUTTON);
3302 laser.redraw = TRUE;
3308 static boolean ObjHit(int obx, int oby, int bits)
3315 if (bits & HIT_POS_CENTER)
3317 if (CheckLaserPixel(cSX + obx + 15,
3322 if (bits & HIT_POS_EDGE)
3324 for (i = 0; i < 4; i++)
3325 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3326 cSY + oby + 31 * (i / 2)))
3330 if (bits & HIT_POS_BETWEEN)
3332 for (i = 0; i < 4; i++)
3333 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3334 cSY + 4 + oby + 22 * (i / 2)))
3341 static void DeletePacMan(int px, int py)
3347 if (game_mm.num_pacman <= 1)
3349 game_mm.num_pacman = 0;
3353 for (i = 0; i < game_mm.num_pacman; i++)
3354 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3357 game_mm.num_pacman--;
3359 for (j = i; j < game_mm.num_pacman; j++)
3361 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3362 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3363 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3367 static void GameActions_MM_Ext(void)
3374 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3377 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3379 element = Tile[x][y];
3381 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3382 StartMoving_MM(x, y);
3383 else if (IS_MOVING(x, y))
3384 ContinueMoving_MM(x, y);
3385 else if (IS_EXPLODING(element))
3386 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3387 else if (element == EL_EXIT_OPENING)
3389 else if (element == EL_GRAY_BALL_OPENING)
3391 else if (IS_ENVELOPE_OPENING(element))
3393 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3395 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3397 else if (IS_MIRROR(element) ||
3398 IS_MIRROR_FIXED(element) ||
3399 element == EL_PRISM)
3400 DrawFieldTwinkle(x, y);
3401 else if (element == EL_GRAY_BALL_ACTIVE ||
3402 element == EL_BOMB_ACTIVE ||
3403 element == EL_MINE_ACTIVE)
3404 DrawFieldAnimated_MM(x, y);
3405 else if (!IS_BLOCKED(x, y))
3406 DrawFieldAnimatedIfNeeded_MM(x, y);
3409 AutoRotateMirrors();
3412 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3414 // redraw after Explode_MM() ...
3416 DrawLaser(0, DL_LASER_ENABLED);
3417 laser.redraw = FALSE;
3422 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3426 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3428 DrawLaser(0, DL_LASER_DISABLED);
3433 // skip all following game actions if game is over
3434 if (game_mm.game_over)
3437 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3441 GameOver_MM(GAME_OVER_NO_ENERGY);
3446 if (FrameReached(&energy_delay))
3448 if (game_mm.energy_left > 0)
3449 game_mm.energy_left--;
3451 // when out of energy, wait another frame to play "out of time" sound
3454 element = laser.dest_element;
3457 if (element != Tile[ELX][ELY])
3459 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3460 element, Tile[ELX][ELY]);
3464 if (!laser.overloaded && laser.overload_value == 0 &&
3465 element != EL_BOMB &&
3466 element != EL_BOMB_ACTIVE &&
3467 element != EL_MINE &&
3468 element != EL_MINE_ACTIVE &&
3469 element != EL_GRAY_BALL &&
3470 element != EL_GRAY_BALL_ACTIVE &&
3471 element != EL_BLOCK_STONE &&
3472 element != EL_BLOCK_WOOD &&
3473 element != EL_FUSE_ON &&
3474 element != EL_FUEL_FULL &&
3475 !IS_WALL_ICE(element) &&
3476 !IS_WALL_AMOEBA(element))
3479 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3481 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3482 (!laser.overloaded && laser.overload_value > 0)) &&
3483 FrameReached(&overload_delay))
3485 if (laser.overloaded)
3486 laser.overload_value++;
3488 laser.overload_value--;
3490 if (game_mm.cheat_no_overload)
3492 laser.overloaded = FALSE;
3493 laser.overload_value = 0;
3496 game_mm.laser_overload_value = laser.overload_value;
3498 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3500 SetLaserColor(0xFF);
3502 DrawLaser(0, DL_LASER_ENABLED);
3505 if (!laser.overloaded)
3506 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3507 else if (setup.sound_loops)
3508 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3510 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3512 if (laser.overload_value == MAX_LASER_OVERLOAD)
3514 UpdateAndDisplayGameControlValues();
3518 GameOver_MM(GAME_OVER_OVERLOADED);
3529 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3531 if (game_mm.cheat_no_explosion)
3536 laser.dest_element = EL_EXPLODING_OPAQUE;
3541 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3543 laser.fuse_off = TRUE;
3547 DrawLaser(0, DL_LASER_DISABLED);
3548 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3551 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3553 if (!Store2[ELX][ELY]) // check if content element not yet determined
3555 int last_anim_random_frame = gfx.anim_random_frame;
3558 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3559 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3561 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3562 native_mm_level.ball_choice_mode, 0,
3563 game_mm.ball_choice_pos);
3565 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3566 gfx.anim_random_frame = last_anim_random_frame;
3568 game_mm.ball_choice_pos++;
3570 int new_element = native_mm_level.ball_content[element_pos];
3571 int new_element_base = map_wall_to_base_element(new_element);
3573 if (IS_WALL(new_element_base))
3575 // always use completely filled wall element
3576 new_element = new_element_base | 0x000f;
3578 else if (native_mm_level.rotate_ball_content &&
3579 get_num_elements(new_element) > 1)
3581 // randomly rotate newly created game element
3582 new_element = get_rotated_element(new_element, RND(16));
3585 Store[ELX][ELY] = new_element;
3586 Store2[ELX][ELY] = TRUE;
3589 if (native_mm_level.explode_ball)
3592 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3594 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3599 if (IS_WALL_ICE(element) && CT > 50)
3601 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3603 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3604 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3605 Store2[ELX][ELY] = laser.wall_mask;
3607 laser.dest_element = Tile[ELX][ELY];
3612 if (IS_WALL_AMOEBA(element) && CT > 60)
3615 int element2 = Tile[ELX][ELY];
3617 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3620 for (i = laser.num_damages - 1; i >= 0; i--)
3621 if (laser.damage[i].is_mirror)
3624 r = laser.num_edges;
3625 d = laser.num_damages;
3632 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3635 DrawLaser(0, DL_LASER_ENABLED);
3638 x = laser.damage[k1].x;
3639 y = laser.damage[k1].y;
3644 for (i = 0; i < 4; i++)
3646 if (laser.wall_mask & (1 << i))
3648 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3649 cSY + ELY * TILEY + 31 * (i / 2)))
3652 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3653 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3660 for (i = 0; i < 4; i++)
3662 if (laser.wall_mask & (1 << i))
3664 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3665 cSY + ELY * TILEY + 31 * (i / 2)))
3672 if (laser.num_beamers > 0 ||
3673 k1 < 1 || k2 < 4 || k3 < 4 ||
3674 CheckLaserPixel(cSX + ELX * TILEX + 14,
3675 cSY + ELY * TILEY + 14))
3677 laser.num_edges = r;
3678 laser.num_damages = d;
3680 DrawLaser(0, DL_LASER_DISABLED);
3683 Tile[ELX][ELY] = element | laser.wall_mask;
3685 int x = ELX, y = ELY;
3686 int wall_mask = laser.wall_mask;
3689 DrawLaser(0, DL_LASER_ENABLED);
3691 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3693 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3694 Store[x][y] = EL_WALL_AMOEBA_BASE;
3695 Store2[x][y] = wall_mask;
3700 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3701 laser.stops_inside_element && CT > native_mm_level.time_block)
3706 if (ABS(XS) > ABS(YS))
3713 for (i = 0; i < 4; i++)
3720 x = ELX + Step[k * 4].x;
3721 y = ELY + Step[k * 4].y;
3723 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3726 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3734 laser.overloaded = (element == EL_BLOCK_STONE);
3739 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3742 Tile[x][y] = element;
3744 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3747 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3749 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3750 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3758 if (element == EL_FUEL_FULL && CT > 10)
3760 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3761 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3763 for (i = start; i <= num_init_game_frames; i++)
3765 if (i == num_init_game_frames)
3766 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3767 else if (setup.sound_loops)
3768 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3770 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3772 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3774 UpdateAndDisplayGameControlValues();
3779 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3781 DrawField_MM(ELX, ELY);
3783 DrawLaser(0, DL_LASER_ENABLED);
3789 void GameActions_MM(struct MouseActionInfo action)
3791 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3792 boolean button_released = (action.button == MB_RELEASED);
3794 GameActions_MM_Ext();
3796 CheckSingleStepMode_MM(element_clicked, button_released);
3799 static void MovePacMen(void)
3801 int mx, my, ox, oy, nx, ny;
3805 if (++pacman_nr >= game_mm.num_pacman)
3808 game_mm.pacman[pacman_nr].dir--;
3810 for (l = 1; l < 5; l++)
3812 game_mm.pacman[pacman_nr].dir++;
3814 if (game_mm.pacman[pacman_nr].dir > 4)
3815 game_mm.pacman[pacman_nr].dir = 1;
3817 if (game_mm.pacman[pacman_nr].dir % 2)
3820 my = game_mm.pacman[pacman_nr].dir - 2;
3825 mx = 3 - game_mm.pacman[pacman_nr].dir;
3828 ox = game_mm.pacman[pacman_nr].x;
3829 oy = game_mm.pacman[pacman_nr].y;
3832 element = Tile[nx][ny];
3834 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3837 if (!IS_EATABLE4PACMAN(element))
3840 if (ObjHit(nx, ny, HIT_POS_CENTER))
3843 Tile[ox][oy] = EL_EMPTY;
3845 EL_PACMAN_RIGHT - 1 +
3846 (game_mm.pacman[pacman_nr].dir - 1 +
3847 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3849 game_mm.pacman[pacman_nr].x = nx;
3850 game_mm.pacman[pacman_nr].y = ny;
3852 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3854 if (element != EL_EMPTY)
3856 int graphic = el2gfx(Tile[nx][ny]);
3861 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3864 ox = cSX + ox * TILEX;
3865 oy = cSY + oy * TILEY;
3867 for (i = 1; i < 33; i += 2)
3868 BlitBitmap(bitmap, window,
3869 src_x, src_y, TILEX, TILEY,
3870 ox + i * mx, oy + i * my);
3871 Ct = Ct + FrameCounter - CT;
3874 DrawField_MM(nx, ny);
3877 if (!laser.fuse_off)
3879 DrawLaser(0, DL_LASER_ENABLED);
3881 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3883 AddDamagedField(nx, ny);
3885 laser.damage[laser.num_damages - 1].edge = 0;
3889 if (element == EL_BOMB)
3890 DeletePacMan(nx, ny);
3892 if (IS_WALL_AMOEBA(element) &&
3893 (LX + 2 * XS) / TILEX == nx &&
3894 (LY + 2 * YS) / TILEY == ny)
3904 static void InitMovingField_MM(int x, int y, int direction)
3906 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3907 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3909 MovDir[x][y] = direction;
3910 MovDir[newx][newy] = direction;
3912 if (Tile[newx][newy] == EL_EMPTY)
3913 Tile[newx][newy] = EL_BLOCKED;
3916 static int MovingOrBlocked2Element_MM(int x, int y)
3918 int element = Tile[x][y];
3920 if (element == EL_BLOCKED)
3924 Blocked2Moving(x, y, &oldx, &oldy);
3926 return Tile[oldx][oldy];
3932 static void RemoveMovingField_MM(int x, int y)
3934 int oldx = x, oldy = y, newx = x, newy = y;
3936 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3939 if (IS_MOVING(x, y))
3941 Moving2Blocked(x, y, &newx, &newy);
3942 if (Tile[newx][newy] != EL_BLOCKED)
3945 else if (Tile[x][y] == EL_BLOCKED)
3947 Blocked2Moving(x, y, &oldx, &oldy);
3948 if (!IS_MOVING(oldx, oldy))
3952 Tile[oldx][oldy] = EL_EMPTY;
3953 Tile[newx][newy] = EL_EMPTY;
3954 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3955 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3957 DrawLevelField_MM(oldx, oldy);
3958 DrawLevelField_MM(newx, newy);
3961 static void RaiseScore_MM(int value)
3963 game_mm.score += value;
3966 void RaiseScoreElement_MM(int element)
3971 case EL_PACMAN_RIGHT:
3973 case EL_PACMAN_LEFT:
3974 case EL_PACMAN_DOWN:
3975 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3979 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3984 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3988 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3997 // ----------------------------------------------------------------------------
3998 // Mirror Magic game engine snapshot handling functions
3999 // ----------------------------------------------------------------------------
4001 void SaveEngineSnapshotValues_MM(void)
4005 engine_snapshot_mm.game_mm = game_mm;
4006 engine_snapshot_mm.laser = laser;
4008 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4010 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4012 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4013 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4014 engine_snapshot_mm.Box[x][y] = Box[x][y];
4015 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4019 engine_snapshot_mm.LX = LX;
4020 engine_snapshot_mm.LY = LY;
4021 engine_snapshot_mm.XS = XS;
4022 engine_snapshot_mm.YS = YS;
4023 engine_snapshot_mm.ELX = ELX;
4024 engine_snapshot_mm.ELY = ELY;
4025 engine_snapshot_mm.CT = CT;
4026 engine_snapshot_mm.Ct = Ct;
4028 engine_snapshot_mm.last_LX = last_LX;
4029 engine_snapshot_mm.last_LY = last_LY;
4030 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4031 engine_snapshot_mm.hold_x = hold_x;
4032 engine_snapshot_mm.hold_y = hold_y;
4033 engine_snapshot_mm.pacman_nr = pacman_nr;
4035 engine_snapshot_mm.rotate_delay = rotate_delay;
4036 engine_snapshot_mm.pacman_delay = pacman_delay;
4037 engine_snapshot_mm.energy_delay = energy_delay;
4038 engine_snapshot_mm.overload_delay = overload_delay;
4041 void LoadEngineSnapshotValues_MM(void)
4045 // stored engine snapshot buffers already restored at this point
4047 game_mm = engine_snapshot_mm.game_mm;
4048 laser = engine_snapshot_mm.laser;
4050 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4052 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4054 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4055 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4056 Box[x][y] = engine_snapshot_mm.Box[x][y];
4057 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4061 LX = engine_snapshot_mm.LX;
4062 LY = engine_snapshot_mm.LY;
4063 XS = engine_snapshot_mm.XS;
4064 YS = engine_snapshot_mm.YS;
4065 ELX = engine_snapshot_mm.ELX;
4066 ELY = engine_snapshot_mm.ELY;
4067 CT = engine_snapshot_mm.CT;
4068 Ct = engine_snapshot_mm.Ct;
4070 last_LX = engine_snapshot_mm.last_LX;
4071 last_LY = engine_snapshot_mm.last_LY;
4072 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4073 hold_x = engine_snapshot_mm.hold_x;
4074 hold_y = engine_snapshot_mm.hold_y;
4075 pacman_nr = engine_snapshot_mm.pacman_nr;
4077 rotate_delay = engine_snapshot_mm.rotate_delay;
4078 pacman_delay = engine_snapshot_mm.pacman_delay;
4079 energy_delay = engine_snapshot_mm.energy_delay;
4080 overload_delay = engine_snapshot_mm.overload_delay;
4082 RedrawPlayfield_MM();
4085 static int getAngleFromTouchDelta(int dx, int dy, int base)
4087 double pi = 3.141592653;
4088 double rad = atan2((double)-dy, (double)dx);
4089 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4090 double deg = rad2 * 180.0 / pi;
4092 return (int)(deg * base / 360.0 + 0.5) % base;
4095 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4097 // calculate start (source) position to be at the middle of the tile
4098 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4099 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4100 int dx = dst_mx - src_mx;
4101 int dy = dst_my - src_my;
4110 if (!IN_LEV_FIELD(x, y))
4113 element = Tile[x][y];
4115 if (!IS_MCDUFFIN(element) &&
4116 !IS_MIRROR(element) &&
4117 !IS_BEAMER(element) &&
4118 !IS_POLAR(element) &&
4119 !IS_POLAR_CROSS(element) &&
4120 !IS_DF_MIRROR(element))
4123 angle_old = get_element_angle(element);
4125 if (IS_MCDUFFIN(element))
4127 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4128 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4129 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4130 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4133 else if (IS_MIRROR(element) ||
4134 IS_DF_MIRROR(element))
4136 for (i = 0; i < laser.num_damages; i++)
4138 if (laser.damage[i].x == x &&
4139 laser.damage[i].y == y &&
4140 ObjHit(x, y, HIT_POS_CENTER))
4142 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4143 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4150 if (angle_new == -1)
4152 if (IS_MIRROR(element) ||
4153 IS_DF_MIRROR(element) ||
4157 if (IS_POLAR_CROSS(element))
4160 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4163 button = (angle_new == angle_old ? 0 :
4164 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4165 MB_LEFTBUTTON : MB_RIGHTBUTTON);