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);
100 static void Moving2Blocked_MM(int, int, int *, int *);
102 static void AddLaserEdge(int, int);
103 static void ScanLaser(void);
104 static void DrawLaser(int, int);
105 static boolean HitElement(int, int);
106 static boolean HitOnlyAnEdge(int);
107 static boolean HitPolarizer(int, int);
108 static boolean HitBlock(int, int);
109 static boolean HitLaserSource(int, int);
110 static boolean HitLaserDestination(int, int);
111 static boolean HitReflectingWalls(int, int);
112 static boolean HitAbsorbingWalls(int, int);
113 static void RotateMirror(int, int, int);
114 static boolean ObjHit(int, int, int);
115 static void DeletePacMan(int, int);
116 static void MovePacMen(void);
118 // bitmap for laser beam detection
119 static Bitmap *laser_bitmap = NULL;
121 // variables for laser control
122 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
123 static int hold_x = -1, hold_y = -1;
125 // variables for pacman control
126 static int pacman_nr = -1;
128 // various game engine delay counters
129 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
130 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
131 static DelayCounter energy_delay = { ENERGY_DELAY };
132 static DelayCounter overload_delay = { 0 };
134 // element mask positions for scanning pixels of MM elements
135 #define MM_MASK_MCDUFFIN_RIGHT 0
136 #define MM_MASK_MCDUFFIN_UP 1
137 #define MM_MASK_MCDUFFIN_LEFT 2
138 #define MM_MASK_MCDUFFIN_DOWN 3
139 #define MM_MASK_GRID_1 4
140 #define MM_MASK_GRID_2 5
141 #define MM_MASK_GRID_3 6
142 #define MM_MASK_GRID_4 7
143 #define MM_MASK_RECTANGLE 8
144 #define MM_MASK_CIRCLE 9
146 #define NUM_MM_MASKS 10
148 // element masks for scanning pixels of MM elements
149 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
333 static int get_element_angle(int element)
335 int element_phase = get_element_phase(element);
337 if (IS_MIRROR_FIXED(element) ||
338 IS_MCDUFFIN(element) ||
340 IS_RECEIVER(element))
341 return 4 * element_phase;
343 return element_phase;
346 static int get_opposite_angle(int angle)
348 int opposite_angle = angle + ANG_RAY_180;
350 // make sure "opposite_angle" is in valid interval [0, 15]
351 return (opposite_angle + 16) % 16;
354 static int get_mirrored_angle(int laser_angle, int mirror_angle)
356 int reflected_angle = 16 - laser_angle + mirror_angle;
358 // make sure "reflected_angle" is in valid interval [0, 15]
359 return (reflected_angle + 16) % 16;
362 static void DrawLaserLines(struct XY *points, int num_points, int mode)
364 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
365 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
367 DrawLines(drawto, points, num_points, pixel_drawto);
371 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
376 static boolean CheckLaserPixel(int x, int y)
382 pixel = ReadPixel(laser_bitmap, x, y);
386 return (pixel == WHITE_PIXEL);
389 static void CheckExitMM(void)
391 int exit_element = EL_EMPTY;
395 static int xy[4][2] =
403 for (y = 0; y < lev_fieldy; y++)
405 for (x = 0; x < lev_fieldx; x++)
407 if (Tile[x][y] == EL_EXIT_CLOSED)
409 // initiate opening animation of exit door
410 Tile[x][y] = EL_EXIT_OPENING;
412 exit_element = EL_EXIT_OPEN;
416 else if (IS_RECEIVER(Tile[x][y]))
418 // remove field that blocks receiver
419 int phase = Tile[x][y] - EL_RECEIVER_START;
420 int blocking_x, blocking_y;
422 blocking_x = x + xy[phase][0];
423 blocking_y = y + xy[phase][1];
425 if (IN_LEV_FIELD(blocking_x, blocking_y))
427 Tile[blocking_x][blocking_y] = EL_EMPTY;
429 DrawField_MM(blocking_x, blocking_y);
432 exit_element = EL_RECEIVER;
439 if (exit_element != EL_EMPTY)
440 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
443 static void SetLaserColor(int brightness)
445 int color_min = 0x00;
446 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
447 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
448 int color_down = color_max - color_up;
451 GetPixelFromRGB(window,
452 (game_mm.laser_red ? color_max : color_up),
453 (game_mm.laser_green ? color_down : color_min),
454 (game_mm.laser_blue ? color_down : color_min));
457 static void InitMovDir_MM(int x, int y)
459 int element = Tile[x][y];
460 static int direction[3][4] =
462 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
463 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
464 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
469 case EL_PACMAN_RIGHT:
473 Tile[x][y] = EL_PACMAN;
474 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
482 static void InitField(int x, int y, boolean init_game)
484 int element = Tile[x][y];
489 Tile[x][y] = EL_EMPTY;
494 if (init_game && native_mm_level.auto_count_kettles)
495 game_mm.kettles_still_needed++;
498 case EL_LIGHTBULB_OFF:
499 game_mm.lights_still_needed++;
503 if (IS_MIRROR(element) ||
504 IS_BEAMER_OLD(element) ||
505 IS_BEAMER(element) ||
507 IS_POLAR_CROSS(element) ||
508 IS_DF_MIRROR(element) ||
509 IS_DF_MIRROR_AUTO(element) ||
510 IS_GRID_STEEL_AUTO(element) ||
511 IS_GRID_WOOD_AUTO(element) ||
512 IS_FIBRE_OPTIC(element))
514 if (IS_BEAMER_OLD(element))
516 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
517 element = Tile[x][y];
520 if (!IS_FIBRE_OPTIC(element))
522 static int steps_grid_auto = 0;
524 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
525 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
527 if (IS_GRID_STEEL_AUTO(element) ||
528 IS_GRID_WOOD_AUTO(element))
529 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
531 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
533 game_mm.cycle[game_mm.num_cycle].x = x;
534 game_mm.cycle[game_mm.num_cycle].y = y;
538 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
540 int beamer_nr = BEAMER_NR(element);
541 int nr = laser.beamer[beamer_nr][0].num;
543 laser.beamer[beamer_nr][nr].x = x;
544 laser.beamer[beamer_nr][nr].y = y;
545 laser.beamer[beamer_nr][nr].num = 1;
548 else if (IS_PACMAN(element))
552 else if (IS_MCDUFFIN(element) || IS_LASER(element))
556 laser.start_edge.x = x;
557 laser.start_edge.y = y;
558 laser.start_angle = get_element_angle(element);
561 if (IS_MCDUFFIN(element))
563 game_mm.laser_red = native_mm_level.mm_laser_red;
564 game_mm.laser_green = native_mm_level.mm_laser_green;
565 game_mm.laser_blue = native_mm_level.mm_laser_blue;
569 game_mm.laser_red = native_mm_level.df_laser_red;
570 game_mm.laser_green = native_mm_level.df_laser_green;
571 game_mm.laser_blue = native_mm_level.df_laser_blue;
579 static void InitCycleElements_RotateSingleStep(void)
583 if (game_mm.num_cycle == 0) // no elements to cycle
586 for (i = 0; i < game_mm.num_cycle; i++)
588 int x = game_mm.cycle[i].x;
589 int y = game_mm.cycle[i].y;
590 int step = SIGN(game_mm.cycle[i].steps);
591 int last_element = Tile[x][y];
592 int next_element = get_rotated_element(last_element, step);
594 if (!game_mm.cycle[i].steps)
597 Tile[x][y] = next_element;
599 game_mm.cycle[i].steps -= step;
603 static void InitLaser(void)
605 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
606 int step = (IS_LASER(start_element) ? 4 : 0);
608 LX = laser.start_edge.x * TILEX;
609 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
612 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
614 LY = laser.start_edge.y * TILEY;
615 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
616 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
620 XS = 2 * Step[laser.start_angle].x;
621 YS = 2 * Step[laser.start_angle].y;
623 laser.current_angle = laser.start_angle;
625 laser.num_damages = 0;
627 laser.num_beamers = 0;
628 laser.beamer_edge[0] = 0;
630 laser.dest_element = EL_EMPTY;
633 AddLaserEdge(LX, LY); // set laser starting edge
638 void InitGameEngine_MM(void)
644 // initialize laser bitmap to current playfield (screen) size
645 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
646 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
650 // set global game control values
651 game_mm.num_cycle = 0;
652 game_mm.num_pacman = 0;
655 game_mm.energy_left = 0; // later set to "native_mm_level.time"
656 game_mm.kettles_still_needed =
657 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
658 game_mm.lights_still_needed = 0;
659 game_mm.num_keys = 0;
660 game_mm.ball_choice_pos = 0;
662 game_mm.laser_red = FALSE;
663 game_mm.laser_green = FALSE;
664 game_mm.laser_blue = TRUE;
666 game_mm.level_solved = FALSE;
667 game_mm.game_over = FALSE;
668 game_mm.game_over_cause = 0;
670 game_mm.laser_overload_value = 0;
671 game_mm.laser_enabled = FALSE;
673 // set global laser control values (must be set before "InitLaser()")
674 laser.start_edge.x = 0;
675 laser.start_edge.y = 0;
676 laser.start_angle = 0;
678 for (i = 0; i < MAX_NUM_BEAMERS; i++)
679 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
681 laser.overloaded = FALSE;
682 laser.overload_value = 0;
683 laser.fuse_off = FALSE;
684 laser.fuse_x = laser.fuse_y = -1;
686 laser.dest_element = EL_EMPTY;
687 laser.dest_element_last = EL_EMPTY;
688 laser.dest_element_last_x = -1;
689 laser.dest_element_last_y = -1;
703 rotate_delay.count = 0;
704 pacman_delay.count = 0;
705 energy_delay.count = 0;
706 overload_delay.count = 0;
708 ClickElement(-1, -1, -1);
710 for (x = 0; x < lev_fieldx; x++)
712 for (y = 0; y < lev_fieldy; y++)
714 Tile[x][y] = Ur[x][y];
715 Hit[x][y] = Box[x][y] = 0;
717 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
718 Store[x][y] = Store2[x][y] = 0;
721 InitField(x, y, TRUE);
728 void InitGameActions_MM(void)
730 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
731 int cycle_steps_done = 0;
736 for (i = 0; i <= num_init_game_frames; i++)
738 if (i == num_init_game_frames)
739 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
740 else if (setup.sound_loops)
741 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
743 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
745 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
747 UpdateAndDisplayGameControlValues();
749 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
751 InitCycleElements_RotateSingleStep();
756 AdvanceFrameCounter();
764 if (setup.quick_doors)
771 if (game_mm.kettles_still_needed == 0)
774 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
775 SetTileCursorActive(TRUE);
777 // restart all delay counters after initially cycling game elements
778 ResetFrameCounter(&rotate_delay);
779 ResetFrameCounter(&pacman_delay);
780 ResetFrameCounter(&energy_delay);
781 ResetFrameCounter(&overload_delay);
784 static void FadeOutLaser(void)
788 for (i = 15; i >= 0; i--)
790 SetLaserColor(0x11 * i);
792 DrawLaser(0, DL_LASER_ENABLED);
795 Delay_WithScreenUpdates(50);
798 DrawLaser(0, DL_LASER_DISABLED);
800 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
803 static void GameOver_MM(int game_over_cause)
805 // do not handle game over if request dialog is already active
806 if (game.request_active)
809 game_mm.game_over = TRUE;
810 game_mm.game_over_cause = game_over_cause;
812 if (setup.ask_on_game_over)
813 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
814 "Bomb killed Mc Duffin! Play it again?" :
815 game_over_cause == GAME_OVER_NO_ENERGY ?
816 "Out of magic energy! Play it again?" :
817 game_over_cause == GAME_OVER_OVERLOADED ?
818 "Magic spell hit Mc Duffin! Play it again?" :
821 SetTileCursorActive(FALSE);
824 static void AddLaserEdge(int lx, int ly)
829 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
831 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
836 laser.edge[laser.num_edges].x = cSX2 + lx;
837 laser.edge[laser.num_edges].y = cSY2 + ly;
843 static void AddDamagedField(int ex, int ey)
845 // prevent adding the same field position again
846 if (laser.num_damages > 0 &&
847 laser.damage[laser.num_damages - 1].x == ex &&
848 laser.damage[laser.num_damages - 1].y == ey &&
849 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
852 laser.damage[laser.num_damages].is_mirror = FALSE;
853 laser.damage[laser.num_damages].angle = laser.current_angle;
854 laser.damage[laser.num_damages].edge = laser.num_edges;
855 laser.damage[laser.num_damages].x = ex;
856 laser.damage[laser.num_damages].y = ey;
860 static boolean StepBehind(void)
866 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
867 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
869 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
875 static int getMaskFromElement(int element)
877 if (IS_GRID(element))
878 return MM_MASK_GRID_1 + get_element_phase(element);
879 else if (IS_MCDUFFIN(element))
880 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
881 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
882 return MM_MASK_RECTANGLE;
884 return MM_MASK_CIRCLE;
887 static int ScanPixel(void)
892 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
893 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
896 // follow laser beam until it hits something (at least the screen border)
897 while (hit_mask == HIT_MASK_NO_HIT)
903 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
904 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
906 Debug("game:mm:ScanPixel", "touched screen border!");
912 for (i = 0; i < 4; i++)
914 int px = LX + (i % 2) * 2;
915 int py = LY + (i / 2) * 2;
918 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
919 int ly = (py + TILEY) / TILEY - 1; // negative values!
922 if (IN_LEV_FIELD(lx, ly))
924 int element = Tile[lx][ly];
926 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
930 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
932 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
934 pixel = ((element & (1 << pos)) ? 1 : 0);
938 int pos = getMaskFromElement(element);
940 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
945 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
946 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
949 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
950 hit_mask |= (1 << i);
953 if (hit_mask == HIT_MASK_NO_HIT)
955 // hit nothing -- go on with another step
964 static void DeactivateLaserTargetElement(void)
966 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
967 laser.dest_element_last == EL_MINE_ACTIVE ||
968 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
969 laser.dest_element_last == EL_GRAY_BALL_OPENING)
971 int x = laser.dest_element_last_x;
972 int y = laser.dest_element_last_y;
973 int element = laser.dest_element_last;
975 if (Tile[x][y] == element)
976 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
977 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
979 if (Tile[x][y] == EL_GRAY_BALL)
982 laser.dest_element_last = EL_EMPTY;
983 laser.dest_element_last_x = -1;
984 laser.dest_element_last_y = -1;
988 static void ScanLaser(void)
990 int element = EL_EMPTY;
991 int last_element = EL_EMPTY;
992 int end = 0, rf = laser.num_edges;
994 // do not scan laser again after the game was lost for whatever reason
995 if (game_mm.game_over)
998 // do not scan laser if fuse is off
1002 DeactivateLaserTargetElement();
1004 laser.overloaded = FALSE;
1005 laser.stops_inside_element = FALSE;
1007 DrawLaser(0, DL_LASER_ENABLED);
1010 Debug("game:mm:ScanLaser",
1011 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1019 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1022 laser.overloaded = TRUE;
1027 hit_mask = ScanPixel();
1030 Debug("game:mm:ScanLaser",
1031 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1035 // hit something -- check out what it was
1036 ELX = (LX + XS) / TILEX;
1037 ELY = (LY + YS) / TILEY;
1040 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1041 hit_mask, LX, LY, ELX, ELY);
1044 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1047 laser.dest_element = element;
1052 if (hit_mask == HIT_MASK_DIAGONAL_1 || hit_mask == HIT_MASK_DIAGONAL_2)
1054 // we have hit two diagonally adjacent elements -- compare them
1055 boolean dia_1 = (hit_mask == HIT_MASK_DIAGONAL_1);
1057 int yoffset = 2 * (dia_1 ? -1 : +1);
1058 int elx1 = (LX - xoffset) / TILEX;
1059 int ely1 = (LY + yoffset) / TILEY;
1060 int elx2 = (LX + xoffset) / TILEX;
1061 int ely2 = (LY - yoffset) / TILEY;
1062 int e1 = Tile[elx1][ely1];
1063 int e2 = Tile[elx2][ely2];
1064 boolean use_element_1 = FALSE;
1066 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1068 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1069 use_element_1 = (RND(2) ? TRUE : FALSE);
1070 else if (IS_WALL_ICE(e1))
1071 use_element_1 = TRUE;
1073 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1075 if (IS_WALL_AMOEBA(e1) && IS_WALL_AMOEBA(e2))
1076 use_element_1 = (RND(2) ? TRUE : FALSE);
1077 else if (IS_WALL_AMOEBA(e1))
1078 use_element_1 = TRUE;
1080 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1082 if (IS_ABSORBING_BLOCK(e1) && IS_ABSORBING_BLOCK(e2))
1083 use_element_1 = (RND(2) ? TRUE : FALSE);
1084 else if (IS_ABSORBING_BLOCK(e1))
1085 use_element_1 = TRUE;
1088 ELX = (use_element_1 ? elx1 : elx2);
1089 ELY = (use_element_1 ? ely1 : ely2);
1093 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1094 hit_mask, LX, LY, ELX, ELY);
1097 last_element = element;
1099 element = Tile[ELX][ELY];
1100 laser.dest_element = element;
1103 Debug("game:mm:ScanLaser",
1104 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1107 LX % TILEX, LY % TILEY,
1112 if (!IN_LEV_FIELD(ELX, ELY))
1113 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1117 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1118 if (element == EL_EMPTY &&
1119 IS_GRID_STEEL(last_element) &&
1120 laser.current_angle % 4) // angle is not 90°
1121 element = last_element;
1123 if (element == EL_EMPTY)
1125 if (!HitOnlyAnEdge(hit_mask))
1128 else if (element == EL_FUSE_ON)
1130 if (HitPolarizer(element, hit_mask))
1133 else if (IS_GRID(element) || IS_DF_GRID(element))
1135 if (HitPolarizer(element, hit_mask))
1138 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1139 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1141 if (HitBlock(element, hit_mask))
1148 else if (IS_MCDUFFIN(element))
1150 if (HitLaserSource(element, hit_mask))
1153 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1154 IS_RECEIVER(element))
1156 if (HitLaserDestination(element, hit_mask))
1159 else if (IS_WALL(element))
1161 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1163 if (HitReflectingWalls(element, hit_mask))
1168 if (HitAbsorbingWalls(element, hit_mask))
1174 if (HitElement(element, hit_mask))
1179 DrawLaser(rf - 1, DL_LASER_ENABLED);
1180 rf = laser.num_edges;
1182 if (!IS_DF_WALL_STEEL(element))
1184 // only used for scanning DF steel walls; reset for all other elements
1192 if (laser.dest_element != Tile[ELX][ELY])
1194 Debug("game:mm:ScanLaser",
1195 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1196 laser.dest_element, Tile[ELX][ELY]);
1200 if (!end && !laser.stops_inside_element && !StepBehind())
1203 Debug("game:mm:ScanLaser", "Go one step back");
1209 AddLaserEdge(LX, LY);
1213 DrawLaser(rf - 1, DL_LASER_ENABLED);
1215 Ct = CT = FrameCounter;
1218 if (!IN_LEV_FIELD(ELX, ELY))
1219 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1223 static void ScanLaser_FromLastMirror(void)
1225 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1228 for (i = start_pos; i >= 0; i--)
1229 if (laser.damage[i].is_mirror)
1232 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1234 DrawLaser(start_edge, DL_LASER_DISABLED);
1239 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1245 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1246 start_edge, num_edges, mode);
1251 Warn("DrawLaserExt: start_edge < 0");
1258 Warn("DrawLaserExt: num_edges < 0");
1264 if (mode == DL_LASER_DISABLED)
1266 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1270 // now draw the laser to the backbuffer and (if enabled) to the screen
1271 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1273 redraw_mask |= REDRAW_FIELD;
1275 if (mode == DL_LASER_ENABLED)
1278 // after the laser was deleted, the "damaged" graphics must be restored
1279 if (laser.num_damages)
1281 int damage_start = 0;
1284 // determine the starting edge, from which graphics need to be restored
1287 for (i = 0; i < laser.num_damages; i++)
1289 if (laser.damage[i].edge == start_edge + 1)
1298 // restore graphics from this starting edge to the end of damage list
1299 for (i = damage_start; i < laser.num_damages; i++)
1301 int lx = laser.damage[i].x;
1302 int ly = laser.damage[i].y;
1303 int element = Tile[lx][ly];
1305 if (Hit[lx][ly] == laser.damage[i].edge)
1306 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1309 if (Box[lx][ly] == laser.damage[i].edge)
1312 if (IS_DRAWABLE(element))
1313 DrawField_MM(lx, ly);
1316 elx = laser.damage[damage_start].x;
1317 ely = laser.damage[damage_start].y;
1318 element = Tile[elx][ely];
1321 if (IS_BEAMER(element))
1325 for (i = 0; i < laser.num_beamers; i++)
1326 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1328 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1329 mode, elx, ely, Hit[elx][ely], start_edge);
1330 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1331 get_element_angle(element), laser.damage[damage_start].angle);
1335 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1336 laser.num_beamers > 0 &&
1337 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1339 // element is outgoing beamer
1340 laser.num_damages = damage_start + 1;
1342 if (IS_BEAMER(element))
1343 laser.current_angle = get_element_angle(element);
1347 // element is incoming beamer or other element
1348 laser.num_damages = damage_start;
1349 laser.current_angle = laser.damage[laser.num_damages].angle;
1354 // no damages but McDuffin himself (who needs to be redrawn anyway)
1356 elx = laser.start_edge.x;
1357 ely = laser.start_edge.y;
1358 element = Tile[elx][ely];
1361 laser.num_edges = start_edge + 1;
1362 if (start_edge == 0)
1363 laser.current_angle = laser.start_angle;
1365 LX = laser.edge[start_edge].x - cSX2;
1366 LY = laser.edge[start_edge].y - cSY2;
1367 XS = 2 * Step[laser.current_angle].x;
1368 YS = 2 * Step[laser.current_angle].y;
1371 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1377 if (IS_BEAMER(element) ||
1378 IS_FIBRE_OPTIC(element) ||
1379 IS_PACMAN(element) ||
1380 IS_POLAR(element) ||
1381 IS_POLAR_CROSS(element) ||
1382 element == EL_FUSE_ON)
1387 Debug("game:mm:DrawLaserExt", "element == %d", element);
1390 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1391 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1395 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1396 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1397 (laser.num_beamers == 0 ||
1398 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1400 // element is incoming beamer or other element
1401 step_size = -step_size;
1406 if (IS_BEAMER(element))
1407 Debug("game:mm:DrawLaserExt",
1408 "start_edge == %d, laser.beamer_edge == %d",
1409 start_edge, laser.beamer_edge);
1412 LX += step_size * XS;
1413 LY += step_size * YS;
1415 else if (element != EL_EMPTY)
1424 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1429 void DrawLaser(int start_edge, int mode)
1431 // do not draw laser if fuse is off
1432 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1435 if (mode == DL_LASER_DISABLED)
1436 DeactivateLaserTargetElement();
1438 if (laser.num_edges - start_edge < 0)
1440 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1445 // check if laser is interrupted by beamer element
1446 if (laser.num_beamers > 0 &&
1447 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1449 if (mode == DL_LASER_ENABLED)
1452 int tmp_start_edge = start_edge;
1454 // draw laser segments forward from the start to the last beamer
1455 for (i = 0; i < laser.num_beamers; i++)
1457 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1459 if (tmp_num_edges <= 0)
1463 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1464 i, laser.beamer_edge[i], tmp_start_edge);
1467 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1469 tmp_start_edge = laser.beamer_edge[i];
1472 // draw last segment from last beamer to the end
1473 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1479 int last_num_edges = laser.num_edges;
1480 int num_beamers = laser.num_beamers;
1482 // delete laser segments backward from the end to the first beamer
1483 for (i = num_beamers - 1; i >= 0; i--)
1485 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1487 if (laser.beamer_edge[i] - start_edge <= 0)
1490 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1492 last_num_edges = laser.beamer_edge[i];
1493 laser.num_beamers--;
1497 if (last_num_edges - start_edge <= 0)
1498 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1499 last_num_edges, start_edge);
1502 // special case when rotating first beamer: delete laser edge on beamer
1503 // (but do not start scanning on previous edge to prevent mirror sound)
1504 if (last_num_edges - start_edge == 1 && start_edge > 0)
1505 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1507 // delete first segment from start to the first beamer
1508 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1513 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1516 game_mm.laser_enabled = mode;
1519 void DrawLaser_MM(void)
1521 DrawLaser(0, game_mm.laser_enabled);
1524 static boolean HitElement(int element, int hit_mask)
1526 if (HitOnlyAnEdge(hit_mask))
1529 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1530 element = MovingOrBlocked2Element_MM(ELX, ELY);
1533 Debug("game:mm:HitElement", "(1): element == %d", element);
1537 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1538 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1541 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1545 AddDamagedField(ELX, ELY);
1547 // this is more precise: check if laser would go through the center
1548 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1552 // prevent cutting through laser emitter with laser beam
1553 if (IS_LASER(element))
1556 // skip the whole element before continuing the scan
1564 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1566 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1568 /* skipping scan positions to the right and down skips one scan
1569 position too much, because this is only the top left scan position
1570 of totally four scan positions (plus one to the right, one to the
1571 bottom and one to the bottom right) */
1572 /* ... but only roll back scan position if more than one step done */
1582 Debug("game:mm:HitElement", "(2): element == %d", element);
1585 if (LX + 5 * XS < 0 ||
1595 Debug("game:mm:HitElement", "(3): element == %d", element);
1598 if (IS_POLAR(element) &&
1599 ((element - EL_POLAR_START) % 2 ||
1600 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1602 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1604 laser.num_damages--;
1609 if (IS_POLAR_CROSS(element) &&
1610 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1612 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1614 laser.num_damages--;
1619 if (!IS_BEAMER(element) &&
1620 !IS_FIBRE_OPTIC(element) &&
1621 !IS_GRID_WOOD(element) &&
1622 element != EL_FUEL_EMPTY)
1625 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1626 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1628 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1631 LX = ELX * TILEX + 14;
1632 LY = ELY * TILEY + 14;
1634 AddLaserEdge(LX, LY);
1637 if (IS_MIRROR(element) ||
1638 IS_MIRROR_FIXED(element) ||
1639 IS_POLAR(element) ||
1640 IS_POLAR_CROSS(element) ||
1641 IS_DF_MIRROR(element) ||
1642 IS_DF_MIRROR_AUTO(element) ||
1643 element == EL_PRISM ||
1644 element == EL_REFRACTOR)
1646 int current_angle = laser.current_angle;
1649 laser.num_damages--;
1651 AddDamagedField(ELX, ELY);
1653 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1656 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1658 if (IS_MIRROR(element) ||
1659 IS_MIRROR_FIXED(element) ||
1660 IS_DF_MIRROR(element) ||
1661 IS_DF_MIRROR_AUTO(element))
1662 laser.current_angle = get_mirrored_angle(laser.current_angle,
1663 get_element_angle(element));
1665 if (element == EL_PRISM || element == EL_REFRACTOR)
1666 laser.current_angle = RND(16);
1668 XS = 2 * Step[laser.current_angle].x;
1669 YS = 2 * Step[laser.current_angle].y;
1671 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1676 LX += step_size * XS;
1677 LY += step_size * YS;
1679 // draw sparkles on mirror
1680 if ((IS_MIRROR(element) ||
1681 IS_MIRROR_FIXED(element) ||
1682 element == EL_PRISM) &&
1683 current_angle != laser.current_angle)
1685 MovDelay[ELX][ELY] = 11; // start animation
1688 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1689 current_angle != laser.current_angle)
1690 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1693 (get_opposite_angle(laser.current_angle) ==
1694 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1696 return (laser.overloaded ? TRUE : FALSE);
1699 if (element == EL_FUEL_FULL)
1701 laser.stops_inside_element = TRUE;
1706 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1708 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1710 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1711 element == EL_MINE ? EL_MINE_ACTIVE :
1712 EL_GRAY_BALL_ACTIVE);
1714 GfxFrame[ELX][ELY] = 0; // restart animation
1716 laser.dest_element_last = Tile[ELX][ELY];
1717 laser.dest_element_last_x = ELX;
1718 laser.dest_element_last_y = ELY;
1720 if (element == EL_MINE)
1721 laser.overloaded = TRUE;
1724 if (element == EL_KETTLE ||
1725 element == EL_CELL ||
1726 element == EL_KEY ||
1727 element == EL_LIGHTBALL ||
1728 element == EL_PACMAN ||
1729 IS_PACMAN(element) ||
1730 IS_ENVELOPE(element))
1732 if (!IS_PACMAN(element) &&
1733 !IS_ENVELOPE(element))
1736 if (element == EL_PACMAN)
1739 if (element == EL_KETTLE || element == EL_CELL)
1741 if (game_mm.kettles_still_needed > 0)
1742 game_mm.kettles_still_needed--;
1744 game.snapshot.collected_item = TRUE;
1746 if (game_mm.kettles_still_needed == 0)
1750 DrawLaser(0, DL_LASER_ENABLED);
1753 else if (element == EL_KEY)
1757 else if (IS_PACMAN(element))
1759 DeletePacMan(ELX, ELY);
1761 else if (IS_ENVELOPE(element))
1763 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1766 RaiseScoreElement_MM(element);
1771 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1773 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1775 DrawLaser(0, DL_LASER_ENABLED);
1777 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1779 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1780 game_mm.lights_still_needed--;
1784 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1785 game_mm.lights_still_needed++;
1788 DrawField_MM(ELX, ELY);
1789 DrawLaser(0, DL_LASER_ENABLED);
1794 laser.stops_inside_element = TRUE;
1800 Debug("game:mm:HitElement", "(4): element == %d", element);
1803 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1804 laser.num_beamers < MAX_NUM_BEAMERS &&
1805 laser.beamer[BEAMER_NR(element)][1].num)
1807 int beamer_angle = get_element_angle(element);
1808 int beamer_nr = BEAMER_NR(element);
1812 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1815 laser.num_damages--;
1817 if (IS_FIBRE_OPTIC(element) ||
1818 laser.current_angle == get_opposite_angle(beamer_angle))
1822 LX = ELX * TILEX + 14;
1823 LY = ELY * TILEY + 14;
1825 AddLaserEdge(LX, LY);
1826 AddDamagedField(ELX, ELY);
1828 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1831 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1833 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1834 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1835 ELX = laser.beamer[beamer_nr][pos].x;
1836 ELY = laser.beamer[beamer_nr][pos].y;
1837 LX = ELX * TILEX + 14;
1838 LY = ELY * TILEY + 14;
1840 if (IS_BEAMER(element))
1842 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1843 XS = 2 * Step[laser.current_angle].x;
1844 YS = 2 * Step[laser.current_angle].y;
1847 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1849 AddLaserEdge(LX, LY);
1850 AddDamagedField(ELX, ELY);
1852 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1855 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1857 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1862 LX += step_size * XS;
1863 LY += step_size * YS;
1865 laser.num_beamers++;
1874 static boolean HitOnlyAnEdge(int hit_mask)
1876 // check if the laser hit only the edge of an element and, if so, go on
1879 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1883 if ((hit_mask == HIT_MASK_TOPLEFT ||
1884 hit_mask == HIT_MASK_TOPRIGHT ||
1885 hit_mask == HIT_MASK_BOTTOMLEFT ||
1886 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1887 laser.current_angle % 4) // angle is not 90°
1891 if (hit_mask == HIT_MASK_TOPLEFT)
1896 else if (hit_mask == HIT_MASK_TOPRIGHT)
1901 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1906 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1912 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1918 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1925 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1931 static boolean HitPolarizer(int element, int hit_mask)
1933 if (HitOnlyAnEdge(hit_mask))
1936 if (IS_DF_GRID(element))
1938 int grid_angle = get_element_angle(element);
1941 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1942 grid_angle, laser.current_angle);
1945 AddLaserEdge(LX, LY);
1946 AddDamagedField(ELX, ELY);
1949 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1951 if (laser.current_angle == grid_angle ||
1952 laser.current_angle == get_opposite_angle(grid_angle))
1954 // skip the whole element before continuing the scan
1960 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1962 if (LX/TILEX > ELX || LY/TILEY > ELY)
1964 /* skipping scan positions to the right and down skips one scan
1965 position too much, because this is only the top left scan position
1966 of totally four scan positions (plus one to the right, one to the
1967 bottom and one to the bottom right) */
1973 AddLaserEdge(LX, LY);
1979 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1981 LX / TILEX, LY / TILEY,
1982 LX % TILEX, LY % TILEY);
1987 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1989 return HitReflectingWalls(element, hit_mask);
1993 return HitAbsorbingWalls(element, hit_mask);
1996 else if (IS_GRID_STEEL(element))
1998 return HitReflectingWalls(element, hit_mask);
2000 else // IS_GRID_WOOD
2002 return HitAbsorbingWalls(element, hit_mask);
2008 static boolean HitBlock(int element, int hit_mask)
2010 boolean check = FALSE;
2012 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2013 game_mm.num_keys == 0)
2016 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2019 int ex = ELX * TILEX + 14;
2020 int ey = ELY * TILEY + 14;
2024 for (i = 1; i < 32; i++)
2029 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2034 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2035 return HitAbsorbingWalls(element, hit_mask);
2039 AddLaserEdge(LX - XS, LY - YS);
2040 AddDamagedField(ELX, ELY);
2043 Box[ELX][ELY] = laser.num_edges;
2045 return HitReflectingWalls(element, hit_mask);
2048 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2050 int xs = XS / 2, ys = YS / 2;
2051 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2052 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2054 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2055 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2057 laser.overloaded = (element == EL_GATE_STONE);
2062 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2063 (hit_mask == HIT_MASK_TOP ||
2064 hit_mask == HIT_MASK_LEFT ||
2065 hit_mask == HIT_MASK_RIGHT ||
2066 hit_mask == HIT_MASK_BOTTOM))
2067 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2068 hit_mask == HIT_MASK_BOTTOM),
2069 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2070 hit_mask == HIT_MASK_RIGHT));
2071 AddLaserEdge(LX, LY);
2077 if (element == EL_GATE_STONE && Box[ELX][ELY])
2079 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2091 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2093 int xs = XS / 2, ys = YS / 2;
2094 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2095 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2097 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2098 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2100 laser.overloaded = (element == EL_BLOCK_STONE);
2105 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2106 (hit_mask == HIT_MASK_TOP ||
2107 hit_mask == HIT_MASK_LEFT ||
2108 hit_mask == HIT_MASK_RIGHT ||
2109 hit_mask == HIT_MASK_BOTTOM))
2110 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2111 hit_mask == HIT_MASK_BOTTOM),
2112 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2113 hit_mask == HIT_MASK_RIGHT));
2114 AddDamagedField(ELX, ELY);
2116 LX = ELX * TILEX + 14;
2117 LY = ELY * TILEY + 14;
2119 AddLaserEdge(LX, LY);
2121 laser.stops_inside_element = TRUE;
2129 static boolean HitLaserSource(int element, int hit_mask)
2131 if (HitOnlyAnEdge(hit_mask))
2134 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2136 laser.overloaded = TRUE;
2141 static boolean HitLaserDestination(int element, int hit_mask)
2143 if (HitOnlyAnEdge(hit_mask))
2146 if (element != EL_EXIT_OPEN &&
2147 !(IS_RECEIVER(element) &&
2148 game_mm.kettles_still_needed == 0 &&
2149 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2151 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2156 if (IS_RECEIVER(element) ||
2157 (IS_22_5_ANGLE(laser.current_angle) &&
2158 (ELX != (LX + 6 * XS) / TILEX ||
2159 ELY != (LY + 6 * YS) / TILEY ||
2168 LX = ELX * TILEX + 14;
2169 LY = ELY * TILEY + 14;
2171 laser.stops_inside_element = TRUE;
2174 AddLaserEdge(LX, LY);
2175 AddDamagedField(ELX, ELY);
2177 if (game_mm.lights_still_needed == 0)
2179 game_mm.level_solved = TRUE;
2181 SetTileCursorActive(FALSE);
2187 static boolean HitReflectingWalls(int element, int hit_mask)
2189 // check if laser hits side of a wall with an angle that is not 90°
2190 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2191 hit_mask == HIT_MASK_LEFT ||
2192 hit_mask == HIT_MASK_RIGHT ||
2193 hit_mask == HIT_MASK_BOTTOM))
2195 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2200 if (!IS_DF_GRID(element))
2201 AddLaserEdge(LX, LY);
2203 // check if laser hits wall with an angle of 45°
2204 if (!IS_22_5_ANGLE(laser.current_angle))
2206 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2209 laser.current_angle = get_mirrored_angle(laser.current_angle,
2212 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2215 laser.current_angle = get_mirrored_angle(laser.current_angle,
2219 AddLaserEdge(LX, LY);
2221 XS = 2 * Step[laser.current_angle].x;
2222 YS = 2 * Step[laser.current_angle].y;
2226 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2228 laser.current_angle = get_mirrored_angle(laser.current_angle,
2233 if (!IS_DF_GRID(element))
2234 AddLaserEdge(LX, LY);
2239 if (!IS_DF_GRID(element))
2240 AddLaserEdge(LX, LY + YS / 2);
2243 if (!IS_DF_GRID(element))
2244 AddLaserEdge(LX, LY);
2247 YS = 2 * Step[laser.current_angle].y;
2251 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2253 laser.current_angle = get_mirrored_angle(laser.current_angle,
2258 if (!IS_DF_GRID(element))
2259 AddLaserEdge(LX, LY);
2264 if (!IS_DF_GRID(element))
2265 AddLaserEdge(LX + XS / 2, LY);
2268 if (!IS_DF_GRID(element))
2269 AddLaserEdge(LX, LY);
2272 XS = 2 * Step[laser.current_angle].x;
2278 // reflection at the edge of reflecting DF style wall
2279 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2281 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2282 hit_mask == HIT_MASK_TOPRIGHT) ||
2283 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2284 hit_mask == HIT_MASK_TOPLEFT) ||
2285 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2286 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2287 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2288 hit_mask == HIT_MASK_BOTTOMRIGHT))
2291 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2292 ANG_MIRROR_135 : ANG_MIRROR_45);
2294 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2296 AddDamagedField(ELX, ELY);
2297 AddLaserEdge(LX, LY);
2299 laser.current_angle = get_mirrored_angle(laser.current_angle,
2307 AddLaserEdge(LX, LY);
2313 // reflection inside an edge of reflecting DF style wall
2314 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2316 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2317 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2318 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2319 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2320 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2321 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2322 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2323 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2326 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2327 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2328 ANG_MIRROR_135 : ANG_MIRROR_45);
2330 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2333 AddDamagedField(ELX, ELY);
2336 AddLaserEdge(LX - XS, LY - YS);
2337 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2338 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2340 laser.current_angle = get_mirrored_angle(laser.current_angle,
2348 AddLaserEdge(LX, LY);
2354 // check if laser hits DF style wall with an angle of 90°
2355 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2357 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2358 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2359 (IS_VERT_ANGLE(laser.current_angle) &&
2360 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2362 // laser at last step touched nothing or the same side of the wall
2363 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2365 AddDamagedField(ELX, ELY);
2372 last_hit_mask = hit_mask;
2379 if (!HitOnlyAnEdge(hit_mask))
2381 laser.overloaded = TRUE;
2389 static boolean HitAbsorbingWalls(int element, int hit_mask)
2391 if (HitOnlyAnEdge(hit_mask))
2395 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2397 AddLaserEdge(LX - XS, LY - YS);
2404 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2406 AddLaserEdge(LX - XS, LY - YS);
2412 if (IS_WALL_WOOD(element) ||
2413 IS_DF_WALL_WOOD(element) ||
2414 IS_GRID_WOOD(element) ||
2415 IS_GRID_WOOD_FIXED(element) ||
2416 IS_GRID_WOOD_AUTO(element) ||
2417 element == EL_FUSE_ON ||
2418 element == EL_BLOCK_WOOD ||
2419 element == EL_GATE_WOOD)
2421 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2426 if (IS_WALL_ICE(element))
2432 // check if laser hit adjacent edges of two diagonal tiles
2433 if (ELX != lx / TILEX)
2435 if (ELY != ly / TILEY)
2438 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2439 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2441 // check if laser hits wall with an angle of 90°
2442 if (IS_90_ANGLE(laser.current_angle))
2443 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2445 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2449 for (i = 0; i < 4; i++)
2451 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2452 mask = 15 - (8 >> i);
2453 else if (ABS(XS) == 4 &&
2455 (XS > 0) == (i % 2) &&
2456 (YS < 0) == (i / 2))
2457 mask = 3 + (i / 2) * 9;
2458 else if (ABS(YS) == 4 &&
2460 (XS < 0) == (i % 2) &&
2461 (YS > 0) == (i / 2))
2462 mask = 5 + (i % 2) * 5;
2466 laser.wall_mask = mask;
2468 else if (IS_WALL_AMOEBA(element))
2470 int elx = (LX - 2 * XS) / TILEX;
2471 int ely = (LY - 2 * YS) / TILEY;
2472 int element2 = Tile[elx][ely];
2475 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2477 laser.dest_element = EL_EMPTY;
2485 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2486 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2488 if (IS_90_ANGLE(laser.current_angle))
2489 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2491 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2493 laser.wall_mask = mask;
2499 static void OpenExit(int x, int y)
2503 if (!MovDelay[x][y]) // next animation frame
2504 MovDelay[x][y] = 4 * delay;
2506 if (MovDelay[x][y]) // wait some time before next frame
2511 phase = MovDelay[x][y] / delay;
2513 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2514 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2516 if (!MovDelay[x][y])
2518 Tile[x][y] = EL_EXIT_OPEN;
2524 static void OpenGrayBall(int x, int y)
2528 if (!MovDelay[x][y]) // next animation frame
2530 if (IS_WALL(Store[x][y]))
2532 DrawWalls_MM(x, y, Store[x][y]);
2534 // copy wall tile to spare bitmap for "melting" animation
2535 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2536 TILEX, TILEY, x * TILEX, y * TILEY);
2538 DrawElement_MM(x, y, EL_GRAY_BALL);
2541 MovDelay[x][y] = 50 * delay;
2544 if (MovDelay[x][y]) // wait some time before next frame
2548 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2552 int dx = RND(26), dy = RND(26);
2554 if (IS_WALL(Store[x][y]))
2556 // copy wall tile from spare bitmap for "melting" animation
2557 bitmap = bitmap_db_field;
2563 int graphic = el2gfx(Store[x][y]);
2565 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2568 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2569 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2571 laser.redraw = TRUE;
2573 MarkTileDirty(x, y);
2576 if (!MovDelay[x][y])
2578 Tile[x][y] = Store[x][y];
2579 Store[x][y] = Store2[x][y] = 0;
2580 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2582 InitField(x, y, FALSE);
2585 ScanLaser_FromLastMirror();
2590 static void OpenEnvelope(int x, int y)
2592 int num_frames = 8; // seven frames plus final empty space
2594 if (!MovDelay[x][y]) // next animation frame
2595 MovDelay[x][y] = num_frames;
2597 if (MovDelay[x][y]) // wait some time before next frame
2599 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2603 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2605 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2606 int frame = num_frames - MovDelay[x][y] - 1;
2608 DrawGraphicAnimation_MM(x, y, graphic, frame);
2610 laser.redraw = TRUE;
2613 if (MovDelay[x][y] == 0)
2615 Tile[x][y] = EL_EMPTY;
2621 ShowEnvelope_MM(nr);
2626 static void MeltIce(int x, int y)
2631 if (!MovDelay[x][y]) // next animation frame
2632 MovDelay[x][y] = frames * delay;
2634 if (MovDelay[x][y]) // wait some time before next frame
2637 int wall_mask = Store2[x][y];
2638 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2641 phase = frames - MovDelay[x][y] / delay - 1;
2643 if (!MovDelay[x][y])
2645 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2646 Store[x][y] = Store2[x][y] = 0;
2648 DrawWalls_MM(x, y, Tile[x][y]);
2650 if (Tile[x][y] == EL_WALL_ICE_BASE)
2651 Tile[x][y] = EL_EMPTY;
2653 ScanLaser_FromLastMirror();
2655 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2657 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2659 laser.redraw = TRUE;
2664 static void GrowAmoeba(int x, int y)
2669 if (!MovDelay[x][y]) // next animation frame
2670 MovDelay[x][y] = frames * delay;
2672 if (MovDelay[x][y]) // wait some time before next frame
2675 int wall_mask = Store2[x][y];
2676 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2679 phase = MovDelay[x][y] / delay;
2681 if (!MovDelay[x][y])
2683 Tile[x][y] = real_element;
2684 Store[x][y] = Store2[x][y] = 0;
2686 DrawWalls_MM(x, y, Tile[x][y]);
2687 DrawLaser(0, DL_LASER_ENABLED);
2689 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2691 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2696 static void DrawFieldAnimated_MM(int x, int y)
2700 laser.redraw = TRUE;
2703 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2705 int element = Tile[x][y];
2706 int graphic = el2gfx(element);
2708 if (!getGraphicInfo_NewFrame(x, y, graphic))
2713 laser.redraw = TRUE;
2716 static void DrawFieldTwinkle(int x, int y)
2718 if (MovDelay[x][y] != 0) // wait some time before next frame
2724 if (MovDelay[x][y] != 0)
2726 int graphic = IMG_TWINKLE_WHITE;
2727 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2729 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2732 laser.redraw = TRUE;
2736 static void Explode_MM(int x, int y, int phase, int mode)
2738 int num_phase = 9, delay = 2;
2739 int last_phase = num_phase * delay;
2740 int half_phase = (num_phase / 2) * delay;
2743 laser.redraw = TRUE;
2745 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2747 center_element = Tile[x][y];
2749 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2751 // put moving element to center field (and let it explode there)
2752 center_element = MovingOrBlocked2Element_MM(x, y);
2753 RemoveMovingField_MM(x, y);
2755 Tile[x][y] = center_element;
2758 if (center_element != EL_GRAY_BALL_ACTIVE)
2759 Store[x][y] = EL_EMPTY;
2760 Store2[x][y] = center_element;
2762 Tile[x][y] = EL_EXPLODING_OPAQUE;
2764 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2765 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2766 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2769 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2771 ExplodePhase[x][y] = 1;
2777 GfxFrame[x][y] = 0; // restart explosion animation
2779 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2781 center_element = Store2[x][y];
2783 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2785 Tile[x][y] = EL_EXPLODING_TRANSP;
2787 if (x == ELX && y == ELY)
2791 if (phase == last_phase)
2793 if (center_element == EL_BOMB_ACTIVE)
2795 DrawLaser(0, DL_LASER_DISABLED);
2798 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2800 GameOver_MM(GAME_OVER_DELAYED);
2802 laser.overloaded = FALSE;
2804 else if (IS_MCDUFFIN(center_element))
2806 GameOver_MM(GAME_OVER_BOMB);
2809 Tile[x][y] = Store[x][y];
2811 Store[x][y] = Store2[x][y] = 0;
2812 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2814 InitField(x, y, FALSE);
2817 if (center_element == EL_GRAY_BALL_ACTIVE)
2818 ScanLaser_FromLastMirror();
2820 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2822 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2823 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2825 DrawGraphicAnimation_MM(x, y, graphic, frame);
2827 MarkTileDirty(x, y);
2831 static void Bang_MM(int x, int y)
2833 int element = Tile[x][y];
2835 if (IS_PACMAN(element))
2836 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2837 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2838 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2839 else if (element == EL_KEY)
2840 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2842 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2844 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2847 static void TurnRound(int x, int y)
2859 { 0, 0 }, { 0, 0 }, { 0, 0 },
2864 int left, right, back;
2868 { MV_DOWN, MV_UP, MV_RIGHT },
2869 { MV_UP, MV_DOWN, MV_LEFT },
2871 { MV_LEFT, MV_RIGHT, MV_DOWN },
2872 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2873 { MV_RIGHT, MV_LEFT, MV_UP }
2876 int element = Tile[x][y];
2877 int old_move_dir = MovDir[x][y];
2878 int right_dir = turn[old_move_dir].right;
2879 int back_dir = turn[old_move_dir].back;
2880 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2881 int right_x = x + right_dx, right_y = y + right_dy;
2883 if (element == EL_PACMAN)
2885 boolean can_turn_right = FALSE;
2887 if (IN_LEV_FIELD(right_x, right_y) &&
2888 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2889 can_turn_right = TRUE;
2892 MovDir[x][y] = right_dir;
2894 MovDir[x][y] = back_dir;
2900 static void StartMoving_MM(int x, int y)
2902 int element = Tile[x][y];
2907 if (CAN_MOVE(element))
2911 if (MovDelay[x][y]) // wait some time before next movement
2919 // now make next step
2921 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2923 if (element == EL_PACMAN &&
2924 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2925 !ObjHit(newx, newy, HIT_POS_CENTER))
2927 Store[newx][newy] = Tile[newx][newy];
2928 Tile[newx][newy] = EL_EMPTY;
2930 DrawField_MM(newx, newy);
2932 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2933 ObjHit(newx, newy, HIT_POS_CENTER))
2935 // object was running against a wall
2942 InitMovingField_MM(x, y, MovDir[x][y]);
2946 ContinueMoving_MM(x, y);
2949 static void ContinueMoving_MM(int x, int y)
2951 int element = Tile[x][y];
2952 int direction = MovDir[x][y];
2953 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2954 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2955 int horiz_move = (dx!=0);
2956 int newx = x + dx, newy = y + dy;
2957 int step = (horiz_move ? dx : dy) * TILEX / 8;
2959 MovPos[x][y] += step;
2961 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2963 Tile[x][y] = EL_EMPTY;
2964 Tile[newx][newy] = element;
2966 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2967 MovDelay[newx][newy] = 0;
2969 if (!CAN_MOVE(element))
2970 MovDir[newx][newy] = 0;
2973 DrawField_MM(newx, newy);
2975 Stop[newx][newy] = TRUE;
2977 if (element == EL_PACMAN)
2979 if (Store[newx][newy] == EL_BOMB)
2980 Bang_MM(newx, newy);
2982 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2983 (LX + 2 * XS) / TILEX == newx &&
2984 (LY + 2 * YS) / TILEY == newy)
2991 else // still moving on
2996 laser.redraw = TRUE;
2999 boolean ClickElement(int x, int y, int button)
3001 static DelayCounter click_delay = { CLICK_DELAY };
3002 static boolean new_button = TRUE;
3003 boolean element_clicked = FALSE;
3008 // initialize static variables
3009 click_delay.count = 0;
3010 click_delay.value = CLICK_DELAY;
3016 // do not rotate objects hit by the laser after the game was solved
3017 if (game_mm.level_solved && Hit[x][y])
3020 if (button == MB_RELEASED)
3023 click_delay.value = CLICK_DELAY;
3025 // release eventually hold auto-rotating mirror
3026 RotateMirror(x, y, MB_RELEASED);
3031 if (!FrameReached(&click_delay) && !new_button)
3034 if (button == MB_MIDDLEBUTTON) // middle button has no function
3037 if (!IN_LEV_FIELD(x, y))
3040 if (Tile[x][y] == EL_EMPTY)
3043 element = Tile[x][y];
3045 if (IS_MIRROR(element) ||
3046 IS_BEAMER(element) ||
3047 IS_POLAR(element) ||
3048 IS_POLAR_CROSS(element) ||
3049 IS_DF_MIRROR(element) ||
3050 IS_DF_MIRROR_AUTO(element))
3052 RotateMirror(x, y, button);
3054 element_clicked = TRUE;
3056 else if (IS_MCDUFFIN(element))
3058 if (!laser.fuse_off)
3060 DrawLaser(0, DL_LASER_DISABLED);
3067 element = get_rotated_element(element, BUTTON_ROTATION(button));
3068 laser.start_angle = get_element_angle(element);
3072 Tile[x][y] = element;
3079 if (!laser.fuse_off)
3082 element_clicked = TRUE;
3084 else if (element == EL_FUSE_ON && laser.fuse_off)
3086 if (x != laser.fuse_x || y != laser.fuse_y)
3089 laser.fuse_off = FALSE;
3090 laser.fuse_x = laser.fuse_y = -1;
3092 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3095 element_clicked = TRUE;
3097 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3099 laser.fuse_off = TRUE;
3102 laser.overloaded = FALSE;
3104 DrawLaser(0, DL_LASER_DISABLED);
3105 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3107 element_clicked = TRUE;
3109 else if (element == EL_LIGHTBALL)
3112 RaiseScoreElement_MM(element);
3113 DrawLaser(0, DL_LASER_ENABLED);
3115 element_clicked = TRUE;
3117 else if (IS_ENVELOPE(element))
3119 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3121 element_clicked = TRUE;
3124 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3127 return element_clicked;
3130 static void RotateMirror(int x, int y, int button)
3132 if (button == MB_RELEASED)
3134 // release eventually hold auto-rotating mirror
3141 if (IS_MIRROR(Tile[x][y]) ||
3142 IS_POLAR_CROSS(Tile[x][y]) ||
3143 IS_POLAR(Tile[x][y]) ||
3144 IS_BEAMER(Tile[x][y]) ||
3145 IS_DF_MIRROR(Tile[x][y]) ||
3146 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3147 IS_GRID_WOOD_AUTO(Tile[x][y]))
3149 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3151 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3153 if (button == MB_LEFTBUTTON)
3155 // left mouse button only for manual adjustment, no auto-rotating;
3156 // freeze mirror for until mouse button released
3160 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3162 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3166 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3168 int edge = Hit[x][y];
3174 DrawLaser(edge - 1, DL_LASER_DISABLED);
3178 else if (ObjHit(x, y, HIT_POS_CENTER))
3180 int edge = Hit[x][y];
3184 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3189 DrawLaser(edge - 1, DL_LASER_DISABLED);
3196 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3201 if ((IS_BEAMER(Tile[x][y]) ||
3202 IS_POLAR(Tile[x][y]) ||
3203 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3205 if (IS_BEAMER(Tile[x][y]))
3208 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3209 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3222 DrawLaser(0, DL_LASER_ENABLED);
3226 static void AutoRotateMirrors(void)
3230 if (!FrameReached(&rotate_delay))
3233 for (x = 0; x < lev_fieldx; x++)
3235 for (y = 0; y < lev_fieldy; y++)
3237 int element = Tile[x][y];
3239 // do not rotate objects hit by the laser after the game was solved
3240 if (game_mm.level_solved && Hit[x][y])
3243 if (IS_DF_MIRROR_AUTO(element) ||
3244 IS_GRID_WOOD_AUTO(element) ||
3245 IS_GRID_STEEL_AUTO(element) ||
3246 element == EL_REFRACTOR)
3247 RotateMirror(x, y, MB_RIGHTBUTTON);
3252 static boolean ObjHit(int obx, int oby, int bits)
3259 if (bits & HIT_POS_CENTER)
3261 if (CheckLaserPixel(cSX + obx + 15,
3266 if (bits & HIT_POS_EDGE)
3268 for (i = 0; i < 4; i++)
3269 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3270 cSY + oby + 31 * (i / 2)))
3274 if (bits & HIT_POS_BETWEEN)
3276 for (i = 0; i < 4; i++)
3277 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3278 cSY + 4 + oby + 22 * (i / 2)))
3285 static void DeletePacMan(int px, int py)
3291 if (game_mm.num_pacman <= 1)
3293 game_mm.num_pacman = 0;
3297 for (i = 0; i < game_mm.num_pacman; i++)
3298 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3301 game_mm.num_pacman--;
3303 for (j = i; j < game_mm.num_pacman; j++)
3305 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3306 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3307 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3311 static void GameActions_MM_Ext(void)
3318 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3321 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3323 element = Tile[x][y];
3325 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3326 StartMoving_MM(x, y);
3327 else if (IS_MOVING(x, y))
3328 ContinueMoving_MM(x, y);
3329 else if (IS_EXPLODING(element))
3330 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3331 else if (element == EL_EXIT_OPENING)
3333 else if (element == EL_GRAY_BALL_OPENING)
3335 else if (IS_ENVELOPE_OPENING(element))
3337 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3339 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3341 else if (IS_MIRROR(element) ||
3342 IS_MIRROR_FIXED(element) ||
3343 element == EL_PRISM)
3344 DrawFieldTwinkle(x, y);
3345 else if (element == EL_GRAY_BALL_ACTIVE ||
3346 element == EL_BOMB_ACTIVE ||
3347 element == EL_MINE_ACTIVE)
3348 DrawFieldAnimated_MM(x, y);
3349 else if (!IS_BLOCKED(x, y))
3350 DrawFieldAnimatedIfNeeded_MM(x, y);
3353 AutoRotateMirrors();
3356 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3358 // redraw after Explode_MM() ...
3360 DrawLaser(0, DL_LASER_ENABLED);
3361 laser.redraw = FALSE;
3366 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3370 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3372 DrawLaser(0, DL_LASER_DISABLED);
3377 // skip all following game actions if game is over
3378 if (game_mm.game_over)
3381 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3385 GameOver_MM(GAME_OVER_NO_ENERGY);
3390 if (FrameReached(&energy_delay))
3392 if (game_mm.energy_left > 0)
3393 game_mm.energy_left--;
3395 // when out of energy, wait another frame to play "out of time" sound
3398 element = laser.dest_element;
3401 if (element != Tile[ELX][ELY])
3403 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3404 element, Tile[ELX][ELY]);
3408 if (!laser.overloaded && laser.overload_value == 0 &&
3409 element != EL_BOMB &&
3410 element != EL_BOMB_ACTIVE &&
3411 element != EL_MINE &&
3412 element != EL_MINE_ACTIVE &&
3413 element != EL_GRAY_BALL &&
3414 element != EL_GRAY_BALL_ACTIVE &&
3415 element != EL_BLOCK_STONE &&
3416 element != EL_BLOCK_WOOD &&
3417 element != EL_FUSE_ON &&
3418 element != EL_FUEL_FULL &&
3419 !IS_WALL_ICE(element) &&
3420 !IS_WALL_AMOEBA(element))
3423 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3425 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3426 (!laser.overloaded && laser.overload_value > 0)) &&
3427 FrameReached(&overload_delay))
3429 if (laser.overloaded)
3430 laser.overload_value++;
3432 laser.overload_value--;
3434 if (game_mm.cheat_no_overload)
3436 laser.overloaded = FALSE;
3437 laser.overload_value = 0;
3440 game_mm.laser_overload_value = laser.overload_value;
3442 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3444 SetLaserColor(0xFF);
3446 DrawLaser(0, DL_LASER_ENABLED);
3449 if (!laser.overloaded)
3450 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3451 else if (setup.sound_loops)
3452 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3454 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3456 if (laser.overload_value == MAX_LASER_OVERLOAD)
3458 UpdateAndDisplayGameControlValues();
3462 GameOver_MM(GAME_OVER_OVERLOADED);
3473 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3475 if (game_mm.cheat_no_explosion)
3480 laser.dest_element = EL_EXPLODING_OPAQUE;
3485 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3487 laser.fuse_off = TRUE;
3491 DrawLaser(0, DL_LASER_DISABLED);
3492 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3495 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3497 if (!Store2[ELX][ELY]) // check if content element not yet determined
3499 int last_anim_random_frame = gfx.anim_random_frame;
3502 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3503 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3505 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3506 native_mm_level.ball_choice_mode, 0,
3507 game_mm.ball_choice_pos);
3509 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3510 gfx.anim_random_frame = last_anim_random_frame;
3512 game_mm.ball_choice_pos++;
3514 int new_element = native_mm_level.ball_content[element_pos];
3515 int new_element_base = map_wall_to_base_element(new_element);
3517 if (IS_WALL(new_element_base))
3519 // always use completely filled wall element
3520 new_element = new_element_base | 0x000f;
3522 else if (native_mm_level.rotate_ball_content &&
3523 get_num_elements(new_element) > 1)
3525 // randomly rotate newly created game element
3526 new_element = get_rotated_element(new_element, RND(16));
3529 Store[ELX][ELY] = new_element;
3530 Store2[ELX][ELY] = TRUE;
3533 if (native_mm_level.explode_ball)
3536 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3538 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3543 if (IS_WALL_ICE(element) && CT > 50)
3545 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3547 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3548 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3549 Store2[ELX][ELY] = laser.wall_mask;
3551 laser.dest_element = Tile[ELX][ELY];
3556 if (IS_WALL_AMOEBA(element) && CT > 60)
3559 int element2 = Tile[ELX][ELY];
3561 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3564 for (i = laser.num_damages - 1; i >= 0; i--)
3565 if (laser.damage[i].is_mirror)
3568 r = laser.num_edges;
3569 d = laser.num_damages;
3576 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3579 DrawLaser(0, DL_LASER_ENABLED);
3582 x = laser.damage[k1].x;
3583 y = laser.damage[k1].y;
3588 for (i = 0; i < 4; i++)
3590 if (laser.wall_mask & (1 << i))
3592 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3593 cSY + ELY * TILEY + 31 * (i / 2)))
3596 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3597 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3604 for (i = 0; i < 4; i++)
3606 if (laser.wall_mask & (1 << i))
3608 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3609 cSY + ELY * TILEY + 31 * (i / 2)))
3616 if (laser.num_beamers > 0 ||
3617 k1 < 1 || k2 < 4 || k3 < 4 ||
3618 CheckLaserPixel(cSX + ELX * TILEX + 14,
3619 cSY + ELY * TILEY + 14))
3621 laser.num_edges = r;
3622 laser.num_damages = d;
3624 DrawLaser(0, DL_LASER_DISABLED);
3627 Tile[ELX][ELY] = element | laser.wall_mask;
3629 int x = ELX, y = ELY;
3630 int wall_mask = laser.wall_mask;
3633 DrawLaser(0, DL_LASER_ENABLED);
3635 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3637 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3638 Store[x][y] = EL_WALL_AMOEBA_BASE;
3639 Store2[x][y] = wall_mask;
3644 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3645 laser.stops_inside_element && CT > native_mm_level.time_block)
3650 if (ABS(XS) > ABS(YS))
3657 for (i = 0; i < 4; i++)
3664 x = ELX + Step[k * 4].x;
3665 y = ELY + Step[k * 4].y;
3667 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3670 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3678 laser.overloaded = (element == EL_BLOCK_STONE);
3683 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3686 Tile[x][y] = element;
3688 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3691 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3693 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3694 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3702 if (element == EL_FUEL_FULL && CT > 10)
3704 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3705 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3707 for (i = start; i <= num_init_game_frames; i++)
3709 if (i == num_init_game_frames)
3710 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3711 else if (setup.sound_loops)
3712 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3714 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3716 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3718 UpdateAndDisplayGameControlValues();
3723 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3725 DrawField_MM(ELX, ELY);
3727 DrawLaser(0, DL_LASER_ENABLED);
3733 void GameActions_MM(struct MouseActionInfo action)
3735 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3736 boolean button_released = (action.button == MB_RELEASED);
3738 GameActions_MM_Ext();
3740 CheckSingleStepMode_MM(element_clicked, button_released);
3743 static void MovePacMen(void)
3745 int mx, my, ox, oy, nx, ny;
3749 if (++pacman_nr >= game_mm.num_pacman)
3752 game_mm.pacman[pacman_nr].dir--;
3754 for (l = 1; l < 5; l++)
3756 game_mm.pacman[pacman_nr].dir++;
3758 if (game_mm.pacman[pacman_nr].dir > 4)
3759 game_mm.pacman[pacman_nr].dir = 1;
3761 if (game_mm.pacman[pacman_nr].dir % 2)
3764 my = game_mm.pacman[pacman_nr].dir - 2;
3769 mx = 3 - game_mm.pacman[pacman_nr].dir;
3772 ox = game_mm.pacman[pacman_nr].x;
3773 oy = game_mm.pacman[pacman_nr].y;
3776 element = Tile[nx][ny];
3778 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3781 if (!IS_EATABLE4PACMAN(element))
3784 if (ObjHit(nx, ny, HIT_POS_CENTER))
3787 Tile[ox][oy] = EL_EMPTY;
3789 EL_PACMAN_RIGHT - 1 +
3790 (game_mm.pacman[pacman_nr].dir - 1 +
3791 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3793 game_mm.pacman[pacman_nr].x = nx;
3794 game_mm.pacman[pacman_nr].y = ny;
3796 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3798 if (element != EL_EMPTY)
3800 int graphic = el2gfx(Tile[nx][ny]);
3805 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3808 ox = cSX + ox * TILEX;
3809 oy = cSY + oy * TILEY;
3811 for (i = 1; i < 33; i += 2)
3812 BlitBitmap(bitmap, window,
3813 src_x, src_y, TILEX, TILEY,
3814 ox + i * mx, oy + i * my);
3815 Ct = Ct + FrameCounter - CT;
3818 DrawField_MM(nx, ny);
3821 if (!laser.fuse_off)
3823 DrawLaser(0, DL_LASER_ENABLED);
3825 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3827 AddDamagedField(nx, ny);
3829 laser.damage[laser.num_damages - 1].edge = 0;
3833 if (element == EL_BOMB)
3834 DeletePacMan(nx, ny);
3836 if (IS_WALL_AMOEBA(element) &&
3837 (LX + 2 * XS) / TILEX == nx &&
3838 (LY + 2 * YS) / TILEY == ny)
3848 static void InitMovingField_MM(int x, int y, int direction)
3850 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3851 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3853 MovDir[x][y] = direction;
3854 MovDir[newx][newy] = direction;
3856 if (Tile[newx][newy] == EL_EMPTY)
3857 Tile[newx][newy] = EL_BLOCKED;
3860 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3862 int direction = MovDir[x][y];
3863 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3864 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3870 static void Blocked2Moving_MM(int x, int y,
3871 int *comes_from_x, int *comes_from_y)
3873 int oldx = x, oldy = y;
3874 int direction = MovDir[x][y];
3876 if (direction == MV_LEFT)
3878 else if (direction == MV_RIGHT)
3880 else if (direction == MV_UP)
3882 else if (direction == MV_DOWN)
3885 *comes_from_x = oldx;
3886 *comes_from_y = oldy;
3889 static int MovingOrBlocked2Element_MM(int x, int y)
3891 int element = Tile[x][y];
3893 if (element == EL_BLOCKED)
3897 Blocked2Moving_MM(x, y, &oldx, &oldy);
3899 return Tile[oldx][oldy];
3905 static void RemoveMovingField_MM(int x, int y)
3907 int oldx = x, oldy = y, newx = x, newy = y;
3909 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3912 if (IS_MOVING(x, y))
3914 Moving2Blocked_MM(x, y, &newx, &newy);
3915 if (Tile[newx][newy] != EL_BLOCKED)
3918 else if (Tile[x][y] == EL_BLOCKED)
3920 Blocked2Moving_MM(x, y, &oldx, &oldy);
3921 if (!IS_MOVING(oldx, oldy))
3925 Tile[oldx][oldy] = EL_EMPTY;
3926 Tile[newx][newy] = EL_EMPTY;
3927 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3928 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3930 DrawLevelField_MM(oldx, oldy);
3931 DrawLevelField_MM(newx, newy);
3934 static void RaiseScore_MM(int value)
3936 game_mm.score += value;
3939 void RaiseScoreElement_MM(int element)
3944 case EL_PACMAN_RIGHT:
3946 case EL_PACMAN_LEFT:
3947 case EL_PACMAN_DOWN:
3948 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3952 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3957 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3961 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3970 // ----------------------------------------------------------------------------
3971 // Mirror Magic game engine snapshot handling functions
3972 // ----------------------------------------------------------------------------
3974 void SaveEngineSnapshotValues_MM(void)
3978 engine_snapshot_mm.game_mm = game_mm;
3979 engine_snapshot_mm.laser = laser;
3981 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3983 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3985 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3986 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3987 engine_snapshot_mm.Box[x][y] = Box[x][y];
3988 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3992 engine_snapshot_mm.LX = LX;
3993 engine_snapshot_mm.LY = LY;
3994 engine_snapshot_mm.XS = XS;
3995 engine_snapshot_mm.YS = YS;
3996 engine_snapshot_mm.ELX = ELX;
3997 engine_snapshot_mm.ELY = ELY;
3998 engine_snapshot_mm.CT = CT;
3999 engine_snapshot_mm.Ct = Ct;
4001 engine_snapshot_mm.last_LX = last_LX;
4002 engine_snapshot_mm.last_LY = last_LY;
4003 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4004 engine_snapshot_mm.hold_x = hold_x;
4005 engine_snapshot_mm.hold_y = hold_y;
4006 engine_snapshot_mm.pacman_nr = pacman_nr;
4008 engine_snapshot_mm.rotate_delay = rotate_delay;
4009 engine_snapshot_mm.pacman_delay = pacman_delay;
4010 engine_snapshot_mm.energy_delay = energy_delay;
4011 engine_snapshot_mm.overload_delay = overload_delay;
4014 void LoadEngineSnapshotValues_MM(void)
4018 // stored engine snapshot buffers already restored at this point
4020 game_mm = engine_snapshot_mm.game_mm;
4021 laser = engine_snapshot_mm.laser;
4023 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4025 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4027 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4028 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4029 Box[x][y] = engine_snapshot_mm.Box[x][y];
4030 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4034 LX = engine_snapshot_mm.LX;
4035 LY = engine_snapshot_mm.LY;
4036 XS = engine_snapshot_mm.XS;
4037 YS = engine_snapshot_mm.YS;
4038 ELX = engine_snapshot_mm.ELX;
4039 ELY = engine_snapshot_mm.ELY;
4040 CT = engine_snapshot_mm.CT;
4041 Ct = engine_snapshot_mm.Ct;
4043 last_LX = engine_snapshot_mm.last_LX;
4044 last_LY = engine_snapshot_mm.last_LY;
4045 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4046 hold_x = engine_snapshot_mm.hold_x;
4047 hold_y = engine_snapshot_mm.hold_y;
4048 pacman_nr = engine_snapshot_mm.pacman_nr;
4050 rotate_delay = engine_snapshot_mm.rotate_delay;
4051 pacman_delay = engine_snapshot_mm.pacman_delay;
4052 energy_delay = engine_snapshot_mm.energy_delay;
4053 overload_delay = engine_snapshot_mm.overload_delay;
4055 RedrawPlayfield_MM();
4058 static int getAngleFromTouchDelta(int dx, int dy, int base)
4060 double pi = 3.141592653;
4061 double rad = atan2((double)-dy, (double)dx);
4062 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4063 double deg = rad2 * 180.0 / pi;
4065 return (int)(deg * base / 360.0 + 0.5) % base;
4068 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4070 // calculate start (source) position to be at the middle of the tile
4071 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4072 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4073 int dx = dst_mx - src_mx;
4074 int dy = dst_my - src_my;
4083 if (!IN_LEV_FIELD(x, y))
4086 element = Tile[x][y];
4088 if (!IS_MCDUFFIN(element) &&
4089 !IS_MIRROR(element) &&
4090 !IS_BEAMER(element) &&
4091 !IS_POLAR(element) &&
4092 !IS_POLAR_CROSS(element) &&
4093 !IS_DF_MIRROR(element))
4096 angle_old = get_element_angle(element);
4098 if (IS_MCDUFFIN(element))
4100 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4101 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4102 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4103 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4106 else if (IS_MIRROR(element) ||
4107 IS_DF_MIRROR(element))
4109 for (i = 0; i < laser.num_damages; i++)
4111 if (laser.damage[i].x == x &&
4112 laser.damage[i].y == y &&
4113 ObjHit(x, y, HIT_POS_CENTER))
4115 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4116 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4123 if (angle_new == -1)
4125 if (IS_MIRROR(element) ||
4126 IS_DF_MIRROR(element) ||
4130 if (IS_POLAR_CROSS(element))
4133 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4136 button = (angle_new == angle_old ? 0 :
4137 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4138 MB_LEFTBUTTON : MB_RIGHTBUTTON);