1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
101 static void AddLaserEdge(int, int);
102 static void ScanLaser(void);
103 static void DrawLaser(int, int);
104 static boolean HitElement(int, int);
105 static boolean HitOnlyAnEdge(int);
106 static boolean HitPolarizer(int, int);
107 static boolean HitBlock(int, int);
108 static boolean HitLaserSource(int, int);
109 static boolean HitLaserDestination(int, int);
110 static boolean HitReflectingWalls(int, int);
111 static boolean HitAbsorbingWalls(int, int);
112 static void RotateMirror(int, int, int);
113 static boolean ObjHit(int, int, int);
114 static void DeletePacMan(int, int);
115 static void MovePacMen(void);
117 // bitmap for laser beam detection
118 static Bitmap *laser_bitmap = NULL;
120 // variables for laser control
121 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
122 static int hold_x = -1, hold_y = -1;
124 // variables for pacman control
125 static int pacman_nr = -1;
127 // various game engine delay counters
128 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
129 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
130 static DelayCounter energy_delay = { ENERGY_DELAY };
131 static DelayCounter overload_delay = { 0 };
133 // element mask positions for scanning pixels of MM elements
134 #define MM_MASK_MCDUFFIN_RIGHT 0
135 #define MM_MASK_MCDUFFIN_UP 1
136 #define MM_MASK_MCDUFFIN_LEFT 2
137 #define MM_MASK_MCDUFFIN_DOWN 3
138 #define MM_MASK_GRID_1 4
139 #define MM_MASK_GRID_2 5
140 #define MM_MASK_GRID_3 6
141 #define MM_MASK_GRID_4 7
142 #define MM_MASK_RECTANGLE 8
143 #define MM_MASK_CIRCLE 9
145 #define NUM_MM_MASKS 10
147 // element masks for scanning pixels of MM elements
148 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
332 static int get_element_angle(int element)
334 int element_phase = get_element_phase(element);
336 if (IS_MIRROR_FIXED(element) ||
337 IS_MCDUFFIN(element) ||
339 IS_RECEIVER(element))
340 return 4 * element_phase;
342 return element_phase;
345 static int get_opposite_angle(int angle)
347 int opposite_angle = angle + ANG_RAY_180;
349 // make sure "opposite_angle" is in valid interval [0, 15]
350 return (opposite_angle + 16) % 16;
353 static int get_mirrored_angle(int laser_angle, int mirror_angle)
355 int reflected_angle = 16 - laser_angle + mirror_angle;
357 // make sure "reflected_angle" is in valid interval [0, 15]
358 return (reflected_angle + 16) % 16;
361 static void DrawLaserLines(struct XY *points, int num_points, int mode)
363 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
364 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
366 DrawLines(drawto_mm, points, num_points, pixel_drawto);
370 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
375 static boolean CheckLaserPixel(int x, int y)
381 pixel = ReadPixel(laser_bitmap, x, y);
385 return (pixel == WHITE_PIXEL);
388 static void CheckExitMM(void)
390 int exit_element = EL_EMPTY;
394 static int xy[4][2] =
402 for (y = 0; y < lev_fieldy; y++)
404 for (x = 0; x < lev_fieldx; x++)
406 if (Tile[x][y] == EL_EXIT_CLOSED)
408 // initiate opening animation of exit door
409 Tile[x][y] = EL_EXIT_OPENING;
411 exit_element = EL_EXIT_OPEN;
415 else if (IS_RECEIVER(Tile[x][y]))
417 // remove field that blocks receiver
418 int phase = Tile[x][y] - EL_RECEIVER_START;
419 int blocking_x, blocking_y;
421 blocking_x = x + xy[phase][0];
422 blocking_y = y + xy[phase][1];
424 if (IN_LEV_FIELD(blocking_x, blocking_y))
426 Tile[blocking_x][blocking_y] = EL_EMPTY;
428 DrawField_MM(blocking_x, blocking_y);
431 exit_element = EL_RECEIVER;
438 if (exit_element != EL_EMPTY)
439 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
442 static void SetLaserColor(int brightness)
444 int color_min = 0x00;
445 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
446 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
447 int color_down = color_max - color_up;
450 GetPixelFromRGB(window,
451 (game_mm.laser_red ? color_max : color_up),
452 (game_mm.laser_green ? color_down : color_min),
453 (game_mm.laser_blue ? color_down : color_min));
456 static void InitMovDir_MM(int x, int y)
458 int element = Tile[x][y];
459 static int direction[3][4] =
461 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
462 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
463 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
468 case EL_PACMAN_RIGHT:
472 Tile[x][y] = EL_PACMAN;
473 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
481 static void InitField(int x, int y, boolean init_game)
483 int element = Tile[x][y];
488 Tile[x][y] = EL_EMPTY;
493 if (init_game && native_mm_level.auto_count_kettles)
494 game_mm.kettles_still_needed++;
497 case EL_LIGHTBULB_OFF:
498 game_mm.lights_still_needed++;
502 if (IS_MIRROR(element) ||
503 IS_BEAMER_OLD(element) ||
504 IS_BEAMER(element) ||
506 IS_POLAR_CROSS(element) ||
507 IS_DF_MIRROR(element) ||
508 IS_DF_MIRROR_AUTO(element) ||
509 IS_GRID_STEEL_AUTO(element) ||
510 IS_GRID_WOOD_AUTO(element) ||
511 IS_FIBRE_OPTIC(element))
513 if (IS_BEAMER_OLD(element))
515 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
516 element = Tile[x][y];
519 if (!IS_FIBRE_OPTIC(element))
521 static int steps_grid_auto = 0;
523 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
524 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
526 if (IS_GRID_STEEL_AUTO(element) ||
527 IS_GRID_WOOD_AUTO(element))
528 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
530 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
532 game_mm.cycle[game_mm.num_cycle].x = x;
533 game_mm.cycle[game_mm.num_cycle].y = y;
537 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
539 int beamer_nr = BEAMER_NR(element);
540 int nr = laser.beamer[beamer_nr][0].num;
542 laser.beamer[beamer_nr][nr].x = x;
543 laser.beamer[beamer_nr][nr].y = y;
544 laser.beamer[beamer_nr][nr].num = 1;
547 else if (IS_PACMAN(element))
551 else if (IS_MCDUFFIN(element) || IS_LASER(element))
555 laser.start_edge.x = x;
556 laser.start_edge.y = y;
557 laser.start_angle = get_element_angle(element);
560 if (IS_MCDUFFIN(element))
562 game_mm.laser_red = native_mm_level.mm_laser_red;
563 game_mm.laser_green = native_mm_level.mm_laser_green;
564 game_mm.laser_blue = native_mm_level.mm_laser_blue;
568 game_mm.laser_red = native_mm_level.df_laser_red;
569 game_mm.laser_green = native_mm_level.df_laser_green;
570 game_mm.laser_blue = native_mm_level.df_laser_blue;
573 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
580 static void InitCycleElements_RotateSingleStep(void)
584 if (game_mm.num_cycle == 0) // no elements to cycle
587 for (i = 0; i < game_mm.num_cycle; i++)
589 int x = game_mm.cycle[i].x;
590 int y = game_mm.cycle[i].y;
591 int step = SIGN(game_mm.cycle[i].steps);
592 int last_element = Tile[x][y];
593 int next_element = get_rotated_element(last_element, step);
595 if (!game_mm.cycle[i].steps)
598 Tile[x][y] = next_element;
600 game_mm.cycle[i].steps -= step;
604 static void InitLaser(void)
606 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
607 int step = (IS_LASER(start_element) ? 4 : 0);
609 LX = laser.start_edge.x * TILEX;
610 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
613 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
615 LY = laser.start_edge.y * TILEY;
616 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
617 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
621 XS = 2 * Step[laser.start_angle].x;
622 YS = 2 * Step[laser.start_angle].y;
624 laser.current_angle = laser.start_angle;
626 laser.num_damages = 0;
628 laser.num_beamers = 0;
629 laser.beamer_edge[0] = 0;
631 laser.dest_element = EL_EMPTY;
634 AddLaserEdge(LX, LY); // set laser starting edge
639 void InitGameEngine_MM(void)
645 // initialize laser bitmap to current playfield (screen) size
646 ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
647 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
651 // set global game control values
652 game_mm.num_cycle = 0;
653 game_mm.num_pacman = 0;
656 game_mm.energy_left = 0; // later set to "native_mm_level.time"
657 game_mm.kettles_still_needed =
658 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
659 game_mm.lights_still_needed = 0;
660 game_mm.num_keys = 0;
661 game_mm.ball_choice_pos = 0;
663 game_mm.laser_red = FALSE;
664 game_mm.laser_green = FALSE;
665 game_mm.laser_blue = TRUE;
666 game_mm.has_mcduffin = TRUE;
668 game_mm.level_solved = FALSE;
669 game_mm.game_over = FALSE;
670 game_mm.game_over_cause = 0;
671 game_mm.game_over_message = NULL;
673 game_mm.laser_overload_value = 0;
674 game_mm.laser_enabled = FALSE;
676 // set global laser control values (must be set before "InitLaser()")
677 laser.start_edge.x = 0;
678 laser.start_edge.y = 0;
679 laser.start_angle = 0;
681 for (i = 0; i < MAX_NUM_BEAMERS; i++)
682 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
684 laser.overloaded = FALSE;
685 laser.overload_value = 0;
686 laser.fuse_off = FALSE;
687 laser.fuse_x = laser.fuse_y = -1;
689 laser.dest_element = EL_EMPTY;
690 laser.dest_element_last = EL_EMPTY;
691 laser.dest_element_last_x = -1;
692 laser.dest_element_last_y = -1;
706 rotate_delay.count = 0;
707 pacman_delay.count = 0;
708 energy_delay.count = 0;
709 overload_delay.count = 0;
711 ClickElement(-1, -1, -1);
713 for (x = 0; x < lev_fieldx; x++)
715 for (y = 0; y < lev_fieldy; y++)
717 Tile[x][y] = Ur[x][y];
718 Hit[x][y] = Box[x][y] = 0;
720 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
721 Store[x][y] = Store2[x][y] = 0;
724 InitField(x, y, TRUE);
731 void InitGameActions_MM(void)
733 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
734 int cycle_steps_done = 0;
739 for (i = 0; i <= num_init_game_frames; i++)
741 if (i == num_init_game_frames)
742 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
743 else if (setup.sound_loops)
744 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
746 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
748 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
750 UpdateAndDisplayGameControlValues();
752 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
754 InitCycleElements_RotateSingleStep();
759 AdvanceFrameCounter();
767 if (setup.quick_doors)
774 if (game_mm.kettles_still_needed == 0)
777 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
778 SetTileCursorActive(TRUE);
780 // restart all delay counters after initially cycling game elements
781 ResetFrameCounter(&rotate_delay);
782 ResetFrameCounter(&pacman_delay);
783 ResetFrameCounter(&energy_delay);
784 ResetFrameCounter(&overload_delay);
787 static void FadeOutLaser(void)
791 for (i = 15; i >= 0; i--)
793 SetLaserColor(0x11 * i);
795 DrawLaser(0, DL_LASER_ENABLED);
798 Delay_WithScreenUpdates(50);
801 DrawLaser(0, DL_LASER_DISABLED);
803 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
806 static void GameOver_MM(int game_over_cause)
808 game_mm.game_over = TRUE;
809 game_mm.game_over_cause = game_over_cause;
810 game_mm.game_over_message = (game_mm.has_mcduffin ?
811 (game_over_cause == GAME_OVER_BOMB ?
812 "Bomb killed Mc Duffin!" :
813 game_over_cause == GAME_OVER_NO_ENERGY ?
814 "Out of magic energy!" :
815 game_over_cause == GAME_OVER_OVERLOADED ?
816 "Magic spell hit Mc Duffin!" :
818 (game_over_cause == GAME_OVER_BOMB ?
819 "Bomb destroyed laser cannon!" :
820 game_over_cause == GAME_OVER_NO_ENERGY ?
821 "Out of laser energy!" :
822 game_over_cause == GAME_OVER_OVERLOADED ?
823 "Laser beam hit laser cannon!" :
826 SetTileCursorActive(FALSE);
829 static void AddLaserEdge(int lx, int ly)
834 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
836 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
841 laser.edge[laser.num_edges].x = cSX2 + lx;
842 laser.edge[laser.num_edges].y = cSY2 + ly;
848 static void AddDamagedField(int ex, int ey)
850 // prevent adding the same field position again
851 if (laser.num_damages > 0 &&
852 laser.damage[laser.num_damages - 1].x == ex &&
853 laser.damage[laser.num_damages - 1].y == ey &&
854 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
857 laser.damage[laser.num_damages].is_mirror = FALSE;
858 laser.damage[laser.num_damages].angle = laser.current_angle;
859 laser.damage[laser.num_damages].edge = laser.num_edges;
860 laser.damage[laser.num_damages].x = ex;
861 laser.damage[laser.num_damages].y = ey;
865 static boolean StepBehind(void)
871 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
872 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
874 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
880 static int getMaskFromElement(int element)
882 if (IS_GRID(element))
883 return MM_MASK_GRID_1 + get_element_phase(element);
884 else if (IS_MCDUFFIN(element))
885 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
886 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
887 return MM_MASK_RECTANGLE;
889 return MM_MASK_CIRCLE;
892 static int ScanPixel(void)
897 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
898 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
901 // follow laser beam until it hits something (at least the screen border)
902 while (hit_mask == HIT_MASK_NO_HIT)
908 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
909 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
911 Debug("game:mm:ScanPixel", "touched screen border!");
917 // check if laser scan has crossed element boundaries (not just mini tiles)
918 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
919 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
921 if (cross_x && cross_y)
923 int elx1 = (LX - XS) / TILEX;
924 int ely1 = (LY + YS) / TILEY;
925 int elx2 = (LX + XS) / TILEX;
926 int ely2 = (LY - YS) / TILEY;
928 // add element corners left and right from the laser beam to damage list
930 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
931 AddDamagedField(elx1, ely1);
933 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
934 AddDamagedField(elx2, ely2);
937 for (i = 0; i < 4; i++)
939 int px = LX + (i % 2) * 2;
940 int py = LY + (i / 2) * 2;
943 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
944 int ly = (py + TILEY) / TILEY - 1; // negative values!
947 if (IN_LEV_FIELD(lx, ly))
949 int element = Tile[lx][ly];
951 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
955 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
957 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
959 pixel = ((element & (1 << pos)) ? 1 : 0);
963 int pos = getMaskFromElement(element);
965 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
970 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
971 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
974 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
975 hit_mask |= (1 << i);
978 if (hit_mask == HIT_MASK_NO_HIT)
980 // hit nothing -- go on with another step
989 static void DeactivateLaserTargetElement(void)
991 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
992 laser.dest_element_last == EL_MINE_ACTIVE ||
993 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
994 laser.dest_element_last == EL_GRAY_BALL_OPENING)
996 int x = laser.dest_element_last_x;
997 int y = laser.dest_element_last_y;
998 int element = laser.dest_element_last;
1000 if (Tile[x][y] == element)
1001 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1002 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1004 if (Tile[x][y] == EL_GRAY_BALL)
1007 laser.dest_element_last = EL_EMPTY;
1008 laser.dest_element_last_x = -1;
1009 laser.dest_element_last_y = -1;
1013 static void ScanLaser(void)
1015 int element = EL_EMPTY;
1016 int last_element = EL_EMPTY;
1017 int end = 0, rf = laser.num_edges;
1019 // do not scan laser again after the game was lost for whatever reason
1020 if (game_mm.game_over)
1023 // do not scan laser if fuse is off
1027 DeactivateLaserTargetElement();
1029 laser.overloaded = FALSE;
1030 laser.stops_inside_element = FALSE;
1032 DrawLaser(0, DL_LASER_ENABLED);
1035 Debug("game:mm:ScanLaser",
1036 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1044 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1047 laser.overloaded = TRUE;
1052 hit_mask = ScanPixel();
1055 Debug("game:mm:ScanLaser",
1056 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1060 // hit something -- check out what it was
1061 ELX = (LX + XS) / TILEX;
1062 ELY = (LY + YS) / TILEY;
1065 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1066 hit_mask, LX, LY, ELX, ELY);
1069 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1072 laser.dest_element = element;
1077 // check if laser scan has hit two diagonally adjacent element corners
1078 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1079 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1081 // check if laser scan has crossed element boundaries (not just mini tiles)
1082 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1083 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1085 // handle special case of laser hitting two diagonally adjacent elements
1086 // (with or without a third corner element behind these two elements)
1087 if ((diag_1 || diag_2) && cross_x && cross_y)
1089 // compare the two diagonally adjacent elements
1091 int yoffset = 2 * (diag_1 ? -1 : +1);
1092 int elx1 = (LX - xoffset) / TILEX;
1093 int ely1 = (LY + yoffset) / TILEY;
1094 int elx2 = (LX + xoffset) / TILEX;
1095 int ely2 = (LY - yoffset) / TILEY;
1096 int e1 = Tile[elx1][ely1];
1097 int e2 = Tile[elx2][ely2];
1098 boolean use_element_1 = FALSE;
1100 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1102 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1103 use_element_1 = (RND(2) ? TRUE : FALSE);
1104 else if (IS_WALL_ICE(e1))
1105 use_element_1 = TRUE;
1107 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1109 // if both tiles match, we can just select the first one
1110 if (IS_WALL_AMOEBA(e1))
1111 use_element_1 = TRUE;
1113 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1115 // if both tiles match, we can just select the first one
1116 if (IS_ABSORBING_BLOCK(e1))
1117 use_element_1 = TRUE;
1120 ELX = (use_element_1 ? elx1 : elx2);
1121 ELY = (use_element_1 ? ely1 : ely2);
1125 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1126 hit_mask, LX, LY, ELX, ELY);
1129 last_element = element;
1131 element = Tile[ELX][ELY];
1132 laser.dest_element = element;
1135 Debug("game:mm:ScanLaser",
1136 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1139 LX % TILEX, LY % TILEY,
1144 if (!IN_LEV_FIELD(ELX, ELY))
1145 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1149 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1150 if (element == EL_EMPTY &&
1151 IS_GRID_STEEL(last_element) &&
1152 laser.current_angle % 4) // angle is not 90°
1153 element = last_element;
1155 if (element == EL_EMPTY)
1157 if (!HitOnlyAnEdge(hit_mask))
1160 else if (element == EL_FUSE_ON)
1162 if (HitPolarizer(element, hit_mask))
1165 else if (IS_GRID(element) || IS_DF_GRID(element))
1167 if (HitPolarizer(element, hit_mask))
1170 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1171 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1173 if (HitBlock(element, hit_mask))
1180 else if (IS_MCDUFFIN(element))
1182 if (HitLaserSource(element, hit_mask))
1185 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1186 IS_RECEIVER(element))
1188 if (HitLaserDestination(element, hit_mask))
1191 else if (IS_WALL(element))
1193 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1195 if (HitReflectingWalls(element, hit_mask))
1200 if (HitAbsorbingWalls(element, hit_mask))
1206 if (HitElement(element, hit_mask))
1211 DrawLaser(rf - 1, DL_LASER_ENABLED);
1212 rf = laser.num_edges;
1214 if (!IS_DF_WALL_STEEL(element))
1216 // only used for scanning DF steel walls; reset for all other elements
1224 if (laser.dest_element != Tile[ELX][ELY])
1226 Debug("game:mm:ScanLaser",
1227 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1228 laser.dest_element, Tile[ELX][ELY]);
1232 if (!end && !laser.stops_inside_element && !StepBehind())
1235 Debug("game:mm:ScanLaser", "Go one step back");
1241 AddLaserEdge(LX, LY);
1245 DrawLaser(rf - 1, DL_LASER_ENABLED);
1247 Ct = CT = FrameCounter;
1250 if (!IN_LEV_FIELD(ELX, ELY))
1251 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1255 static void ScanLaser_FromLastMirror(void)
1257 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1260 for (i = start_pos; i >= 0; i--)
1261 if (laser.damage[i].is_mirror)
1264 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1266 DrawLaser(start_edge, DL_LASER_DISABLED);
1271 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1277 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1278 start_edge, num_edges, mode);
1283 Warn("DrawLaserExt: start_edge < 0");
1290 Warn("DrawLaserExt: num_edges < 0");
1296 if (mode == DL_LASER_DISABLED)
1298 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1302 // now draw the laser to the backbuffer and (if enabled) to the screen
1303 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1305 redraw_mask |= REDRAW_FIELD;
1307 if (mode == DL_LASER_ENABLED)
1310 // after the laser was deleted, the "damaged" graphics must be restored
1311 if (laser.num_damages)
1313 int damage_start = 0;
1316 // determine the starting edge, from which graphics need to be restored
1319 for (i = 0; i < laser.num_damages; i++)
1321 if (laser.damage[i].edge == start_edge + 1)
1330 // restore graphics from this starting edge to the end of damage list
1331 for (i = damage_start; i < laser.num_damages; i++)
1333 int lx = laser.damage[i].x;
1334 int ly = laser.damage[i].y;
1335 int element = Tile[lx][ly];
1337 if (Hit[lx][ly] == laser.damage[i].edge)
1338 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1341 if (Box[lx][ly] == laser.damage[i].edge)
1344 if (IS_DRAWABLE(element))
1345 DrawField_MM(lx, ly);
1348 elx = laser.damage[damage_start].x;
1349 ely = laser.damage[damage_start].y;
1350 element = Tile[elx][ely];
1353 if (IS_BEAMER(element))
1357 for (i = 0; i < laser.num_beamers; i++)
1358 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1360 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1361 mode, elx, ely, Hit[elx][ely], start_edge);
1362 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1363 get_element_angle(element), laser.damage[damage_start].angle);
1367 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1368 laser.num_beamers > 0 &&
1369 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1371 // element is outgoing beamer
1372 laser.num_damages = damage_start + 1;
1374 if (IS_BEAMER(element))
1375 laser.current_angle = get_element_angle(element);
1379 // element is incoming beamer or other element
1380 laser.num_damages = damage_start;
1381 laser.current_angle = laser.damage[laser.num_damages].angle;
1386 // no damages but McDuffin himself (who needs to be redrawn anyway)
1388 elx = laser.start_edge.x;
1389 ely = laser.start_edge.y;
1390 element = Tile[elx][ely];
1393 laser.num_edges = start_edge + 1;
1394 if (start_edge == 0)
1395 laser.current_angle = laser.start_angle;
1397 LX = laser.edge[start_edge].x - cSX2;
1398 LY = laser.edge[start_edge].y - cSY2;
1399 XS = 2 * Step[laser.current_angle].x;
1400 YS = 2 * Step[laser.current_angle].y;
1403 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1409 if (IS_BEAMER(element) ||
1410 IS_FIBRE_OPTIC(element) ||
1411 IS_PACMAN(element) ||
1412 IS_POLAR(element) ||
1413 IS_POLAR_CROSS(element) ||
1414 element == EL_FUSE_ON)
1419 Debug("game:mm:DrawLaserExt", "element == %d", element);
1422 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1423 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1427 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1428 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1429 (laser.num_beamers == 0 ||
1430 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1432 // element is incoming beamer or other element
1433 step_size = -step_size;
1438 if (IS_BEAMER(element))
1439 Debug("game:mm:DrawLaserExt",
1440 "start_edge == %d, laser.beamer_edge == %d",
1441 start_edge, laser.beamer_edge);
1444 LX += step_size * XS;
1445 LY += step_size * YS;
1447 else if (element != EL_EMPTY)
1456 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1461 void DrawLaser(int start_edge, int mode)
1463 // do not draw laser if fuse is off
1464 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1467 if (mode == DL_LASER_DISABLED)
1468 DeactivateLaserTargetElement();
1470 if (laser.num_edges - start_edge < 0)
1472 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1477 // check if laser is interrupted by beamer element
1478 if (laser.num_beamers > 0 &&
1479 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1481 if (mode == DL_LASER_ENABLED)
1484 int tmp_start_edge = start_edge;
1486 // draw laser segments forward from the start to the last beamer
1487 for (i = 0; i < laser.num_beamers; i++)
1489 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1491 if (tmp_num_edges <= 0)
1495 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1496 i, laser.beamer_edge[i], tmp_start_edge);
1499 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1501 tmp_start_edge = laser.beamer_edge[i];
1504 // draw last segment from last beamer to the end
1505 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1511 int last_num_edges = laser.num_edges;
1512 int num_beamers = laser.num_beamers;
1514 // delete laser segments backward from the end to the first beamer
1515 for (i = num_beamers - 1; i >= 0; i--)
1517 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1519 if (laser.beamer_edge[i] - start_edge <= 0)
1522 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1524 last_num_edges = laser.beamer_edge[i];
1525 laser.num_beamers--;
1529 if (last_num_edges - start_edge <= 0)
1530 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1531 last_num_edges, start_edge);
1534 // special case when rotating first beamer: delete laser edge on beamer
1535 // (but do not start scanning on previous edge to prevent mirror sound)
1536 if (last_num_edges - start_edge == 1 && start_edge > 0)
1537 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1539 // delete first segment from start to the first beamer
1540 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1545 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1548 game_mm.laser_enabled = mode;
1551 void DrawLaser_MM(void)
1553 DrawLaser(0, game_mm.laser_enabled);
1556 static boolean HitElement(int element, int hit_mask)
1558 if (HitOnlyAnEdge(hit_mask))
1561 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1562 element = MovingOrBlocked2Element_MM(ELX, ELY);
1565 Debug("game:mm:HitElement", "(1): element == %d", element);
1569 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1570 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1573 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1577 AddDamagedField(ELX, ELY);
1579 // this is more precise: check if laser would go through the center
1580 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1584 // prevent cutting through laser emitter with laser beam
1585 if (IS_LASER(element))
1588 // skip the whole element before continuing the scan
1596 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1598 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1600 /* skipping scan positions to the right and down skips one scan
1601 position too much, because this is only the top left scan position
1602 of totally four scan positions (plus one to the right, one to the
1603 bottom and one to the bottom right) */
1604 /* ... but only roll back scan position if more than one step done */
1614 Debug("game:mm:HitElement", "(2): element == %d", element);
1617 if (LX + 5 * XS < 0 ||
1627 Debug("game:mm:HitElement", "(3): element == %d", element);
1630 if (IS_POLAR(element) &&
1631 ((element - EL_POLAR_START) % 2 ||
1632 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1634 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1636 laser.num_damages--;
1641 if (IS_POLAR_CROSS(element) &&
1642 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1644 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1646 laser.num_damages--;
1651 if (!IS_BEAMER(element) &&
1652 !IS_FIBRE_OPTIC(element) &&
1653 !IS_GRID_WOOD(element) &&
1654 element != EL_FUEL_EMPTY)
1657 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1658 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1660 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1663 LX = ELX * TILEX + 14;
1664 LY = ELY * TILEY + 14;
1666 AddLaserEdge(LX, LY);
1669 if (IS_MIRROR(element) ||
1670 IS_MIRROR_FIXED(element) ||
1671 IS_POLAR(element) ||
1672 IS_POLAR_CROSS(element) ||
1673 IS_DF_MIRROR(element) ||
1674 IS_DF_MIRROR_AUTO(element) ||
1675 element == EL_PRISM ||
1676 element == EL_REFRACTOR)
1678 int current_angle = laser.current_angle;
1681 laser.num_damages--;
1683 AddDamagedField(ELX, ELY);
1685 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1688 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1690 if (IS_MIRROR(element) ||
1691 IS_MIRROR_FIXED(element) ||
1692 IS_DF_MIRROR(element) ||
1693 IS_DF_MIRROR_AUTO(element))
1694 laser.current_angle = get_mirrored_angle(laser.current_angle,
1695 get_element_angle(element));
1697 if (element == EL_PRISM || element == EL_REFRACTOR)
1698 laser.current_angle = RND(16);
1700 XS = 2 * Step[laser.current_angle].x;
1701 YS = 2 * Step[laser.current_angle].y;
1703 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1708 LX += step_size * XS;
1709 LY += step_size * YS;
1711 // draw sparkles on mirror
1712 if ((IS_MIRROR(element) ||
1713 IS_MIRROR_FIXED(element) ||
1714 element == EL_PRISM) &&
1715 current_angle != laser.current_angle)
1717 MovDelay[ELX][ELY] = 11; // start animation
1720 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1721 current_angle != laser.current_angle)
1722 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1725 (get_opposite_angle(laser.current_angle) ==
1726 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1728 return (laser.overloaded ? TRUE : FALSE);
1731 if (element == EL_FUEL_FULL)
1733 laser.stops_inside_element = TRUE;
1738 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1740 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1742 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1743 element == EL_MINE ? EL_MINE_ACTIVE :
1744 EL_GRAY_BALL_ACTIVE);
1746 GfxFrame[ELX][ELY] = 0; // restart animation
1748 laser.dest_element_last = Tile[ELX][ELY];
1749 laser.dest_element_last_x = ELX;
1750 laser.dest_element_last_y = ELY;
1752 if (element == EL_MINE)
1753 laser.overloaded = TRUE;
1756 if (element == EL_KETTLE ||
1757 element == EL_CELL ||
1758 element == EL_KEY ||
1759 element == EL_LIGHTBALL ||
1760 element == EL_PACMAN ||
1761 IS_PACMAN(element) ||
1762 IS_ENVELOPE(element))
1764 if (!IS_PACMAN(element) &&
1765 !IS_ENVELOPE(element))
1768 if (element == EL_PACMAN)
1771 if (element == EL_KETTLE || element == EL_CELL)
1773 if (game_mm.kettles_still_needed > 0)
1774 game_mm.kettles_still_needed--;
1776 game.snapshot.collected_item = TRUE;
1778 if (game_mm.kettles_still_needed == 0)
1782 DrawLaser(0, DL_LASER_ENABLED);
1785 else if (element == EL_KEY)
1789 else if (IS_PACMAN(element))
1791 DeletePacMan(ELX, ELY);
1793 else if (IS_ENVELOPE(element))
1795 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1798 RaiseScoreElement_MM(element);
1803 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1805 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1807 DrawLaser(0, DL_LASER_ENABLED);
1809 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1811 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1812 game_mm.lights_still_needed--;
1816 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1817 game_mm.lights_still_needed++;
1820 DrawField_MM(ELX, ELY);
1821 DrawLaser(0, DL_LASER_ENABLED);
1826 laser.stops_inside_element = TRUE;
1832 Debug("game:mm:HitElement", "(4): element == %d", element);
1835 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1836 laser.num_beamers < MAX_NUM_BEAMERS &&
1837 laser.beamer[BEAMER_NR(element)][1].num)
1839 int beamer_angle = get_element_angle(element);
1840 int beamer_nr = BEAMER_NR(element);
1844 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1847 laser.num_damages--;
1849 if (IS_FIBRE_OPTIC(element) ||
1850 laser.current_angle == get_opposite_angle(beamer_angle))
1854 LX = ELX * TILEX + 14;
1855 LY = ELY * TILEY + 14;
1857 AddLaserEdge(LX, LY);
1858 AddDamagedField(ELX, ELY);
1860 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1863 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1865 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1866 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1867 ELX = laser.beamer[beamer_nr][pos].x;
1868 ELY = laser.beamer[beamer_nr][pos].y;
1869 LX = ELX * TILEX + 14;
1870 LY = ELY * TILEY + 14;
1872 if (IS_BEAMER(element))
1874 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1875 XS = 2 * Step[laser.current_angle].x;
1876 YS = 2 * Step[laser.current_angle].y;
1879 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1881 AddLaserEdge(LX, LY);
1882 AddDamagedField(ELX, ELY);
1884 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1887 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1889 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1894 LX += step_size * XS;
1895 LY += step_size * YS;
1897 laser.num_beamers++;
1906 static boolean HitOnlyAnEdge(int hit_mask)
1908 // check if the laser hit only the edge of an element and, if so, go on
1911 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1915 if ((hit_mask == HIT_MASK_TOPLEFT ||
1916 hit_mask == HIT_MASK_TOPRIGHT ||
1917 hit_mask == HIT_MASK_BOTTOMLEFT ||
1918 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1919 laser.current_angle % 4) // angle is not 90°
1923 if (hit_mask == HIT_MASK_TOPLEFT)
1928 else if (hit_mask == HIT_MASK_TOPRIGHT)
1933 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1938 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1944 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1950 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1957 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1963 static boolean HitPolarizer(int element, int hit_mask)
1965 if (HitOnlyAnEdge(hit_mask))
1968 if (IS_DF_GRID(element))
1970 int grid_angle = get_element_angle(element);
1973 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1974 grid_angle, laser.current_angle);
1977 AddLaserEdge(LX, LY);
1978 AddDamagedField(ELX, ELY);
1981 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1983 if (laser.current_angle == grid_angle ||
1984 laser.current_angle == get_opposite_angle(grid_angle))
1986 // skip the whole element before continuing the scan
1992 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1994 if (LX/TILEX > ELX || LY/TILEY > ELY)
1996 /* skipping scan positions to the right and down skips one scan
1997 position too much, because this is only the top left scan position
1998 of totally four scan positions (plus one to the right, one to the
1999 bottom and one to the bottom right) */
2005 AddLaserEdge(LX, LY);
2011 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2013 LX / TILEX, LY / TILEY,
2014 LX % TILEX, LY % TILEY);
2019 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2021 return HitReflectingWalls(element, hit_mask);
2025 return HitAbsorbingWalls(element, hit_mask);
2028 else if (IS_GRID_STEEL(element))
2030 // may be required if graphics for steel grid redefined
2031 AddDamagedField(ELX, ELY);
2033 return HitReflectingWalls(element, hit_mask);
2035 else // IS_GRID_WOOD
2037 // may be required if graphics for wooden grid redefined
2038 AddDamagedField(ELX, ELY);
2040 return HitAbsorbingWalls(element, hit_mask);
2046 static boolean HitBlock(int element, int hit_mask)
2048 boolean check = FALSE;
2050 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2051 game_mm.num_keys == 0)
2054 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2057 int ex = ELX * TILEX + 14;
2058 int ey = ELY * TILEY + 14;
2062 for (i = 1; i < 32; i++)
2067 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2072 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2073 return HitAbsorbingWalls(element, hit_mask);
2077 AddLaserEdge(LX - XS, LY - YS);
2078 AddDamagedField(ELX, ELY);
2081 Box[ELX][ELY] = laser.num_edges;
2083 return HitReflectingWalls(element, hit_mask);
2086 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2088 int xs = XS / 2, ys = YS / 2;
2090 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2091 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2093 laser.overloaded = (element == EL_GATE_STONE);
2098 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2099 (hit_mask == HIT_MASK_TOP ||
2100 hit_mask == HIT_MASK_LEFT ||
2101 hit_mask == HIT_MASK_RIGHT ||
2102 hit_mask == HIT_MASK_BOTTOM))
2103 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2104 hit_mask == HIT_MASK_BOTTOM),
2105 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2106 hit_mask == HIT_MASK_RIGHT));
2107 AddLaserEdge(LX, LY);
2113 if (element == EL_GATE_STONE && Box[ELX][ELY])
2115 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2127 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2129 int xs = XS / 2, ys = YS / 2;
2131 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2132 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2134 laser.overloaded = (element == EL_BLOCK_STONE);
2139 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2140 (hit_mask == HIT_MASK_TOP ||
2141 hit_mask == HIT_MASK_LEFT ||
2142 hit_mask == HIT_MASK_RIGHT ||
2143 hit_mask == HIT_MASK_BOTTOM))
2144 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2145 hit_mask == HIT_MASK_BOTTOM),
2146 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2147 hit_mask == HIT_MASK_RIGHT));
2148 AddDamagedField(ELX, ELY);
2150 LX = ELX * TILEX + 14;
2151 LY = ELY * TILEY + 14;
2153 AddLaserEdge(LX, LY);
2155 laser.stops_inside_element = TRUE;
2163 static boolean HitLaserSource(int element, int hit_mask)
2165 if (HitOnlyAnEdge(hit_mask))
2168 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2170 laser.overloaded = TRUE;
2175 static boolean HitLaserDestination(int element, int hit_mask)
2177 if (HitOnlyAnEdge(hit_mask))
2180 if (element != EL_EXIT_OPEN &&
2181 !(IS_RECEIVER(element) &&
2182 game_mm.kettles_still_needed == 0 &&
2183 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2185 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2190 if (IS_RECEIVER(element) ||
2191 (IS_22_5_ANGLE(laser.current_angle) &&
2192 (ELX != (LX + 6 * XS) / TILEX ||
2193 ELY != (LY + 6 * YS) / TILEY ||
2202 LX = ELX * TILEX + 14;
2203 LY = ELY * TILEY + 14;
2205 laser.stops_inside_element = TRUE;
2208 AddLaserEdge(LX, LY);
2209 AddDamagedField(ELX, ELY);
2211 if (game_mm.lights_still_needed == 0)
2213 game_mm.level_solved = TRUE;
2215 SetTileCursorActive(FALSE);
2221 static boolean HitReflectingWalls(int element, int hit_mask)
2223 // check if laser hits side of a wall with an angle that is not 90°
2224 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2225 hit_mask == HIT_MASK_LEFT ||
2226 hit_mask == HIT_MASK_RIGHT ||
2227 hit_mask == HIT_MASK_BOTTOM))
2229 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2234 if (!IS_DF_GRID(element))
2235 AddLaserEdge(LX, LY);
2237 // check if laser hits wall with an angle of 45°
2238 if (!IS_22_5_ANGLE(laser.current_angle))
2240 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2243 laser.current_angle = get_mirrored_angle(laser.current_angle,
2246 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2249 laser.current_angle = get_mirrored_angle(laser.current_angle,
2253 AddLaserEdge(LX, LY);
2255 XS = 2 * Step[laser.current_angle].x;
2256 YS = 2 * Step[laser.current_angle].y;
2260 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2262 laser.current_angle = get_mirrored_angle(laser.current_angle,
2267 if (!IS_DF_GRID(element))
2268 AddLaserEdge(LX, LY);
2273 if (!IS_DF_GRID(element))
2274 AddLaserEdge(LX, LY + YS / 2);
2277 if (!IS_DF_GRID(element))
2278 AddLaserEdge(LX, LY);
2281 YS = 2 * Step[laser.current_angle].y;
2285 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2287 laser.current_angle = get_mirrored_angle(laser.current_angle,
2292 if (!IS_DF_GRID(element))
2293 AddLaserEdge(LX, LY);
2298 if (!IS_DF_GRID(element))
2299 AddLaserEdge(LX + XS / 2, LY);
2302 if (!IS_DF_GRID(element))
2303 AddLaserEdge(LX, LY);
2306 XS = 2 * Step[laser.current_angle].x;
2312 // reflection at the edge of reflecting DF style wall
2313 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2315 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2316 hit_mask == HIT_MASK_TOPRIGHT) ||
2317 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2318 hit_mask == HIT_MASK_TOPLEFT) ||
2319 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2320 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2321 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2322 hit_mask == HIT_MASK_BOTTOMRIGHT))
2325 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2326 ANG_MIRROR_135 : ANG_MIRROR_45);
2328 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2330 AddDamagedField(ELX, ELY);
2331 AddLaserEdge(LX, LY);
2333 laser.current_angle = get_mirrored_angle(laser.current_angle,
2341 AddLaserEdge(LX, LY);
2347 // reflection inside an edge of reflecting DF style wall
2348 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2350 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2351 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2352 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2353 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2354 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2355 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2356 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2357 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2360 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2361 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2362 ANG_MIRROR_135 : ANG_MIRROR_45);
2364 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2367 AddDamagedField(ELX, ELY);
2370 AddLaserEdge(LX - XS, LY - YS);
2371 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2372 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2374 laser.current_angle = get_mirrored_angle(laser.current_angle,
2382 AddLaserEdge(LX, LY);
2388 // check if laser hits DF style wall with an angle of 90°
2389 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2391 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2392 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2393 (IS_VERT_ANGLE(laser.current_angle) &&
2394 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2396 // laser at last step touched nothing or the same side of the wall
2397 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2399 AddDamagedField(ELX, ELY);
2406 last_hit_mask = hit_mask;
2413 if (!HitOnlyAnEdge(hit_mask))
2415 laser.overloaded = TRUE;
2423 static boolean HitAbsorbingWalls(int element, int hit_mask)
2425 if (HitOnlyAnEdge(hit_mask))
2429 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2431 AddLaserEdge(LX - XS, LY - YS);
2438 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2440 AddLaserEdge(LX - XS, LY - YS);
2446 if (IS_WALL_WOOD(element) ||
2447 IS_DF_WALL_WOOD(element) ||
2448 IS_GRID_WOOD(element) ||
2449 IS_GRID_WOOD_FIXED(element) ||
2450 IS_GRID_WOOD_AUTO(element) ||
2451 element == EL_FUSE_ON ||
2452 element == EL_BLOCK_WOOD ||
2453 element == EL_GATE_WOOD)
2455 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2460 if (IS_WALL_ICE(element))
2466 // check if laser hit adjacent edges of two diagonal tiles
2467 if (ELX != lx / TILEX)
2469 if (ELY != ly / TILEY)
2472 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2473 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2475 // check if laser hits wall with an angle of 90°
2476 if (IS_90_ANGLE(laser.current_angle))
2477 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2479 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2483 for (i = 0; i < 4; i++)
2485 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2486 mask = 15 - (8 >> i);
2487 else if (ABS(XS) == 4 &&
2489 (XS > 0) == (i % 2) &&
2490 (YS < 0) == (i / 2))
2491 mask = 3 + (i / 2) * 9;
2492 else if (ABS(YS) == 4 &&
2494 (XS < 0) == (i % 2) &&
2495 (YS > 0) == (i / 2))
2496 mask = 5 + (i % 2) * 5;
2500 laser.wall_mask = mask;
2502 else if (IS_WALL_AMOEBA(element))
2504 int elx = (LX - 2 * XS) / TILEX;
2505 int ely = (LY - 2 * YS) / TILEY;
2506 int element2 = Tile[elx][ely];
2509 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2511 laser.dest_element = EL_EMPTY;
2519 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2520 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2522 if (IS_90_ANGLE(laser.current_angle))
2523 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2525 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2527 laser.wall_mask = mask;
2533 static void OpenExit(int x, int y)
2537 if (!MovDelay[x][y]) // next animation frame
2538 MovDelay[x][y] = 4 * delay;
2540 if (MovDelay[x][y]) // wait some time before next frame
2545 phase = MovDelay[x][y] / delay;
2547 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2548 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2550 if (!MovDelay[x][y])
2552 Tile[x][y] = EL_EXIT_OPEN;
2558 static void OpenGrayBall(int x, int y)
2562 if (!MovDelay[x][y]) // next animation frame
2564 if (IS_WALL(Store[x][y]))
2566 DrawWalls_MM(x, y, Store[x][y]);
2568 // copy wall tile to spare bitmap for "melting" animation
2569 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2570 TILEX, TILEY, x * TILEX, y * TILEY);
2572 DrawElement_MM(x, y, EL_GRAY_BALL);
2575 MovDelay[x][y] = 50 * delay;
2578 if (MovDelay[x][y]) // wait some time before next frame
2582 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2586 int dx = RND(26), dy = RND(26);
2588 if (IS_WALL(Store[x][y]))
2590 // copy wall tile from spare bitmap for "melting" animation
2591 bitmap = bitmap_db_field;
2597 int graphic = el2gfx(Store[x][y]);
2599 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2602 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2603 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2605 laser.redraw = TRUE;
2607 MarkTileDirty(x, y);
2610 if (!MovDelay[x][y])
2612 Tile[x][y] = Store[x][y];
2613 Store[x][y] = Store2[x][y] = 0;
2614 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2616 InitField(x, y, FALSE);
2619 ScanLaser_FromLastMirror();
2624 static void OpenEnvelope(int x, int y)
2626 int num_frames = 8; // seven frames plus final empty space
2628 if (!MovDelay[x][y]) // next animation frame
2629 MovDelay[x][y] = num_frames;
2631 if (MovDelay[x][y]) // wait some time before next frame
2633 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2637 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2639 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2640 int frame = num_frames - MovDelay[x][y] - 1;
2642 DrawGraphicAnimation_MM(x, y, graphic, frame);
2644 laser.redraw = TRUE;
2647 if (MovDelay[x][y] == 0)
2649 Tile[x][y] = EL_EMPTY;
2660 static void MeltIce(int x, int y)
2665 if (!MovDelay[x][y]) // next animation frame
2666 MovDelay[x][y] = frames * delay;
2668 if (MovDelay[x][y]) // wait some time before next frame
2671 int wall_mask = Store2[x][y];
2672 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2675 phase = frames - MovDelay[x][y] / delay - 1;
2677 if (!MovDelay[x][y])
2679 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2680 Store[x][y] = Store2[x][y] = 0;
2682 DrawWalls_MM(x, y, Tile[x][y]);
2684 if (Tile[x][y] == EL_WALL_ICE_BASE)
2685 Tile[x][y] = EL_EMPTY;
2687 ScanLaser_FromLastMirror();
2689 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2691 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2693 laser.redraw = TRUE;
2698 static void GrowAmoeba(int x, int y)
2703 if (!MovDelay[x][y]) // next animation frame
2704 MovDelay[x][y] = frames * delay;
2706 if (MovDelay[x][y]) // wait some time before next frame
2709 int wall_mask = Store2[x][y];
2710 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2713 phase = MovDelay[x][y] / delay;
2715 if (!MovDelay[x][y])
2717 Tile[x][y] = real_element;
2718 Store[x][y] = Store2[x][y] = 0;
2720 DrawWalls_MM(x, y, Tile[x][y]);
2721 DrawLaser(0, DL_LASER_ENABLED);
2723 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2725 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2730 static void DrawFieldAnimated_MM(int x, int y)
2734 laser.redraw = TRUE;
2737 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2739 int element = Tile[x][y];
2740 int graphic = el2gfx(element);
2742 if (!getGraphicInfo_NewFrame(x, y, graphic))
2747 laser.redraw = TRUE;
2750 static void DrawFieldTwinkle(int x, int y)
2752 if (MovDelay[x][y] != 0) // wait some time before next frame
2758 if (MovDelay[x][y] != 0)
2760 int graphic = IMG_TWINKLE_WHITE;
2761 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2763 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2766 laser.redraw = TRUE;
2770 static void Explode_MM(int x, int y, int phase, int mode)
2772 int num_phase = 9, delay = 2;
2773 int last_phase = num_phase * delay;
2774 int half_phase = (num_phase / 2) * delay;
2777 laser.redraw = TRUE;
2779 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2781 center_element = Tile[x][y];
2783 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2785 // put moving element to center field (and let it explode there)
2786 center_element = MovingOrBlocked2Element_MM(x, y);
2787 RemoveMovingField_MM(x, y);
2789 Tile[x][y] = center_element;
2792 if (center_element != EL_GRAY_BALL_ACTIVE)
2793 Store[x][y] = EL_EMPTY;
2794 Store2[x][y] = center_element;
2796 Tile[x][y] = EL_EXPLODING_OPAQUE;
2798 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2799 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2800 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2803 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2805 ExplodePhase[x][y] = 1;
2811 GfxFrame[x][y] = 0; // restart explosion animation
2813 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2815 center_element = Store2[x][y];
2817 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2819 Tile[x][y] = EL_EXPLODING_TRANSP;
2821 if (x == ELX && y == ELY)
2825 if (phase == last_phase)
2827 if (center_element == EL_BOMB_ACTIVE)
2829 DrawLaser(0, DL_LASER_DISABLED);
2832 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2834 laser.overloaded = FALSE;
2836 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2838 GameOver_MM(GAME_OVER_BOMB);
2841 Tile[x][y] = Store[x][y];
2843 Store[x][y] = Store2[x][y] = 0;
2844 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2846 InitField(x, y, FALSE);
2849 if (center_element == EL_GRAY_BALL_ACTIVE)
2850 ScanLaser_FromLastMirror();
2852 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2854 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2855 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2857 DrawGraphicAnimation_MM(x, y, graphic, frame);
2859 MarkTileDirty(x, y);
2863 static void Bang_MM(int x, int y)
2865 int element = Tile[x][y];
2867 if (IS_PACMAN(element))
2868 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2869 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2870 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2871 else if (element == EL_KEY)
2872 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2874 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2876 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2879 static void TurnRound(int x, int y)
2891 { 0, 0 }, { 0, 0 }, { 0, 0 },
2896 int left, right, back;
2900 { MV_DOWN, MV_UP, MV_RIGHT },
2901 { MV_UP, MV_DOWN, MV_LEFT },
2903 { MV_LEFT, MV_RIGHT, MV_DOWN },
2907 { MV_RIGHT, MV_LEFT, MV_UP }
2910 int element = Tile[x][y];
2911 int old_move_dir = MovDir[x][y];
2912 int right_dir = turn[old_move_dir].right;
2913 int back_dir = turn[old_move_dir].back;
2914 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2915 int right_x = x + right_dx, right_y = y + right_dy;
2917 if (element == EL_PACMAN)
2919 boolean can_turn_right = FALSE;
2921 if (IN_LEV_FIELD(right_x, right_y) &&
2922 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2923 can_turn_right = TRUE;
2926 MovDir[x][y] = right_dir;
2928 MovDir[x][y] = back_dir;
2934 static void StartMoving_MM(int x, int y)
2936 int element = Tile[x][y];
2941 if (CAN_MOVE(element))
2945 if (MovDelay[x][y]) // wait some time before next movement
2953 // now make next step
2955 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2957 if (element == EL_PACMAN &&
2958 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2959 !ObjHit(newx, newy, HIT_POS_CENTER))
2961 Store[newx][newy] = Tile[newx][newy];
2962 Tile[newx][newy] = EL_EMPTY;
2964 DrawField_MM(newx, newy);
2966 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2967 ObjHit(newx, newy, HIT_POS_CENTER))
2969 // object was running against a wall
2976 InitMovingField_MM(x, y, MovDir[x][y]);
2980 ContinueMoving_MM(x, y);
2983 static void ContinueMoving_MM(int x, int y)
2985 int element = Tile[x][y];
2986 int direction = MovDir[x][y];
2987 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2988 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2989 int horiz_move = (dx!=0);
2990 int newx = x + dx, newy = y + dy;
2991 int step = (horiz_move ? dx : dy) * TILEX / 8;
2993 MovPos[x][y] += step;
2995 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2997 Tile[x][y] = EL_EMPTY;
2998 Tile[newx][newy] = element;
3000 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3001 MovDelay[newx][newy] = 0;
3003 if (!CAN_MOVE(element))
3004 MovDir[newx][newy] = 0;
3007 DrawField_MM(newx, newy);
3009 Stop[newx][newy] = TRUE;
3011 if (element == EL_PACMAN)
3013 if (Store[newx][newy] == EL_BOMB)
3014 Bang_MM(newx, newy);
3016 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3017 (LX + 2 * XS) / TILEX == newx &&
3018 (LY + 2 * YS) / TILEY == newy)
3025 else // still moving on
3030 laser.redraw = TRUE;
3033 boolean ClickElement(int x, int y, int button)
3035 static DelayCounter click_delay = { CLICK_DELAY };
3036 static boolean new_button = TRUE;
3037 boolean element_clicked = FALSE;
3042 // initialize static variables
3043 click_delay.count = 0;
3044 click_delay.value = CLICK_DELAY;
3050 // do not rotate objects hit by the laser after the game was solved
3051 if (game_mm.level_solved && Hit[x][y])
3054 if (button == MB_RELEASED)
3057 click_delay.value = CLICK_DELAY;
3059 // release eventually hold auto-rotating mirror
3060 RotateMirror(x, y, MB_RELEASED);
3065 if (!FrameReached(&click_delay) && !new_button)
3068 if (button == MB_MIDDLEBUTTON) // middle button has no function
3071 if (!IN_LEV_FIELD(x, y))
3074 if (Tile[x][y] == EL_EMPTY)
3077 element = Tile[x][y];
3079 if (IS_MIRROR(element) ||
3080 IS_BEAMER(element) ||
3081 IS_POLAR(element) ||
3082 IS_POLAR_CROSS(element) ||
3083 IS_DF_MIRROR(element) ||
3084 IS_DF_MIRROR_AUTO(element))
3086 RotateMirror(x, y, button);
3088 element_clicked = TRUE;
3090 else if (IS_MCDUFFIN(element))
3092 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3094 if (has_laser && !laser.fuse_off)
3095 DrawLaser(0, DL_LASER_DISABLED);
3097 element = get_rotated_element(element, BUTTON_ROTATION(button));
3099 Tile[x][y] = element;
3104 laser.start_angle = get_element_angle(element);
3108 if (!laser.fuse_off)
3112 element_clicked = TRUE;
3114 else if (element == EL_FUSE_ON && laser.fuse_off)
3116 if (x != laser.fuse_x || y != laser.fuse_y)
3119 laser.fuse_off = FALSE;
3120 laser.fuse_x = laser.fuse_y = -1;
3122 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3125 element_clicked = TRUE;
3127 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3129 laser.fuse_off = TRUE;
3132 laser.overloaded = FALSE;
3134 DrawLaser(0, DL_LASER_DISABLED);
3135 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3137 element_clicked = TRUE;
3139 else if (element == EL_LIGHTBALL)
3142 RaiseScoreElement_MM(element);
3143 DrawLaser(0, DL_LASER_ENABLED);
3145 element_clicked = TRUE;
3147 else if (IS_ENVELOPE(element))
3149 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3151 element_clicked = TRUE;
3154 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3157 return element_clicked;
3160 static void RotateMirror(int x, int y, int button)
3162 if (button == MB_RELEASED)
3164 // release eventually hold auto-rotating mirror
3171 if (IS_MIRROR(Tile[x][y]) ||
3172 IS_POLAR_CROSS(Tile[x][y]) ||
3173 IS_POLAR(Tile[x][y]) ||
3174 IS_BEAMER(Tile[x][y]) ||
3175 IS_DF_MIRROR(Tile[x][y]) ||
3176 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3177 IS_GRID_WOOD_AUTO(Tile[x][y]))
3179 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3181 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3183 if (button == MB_LEFTBUTTON)
3185 // left mouse button only for manual adjustment, no auto-rotating;
3186 // freeze mirror for until mouse button released
3190 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3192 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3196 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3198 int edge = Hit[x][y];
3204 DrawLaser(edge - 1, DL_LASER_DISABLED);
3208 else if (ObjHit(x, y, HIT_POS_CENTER))
3210 int edge = Hit[x][y];
3214 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3219 DrawLaser(edge - 1, DL_LASER_DISABLED);
3226 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3231 if ((IS_BEAMER(Tile[x][y]) ||
3232 IS_POLAR(Tile[x][y]) ||
3233 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3235 if (IS_BEAMER(Tile[x][y]))
3238 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3239 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3252 DrawLaser(0, DL_LASER_ENABLED);
3256 static void AutoRotateMirrors(void)
3260 if (!FrameReached(&rotate_delay))
3263 for (x = 0; x < lev_fieldx; x++)
3265 for (y = 0; y < lev_fieldy; y++)
3267 int element = Tile[x][y];
3269 // do not rotate objects hit by the laser after the game was solved
3270 if (game_mm.level_solved && Hit[x][y])
3273 if (IS_DF_MIRROR_AUTO(element) ||
3274 IS_GRID_WOOD_AUTO(element) ||
3275 IS_GRID_STEEL_AUTO(element) ||
3276 element == EL_REFRACTOR)
3277 RotateMirror(x, y, MB_RIGHTBUTTON);
3282 static boolean ObjHit(int obx, int oby, int bits)
3289 if (bits & HIT_POS_CENTER)
3291 if (CheckLaserPixel(cSX + obx + 15,
3296 if (bits & HIT_POS_EDGE)
3298 for (i = 0; i < 4; i++)
3299 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3300 cSY + oby + 31 * (i / 2)))
3304 if (bits & HIT_POS_BETWEEN)
3306 for (i = 0; i < 4; i++)
3307 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3308 cSY + 4 + oby + 22 * (i / 2)))
3315 static void DeletePacMan(int px, int py)
3321 if (game_mm.num_pacman <= 1)
3323 game_mm.num_pacman = 0;
3327 for (i = 0; i < game_mm.num_pacman; i++)
3328 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3331 game_mm.num_pacman--;
3333 for (j = i; j < game_mm.num_pacman; j++)
3335 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3336 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3337 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3341 static void GameActions_MM_Ext(void)
3348 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3351 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3353 element = Tile[x][y];
3355 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3356 StartMoving_MM(x, y);
3357 else if (IS_MOVING(x, y))
3358 ContinueMoving_MM(x, y);
3359 else if (IS_EXPLODING(element))
3360 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3361 else if (element == EL_EXIT_OPENING)
3363 else if (element == EL_GRAY_BALL_OPENING)
3365 else if (IS_ENVELOPE_OPENING(element))
3367 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3369 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3371 else if (IS_MIRROR(element) ||
3372 IS_MIRROR_FIXED(element) ||
3373 element == EL_PRISM)
3374 DrawFieldTwinkle(x, y);
3375 else if (element == EL_GRAY_BALL_ACTIVE ||
3376 element == EL_BOMB_ACTIVE ||
3377 element == EL_MINE_ACTIVE)
3378 DrawFieldAnimated_MM(x, y);
3379 else if (!IS_BLOCKED(x, y))
3380 DrawFieldAnimatedIfNeeded_MM(x, y);
3383 AutoRotateMirrors();
3386 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3388 // redraw after Explode_MM() ...
3390 DrawLaser(0, DL_LASER_ENABLED);
3391 laser.redraw = FALSE;
3396 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3400 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3402 DrawLaser(0, DL_LASER_DISABLED);
3407 // skip all following game actions if game is over
3408 if (game_mm.game_over)
3411 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3415 GameOver_MM(GAME_OVER_NO_ENERGY);
3420 if (FrameReached(&energy_delay))
3422 if (game_mm.energy_left > 0)
3423 game_mm.energy_left--;
3425 // when out of energy, wait another frame to play "out of time" sound
3428 element = laser.dest_element;
3431 if (element != Tile[ELX][ELY])
3433 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3434 element, Tile[ELX][ELY]);
3438 if (!laser.overloaded && laser.overload_value == 0 &&
3439 element != EL_BOMB &&
3440 element != EL_BOMB_ACTIVE &&
3441 element != EL_MINE &&
3442 element != EL_MINE_ACTIVE &&
3443 element != EL_GRAY_BALL &&
3444 element != EL_GRAY_BALL_ACTIVE &&
3445 element != EL_BLOCK_STONE &&
3446 element != EL_BLOCK_WOOD &&
3447 element != EL_FUSE_ON &&
3448 element != EL_FUEL_FULL &&
3449 !IS_WALL_ICE(element) &&
3450 !IS_WALL_AMOEBA(element))
3453 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3455 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3456 (!laser.overloaded && laser.overload_value > 0)) &&
3457 FrameReached(&overload_delay))
3459 if (laser.overloaded)
3460 laser.overload_value++;
3462 laser.overload_value--;
3464 if (game_mm.cheat_no_overload)
3466 laser.overloaded = FALSE;
3467 laser.overload_value = 0;
3470 game_mm.laser_overload_value = laser.overload_value;
3472 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3474 SetLaserColor(0xFF);
3476 DrawLaser(0, DL_LASER_ENABLED);
3479 if (!laser.overloaded)
3480 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3481 else if (setup.sound_loops)
3482 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3484 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3486 if (laser.overload_value == MAX_LASER_OVERLOAD)
3488 UpdateAndDisplayGameControlValues();
3492 GameOver_MM(GAME_OVER_OVERLOADED);
3503 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3505 if (game_mm.cheat_no_explosion)
3510 laser.dest_element = EL_EXPLODING_OPAQUE;
3515 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3517 laser.fuse_off = TRUE;
3521 DrawLaser(0, DL_LASER_DISABLED);
3522 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3525 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3527 if (!Store2[ELX][ELY]) // check if content element not yet determined
3529 int last_anim_random_frame = gfx.anim_random_frame;
3532 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3533 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3535 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3536 native_mm_level.ball_choice_mode, 0,
3537 game_mm.ball_choice_pos);
3539 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3540 gfx.anim_random_frame = last_anim_random_frame;
3542 game_mm.ball_choice_pos++;
3544 int new_element = native_mm_level.ball_content[element_pos];
3545 int new_element_base = map_wall_to_base_element(new_element);
3547 if (IS_WALL(new_element_base))
3549 // always use completely filled wall element
3550 new_element = new_element_base | 0x000f;
3552 else if (native_mm_level.rotate_ball_content &&
3553 get_num_elements(new_element) > 1)
3555 // randomly rotate newly created game element
3556 new_element = get_rotated_element(new_element, RND(16));
3559 Store[ELX][ELY] = new_element;
3560 Store2[ELX][ELY] = TRUE;
3563 if (native_mm_level.explode_ball)
3566 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3568 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3573 if (IS_WALL_ICE(element) && CT > 50)
3575 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3577 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3578 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3579 Store2[ELX][ELY] = laser.wall_mask;
3581 laser.dest_element = Tile[ELX][ELY];
3586 if (IS_WALL_AMOEBA(element) && CT > 60)
3589 int element2 = Tile[ELX][ELY];
3591 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3594 for (i = laser.num_damages - 1; i >= 0; i--)
3595 if (laser.damage[i].is_mirror)
3598 r = laser.num_edges;
3599 d = laser.num_damages;
3606 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3609 DrawLaser(0, DL_LASER_ENABLED);
3612 x = laser.damage[k1].x;
3613 y = laser.damage[k1].y;
3618 for (i = 0; i < 4; i++)
3620 if (laser.wall_mask & (1 << i))
3622 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3623 cSY + ELY * TILEY + 31 * (i / 2)))
3626 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3627 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3634 for (i = 0; i < 4; i++)
3636 if (laser.wall_mask & (1 << i))
3638 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3639 cSY + ELY * TILEY + 31 * (i / 2)))
3646 if (laser.num_beamers > 0 ||
3647 k1 < 1 || k2 < 4 || k3 < 4 ||
3648 CheckLaserPixel(cSX + ELX * TILEX + 14,
3649 cSY + ELY * TILEY + 14))
3651 laser.num_edges = r;
3652 laser.num_damages = d;
3654 DrawLaser(0, DL_LASER_DISABLED);
3657 Tile[ELX][ELY] = element | laser.wall_mask;
3659 int x = ELX, y = ELY;
3660 int wall_mask = laser.wall_mask;
3663 DrawLaser(0, DL_LASER_ENABLED);
3665 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3667 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3668 Store[x][y] = EL_WALL_AMOEBA_BASE;
3669 Store2[x][y] = wall_mask;
3674 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3675 laser.stops_inside_element && CT > native_mm_level.time_block)
3680 if (ABS(XS) > ABS(YS))
3687 for (i = 0; i < 4; i++)
3694 x = ELX + Step[k * 4].x;
3695 y = ELY + Step[k * 4].y;
3697 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3700 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3708 laser.overloaded = (element == EL_BLOCK_STONE);
3713 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3716 Tile[x][y] = element;
3718 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3721 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3723 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3724 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3732 if (element == EL_FUEL_FULL && CT > 10)
3734 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3735 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3737 for (i = start; i <= num_init_game_frames; i++)
3739 if (i == num_init_game_frames)
3740 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3741 else if (setup.sound_loops)
3742 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3744 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3746 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3748 UpdateAndDisplayGameControlValues();
3753 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3755 DrawField_MM(ELX, ELY);
3757 DrawLaser(0, DL_LASER_ENABLED);
3763 void GameActions_MM(struct MouseActionInfo action)
3765 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3766 boolean button_released = (action.button == MB_RELEASED);
3768 GameActions_MM_Ext();
3770 CheckSingleStepMode_MM(element_clicked, button_released);
3773 static void MovePacMen(void)
3775 int mx, my, ox, oy, nx, ny;
3779 if (++pacman_nr >= game_mm.num_pacman)
3782 game_mm.pacman[pacman_nr].dir--;
3784 for (l = 1; l < 5; l++)
3786 game_mm.pacman[pacman_nr].dir++;
3788 if (game_mm.pacman[pacman_nr].dir > 4)
3789 game_mm.pacman[pacman_nr].dir = 1;
3791 if (game_mm.pacman[pacman_nr].dir % 2)
3794 my = game_mm.pacman[pacman_nr].dir - 2;
3799 mx = 3 - game_mm.pacman[pacman_nr].dir;
3802 ox = game_mm.pacman[pacman_nr].x;
3803 oy = game_mm.pacman[pacman_nr].y;
3806 element = Tile[nx][ny];
3808 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3811 if (!IS_EATABLE4PACMAN(element))
3814 if (ObjHit(nx, ny, HIT_POS_CENTER))
3817 Tile[ox][oy] = EL_EMPTY;
3819 EL_PACMAN_RIGHT - 1 +
3820 (game_mm.pacman[pacman_nr].dir - 1 +
3821 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3823 game_mm.pacman[pacman_nr].x = nx;
3824 game_mm.pacman[pacman_nr].y = ny;
3826 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3828 if (element != EL_EMPTY)
3830 int graphic = el2gfx(Tile[nx][ny]);
3835 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3838 ox = cSX + ox * TILEX;
3839 oy = cSY + oy * TILEY;
3841 for (i = 1; i < 33; i += 2)
3842 BlitBitmap(bitmap, window,
3843 src_x, src_y, TILEX, TILEY,
3844 ox + i * mx, oy + i * my);
3845 Ct = Ct + FrameCounter - CT;
3848 DrawField_MM(nx, ny);
3851 if (!laser.fuse_off)
3853 DrawLaser(0, DL_LASER_ENABLED);
3855 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3857 AddDamagedField(nx, ny);
3859 laser.damage[laser.num_damages - 1].edge = 0;
3863 if (element == EL_BOMB)
3864 DeletePacMan(nx, ny);
3866 if (IS_WALL_AMOEBA(element) &&
3867 (LX + 2 * XS) / TILEX == nx &&
3868 (LY + 2 * YS) / TILEY == ny)
3878 static void InitMovingField_MM(int x, int y, int direction)
3880 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3881 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3883 MovDir[x][y] = direction;
3884 MovDir[newx][newy] = direction;
3886 if (Tile[newx][newy] == EL_EMPTY)
3887 Tile[newx][newy] = EL_BLOCKED;
3890 static int MovingOrBlocked2Element_MM(int x, int y)
3892 int element = Tile[x][y];
3894 if (element == EL_BLOCKED)
3898 Blocked2Moving(x, y, &oldx, &oldy);
3900 return Tile[oldx][oldy];
3906 static void RemoveMovingField_MM(int x, int y)
3908 int oldx = x, oldy = y, newx = x, newy = y;
3910 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3913 if (IS_MOVING(x, y))
3915 Moving2Blocked(x, y, &newx, &newy);
3916 if (Tile[newx][newy] != EL_BLOCKED)
3919 else if (Tile[x][y] == EL_BLOCKED)
3921 Blocked2Moving(x, y, &oldx, &oldy);
3922 if (!IS_MOVING(oldx, oldy))
3926 Tile[oldx][oldy] = EL_EMPTY;
3927 Tile[newx][newy] = EL_EMPTY;
3928 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3929 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3931 DrawLevelField_MM(oldx, oldy);
3932 DrawLevelField_MM(newx, newy);
3935 static void RaiseScore_MM(int value)
3937 game_mm.score += value;
3940 void RaiseScoreElement_MM(int element)
3945 case EL_PACMAN_RIGHT:
3947 case EL_PACMAN_LEFT:
3948 case EL_PACMAN_DOWN:
3949 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3953 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3958 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3962 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3971 // ----------------------------------------------------------------------------
3972 // Mirror Magic game engine snapshot handling functions
3973 // ----------------------------------------------------------------------------
3975 void SaveEngineSnapshotValues_MM(void)
3979 engine_snapshot_mm.game_mm = game_mm;
3980 engine_snapshot_mm.laser = laser;
3982 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3984 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3986 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3987 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3988 engine_snapshot_mm.Box[x][y] = Box[x][y];
3989 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3993 engine_snapshot_mm.LX = LX;
3994 engine_snapshot_mm.LY = LY;
3995 engine_snapshot_mm.XS = XS;
3996 engine_snapshot_mm.YS = YS;
3997 engine_snapshot_mm.ELX = ELX;
3998 engine_snapshot_mm.ELY = ELY;
3999 engine_snapshot_mm.CT = CT;
4000 engine_snapshot_mm.Ct = Ct;
4002 engine_snapshot_mm.last_LX = last_LX;
4003 engine_snapshot_mm.last_LY = last_LY;
4004 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4005 engine_snapshot_mm.hold_x = hold_x;
4006 engine_snapshot_mm.hold_y = hold_y;
4007 engine_snapshot_mm.pacman_nr = pacman_nr;
4009 engine_snapshot_mm.rotate_delay = rotate_delay;
4010 engine_snapshot_mm.pacman_delay = pacman_delay;
4011 engine_snapshot_mm.energy_delay = energy_delay;
4012 engine_snapshot_mm.overload_delay = overload_delay;
4015 void LoadEngineSnapshotValues_MM(void)
4019 // stored engine snapshot buffers already restored at this point
4021 game_mm = engine_snapshot_mm.game_mm;
4022 laser = engine_snapshot_mm.laser;
4024 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4026 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4028 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4029 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4030 Box[x][y] = engine_snapshot_mm.Box[x][y];
4031 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4035 LX = engine_snapshot_mm.LX;
4036 LY = engine_snapshot_mm.LY;
4037 XS = engine_snapshot_mm.XS;
4038 YS = engine_snapshot_mm.YS;
4039 ELX = engine_snapshot_mm.ELX;
4040 ELY = engine_snapshot_mm.ELY;
4041 CT = engine_snapshot_mm.CT;
4042 Ct = engine_snapshot_mm.Ct;
4044 last_LX = engine_snapshot_mm.last_LX;
4045 last_LY = engine_snapshot_mm.last_LY;
4046 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4047 hold_x = engine_snapshot_mm.hold_x;
4048 hold_y = engine_snapshot_mm.hold_y;
4049 pacman_nr = engine_snapshot_mm.pacman_nr;
4051 rotate_delay = engine_snapshot_mm.rotate_delay;
4052 pacman_delay = engine_snapshot_mm.pacman_delay;
4053 energy_delay = engine_snapshot_mm.energy_delay;
4054 overload_delay = engine_snapshot_mm.overload_delay;
4056 RedrawPlayfield_MM();
4059 static int getAngleFromTouchDelta(int dx, int dy, int base)
4061 double pi = 3.141592653;
4062 double rad = atan2((double)-dy, (double)dx);
4063 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4064 double deg = rad2 * 180.0 / pi;
4066 return (int)(deg * base / 360.0 + 0.5) % base;
4069 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4071 // calculate start (source) position to be at the middle of the tile
4072 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4073 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4074 int dx = dst_mx - src_mx;
4075 int dy = dst_my - src_my;
4084 if (!IN_LEV_FIELD(x, y))
4087 element = Tile[x][y];
4089 if (!IS_MCDUFFIN(element) &&
4090 !IS_MIRROR(element) &&
4091 !IS_BEAMER(element) &&
4092 !IS_POLAR(element) &&
4093 !IS_POLAR_CROSS(element) &&
4094 !IS_DF_MIRROR(element))
4097 angle_old = get_element_angle(element);
4099 if (IS_MCDUFFIN(element))
4101 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4102 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4103 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4104 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4107 else if (IS_MIRROR(element) ||
4108 IS_DF_MIRROR(element))
4110 for (i = 0; i < laser.num_damages; i++)
4112 if (laser.damage[i].x == x &&
4113 laser.damage[i].y == y &&
4114 ObjHit(x, y, HIT_POS_CENTER))
4116 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4117 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4124 if (angle_new == -1)
4126 if (IS_MIRROR(element) ||
4127 IS_DF_MIRROR(element) ||
4131 if (IS_POLAR_CROSS(element))
4134 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4137 button = (angle_new == angle_old ? 0 :
4138 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4139 MB_LEFTBUTTON : MB_RIGHTBUTTON);