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_GRID_CLOSED 8
143 #define MM_MASK_RECTANGLE 9
144 #define MM_MASK_CIRCLE 10
146 #define NUM_MM_MASKS 11
148 // element masks for scanning pixels of MM elements
149 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
351 static int get_element_angle(int element)
353 int element_phase = get_element_phase(element);
355 if (IS_MIRROR_FIXED(element) ||
356 IS_MCDUFFIN(element) ||
358 IS_RECEIVER(element))
359 return 4 * element_phase;
361 return element_phase;
364 static int get_opposite_angle(int angle)
366 int opposite_angle = angle + ANG_RAY_180;
368 // make sure "opposite_angle" is in valid interval [0, 15]
369 return (opposite_angle + 16) % 16;
372 static int get_mirrored_angle(int laser_angle, int mirror_angle)
374 int reflected_angle = 16 - laser_angle + mirror_angle;
376 // make sure "reflected_angle" is in valid interval [0, 15]
377 return (reflected_angle + 16) % 16;
380 static void DrawLaserLines(struct XY *points, int num_points, int mode)
382 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
383 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
385 DrawLines(drawto_mm, points, num_points, pixel_drawto);
389 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
394 static boolean CheckLaserPixel(int x, int y)
400 pixel = ReadPixel(laser_bitmap, x, y);
404 return (pixel == WHITE_PIXEL);
407 static void CheckExitMM(void)
409 int exit_element = EL_EMPTY;
413 static int xy[4][2] =
421 for (y = 0; y < lev_fieldy; y++)
423 for (x = 0; x < lev_fieldx; x++)
425 if (Tile[x][y] == EL_EXIT_CLOSED)
427 // initiate opening animation of exit door
428 Tile[x][y] = EL_EXIT_OPENING;
430 exit_element = EL_EXIT_OPEN;
434 else if (IS_RECEIVER(Tile[x][y]))
436 // remove field that blocks receiver
437 int phase = Tile[x][y] - EL_RECEIVER_START;
438 int blocking_x, blocking_y;
440 blocking_x = x + xy[phase][0];
441 blocking_y = y + xy[phase][1];
443 if (IN_LEV_FIELD(blocking_x, blocking_y))
445 Tile[blocking_x][blocking_y] = EL_EMPTY;
447 DrawField_MM(blocking_x, blocking_y);
450 exit_element = EL_RECEIVER;
457 if (exit_element != EL_EMPTY)
458 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
461 static void SetLaserColor(int brightness)
463 int color_min = 0x00;
464 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
465 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
466 int color_down = color_max - color_up;
469 GetPixelFromRGB(window,
470 (game_mm.laser_red ? color_max : color_up),
471 (game_mm.laser_green ? color_down : color_min),
472 (game_mm.laser_blue ? color_down : color_min));
475 static void InitMovDir_MM(int x, int y)
477 int element = Tile[x][y];
478 static int direction[3][4] =
480 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
481 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
482 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
487 case EL_PACMAN_RIGHT:
491 Tile[x][y] = EL_PACMAN;
492 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
500 static void InitField(int x, int y, boolean init_game)
502 int element = Tile[x][y];
507 Tile[x][y] = EL_EMPTY;
512 if (init_game && native_mm_level.auto_count_kettles)
513 game_mm.kettles_still_needed++;
516 case EL_LIGHTBULB_OFF:
517 game_mm.lights_still_needed++;
521 if (IS_MIRROR(element) ||
522 IS_BEAMER_OLD(element) ||
523 IS_BEAMER(element) ||
525 IS_POLAR_CROSS(element) ||
526 IS_DF_MIRROR(element) ||
527 IS_DF_MIRROR_AUTO(element) ||
528 IS_GRID_STEEL_AUTO(element) ||
529 IS_GRID_WOOD_AUTO(element) ||
530 IS_FIBRE_OPTIC(element))
532 if (IS_BEAMER_OLD(element))
534 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
535 element = Tile[x][y];
538 if (!IS_FIBRE_OPTIC(element))
540 static int steps_grid_auto = 0;
542 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
543 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
545 if (IS_GRID_STEEL_AUTO(element) ||
546 IS_GRID_WOOD_AUTO(element))
547 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
549 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
551 game_mm.cycle[game_mm.num_cycle].x = x;
552 game_mm.cycle[game_mm.num_cycle].y = y;
556 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
558 int beamer_nr = BEAMER_NR(element);
559 int nr = laser.beamer[beamer_nr][0].num;
561 laser.beamer[beamer_nr][nr].x = x;
562 laser.beamer[beamer_nr][nr].y = y;
563 laser.beamer[beamer_nr][nr].num = 1;
566 else if (IS_PACMAN(element))
570 else if (IS_MCDUFFIN(element) || IS_LASER(element))
574 laser.start_edge.x = x;
575 laser.start_edge.y = y;
576 laser.start_angle = get_element_angle(element);
579 if (IS_MCDUFFIN(element))
581 game_mm.laser_red = native_mm_level.mm_laser_red;
582 game_mm.laser_green = native_mm_level.mm_laser_green;
583 game_mm.laser_blue = native_mm_level.mm_laser_blue;
587 game_mm.laser_red = native_mm_level.df_laser_red;
588 game_mm.laser_green = native_mm_level.df_laser_green;
589 game_mm.laser_blue = native_mm_level.df_laser_blue;
592 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
599 static void InitCycleElements_RotateSingleStep(void)
603 if (game_mm.num_cycle == 0) // no elements to cycle
606 for (i = 0; i < game_mm.num_cycle; i++)
608 int x = game_mm.cycle[i].x;
609 int y = game_mm.cycle[i].y;
610 int step = SIGN(game_mm.cycle[i].steps);
611 int last_element = Tile[x][y];
612 int next_element = get_rotated_element(last_element, step);
614 if (!game_mm.cycle[i].steps)
617 Tile[x][y] = next_element;
619 game_mm.cycle[i].steps -= step;
623 static void InitLaser(void)
625 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
626 int step = (IS_LASER(start_element) ? 4 : 0);
628 LX = laser.start_edge.x * TILEX;
629 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
632 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
634 LY = laser.start_edge.y * TILEY;
635 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
636 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
640 XS = 2 * Step[laser.start_angle].x;
641 YS = 2 * Step[laser.start_angle].y;
643 laser.current_angle = laser.start_angle;
645 laser.num_damages = 0;
647 laser.num_beamers = 0;
648 laser.beamer_edge[0] = 0;
650 laser.dest_element = EL_EMPTY;
653 AddLaserEdge(LX, LY); // set laser starting edge
658 void InitGameEngine_MM(void)
664 // initialize laser bitmap to current playfield (screen) size
665 ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
666 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
670 // set global game control values
671 game_mm.num_cycle = 0;
672 game_mm.num_pacman = 0;
675 game_mm.energy_left = 0; // later set to "native_mm_level.time"
676 game_mm.kettles_still_needed =
677 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
678 game_mm.lights_still_needed = 0;
679 game_mm.num_keys = 0;
680 game_mm.ball_choice_pos = 0;
682 game_mm.laser_red = FALSE;
683 game_mm.laser_green = FALSE;
684 game_mm.laser_blue = TRUE;
685 game_mm.has_mcduffin = TRUE;
687 game_mm.level_solved = FALSE;
688 game_mm.game_over = FALSE;
689 game_mm.game_over_cause = 0;
690 game_mm.game_over_message = NULL;
692 game_mm.laser_overload_value = 0;
693 game_mm.laser_enabled = FALSE;
695 // set global laser control values (must be set before "InitLaser()")
696 laser.start_edge.x = 0;
697 laser.start_edge.y = 0;
698 laser.start_angle = 0;
700 for (i = 0; i < MAX_NUM_BEAMERS; i++)
701 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
703 laser.overloaded = FALSE;
704 laser.overload_value = 0;
705 laser.fuse_off = FALSE;
706 laser.fuse_x = laser.fuse_y = -1;
708 laser.dest_element = EL_EMPTY;
709 laser.dest_element_last = EL_EMPTY;
710 laser.dest_element_last_x = -1;
711 laser.dest_element_last_y = -1;
725 rotate_delay.count = 0;
726 pacman_delay.count = 0;
727 energy_delay.count = 0;
728 overload_delay.count = 0;
730 ClickElement(-1, -1, -1);
732 for (x = 0; x < lev_fieldx; x++)
734 for (y = 0; y < lev_fieldy; y++)
736 Tile[x][y] = Ur[x][y];
737 Hit[x][y] = Box[x][y] = 0;
739 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
740 Store[x][y] = Store2[x][y] = 0;
743 InitField(x, y, TRUE);
750 void InitGameActions_MM(void)
752 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
753 int cycle_steps_done = 0;
758 for (i = 0; i <= num_init_game_frames; i++)
760 if (i == num_init_game_frames)
761 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
762 else if (setup.sound_loops)
763 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
765 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
767 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
769 UpdateAndDisplayGameControlValues();
771 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
773 InitCycleElements_RotateSingleStep();
778 AdvanceFrameCounter();
786 if (setup.quick_doors)
793 if (game_mm.kettles_still_needed == 0)
796 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
797 SetTileCursorActive(TRUE);
799 // restart all delay counters after initially cycling game elements
800 ResetFrameCounter(&rotate_delay);
801 ResetFrameCounter(&pacman_delay);
802 ResetFrameCounter(&energy_delay);
803 ResetFrameCounter(&overload_delay);
806 static void FadeOutLaser(void)
810 for (i = 15; i >= 0; i--)
812 SetLaserColor(0x11 * i);
814 DrawLaser(0, DL_LASER_ENABLED);
817 Delay_WithScreenUpdates(50);
820 DrawLaser(0, DL_LASER_DISABLED);
822 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
825 static void GameOver_MM(int game_over_cause)
827 game_mm.game_over = TRUE;
828 game_mm.game_over_cause = game_over_cause;
829 game_mm.game_over_message = (game_mm.has_mcduffin ?
830 (game_over_cause == GAME_OVER_BOMB ?
831 "Bomb killed Mc Duffin!" :
832 game_over_cause == GAME_OVER_NO_ENERGY ?
833 "Out of magic energy!" :
834 game_over_cause == GAME_OVER_OVERLOADED ?
835 "Magic spell hit Mc Duffin!" :
837 (game_over_cause == GAME_OVER_BOMB ?
838 "Bomb destroyed laser cannon!" :
839 game_over_cause == GAME_OVER_NO_ENERGY ?
840 "Out of laser energy!" :
841 game_over_cause == GAME_OVER_OVERLOADED ?
842 "Laser beam hit laser cannon!" :
845 SetTileCursorActive(FALSE);
848 static void AddLaserEdge(int lx, int ly)
850 int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
851 int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
853 if (SX + dSX + lx < REAL_SX || SX + dSX + lx >= REAL_SX + full_sxsize ||
854 SY + dSY + ly < REAL_SY || SY + dSY + ly >= REAL_SY + full_sysize)
856 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
861 laser.edge[laser.num_edges].x = cSX2 + lx;
862 laser.edge[laser.num_edges].y = cSY2 + ly;
868 static void AddDamagedField(int ex, int ey)
870 // prevent adding the same field position again
871 if (laser.num_damages > 0 &&
872 laser.damage[laser.num_damages - 1].x == ex &&
873 laser.damage[laser.num_damages - 1].y == ey &&
874 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
877 laser.damage[laser.num_damages].is_mirror = FALSE;
878 laser.damage[laser.num_damages].angle = laser.current_angle;
879 laser.damage[laser.num_damages].edge = laser.num_edges;
880 laser.damage[laser.num_damages].x = ex;
881 laser.damage[laser.num_damages].y = ey;
885 static boolean StepBehind(void)
891 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
892 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
894 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
900 static int getMaskFromElement(int element)
902 if (IS_MCDUFFIN(element))
903 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
904 else if (IS_GRID(element))
905 return MM_MASK_GRID_1 + get_element_phase(element);
906 else if (IS_DF_GRID(element))
907 return MM_MASK_GRID_CLOSED;
908 else if (IS_RECTANGLE(element))
909 return MM_MASK_RECTANGLE;
911 return MM_MASK_CIRCLE;
914 static int ScanPixel(void)
919 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
920 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
923 // follow laser beam until it hits something (at least the screen border)
924 while (hit_mask == HIT_MASK_NO_HIT)
930 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
931 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
933 Debug("game:mm:ScanPixel", "touched screen border!");
939 // check if laser scan has crossed element boundaries (not just mini tiles)
940 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
941 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
943 if (cross_x && cross_y)
945 int elx1 = (LX - XS) / TILEX;
946 int ely1 = (LY + YS) / TILEY;
947 int elx2 = (LX + XS) / TILEX;
948 int ely2 = (LY - YS) / TILEY;
950 // add element corners left and right from the laser beam to damage list
952 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
953 AddDamagedField(elx1, ely1);
955 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
956 AddDamagedField(elx2, ely2);
959 for (i = 0; i < 4; i++)
961 int px = LX + (i % 2) * 2;
962 int py = LY + (i / 2) * 2;
965 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
966 int ly = (py + TILEY) / TILEY - 1; // negative values!
969 if (IN_LEV_FIELD(lx, ly))
971 int element = Tile[lx][ly];
973 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
977 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
979 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
981 pixel = ((element & (1 << pos)) ? 1 : 0);
985 int pos = getMaskFromElement(element);
987 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
992 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
993 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
996 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
997 hit_mask |= (1 << i);
1000 if (hit_mask == HIT_MASK_NO_HIT)
1002 // hit nothing -- go on with another step
1011 static void DeactivateLaserTargetElement(void)
1013 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1014 laser.dest_element_last == EL_MINE_ACTIVE ||
1015 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1016 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1018 int x = laser.dest_element_last_x;
1019 int y = laser.dest_element_last_y;
1020 int element = laser.dest_element_last;
1022 if (Tile[x][y] == element)
1023 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1024 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1026 if (Tile[x][y] == EL_GRAY_BALL)
1029 laser.dest_element_last = EL_EMPTY;
1030 laser.dest_element_last_x = -1;
1031 laser.dest_element_last_y = -1;
1035 static void ScanLaser(void)
1037 int element = EL_EMPTY;
1038 int last_element = EL_EMPTY;
1039 int end = 0, rf = laser.num_edges;
1041 // do not scan laser again after the game was lost for whatever reason
1042 if (game_mm.game_over)
1045 // do not scan laser if fuse is off
1049 DeactivateLaserTargetElement();
1051 laser.overloaded = FALSE;
1052 laser.stops_inside_element = FALSE;
1054 DrawLaser(0, DL_LASER_ENABLED);
1057 Debug("game:mm:ScanLaser",
1058 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1066 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1069 laser.overloaded = TRUE;
1074 hit_mask = ScanPixel();
1077 Debug("game:mm:ScanLaser",
1078 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1082 // hit something -- check out what it was
1083 ELX = (LX + XS + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
1084 ELY = (LY + YS + TILEY) / TILEY - 1; // negative values!
1087 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1088 hit_mask, LX, LY, ELX, ELY);
1091 if (!IN_LEV_FIELD(ELX, ELY))
1094 laser.dest_element = element;
1099 // check if laser scan has hit two diagonally adjacent element corners
1100 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1101 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1103 // check if laser scan has crossed element boundaries (not just mini tiles)
1104 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1105 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1107 // handle special case of laser hitting two diagonally adjacent elements
1108 // (with or without a third corner element behind these two elements)
1109 if ((diag_1 || diag_2) && cross_x && cross_y)
1111 // compare the two diagonally adjacent elements
1113 int yoffset = 2 * (diag_1 ? -1 : +1);
1114 int elx1 = (LX - xoffset) / TILEX;
1115 int ely1 = (LY + yoffset) / TILEY;
1116 int elx2 = (LX + xoffset) / TILEX;
1117 int ely2 = (LY - yoffset) / TILEY;
1118 int e1 = Tile[elx1][ely1];
1119 int e2 = Tile[elx2][ely2];
1120 boolean use_element_1 = FALSE;
1122 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1124 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1125 use_element_1 = (RND(2) ? TRUE : FALSE);
1126 else if (IS_WALL_ICE(e1))
1127 use_element_1 = TRUE;
1129 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1131 // if both tiles match, we can just select the first one
1132 if (IS_WALL_AMOEBA(e1))
1133 use_element_1 = TRUE;
1135 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1137 // if both tiles match, we can just select the first one
1138 if (IS_ABSORBING_BLOCK(e1))
1139 use_element_1 = TRUE;
1142 ELX = (use_element_1 ? elx1 : elx2);
1143 ELY = (use_element_1 ? ely1 : ely2);
1147 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1148 hit_mask, LX, LY, ELX, ELY);
1151 last_element = element;
1153 element = Tile[ELX][ELY];
1154 laser.dest_element = element;
1157 Debug("game:mm:ScanLaser",
1158 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1161 LX % TILEX, LY % TILEY,
1166 if (!IN_LEV_FIELD(ELX, ELY))
1167 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1171 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1172 if (element == EL_EMPTY &&
1173 IS_GRID_STEEL(last_element) &&
1174 laser.current_angle % 4) // angle is not 90°
1175 element = last_element;
1177 if (element == EL_EMPTY)
1179 if (!HitOnlyAnEdge(hit_mask))
1182 else if (element == EL_FUSE_ON)
1184 if (HitPolarizer(element, hit_mask))
1187 else if (IS_GRID(element) || IS_DF_GRID(element))
1189 if (HitPolarizer(element, hit_mask))
1192 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1193 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1195 if (HitBlock(element, hit_mask))
1202 else if (IS_MCDUFFIN(element))
1204 if (HitLaserSource(element, hit_mask))
1207 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1208 IS_RECEIVER(element))
1210 if (HitLaserDestination(element, hit_mask))
1213 else if (IS_WALL(element))
1215 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1217 if (HitReflectingWalls(element, hit_mask))
1222 if (HitAbsorbingWalls(element, hit_mask))
1228 if (HitElement(element, hit_mask))
1233 DrawLaser(rf - 1, DL_LASER_ENABLED);
1234 rf = laser.num_edges;
1236 if (!IS_DF_WALL_STEEL(element))
1238 // only used for scanning DF steel walls; reset for all other elements
1246 if (laser.dest_element != Tile[ELX][ELY])
1248 Debug("game:mm:ScanLaser",
1249 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1250 laser.dest_element, Tile[ELX][ELY]);
1254 if (!end && !laser.stops_inside_element && !StepBehind())
1257 Debug("game:mm:ScanLaser", "Go one step back");
1263 AddLaserEdge(LX, LY);
1267 DrawLaser(rf - 1, DL_LASER_ENABLED);
1269 Ct = CT = FrameCounter;
1272 if (!IN_LEV_FIELD(ELX, ELY))
1273 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1277 static void ScanLaser_FromLastMirror(void)
1279 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1282 for (i = start_pos; i >= 0; i--)
1283 if (laser.damage[i].is_mirror)
1286 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1288 DrawLaser(start_edge, DL_LASER_DISABLED);
1293 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1299 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1300 start_edge, num_edges, mode);
1305 Warn("DrawLaserExt: start_edge < 0");
1312 Warn("DrawLaserExt: num_edges < 0");
1318 if (mode == DL_LASER_DISABLED)
1320 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1324 // now draw the laser to the backbuffer and (if enabled) to the screen
1325 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1327 redraw_mask |= REDRAW_FIELD;
1329 if (mode == DL_LASER_ENABLED)
1332 // after the laser was deleted, the "damaged" graphics must be restored
1333 if (laser.num_damages)
1335 int damage_start = 0;
1338 // determine the starting edge, from which graphics need to be restored
1341 for (i = 0; i < laser.num_damages; i++)
1343 if (laser.damage[i].edge == start_edge + 1)
1352 // restore graphics from this starting edge to the end of damage list
1353 for (i = damage_start; i < laser.num_damages; i++)
1355 int lx = laser.damage[i].x;
1356 int ly = laser.damage[i].y;
1357 int element = Tile[lx][ly];
1359 if (Hit[lx][ly] == laser.damage[i].edge)
1360 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1363 if (Box[lx][ly] == laser.damage[i].edge)
1366 if (IS_DRAWABLE(element))
1367 DrawField_MM(lx, ly);
1370 elx = laser.damage[damage_start].x;
1371 ely = laser.damage[damage_start].y;
1372 element = Tile[elx][ely];
1375 if (IS_BEAMER(element))
1379 for (i = 0; i < laser.num_beamers; i++)
1380 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1382 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1383 mode, elx, ely, Hit[elx][ely], start_edge);
1384 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1385 get_element_angle(element), laser.damage[damage_start].angle);
1389 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1390 laser.num_beamers > 0 &&
1391 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1393 // element is outgoing beamer
1394 laser.num_damages = damage_start + 1;
1396 if (IS_BEAMER(element))
1397 laser.current_angle = get_element_angle(element);
1401 // element is incoming beamer or other element
1402 laser.num_damages = damage_start;
1403 laser.current_angle = laser.damage[laser.num_damages].angle;
1408 // no damages but McDuffin himself (who needs to be redrawn anyway)
1410 elx = laser.start_edge.x;
1411 ely = laser.start_edge.y;
1412 element = Tile[elx][ely];
1415 laser.num_edges = start_edge + 1;
1416 if (start_edge == 0)
1417 laser.current_angle = laser.start_angle;
1419 LX = laser.edge[start_edge].x - cSX2;
1420 LY = laser.edge[start_edge].y - cSY2;
1421 XS = 2 * Step[laser.current_angle].x;
1422 YS = 2 * Step[laser.current_angle].y;
1425 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1431 if (IS_BEAMER(element) ||
1432 IS_FIBRE_OPTIC(element) ||
1433 IS_PACMAN(element) ||
1434 IS_POLAR(element) ||
1435 IS_POLAR_CROSS(element) ||
1436 element == EL_FUSE_ON)
1441 Debug("game:mm:DrawLaserExt", "element == %d", element);
1444 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1445 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1449 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1450 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1451 (laser.num_beamers == 0 ||
1452 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1454 // element is incoming beamer or other element
1455 step_size = -step_size;
1460 if (IS_BEAMER(element))
1461 Debug("game:mm:DrawLaserExt",
1462 "start_edge == %d, laser.beamer_edge == %d",
1463 start_edge, laser.beamer_edge);
1466 LX += step_size * XS;
1467 LY += step_size * YS;
1469 else if (element != EL_EMPTY)
1478 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1483 void DrawLaser(int start_edge, int mode)
1485 // do not draw laser if fuse is off
1486 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1489 if (mode == DL_LASER_DISABLED)
1490 DeactivateLaserTargetElement();
1492 if (laser.num_edges - start_edge < 0)
1494 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1499 // check if laser is interrupted by beamer element
1500 if (laser.num_beamers > 0 &&
1501 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1503 if (mode == DL_LASER_ENABLED)
1506 int tmp_start_edge = start_edge;
1508 // draw laser segments forward from the start to the last beamer
1509 for (i = 0; i < laser.num_beamers; i++)
1511 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1513 if (tmp_num_edges <= 0)
1517 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1518 i, laser.beamer_edge[i], tmp_start_edge);
1521 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1523 tmp_start_edge = laser.beamer_edge[i];
1526 // draw last segment from last beamer to the end
1527 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1533 int last_num_edges = laser.num_edges;
1534 int num_beamers = laser.num_beamers;
1536 // delete laser segments backward from the end to the first beamer
1537 for (i = num_beamers - 1; i >= 0; i--)
1539 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1541 if (laser.beamer_edge[i] - start_edge <= 0)
1544 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1546 last_num_edges = laser.beamer_edge[i];
1547 laser.num_beamers--;
1551 if (last_num_edges - start_edge <= 0)
1552 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1553 last_num_edges, start_edge);
1556 // special case when rotating first beamer: delete laser edge on beamer
1557 // (but do not start scanning on previous edge to prevent mirror sound)
1558 if (last_num_edges - start_edge == 1 && start_edge > 0)
1559 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1561 // delete first segment from start to the first beamer
1562 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1567 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1570 game_mm.laser_enabled = mode;
1573 void DrawLaser_MM(void)
1575 DrawLaser(0, game_mm.laser_enabled);
1578 static boolean HitElement(int element, int hit_mask)
1580 if (HitOnlyAnEdge(hit_mask))
1583 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1584 element = MovingOrBlocked2Element_MM(ELX, ELY);
1587 Debug("game:mm:HitElement", "(1): element == %d", element);
1591 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1592 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1595 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1599 AddDamagedField(ELX, ELY);
1601 // this is more precise: check if laser would go through the center
1602 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1606 // prevent cutting through laser emitter with laser beam
1607 if (IS_LASER(element))
1610 // skip the whole element before continuing the scan
1618 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1620 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1622 /* skipping scan positions to the right and down skips one scan
1623 position too much, because this is only the top left scan position
1624 of totally four scan positions (plus one to the right, one to the
1625 bottom and one to the bottom right) */
1626 /* ... but only roll back scan position if more than one step done */
1636 Debug("game:mm:HitElement", "(2): element == %d", element);
1639 if (LX + 5 * XS < 0 ||
1649 Debug("game:mm:HitElement", "(3): element == %d", element);
1652 if (IS_POLAR(element) &&
1653 ((element - EL_POLAR_START) % 2 ||
1654 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1656 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1658 laser.num_damages--;
1663 if (IS_POLAR_CROSS(element) &&
1664 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1666 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1668 laser.num_damages--;
1673 if (!IS_BEAMER(element) &&
1674 !IS_FIBRE_OPTIC(element) &&
1675 !IS_GRID_WOOD(element) &&
1676 element != EL_FUEL_EMPTY)
1679 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1680 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1682 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1685 LX = ELX * TILEX + 14;
1686 LY = ELY * TILEY + 14;
1688 AddLaserEdge(LX, LY);
1691 if (IS_MIRROR(element) ||
1692 IS_MIRROR_FIXED(element) ||
1693 IS_POLAR(element) ||
1694 IS_POLAR_CROSS(element) ||
1695 IS_DF_MIRROR(element) ||
1696 IS_DF_MIRROR_AUTO(element) ||
1697 element == EL_PRISM ||
1698 element == EL_REFRACTOR)
1700 int current_angle = laser.current_angle;
1703 laser.num_damages--;
1705 AddDamagedField(ELX, ELY);
1707 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1710 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1712 if (IS_MIRROR(element) ||
1713 IS_MIRROR_FIXED(element) ||
1714 IS_DF_MIRROR(element) ||
1715 IS_DF_MIRROR_AUTO(element))
1716 laser.current_angle = get_mirrored_angle(laser.current_angle,
1717 get_element_angle(element));
1719 if (element == EL_PRISM || element == EL_REFRACTOR)
1720 laser.current_angle = RND(16);
1722 XS = 2 * Step[laser.current_angle].x;
1723 YS = 2 * Step[laser.current_angle].y;
1725 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1730 LX += step_size * XS;
1731 LY += step_size * YS;
1733 // draw sparkles on mirror
1734 if ((IS_MIRROR(element) ||
1735 IS_MIRROR_FIXED(element) ||
1736 element == EL_PRISM) &&
1737 current_angle != laser.current_angle)
1739 MovDelay[ELX][ELY] = 11; // start animation
1742 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1743 current_angle != laser.current_angle)
1744 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1747 (get_opposite_angle(laser.current_angle) ==
1748 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1750 return (laser.overloaded ? TRUE : FALSE);
1753 if (element == EL_FUEL_FULL)
1755 laser.stops_inside_element = TRUE;
1760 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1762 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1764 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1765 element == EL_MINE ? EL_MINE_ACTIVE :
1766 EL_GRAY_BALL_ACTIVE);
1768 GfxFrame[ELX][ELY] = 0; // restart animation
1770 laser.dest_element_last = Tile[ELX][ELY];
1771 laser.dest_element_last_x = ELX;
1772 laser.dest_element_last_y = ELY;
1774 if (element == EL_MINE)
1775 laser.overloaded = TRUE;
1778 if (element == EL_KETTLE ||
1779 element == EL_CELL ||
1780 element == EL_KEY ||
1781 element == EL_LIGHTBALL ||
1782 element == EL_PACMAN ||
1783 IS_PACMAN(element) ||
1784 IS_ENVELOPE(element))
1786 if (!IS_PACMAN(element) &&
1787 !IS_ENVELOPE(element))
1790 if (element == EL_PACMAN)
1793 if (element == EL_KETTLE || element == EL_CELL)
1795 if (game_mm.kettles_still_needed > 0)
1796 game_mm.kettles_still_needed--;
1798 game.snapshot.collected_item = TRUE;
1800 if (game_mm.kettles_still_needed == 0)
1804 DrawLaser(0, DL_LASER_ENABLED);
1807 else if (element == EL_KEY)
1811 else if (IS_PACMAN(element))
1813 DeletePacMan(ELX, ELY);
1815 else if (IS_ENVELOPE(element))
1817 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1820 RaiseScoreElement_MM(element);
1825 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1827 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1829 DrawLaser(0, DL_LASER_ENABLED);
1831 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1833 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1834 game_mm.lights_still_needed--;
1838 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1839 game_mm.lights_still_needed++;
1842 DrawField_MM(ELX, ELY);
1843 DrawLaser(0, DL_LASER_ENABLED);
1848 laser.stops_inside_element = TRUE;
1854 Debug("game:mm:HitElement", "(4): element == %d", element);
1857 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1858 laser.num_beamers < MAX_NUM_BEAMERS &&
1859 laser.beamer[BEAMER_NR(element)][1].num)
1861 int beamer_angle = get_element_angle(element);
1862 int beamer_nr = BEAMER_NR(element);
1866 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1869 laser.num_damages--;
1871 if (IS_FIBRE_OPTIC(element) ||
1872 laser.current_angle == get_opposite_angle(beamer_angle))
1876 LX = ELX * TILEX + 14;
1877 LY = ELY * TILEY + 14;
1879 AddLaserEdge(LX, LY);
1880 AddDamagedField(ELX, ELY);
1882 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1885 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1887 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1888 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1889 ELX = laser.beamer[beamer_nr][pos].x;
1890 ELY = laser.beamer[beamer_nr][pos].y;
1891 LX = ELX * TILEX + 14;
1892 LY = ELY * TILEY + 14;
1894 if (IS_BEAMER(element))
1896 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1897 XS = 2 * Step[laser.current_angle].x;
1898 YS = 2 * Step[laser.current_angle].y;
1901 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1903 AddLaserEdge(LX, LY);
1904 AddDamagedField(ELX, ELY);
1906 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1909 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1911 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1916 LX += step_size * XS;
1917 LY += step_size * YS;
1919 laser.num_beamers++;
1928 static boolean HitOnlyAnEdge(int hit_mask)
1930 // check if the laser hit only the edge of an element and, if so, go on
1933 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1937 if ((hit_mask == HIT_MASK_TOPLEFT ||
1938 hit_mask == HIT_MASK_TOPRIGHT ||
1939 hit_mask == HIT_MASK_BOTTOMLEFT ||
1940 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1941 laser.current_angle % 4) // angle is not 90°
1945 if (hit_mask == HIT_MASK_TOPLEFT)
1950 else if (hit_mask == HIT_MASK_TOPRIGHT)
1955 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1960 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1966 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1972 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1979 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1985 static boolean HitPolarizer(int element, int hit_mask)
1987 if (HitOnlyAnEdge(hit_mask))
1990 if (IS_DF_GRID(element))
1992 int grid_angle = get_element_angle(element);
1995 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1996 grid_angle, laser.current_angle);
1999 AddLaserEdge(LX, LY);
2000 AddDamagedField(ELX, ELY);
2003 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2005 if (laser.current_angle == grid_angle ||
2006 laser.current_angle == get_opposite_angle(grid_angle))
2008 // skip the whole element before continuing the scan
2014 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2016 if (LX/TILEX > ELX || LY/TILEY > ELY)
2018 /* skipping scan positions to the right and down skips one scan
2019 position too much, because this is only the top left scan position
2020 of totally four scan positions (plus one to the right, one to the
2021 bottom and one to the bottom right) */
2027 AddLaserEdge(LX, LY);
2033 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2035 LX / TILEX, LY / TILEY,
2036 LX % TILEX, LY % TILEY);
2041 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2043 return HitReflectingWalls(element, hit_mask);
2047 return HitAbsorbingWalls(element, hit_mask);
2050 else if (IS_GRID_STEEL(element))
2052 // may be required if graphics for steel grid redefined
2053 AddDamagedField(ELX, ELY);
2055 return HitReflectingWalls(element, hit_mask);
2057 else // IS_GRID_WOOD
2059 // may be required if graphics for wooden grid redefined
2060 AddDamagedField(ELX, ELY);
2062 return HitAbsorbingWalls(element, hit_mask);
2068 static boolean HitBlock(int element, int hit_mask)
2070 boolean check = FALSE;
2072 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2073 game_mm.num_keys == 0)
2076 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2079 int ex = ELX * TILEX + 14;
2080 int ey = ELY * TILEY + 14;
2084 for (i = 1; i < 32; i++)
2089 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2094 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2095 return HitAbsorbingWalls(element, hit_mask);
2099 AddLaserEdge(LX - XS, LY - YS);
2100 AddDamagedField(ELX, ELY);
2103 Box[ELX][ELY] = laser.num_edges;
2105 return HitReflectingWalls(element, hit_mask);
2108 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2110 int xs = XS / 2, ys = YS / 2;
2112 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2113 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2115 laser.overloaded = (element == EL_GATE_STONE);
2120 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2121 (hit_mask == HIT_MASK_TOP ||
2122 hit_mask == HIT_MASK_LEFT ||
2123 hit_mask == HIT_MASK_RIGHT ||
2124 hit_mask == HIT_MASK_BOTTOM))
2125 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2126 hit_mask == HIT_MASK_BOTTOM),
2127 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2128 hit_mask == HIT_MASK_RIGHT));
2129 AddLaserEdge(LX, LY);
2135 if (element == EL_GATE_STONE && Box[ELX][ELY])
2137 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2149 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2151 int xs = XS / 2, ys = YS / 2;
2153 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2154 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2156 laser.overloaded = (element == EL_BLOCK_STONE);
2161 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2162 (hit_mask == HIT_MASK_TOP ||
2163 hit_mask == HIT_MASK_LEFT ||
2164 hit_mask == HIT_MASK_RIGHT ||
2165 hit_mask == HIT_MASK_BOTTOM))
2166 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2167 hit_mask == HIT_MASK_BOTTOM),
2168 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2169 hit_mask == HIT_MASK_RIGHT));
2170 AddDamagedField(ELX, ELY);
2172 LX = ELX * TILEX + 14;
2173 LY = ELY * TILEY + 14;
2175 AddLaserEdge(LX, LY);
2177 laser.stops_inside_element = TRUE;
2185 static boolean HitLaserSource(int element, int hit_mask)
2187 if (HitOnlyAnEdge(hit_mask))
2190 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2192 laser.overloaded = TRUE;
2197 static boolean HitLaserDestination(int element, int hit_mask)
2199 if (HitOnlyAnEdge(hit_mask))
2202 if (element != EL_EXIT_OPEN &&
2203 !(IS_RECEIVER(element) &&
2204 game_mm.kettles_still_needed == 0 &&
2205 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2207 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2212 if (IS_RECEIVER(element) ||
2213 (IS_22_5_ANGLE(laser.current_angle) &&
2214 (ELX != (LX + 6 * XS) / TILEX ||
2215 ELY != (LY + 6 * YS) / TILEY ||
2224 LX = ELX * TILEX + 14;
2225 LY = ELY * TILEY + 14;
2227 laser.stops_inside_element = TRUE;
2230 AddLaserEdge(LX, LY);
2231 AddDamagedField(ELX, ELY);
2233 if (game_mm.lights_still_needed == 0)
2235 game_mm.level_solved = TRUE;
2237 SetTileCursorActive(FALSE);
2243 static boolean HitReflectingWalls(int element, int hit_mask)
2245 // check if laser hits side of a wall with an angle that is not 90°
2246 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2247 hit_mask == HIT_MASK_LEFT ||
2248 hit_mask == HIT_MASK_RIGHT ||
2249 hit_mask == HIT_MASK_BOTTOM))
2251 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2256 if (!IS_DF_GRID(element))
2257 AddLaserEdge(LX, LY);
2259 // check if laser hits wall with an angle of 45°
2260 if (!IS_22_5_ANGLE(laser.current_angle))
2262 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2265 laser.current_angle = get_mirrored_angle(laser.current_angle,
2268 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2271 laser.current_angle = get_mirrored_angle(laser.current_angle,
2275 AddLaserEdge(LX, LY);
2277 XS = 2 * Step[laser.current_angle].x;
2278 YS = 2 * Step[laser.current_angle].y;
2282 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2284 laser.current_angle = get_mirrored_angle(laser.current_angle,
2289 if (!IS_DF_GRID(element))
2290 AddLaserEdge(LX, LY);
2295 if (!IS_DF_GRID(element))
2296 AddLaserEdge(LX, LY + YS / 2);
2299 if (!IS_DF_GRID(element))
2300 AddLaserEdge(LX, LY);
2303 YS = 2 * Step[laser.current_angle].y;
2307 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2309 laser.current_angle = get_mirrored_angle(laser.current_angle,
2314 if (!IS_DF_GRID(element))
2315 AddLaserEdge(LX, LY);
2320 if (!IS_DF_GRID(element))
2321 AddLaserEdge(LX + XS / 2, LY);
2324 if (!IS_DF_GRID(element))
2325 AddLaserEdge(LX, LY);
2328 XS = 2 * Step[laser.current_angle].x;
2334 // reflection at the edge of reflecting DF style wall
2335 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2337 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2338 hit_mask == HIT_MASK_TOPRIGHT) ||
2339 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2340 hit_mask == HIT_MASK_TOPLEFT) ||
2341 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2342 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2343 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2344 hit_mask == HIT_MASK_BOTTOMRIGHT))
2347 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2348 ANG_MIRROR_135 : ANG_MIRROR_45);
2350 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2352 AddDamagedField(ELX, ELY);
2353 AddLaserEdge(LX, LY);
2355 laser.current_angle = get_mirrored_angle(laser.current_angle,
2363 AddLaserEdge(LX, LY);
2369 // reflection inside an edge of reflecting DF style wall
2370 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2372 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2373 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2374 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2375 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2376 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2377 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2378 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2379 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2382 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2383 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2384 ANG_MIRROR_135 : ANG_MIRROR_45);
2386 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2389 AddDamagedField(ELX, ELY);
2392 AddLaserEdge(LX - XS, LY - YS);
2393 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2394 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2396 laser.current_angle = get_mirrored_angle(laser.current_angle,
2404 AddLaserEdge(LX, LY);
2410 // check if laser hits DF style wall with an angle of 90°
2411 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2413 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2414 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2415 (IS_VERT_ANGLE(laser.current_angle) &&
2416 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2418 // laser at last step touched nothing or the same side of the wall
2419 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2421 AddDamagedField(ELX, ELY);
2428 last_hit_mask = hit_mask;
2435 if (!HitOnlyAnEdge(hit_mask))
2437 laser.overloaded = TRUE;
2445 static boolean HitAbsorbingWalls(int element, int hit_mask)
2447 if (HitOnlyAnEdge(hit_mask))
2451 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2453 AddLaserEdge(LX - XS, LY - YS);
2460 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2462 AddLaserEdge(LX - XS, LY - YS);
2468 if (IS_WALL_WOOD(element) ||
2469 IS_DF_WALL_WOOD(element) ||
2470 IS_GRID_WOOD(element) ||
2471 IS_GRID_WOOD_FIXED(element) ||
2472 IS_GRID_WOOD_AUTO(element) ||
2473 element == EL_FUSE_ON ||
2474 element == EL_BLOCK_WOOD ||
2475 element == EL_GATE_WOOD)
2477 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2482 if (IS_WALL_ICE(element))
2488 // check if laser hit adjacent edges of two diagonal tiles
2489 if (ELX != lx / TILEX)
2491 if (ELY != ly / TILEY)
2494 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2495 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2497 // check if laser hits wall with an angle of 90°
2498 if (IS_90_ANGLE(laser.current_angle))
2499 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2501 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2505 for (i = 0; i < 4; i++)
2507 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2508 mask = 15 - (8 >> i);
2509 else if (ABS(XS) == 4 &&
2511 (XS > 0) == (i % 2) &&
2512 (YS < 0) == (i / 2))
2513 mask = 3 + (i / 2) * 9;
2514 else if (ABS(YS) == 4 &&
2516 (XS < 0) == (i % 2) &&
2517 (YS > 0) == (i / 2))
2518 mask = 5 + (i % 2) * 5;
2522 laser.wall_mask = mask;
2524 else if (IS_WALL_AMOEBA(element))
2526 int elx = (LX - 2 * XS) / TILEX;
2527 int ely = (LY - 2 * YS) / TILEY;
2528 int element2 = Tile[elx][ely];
2531 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2533 laser.dest_element = EL_EMPTY;
2541 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2542 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2544 if (IS_90_ANGLE(laser.current_angle))
2545 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2547 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2549 laser.wall_mask = mask;
2555 static void OpenExit(int x, int y)
2559 if (!MovDelay[x][y]) // next animation frame
2560 MovDelay[x][y] = 4 * delay;
2562 if (MovDelay[x][y]) // wait some time before next frame
2567 phase = MovDelay[x][y] / delay;
2569 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2570 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2572 if (!MovDelay[x][y])
2574 Tile[x][y] = EL_EXIT_OPEN;
2580 static void OpenGrayBall(int x, int y)
2584 if (!MovDelay[x][y]) // next animation frame
2586 if (IS_WALL(Store[x][y]))
2588 DrawWalls_MM(x, y, Store[x][y]);
2590 // copy wall tile to spare bitmap for "melting" animation
2591 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2592 TILEX, TILEY, x * TILEX, y * TILEY);
2594 DrawElement_MM(x, y, EL_GRAY_BALL);
2597 MovDelay[x][y] = 50 * delay;
2600 if (MovDelay[x][y]) // wait some time before next frame
2604 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2608 int dx = RND(26), dy = RND(26);
2610 if (IS_WALL(Store[x][y]))
2612 // copy wall tile from spare bitmap for "melting" animation
2613 bitmap = bitmap_db_field;
2619 int graphic = el2gfx(Store[x][y]);
2621 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2624 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2625 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2627 laser.redraw = TRUE;
2629 MarkTileDirty(x, y);
2632 if (!MovDelay[x][y])
2634 Tile[x][y] = Store[x][y];
2635 Store[x][y] = Store2[x][y] = 0;
2636 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2638 InitField(x, y, FALSE);
2641 ScanLaser_FromLastMirror();
2646 static void OpenEnvelope(int x, int y)
2648 int num_frames = 8; // seven frames plus final empty space
2650 if (!MovDelay[x][y]) // next animation frame
2651 MovDelay[x][y] = num_frames;
2653 if (MovDelay[x][y]) // wait some time before next frame
2655 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2659 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2661 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2662 int frame = num_frames - MovDelay[x][y] - 1;
2664 DrawGraphicAnimation_MM(x, y, graphic, frame);
2666 laser.redraw = TRUE;
2669 if (MovDelay[x][y] == 0)
2671 Tile[x][y] = EL_EMPTY;
2682 static void MeltIce(int x, int y)
2687 if (!MovDelay[x][y]) // next animation frame
2688 MovDelay[x][y] = frames * delay;
2690 if (MovDelay[x][y]) // wait some time before next frame
2693 int wall_mask = Store2[x][y];
2694 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2697 phase = frames - MovDelay[x][y] / delay - 1;
2699 if (!MovDelay[x][y])
2701 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2702 Store[x][y] = Store2[x][y] = 0;
2704 DrawWalls_MM(x, y, Tile[x][y]);
2706 if (Tile[x][y] == EL_WALL_ICE_BASE)
2707 Tile[x][y] = EL_EMPTY;
2709 ScanLaser_FromLastMirror();
2711 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2713 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2715 laser.redraw = TRUE;
2720 static void GrowAmoeba(int x, int y)
2725 if (!MovDelay[x][y]) // next animation frame
2726 MovDelay[x][y] = frames * delay;
2728 if (MovDelay[x][y]) // wait some time before next frame
2731 int wall_mask = Store2[x][y];
2732 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2735 phase = MovDelay[x][y] / delay;
2737 if (!MovDelay[x][y])
2739 Tile[x][y] = real_element;
2740 Store[x][y] = Store2[x][y] = 0;
2742 DrawWalls_MM(x, y, Tile[x][y]);
2743 DrawLaser(0, DL_LASER_ENABLED);
2745 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2747 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2752 static void DrawFieldAnimated_MM(int x, int y)
2756 laser.redraw = TRUE;
2759 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2761 int element = Tile[x][y];
2762 int graphic = el2gfx(element);
2764 if (!getGraphicInfo_NewFrame(x, y, graphic))
2769 laser.redraw = TRUE;
2772 static void DrawFieldTwinkle(int x, int y)
2774 if (MovDelay[x][y] != 0) // wait some time before next frame
2780 if (MovDelay[x][y] != 0)
2782 int graphic = IMG_TWINKLE_WHITE;
2783 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2785 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2788 laser.redraw = TRUE;
2792 static void Explode_MM(int x, int y, int phase, int mode)
2794 int num_phase = 9, delay = 2;
2795 int last_phase = num_phase * delay;
2796 int half_phase = (num_phase / 2) * delay;
2799 laser.redraw = TRUE;
2801 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2803 center_element = Tile[x][y];
2805 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2807 // put moving element to center field (and let it explode there)
2808 center_element = MovingOrBlocked2Element_MM(x, y);
2809 RemoveMovingField_MM(x, y);
2811 Tile[x][y] = center_element;
2814 if (center_element != EL_GRAY_BALL_ACTIVE)
2815 Store[x][y] = EL_EMPTY;
2816 Store2[x][y] = center_element;
2818 Tile[x][y] = EL_EXPLODING_OPAQUE;
2820 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2821 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2822 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2825 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2827 ExplodePhase[x][y] = 1;
2833 GfxFrame[x][y] = 0; // restart explosion animation
2835 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2837 center_element = Store2[x][y];
2839 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2841 Tile[x][y] = EL_EXPLODING_TRANSP;
2843 if (x == ELX && y == ELY)
2847 if (phase == last_phase)
2849 if (center_element == EL_BOMB_ACTIVE)
2851 DrawLaser(0, DL_LASER_DISABLED);
2854 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2856 laser.overloaded = FALSE;
2858 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2860 GameOver_MM(GAME_OVER_BOMB);
2863 Tile[x][y] = Store[x][y];
2865 Store[x][y] = Store2[x][y] = 0;
2866 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2868 InitField(x, y, FALSE);
2871 if (center_element == EL_GRAY_BALL_ACTIVE)
2872 ScanLaser_FromLastMirror();
2874 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2876 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2877 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2879 DrawGraphicAnimation_MM(x, y, graphic, frame);
2881 MarkTileDirty(x, y);
2885 static void Bang_MM(int x, int y)
2887 int element = Tile[x][y];
2889 if (IS_PACMAN(element))
2890 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2891 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2892 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2893 else if (element == EL_KEY)
2894 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2896 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2898 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2901 static void TurnRound(int x, int y)
2913 { 0, 0 }, { 0, 0 }, { 0, 0 },
2918 int left, right, back;
2922 { MV_DOWN, MV_UP, MV_RIGHT },
2923 { MV_UP, MV_DOWN, MV_LEFT },
2925 { MV_LEFT, MV_RIGHT, MV_DOWN },
2929 { MV_RIGHT, MV_LEFT, MV_UP }
2932 int element = Tile[x][y];
2933 int old_move_dir = MovDir[x][y];
2934 int right_dir = turn[old_move_dir].right;
2935 int back_dir = turn[old_move_dir].back;
2936 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2937 int right_x = x + right_dx, right_y = y + right_dy;
2939 if (element == EL_PACMAN)
2941 boolean can_turn_right = FALSE;
2943 if (IN_LEV_FIELD(right_x, right_y) &&
2944 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2945 can_turn_right = TRUE;
2948 MovDir[x][y] = right_dir;
2950 MovDir[x][y] = back_dir;
2956 static void StartMoving_MM(int x, int y)
2958 int element = Tile[x][y];
2963 if (CAN_MOVE(element))
2967 if (MovDelay[x][y]) // wait some time before next movement
2975 // now make next step
2977 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2979 if (element == EL_PACMAN &&
2980 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2981 !ObjHit(newx, newy, HIT_POS_CENTER))
2983 Store[newx][newy] = Tile[newx][newy];
2984 Tile[newx][newy] = EL_EMPTY;
2986 DrawField_MM(newx, newy);
2988 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2989 ObjHit(newx, newy, HIT_POS_CENTER))
2991 // object was running against a wall
2998 InitMovingField_MM(x, y, MovDir[x][y]);
3002 ContinueMoving_MM(x, y);
3005 static void ContinueMoving_MM(int x, int y)
3007 int element = Tile[x][y];
3008 int direction = MovDir[x][y];
3009 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3010 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3011 int horiz_move = (dx!=0);
3012 int newx = x + dx, newy = y + dy;
3013 int step = (horiz_move ? dx : dy) * TILEX / 8;
3015 MovPos[x][y] += step;
3017 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3019 Tile[x][y] = EL_EMPTY;
3020 Tile[newx][newy] = element;
3022 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3023 MovDelay[newx][newy] = 0;
3025 if (!CAN_MOVE(element))
3026 MovDir[newx][newy] = 0;
3029 DrawField_MM(newx, newy);
3031 Stop[newx][newy] = TRUE;
3033 if (element == EL_PACMAN)
3035 if (Store[newx][newy] == EL_BOMB)
3036 Bang_MM(newx, newy);
3038 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3039 (LX + 2 * XS) / TILEX == newx &&
3040 (LY + 2 * YS) / TILEY == newy)
3047 else // still moving on
3052 laser.redraw = TRUE;
3055 boolean ClickElement(int x, int y, int button)
3057 static DelayCounter click_delay = { CLICK_DELAY };
3058 static boolean new_button = TRUE;
3059 boolean element_clicked = FALSE;
3064 // initialize static variables
3065 click_delay.count = 0;
3066 click_delay.value = CLICK_DELAY;
3072 // do not rotate objects hit by the laser after the game was solved
3073 if (game_mm.level_solved && Hit[x][y])
3076 if (button == MB_RELEASED)
3079 click_delay.value = CLICK_DELAY;
3081 // release eventually hold auto-rotating mirror
3082 RotateMirror(x, y, MB_RELEASED);
3087 if (!FrameReached(&click_delay) && !new_button)
3090 if (button == MB_MIDDLEBUTTON) // middle button has no function
3093 if (!IN_LEV_FIELD(x, y))
3096 if (Tile[x][y] == EL_EMPTY)
3099 element = Tile[x][y];
3101 if (IS_MIRROR(element) ||
3102 IS_BEAMER(element) ||
3103 IS_POLAR(element) ||
3104 IS_POLAR_CROSS(element) ||
3105 IS_DF_MIRROR(element) ||
3106 IS_DF_MIRROR_AUTO(element))
3108 RotateMirror(x, y, button);
3110 element_clicked = TRUE;
3112 else if (IS_MCDUFFIN(element))
3114 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3116 if (has_laser && !laser.fuse_off)
3117 DrawLaser(0, DL_LASER_DISABLED);
3119 element = get_rotated_element(element, BUTTON_ROTATION(button));
3121 Tile[x][y] = element;
3126 laser.start_angle = get_element_angle(element);
3130 if (!laser.fuse_off)
3134 element_clicked = TRUE;
3136 else if (element == EL_FUSE_ON && laser.fuse_off)
3138 if (x != laser.fuse_x || y != laser.fuse_y)
3141 laser.fuse_off = FALSE;
3142 laser.fuse_x = laser.fuse_y = -1;
3144 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3147 element_clicked = TRUE;
3149 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3151 laser.fuse_off = TRUE;
3154 laser.overloaded = FALSE;
3156 DrawLaser(0, DL_LASER_DISABLED);
3157 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3159 element_clicked = TRUE;
3161 else if (element == EL_LIGHTBALL)
3164 RaiseScoreElement_MM(element);
3165 DrawLaser(0, DL_LASER_ENABLED);
3167 element_clicked = TRUE;
3169 else if (IS_ENVELOPE(element))
3171 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3173 element_clicked = TRUE;
3176 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3179 return element_clicked;
3182 static void RotateMirror(int x, int y, int button)
3184 if (button == MB_RELEASED)
3186 // release eventually hold auto-rotating mirror
3193 if (IS_MIRROR(Tile[x][y]) ||
3194 IS_POLAR_CROSS(Tile[x][y]) ||
3195 IS_POLAR(Tile[x][y]) ||
3196 IS_BEAMER(Tile[x][y]) ||
3197 IS_DF_MIRROR(Tile[x][y]) ||
3198 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3199 IS_GRID_WOOD_AUTO(Tile[x][y]))
3201 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3203 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3205 if (button == MB_LEFTBUTTON)
3207 // left mouse button only for manual adjustment, no auto-rotating;
3208 // freeze mirror for until mouse button released
3212 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3214 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3218 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3220 int edge = Hit[x][y];
3226 DrawLaser(edge - 1, DL_LASER_DISABLED);
3230 else if (ObjHit(x, y, HIT_POS_CENTER))
3232 int edge = Hit[x][y];
3236 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3241 DrawLaser(edge - 1, DL_LASER_DISABLED);
3248 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3253 if ((IS_BEAMER(Tile[x][y]) ||
3254 IS_POLAR(Tile[x][y]) ||
3255 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3257 if (IS_BEAMER(Tile[x][y]))
3260 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3261 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3274 DrawLaser(0, DL_LASER_ENABLED);
3278 static void AutoRotateMirrors(void)
3282 if (!FrameReached(&rotate_delay))
3285 for (x = 0; x < lev_fieldx; x++)
3287 for (y = 0; y < lev_fieldy; y++)
3289 int element = Tile[x][y];
3291 // do not rotate objects hit by the laser after the game was solved
3292 if (game_mm.level_solved && Hit[x][y])
3295 if (IS_DF_MIRROR_AUTO(element) ||
3296 IS_GRID_WOOD_AUTO(element) ||
3297 IS_GRID_STEEL_AUTO(element) ||
3298 element == EL_REFRACTOR)
3300 RotateMirror(x, y, MB_RIGHTBUTTON);
3302 laser.redraw = TRUE;
3308 static boolean ObjHit(int obx, int oby, int bits)
3315 if (bits & HIT_POS_CENTER)
3317 if (CheckLaserPixel(cSX + obx + 15,
3322 if (bits & HIT_POS_EDGE)
3324 for (i = 0; i < 4; i++)
3325 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3326 cSY + oby + 31 * (i / 2)))
3330 if (bits & HIT_POS_BETWEEN)
3332 for (i = 0; i < 4; i++)
3333 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3334 cSY + 4 + oby + 22 * (i / 2)))
3341 static void DeletePacMan(int px, int py)
3347 if (game_mm.num_pacman <= 1)
3349 game_mm.num_pacman = 0;
3353 for (i = 0; i < game_mm.num_pacman; i++)
3354 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3357 game_mm.num_pacman--;
3359 for (j = i; j < game_mm.num_pacman; j++)
3361 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3362 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3363 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3367 static void GameActions_MM_Ext(void)
3374 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3377 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3379 element = Tile[x][y];
3381 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3382 StartMoving_MM(x, y);
3383 else if (IS_MOVING(x, y))
3384 ContinueMoving_MM(x, y);
3385 else if (IS_EXPLODING(element))
3386 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3387 else if (element == EL_EXIT_OPENING)
3389 else if (element == EL_GRAY_BALL_OPENING)
3391 else if (IS_ENVELOPE_OPENING(element))
3393 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3395 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3397 else if (IS_MIRROR(element) ||
3398 IS_MIRROR_FIXED(element) ||
3399 element == EL_PRISM)
3400 DrawFieldTwinkle(x, y);
3401 else if (element == EL_GRAY_BALL_ACTIVE ||
3402 element == EL_BOMB_ACTIVE ||
3403 element == EL_MINE_ACTIVE)
3404 DrawFieldAnimated_MM(x, y);
3405 else if (!IS_BLOCKED(x, y))
3406 DrawFieldAnimatedIfNeeded_MM(x, y);
3409 AutoRotateMirrors();
3412 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3414 // redraw after Explode_MM() ...
3416 DrawLaser(0, DL_LASER_ENABLED);
3417 laser.redraw = FALSE;
3422 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3426 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3428 DrawLaser(0, DL_LASER_DISABLED);
3433 // skip all following game actions if game is over
3434 if (game_mm.game_over)
3437 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3441 GameOver_MM(GAME_OVER_NO_ENERGY);
3446 if (FrameReached(&energy_delay))
3448 if (game_mm.energy_left > 0)
3449 game_mm.energy_left--;
3451 // when out of energy, wait another frame to play "out of time" sound
3454 element = laser.dest_element;
3457 if (element != Tile[ELX][ELY])
3459 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3460 element, Tile[ELX][ELY]);
3464 if (!laser.overloaded && laser.overload_value == 0 &&
3465 element != EL_BOMB &&
3466 element != EL_BOMB_ACTIVE &&
3467 element != EL_MINE &&
3468 element != EL_MINE_ACTIVE &&
3469 element != EL_GRAY_BALL &&
3470 element != EL_GRAY_BALL_ACTIVE &&
3471 element != EL_BLOCK_STONE &&
3472 element != EL_BLOCK_WOOD &&
3473 element != EL_FUSE_ON &&
3474 element != EL_FUEL_FULL &&
3475 !IS_WALL_ICE(element) &&
3476 !IS_WALL_AMOEBA(element))
3479 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3481 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3482 (!laser.overloaded && laser.overload_value > 0)) &&
3483 FrameReached(&overload_delay))
3485 if (laser.overloaded)
3486 laser.overload_value++;
3488 laser.overload_value--;
3490 if (game_mm.cheat_no_overload)
3492 laser.overloaded = FALSE;
3493 laser.overload_value = 0;
3496 game_mm.laser_overload_value = laser.overload_value;
3498 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3500 SetLaserColor(0xFF);
3502 DrawLaser(0, DL_LASER_ENABLED);
3505 if (!laser.overloaded)
3506 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3507 else if (setup.sound_loops)
3508 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3510 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3512 if (laser.overload_value == MAX_LASER_OVERLOAD)
3514 UpdateAndDisplayGameControlValues();
3518 GameOver_MM(GAME_OVER_OVERLOADED);
3529 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3531 if (game_mm.cheat_no_explosion)
3536 laser.dest_element = EL_EXPLODING_OPAQUE;
3541 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3543 laser.fuse_off = TRUE;
3547 DrawLaser(0, DL_LASER_DISABLED);
3548 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3551 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3553 if (!Store2[ELX][ELY]) // check if content element not yet determined
3555 int last_anim_random_frame = gfx.anim_random_frame;
3558 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3559 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3561 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3562 native_mm_level.ball_choice_mode, 0,
3563 game_mm.ball_choice_pos);
3565 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3566 gfx.anim_random_frame = last_anim_random_frame;
3568 game_mm.ball_choice_pos++;
3570 int new_element = native_mm_level.ball_content[element_pos];
3571 int new_element_base = map_wall_to_base_element(new_element);
3573 if (IS_WALL(new_element_base))
3575 // always use completely filled wall element
3576 new_element = new_element_base | 0x000f;
3578 else if (native_mm_level.rotate_ball_content &&
3579 get_num_elements(new_element) > 1)
3581 // randomly rotate newly created game element
3582 new_element = get_rotated_element(new_element, RND(16));
3585 Store[ELX][ELY] = new_element;
3586 Store2[ELX][ELY] = TRUE;
3589 if (native_mm_level.explode_ball)
3592 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3594 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3599 if (IS_WALL_ICE(element) && CT > 50)
3601 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3603 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3604 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3605 Store2[ELX][ELY] = laser.wall_mask;
3607 laser.dest_element = Tile[ELX][ELY];
3612 if (IS_WALL_AMOEBA(element) && CT > 60)
3615 int element2 = Tile[ELX][ELY];
3617 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3620 for (i = laser.num_damages - 1; i >= 0; i--)
3621 if (laser.damage[i].is_mirror)
3624 r = laser.num_edges;
3625 d = laser.num_damages;
3632 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3635 DrawLaser(0, DL_LASER_ENABLED);
3638 x = laser.damage[k1].x;
3639 y = laser.damage[k1].y;
3644 for (i = 0; i < 4; i++)
3646 if (laser.wall_mask & (1 << i))
3648 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3649 cSY + ELY * TILEY + 31 * (i / 2)))
3652 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3653 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3660 for (i = 0; i < 4; i++)
3662 if (laser.wall_mask & (1 << i))
3664 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3665 cSY + ELY * TILEY + 31 * (i / 2)))
3672 if (laser.num_beamers > 0 ||
3673 k1 < 1 || k2 < 4 || k3 < 4 ||
3674 CheckLaserPixel(cSX + ELX * TILEX + 14,
3675 cSY + ELY * TILEY + 14))
3677 laser.num_edges = r;
3678 laser.num_damages = d;
3680 DrawLaser(0, DL_LASER_DISABLED);
3683 Tile[ELX][ELY] = element | laser.wall_mask;
3685 int x = ELX, y = ELY;
3686 int wall_mask = laser.wall_mask;
3689 DrawLaser(0, DL_LASER_ENABLED);
3691 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3693 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3694 Store[x][y] = EL_WALL_AMOEBA_BASE;
3695 Store2[x][y] = wall_mask;
3700 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3701 laser.stops_inside_element && CT > native_mm_level.time_block)
3706 if (ABS(XS) > ABS(YS))
3713 for (i = 0; i < 4; i++)
3720 x = ELX + Step[k * 4].x;
3721 y = ELY + Step[k * 4].y;
3723 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3726 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3734 laser.overloaded = (element == EL_BLOCK_STONE);
3739 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3742 Tile[x][y] = element;
3744 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3747 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3749 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3750 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3758 if (element == EL_FUEL_FULL && CT > 10)
3760 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3761 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3763 for (i = start; i <= num_init_game_frames; i++)
3765 if (i == num_init_game_frames)
3766 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3767 else if (setup.sound_loops)
3768 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3770 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3772 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3774 UpdateAndDisplayGameControlValues();
3779 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3781 DrawField_MM(ELX, ELY);
3783 DrawLaser(0, DL_LASER_ENABLED);
3789 void GameActions_MM(struct MouseActionInfo action)
3791 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3792 boolean button_released = (action.button == MB_RELEASED);
3794 GameActions_MM_Ext();
3796 CheckSingleStepMode_MM(element_clicked, button_released);
3799 static void MovePacMen(void)
3801 int mx, my, ox, oy, nx, ny;
3805 if (++pacman_nr >= game_mm.num_pacman)
3808 game_mm.pacman[pacman_nr].dir--;
3810 for (l = 1; l < 5; l++)
3812 game_mm.pacman[pacman_nr].dir++;
3814 if (game_mm.pacman[pacman_nr].dir > 4)
3815 game_mm.pacman[pacman_nr].dir = 1;
3817 if (game_mm.pacman[pacman_nr].dir % 2)
3820 my = game_mm.pacman[pacman_nr].dir - 2;
3825 mx = 3 - game_mm.pacman[pacman_nr].dir;
3828 ox = game_mm.pacman[pacman_nr].x;
3829 oy = game_mm.pacman[pacman_nr].y;
3832 element = Tile[nx][ny];
3834 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3837 if (!IS_EATABLE4PACMAN(element))
3840 if (ObjHit(nx, ny, HIT_POS_CENTER))
3843 Tile[ox][oy] = EL_EMPTY;
3845 EL_PACMAN_RIGHT - 1 +
3846 (game_mm.pacman[pacman_nr].dir - 1 +
3847 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3849 game_mm.pacman[pacman_nr].x = nx;
3850 game_mm.pacman[pacman_nr].y = ny;
3852 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3854 if (element != EL_EMPTY)
3856 int graphic = el2gfx(Tile[nx][ny]);
3861 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3864 ox = cSX + ox * TILEX;
3865 oy = cSY + oy * TILEY;
3867 for (i = 1; i < 33; i += 2)
3868 BlitBitmap(bitmap, window,
3869 src_x, src_y, TILEX, TILEY,
3870 ox + i * mx, oy + i * my);
3871 Ct = Ct + FrameCounter - CT;
3874 DrawField_MM(nx, ny);
3877 if (!laser.fuse_off)
3879 DrawLaser(0, DL_LASER_ENABLED);
3881 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3883 AddDamagedField(nx, ny);
3885 laser.damage[laser.num_damages - 1].edge = 0;
3889 if (element == EL_BOMB)
3890 DeletePacMan(nx, ny);
3892 if (IS_WALL_AMOEBA(element) &&
3893 (LX + 2 * XS) / TILEX == nx &&
3894 (LY + 2 * YS) / TILEY == ny)
3904 static void InitMovingField_MM(int x, int y, int direction)
3906 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3907 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3909 MovDir[x][y] = direction;
3910 MovDir[newx][newy] = direction;
3912 if (Tile[newx][newy] == EL_EMPTY)
3913 Tile[newx][newy] = EL_BLOCKED;
3916 static int MovingOrBlocked2Element_MM(int x, int y)
3918 int element = Tile[x][y];
3920 if (element == EL_BLOCKED)
3924 Blocked2Moving(x, y, &oldx, &oldy);
3926 return Tile[oldx][oldy];
3932 static void RemoveMovingField_MM(int x, int y)
3934 int oldx = x, oldy = y, newx = x, newy = y;
3936 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3939 if (IS_MOVING(x, y))
3941 Moving2Blocked(x, y, &newx, &newy);
3942 if (Tile[newx][newy] != EL_BLOCKED)
3945 else if (Tile[x][y] == EL_BLOCKED)
3947 Blocked2Moving(x, y, &oldx, &oldy);
3948 if (!IS_MOVING(oldx, oldy))
3952 Tile[oldx][oldy] = EL_EMPTY;
3953 Tile[newx][newy] = EL_EMPTY;
3954 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3955 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3957 DrawLevelField_MM(oldx, oldy);
3958 DrawLevelField_MM(newx, newy);
3961 static void RaiseScore_MM(int value)
3963 game_mm.score += value;
3966 void RaiseScoreElement_MM(int element)
3971 case EL_PACMAN_RIGHT:
3973 case EL_PACMAN_LEFT:
3974 case EL_PACMAN_DOWN:
3975 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3979 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3984 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3988 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3997 // ----------------------------------------------------------------------------
3998 // Mirror Magic game engine snapshot handling functions
3999 // ----------------------------------------------------------------------------
4001 void SaveEngineSnapshotValues_MM(void)
4005 engine_snapshot_mm.game_mm = game_mm;
4006 engine_snapshot_mm.laser = laser;
4008 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4010 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4012 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4013 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4014 engine_snapshot_mm.Box[x][y] = Box[x][y];
4015 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4019 engine_snapshot_mm.LX = LX;
4020 engine_snapshot_mm.LY = LY;
4021 engine_snapshot_mm.XS = XS;
4022 engine_snapshot_mm.YS = YS;
4023 engine_snapshot_mm.ELX = ELX;
4024 engine_snapshot_mm.ELY = ELY;
4025 engine_snapshot_mm.CT = CT;
4026 engine_snapshot_mm.Ct = Ct;
4028 engine_snapshot_mm.last_LX = last_LX;
4029 engine_snapshot_mm.last_LY = last_LY;
4030 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4031 engine_snapshot_mm.hold_x = hold_x;
4032 engine_snapshot_mm.hold_y = hold_y;
4033 engine_snapshot_mm.pacman_nr = pacman_nr;
4035 engine_snapshot_mm.rotate_delay = rotate_delay;
4036 engine_snapshot_mm.pacman_delay = pacman_delay;
4037 engine_snapshot_mm.energy_delay = energy_delay;
4038 engine_snapshot_mm.overload_delay = overload_delay;
4041 void LoadEngineSnapshotValues_MM(void)
4045 // stored engine snapshot buffers already restored at this point
4047 game_mm = engine_snapshot_mm.game_mm;
4048 laser = engine_snapshot_mm.laser;
4050 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4052 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4054 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4055 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4056 Box[x][y] = engine_snapshot_mm.Box[x][y];
4057 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4061 LX = engine_snapshot_mm.LX;
4062 LY = engine_snapshot_mm.LY;
4063 XS = engine_snapshot_mm.XS;
4064 YS = engine_snapshot_mm.YS;
4065 ELX = engine_snapshot_mm.ELX;
4066 ELY = engine_snapshot_mm.ELY;
4067 CT = engine_snapshot_mm.CT;
4068 Ct = engine_snapshot_mm.Ct;
4070 last_LX = engine_snapshot_mm.last_LX;
4071 last_LY = engine_snapshot_mm.last_LY;
4072 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4073 hold_x = engine_snapshot_mm.hold_x;
4074 hold_y = engine_snapshot_mm.hold_y;
4075 pacman_nr = engine_snapshot_mm.pacman_nr;
4077 rotate_delay = engine_snapshot_mm.rotate_delay;
4078 pacman_delay = engine_snapshot_mm.pacman_delay;
4079 energy_delay = engine_snapshot_mm.energy_delay;
4080 overload_delay = engine_snapshot_mm.overload_delay;
4082 RedrawPlayfield_MM();
4085 static int getAngleFromTouchDelta(int dx, int dy, int base)
4087 double pi = 3.141592653;
4088 double rad = atan2((double)-dy, (double)dx);
4089 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4090 double deg = rad2 * 180.0 / pi;
4092 return (int)(deg * base / 360.0 + 0.5) % base;
4095 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4097 // calculate start (source) position to be at the middle of the tile
4098 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4099 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4100 int dx = dst_mx - src_mx;
4101 int dy = dst_my - src_my;
4110 if (!IN_LEV_FIELD(x, y))
4113 element = Tile[x][y];
4115 if (!IS_MCDUFFIN(element) &&
4116 !IS_MIRROR(element) &&
4117 !IS_BEAMER(element) &&
4118 !IS_POLAR(element) &&
4119 !IS_POLAR_CROSS(element) &&
4120 !IS_DF_MIRROR(element))
4123 angle_old = get_element_angle(element);
4125 if (IS_MCDUFFIN(element))
4127 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4128 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4129 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4130 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4133 else if (IS_MIRROR(element) ||
4134 IS_DF_MIRROR(element))
4136 for (i = 0; i < laser.num_damages; i++)
4138 if (laser.damage[i].x == x &&
4139 laser.damage[i].y == y &&
4140 ObjHit(x, y, HIT_POS_CENTER))
4142 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4143 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4150 if (angle_new == -1)
4152 if (IS_MIRROR(element) ||
4153 IS_DF_MIRROR(element) ||
4157 if (IS_POLAR_CROSS(element))
4160 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4163 button = (angle_new == angle_old ? 0 :
4164 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4165 MB_LEFTBUTTON : MB_RIGHTBUTTON);