1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
100 static void Moving2Blocked_MM(int, int, int *, int *);
102 static void AddLaserEdge(int, int);
103 static void ScanLaser(void);
104 static void DrawLaser(int, int);
105 static boolean HitElement(int, int);
106 static boolean HitOnlyAnEdge(int);
107 static boolean HitPolarizer(int, int);
108 static boolean HitBlock(int, int);
109 static boolean HitLaserSource(int, int);
110 static boolean HitLaserDestination(int, int);
111 static boolean HitReflectingWalls(int, int);
112 static boolean HitAbsorbingWalls(int, int);
113 static void RotateMirror(int, int, int);
114 static boolean ObjHit(int, int, int);
115 static void DeletePacMan(int, int);
116 static void MovePacMen(void);
118 // bitmap for laser beam detection
119 static Bitmap *laser_bitmap = NULL;
121 // variables for laser control
122 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
123 static int hold_x = -1, hold_y = -1;
125 // variables for pacman control
126 static int pacman_nr = -1;
128 // various game engine delay counters
129 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
130 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
131 static DelayCounter energy_delay = { ENERGY_DELAY };
132 static DelayCounter overload_delay = { 0 };
134 // element mask positions for scanning pixels of MM elements
135 #define MM_MASK_MCDUFFIN_RIGHT 0
136 #define MM_MASK_MCDUFFIN_UP 1
137 #define MM_MASK_MCDUFFIN_LEFT 2
138 #define MM_MASK_MCDUFFIN_DOWN 3
139 #define MM_MASK_GRID_1 4
140 #define MM_MASK_GRID_2 5
141 #define MM_MASK_GRID_3 6
142 #define MM_MASK_GRID_4 7
143 #define MM_MASK_RECTANGLE 8
144 #define MM_MASK_CIRCLE 9
146 #define NUM_MM_MASKS 10
148 // element masks for scanning pixels of MM elements
149 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
333 static int get_element_angle(int element)
335 int element_phase = get_element_phase(element);
337 if (IS_MIRROR_FIXED(element) ||
338 IS_MCDUFFIN(element) ||
340 IS_RECEIVER(element))
341 return 4 * element_phase;
343 return element_phase;
346 static int get_opposite_angle(int angle)
348 int opposite_angle = angle + ANG_RAY_180;
350 // make sure "opposite_angle" is in valid interval [0, 15]
351 return (opposite_angle + 16) % 16;
354 static int get_mirrored_angle(int laser_angle, int mirror_angle)
356 int reflected_angle = 16 - laser_angle + mirror_angle;
358 // make sure "reflected_angle" is in valid interval [0, 15]
359 return (reflected_angle + 16) % 16;
362 static void DrawLaserLines(struct XY *points, int num_points, int mode)
364 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
365 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
367 DrawLines(drawto, points, num_points, pixel_drawto);
371 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
376 static boolean CheckLaserPixel(int x, int y)
382 pixel = ReadPixel(laser_bitmap, x, y);
386 return (pixel == WHITE_PIXEL);
389 static void CheckExitMM(void)
391 int exit_element = EL_EMPTY;
395 static int xy[4][2] =
403 for (y = 0; y < lev_fieldy; y++)
405 for (x = 0; x < lev_fieldx; x++)
407 if (Tile[x][y] == EL_EXIT_CLOSED)
409 // initiate opening animation of exit door
410 Tile[x][y] = EL_EXIT_OPENING;
412 exit_element = EL_EXIT_OPEN;
416 else if (IS_RECEIVER(Tile[x][y]))
418 // remove field that blocks receiver
419 int phase = Tile[x][y] - EL_RECEIVER_START;
420 int blocking_x, blocking_y;
422 blocking_x = x + xy[phase][0];
423 blocking_y = y + xy[phase][1];
425 if (IN_LEV_FIELD(blocking_x, blocking_y))
427 Tile[blocking_x][blocking_y] = EL_EMPTY;
429 DrawField_MM(blocking_x, blocking_y);
432 exit_element = EL_RECEIVER;
439 if (exit_element != EL_EMPTY)
440 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
443 static void SetLaserColor(int brightness)
445 int color_min = 0x00;
446 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
447 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
448 int color_down = color_max - color_up;
451 GetPixelFromRGB(window,
452 (game_mm.laser_red ? color_max : color_up),
453 (game_mm.laser_green ? color_down : color_min),
454 (game_mm.laser_blue ? color_down : color_min));
457 static void InitMovDir_MM(int x, int y)
459 int element = Tile[x][y];
460 static int direction[3][4] =
462 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
463 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
464 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
469 case EL_PACMAN_RIGHT:
473 Tile[x][y] = EL_PACMAN;
474 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
482 static void InitField(int x, int y, boolean init_game)
484 int element = Tile[x][y];
489 Tile[x][y] = EL_EMPTY;
494 if (init_game && native_mm_level.auto_count_kettles)
495 game_mm.kettles_still_needed++;
498 case EL_LIGHTBULB_OFF:
499 game_mm.lights_still_needed++;
503 if (IS_MIRROR(element) ||
504 IS_BEAMER_OLD(element) ||
505 IS_BEAMER(element) ||
507 IS_POLAR_CROSS(element) ||
508 IS_DF_MIRROR(element) ||
509 IS_DF_MIRROR_AUTO(element) ||
510 IS_GRID_STEEL_AUTO(element) ||
511 IS_GRID_WOOD_AUTO(element) ||
512 IS_FIBRE_OPTIC(element))
514 if (IS_BEAMER_OLD(element))
516 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
517 element = Tile[x][y];
520 if (!IS_FIBRE_OPTIC(element))
522 static int steps_grid_auto = 0;
524 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
525 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
527 if (IS_GRID_STEEL_AUTO(element) ||
528 IS_GRID_WOOD_AUTO(element))
529 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
531 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
533 game_mm.cycle[game_mm.num_cycle].x = x;
534 game_mm.cycle[game_mm.num_cycle].y = y;
538 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
540 int beamer_nr = BEAMER_NR(element);
541 int nr = laser.beamer[beamer_nr][0].num;
543 laser.beamer[beamer_nr][nr].x = x;
544 laser.beamer[beamer_nr][nr].y = y;
545 laser.beamer[beamer_nr][nr].num = 1;
548 else if (IS_PACMAN(element))
552 else if (IS_MCDUFFIN(element) || IS_LASER(element))
556 laser.start_edge.x = x;
557 laser.start_edge.y = y;
558 laser.start_angle = get_element_angle(element);
561 if (IS_MCDUFFIN(element))
563 game_mm.laser_red = native_mm_level.mm_laser_red;
564 game_mm.laser_green = native_mm_level.mm_laser_green;
565 game_mm.laser_blue = native_mm_level.mm_laser_blue;
569 game_mm.laser_red = native_mm_level.df_laser_red;
570 game_mm.laser_green = native_mm_level.df_laser_green;
571 game_mm.laser_blue = native_mm_level.df_laser_blue;
579 static void InitCycleElements_RotateSingleStep(void)
583 if (game_mm.num_cycle == 0) // no elements to cycle
586 for (i = 0; i < game_mm.num_cycle; i++)
588 int x = game_mm.cycle[i].x;
589 int y = game_mm.cycle[i].y;
590 int step = SIGN(game_mm.cycle[i].steps);
591 int last_element = Tile[x][y];
592 int next_element = get_rotated_element(last_element, step);
594 if (!game_mm.cycle[i].steps)
597 Tile[x][y] = next_element;
599 game_mm.cycle[i].steps -= step;
603 static void InitLaser(void)
605 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
606 int step = (IS_LASER(start_element) ? 4 : 0);
608 LX = laser.start_edge.x * TILEX;
609 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
612 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
614 LY = laser.start_edge.y * TILEY;
615 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
616 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
620 XS = 2 * Step[laser.start_angle].x;
621 YS = 2 * Step[laser.start_angle].y;
623 laser.current_angle = laser.start_angle;
625 laser.num_damages = 0;
627 laser.num_beamers = 0;
628 laser.beamer_edge[0] = 0;
630 laser.dest_element = EL_EMPTY;
633 AddLaserEdge(LX, LY); // set laser starting edge
638 void InitGameEngine_MM(void)
644 // initialize laser bitmap to current playfield (screen) size
645 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
646 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
650 // set global game control values
651 game_mm.num_cycle = 0;
652 game_mm.num_pacman = 0;
655 game_mm.energy_left = 0; // later set to "native_mm_level.time"
656 game_mm.kettles_still_needed =
657 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
658 game_mm.lights_still_needed = 0;
659 game_mm.num_keys = 0;
660 game_mm.ball_choice_pos = 0;
662 game_mm.laser_red = FALSE;
663 game_mm.laser_green = FALSE;
664 game_mm.laser_blue = TRUE;
666 game_mm.level_solved = FALSE;
667 game_mm.game_over = FALSE;
668 game_mm.game_over_cause = 0;
670 game_mm.laser_overload_value = 0;
671 game_mm.laser_enabled = FALSE;
673 // set global laser control values (must be set before "InitLaser()")
674 laser.start_edge.x = 0;
675 laser.start_edge.y = 0;
676 laser.start_angle = 0;
678 for (i = 0; i < MAX_NUM_BEAMERS; i++)
679 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
681 laser.overloaded = FALSE;
682 laser.overload_value = 0;
683 laser.fuse_off = FALSE;
684 laser.fuse_x = laser.fuse_y = -1;
686 laser.dest_element = EL_EMPTY;
687 laser.dest_element_last = EL_EMPTY;
688 laser.dest_element_last_x = -1;
689 laser.dest_element_last_y = -1;
703 rotate_delay.count = 0;
704 pacman_delay.count = 0;
705 energy_delay.count = 0;
706 overload_delay.count = 0;
708 ClickElement(-1, -1, -1);
710 for (x = 0; x < lev_fieldx; x++)
712 for (y = 0; y < lev_fieldy; y++)
714 Tile[x][y] = Ur[x][y];
715 Hit[x][y] = Box[x][y] = 0;
717 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
718 Store[x][y] = Store2[x][y] = 0;
721 InitField(x, y, TRUE);
728 void InitGameActions_MM(void)
730 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
731 int cycle_steps_done = 0;
736 for (i = 0; i <= num_init_game_frames; i++)
738 if (i == num_init_game_frames)
739 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
740 else if (setup.sound_loops)
741 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
743 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
745 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
747 UpdateAndDisplayGameControlValues();
749 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
751 InitCycleElements_RotateSingleStep();
756 AdvanceFrameCounter();
764 if (setup.quick_doors)
771 if (game_mm.kettles_still_needed == 0)
774 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
775 SetTileCursorActive(TRUE);
777 // restart all delay counters after initially cycling game elements
778 ResetFrameCounter(&rotate_delay);
779 ResetFrameCounter(&pacman_delay);
780 ResetFrameCounter(&energy_delay);
781 ResetFrameCounter(&overload_delay);
784 static void FadeOutLaser(void)
788 for (i = 15; i >= 0; i--)
790 SetLaserColor(0x11 * i);
792 DrawLaser(0, DL_LASER_ENABLED);
795 Delay_WithScreenUpdates(50);
798 DrawLaser(0, DL_LASER_DISABLED);
800 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
803 static void GameOver_MM(int game_over_cause)
805 // do not handle game over if request dialog is already active
806 if (game.request_active)
809 game_mm.game_over = TRUE;
810 game_mm.game_over_cause = game_over_cause;
812 if (setup.ask_on_game_over)
813 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
814 "Bomb killed Mc Duffin! Play it again?" :
815 game_over_cause == GAME_OVER_NO_ENERGY ?
816 "Out of magic energy! Play it again?" :
817 game_over_cause == GAME_OVER_OVERLOADED ?
818 "Magic spell hit Mc Duffin! Play it again?" :
821 SetTileCursorActive(FALSE);
824 static void AddLaserEdge(int lx, int ly)
829 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
831 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
836 laser.edge[laser.num_edges].x = cSX2 + lx;
837 laser.edge[laser.num_edges].y = cSY2 + ly;
843 static void AddDamagedField(int ex, int ey)
845 // prevent adding the same field position again
846 if (laser.num_damages > 0 &&
847 laser.damage[laser.num_damages - 1].x == ex &&
848 laser.damage[laser.num_damages - 1].y == ey &&
849 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
852 laser.damage[laser.num_damages].is_mirror = FALSE;
853 laser.damage[laser.num_damages].angle = laser.current_angle;
854 laser.damage[laser.num_damages].edge = laser.num_edges;
855 laser.damage[laser.num_damages].x = ex;
856 laser.damage[laser.num_damages].y = ey;
860 static boolean StepBehind(void)
866 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
867 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
869 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
875 static int getMaskFromElement(int element)
877 if (IS_GRID(element))
878 return MM_MASK_GRID_1 + get_element_phase(element);
879 else if (IS_MCDUFFIN(element))
880 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
881 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
882 return MM_MASK_RECTANGLE;
884 return MM_MASK_CIRCLE;
887 static int ScanPixel(void)
892 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
893 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
896 // follow laser beam until it hits something (at least the screen border)
897 while (hit_mask == HIT_MASK_NO_HIT)
903 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
904 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
906 Debug("game:mm:ScanPixel", "touched screen border!");
912 for (i = 0; i < 4; i++)
914 int px = LX + (i % 2) * 2;
915 int py = LY + (i / 2) * 2;
918 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
919 int ly = (py + TILEY) / TILEY - 1; // negative values!
922 if (IN_LEV_FIELD(lx, ly))
924 int element = Tile[lx][ly];
926 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
930 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
932 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
934 pixel = ((element & (1 << pos)) ? 1 : 0);
938 int pos = getMaskFromElement(element);
940 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
945 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
946 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
949 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
950 hit_mask |= (1 << i);
953 if (hit_mask == HIT_MASK_NO_HIT)
955 // hit nothing -- go on with another step
964 static void DeactivateLaserTargetElement(void)
966 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
967 laser.dest_element_last == EL_MINE_ACTIVE ||
968 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
969 laser.dest_element_last == EL_GRAY_BALL_OPENING)
971 int x = laser.dest_element_last_x;
972 int y = laser.dest_element_last_y;
973 int element = laser.dest_element_last;
975 if (Tile[x][y] == element)
976 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
977 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
979 if (Tile[x][y] == EL_GRAY_BALL)
982 laser.dest_element_last = EL_EMPTY;
983 laser.dest_element_last_x = -1;
984 laser.dest_element_last_y = -1;
988 static void ScanLaser(void)
990 int element = EL_EMPTY;
991 int last_element = EL_EMPTY;
992 int end = 0, rf = laser.num_edges;
994 // do not scan laser again after the game was lost for whatever reason
995 if (game_mm.game_over)
998 // do not scan laser if fuse is off
1002 DeactivateLaserTargetElement();
1004 laser.overloaded = FALSE;
1005 laser.stops_inside_element = FALSE;
1007 DrawLaser(0, DL_LASER_ENABLED);
1010 Debug("game:mm:ScanLaser",
1011 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1019 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1022 laser.overloaded = TRUE;
1027 hit_mask = ScanPixel();
1030 Debug("game:mm:ScanLaser",
1031 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1035 // hit something -- check out what it was
1036 ELX = (LX + XS) / TILEX;
1037 ELY = (LY + YS) / TILEY;
1040 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1041 hit_mask, LX, LY, ELX, ELY);
1044 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1047 laser.dest_element = element;
1052 // check if laser scan has hit two diagonally adjacent element corners
1053 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1054 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1056 // check if laser scan has crossed element boundaries (not just mini tiles)
1057 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1058 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1060 // handle special case of laser hitting two diagonally adjacent elements
1061 // (with or without a third corner element behind these two elements)
1062 if ((diag_1 || diag_2) && cross_x && cross_y)
1064 // compare the two diagonally adjacent elements
1066 int yoffset = 2 * (diag_1 ? -1 : +1);
1067 int elx1 = (LX - xoffset) / TILEX;
1068 int ely1 = (LY + yoffset) / TILEY;
1069 int elx2 = (LX + xoffset) / TILEX;
1070 int ely2 = (LY - yoffset) / TILEY;
1071 int e1 = Tile[elx1][ely1];
1072 int e2 = Tile[elx2][ely2];
1073 boolean use_element_1 = FALSE;
1075 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1077 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1078 use_element_1 = (RND(2) ? TRUE : FALSE);
1079 else if (IS_WALL_ICE(e1))
1080 use_element_1 = TRUE;
1082 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1084 // if both tiles match, we can just select the first one
1085 if (IS_WALL_AMOEBA(e1))
1086 use_element_1 = TRUE;
1088 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1090 // if both tiles match, we can just select the first one
1091 if (IS_ABSORBING_BLOCK(e1))
1092 use_element_1 = TRUE;
1095 ELX = (use_element_1 ? elx1 : elx2);
1096 ELY = (use_element_1 ? ely1 : ely2);
1100 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1101 hit_mask, LX, LY, ELX, ELY);
1104 last_element = element;
1106 element = Tile[ELX][ELY];
1107 laser.dest_element = element;
1110 Debug("game:mm:ScanLaser",
1111 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1114 LX % TILEX, LY % TILEY,
1119 if (!IN_LEV_FIELD(ELX, ELY))
1120 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1124 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1125 if (element == EL_EMPTY &&
1126 IS_GRID_STEEL(last_element) &&
1127 laser.current_angle % 4) // angle is not 90°
1128 element = last_element;
1130 if (element == EL_EMPTY)
1132 if (!HitOnlyAnEdge(hit_mask))
1135 else if (element == EL_FUSE_ON)
1137 if (HitPolarizer(element, hit_mask))
1140 else if (IS_GRID(element) || IS_DF_GRID(element))
1142 if (HitPolarizer(element, hit_mask))
1145 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1146 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1148 if (HitBlock(element, hit_mask))
1155 else if (IS_MCDUFFIN(element))
1157 if (HitLaserSource(element, hit_mask))
1160 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1161 IS_RECEIVER(element))
1163 if (HitLaserDestination(element, hit_mask))
1166 else if (IS_WALL(element))
1168 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1170 if (HitReflectingWalls(element, hit_mask))
1175 if (HitAbsorbingWalls(element, hit_mask))
1181 if (HitElement(element, hit_mask))
1186 DrawLaser(rf - 1, DL_LASER_ENABLED);
1187 rf = laser.num_edges;
1189 if (!IS_DF_WALL_STEEL(element))
1191 // only used for scanning DF steel walls; reset for all other elements
1199 if (laser.dest_element != Tile[ELX][ELY])
1201 Debug("game:mm:ScanLaser",
1202 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1203 laser.dest_element, Tile[ELX][ELY]);
1207 if (!end && !laser.stops_inside_element && !StepBehind())
1210 Debug("game:mm:ScanLaser", "Go one step back");
1216 AddLaserEdge(LX, LY);
1220 DrawLaser(rf - 1, DL_LASER_ENABLED);
1222 Ct = CT = FrameCounter;
1225 if (!IN_LEV_FIELD(ELX, ELY))
1226 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1230 static void ScanLaser_FromLastMirror(void)
1232 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1235 for (i = start_pos; i >= 0; i--)
1236 if (laser.damage[i].is_mirror)
1239 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1241 DrawLaser(start_edge, DL_LASER_DISABLED);
1246 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1252 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1253 start_edge, num_edges, mode);
1258 Warn("DrawLaserExt: start_edge < 0");
1265 Warn("DrawLaserExt: num_edges < 0");
1271 if (mode == DL_LASER_DISABLED)
1273 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1277 // now draw the laser to the backbuffer and (if enabled) to the screen
1278 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1280 redraw_mask |= REDRAW_FIELD;
1282 if (mode == DL_LASER_ENABLED)
1285 // after the laser was deleted, the "damaged" graphics must be restored
1286 if (laser.num_damages)
1288 int damage_start = 0;
1291 // determine the starting edge, from which graphics need to be restored
1294 for (i = 0; i < laser.num_damages; i++)
1296 if (laser.damage[i].edge == start_edge + 1)
1305 // restore graphics from this starting edge to the end of damage list
1306 for (i = damage_start; i < laser.num_damages; i++)
1308 int lx = laser.damage[i].x;
1309 int ly = laser.damage[i].y;
1310 int element = Tile[lx][ly];
1312 if (Hit[lx][ly] == laser.damage[i].edge)
1313 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1316 if (Box[lx][ly] == laser.damage[i].edge)
1319 if (IS_DRAWABLE(element))
1320 DrawField_MM(lx, ly);
1323 elx = laser.damage[damage_start].x;
1324 ely = laser.damage[damage_start].y;
1325 element = Tile[elx][ely];
1328 if (IS_BEAMER(element))
1332 for (i = 0; i < laser.num_beamers; i++)
1333 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1335 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1336 mode, elx, ely, Hit[elx][ely], start_edge);
1337 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1338 get_element_angle(element), laser.damage[damage_start].angle);
1342 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1343 laser.num_beamers > 0 &&
1344 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1346 // element is outgoing beamer
1347 laser.num_damages = damage_start + 1;
1349 if (IS_BEAMER(element))
1350 laser.current_angle = get_element_angle(element);
1354 // element is incoming beamer or other element
1355 laser.num_damages = damage_start;
1356 laser.current_angle = laser.damage[laser.num_damages].angle;
1361 // no damages but McDuffin himself (who needs to be redrawn anyway)
1363 elx = laser.start_edge.x;
1364 ely = laser.start_edge.y;
1365 element = Tile[elx][ely];
1368 laser.num_edges = start_edge + 1;
1369 if (start_edge == 0)
1370 laser.current_angle = laser.start_angle;
1372 LX = laser.edge[start_edge].x - cSX2;
1373 LY = laser.edge[start_edge].y - cSY2;
1374 XS = 2 * Step[laser.current_angle].x;
1375 YS = 2 * Step[laser.current_angle].y;
1378 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1384 if (IS_BEAMER(element) ||
1385 IS_FIBRE_OPTIC(element) ||
1386 IS_PACMAN(element) ||
1387 IS_POLAR(element) ||
1388 IS_POLAR_CROSS(element) ||
1389 element == EL_FUSE_ON)
1394 Debug("game:mm:DrawLaserExt", "element == %d", element);
1397 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1398 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1402 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1403 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1404 (laser.num_beamers == 0 ||
1405 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1407 // element is incoming beamer or other element
1408 step_size = -step_size;
1413 if (IS_BEAMER(element))
1414 Debug("game:mm:DrawLaserExt",
1415 "start_edge == %d, laser.beamer_edge == %d",
1416 start_edge, laser.beamer_edge);
1419 LX += step_size * XS;
1420 LY += step_size * YS;
1422 else if (element != EL_EMPTY)
1431 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1436 void DrawLaser(int start_edge, int mode)
1438 // do not draw laser if fuse is off
1439 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1442 if (mode == DL_LASER_DISABLED)
1443 DeactivateLaserTargetElement();
1445 if (laser.num_edges - start_edge < 0)
1447 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1452 // check if laser is interrupted by beamer element
1453 if (laser.num_beamers > 0 &&
1454 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1456 if (mode == DL_LASER_ENABLED)
1459 int tmp_start_edge = start_edge;
1461 // draw laser segments forward from the start to the last beamer
1462 for (i = 0; i < laser.num_beamers; i++)
1464 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1466 if (tmp_num_edges <= 0)
1470 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1471 i, laser.beamer_edge[i], tmp_start_edge);
1474 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1476 tmp_start_edge = laser.beamer_edge[i];
1479 // draw last segment from last beamer to the end
1480 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1486 int last_num_edges = laser.num_edges;
1487 int num_beamers = laser.num_beamers;
1489 // delete laser segments backward from the end to the first beamer
1490 for (i = num_beamers - 1; i >= 0; i--)
1492 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1494 if (laser.beamer_edge[i] - start_edge <= 0)
1497 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1499 last_num_edges = laser.beamer_edge[i];
1500 laser.num_beamers--;
1504 if (last_num_edges - start_edge <= 0)
1505 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1506 last_num_edges, start_edge);
1509 // special case when rotating first beamer: delete laser edge on beamer
1510 // (but do not start scanning on previous edge to prevent mirror sound)
1511 if (last_num_edges - start_edge == 1 && start_edge > 0)
1512 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1514 // delete first segment from start to the first beamer
1515 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1520 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1523 game_mm.laser_enabled = mode;
1526 void DrawLaser_MM(void)
1528 DrawLaser(0, game_mm.laser_enabled);
1531 static boolean HitElement(int element, int hit_mask)
1533 if (HitOnlyAnEdge(hit_mask))
1536 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1537 element = MovingOrBlocked2Element_MM(ELX, ELY);
1540 Debug("game:mm:HitElement", "(1): element == %d", element);
1544 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1545 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1548 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1552 AddDamagedField(ELX, ELY);
1554 // this is more precise: check if laser would go through the center
1555 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1559 // prevent cutting through laser emitter with laser beam
1560 if (IS_LASER(element))
1563 // skip the whole element before continuing the scan
1571 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1573 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1575 /* skipping scan positions to the right and down skips one scan
1576 position too much, because this is only the top left scan position
1577 of totally four scan positions (plus one to the right, one to the
1578 bottom and one to the bottom right) */
1579 /* ... but only roll back scan position if more than one step done */
1589 Debug("game:mm:HitElement", "(2): element == %d", element);
1592 if (LX + 5 * XS < 0 ||
1602 Debug("game:mm:HitElement", "(3): element == %d", element);
1605 if (IS_POLAR(element) &&
1606 ((element - EL_POLAR_START) % 2 ||
1607 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1609 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1611 laser.num_damages--;
1616 if (IS_POLAR_CROSS(element) &&
1617 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1619 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1621 laser.num_damages--;
1626 if (!IS_BEAMER(element) &&
1627 !IS_FIBRE_OPTIC(element) &&
1628 !IS_GRID_WOOD(element) &&
1629 element != EL_FUEL_EMPTY)
1632 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1633 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1635 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1638 LX = ELX * TILEX + 14;
1639 LY = ELY * TILEY + 14;
1641 AddLaserEdge(LX, LY);
1644 if (IS_MIRROR(element) ||
1645 IS_MIRROR_FIXED(element) ||
1646 IS_POLAR(element) ||
1647 IS_POLAR_CROSS(element) ||
1648 IS_DF_MIRROR(element) ||
1649 IS_DF_MIRROR_AUTO(element) ||
1650 element == EL_PRISM ||
1651 element == EL_REFRACTOR)
1653 int current_angle = laser.current_angle;
1656 laser.num_damages--;
1658 AddDamagedField(ELX, ELY);
1660 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1663 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1665 if (IS_MIRROR(element) ||
1666 IS_MIRROR_FIXED(element) ||
1667 IS_DF_MIRROR(element) ||
1668 IS_DF_MIRROR_AUTO(element))
1669 laser.current_angle = get_mirrored_angle(laser.current_angle,
1670 get_element_angle(element));
1672 if (element == EL_PRISM || element == EL_REFRACTOR)
1673 laser.current_angle = RND(16);
1675 XS = 2 * Step[laser.current_angle].x;
1676 YS = 2 * Step[laser.current_angle].y;
1678 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1683 LX += step_size * XS;
1684 LY += step_size * YS;
1686 // draw sparkles on mirror
1687 if ((IS_MIRROR(element) ||
1688 IS_MIRROR_FIXED(element) ||
1689 element == EL_PRISM) &&
1690 current_angle != laser.current_angle)
1692 MovDelay[ELX][ELY] = 11; // start animation
1695 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1696 current_angle != laser.current_angle)
1697 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1700 (get_opposite_angle(laser.current_angle) ==
1701 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1703 return (laser.overloaded ? TRUE : FALSE);
1706 if (element == EL_FUEL_FULL)
1708 laser.stops_inside_element = TRUE;
1713 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1715 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1717 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1718 element == EL_MINE ? EL_MINE_ACTIVE :
1719 EL_GRAY_BALL_ACTIVE);
1721 GfxFrame[ELX][ELY] = 0; // restart animation
1723 laser.dest_element_last = Tile[ELX][ELY];
1724 laser.dest_element_last_x = ELX;
1725 laser.dest_element_last_y = ELY;
1727 if (element == EL_MINE)
1728 laser.overloaded = TRUE;
1731 if (element == EL_KETTLE ||
1732 element == EL_CELL ||
1733 element == EL_KEY ||
1734 element == EL_LIGHTBALL ||
1735 element == EL_PACMAN ||
1736 IS_PACMAN(element) ||
1737 IS_ENVELOPE(element))
1739 if (!IS_PACMAN(element) &&
1740 !IS_ENVELOPE(element))
1743 if (element == EL_PACMAN)
1746 if (element == EL_KETTLE || element == EL_CELL)
1748 if (game_mm.kettles_still_needed > 0)
1749 game_mm.kettles_still_needed--;
1751 game.snapshot.collected_item = TRUE;
1753 if (game_mm.kettles_still_needed == 0)
1757 DrawLaser(0, DL_LASER_ENABLED);
1760 else if (element == EL_KEY)
1764 else if (IS_PACMAN(element))
1766 DeletePacMan(ELX, ELY);
1768 else if (IS_ENVELOPE(element))
1770 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1773 RaiseScoreElement_MM(element);
1778 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1780 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1782 DrawLaser(0, DL_LASER_ENABLED);
1784 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1786 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1787 game_mm.lights_still_needed--;
1791 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1792 game_mm.lights_still_needed++;
1795 DrawField_MM(ELX, ELY);
1796 DrawLaser(0, DL_LASER_ENABLED);
1801 laser.stops_inside_element = TRUE;
1807 Debug("game:mm:HitElement", "(4): element == %d", element);
1810 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1811 laser.num_beamers < MAX_NUM_BEAMERS &&
1812 laser.beamer[BEAMER_NR(element)][1].num)
1814 int beamer_angle = get_element_angle(element);
1815 int beamer_nr = BEAMER_NR(element);
1819 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1822 laser.num_damages--;
1824 if (IS_FIBRE_OPTIC(element) ||
1825 laser.current_angle == get_opposite_angle(beamer_angle))
1829 LX = ELX * TILEX + 14;
1830 LY = ELY * TILEY + 14;
1832 AddLaserEdge(LX, LY);
1833 AddDamagedField(ELX, ELY);
1835 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1838 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1840 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1841 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1842 ELX = laser.beamer[beamer_nr][pos].x;
1843 ELY = laser.beamer[beamer_nr][pos].y;
1844 LX = ELX * TILEX + 14;
1845 LY = ELY * TILEY + 14;
1847 if (IS_BEAMER(element))
1849 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1850 XS = 2 * Step[laser.current_angle].x;
1851 YS = 2 * Step[laser.current_angle].y;
1854 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1856 AddLaserEdge(LX, LY);
1857 AddDamagedField(ELX, ELY);
1859 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1862 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1864 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1869 LX += step_size * XS;
1870 LY += step_size * YS;
1872 laser.num_beamers++;
1881 static boolean HitOnlyAnEdge(int hit_mask)
1883 // check if the laser hit only the edge of an element and, if so, go on
1886 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1890 if ((hit_mask == HIT_MASK_TOPLEFT ||
1891 hit_mask == HIT_MASK_TOPRIGHT ||
1892 hit_mask == HIT_MASK_BOTTOMLEFT ||
1893 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1894 laser.current_angle % 4) // angle is not 90°
1898 if (hit_mask == HIT_MASK_TOPLEFT)
1903 else if (hit_mask == HIT_MASK_TOPRIGHT)
1908 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1913 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1919 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1925 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1932 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1938 static boolean HitPolarizer(int element, int hit_mask)
1940 if (HitOnlyAnEdge(hit_mask))
1943 if (IS_DF_GRID(element))
1945 int grid_angle = get_element_angle(element);
1948 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1949 grid_angle, laser.current_angle);
1952 AddLaserEdge(LX, LY);
1953 AddDamagedField(ELX, ELY);
1956 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1958 if (laser.current_angle == grid_angle ||
1959 laser.current_angle == get_opposite_angle(grid_angle))
1961 // skip the whole element before continuing the scan
1967 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1969 if (LX/TILEX > ELX || LY/TILEY > ELY)
1971 /* skipping scan positions to the right and down skips one scan
1972 position too much, because this is only the top left scan position
1973 of totally four scan positions (plus one to the right, one to the
1974 bottom and one to the bottom right) */
1980 AddLaserEdge(LX, LY);
1986 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1988 LX / TILEX, LY / TILEY,
1989 LX % TILEX, LY % TILEY);
1994 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1996 return HitReflectingWalls(element, hit_mask);
2000 return HitAbsorbingWalls(element, hit_mask);
2003 else if (IS_GRID_STEEL(element))
2005 return HitReflectingWalls(element, hit_mask);
2007 else // IS_GRID_WOOD
2009 return HitAbsorbingWalls(element, hit_mask);
2015 static boolean HitBlock(int element, int hit_mask)
2017 boolean check = FALSE;
2019 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2020 game_mm.num_keys == 0)
2023 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2026 int ex = ELX * TILEX + 14;
2027 int ey = ELY * TILEY + 14;
2031 for (i = 1; i < 32; i++)
2036 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2041 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2042 return HitAbsorbingWalls(element, hit_mask);
2046 AddLaserEdge(LX - XS, LY - YS);
2047 AddDamagedField(ELX, ELY);
2050 Box[ELX][ELY] = laser.num_edges;
2052 return HitReflectingWalls(element, hit_mask);
2055 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2057 int xs = XS / 2, ys = YS / 2;
2058 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2059 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2061 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2062 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2064 laser.overloaded = (element == EL_GATE_STONE);
2069 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2070 (hit_mask == HIT_MASK_TOP ||
2071 hit_mask == HIT_MASK_LEFT ||
2072 hit_mask == HIT_MASK_RIGHT ||
2073 hit_mask == HIT_MASK_BOTTOM))
2074 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2075 hit_mask == HIT_MASK_BOTTOM),
2076 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2077 hit_mask == HIT_MASK_RIGHT));
2078 AddLaserEdge(LX, LY);
2084 if (element == EL_GATE_STONE && Box[ELX][ELY])
2086 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2098 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2100 int xs = XS / 2, ys = YS / 2;
2101 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2102 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2104 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2105 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2107 laser.overloaded = (element == EL_BLOCK_STONE);
2112 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2113 (hit_mask == HIT_MASK_TOP ||
2114 hit_mask == HIT_MASK_LEFT ||
2115 hit_mask == HIT_MASK_RIGHT ||
2116 hit_mask == HIT_MASK_BOTTOM))
2117 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2118 hit_mask == HIT_MASK_BOTTOM),
2119 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2120 hit_mask == HIT_MASK_RIGHT));
2121 AddDamagedField(ELX, ELY);
2123 LX = ELX * TILEX + 14;
2124 LY = ELY * TILEY + 14;
2126 AddLaserEdge(LX, LY);
2128 laser.stops_inside_element = TRUE;
2136 static boolean HitLaserSource(int element, int hit_mask)
2138 if (HitOnlyAnEdge(hit_mask))
2141 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2143 laser.overloaded = TRUE;
2148 static boolean HitLaserDestination(int element, int hit_mask)
2150 if (HitOnlyAnEdge(hit_mask))
2153 if (element != EL_EXIT_OPEN &&
2154 !(IS_RECEIVER(element) &&
2155 game_mm.kettles_still_needed == 0 &&
2156 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2158 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2163 if (IS_RECEIVER(element) ||
2164 (IS_22_5_ANGLE(laser.current_angle) &&
2165 (ELX != (LX + 6 * XS) / TILEX ||
2166 ELY != (LY + 6 * YS) / TILEY ||
2175 LX = ELX * TILEX + 14;
2176 LY = ELY * TILEY + 14;
2178 laser.stops_inside_element = TRUE;
2181 AddLaserEdge(LX, LY);
2182 AddDamagedField(ELX, ELY);
2184 if (game_mm.lights_still_needed == 0)
2186 game_mm.level_solved = TRUE;
2188 SetTileCursorActive(FALSE);
2194 static boolean HitReflectingWalls(int element, int hit_mask)
2196 // check if laser hits side of a wall with an angle that is not 90°
2197 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2198 hit_mask == HIT_MASK_LEFT ||
2199 hit_mask == HIT_MASK_RIGHT ||
2200 hit_mask == HIT_MASK_BOTTOM))
2202 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2207 if (!IS_DF_GRID(element))
2208 AddLaserEdge(LX, LY);
2210 // check if laser hits wall with an angle of 45°
2211 if (!IS_22_5_ANGLE(laser.current_angle))
2213 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2216 laser.current_angle = get_mirrored_angle(laser.current_angle,
2219 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2222 laser.current_angle = get_mirrored_angle(laser.current_angle,
2226 AddLaserEdge(LX, LY);
2228 XS = 2 * Step[laser.current_angle].x;
2229 YS = 2 * Step[laser.current_angle].y;
2233 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2235 laser.current_angle = get_mirrored_angle(laser.current_angle,
2240 if (!IS_DF_GRID(element))
2241 AddLaserEdge(LX, LY);
2246 if (!IS_DF_GRID(element))
2247 AddLaserEdge(LX, LY + YS / 2);
2250 if (!IS_DF_GRID(element))
2251 AddLaserEdge(LX, LY);
2254 YS = 2 * Step[laser.current_angle].y;
2258 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2260 laser.current_angle = get_mirrored_angle(laser.current_angle,
2265 if (!IS_DF_GRID(element))
2266 AddLaserEdge(LX, LY);
2271 if (!IS_DF_GRID(element))
2272 AddLaserEdge(LX + XS / 2, LY);
2275 if (!IS_DF_GRID(element))
2276 AddLaserEdge(LX, LY);
2279 XS = 2 * Step[laser.current_angle].x;
2285 // reflection at the edge of reflecting DF style wall
2286 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2288 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2289 hit_mask == HIT_MASK_TOPRIGHT) ||
2290 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2291 hit_mask == HIT_MASK_TOPLEFT) ||
2292 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2293 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2294 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2295 hit_mask == HIT_MASK_BOTTOMRIGHT))
2298 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2299 ANG_MIRROR_135 : ANG_MIRROR_45);
2301 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2303 AddDamagedField(ELX, ELY);
2304 AddLaserEdge(LX, LY);
2306 laser.current_angle = get_mirrored_angle(laser.current_angle,
2314 AddLaserEdge(LX, LY);
2320 // reflection inside an edge of reflecting DF style wall
2321 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2323 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2324 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2325 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2326 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2327 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2328 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2329 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2330 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2333 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2334 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2335 ANG_MIRROR_135 : ANG_MIRROR_45);
2337 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2340 AddDamagedField(ELX, ELY);
2343 AddLaserEdge(LX - XS, LY - YS);
2344 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2345 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2347 laser.current_angle = get_mirrored_angle(laser.current_angle,
2355 AddLaserEdge(LX, LY);
2361 // check if laser hits DF style wall with an angle of 90°
2362 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2364 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2365 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2366 (IS_VERT_ANGLE(laser.current_angle) &&
2367 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2369 // laser at last step touched nothing or the same side of the wall
2370 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2372 AddDamagedField(ELX, ELY);
2379 last_hit_mask = hit_mask;
2386 if (!HitOnlyAnEdge(hit_mask))
2388 laser.overloaded = TRUE;
2396 static boolean HitAbsorbingWalls(int element, int hit_mask)
2398 if (HitOnlyAnEdge(hit_mask))
2402 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2404 AddLaserEdge(LX - XS, LY - YS);
2411 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2413 AddLaserEdge(LX - XS, LY - YS);
2419 if (IS_WALL_WOOD(element) ||
2420 IS_DF_WALL_WOOD(element) ||
2421 IS_GRID_WOOD(element) ||
2422 IS_GRID_WOOD_FIXED(element) ||
2423 IS_GRID_WOOD_AUTO(element) ||
2424 element == EL_FUSE_ON ||
2425 element == EL_BLOCK_WOOD ||
2426 element == EL_GATE_WOOD)
2428 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2433 if (IS_WALL_ICE(element))
2439 // check if laser hit adjacent edges of two diagonal tiles
2440 if (ELX != lx / TILEX)
2442 if (ELY != ly / TILEY)
2445 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2446 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2448 // check if laser hits wall with an angle of 90°
2449 if (IS_90_ANGLE(laser.current_angle))
2450 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2452 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2456 for (i = 0; i < 4; i++)
2458 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2459 mask = 15 - (8 >> i);
2460 else if (ABS(XS) == 4 &&
2462 (XS > 0) == (i % 2) &&
2463 (YS < 0) == (i / 2))
2464 mask = 3 + (i / 2) * 9;
2465 else if (ABS(YS) == 4 &&
2467 (XS < 0) == (i % 2) &&
2468 (YS > 0) == (i / 2))
2469 mask = 5 + (i % 2) * 5;
2473 laser.wall_mask = mask;
2475 else if (IS_WALL_AMOEBA(element))
2477 int elx = (LX - 2 * XS) / TILEX;
2478 int ely = (LY - 2 * YS) / TILEY;
2479 int element2 = Tile[elx][ely];
2482 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2484 laser.dest_element = EL_EMPTY;
2492 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2493 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2495 if (IS_90_ANGLE(laser.current_angle))
2496 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2498 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2500 laser.wall_mask = mask;
2506 static void OpenExit(int x, int y)
2510 if (!MovDelay[x][y]) // next animation frame
2511 MovDelay[x][y] = 4 * delay;
2513 if (MovDelay[x][y]) // wait some time before next frame
2518 phase = MovDelay[x][y] / delay;
2520 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2521 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2523 if (!MovDelay[x][y])
2525 Tile[x][y] = EL_EXIT_OPEN;
2531 static void OpenGrayBall(int x, int y)
2535 if (!MovDelay[x][y]) // next animation frame
2537 if (IS_WALL(Store[x][y]))
2539 DrawWalls_MM(x, y, Store[x][y]);
2541 // copy wall tile to spare bitmap for "melting" animation
2542 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2543 TILEX, TILEY, x * TILEX, y * TILEY);
2545 DrawElement_MM(x, y, EL_GRAY_BALL);
2548 MovDelay[x][y] = 50 * delay;
2551 if (MovDelay[x][y]) // wait some time before next frame
2555 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2559 int dx = RND(26), dy = RND(26);
2561 if (IS_WALL(Store[x][y]))
2563 // copy wall tile from spare bitmap for "melting" animation
2564 bitmap = bitmap_db_field;
2570 int graphic = el2gfx(Store[x][y]);
2572 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2575 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2576 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2578 laser.redraw = TRUE;
2580 MarkTileDirty(x, y);
2583 if (!MovDelay[x][y])
2585 Tile[x][y] = Store[x][y];
2586 Store[x][y] = Store2[x][y] = 0;
2587 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2589 InitField(x, y, FALSE);
2592 ScanLaser_FromLastMirror();
2597 static void OpenEnvelope(int x, int y)
2599 int num_frames = 8; // seven frames plus final empty space
2601 if (!MovDelay[x][y]) // next animation frame
2602 MovDelay[x][y] = num_frames;
2604 if (MovDelay[x][y]) // wait some time before next frame
2606 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2610 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2612 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2613 int frame = num_frames - MovDelay[x][y] - 1;
2615 DrawGraphicAnimation_MM(x, y, graphic, frame);
2617 laser.redraw = TRUE;
2620 if (MovDelay[x][y] == 0)
2622 Tile[x][y] = EL_EMPTY;
2628 ShowEnvelope_MM(nr);
2633 static void MeltIce(int x, int y)
2638 if (!MovDelay[x][y]) // next animation frame
2639 MovDelay[x][y] = frames * delay;
2641 if (MovDelay[x][y]) // wait some time before next frame
2644 int wall_mask = Store2[x][y];
2645 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2648 phase = frames - MovDelay[x][y] / delay - 1;
2650 if (!MovDelay[x][y])
2652 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2653 Store[x][y] = Store2[x][y] = 0;
2655 DrawWalls_MM(x, y, Tile[x][y]);
2657 if (Tile[x][y] == EL_WALL_ICE_BASE)
2658 Tile[x][y] = EL_EMPTY;
2660 ScanLaser_FromLastMirror();
2662 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2664 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2666 laser.redraw = TRUE;
2671 static void GrowAmoeba(int x, int y)
2676 if (!MovDelay[x][y]) // next animation frame
2677 MovDelay[x][y] = frames * delay;
2679 if (MovDelay[x][y]) // wait some time before next frame
2682 int wall_mask = Store2[x][y];
2683 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2686 phase = MovDelay[x][y] / delay;
2688 if (!MovDelay[x][y])
2690 Tile[x][y] = real_element;
2691 Store[x][y] = Store2[x][y] = 0;
2693 DrawWalls_MM(x, y, Tile[x][y]);
2694 DrawLaser(0, DL_LASER_ENABLED);
2696 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2698 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2703 static void DrawFieldAnimated_MM(int x, int y)
2707 laser.redraw = TRUE;
2710 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2712 int element = Tile[x][y];
2713 int graphic = el2gfx(element);
2715 if (!getGraphicInfo_NewFrame(x, y, graphic))
2720 laser.redraw = TRUE;
2723 static void DrawFieldTwinkle(int x, int y)
2725 if (MovDelay[x][y] != 0) // wait some time before next frame
2731 if (MovDelay[x][y] != 0)
2733 int graphic = IMG_TWINKLE_WHITE;
2734 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2736 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2739 laser.redraw = TRUE;
2743 static void Explode_MM(int x, int y, int phase, int mode)
2745 int num_phase = 9, delay = 2;
2746 int last_phase = num_phase * delay;
2747 int half_phase = (num_phase / 2) * delay;
2750 laser.redraw = TRUE;
2752 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2754 center_element = Tile[x][y];
2756 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2758 // put moving element to center field (and let it explode there)
2759 center_element = MovingOrBlocked2Element_MM(x, y);
2760 RemoveMovingField_MM(x, y);
2762 Tile[x][y] = center_element;
2765 if (center_element != EL_GRAY_BALL_ACTIVE)
2766 Store[x][y] = EL_EMPTY;
2767 Store2[x][y] = center_element;
2769 Tile[x][y] = EL_EXPLODING_OPAQUE;
2771 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2772 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2773 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2776 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2778 ExplodePhase[x][y] = 1;
2784 GfxFrame[x][y] = 0; // restart explosion animation
2786 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2788 center_element = Store2[x][y];
2790 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2792 Tile[x][y] = EL_EXPLODING_TRANSP;
2794 if (x == ELX && y == ELY)
2798 if (phase == last_phase)
2800 if (center_element == EL_BOMB_ACTIVE)
2802 DrawLaser(0, DL_LASER_DISABLED);
2805 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2807 GameOver_MM(GAME_OVER_DELAYED);
2809 laser.overloaded = FALSE;
2811 else if (IS_MCDUFFIN(center_element))
2813 GameOver_MM(GAME_OVER_BOMB);
2816 Tile[x][y] = Store[x][y];
2818 Store[x][y] = Store2[x][y] = 0;
2819 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2821 InitField(x, y, FALSE);
2824 if (center_element == EL_GRAY_BALL_ACTIVE)
2825 ScanLaser_FromLastMirror();
2827 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2829 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2830 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2832 DrawGraphicAnimation_MM(x, y, graphic, frame);
2834 MarkTileDirty(x, y);
2838 static void Bang_MM(int x, int y)
2840 int element = Tile[x][y];
2842 if (IS_PACMAN(element))
2843 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2844 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2845 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2846 else if (element == EL_KEY)
2847 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2849 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2851 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2854 static void TurnRound(int x, int y)
2866 { 0, 0 }, { 0, 0 }, { 0, 0 },
2871 int left, right, back;
2875 { MV_DOWN, MV_UP, MV_RIGHT },
2876 { MV_UP, MV_DOWN, MV_LEFT },
2878 { MV_LEFT, MV_RIGHT, MV_DOWN },
2879 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2880 { MV_RIGHT, MV_LEFT, MV_UP }
2883 int element = Tile[x][y];
2884 int old_move_dir = MovDir[x][y];
2885 int right_dir = turn[old_move_dir].right;
2886 int back_dir = turn[old_move_dir].back;
2887 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2888 int right_x = x + right_dx, right_y = y + right_dy;
2890 if (element == EL_PACMAN)
2892 boolean can_turn_right = FALSE;
2894 if (IN_LEV_FIELD(right_x, right_y) &&
2895 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2896 can_turn_right = TRUE;
2899 MovDir[x][y] = right_dir;
2901 MovDir[x][y] = back_dir;
2907 static void StartMoving_MM(int x, int y)
2909 int element = Tile[x][y];
2914 if (CAN_MOVE(element))
2918 if (MovDelay[x][y]) // wait some time before next movement
2926 // now make next step
2928 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2930 if (element == EL_PACMAN &&
2931 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2932 !ObjHit(newx, newy, HIT_POS_CENTER))
2934 Store[newx][newy] = Tile[newx][newy];
2935 Tile[newx][newy] = EL_EMPTY;
2937 DrawField_MM(newx, newy);
2939 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2940 ObjHit(newx, newy, HIT_POS_CENTER))
2942 // object was running against a wall
2949 InitMovingField_MM(x, y, MovDir[x][y]);
2953 ContinueMoving_MM(x, y);
2956 static void ContinueMoving_MM(int x, int y)
2958 int element = Tile[x][y];
2959 int direction = MovDir[x][y];
2960 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2961 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2962 int horiz_move = (dx!=0);
2963 int newx = x + dx, newy = y + dy;
2964 int step = (horiz_move ? dx : dy) * TILEX / 8;
2966 MovPos[x][y] += step;
2968 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2970 Tile[x][y] = EL_EMPTY;
2971 Tile[newx][newy] = element;
2973 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2974 MovDelay[newx][newy] = 0;
2976 if (!CAN_MOVE(element))
2977 MovDir[newx][newy] = 0;
2980 DrawField_MM(newx, newy);
2982 Stop[newx][newy] = TRUE;
2984 if (element == EL_PACMAN)
2986 if (Store[newx][newy] == EL_BOMB)
2987 Bang_MM(newx, newy);
2989 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2990 (LX + 2 * XS) / TILEX == newx &&
2991 (LY + 2 * YS) / TILEY == newy)
2998 else // still moving on
3003 laser.redraw = TRUE;
3006 boolean ClickElement(int x, int y, int button)
3008 static DelayCounter click_delay = { CLICK_DELAY };
3009 static boolean new_button = TRUE;
3010 boolean element_clicked = FALSE;
3015 // initialize static variables
3016 click_delay.count = 0;
3017 click_delay.value = CLICK_DELAY;
3023 // do not rotate objects hit by the laser after the game was solved
3024 if (game_mm.level_solved && Hit[x][y])
3027 if (button == MB_RELEASED)
3030 click_delay.value = CLICK_DELAY;
3032 // release eventually hold auto-rotating mirror
3033 RotateMirror(x, y, MB_RELEASED);
3038 if (!FrameReached(&click_delay) && !new_button)
3041 if (button == MB_MIDDLEBUTTON) // middle button has no function
3044 if (!IN_LEV_FIELD(x, y))
3047 if (Tile[x][y] == EL_EMPTY)
3050 element = Tile[x][y];
3052 if (IS_MIRROR(element) ||
3053 IS_BEAMER(element) ||
3054 IS_POLAR(element) ||
3055 IS_POLAR_CROSS(element) ||
3056 IS_DF_MIRROR(element) ||
3057 IS_DF_MIRROR_AUTO(element))
3059 RotateMirror(x, y, button);
3061 element_clicked = TRUE;
3063 else if (IS_MCDUFFIN(element))
3065 if (!laser.fuse_off)
3067 DrawLaser(0, DL_LASER_DISABLED);
3074 element = get_rotated_element(element, BUTTON_ROTATION(button));
3075 laser.start_angle = get_element_angle(element);
3079 Tile[x][y] = element;
3086 if (!laser.fuse_off)
3089 element_clicked = TRUE;
3091 else if (element == EL_FUSE_ON && laser.fuse_off)
3093 if (x != laser.fuse_x || y != laser.fuse_y)
3096 laser.fuse_off = FALSE;
3097 laser.fuse_x = laser.fuse_y = -1;
3099 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3102 element_clicked = TRUE;
3104 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3106 laser.fuse_off = TRUE;
3109 laser.overloaded = FALSE;
3111 DrawLaser(0, DL_LASER_DISABLED);
3112 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3114 element_clicked = TRUE;
3116 else if (element == EL_LIGHTBALL)
3119 RaiseScoreElement_MM(element);
3120 DrawLaser(0, DL_LASER_ENABLED);
3122 element_clicked = TRUE;
3124 else if (IS_ENVELOPE(element))
3126 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3128 element_clicked = TRUE;
3131 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3134 return element_clicked;
3137 static void RotateMirror(int x, int y, int button)
3139 if (button == MB_RELEASED)
3141 // release eventually hold auto-rotating mirror
3148 if (IS_MIRROR(Tile[x][y]) ||
3149 IS_POLAR_CROSS(Tile[x][y]) ||
3150 IS_POLAR(Tile[x][y]) ||
3151 IS_BEAMER(Tile[x][y]) ||
3152 IS_DF_MIRROR(Tile[x][y]) ||
3153 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3154 IS_GRID_WOOD_AUTO(Tile[x][y]))
3156 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3158 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3160 if (button == MB_LEFTBUTTON)
3162 // left mouse button only for manual adjustment, no auto-rotating;
3163 // freeze mirror for until mouse button released
3167 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3169 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3173 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3175 int edge = Hit[x][y];
3181 DrawLaser(edge - 1, DL_LASER_DISABLED);
3185 else if (ObjHit(x, y, HIT_POS_CENTER))
3187 int edge = Hit[x][y];
3191 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3196 DrawLaser(edge - 1, DL_LASER_DISABLED);
3203 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3208 if ((IS_BEAMER(Tile[x][y]) ||
3209 IS_POLAR(Tile[x][y]) ||
3210 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3212 if (IS_BEAMER(Tile[x][y]))
3215 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3216 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3229 DrawLaser(0, DL_LASER_ENABLED);
3233 static void AutoRotateMirrors(void)
3237 if (!FrameReached(&rotate_delay))
3240 for (x = 0; x < lev_fieldx; x++)
3242 for (y = 0; y < lev_fieldy; y++)
3244 int element = Tile[x][y];
3246 // do not rotate objects hit by the laser after the game was solved
3247 if (game_mm.level_solved && Hit[x][y])
3250 if (IS_DF_MIRROR_AUTO(element) ||
3251 IS_GRID_WOOD_AUTO(element) ||
3252 IS_GRID_STEEL_AUTO(element) ||
3253 element == EL_REFRACTOR)
3254 RotateMirror(x, y, MB_RIGHTBUTTON);
3259 static boolean ObjHit(int obx, int oby, int bits)
3266 if (bits & HIT_POS_CENTER)
3268 if (CheckLaserPixel(cSX + obx + 15,
3273 if (bits & HIT_POS_EDGE)
3275 for (i = 0; i < 4; i++)
3276 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3277 cSY + oby + 31 * (i / 2)))
3281 if (bits & HIT_POS_BETWEEN)
3283 for (i = 0; i < 4; i++)
3284 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3285 cSY + 4 + oby + 22 * (i / 2)))
3292 static void DeletePacMan(int px, int py)
3298 if (game_mm.num_pacman <= 1)
3300 game_mm.num_pacman = 0;
3304 for (i = 0; i < game_mm.num_pacman; i++)
3305 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3308 game_mm.num_pacman--;
3310 for (j = i; j < game_mm.num_pacman; j++)
3312 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3313 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3314 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3318 static void GameActions_MM_Ext(void)
3325 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3328 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3330 element = Tile[x][y];
3332 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3333 StartMoving_MM(x, y);
3334 else if (IS_MOVING(x, y))
3335 ContinueMoving_MM(x, y);
3336 else if (IS_EXPLODING(element))
3337 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3338 else if (element == EL_EXIT_OPENING)
3340 else if (element == EL_GRAY_BALL_OPENING)
3342 else if (IS_ENVELOPE_OPENING(element))
3344 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3346 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3348 else if (IS_MIRROR(element) ||
3349 IS_MIRROR_FIXED(element) ||
3350 element == EL_PRISM)
3351 DrawFieldTwinkle(x, y);
3352 else if (element == EL_GRAY_BALL_ACTIVE ||
3353 element == EL_BOMB_ACTIVE ||
3354 element == EL_MINE_ACTIVE)
3355 DrawFieldAnimated_MM(x, y);
3356 else if (!IS_BLOCKED(x, y))
3357 DrawFieldAnimatedIfNeeded_MM(x, y);
3360 AutoRotateMirrors();
3363 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3365 // redraw after Explode_MM() ...
3367 DrawLaser(0, DL_LASER_ENABLED);
3368 laser.redraw = FALSE;
3373 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3377 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3379 DrawLaser(0, DL_LASER_DISABLED);
3384 // skip all following game actions if game is over
3385 if (game_mm.game_over)
3388 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3392 GameOver_MM(GAME_OVER_NO_ENERGY);
3397 if (FrameReached(&energy_delay))
3399 if (game_mm.energy_left > 0)
3400 game_mm.energy_left--;
3402 // when out of energy, wait another frame to play "out of time" sound
3405 element = laser.dest_element;
3408 if (element != Tile[ELX][ELY])
3410 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3411 element, Tile[ELX][ELY]);
3415 if (!laser.overloaded && laser.overload_value == 0 &&
3416 element != EL_BOMB &&
3417 element != EL_BOMB_ACTIVE &&
3418 element != EL_MINE &&
3419 element != EL_MINE_ACTIVE &&
3420 element != EL_GRAY_BALL &&
3421 element != EL_GRAY_BALL_ACTIVE &&
3422 element != EL_BLOCK_STONE &&
3423 element != EL_BLOCK_WOOD &&
3424 element != EL_FUSE_ON &&
3425 element != EL_FUEL_FULL &&
3426 !IS_WALL_ICE(element) &&
3427 !IS_WALL_AMOEBA(element))
3430 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3432 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3433 (!laser.overloaded && laser.overload_value > 0)) &&
3434 FrameReached(&overload_delay))
3436 if (laser.overloaded)
3437 laser.overload_value++;
3439 laser.overload_value--;
3441 if (game_mm.cheat_no_overload)
3443 laser.overloaded = FALSE;
3444 laser.overload_value = 0;
3447 game_mm.laser_overload_value = laser.overload_value;
3449 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3451 SetLaserColor(0xFF);
3453 DrawLaser(0, DL_LASER_ENABLED);
3456 if (!laser.overloaded)
3457 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3458 else if (setup.sound_loops)
3459 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3461 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3463 if (laser.overload_value == MAX_LASER_OVERLOAD)
3465 UpdateAndDisplayGameControlValues();
3469 GameOver_MM(GAME_OVER_OVERLOADED);
3480 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3482 if (game_mm.cheat_no_explosion)
3487 laser.dest_element = EL_EXPLODING_OPAQUE;
3492 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3494 laser.fuse_off = TRUE;
3498 DrawLaser(0, DL_LASER_DISABLED);
3499 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3502 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3504 if (!Store2[ELX][ELY]) // check if content element not yet determined
3506 int last_anim_random_frame = gfx.anim_random_frame;
3509 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3510 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3512 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3513 native_mm_level.ball_choice_mode, 0,
3514 game_mm.ball_choice_pos);
3516 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3517 gfx.anim_random_frame = last_anim_random_frame;
3519 game_mm.ball_choice_pos++;
3521 int new_element = native_mm_level.ball_content[element_pos];
3522 int new_element_base = map_wall_to_base_element(new_element);
3524 if (IS_WALL(new_element_base))
3526 // always use completely filled wall element
3527 new_element = new_element_base | 0x000f;
3529 else if (native_mm_level.rotate_ball_content &&
3530 get_num_elements(new_element) > 1)
3532 // randomly rotate newly created game element
3533 new_element = get_rotated_element(new_element, RND(16));
3536 Store[ELX][ELY] = new_element;
3537 Store2[ELX][ELY] = TRUE;
3540 if (native_mm_level.explode_ball)
3543 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3545 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3550 if (IS_WALL_ICE(element) && CT > 50)
3552 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3554 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3555 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3556 Store2[ELX][ELY] = laser.wall_mask;
3558 laser.dest_element = Tile[ELX][ELY];
3563 if (IS_WALL_AMOEBA(element) && CT > 60)
3566 int element2 = Tile[ELX][ELY];
3568 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3571 for (i = laser.num_damages - 1; i >= 0; i--)
3572 if (laser.damage[i].is_mirror)
3575 r = laser.num_edges;
3576 d = laser.num_damages;
3583 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3586 DrawLaser(0, DL_LASER_ENABLED);
3589 x = laser.damage[k1].x;
3590 y = laser.damage[k1].y;
3595 for (i = 0; i < 4; i++)
3597 if (laser.wall_mask & (1 << i))
3599 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3600 cSY + ELY * TILEY + 31 * (i / 2)))
3603 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3604 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3611 for (i = 0; i < 4; i++)
3613 if (laser.wall_mask & (1 << i))
3615 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3616 cSY + ELY * TILEY + 31 * (i / 2)))
3623 if (laser.num_beamers > 0 ||
3624 k1 < 1 || k2 < 4 || k3 < 4 ||
3625 CheckLaserPixel(cSX + ELX * TILEX + 14,
3626 cSY + ELY * TILEY + 14))
3628 laser.num_edges = r;
3629 laser.num_damages = d;
3631 DrawLaser(0, DL_LASER_DISABLED);
3634 Tile[ELX][ELY] = element | laser.wall_mask;
3636 int x = ELX, y = ELY;
3637 int wall_mask = laser.wall_mask;
3640 DrawLaser(0, DL_LASER_ENABLED);
3642 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3644 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3645 Store[x][y] = EL_WALL_AMOEBA_BASE;
3646 Store2[x][y] = wall_mask;
3651 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3652 laser.stops_inside_element && CT > native_mm_level.time_block)
3657 if (ABS(XS) > ABS(YS))
3664 for (i = 0; i < 4; i++)
3671 x = ELX + Step[k * 4].x;
3672 y = ELY + Step[k * 4].y;
3674 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3677 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3685 laser.overloaded = (element == EL_BLOCK_STONE);
3690 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3693 Tile[x][y] = element;
3695 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3698 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3700 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3701 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3709 if (element == EL_FUEL_FULL && CT > 10)
3711 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3712 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3714 for (i = start; i <= num_init_game_frames; i++)
3716 if (i == num_init_game_frames)
3717 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3718 else if (setup.sound_loops)
3719 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3721 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3723 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3725 UpdateAndDisplayGameControlValues();
3730 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3732 DrawField_MM(ELX, ELY);
3734 DrawLaser(0, DL_LASER_ENABLED);
3740 void GameActions_MM(struct MouseActionInfo action)
3742 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3743 boolean button_released = (action.button == MB_RELEASED);
3745 GameActions_MM_Ext();
3747 CheckSingleStepMode_MM(element_clicked, button_released);
3750 static void MovePacMen(void)
3752 int mx, my, ox, oy, nx, ny;
3756 if (++pacman_nr >= game_mm.num_pacman)
3759 game_mm.pacman[pacman_nr].dir--;
3761 for (l = 1; l < 5; l++)
3763 game_mm.pacman[pacman_nr].dir++;
3765 if (game_mm.pacman[pacman_nr].dir > 4)
3766 game_mm.pacman[pacman_nr].dir = 1;
3768 if (game_mm.pacman[pacman_nr].dir % 2)
3771 my = game_mm.pacman[pacman_nr].dir - 2;
3776 mx = 3 - game_mm.pacman[pacman_nr].dir;
3779 ox = game_mm.pacman[pacman_nr].x;
3780 oy = game_mm.pacman[pacman_nr].y;
3783 element = Tile[nx][ny];
3785 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3788 if (!IS_EATABLE4PACMAN(element))
3791 if (ObjHit(nx, ny, HIT_POS_CENTER))
3794 Tile[ox][oy] = EL_EMPTY;
3796 EL_PACMAN_RIGHT - 1 +
3797 (game_mm.pacman[pacman_nr].dir - 1 +
3798 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3800 game_mm.pacman[pacman_nr].x = nx;
3801 game_mm.pacman[pacman_nr].y = ny;
3803 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3805 if (element != EL_EMPTY)
3807 int graphic = el2gfx(Tile[nx][ny]);
3812 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3815 ox = cSX + ox * TILEX;
3816 oy = cSY + oy * TILEY;
3818 for (i = 1; i < 33; i += 2)
3819 BlitBitmap(bitmap, window,
3820 src_x, src_y, TILEX, TILEY,
3821 ox + i * mx, oy + i * my);
3822 Ct = Ct + FrameCounter - CT;
3825 DrawField_MM(nx, ny);
3828 if (!laser.fuse_off)
3830 DrawLaser(0, DL_LASER_ENABLED);
3832 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3834 AddDamagedField(nx, ny);
3836 laser.damage[laser.num_damages - 1].edge = 0;
3840 if (element == EL_BOMB)
3841 DeletePacMan(nx, ny);
3843 if (IS_WALL_AMOEBA(element) &&
3844 (LX + 2 * XS) / TILEX == nx &&
3845 (LY + 2 * YS) / TILEY == ny)
3855 static void InitMovingField_MM(int x, int y, int direction)
3857 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3858 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3860 MovDir[x][y] = direction;
3861 MovDir[newx][newy] = direction;
3863 if (Tile[newx][newy] == EL_EMPTY)
3864 Tile[newx][newy] = EL_BLOCKED;
3867 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3869 int direction = MovDir[x][y];
3870 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3871 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3877 static void Blocked2Moving_MM(int x, int y,
3878 int *comes_from_x, int *comes_from_y)
3880 int oldx = x, oldy = y;
3881 int direction = MovDir[x][y];
3883 if (direction == MV_LEFT)
3885 else if (direction == MV_RIGHT)
3887 else if (direction == MV_UP)
3889 else if (direction == MV_DOWN)
3892 *comes_from_x = oldx;
3893 *comes_from_y = oldy;
3896 static int MovingOrBlocked2Element_MM(int x, int y)
3898 int element = Tile[x][y];
3900 if (element == EL_BLOCKED)
3904 Blocked2Moving_MM(x, y, &oldx, &oldy);
3906 return Tile[oldx][oldy];
3912 static void RemoveMovingField_MM(int x, int y)
3914 int oldx = x, oldy = y, newx = x, newy = y;
3916 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3919 if (IS_MOVING(x, y))
3921 Moving2Blocked_MM(x, y, &newx, &newy);
3922 if (Tile[newx][newy] != EL_BLOCKED)
3925 else if (Tile[x][y] == EL_BLOCKED)
3927 Blocked2Moving_MM(x, y, &oldx, &oldy);
3928 if (!IS_MOVING(oldx, oldy))
3932 Tile[oldx][oldy] = EL_EMPTY;
3933 Tile[newx][newy] = EL_EMPTY;
3934 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3935 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3937 DrawLevelField_MM(oldx, oldy);
3938 DrawLevelField_MM(newx, newy);
3941 static void RaiseScore_MM(int value)
3943 game_mm.score += value;
3946 void RaiseScoreElement_MM(int element)
3951 case EL_PACMAN_RIGHT:
3953 case EL_PACMAN_LEFT:
3954 case EL_PACMAN_DOWN:
3955 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3959 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3964 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3968 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3977 // ----------------------------------------------------------------------------
3978 // Mirror Magic game engine snapshot handling functions
3979 // ----------------------------------------------------------------------------
3981 void SaveEngineSnapshotValues_MM(void)
3985 engine_snapshot_mm.game_mm = game_mm;
3986 engine_snapshot_mm.laser = laser;
3988 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3990 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3992 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3993 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3994 engine_snapshot_mm.Box[x][y] = Box[x][y];
3995 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3999 engine_snapshot_mm.LX = LX;
4000 engine_snapshot_mm.LY = LY;
4001 engine_snapshot_mm.XS = XS;
4002 engine_snapshot_mm.YS = YS;
4003 engine_snapshot_mm.ELX = ELX;
4004 engine_snapshot_mm.ELY = ELY;
4005 engine_snapshot_mm.CT = CT;
4006 engine_snapshot_mm.Ct = Ct;
4008 engine_snapshot_mm.last_LX = last_LX;
4009 engine_snapshot_mm.last_LY = last_LY;
4010 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4011 engine_snapshot_mm.hold_x = hold_x;
4012 engine_snapshot_mm.hold_y = hold_y;
4013 engine_snapshot_mm.pacman_nr = pacman_nr;
4015 engine_snapshot_mm.rotate_delay = rotate_delay;
4016 engine_snapshot_mm.pacman_delay = pacman_delay;
4017 engine_snapshot_mm.energy_delay = energy_delay;
4018 engine_snapshot_mm.overload_delay = overload_delay;
4021 void LoadEngineSnapshotValues_MM(void)
4025 // stored engine snapshot buffers already restored at this point
4027 game_mm = engine_snapshot_mm.game_mm;
4028 laser = engine_snapshot_mm.laser;
4030 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4032 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4034 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4035 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4036 Box[x][y] = engine_snapshot_mm.Box[x][y];
4037 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4041 LX = engine_snapshot_mm.LX;
4042 LY = engine_snapshot_mm.LY;
4043 XS = engine_snapshot_mm.XS;
4044 YS = engine_snapshot_mm.YS;
4045 ELX = engine_snapshot_mm.ELX;
4046 ELY = engine_snapshot_mm.ELY;
4047 CT = engine_snapshot_mm.CT;
4048 Ct = engine_snapshot_mm.Ct;
4050 last_LX = engine_snapshot_mm.last_LX;
4051 last_LY = engine_snapshot_mm.last_LY;
4052 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4053 hold_x = engine_snapshot_mm.hold_x;
4054 hold_y = engine_snapshot_mm.hold_y;
4055 pacman_nr = engine_snapshot_mm.pacman_nr;
4057 rotate_delay = engine_snapshot_mm.rotate_delay;
4058 pacman_delay = engine_snapshot_mm.pacman_delay;
4059 energy_delay = engine_snapshot_mm.energy_delay;
4060 overload_delay = engine_snapshot_mm.overload_delay;
4062 RedrawPlayfield_MM();
4065 static int getAngleFromTouchDelta(int dx, int dy, int base)
4067 double pi = 3.141592653;
4068 double rad = atan2((double)-dy, (double)dx);
4069 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4070 double deg = rad2 * 180.0 / pi;
4072 return (int)(deg * base / 360.0 + 0.5) % base;
4075 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4077 // calculate start (source) position to be at the middle of the tile
4078 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4079 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4080 int dx = dst_mx - src_mx;
4081 int dy = dst_my - src_my;
4090 if (!IN_LEV_FIELD(x, y))
4093 element = Tile[x][y];
4095 if (!IS_MCDUFFIN(element) &&
4096 !IS_MIRROR(element) &&
4097 !IS_BEAMER(element) &&
4098 !IS_POLAR(element) &&
4099 !IS_POLAR_CROSS(element) &&
4100 !IS_DF_MIRROR(element))
4103 angle_old = get_element_angle(element);
4105 if (IS_MCDUFFIN(element))
4107 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4108 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4109 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4110 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4113 else if (IS_MIRROR(element) ||
4114 IS_DF_MIRROR(element))
4116 for (i = 0; i < laser.num_damages; i++)
4118 if (laser.damage[i].x == x &&
4119 laser.damage[i].y == y &&
4120 ObjHit(x, y, HIT_POS_CENTER))
4122 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4123 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4130 if (angle_new == -1)
4132 if (IS_MIRROR(element) ||
4133 IS_DF_MIRROR(element) ||
4137 if (IS_POLAR_CROSS(element))
4140 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4143 button = (angle_new == angle_old ? 0 :
4144 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4145 MB_LEFTBUTTON : MB_RIGHTBUTTON);