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 },
2904 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2905 { MV_RIGHT, MV_LEFT, MV_UP }
2908 int element = Tile[x][y];
2909 int old_move_dir = MovDir[x][y];
2910 int right_dir = turn[old_move_dir].right;
2911 int back_dir = turn[old_move_dir].back;
2912 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2913 int right_x = x + right_dx, right_y = y + right_dy;
2915 if (element == EL_PACMAN)
2917 boolean can_turn_right = FALSE;
2919 if (IN_LEV_FIELD(right_x, right_y) &&
2920 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2921 can_turn_right = TRUE;
2924 MovDir[x][y] = right_dir;
2926 MovDir[x][y] = back_dir;
2932 static void StartMoving_MM(int x, int y)
2934 int element = Tile[x][y];
2939 if (CAN_MOVE(element))
2943 if (MovDelay[x][y]) // wait some time before next movement
2951 // now make next step
2953 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2955 if (element == EL_PACMAN &&
2956 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2957 !ObjHit(newx, newy, HIT_POS_CENTER))
2959 Store[newx][newy] = Tile[newx][newy];
2960 Tile[newx][newy] = EL_EMPTY;
2962 DrawField_MM(newx, newy);
2964 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2965 ObjHit(newx, newy, HIT_POS_CENTER))
2967 // object was running against a wall
2974 InitMovingField_MM(x, y, MovDir[x][y]);
2978 ContinueMoving_MM(x, y);
2981 static void ContinueMoving_MM(int x, int y)
2983 int element = Tile[x][y];
2984 int direction = MovDir[x][y];
2985 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2986 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2987 int horiz_move = (dx!=0);
2988 int newx = x + dx, newy = y + dy;
2989 int step = (horiz_move ? dx : dy) * TILEX / 8;
2991 MovPos[x][y] += step;
2993 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2995 Tile[x][y] = EL_EMPTY;
2996 Tile[newx][newy] = element;
2998 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2999 MovDelay[newx][newy] = 0;
3001 if (!CAN_MOVE(element))
3002 MovDir[newx][newy] = 0;
3005 DrawField_MM(newx, newy);
3007 Stop[newx][newy] = TRUE;
3009 if (element == EL_PACMAN)
3011 if (Store[newx][newy] == EL_BOMB)
3012 Bang_MM(newx, newy);
3014 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3015 (LX + 2 * XS) / TILEX == newx &&
3016 (LY + 2 * YS) / TILEY == newy)
3023 else // still moving on
3028 laser.redraw = TRUE;
3031 boolean ClickElement(int x, int y, int button)
3033 static DelayCounter click_delay = { CLICK_DELAY };
3034 static boolean new_button = TRUE;
3035 boolean element_clicked = FALSE;
3040 // initialize static variables
3041 click_delay.count = 0;
3042 click_delay.value = CLICK_DELAY;
3048 // do not rotate objects hit by the laser after the game was solved
3049 if (game_mm.level_solved && Hit[x][y])
3052 if (button == MB_RELEASED)
3055 click_delay.value = CLICK_DELAY;
3057 // release eventually hold auto-rotating mirror
3058 RotateMirror(x, y, MB_RELEASED);
3063 if (!FrameReached(&click_delay) && !new_button)
3066 if (button == MB_MIDDLEBUTTON) // middle button has no function
3069 if (!IN_LEV_FIELD(x, y))
3072 if (Tile[x][y] == EL_EMPTY)
3075 element = Tile[x][y];
3077 if (IS_MIRROR(element) ||
3078 IS_BEAMER(element) ||
3079 IS_POLAR(element) ||
3080 IS_POLAR_CROSS(element) ||
3081 IS_DF_MIRROR(element) ||
3082 IS_DF_MIRROR_AUTO(element))
3084 RotateMirror(x, y, button);
3086 element_clicked = TRUE;
3088 else if (IS_MCDUFFIN(element))
3090 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3092 if (has_laser && !laser.fuse_off)
3093 DrawLaser(0, DL_LASER_DISABLED);
3095 element = get_rotated_element(element, BUTTON_ROTATION(button));
3097 Tile[x][y] = element;
3102 laser.start_angle = get_element_angle(element);
3106 if (!laser.fuse_off)
3110 element_clicked = TRUE;
3112 else if (element == EL_FUSE_ON && laser.fuse_off)
3114 if (x != laser.fuse_x || y != laser.fuse_y)
3117 laser.fuse_off = FALSE;
3118 laser.fuse_x = laser.fuse_y = -1;
3120 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3123 element_clicked = TRUE;
3125 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3127 laser.fuse_off = TRUE;
3130 laser.overloaded = FALSE;
3132 DrawLaser(0, DL_LASER_DISABLED);
3133 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3135 element_clicked = TRUE;
3137 else if (element == EL_LIGHTBALL)
3140 RaiseScoreElement_MM(element);
3141 DrawLaser(0, DL_LASER_ENABLED);
3143 element_clicked = TRUE;
3145 else if (IS_ENVELOPE(element))
3147 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3149 element_clicked = TRUE;
3152 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3155 return element_clicked;
3158 static void RotateMirror(int x, int y, int button)
3160 if (button == MB_RELEASED)
3162 // release eventually hold auto-rotating mirror
3169 if (IS_MIRROR(Tile[x][y]) ||
3170 IS_POLAR_CROSS(Tile[x][y]) ||
3171 IS_POLAR(Tile[x][y]) ||
3172 IS_BEAMER(Tile[x][y]) ||
3173 IS_DF_MIRROR(Tile[x][y]) ||
3174 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3175 IS_GRID_WOOD_AUTO(Tile[x][y]))
3177 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3179 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3181 if (button == MB_LEFTBUTTON)
3183 // left mouse button only for manual adjustment, no auto-rotating;
3184 // freeze mirror for until mouse button released
3188 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3190 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3194 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3196 int edge = Hit[x][y];
3202 DrawLaser(edge - 1, DL_LASER_DISABLED);
3206 else if (ObjHit(x, y, HIT_POS_CENTER))
3208 int edge = Hit[x][y];
3212 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3217 DrawLaser(edge - 1, DL_LASER_DISABLED);
3224 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3229 if ((IS_BEAMER(Tile[x][y]) ||
3230 IS_POLAR(Tile[x][y]) ||
3231 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3233 if (IS_BEAMER(Tile[x][y]))
3236 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3237 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3250 DrawLaser(0, DL_LASER_ENABLED);
3254 static void AutoRotateMirrors(void)
3258 if (!FrameReached(&rotate_delay))
3261 for (x = 0; x < lev_fieldx; x++)
3263 for (y = 0; y < lev_fieldy; y++)
3265 int element = Tile[x][y];
3267 // do not rotate objects hit by the laser after the game was solved
3268 if (game_mm.level_solved && Hit[x][y])
3271 if (IS_DF_MIRROR_AUTO(element) ||
3272 IS_GRID_WOOD_AUTO(element) ||
3273 IS_GRID_STEEL_AUTO(element) ||
3274 element == EL_REFRACTOR)
3275 RotateMirror(x, y, MB_RIGHTBUTTON);
3280 static boolean ObjHit(int obx, int oby, int bits)
3287 if (bits & HIT_POS_CENTER)
3289 if (CheckLaserPixel(cSX + obx + 15,
3294 if (bits & HIT_POS_EDGE)
3296 for (i = 0; i < 4; i++)
3297 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3298 cSY + oby + 31 * (i / 2)))
3302 if (bits & HIT_POS_BETWEEN)
3304 for (i = 0; i < 4; i++)
3305 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3306 cSY + 4 + oby + 22 * (i / 2)))
3313 static void DeletePacMan(int px, int py)
3319 if (game_mm.num_pacman <= 1)
3321 game_mm.num_pacman = 0;
3325 for (i = 0; i < game_mm.num_pacman; i++)
3326 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3329 game_mm.num_pacman--;
3331 for (j = i; j < game_mm.num_pacman; j++)
3333 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3334 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3335 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3339 static void GameActions_MM_Ext(void)
3346 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3349 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3351 element = Tile[x][y];
3353 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3354 StartMoving_MM(x, y);
3355 else if (IS_MOVING(x, y))
3356 ContinueMoving_MM(x, y);
3357 else if (IS_EXPLODING(element))
3358 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3359 else if (element == EL_EXIT_OPENING)
3361 else if (element == EL_GRAY_BALL_OPENING)
3363 else if (IS_ENVELOPE_OPENING(element))
3365 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3367 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3369 else if (IS_MIRROR(element) ||
3370 IS_MIRROR_FIXED(element) ||
3371 element == EL_PRISM)
3372 DrawFieldTwinkle(x, y);
3373 else if (element == EL_GRAY_BALL_ACTIVE ||
3374 element == EL_BOMB_ACTIVE ||
3375 element == EL_MINE_ACTIVE)
3376 DrawFieldAnimated_MM(x, y);
3377 else if (!IS_BLOCKED(x, y))
3378 DrawFieldAnimatedIfNeeded_MM(x, y);
3381 AutoRotateMirrors();
3384 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3386 // redraw after Explode_MM() ...
3388 DrawLaser(0, DL_LASER_ENABLED);
3389 laser.redraw = FALSE;
3394 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3398 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3400 DrawLaser(0, DL_LASER_DISABLED);
3405 // skip all following game actions if game is over
3406 if (game_mm.game_over)
3409 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3413 GameOver_MM(GAME_OVER_NO_ENERGY);
3418 if (FrameReached(&energy_delay))
3420 if (game_mm.energy_left > 0)
3421 game_mm.energy_left--;
3423 // when out of energy, wait another frame to play "out of time" sound
3426 element = laser.dest_element;
3429 if (element != Tile[ELX][ELY])
3431 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3432 element, Tile[ELX][ELY]);
3436 if (!laser.overloaded && laser.overload_value == 0 &&
3437 element != EL_BOMB &&
3438 element != EL_BOMB_ACTIVE &&
3439 element != EL_MINE &&
3440 element != EL_MINE_ACTIVE &&
3441 element != EL_GRAY_BALL &&
3442 element != EL_GRAY_BALL_ACTIVE &&
3443 element != EL_BLOCK_STONE &&
3444 element != EL_BLOCK_WOOD &&
3445 element != EL_FUSE_ON &&
3446 element != EL_FUEL_FULL &&
3447 !IS_WALL_ICE(element) &&
3448 !IS_WALL_AMOEBA(element))
3451 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3453 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3454 (!laser.overloaded && laser.overload_value > 0)) &&
3455 FrameReached(&overload_delay))
3457 if (laser.overloaded)
3458 laser.overload_value++;
3460 laser.overload_value--;
3462 if (game_mm.cheat_no_overload)
3464 laser.overloaded = FALSE;
3465 laser.overload_value = 0;
3468 game_mm.laser_overload_value = laser.overload_value;
3470 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3472 SetLaserColor(0xFF);
3474 DrawLaser(0, DL_LASER_ENABLED);
3477 if (!laser.overloaded)
3478 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3479 else if (setup.sound_loops)
3480 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3482 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3484 if (laser.overload_value == MAX_LASER_OVERLOAD)
3486 UpdateAndDisplayGameControlValues();
3490 GameOver_MM(GAME_OVER_OVERLOADED);
3501 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3503 if (game_mm.cheat_no_explosion)
3508 laser.dest_element = EL_EXPLODING_OPAQUE;
3513 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3515 laser.fuse_off = TRUE;
3519 DrawLaser(0, DL_LASER_DISABLED);
3520 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3523 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3525 if (!Store2[ELX][ELY]) // check if content element not yet determined
3527 int last_anim_random_frame = gfx.anim_random_frame;
3530 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3531 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3533 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3534 native_mm_level.ball_choice_mode, 0,
3535 game_mm.ball_choice_pos);
3537 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3538 gfx.anim_random_frame = last_anim_random_frame;
3540 game_mm.ball_choice_pos++;
3542 int new_element = native_mm_level.ball_content[element_pos];
3543 int new_element_base = map_wall_to_base_element(new_element);
3545 if (IS_WALL(new_element_base))
3547 // always use completely filled wall element
3548 new_element = new_element_base | 0x000f;
3550 else if (native_mm_level.rotate_ball_content &&
3551 get_num_elements(new_element) > 1)
3553 // randomly rotate newly created game element
3554 new_element = get_rotated_element(new_element, RND(16));
3557 Store[ELX][ELY] = new_element;
3558 Store2[ELX][ELY] = TRUE;
3561 if (native_mm_level.explode_ball)
3564 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3566 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3571 if (IS_WALL_ICE(element) && CT > 50)
3573 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3575 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3576 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3577 Store2[ELX][ELY] = laser.wall_mask;
3579 laser.dest_element = Tile[ELX][ELY];
3584 if (IS_WALL_AMOEBA(element) && CT > 60)
3587 int element2 = Tile[ELX][ELY];
3589 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3592 for (i = laser.num_damages - 1; i >= 0; i--)
3593 if (laser.damage[i].is_mirror)
3596 r = laser.num_edges;
3597 d = laser.num_damages;
3604 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3607 DrawLaser(0, DL_LASER_ENABLED);
3610 x = laser.damage[k1].x;
3611 y = laser.damage[k1].y;
3616 for (i = 0; i < 4; i++)
3618 if (laser.wall_mask & (1 << i))
3620 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3621 cSY + ELY * TILEY + 31 * (i / 2)))
3624 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3625 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3632 for (i = 0; i < 4; i++)
3634 if (laser.wall_mask & (1 << i))
3636 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3637 cSY + ELY * TILEY + 31 * (i / 2)))
3644 if (laser.num_beamers > 0 ||
3645 k1 < 1 || k2 < 4 || k3 < 4 ||
3646 CheckLaserPixel(cSX + ELX * TILEX + 14,
3647 cSY + ELY * TILEY + 14))
3649 laser.num_edges = r;
3650 laser.num_damages = d;
3652 DrawLaser(0, DL_LASER_DISABLED);
3655 Tile[ELX][ELY] = element | laser.wall_mask;
3657 int x = ELX, y = ELY;
3658 int wall_mask = laser.wall_mask;
3661 DrawLaser(0, DL_LASER_ENABLED);
3663 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3665 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3666 Store[x][y] = EL_WALL_AMOEBA_BASE;
3667 Store2[x][y] = wall_mask;
3672 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3673 laser.stops_inside_element && CT > native_mm_level.time_block)
3678 if (ABS(XS) > ABS(YS))
3685 for (i = 0; i < 4; i++)
3692 x = ELX + Step[k * 4].x;
3693 y = ELY + Step[k * 4].y;
3695 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3698 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3706 laser.overloaded = (element == EL_BLOCK_STONE);
3711 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3714 Tile[x][y] = element;
3716 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3719 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3721 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3722 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3730 if (element == EL_FUEL_FULL && CT > 10)
3732 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3733 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3735 for (i = start; i <= num_init_game_frames; i++)
3737 if (i == num_init_game_frames)
3738 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3739 else if (setup.sound_loops)
3740 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3742 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3744 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3746 UpdateAndDisplayGameControlValues();
3751 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3753 DrawField_MM(ELX, ELY);
3755 DrawLaser(0, DL_LASER_ENABLED);
3761 void GameActions_MM(struct MouseActionInfo action)
3763 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3764 boolean button_released = (action.button == MB_RELEASED);
3766 GameActions_MM_Ext();
3768 CheckSingleStepMode_MM(element_clicked, button_released);
3771 static void MovePacMen(void)
3773 int mx, my, ox, oy, nx, ny;
3777 if (++pacman_nr >= game_mm.num_pacman)
3780 game_mm.pacman[pacman_nr].dir--;
3782 for (l = 1; l < 5; l++)
3784 game_mm.pacman[pacman_nr].dir++;
3786 if (game_mm.pacman[pacman_nr].dir > 4)
3787 game_mm.pacman[pacman_nr].dir = 1;
3789 if (game_mm.pacman[pacman_nr].dir % 2)
3792 my = game_mm.pacman[pacman_nr].dir - 2;
3797 mx = 3 - game_mm.pacman[pacman_nr].dir;
3800 ox = game_mm.pacman[pacman_nr].x;
3801 oy = game_mm.pacman[pacman_nr].y;
3804 element = Tile[nx][ny];
3806 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3809 if (!IS_EATABLE4PACMAN(element))
3812 if (ObjHit(nx, ny, HIT_POS_CENTER))
3815 Tile[ox][oy] = EL_EMPTY;
3817 EL_PACMAN_RIGHT - 1 +
3818 (game_mm.pacman[pacman_nr].dir - 1 +
3819 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3821 game_mm.pacman[pacman_nr].x = nx;
3822 game_mm.pacman[pacman_nr].y = ny;
3824 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3826 if (element != EL_EMPTY)
3828 int graphic = el2gfx(Tile[nx][ny]);
3833 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3836 ox = cSX + ox * TILEX;
3837 oy = cSY + oy * TILEY;
3839 for (i = 1; i < 33; i += 2)
3840 BlitBitmap(bitmap, window,
3841 src_x, src_y, TILEX, TILEY,
3842 ox + i * mx, oy + i * my);
3843 Ct = Ct + FrameCounter - CT;
3846 DrawField_MM(nx, ny);
3849 if (!laser.fuse_off)
3851 DrawLaser(0, DL_LASER_ENABLED);
3853 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3855 AddDamagedField(nx, ny);
3857 laser.damage[laser.num_damages - 1].edge = 0;
3861 if (element == EL_BOMB)
3862 DeletePacMan(nx, ny);
3864 if (IS_WALL_AMOEBA(element) &&
3865 (LX + 2 * XS) / TILEX == nx &&
3866 (LY + 2 * YS) / TILEY == ny)
3876 static void InitMovingField_MM(int x, int y, int direction)
3878 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3879 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3881 MovDir[x][y] = direction;
3882 MovDir[newx][newy] = direction;
3884 if (Tile[newx][newy] == EL_EMPTY)
3885 Tile[newx][newy] = EL_BLOCKED;
3888 static int MovingOrBlocked2Element_MM(int x, int y)
3890 int element = Tile[x][y];
3892 if (element == EL_BLOCKED)
3896 Blocked2Moving(x, y, &oldx, &oldy);
3898 return Tile[oldx][oldy];
3904 static void RemoveMovingField_MM(int x, int y)
3906 int oldx = x, oldy = y, newx = x, newy = y;
3908 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3911 if (IS_MOVING(x, y))
3913 Moving2Blocked(x, y, &newx, &newy);
3914 if (Tile[newx][newy] != EL_BLOCKED)
3917 else if (Tile[x][y] == EL_BLOCKED)
3919 Blocked2Moving(x, y, &oldx, &oldy);
3920 if (!IS_MOVING(oldx, oldy))
3924 Tile[oldx][oldy] = EL_EMPTY;
3925 Tile[newx][newy] = EL_EMPTY;
3926 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3927 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3929 DrawLevelField_MM(oldx, oldy);
3930 DrawLevelField_MM(newx, newy);
3933 static void RaiseScore_MM(int value)
3935 game_mm.score += value;
3938 void RaiseScoreElement_MM(int element)
3943 case EL_PACMAN_RIGHT:
3945 case EL_PACMAN_LEFT:
3946 case EL_PACMAN_DOWN:
3947 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3951 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3956 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3960 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3969 // ----------------------------------------------------------------------------
3970 // Mirror Magic game engine snapshot handling functions
3971 // ----------------------------------------------------------------------------
3973 void SaveEngineSnapshotValues_MM(void)
3977 engine_snapshot_mm.game_mm = game_mm;
3978 engine_snapshot_mm.laser = laser;
3980 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3982 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3984 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3985 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3986 engine_snapshot_mm.Box[x][y] = Box[x][y];
3987 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3991 engine_snapshot_mm.LX = LX;
3992 engine_snapshot_mm.LY = LY;
3993 engine_snapshot_mm.XS = XS;
3994 engine_snapshot_mm.YS = YS;
3995 engine_snapshot_mm.ELX = ELX;
3996 engine_snapshot_mm.ELY = ELY;
3997 engine_snapshot_mm.CT = CT;
3998 engine_snapshot_mm.Ct = Ct;
4000 engine_snapshot_mm.last_LX = last_LX;
4001 engine_snapshot_mm.last_LY = last_LY;
4002 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4003 engine_snapshot_mm.hold_x = hold_x;
4004 engine_snapshot_mm.hold_y = hold_y;
4005 engine_snapshot_mm.pacman_nr = pacman_nr;
4007 engine_snapshot_mm.rotate_delay = rotate_delay;
4008 engine_snapshot_mm.pacman_delay = pacman_delay;
4009 engine_snapshot_mm.energy_delay = energy_delay;
4010 engine_snapshot_mm.overload_delay = overload_delay;
4013 void LoadEngineSnapshotValues_MM(void)
4017 // stored engine snapshot buffers already restored at this point
4019 game_mm = engine_snapshot_mm.game_mm;
4020 laser = engine_snapshot_mm.laser;
4022 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4024 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4026 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4027 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4028 Box[x][y] = engine_snapshot_mm.Box[x][y];
4029 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4033 LX = engine_snapshot_mm.LX;
4034 LY = engine_snapshot_mm.LY;
4035 XS = engine_snapshot_mm.XS;
4036 YS = engine_snapshot_mm.YS;
4037 ELX = engine_snapshot_mm.ELX;
4038 ELY = engine_snapshot_mm.ELY;
4039 CT = engine_snapshot_mm.CT;
4040 Ct = engine_snapshot_mm.Ct;
4042 last_LX = engine_snapshot_mm.last_LX;
4043 last_LY = engine_snapshot_mm.last_LY;
4044 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4045 hold_x = engine_snapshot_mm.hold_x;
4046 hold_y = engine_snapshot_mm.hold_y;
4047 pacman_nr = engine_snapshot_mm.pacman_nr;
4049 rotate_delay = engine_snapshot_mm.rotate_delay;
4050 pacman_delay = engine_snapshot_mm.pacman_delay;
4051 energy_delay = engine_snapshot_mm.energy_delay;
4052 overload_delay = engine_snapshot_mm.overload_delay;
4054 RedrawPlayfield_MM();
4057 static int getAngleFromTouchDelta(int dx, int dy, int base)
4059 double pi = 3.141592653;
4060 double rad = atan2((double)-dy, (double)dx);
4061 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4062 double deg = rad2 * 180.0 / pi;
4064 return (int)(deg * base / 360.0 + 0.5) % base;
4067 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4069 // calculate start (source) position to be at the middle of the tile
4070 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4071 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4072 int dx = dst_mx - src_mx;
4073 int dy = dst_my - src_my;
4082 if (!IN_LEV_FIELD(x, y))
4085 element = Tile[x][y];
4087 if (!IS_MCDUFFIN(element) &&
4088 !IS_MIRROR(element) &&
4089 !IS_BEAMER(element) &&
4090 !IS_POLAR(element) &&
4091 !IS_POLAR_CROSS(element) &&
4092 !IS_DF_MIRROR(element))
4095 angle_old = get_element_angle(element);
4097 if (IS_MCDUFFIN(element))
4099 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4100 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4101 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4102 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4105 else if (IS_MIRROR(element) ||
4106 IS_DF_MIRROR(element))
4108 for (i = 0; i < laser.num_damages; i++)
4110 if (laser.damage[i].x == x &&
4111 laser.damage[i].y == y &&
4112 ObjHit(x, y, HIT_POS_CENTER))
4114 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4115 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4122 if (angle_new == -1)
4124 if (IS_MIRROR(element) ||
4125 IS_DF_MIRROR(element) ||
4129 if (IS_POLAR_CROSS(element))
4132 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4135 button = (angle_new == angle_old ? 0 :
4136 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4137 MB_LEFTBUTTON : MB_RIGHTBUTTON);