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 element == EL_PRISM ||
1696 element == EL_REFRACTOR)
1698 int current_angle = laser.current_angle;
1701 laser.num_damages--;
1703 AddDamagedField(ELX, ELY);
1705 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1708 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1710 if (IS_MIRROR(element) ||
1711 IS_MIRROR_FIXED(element) ||
1712 IS_DF_MIRROR(element) ||
1713 IS_DF_MIRROR_AUTO(element))
1714 laser.current_angle = get_mirrored_angle(laser.current_angle,
1715 get_element_angle(element));
1717 if (element == EL_PRISM || element == EL_REFRACTOR)
1718 laser.current_angle = RND(16);
1720 XS = 2 * Step[laser.current_angle].x;
1721 YS = 2 * Step[laser.current_angle].y;
1723 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1728 LX += step_size * XS;
1729 LY += step_size * YS;
1731 // draw sparkles on mirror
1732 if ((IS_MIRROR(element) ||
1733 IS_MIRROR_FIXED(element) ||
1734 element == EL_PRISM) &&
1735 current_angle != laser.current_angle)
1737 MovDelay[ELX][ELY] = 11; // start animation
1740 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1741 current_angle != laser.current_angle)
1742 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1745 (get_opposite_angle(laser.current_angle) ==
1746 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1748 return (laser.overloaded ? TRUE : FALSE);
1751 if (element == EL_FUEL_FULL)
1753 laser.stops_inside_element = TRUE;
1758 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1760 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1762 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1763 element == EL_MINE ? EL_MINE_ACTIVE :
1764 EL_GRAY_BALL_ACTIVE);
1766 GfxFrame[ELX][ELY] = 0; // restart animation
1768 laser.dest_element_last = Tile[ELX][ELY];
1769 laser.dest_element_last_x = ELX;
1770 laser.dest_element_last_y = ELY;
1772 if (element == EL_MINE)
1773 laser.overloaded = TRUE;
1776 if (element == EL_KETTLE ||
1777 element == EL_CELL ||
1778 element == EL_KEY ||
1779 element == EL_LIGHTBALL ||
1780 element == EL_PACMAN ||
1781 IS_PACMAN(element) ||
1782 IS_ENVELOPE(element))
1784 if (!IS_PACMAN(element) &&
1785 !IS_ENVELOPE(element))
1788 if (element == EL_PACMAN)
1791 if (element == EL_KETTLE || element == EL_CELL)
1793 if (game_mm.kettles_still_needed > 0)
1794 game_mm.kettles_still_needed--;
1796 game.snapshot.collected_item = TRUE;
1798 if (game_mm.kettles_still_needed == 0)
1802 DrawLaser(0, DL_LASER_ENABLED);
1805 else if (element == EL_KEY)
1809 else if (IS_PACMAN(element))
1811 DeletePacMan(ELX, ELY);
1813 else if (IS_ENVELOPE(element))
1815 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1818 RaiseScoreElement_MM(element);
1823 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1825 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1827 DrawLaser(0, DL_LASER_ENABLED);
1829 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1831 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1832 game_mm.lights_still_needed--;
1836 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1837 game_mm.lights_still_needed++;
1840 DrawField_MM(ELX, ELY);
1841 DrawLaser(0, DL_LASER_ENABLED);
1846 laser.stops_inside_element = TRUE;
1852 Debug("game:mm:HitElement", "(4): element == %d", element);
1855 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1856 laser.num_beamers < MAX_NUM_BEAMERS &&
1857 laser.beamer[BEAMER_NR(element)][1].num)
1859 int beamer_angle = get_element_angle(element);
1860 int beamer_nr = BEAMER_NR(element);
1864 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1867 laser.num_damages--;
1869 if (IS_FIBRE_OPTIC(element) ||
1870 laser.current_angle == get_opposite_angle(beamer_angle))
1874 LX = ELX * TILEX + 14;
1875 LY = ELY * TILEY + 14;
1877 AddLaserEdge(LX, LY);
1878 AddDamagedField(ELX, ELY);
1880 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1883 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1885 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1886 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1887 ELX = laser.beamer[beamer_nr][pos].x;
1888 ELY = laser.beamer[beamer_nr][pos].y;
1889 LX = ELX * TILEX + 14;
1890 LY = ELY * TILEY + 14;
1892 if (IS_BEAMER(element))
1894 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1895 XS = 2 * Step[laser.current_angle].x;
1896 YS = 2 * Step[laser.current_angle].y;
1899 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1901 AddLaserEdge(LX, LY);
1902 AddDamagedField(ELX, ELY);
1904 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1907 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1909 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1914 LX += step_size * XS;
1915 LY += step_size * YS;
1917 laser.num_beamers++;
1926 static boolean HitOnlyAnEdge(int hit_mask)
1928 // check if the laser hit only the edge of an element and, if so, go on
1931 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1935 if ((hit_mask == HIT_MASK_TOPLEFT ||
1936 hit_mask == HIT_MASK_TOPRIGHT ||
1937 hit_mask == HIT_MASK_BOTTOMLEFT ||
1938 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1939 laser.current_angle % 4) // angle is not 90°
1943 if (hit_mask == HIT_MASK_TOPLEFT)
1948 else if (hit_mask == HIT_MASK_TOPRIGHT)
1953 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1958 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1964 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1970 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1977 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1983 static boolean HitPolarizer(int element, int hit_mask)
1985 if (HitOnlyAnEdge(hit_mask))
1988 if (IS_DF_GRID(element))
1990 int grid_angle = get_element_angle(element);
1993 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1994 grid_angle, laser.current_angle);
1997 AddLaserEdge(LX, LY);
1998 AddDamagedField(ELX, ELY);
2001 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2003 if (laser.current_angle == grid_angle ||
2004 laser.current_angle == get_opposite_angle(grid_angle))
2006 // skip the whole element before continuing the scan
2012 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2014 if (LX/TILEX > ELX || LY/TILEY > ELY)
2016 /* skipping scan positions to the right and down skips one scan
2017 position too much, because this is only the top left scan position
2018 of totally four scan positions (plus one to the right, one to the
2019 bottom and one to the bottom right) */
2025 AddLaserEdge(LX, LY);
2031 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2033 LX / TILEX, LY / TILEY,
2034 LX % TILEX, LY % TILEY);
2039 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2041 return HitReflectingWalls(element, hit_mask);
2045 return HitAbsorbingWalls(element, hit_mask);
2048 else if (IS_GRID_STEEL(element))
2050 // may be required if graphics for steel grid redefined
2051 AddDamagedField(ELX, ELY);
2053 return HitReflectingWalls(element, hit_mask);
2055 else // IS_GRID_WOOD
2057 // may be required if graphics for wooden grid redefined
2058 AddDamagedField(ELX, ELY);
2060 return HitAbsorbingWalls(element, hit_mask);
2066 static boolean HitBlock(int element, int hit_mask)
2068 boolean check = FALSE;
2070 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2071 game_mm.num_keys == 0)
2074 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2077 int ex = ELX * TILEX + 14;
2078 int ey = ELY * TILEY + 14;
2082 for (i = 1; i < 32; i++)
2087 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2092 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2093 return HitAbsorbingWalls(element, hit_mask);
2097 AddLaserEdge(LX - XS, LY - YS);
2098 AddDamagedField(ELX, ELY);
2101 Box[ELX][ELY] = laser.num_edges;
2103 return HitReflectingWalls(element, hit_mask);
2106 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2108 int xs = XS / 2, ys = YS / 2;
2110 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2111 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2113 laser.overloaded = (element == EL_GATE_STONE);
2118 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2119 (hit_mask == HIT_MASK_TOP ||
2120 hit_mask == HIT_MASK_LEFT ||
2121 hit_mask == HIT_MASK_RIGHT ||
2122 hit_mask == HIT_MASK_BOTTOM))
2123 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2124 hit_mask == HIT_MASK_BOTTOM),
2125 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2126 hit_mask == HIT_MASK_RIGHT));
2127 AddLaserEdge(LX, LY);
2133 if (element == EL_GATE_STONE && Box[ELX][ELY])
2135 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2147 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2149 int xs = XS / 2, ys = YS / 2;
2151 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2152 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2154 laser.overloaded = (element == EL_BLOCK_STONE);
2159 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2160 (hit_mask == HIT_MASK_TOP ||
2161 hit_mask == HIT_MASK_LEFT ||
2162 hit_mask == HIT_MASK_RIGHT ||
2163 hit_mask == HIT_MASK_BOTTOM))
2164 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2165 hit_mask == HIT_MASK_BOTTOM),
2166 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2167 hit_mask == HIT_MASK_RIGHT));
2168 AddDamagedField(ELX, ELY);
2170 LX = ELX * TILEX + 14;
2171 LY = ELY * TILEY + 14;
2173 AddLaserEdge(LX, LY);
2175 laser.stops_inside_element = TRUE;
2183 static boolean HitLaserSource(int element, int hit_mask)
2185 if (HitOnlyAnEdge(hit_mask))
2188 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2190 laser.overloaded = TRUE;
2195 static boolean HitLaserDestination(int element, int hit_mask)
2197 if (HitOnlyAnEdge(hit_mask))
2200 if (element != EL_EXIT_OPEN &&
2201 !(IS_RECEIVER(element) &&
2202 game_mm.kettles_still_needed == 0 &&
2203 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2205 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2210 if (IS_RECEIVER(element) ||
2211 (IS_22_5_ANGLE(laser.current_angle) &&
2212 (ELX != (LX + 6 * XS) / TILEX ||
2213 ELY != (LY + 6 * YS) / TILEY ||
2222 LX = ELX * TILEX + 14;
2223 LY = ELY * TILEY + 14;
2225 laser.stops_inside_element = TRUE;
2228 AddLaserEdge(LX, LY);
2229 AddDamagedField(ELX, ELY);
2231 if (game_mm.lights_still_needed == 0)
2233 game_mm.level_solved = TRUE;
2235 SetTileCursorActive(FALSE);
2241 static boolean HitReflectingWalls(int element, int hit_mask)
2243 // check if laser hits side of a wall with an angle that is not 90°
2244 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2245 hit_mask == HIT_MASK_LEFT ||
2246 hit_mask == HIT_MASK_RIGHT ||
2247 hit_mask == HIT_MASK_BOTTOM))
2249 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2254 if (!IS_DF_GRID(element))
2255 AddLaserEdge(LX, LY);
2257 // check if laser hits wall with an angle of 45°
2258 if (!IS_22_5_ANGLE(laser.current_angle))
2260 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2263 laser.current_angle = get_mirrored_angle(laser.current_angle,
2266 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2269 laser.current_angle = get_mirrored_angle(laser.current_angle,
2273 AddLaserEdge(LX, LY);
2275 XS = 2 * Step[laser.current_angle].x;
2276 YS = 2 * Step[laser.current_angle].y;
2280 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2282 laser.current_angle = get_mirrored_angle(laser.current_angle,
2287 if (!IS_DF_GRID(element))
2288 AddLaserEdge(LX, LY);
2293 if (!IS_DF_GRID(element))
2294 AddLaserEdge(LX, LY + YS / 2);
2297 if (!IS_DF_GRID(element))
2298 AddLaserEdge(LX, LY);
2301 YS = 2 * Step[laser.current_angle].y;
2305 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2307 laser.current_angle = get_mirrored_angle(laser.current_angle,
2312 if (!IS_DF_GRID(element))
2313 AddLaserEdge(LX, LY);
2318 if (!IS_DF_GRID(element))
2319 AddLaserEdge(LX + XS / 2, LY);
2322 if (!IS_DF_GRID(element))
2323 AddLaserEdge(LX, LY);
2326 XS = 2 * Step[laser.current_angle].x;
2332 // reflection at the edge of reflecting DF style wall
2333 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2335 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2336 hit_mask == HIT_MASK_TOPRIGHT) ||
2337 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2338 hit_mask == HIT_MASK_TOPLEFT) ||
2339 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2340 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2341 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2342 hit_mask == HIT_MASK_BOTTOMRIGHT))
2345 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2346 ANG_MIRROR_135 : ANG_MIRROR_45);
2348 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2350 AddDamagedField(ELX, ELY);
2351 AddLaserEdge(LX, LY);
2353 laser.current_angle = get_mirrored_angle(laser.current_angle,
2361 AddLaserEdge(LX, LY);
2367 // reflection inside an edge of reflecting DF style wall
2368 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2370 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2371 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2372 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2373 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2374 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2375 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2376 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2377 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2380 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2381 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2382 ANG_MIRROR_135 : ANG_MIRROR_45);
2384 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2387 AddDamagedField(ELX, ELY);
2390 AddLaserEdge(LX - XS, LY - YS);
2391 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2392 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2394 laser.current_angle = get_mirrored_angle(laser.current_angle,
2402 AddLaserEdge(LX, LY);
2408 // check if laser hits DF style wall with an angle of 90°
2409 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2411 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2412 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2413 (IS_VERT_ANGLE(laser.current_angle) &&
2414 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2416 // laser at last step touched nothing or the same side of the wall
2417 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2419 AddDamagedField(ELX, ELY);
2426 last_hit_mask = hit_mask;
2433 if (!HitOnlyAnEdge(hit_mask))
2435 laser.overloaded = TRUE;
2443 static boolean HitAbsorbingWalls(int element, int hit_mask)
2445 if (HitOnlyAnEdge(hit_mask))
2449 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2451 AddLaserEdge(LX - XS, LY - YS);
2458 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2460 AddLaserEdge(LX - XS, LY - YS);
2466 if (IS_WALL_WOOD(element) ||
2467 IS_DF_WALL_WOOD(element) ||
2468 IS_GRID_WOOD(element) ||
2469 IS_GRID_WOOD_FIXED(element) ||
2470 IS_GRID_WOOD_AUTO(element) ||
2471 element == EL_FUSE_ON ||
2472 element == EL_BLOCK_WOOD ||
2473 element == EL_GATE_WOOD)
2475 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2480 if (IS_WALL_ICE(element))
2486 // check if laser hit adjacent edges of two diagonal tiles
2487 if (ELX != lx / TILEX)
2489 if (ELY != ly / TILEY)
2492 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2493 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2495 // check if laser hits wall with an angle of 90°
2496 if (IS_90_ANGLE(laser.current_angle))
2497 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2499 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2503 for (i = 0; i < 4; i++)
2505 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2506 mask = 15 - (8 >> i);
2507 else if (ABS(XS) == 4 &&
2509 (XS > 0) == (i % 2) &&
2510 (YS < 0) == (i / 2))
2511 mask = 3 + (i / 2) * 9;
2512 else if (ABS(YS) == 4 &&
2514 (XS < 0) == (i % 2) &&
2515 (YS > 0) == (i / 2))
2516 mask = 5 + (i % 2) * 5;
2520 laser.wall_mask = mask;
2522 else if (IS_WALL_AMOEBA(element))
2524 int elx = (LX - 2 * XS) / TILEX;
2525 int ely = (LY - 2 * YS) / TILEY;
2526 int element2 = Tile[elx][ely];
2529 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2531 laser.dest_element = EL_EMPTY;
2539 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2540 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2542 if (IS_90_ANGLE(laser.current_angle))
2543 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2545 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2547 laser.wall_mask = mask;
2553 static void OpenExit(int x, int y)
2557 if (!MovDelay[x][y]) // next animation frame
2558 MovDelay[x][y] = 4 * delay;
2560 if (MovDelay[x][y]) // wait some time before next frame
2565 phase = MovDelay[x][y] / delay;
2567 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2568 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2570 if (!MovDelay[x][y])
2572 Tile[x][y] = EL_EXIT_OPEN;
2578 static void OpenGrayBall(int x, int y)
2582 if (!MovDelay[x][y]) // next animation frame
2584 if (IS_WALL(Store[x][y]))
2586 DrawWalls_MM(x, y, Store[x][y]);
2588 // copy wall tile to spare bitmap for "melting" animation
2589 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2590 TILEX, TILEY, x * TILEX, y * TILEY);
2592 DrawElement_MM(x, y, EL_GRAY_BALL);
2595 MovDelay[x][y] = 50 * delay;
2598 if (MovDelay[x][y]) // wait some time before next frame
2602 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2606 int dx = RND(26), dy = RND(26);
2608 if (IS_WALL(Store[x][y]))
2610 // copy wall tile from spare bitmap for "melting" animation
2611 bitmap = bitmap_db_field;
2617 int graphic = el2gfx(Store[x][y]);
2619 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2622 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2623 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2625 laser.redraw = TRUE;
2627 MarkTileDirty(x, y);
2630 if (!MovDelay[x][y])
2632 Tile[x][y] = Store[x][y];
2633 Store[x][y] = Store2[x][y] = 0;
2634 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2636 InitField(x, y, FALSE);
2639 ScanLaser_FromLastMirror();
2644 static void OpenEnvelope(int x, int y)
2646 int num_frames = 8; // seven frames plus final empty space
2648 if (!MovDelay[x][y]) // next animation frame
2649 MovDelay[x][y] = num_frames;
2651 if (MovDelay[x][y]) // wait some time before next frame
2653 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2657 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2659 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2660 int frame = num_frames - MovDelay[x][y] - 1;
2662 DrawGraphicAnimation_MM(x, y, graphic, frame);
2664 laser.redraw = TRUE;
2667 if (MovDelay[x][y] == 0)
2669 Tile[x][y] = EL_EMPTY;
2680 static void MeltIce(int x, int y)
2685 if (!MovDelay[x][y]) // next animation frame
2686 MovDelay[x][y] = frames * delay;
2688 if (MovDelay[x][y]) // wait some time before next frame
2691 int wall_mask = Store2[x][y];
2692 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2695 phase = frames - MovDelay[x][y] / delay - 1;
2697 if (!MovDelay[x][y])
2699 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2700 Store[x][y] = Store2[x][y] = 0;
2702 DrawWalls_MM(x, y, Tile[x][y]);
2704 if (Tile[x][y] == EL_WALL_ICE_BASE)
2705 Tile[x][y] = EL_EMPTY;
2707 ScanLaser_FromLastMirror();
2709 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2711 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2713 laser.redraw = TRUE;
2718 static void GrowAmoeba(int x, int y)
2723 if (!MovDelay[x][y]) // next animation frame
2724 MovDelay[x][y] = frames * delay;
2726 if (MovDelay[x][y]) // wait some time before next frame
2729 int wall_mask = Store2[x][y];
2730 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2733 phase = MovDelay[x][y] / delay;
2735 if (!MovDelay[x][y])
2737 Tile[x][y] = real_element;
2738 Store[x][y] = Store2[x][y] = 0;
2740 DrawWalls_MM(x, y, Tile[x][y]);
2741 DrawLaser(0, DL_LASER_ENABLED);
2743 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2745 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2750 static void DrawFieldAnimated_MM(int x, int y)
2754 laser.redraw = TRUE;
2757 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2759 int element = Tile[x][y];
2760 int graphic = el2gfx(element);
2762 if (!getGraphicInfo_NewFrame(x, y, graphic))
2767 laser.redraw = TRUE;
2770 static void DrawFieldTwinkle(int x, int y)
2772 if (MovDelay[x][y] != 0) // wait some time before next frame
2778 if (MovDelay[x][y] != 0)
2780 int graphic = IMG_TWINKLE_WHITE;
2781 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2783 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2786 laser.redraw = TRUE;
2790 static void Explode_MM(int x, int y, int phase, int mode)
2792 int num_phase = 9, delay = 2;
2793 int last_phase = num_phase * delay;
2794 int half_phase = (num_phase / 2) * delay;
2797 laser.redraw = TRUE;
2799 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2801 center_element = Tile[x][y];
2803 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2805 // put moving element to center field (and let it explode there)
2806 center_element = MovingOrBlocked2Element_MM(x, y);
2807 RemoveMovingField_MM(x, y);
2809 Tile[x][y] = center_element;
2812 if (center_element != EL_GRAY_BALL_ACTIVE)
2813 Store[x][y] = EL_EMPTY;
2814 Store2[x][y] = center_element;
2816 Tile[x][y] = EL_EXPLODING_OPAQUE;
2818 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2819 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2820 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2823 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2825 ExplodePhase[x][y] = 1;
2831 GfxFrame[x][y] = 0; // restart explosion animation
2833 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2835 center_element = Store2[x][y];
2837 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2839 Tile[x][y] = EL_EXPLODING_TRANSP;
2841 if (x == ELX && y == ELY)
2845 if (phase == last_phase)
2847 if (center_element == EL_BOMB_ACTIVE)
2849 DrawLaser(0, DL_LASER_DISABLED);
2852 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2854 laser.overloaded = FALSE;
2856 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2858 GameOver_MM(GAME_OVER_BOMB);
2861 Tile[x][y] = Store[x][y];
2863 Store[x][y] = Store2[x][y] = 0;
2864 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2866 InitField(x, y, FALSE);
2869 if (center_element == EL_GRAY_BALL_ACTIVE)
2870 ScanLaser_FromLastMirror();
2872 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2874 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2875 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2877 DrawGraphicAnimation_MM(x, y, graphic, frame);
2879 MarkTileDirty(x, y);
2883 static void Bang_MM(int x, int y)
2885 int element = Tile[x][y];
2887 if (IS_PACMAN(element))
2888 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2889 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2890 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2891 else if (element == EL_KEY)
2892 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2894 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2896 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2899 static void TurnRound(int x, int y)
2911 { 0, 0 }, { 0, 0 }, { 0, 0 },
2916 int left, right, back;
2920 { MV_DOWN, MV_UP, MV_RIGHT },
2921 { MV_UP, MV_DOWN, MV_LEFT },
2923 { MV_LEFT, MV_RIGHT, MV_DOWN },
2927 { MV_RIGHT, MV_LEFT, MV_UP }
2930 int element = Tile[x][y];
2931 int old_move_dir = MovDir[x][y];
2932 int right_dir = turn[old_move_dir].right;
2933 int back_dir = turn[old_move_dir].back;
2934 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2935 int right_x = x + right_dx, right_y = y + right_dy;
2937 if (element == EL_PACMAN)
2939 boolean can_turn_right = FALSE;
2941 if (IN_LEV_FIELD(right_x, right_y) &&
2942 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2943 can_turn_right = TRUE;
2946 MovDir[x][y] = right_dir;
2948 MovDir[x][y] = back_dir;
2954 static void StartMoving_MM(int x, int y)
2956 int element = Tile[x][y];
2961 if (CAN_MOVE(element))
2965 if (MovDelay[x][y]) // wait some time before next movement
2973 // now make next step
2975 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2977 if (element == EL_PACMAN &&
2978 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2979 !ObjHit(newx, newy, HIT_POS_CENTER))
2981 Store[newx][newy] = Tile[newx][newy];
2982 Tile[newx][newy] = EL_EMPTY;
2984 DrawField_MM(newx, newy);
2986 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2987 ObjHit(newx, newy, HIT_POS_CENTER))
2989 // object was running against a wall
2996 InitMovingField_MM(x, y, MovDir[x][y]);
3000 ContinueMoving_MM(x, y);
3003 static void ContinueMoving_MM(int x, int y)
3005 int element = Tile[x][y];
3006 int direction = MovDir[x][y];
3007 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3008 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3009 int horiz_move = (dx!=0);
3010 int newx = x + dx, newy = y + dy;
3011 int step = (horiz_move ? dx : dy) * TILEX / 8;
3013 MovPos[x][y] += step;
3015 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3017 Tile[x][y] = EL_EMPTY;
3018 Tile[newx][newy] = element;
3020 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3021 MovDelay[newx][newy] = 0;
3023 if (!CAN_MOVE(element))
3024 MovDir[newx][newy] = 0;
3027 DrawField_MM(newx, newy);
3029 Stop[newx][newy] = TRUE;
3031 if (element == EL_PACMAN)
3033 if (Store[newx][newy] == EL_BOMB)
3034 Bang_MM(newx, newy);
3036 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3037 (LX + 2 * XS) / TILEX == newx &&
3038 (LY + 2 * YS) / TILEY == newy)
3045 else // still moving on
3050 laser.redraw = TRUE;
3053 boolean ClickElement(int x, int y, int button)
3055 static DelayCounter click_delay = { CLICK_DELAY };
3056 static boolean new_button = TRUE;
3057 boolean element_clicked = FALSE;
3062 // initialize static variables
3063 click_delay.count = 0;
3064 click_delay.value = CLICK_DELAY;
3070 // do not rotate objects hit by the laser after the game was solved
3071 if (game_mm.level_solved && Hit[x][y])
3074 if (button == MB_RELEASED)
3077 click_delay.value = CLICK_DELAY;
3079 // release eventually hold auto-rotating mirror
3080 RotateMirror(x, y, MB_RELEASED);
3085 if (!FrameReached(&click_delay) && !new_button)
3088 if (button == MB_MIDDLEBUTTON) // middle button has no function
3091 if (!IN_LEV_FIELD(x, y))
3094 if (Tile[x][y] == EL_EMPTY)
3097 element = Tile[x][y];
3099 if (IS_MIRROR(element) ||
3100 IS_BEAMER(element) ||
3101 IS_POLAR(element) ||
3102 IS_POLAR_CROSS(element) ||
3103 IS_DF_MIRROR(element) ||
3104 IS_DF_MIRROR_AUTO(element))
3106 RotateMirror(x, y, button);
3108 element_clicked = TRUE;
3110 else if (IS_MCDUFFIN(element))
3112 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3114 if (has_laser && !laser.fuse_off)
3115 DrawLaser(0, DL_LASER_DISABLED);
3117 element = get_rotated_element(element, BUTTON_ROTATION(button));
3119 Tile[x][y] = element;
3124 laser.start_angle = get_element_angle(element);
3128 if (!laser.fuse_off)
3132 element_clicked = TRUE;
3134 else if (element == EL_FUSE_ON && laser.fuse_off)
3136 if (x != laser.fuse_x || y != laser.fuse_y)
3139 laser.fuse_off = FALSE;
3140 laser.fuse_x = laser.fuse_y = -1;
3142 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3145 element_clicked = TRUE;
3147 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3149 laser.fuse_off = TRUE;
3152 laser.overloaded = FALSE;
3154 DrawLaser(0, DL_LASER_DISABLED);
3155 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3157 element_clicked = TRUE;
3159 else if (element == EL_LIGHTBALL)
3162 RaiseScoreElement_MM(element);
3163 DrawLaser(0, DL_LASER_ENABLED);
3165 element_clicked = TRUE;
3167 else if (IS_ENVELOPE(element))
3169 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3171 element_clicked = TRUE;
3174 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3177 return element_clicked;
3180 static void RotateMirror(int x, int y, int button)
3182 if (button == MB_RELEASED)
3184 // release eventually hold auto-rotating mirror
3191 if (IS_MIRROR(Tile[x][y]) ||
3192 IS_POLAR_CROSS(Tile[x][y]) ||
3193 IS_POLAR(Tile[x][y]) ||
3194 IS_BEAMER(Tile[x][y]) ||
3195 IS_DF_MIRROR(Tile[x][y]) ||
3196 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3197 IS_GRID_WOOD_AUTO(Tile[x][y]))
3199 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3201 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3203 if (button == MB_LEFTBUTTON)
3205 // left mouse button only for manual adjustment, no auto-rotating;
3206 // freeze mirror for until mouse button released
3210 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3212 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3216 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3218 int edge = Hit[x][y];
3224 DrawLaser(edge - 1, DL_LASER_DISABLED);
3228 else if (ObjHit(x, y, HIT_POS_CENTER))
3230 int edge = Hit[x][y];
3234 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3239 DrawLaser(edge - 1, DL_LASER_DISABLED);
3246 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3251 if ((IS_BEAMER(Tile[x][y]) ||
3252 IS_POLAR(Tile[x][y]) ||
3253 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3255 if (IS_BEAMER(Tile[x][y]))
3258 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3259 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3272 DrawLaser(0, DL_LASER_ENABLED);
3276 static void AutoRotateMirrors(void)
3280 if (!FrameReached(&rotate_delay))
3283 for (x = 0; x < lev_fieldx; x++)
3285 for (y = 0; y < lev_fieldy; y++)
3287 int element = Tile[x][y];
3289 // do not rotate objects hit by the laser after the game was solved
3290 if (game_mm.level_solved && Hit[x][y])
3293 if (IS_DF_MIRROR_AUTO(element) ||
3294 IS_GRID_WOOD_AUTO(element) ||
3295 IS_GRID_STEEL_AUTO(element) ||
3296 element == EL_REFRACTOR)
3298 RotateMirror(x, y, MB_RIGHTBUTTON);
3300 laser.redraw = TRUE;
3306 static boolean ObjHit(int obx, int oby, int bits)
3313 if (bits & HIT_POS_CENTER)
3315 if (CheckLaserPixel(cSX + obx + 15,
3320 if (bits & HIT_POS_EDGE)
3322 for (i = 0; i < 4; i++)
3323 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3324 cSY + oby + 31 * (i / 2)))
3328 if (bits & HIT_POS_BETWEEN)
3330 for (i = 0; i < 4; i++)
3331 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3332 cSY + 4 + oby + 22 * (i / 2)))
3339 static void DeletePacMan(int px, int py)
3345 if (game_mm.num_pacman <= 1)
3347 game_mm.num_pacman = 0;
3351 for (i = 0; i < game_mm.num_pacman; i++)
3352 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3355 game_mm.num_pacman--;
3357 for (j = i; j < game_mm.num_pacman; j++)
3359 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3360 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3361 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3365 static void GameActions_MM_Ext(void)
3372 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3375 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3377 element = Tile[x][y];
3379 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3380 StartMoving_MM(x, y);
3381 else if (IS_MOVING(x, y))
3382 ContinueMoving_MM(x, y);
3383 else if (IS_EXPLODING(element))
3384 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3385 else if (element == EL_EXIT_OPENING)
3387 else if (element == EL_GRAY_BALL_OPENING)
3389 else if (IS_ENVELOPE_OPENING(element))
3391 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3393 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3395 else if (IS_MIRROR(element) ||
3396 IS_MIRROR_FIXED(element) ||
3397 element == EL_PRISM)
3398 DrawFieldTwinkle(x, y);
3399 else if (element == EL_GRAY_BALL_ACTIVE ||
3400 element == EL_BOMB_ACTIVE ||
3401 element == EL_MINE_ACTIVE)
3402 DrawFieldAnimated_MM(x, y);
3403 else if (!IS_BLOCKED(x, y))
3404 DrawFieldAnimatedIfNeeded_MM(x, y);
3407 AutoRotateMirrors();
3410 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3412 // redraw after Explode_MM() ...
3414 DrawLaser(0, DL_LASER_ENABLED);
3415 laser.redraw = FALSE;
3420 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3424 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3426 DrawLaser(0, DL_LASER_DISABLED);
3431 // skip all following game actions if game is over
3432 if (game_mm.game_over)
3435 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3439 GameOver_MM(GAME_OVER_NO_ENERGY);
3444 if (FrameReached(&energy_delay))
3446 if (game_mm.energy_left > 0)
3447 game_mm.energy_left--;
3449 // when out of energy, wait another frame to play "out of time" sound
3452 element = laser.dest_element;
3455 if (element != Tile[ELX][ELY])
3457 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3458 element, Tile[ELX][ELY]);
3462 if (!laser.overloaded && laser.overload_value == 0 &&
3463 element != EL_BOMB &&
3464 element != EL_BOMB_ACTIVE &&
3465 element != EL_MINE &&
3466 element != EL_MINE_ACTIVE &&
3467 element != EL_GRAY_BALL &&
3468 element != EL_GRAY_BALL_ACTIVE &&
3469 element != EL_BLOCK_STONE &&
3470 element != EL_BLOCK_WOOD &&
3471 element != EL_FUSE_ON &&
3472 element != EL_FUEL_FULL &&
3473 !IS_WALL_ICE(element) &&
3474 !IS_WALL_AMOEBA(element))
3477 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3479 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3480 (!laser.overloaded && laser.overload_value > 0)) &&
3481 FrameReached(&overload_delay))
3483 if (laser.overloaded)
3484 laser.overload_value++;
3486 laser.overload_value--;
3488 if (game_mm.cheat_no_overload)
3490 laser.overloaded = FALSE;
3491 laser.overload_value = 0;
3494 game_mm.laser_overload_value = laser.overload_value;
3496 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3498 SetLaserColor(0xFF);
3500 DrawLaser(0, DL_LASER_ENABLED);
3503 if (!laser.overloaded)
3504 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3505 else if (setup.sound_loops)
3506 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3508 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3510 if (laser.overload_value == MAX_LASER_OVERLOAD)
3512 UpdateAndDisplayGameControlValues();
3516 GameOver_MM(GAME_OVER_OVERLOADED);
3527 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3529 if (game_mm.cheat_no_explosion)
3534 laser.dest_element = EL_EXPLODING_OPAQUE;
3539 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3541 laser.fuse_off = TRUE;
3545 DrawLaser(0, DL_LASER_DISABLED);
3546 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3549 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3551 if (!Store2[ELX][ELY]) // check if content element not yet determined
3553 int last_anim_random_frame = gfx.anim_random_frame;
3556 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3557 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3559 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3560 native_mm_level.ball_choice_mode, 0,
3561 game_mm.ball_choice_pos);
3563 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3564 gfx.anim_random_frame = last_anim_random_frame;
3566 game_mm.ball_choice_pos++;
3568 int new_element = native_mm_level.ball_content[element_pos];
3569 int new_element_base = map_wall_to_base_element(new_element);
3571 if (IS_WALL(new_element_base))
3573 // always use completely filled wall element
3574 new_element = new_element_base | 0x000f;
3576 else if (native_mm_level.rotate_ball_content &&
3577 get_num_elements(new_element) > 1)
3579 // randomly rotate newly created game element
3580 new_element = get_rotated_element(new_element, RND(16));
3583 Store[ELX][ELY] = new_element;
3584 Store2[ELX][ELY] = TRUE;
3587 if (native_mm_level.explode_ball)
3590 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3592 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3597 if (IS_WALL_ICE(element) && CT > 50)
3599 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3601 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3602 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3603 Store2[ELX][ELY] = laser.wall_mask;
3605 laser.dest_element = Tile[ELX][ELY];
3610 if (IS_WALL_AMOEBA(element) && CT > 60)
3613 int element2 = Tile[ELX][ELY];
3615 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3618 for (i = laser.num_damages - 1; i >= 0; i--)
3619 if (laser.damage[i].is_mirror)
3622 r = laser.num_edges;
3623 d = laser.num_damages;
3630 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3633 DrawLaser(0, DL_LASER_ENABLED);
3636 x = laser.damage[k1].x;
3637 y = laser.damage[k1].y;
3642 for (i = 0; i < 4; i++)
3644 if (laser.wall_mask & (1 << i))
3646 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3647 cSY + ELY * TILEY + 31 * (i / 2)))
3650 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3651 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3658 for (i = 0; i < 4; i++)
3660 if (laser.wall_mask & (1 << i))
3662 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3663 cSY + ELY * TILEY + 31 * (i / 2)))
3670 if (laser.num_beamers > 0 ||
3671 k1 < 1 || k2 < 4 || k3 < 4 ||
3672 CheckLaserPixel(cSX + ELX * TILEX + 14,
3673 cSY + ELY * TILEY + 14))
3675 laser.num_edges = r;
3676 laser.num_damages = d;
3678 DrawLaser(0, DL_LASER_DISABLED);
3681 Tile[ELX][ELY] = element | laser.wall_mask;
3683 int x = ELX, y = ELY;
3684 int wall_mask = laser.wall_mask;
3687 DrawLaser(0, DL_LASER_ENABLED);
3689 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3691 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3692 Store[x][y] = EL_WALL_AMOEBA_BASE;
3693 Store2[x][y] = wall_mask;
3698 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3699 laser.stops_inside_element && CT > native_mm_level.time_block)
3704 if (ABS(XS) > ABS(YS))
3711 for (i = 0; i < 4; i++)
3718 x = ELX + Step[k * 4].x;
3719 y = ELY + Step[k * 4].y;
3721 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3724 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3732 laser.overloaded = (element == EL_BLOCK_STONE);
3737 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3740 Tile[x][y] = element;
3742 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3745 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3747 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3748 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3756 if (element == EL_FUEL_FULL && CT > 10)
3758 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3759 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3761 for (i = start; i <= num_init_game_frames; i++)
3763 if (i == num_init_game_frames)
3764 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3765 else if (setup.sound_loops)
3766 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3768 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3770 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3772 UpdateAndDisplayGameControlValues();
3777 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3779 DrawField_MM(ELX, ELY);
3781 DrawLaser(0, DL_LASER_ENABLED);
3787 void GameActions_MM(struct MouseActionInfo action)
3789 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3790 boolean button_released = (action.button == MB_RELEASED);
3792 GameActions_MM_Ext();
3794 CheckSingleStepMode_MM(element_clicked, button_released);
3797 static void MovePacMen(void)
3799 int mx, my, ox, oy, nx, ny;
3803 if (++pacman_nr >= game_mm.num_pacman)
3806 game_mm.pacman[pacman_nr].dir--;
3808 for (l = 1; l < 5; l++)
3810 game_mm.pacman[pacman_nr].dir++;
3812 if (game_mm.pacman[pacman_nr].dir > 4)
3813 game_mm.pacman[pacman_nr].dir = 1;
3815 if (game_mm.pacman[pacman_nr].dir % 2)
3818 my = game_mm.pacman[pacman_nr].dir - 2;
3823 mx = 3 - game_mm.pacman[pacman_nr].dir;
3826 ox = game_mm.pacman[pacman_nr].x;
3827 oy = game_mm.pacman[pacman_nr].y;
3830 element = Tile[nx][ny];
3832 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3835 if (!IS_EATABLE4PACMAN(element))
3838 if (ObjHit(nx, ny, HIT_POS_CENTER))
3841 Tile[ox][oy] = EL_EMPTY;
3843 EL_PACMAN_RIGHT - 1 +
3844 (game_mm.pacman[pacman_nr].dir - 1 +
3845 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3847 game_mm.pacman[pacman_nr].x = nx;
3848 game_mm.pacman[pacman_nr].y = ny;
3850 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3852 if (element != EL_EMPTY)
3854 int graphic = el2gfx(Tile[nx][ny]);
3859 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3862 ox = cSX + ox * TILEX;
3863 oy = cSY + oy * TILEY;
3865 for (i = 1; i < 33; i += 2)
3866 BlitBitmap(bitmap, window,
3867 src_x, src_y, TILEX, TILEY,
3868 ox + i * mx, oy + i * my);
3869 Ct = Ct + FrameCounter - CT;
3872 DrawField_MM(nx, ny);
3875 if (!laser.fuse_off)
3877 DrawLaser(0, DL_LASER_ENABLED);
3879 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3881 AddDamagedField(nx, ny);
3883 laser.damage[laser.num_damages - 1].edge = 0;
3887 if (element == EL_BOMB)
3888 DeletePacMan(nx, ny);
3890 if (IS_WALL_AMOEBA(element) &&
3891 (LX + 2 * XS) / TILEX == nx &&
3892 (LY + 2 * YS) / TILEY == ny)
3902 static void InitMovingField_MM(int x, int y, int direction)
3904 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3905 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3907 MovDir[x][y] = direction;
3908 MovDir[newx][newy] = direction;
3910 if (Tile[newx][newy] == EL_EMPTY)
3911 Tile[newx][newy] = EL_BLOCKED;
3914 static int MovingOrBlocked2Element_MM(int x, int y)
3916 int element = Tile[x][y];
3918 if (element == EL_BLOCKED)
3922 Blocked2Moving(x, y, &oldx, &oldy);
3924 return Tile[oldx][oldy];
3930 static void RemoveMovingField_MM(int x, int y)
3932 int oldx = x, oldy = y, newx = x, newy = y;
3934 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3937 if (IS_MOVING(x, y))
3939 Moving2Blocked(x, y, &newx, &newy);
3940 if (Tile[newx][newy] != EL_BLOCKED)
3943 else if (Tile[x][y] == EL_BLOCKED)
3945 Blocked2Moving(x, y, &oldx, &oldy);
3946 if (!IS_MOVING(oldx, oldy))
3950 Tile[oldx][oldy] = EL_EMPTY;
3951 Tile[newx][newy] = EL_EMPTY;
3952 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3953 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3955 DrawLevelField_MM(oldx, oldy);
3956 DrawLevelField_MM(newx, newy);
3959 static void RaiseScore_MM(int value)
3961 game_mm.score += value;
3964 void RaiseScoreElement_MM(int element)
3969 case EL_PACMAN_RIGHT:
3971 case EL_PACMAN_LEFT:
3972 case EL_PACMAN_DOWN:
3973 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3977 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3982 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3986 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3995 // ----------------------------------------------------------------------------
3996 // Mirror Magic game engine snapshot handling functions
3997 // ----------------------------------------------------------------------------
3999 void SaveEngineSnapshotValues_MM(void)
4003 engine_snapshot_mm.game_mm = game_mm;
4004 engine_snapshot_mm.laser = laser;
4006 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4008 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4010 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4011 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4012 engine_snapshot_mm.Box[x][y] = Box[x][y];
4013 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4017 engine_snapshot_mm.LX = LX;
4018 engine_snapshot_mm.LY = LY;
4019 engine_snapshot_mm.XS = XS;
4020 engine_snapshot_mm.YS = YS;
4021 engine_snapshot_mm.ELX = ELX;
4022 engine_snapshot_mm.ELY = ELY;
4023 engine_snapshot_mm.CT = CT;
4024 engine_snapshot_mm.Ct = Ct;
4026 engine_snapshot_mm.last_LX = last_LX;
4027 engine_snapshot_mm.last_LY = last_LY;
4028 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4029 engine_snapshot_mm.hold_x = hold_x;
4030 engine_snapshot_mm.hold_y = hold_y;
4031 engine_snapshot_mm.pacman_nr = pacman_nr;
4033 engine_snapshot_mm.rotate_delay = rotate_delay;
4034 engine_snapshot_mm.pacman_delay = pacman_delay;
4035 engine_snapshot_mm.energy_delay = energy_delay;
4036 engine_snapshot_mm.overload_delay = overload_delay;
4039 void LoadEngineSnapshotValues_MM(void)
4043 // stored engine snapshot buffers already restored at this point
4045 game_mm = engine_snapshot_mm.game_mm;
4046 laser = engine_snapshot_mm.laser;
4048 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4050 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4052 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4053 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4054 Box[x][y] = engine_snapshot_mm.Box[x][y];
4055 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4059 LX = engine_snapshot_mm.LX;
4060 LY = engine_snapshot_mm.LY;
4061 XS = engine_snapshot_mm.XS;
4062 YS = engine_snapshot_mm.YS;
4063 ELX = engine_snapshot_mm.ELX;
4064 ELY = engine_snapshot_mm.ELY;
4065 CT = engine_snapshot_mm.CT;
4066 Ct = engine_snapshot_mm.Ct;
4068 last_LX = engine_snapshot_mm.last_LX;
4069 last_LY = engine_snapshot_mm.last_LY;
4070 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4071 hold_x = engine_snapshot_mm.hold_x;
4072 hold_y = engine_snapshot_mm.hold_y;
4073 pacman_nr = engine_snapshot_mm.pacman_nr;
4075 rotate_delay = engine_snapshot_mm.rotate_delay;
4076 pacman_delay = engine_snapshot_mm.pacman_delay;
4077 energy_delay = engine_snapshot_mm.energy_delay;
4078 overload_delay = engine_snapshot_mm.overload_delay;
4080 RedrawPlayfield_MM();
4083 static int getAngleFromTouchDelta(int dx, int dy, int base)
4085 double pi = 3.141592653;
4086 double rad = atan2((double)-dy, (double)dx);
4087 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4088 double deg = rad2 * 180.0 / pi;
4090 return (int)(deg * base / 360.0 + 0.5) % base;
4093 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4095 // calculate start (source) position to be at the middle of the tile
4096 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4097 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4098 int dx = dst_mx - src_mx;
4099 int dy = dst_my - src_my;
4108 if (!IN_LEV_FIELD(x, y))
4111 element = Tile[x][y];
4113 if (!IS_MCDUFFIN(element) &&
4114 !IS_MIRROR(element) &&
4115 !IS_BEAMER(element) &&
4116 !IS_POLAR(element) &&
4117 !IS_POLAR_CROSS(element) &&
4118 !IS_DF_MIRROR(element))
4121 angle_old = get_element_angle(element);
4123 if (IS_MCDUFFIN(element))
4125 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4126 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4127 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4128 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4131 else if (IS_MIRROR(element) ||
4132 IS_DF_MIRROR(element))
4134 for (i = 0; i < laser.num_damages; i++)
4136 if (laser.damage[i].x == x &&
4137 laser.damage[i].y == y &&
4138 ObjHit(x, y, HIT_POS_CENTER))
4140 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4141 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4148 if (angle_new == -1)
4150 if (IS_MIRROR(element) ||
4151 IS_DF_MIRROR(element) ||
4155 if (IS_POLAR_CROSS(element))
4158 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4161 button = (angle_new == angle_old ? 0 :
4162 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4163 MB_LEFTBUTTON : MB_RIGHTBUTTON);