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;
2059 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2060 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2062 laser.overloaded = (element == EL_GATE_STONE);
2067 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2068 (hit_mask == HIT_MASK_TOP ||
2069 hit_mask == HIT_MASK_LEFT ||
2070 hit_mask == HIT_MASK_RIGHT ||
2071 hit_mask == HIT_MASK_BOTTOM))
2072 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2073 hit_mask == HIT_MASK_BOTTOM),
2074 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2075 hit_mask == HIT_MASK_RIGHT));
2076 AddLaserEdge(LX, LY);
2082 if (element == EL_GATE_STONE && Box[ELX][ELY])
2084 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2096 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2098 int xs = XS / 2, ys = YS / 2;
2100 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2101 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2103 laser.overloaded = (element == EL_BLOCK_STONE);
2108 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2109 (hit_mask == HIT_MASK_TOP ||
2110 hit_mask == HIT_MASK_LEFT ||
2111 hit_mask == HIT_MASK_RIGHT ||
2112 hit_mask == HIT_MASK_BOTTOM))
2113 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2114 hit_mask == HIT_MASK_BOTTOM),
2115 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2116 hit_mask == HIT_MASK_RIGHT));
2117 AddDamagedField(ELX, ELY);
2119 LX = ELX * TILEX + 14;
2120 LY = ELY * TILEY + 14;
2122 AddLaserEdge(LX, LY);
2124 laser.stops_inside_element = TRUE;
2132 static boolean HitLaserSource(int element, int hit_mask)
2134 if (HitOnlyAnEdge(hit_mask))
2137 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2139 laser.overloaded = TRUE;
2144 static boolean HitLaserDestination(int element, int hit_mask)
2146 if (HitOnlyAnEdge(hit_mask))
2149 if (element != EL_EXIT_OPEN &&
2150 !(IS_RECEIVER(element) &&
2151 game_mm.kettles_still_needed == 0 &&
2152 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2154 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2159 if (IS_RECEIVER(element) ||
2160 (IS_22_5_ANGLE(laser.current_angle) &&
2161 (ELX != (LX + 6 * XS) / TILEX ||
2162 ELY != (LY + 6 * YS) / TILEY ||
2171 LX = ELX * TILEX + 14;
2172 LY = ELY * TILEY + 14;
2174 laser.stops_inside_element = TRUE;
2177 AddLaserEdge(LX, LY);
2178 AddDamagedField(ELX, ELY);
2180 if (game_mm.lights_still_needed == 0)
2182 game_mm.level_solved = TRUE;
2184 SetTileCursorActive(FALSE);
2190 static boolean HitReflectingWalls(int element, int hit_mask)
2192 // check if laser hits side of a wall with an angle that is not 90°
2193 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2194 hit_mask == HIT_MASK_LEFT ||
2195 hit_mask == HIT_MASK_RIGHT ||
2196 hit_mask == HIT_MASK_BOTTOM))
2198 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2203 if (!IS_DF_GRID(element))
2204 AddLaserEdge(LX, LY);
2206 // check if laser hits wall with an angle of 45°
2207 if (!IS_22_5_ANGLE(laser.current_angle))
2209 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2212 laser.current_angle = get_mirrored_angle(laser.current_angle,
2215 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2218 laser.current_angle = get_mirrored_angle(laser.current_angle,
2222 AddLaserEdge(LX, LY);
2224 XS = 2 * Step[laser.current_angle].x;
2225 YS = 2 * Step[laser.current_angle].y;
2229 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2231 laser.current_angle = get_mirrored_angle(laser.current_angle,
2236 if (!IS_DF_GRID(element))
2237 AddLaserEdge(LX, LY);
2242 if (!IS_DF_GRID(element))
2243 AddLaserEdge(LX, LY + YS / 2);
2246 if (!IS_DF_GRID(element))
2247 AddLaserEdge(LX, LY);
2250 YS = 2 * Step[laser.current_angle].y;
2254 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2256 laser.current_angle = get_mirrored_angle(laser.current_angle,
2261 if (!IS_DF_GRID(element))
2262 AddLaserEdge(LX, LY);
2267 if (!IS_DF_GRID(element))
2268 AddLaserEdge(LX + XS / 2, LY);
2271 if (!IS_DF_GRID(element))
2272 AddLaserEdge(LX, LY);
2275 XS = 2 * Step[laser.current_angle].x;
2281 // reflection at the edge of reflecting DF style wall
2282 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2284 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2285 hit_mask == HIT_MASK_TOPRIGHT) ||
2286 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2287 hit_mask == HIT_MASK_TOPLEFT) ||
2288 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2289 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2290 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2291 hit_mask == HIT_MASK_BOTTOMRIGHT))
2294 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2295 ANG_MIRROR_135 : ANG_MIRROR_45);
2297 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2299 AddDamagedField(ELX, ELY);
2300 AddLaserEdge(LX, LY);
2302 laser.current_angle = get_mirrored_angle(laser.current_angle,
2310 AddLaserEdge(LX, LY);
2316 // reflection inside an edge of reflecting DF style wall
2317 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2319 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2320 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2321 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2322 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2323 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2324 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2325 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2326 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2329 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2330 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2331 ANG_MIRROR_135 : ANG_MIRROR_45);
2333 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2336 AddDamagedField(ELX, ELY);
2339 AddLaserEdge(LX - XS, LY - YS);
2340 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2341 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2343 laser.current_angle = get_mirrored_angle(laser.current_angle,
2351 AddLaserEdge(LX, LY);
2357 // check if laser hits DF style wall with an angle of 90°
2358 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2360 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2361 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2362 (IS_VERT_ANGLE(laser.current_angle) &&
2363 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2365 // laser at last step touched nothing or the same side of the wall
2366 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2368 AddDamagedField(ELX, ELY);
2375 last_hit_mask = hit_mask;
2382 if (!HitOnlyAnEdge(hit_mask))
2384 laser.overloaded = TRUE;
2392 static boolean HitAbsorbingWalls(int element, int hit_mask)
2394 if (HitOnlyAnEdge(hit_mask))
2398 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2400 AddLaserEdge(LX - XS, LY - YS);
2407 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2409 AddLaserEdge(LX - XS, LY - YS);
2415 if (IS_WALL_WOOD(element) ||
2416 IS_DF_WALL_WOOD(element) ||
2417 IS_GRID_WOOD(element) ||
2418 IS_GRID_WOOD_FIXED(element) ||
2419 IS_GRID_WOOD_AUTO(element) ||
2420 element == EL_FUSE_ON ||
2421 element == EL_BLOCK_WOOD ||
2422 element == EL_GATE_WOOD)
2424 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2429 if (IS_WALL_ICE(element))
2435 // check if laser hit adjacent edges of two diagonal tiles
2436 if (ELX != lx / TILEX)
2438 if (ELY != ly / TILEY)
2441 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2442 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2444 // check if laser hits wall with an angle of 90°
2445 if (IS_90_ANGLE(laser.current_angle))
2446 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2448 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2452 for (i = 0; i < 4; i++)
2454 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2455 mask = 15 - (8 >> i);
2456 else if (ABS(XS) == 4 &&
2458 (XS > 0) == (i % 2) &&
2459 (YS < 0) == (i / 2))
2460 mask = 3 + (i / 2) * 9;
2461 else if (ABS(YS) == 4 &&
2463 (XS < 0) == (i % 2) &&
2464 (YS > 0) == (i / 2))
2465 mask = 5 + (i % 2) * 5;
2469 laser.wall_mask = mask;
2471 else if (IS_WALL_AMOEBA(element))
2473 int elx = (LX - 2 * XS) / TILEX;
2474 int ely = (LY - 2 * YS) / TILEY;
2475 int element2 = Tile[elx][ely];
2478 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2480 laser.dest_element = EL_EMPTY;
2488 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2489 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2491 if (IS_90_ANGLE(laser.current_angle))
2492 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2494 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2496 laser.wall_mask = mask;
2502 static void OpenExit(int x, int y)
2506 if (!MovDelay[x][y]) // next animation frame
2507 MovDelay[x][y] = 4 * delay;
2509 if (MovDelay[x][y]) // wait some time before next frame
2514 phase = MovDelay[x][y] / delay;
2516 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2517 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2519 if (!MovDelay[x][y])
2521 Tile[x][y] = EL_EXIT_OPEN;
2527 static void OpenGrayBall(int x, int y)
2531 if (!MovDelay[x][y]) // next animation frame
2533 if (IS_WALL(Store[x][y]))
2535 DrawWalls_MM(x, y, Store[x][y]);
2537 // copy wall tile to spare bitmap for "melting" animation
2538 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2539 TILEX, TILEY, x * TILEX, y * TILEY);
2541 DrawElement_MM(x, y, EL_GRAY_BALL);
2544 MovDelay[x][y] = 50 * delay;
2547 if (MovDelay[x][y]) // wait some time before next frame
2551 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2555 int dx = RND(26), dy = RND(26);
2557 if (IS_WALL(Store[x][y]))
2559 // copy wall tile from spare bitmap for "melting" animation
2560 bitmap = bitmap_db_field;
2566 int graphic = el2gfx(Store[x][y]);
2568 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2571 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2572 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2574 laser.redraw = TRUE;
2576 MarkTileDirty(x, y);
2579 if (!MovDelay[x][y])
2581 Tile[x][y] = Store[x][y];
2582 Store[x][y] = Store2[x][y] = 0;
2583 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2585 InitField(x, y, FALSE);
2588 ScanLaser_FromLastMirror();
2593 static void OpenEnvelope(int x, int y)
2595 int num_frames = 8; // seven frames plus final empty space
2597 if (!MovDelay[x][y]) // next animation frame
2598 MovDelay[x][y] = num_frames;
2600 if (MovDelay[x][y]) // wait some time before next frame
2602 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2606 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2608 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2609 int frame = num_frames - MovDelay[x][y] - 1;
2611 DrawGraphicAnimation_MM(x, y, graphic, frame);
2613 laser.redraw = TRUE;
2616 if (MovDelay[x][y] == 0)
2618 Tile[x][y] = EL_EMPTY;
2624 ShowEnvelope_MM(nr);
2629 static void MeltIce(int x, int y)
2634 if (!MovDelay[x][y]) // next animation frame
2635 MovDelay[x][y] = frames * delay;
2637 if (MovDelay[x][y]) // wait some time before next frame
2640 int wall_mask = Store2[x][y];
2641 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2644 phase = frames - MovDelay[x][y] / delay - 1;
2646 if (!MovDelay[x][y])
2648 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2649 Store[x][y] = Store2[x][y] = 0;
2651 DrawWalls_MM(x, y, Tile[x][y]);
2653 if (Tile[x][y] == EL_WALL_ICE_BASE)
2654 Tile[x][y] = EL_EMPTY;
2656 ScanLaser_FromLastMirror();
2658 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2660 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2662 laser.redraw = TRUE;
2667 static void GrowAmoeba(int x, int y)
2672 if (!MovDelay[x][y]) // next animation frame
2673 MovDelay[x][y] = frames * delay;
2675 if (MovDelay[x][y]) // wait some time before next frame
2678 int wall_mask = Store2[x][y];
2679 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2682 phase = MovDelay[x][y] / delay;
2684 if (!MovDelay[x][y])
2686 Tile[x][y] = real_element;
2687 Store[x][y] = Store2[x][y] = 0;
2689 DrawWalls_MM(x, y, Tile[x][y]);
2690 DrawLaser(0, DL_LASER_ENABLED);
2692 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2694 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2699 static void DrawFieldAnimated_MM(int x, int y)
2703 laser.redraw = TRUE;
2706 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2708 int element = Tile[x][y];
2709 int graphic = el2gfx(element);
2711 if (!getGraphicInfo_NewFrame(x, y, graphic))
2716 laser.redraw = TRUE;
2719 static void DrawFieldTwinkle(int x, int y)
2721 if (MovDelay[x][y] != 0) // wait some time before next frame
2727 if (MovDelay[x][y] != 0)
2729 int graphic = IMG_TWINKLE_WHITE;
2730 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2732 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2735 laser.redraw = TRUE;
2739 static void Explode_MM(int x, int y, int phase, int mode)
2741 int num_phase = 9, delay = 2;
2742 int last_phase = num_phase * delay;
2743 int half_phase = (num_phase / 2) * delay;
2746 laser.redraw = TRUE;
2748 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2750 center_element = Tile[x][y];
2752 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2754 // put moving element to center field (and let it explode there)
2755 center_element = MovingOrBlocked2Element_MM(x, y);
2756 RemoveMovingField_MM(x, y);
2758 Tile[x][y] = center_element;
2761 if (center_element != EL_GRAY_BALL_ACTIVE)
2762 Store[x][y] = EL_EMPTY;
2763 Store2[x][y] = center_element;
2765 Tile[x][y] = EL_EXPLODING_OPAQUE;
2767 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2768 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2769 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2772 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2774 ExplodePhase[x][y] = 1;
2780 GfxFrame[x][y] = 0; // restart explosion animation
2782 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2784 center_element = Store2[x][y];
2786 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2788 Tile[x][y] = EL_EXPLODING_TRANSP;
2790 if (x == ELX && y == ELY)
2794 if (phase == last_phase)
2796 if (center_element == EL_BOMB_ACTIVE)
2798 DrawLaser(0, DL_LASER_DISABLED);
2801 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2803 GameOver_MM(GAME_OVER_DELAYED);
2805 laser.overloaded = FALSE;
2807 else if (IS_MCDUFFIN(center_element))
2809 GameOver_MM(GAME_OVER_BOMB);
2812 Tile[x][y] = Store[x][y];
2814 Store[x][y] = Store2[x][y] = 0;
2815 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2817 InitField(x, y, FALSE);
2820 if (center_element == EL_GRAY_BALL_ACTIVE)
2821 ScanLaser_FromLastMirror();
2823 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2825 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2826 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2828 DrawGraphicAnimation_MM(x, y, graphic, frame);
2830 MarkTileDirty(x, y);
2834 static void Bang_MM(int x, int y)
2836 int element = Tile[x][y];
2838 if (IS_PACMAN(element))
2839 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2840 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2841 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2842 else if (element == EL_KEY)
2843 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2845 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2847 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2850 static void TurnRound(int x, int y)
2862 { 0, 0 }, { 0, 0 }, { 0, 0 },
2867 int left, right, back;
2871 { MV_DOWN, MV_UP, MV_RIGHT },
2872 { MV_UP, MV_DOWN, MV_LEFT },
2874 { MV_LEFT, MV_RIGHT, MV_DOWN },
2875 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2876 { MV_RIGHT, MV_LEFT, MV_UP }
2879 int element = Tile[x][y];
2880 int old_move_dir = MovDir[x][y];
2881 int right_dir = turn[old_move_dir].right;
2882 int back_dir = turn[old_move_dir].back;
2883 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2884 int right_x = x + right_dx, right_y = y + right_dy;
2886 if (element == EL_PACMAN)
2888 boolean can_turn_right = FALSE;
2890 if (IN_LEV_FIELD(right_x, right_y) &&
2891 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2892 can_turn_right = TRUE;
2895 MovDir[x][y] = right_dir;
2897 MovDir[x][y] = back_dir;
2903 static void StartMoving_MM(int x, int y)
2905 int element = Tile[x][y];
2910 if (CAN_MOVE(element))
2914 if (MovDelay[x][y]) // wait some time before next movement
2922 // now make next step
2924 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2926 if (element == EL_PACMAN &&
2927 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2928 !ObjHit(newx, newy, HIT_POS_CENTER))
2930 Store[newx][newy] = Tile[newx][newy];
2931 Tile[newx][newy] = EL_EMPTY;
2933 DrawField_MM(newx, newy);
2935 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2936 ObjHit(newx, newy, HIT_POS_CENTER))
2938 // object was running against a wall
2945 InitMovingField_MM(x, y, MovDir[x][y]);
2949 ContinueMoving_MM(x, y);
2952 static void ContinueMoving_MM(int x, int y)
2954 int element = Tile[x][y];
2955 int direction = MovDir[x][y];
2956 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2957 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2958 int horiz_move = (dx!=0);
2959 int newx = x + dx, newy = y + dy;
2960 int step = (horiz_move ? dx : dy) * TILEX / 8;
2962 MovPos[x][y] += step;
2964 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2966 Tile[x][y] = EL_EMPTY;
2967 Tile[newx][newy] = element;
2969 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2970 MovDelay[newx][newy] = 0;
2972 if (!CAN_MOVE(element))
2973 MovDir[newx][newy] = 0;
2976 DrawField_MM(newx, newy);
2978 Stop[newx][newy] = TRUE;
2980 if (element == EL_PACMAN)
2982 if (Store[newx][newy] == EL_BOMB)
2983 Bang_MM(newx, newy);
2985 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2986 (LX + 2 * XS) / TILEX == newx &&
2987 (LY + 2 * YS) / TILEY == newy)
2994 else // still moving on
2999 laser.redraw = TRUE;
3002 boolean ClickElement(int x, int y, int button)
3004 static DelayCounter click_delay = { CLICK_DELAY };
3005 static boolean new_button = TRUE;
3006 boolean element_clicked = FALSE;
3011 // initialize static variables
3012 click_delay.count = 0;
3013 click_delay.value = CLICK_DELAY;
3019 // do not rotate objects hit by the laser after the game was solved
3020 if (game_mm.level_solved && Hit[x][y])
3023 if (button == MB_RELEASED)
3026 click_delay.value = CLICK_DELAY;
3028 // release eventually hold auto-rotating mirror
3029 RotateMirror(x, y, MB_RELEASED);
3034 if (!FrameReached(&click_delay) && !new_button)
3037 if (button == MB_MIDDLEBUTTON) // middle button has no function
3040 if (!IN_LEV_FIELD(x, y))
3043 if (Tile[x][y] == EL_EMPTY)
3046 element = Tile[x][y];
3048 if (IS_MIRROR(element) ||
3049 IS_BEAMER(element) ||
3050 IS_POLAR(element) ||
3051 IS_POLAR_CROSS(element) ||
3052 IS_DF_MIRROR(element) ||
3053 IS_DF_MIRROR_AUTO(element))
3055 RotateMirror(x, y, button);
3057 element_clicked = TRUE;
3059 else if (IS_MCDUFFIN(element))
3061 if (!laser.fuse_off)
3063 DrawLaser(0, DL_LASER_DISABLED);
3070 element = get_rotated_element(element, BUTTON_ROTATION(button));
3071 laser.start_angle = get_element_angle(element);
3075 Tile[x][y] = element;
3082 if (!laser.fuse_off)
3085 element_clicked = TRUE;
3087 else if (element == EL_FUSE_ON && laser.fuse_off)
3089 if (x != laser.fuse_x || y != laser.fuse_y)
3092 laser.fuse_off = FALSE;
3093 laser.fuse_x = laser.fuse_y = -1;
3095 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3098 element_clicked = TRUE;
3100 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3102 laser.fuse_off = TRUE;
3105 laser.overloaded = FALSE;
3107 DrawLaser(0, DL_LASER_DISABLED);
3108 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3110 element_clicked = TRUE;
3112 else if (element == EL_LIGHTBALL)
3115 RaiseScoreElement_MM(element);
3116 DrawLaser(0, DL_LASER_ENABLED);
3118 element_clicked = TRUE;
3120 else if (IS_ENVELOPE(element))
3122 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3124 element_clicked = TRUE;
3127 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3130 return element_clicked;
3133 static void RotateMirror(int x, int y, int button)
3135 if (button == MB_RELEASED)
3137 // release eventually hold auto-rotating mirror
3144 if (IS_MIRROR(Tile[x][y]) ||
3145 IS_POLAR_CROSS(Tile[x][y]) ||
3146 IS_POLAR(Tile[x][y]) ||
3147 IS_BEAMER(Tile[x][y]) ||
3148 IS_DF_MIRROR(Tile[x][y]) ||
3149 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3150 IS_GRID_WOOD_AUTO(Tile[x][y]))
3152 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3154 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3156 if (button == MB_LEFTBUTTON)
3158 // left mouse button only for manual adjustment, no auto-rotating;
3159 // freeze mirror for until mouse button released
3163 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3165 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3169 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3171 int edge = Hit[x][y];
3177 DrawLaser(edge - 1, DL_LASER_DISABLED);
3181 else if (ObjHit(x, y, HIT_POS_CENTER))
3183 int edge = Hit[x][y];
3187 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3192 DrawLaser(edge - 1, DL_LASER_DISABLED);
3199 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3204 if ((IS_BEAMER(Tile[x][y]) ||
3205 IS_POLAR(Tile[x][y]) ||
3206 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3208 if (IS_BEAMER(Tile[x][y]))
3211 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3212 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3225 DrawLaser(0, DL_LASER_ENABLED);
3229 static void AutoRotateMirrors(void)
3233 if (!FrameReached(&rotate_delay))
3236 for (x = 0; x < lev_fieldx; x++)
3238 for (y = 0; y < lev_fieldy; y++)
3240 int element = Tile[x][y];
3242 // do not rotate objects hit by the laser after the game was solved
3243 if (game_mm.level_solved && Hit[x][y])
3246 if (IS_DF_MIRROR_AUTO(element) ||
3247 IS_GRID_WOOD_AUTO(element) ||
3248 IS_GRID_STEEL_AUTO(element) ||
3249 element == EL_REFRACTOR)
3250 RotateMirror(x, y, MB_RIGHTBUTTON);
3255 static boolean ObjHit(int obx, int oby, int bits)
3262 if (bits & HIT_POS_CENTER)
3264 if (CheckLaserPixel(cSX + obx + 15,
3269 if (bits & HIT_POS_EDGE)
3271 for (i = 0; i < 4; i++)
3272 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3273 cSY + oby + 31 * (i / 2)))
3277 if (bits & HIT_POS_BETWEEN)
3279 for (i = 0; i < 4; i++)
3280 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3281 cSY + 4 + oby + 22 * (i / 2)))
3288 static void DeletePacMan(int px, int py)
3294 if (game_mm.num_pacman <= 1)
3296 game_mm.num_pacman = 0;
3300 for (i = 0; i < game_mm.num_pacman; i++)
3301 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3304 game_mm.num_pacman--;
3306 for (j = i; j < game_mm.num_pacman; j++)
3308 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3309 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3310 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3314 static void GameActions_MM_Ext(void)
3321 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3324 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3326 element = Tile[x][y];
3328 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3329 StartMoving_MM(x, y);
3330 else if (IS_MOVING(x, y))
3331 ContinueMoving_MM(x, y);
3332 else if (IS_EXPLODING(element))
3333 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3334 else if (element == EL_EXIT_OPENING)
3336 else if (element == EL_GRAY_BALL_OPENING)
3338 else if (IS_ENVELOPE_OPENING(element))
3340 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3342 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3344 else if (IS_MIRROR(element) ||
3345 IS_MIRROR_FIXED(element) ||
3346 element == EL_PRISM)
3347 DrawFieldTwinkle(x, y);
3348 else if (element == EL_GRAY_BALL_ACTIVE ||
3349 element == EL_BOMB_ACTIVE ||
3350 element == EL_MINE_ACTIVE)
3351 DrawFieldAnimated_MM(x, y);
3352 else if (!IS_BLOCKED(x, y))
3353 DrawFieldAnimatedIfNeeded_MM(x, y);
3356 AutoRotateMirrors();
3359 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3361 // redraw after Explode_MM() ...
3363 DrawLaser(0, DL_LASER_ENABLED);
3364 laser.redraw = FALSE;
3369 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3373 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3375 DrawLaser(0, DL_LASER_DISABLED);
3380 // skip all following game actions if game is over
3381 if (game_mm.game_over)
3384 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3388 GameOver_MM(GAME_OVER_NO_ENERGY);
3393 if (FrameReached(&energy_delay))
3395 if (game_mm.energy_left > 0)
3396 game_mm.energy_left--;
3398 // when out of energy, wait another frame to play "out of time" sound
3401 element = laser.dest_element;
3404 if (element != Tile[ELX][ELY])
3406 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3407 element, Tile[ELX][ELY]);
3411 if (!laser.overloaded && laser.overload_value == 0 &&
3412 element != EL_BOMB &&
3413 element != EL_BOMB_ACTIVE &&
3414 element != EL_MINE &&
3415 element != EL_MINE_ACTIVE &&
3416 element != EL_GRAY_BALL &&
3417 element != EL_GRAY_BALL_ACTIVE &&
3418 element != EL_BLOCK_STONE &&
3419 element != EL_BLOCK_WOOD &&
3420 element != EL_FUSE_ON &&
3421 element != EL_FUEL_FULL &&
3422 !IS_WALL_ICE(element) &&
3423 !IS_WALL_AMOEBA(element))
3426 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3428 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3429 (!laser.overloaded && laser.overload_value > 0)) &&
3430 FrameReached(&overload_delay))
3432 if (laser.overloaded)
3433 laser.overload_value++;
3435 laser.overload_value--;
3437 if (game_mm.cheat_no_overload)
3439 laser.overloaded = FALSE;
3440 laser.overload_value = 0;
3443 game_mm.laser_overload_value = laser.overload_value;
3445 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3447 SetLaserColor(0xFF);
3449 DrawLaser(0, DL_LASER_ENABLED);
3452 if (!laser.overloaded)
3453 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3454 else if (setup.sound_loops)
3455 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3457 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3459 if (laser.overload_value == MAX_LASER_OVERLOAD)
3461 UpdateAndDisplayGameControlValues();
3465 GameOver_MM(GAME_OVER_OVERLOADED);
3476 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3478 if (game_mm.cheat_no_explosion)
3483 laser.dest_element = EL_EXPLODING_OPAQUE;
3488 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3490 laser.fuse_off = TRUE;
3494 DrawLaser(0, DL_LASER_DISABLED);
3495 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3498 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3500 if (!Store2[ELX][ELY]) // check if content element not yet determined
3502 int last_anim_random_frame = gfx.anim_random_frame;
3505 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3506 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3508 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3509 native_mm_level.ball_choice_mode, 0,
3510 game_mm.ball_choice_pos);
3512 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3513 gfx.anim_random_frame = last_anim_random_frame;
3515 game_mm.ball_choice_pos++;
3517 int new_element = native_mm_level.ball_content[element_pos];
3518 int new_element_base = map_wall_to_base_element(new_element);
3520 if (IS_WALL(new_element_base))
3522 // always use completely filled wall element
3523 new_element = new_element_base | 0x000f;
3525 else if (native_mm_level.rotate_ball_content &&
3526 get_num_elements(new_element) > 1)
3528 // randomly rotate newly created game element
3529 new_element = get_rotated_element(new_element, RND(16));
3532 Store[ELX][ELY] = new_element;
3533 Store2[ELX][ELY] = TRUE;
3536 if (native_mm_level.explode_ball)
3539 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3541 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3546 if (IS_WALL_ICE(element) && CT > 50)
3548 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3550 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3551 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3552 Store2[ELX][ELY] = laser.wall_mask;
3554 laser.dest_element = Tile[ELX][ELY];
3559 if (IS_WALL_AMOEBA(element) && CT > 60)
3562 int element2 = Tile[ELX][ELY];
3564 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3567 for (i = laser.num_damages - 1; i >= 0; i--)
3568 if (laser.damage[i].is_mirror)
3571 r = laser.num_edges;
3572 d = laser.num_damages;
3579 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3582 DrawLaser(0, DL_LASER_ENABLED);
3585 x = laser.damage[k1].x;
3586 y = laser.damage[k1].y;
3591 for (i = 0; i < 4; i++)
3593 if (laser.wall_mask & (1 << i))
3595 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3596 cSY + ELY * TILEY + 31 * (i / 2)))
3599 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3600 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3607 for (i = 0; i < 4; i++)
3609 if (laser.wall_mask & (1 << i))
3611 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3612 cSY + ELY * TILEY + 31 * (i / 2)))
3619 if (laser.num_beamers > 0 ||
3620 k1 < 1 || k2 < 4 || k3 < 4 ||
3621 CheckLaserPixel(cSX + ELX * TILEX + 14,
3622 cSY + ELY * TILEY + 14))
3624 laser.num_edges = r;
3625 laser.num_damages = d;
3627 DrawLaser(0, DL_LASER_DISABLED);
3630 Tile[ELX][ELY] = element | laser.wall_mask;
3632 int x = ELX, y = ELY;
3633 int wall_mask = laser.wall_mask;
3636 DrawLaser(0, DL_LASER_ENABLED);
3638 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3640 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3641 Store[x][y] = EL_WALL_AMOEBA_BASE;
3642 Store2[x][y] = wall_mask;
3647 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3648 laser.stops_inside_element && CT > native_mm_level.time_block)
3653 if (ABS(XS) > ABS(YS))
3660 for (i = 0; i < 4; i++)
3667 x = ELX + Step[k * 4].x;
3668 y = ELY + Step[k * 4].y;
3670 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3673 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3681 laser.overloaded = (element == EL_BLOCK_STONE);
3686 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3689 Tile[x][y] = element;
3691 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3694 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3696 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3697 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3705 if (element == EL_FUEL_FULL && CT > 10)
3707 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3708 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3710 for (i = start; i <= num_init_game_frames; i++)
3712 if (i == num_init_game_frames)
3713 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3714 else if (setup.sound_loops)
3715 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3717 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3719 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3721 UpdateAndDisplayGameControlValues();
3726 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3728 DrawField_MM(ELX, ELY);
3730 DrawLaser(0, DL_LASER_ENABLED);
3736 void GameActions_MM(struct MouseActionInfo action)
3738 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3739 boolean button_released = (action.button == MB_RELEASED);
3741 GameActions_MM_Ext();
3743 CheckSingleStepMode_MM(element_clicked, button_released);
3746 static void MovePacMen(void)
3748 int mx, my, ox, oy, nx, ny;
3752 if (++pacman_nr >= game_mm.num_pacman)
3755 game_mm.pacman[pacman_nr].dir--;
3757 for (l = 1; l < 5; l++)
3759 game_mm.pacman[pacman_nr].dir++;
3761 if (game_mm.pacman[pacman_nr].dir > 4)
3762 game_mm.pacman[pacman_nr].dir = 1;
3764 if (game_mm.pacman[pacman_nr].dir % 2)
3767 my = game_mm.pacman[pacman_nr].dir - 2;
3772 mx = 3 - game_mm.pacman[pacman_nr].dir;
3775 ox = game_mm.pacman[pacman_nr].x;
3776 oy = game_mm.pacman[pacman_nr].y;
3779 element = Tile[nx][ny];
3781 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3784 if (!IS_EATABLE4PACMAN(element))
3787 if (ObjHit(nx, ny, HIT_POS_CENTER))
3790 Tile[ox][oy] = EL_EMPTY;
3792 EL_PACMAN_RIGHT - 1 +
3793 (game_mm.pacman[pacman_nr].dir - 1 +
3794 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3796 game_mm.pacman[pacman_nr].x = nx;
3797 game_mm.pacman[pacman_nr].y = ny;
3799 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3801 if (element != EL_EMPTY)
3803 int graphic = el2gfx(Tile[nx][ny]);
3808 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3811 ox = cSX + ox * TILEX;
3812 oy = cSY + oy * TILEY;
3814 for (i = 1; i < 33; i += 2)
3815 BlitBitmap(bitmap, window,
3816 src_x, src_y, TILEX, TILEY,
3817 ox + i * mx, oy + i * my);
3818 Ct = Ct + FrameCounter - CT;
3821 DrawField_MM(nx, ny);
3824 if (!laser.fuse_off)
3826 DrawLaser(0, DL_LASER_ENABLED);
3828 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3830 AddDamagedField(nx, ny);
3832 laser.damage[laser.num_damages - 1].edge = 0;
3836 if (element == EL_BOMB)
3837 DeletePacMan(nx, ny);
3839 if (IS_WALL_AMOEBA(element) &&
3840 (LX + 2 * XS) / TILEX == nx &&
3841 (LY + 2 * YS) / TILEY == ny)
3851 static void InitMovingField_MM(int x, int y, int direction)
3853 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3854 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3856 MovDir[x][y] = direction;
3857 MovDir[newx][newy] = direction;
3859 if (Tile[newx][newy] == EL_EMPTY)
3860 Tile[newx][newy] = EL_BLOCKED;
3863 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3865 int direction = MovDir[x][y];
3866 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3867 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3873 static void Blocked2Moving_MM(int x, int y,
3874 int *comes_from_x, int *comes_from_y)
3876 int oldx = x, oldy = y;
3877 int direction = MovDir[x][y];
3879 if (direction == MV_LEFT)
3881 else if (direction == MV_RIGHT)
3883 else if (direction == MV_UP)
3885 else if (direction == MV_DOWN)
3888 *comes_from_x = oldx;
3889 *comes_from_y = oldy;
3892 static int MovingOrBlocked2Element_MM(int x, int y)
3894 int element = Tile[x][y];
3896 if (element == EL_BLOCKED)
3900 Blocked2Moving_MM(x, y, &oldx, &oldy);
3902 return Tile[oldx][oldy];
3908 static void RemoveMovingField_MM(int x, int y)
3910 int oldx = x, oldy = y, newx = x, newy = y;
3912 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3915 if (IS_MOVING(x, y))
3917 Moving2Blocked_MM(x, y, &newx, &newy);
3918 if (Tile[newx][newy] != EL_BLOCKED)
3921 else if (Tile[x][y] == EL_BLOCKED)
3923 Blocked2Moving_MM(x, y, &oldx, &oldy);
3924 if (!IS_MOVING(oldx, oldy))
3928 Tile[oldx][oldy] = EL_EMPTY;
3929 Tile[newx][newy] = EL_EMPTY;
3930 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3931 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3933 DrawLevelField_MM(oldx, oldy);
3934 DrawLevelField_MM(newx, newy);
3937 static void RaiseScore_MM(int value)
3939 game_mm.score += value;
3942 void RaiseScoreElement_MM(int element)
3947 case EL_PACMAN_RIGHT:
3949 case EL_PACMAN_LEFT:
3950 case EL_PACMAN_DOWN:
3951 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3955 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3960 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3964 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3973 // ----------------------------------------------------------------------------
3974 // Mirror Magic game engine snapshot handling functions
3975 // ----------------------------------------------------------------------------
3977 void SaveEngineSnapshotValues_MM(void)
3981 engine_snapshot_mm.game_mm = game_mm;
3982 engine_snapshot_mm.laser = laser;
3984 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3986 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3988 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3989 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3990 engine_snapshot_mm.Box[x][y] = Box[x][y];
3991 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3995 engine_snapshot_mm.LX = LX;
3996 engine_snapshot_mm.LY = LY;
3997 engine_snapshot_mm.XS = XS;
3998 engine_snapshot_mm.YS = YS;
3999 engine_snapshot_mm.ELX = ELX;
4000 engine_snapshot_mm.ELY = ELY;
4001 engine_snapshot_mm.CT = CT;
4002 engine_snapshot_mm.Ct = Ct;
4004 engine_snapshot_mm.last_LX = last_LX;
4005 engine_snapshot_mm.last_LY = last_LY;
4006 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4007 engine_snapshot_mm.hold_x = hold_x;
4008 engine_snapshot_mm.hold_y = hold_y;
4009 engine_snapshot_mm.pacman_nr = pacman_nr;
4011 engine_snapshot_mm.rotate_delay = rotate_delay;
4012 engine_snapshot_mm.pacman_delay = pacman_delay;
4013 engine_snapshot_mm.energy_delay = energy_delay;
4014 engine_snapshot_mm.overload_delay = overload_delay;
4017 void LoadEngineSnapshotValues_MM(void)
4021 // stored engine snapshot buffers already restored at this point
4023 game_mm = engine_snapshot_mm.game_mm;
4024 laser = engine_snapshot_mm.laser;
4026 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4028 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4030 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4031 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4032 Box[x][y] = engine_snapshot_mm.Box[x][y];
4033 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4037 LX = engine_snapshot_mm.LX;
4038 LY = engine_snapshot_mm.LY;
4039 XS = engine_snapshot_mm.XS;
4040 YS = engine_snapshot_mm.YS;
4041 ELX = engine_snapshot_mm.ELX;
4042 ELY = engine_snapshot_mm.ELY;
4043 CT = engine_snapshot_mm.CT;
4044 Ct = engine_snapshot_mm.Ct;
4046 last_LX = engine_snapshot_mm.last_LX;
4047 last_LY = engine_snapshot_mm.last_LY;
4048 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4049 hold_x = engine_snapshot_mm.hold_x;
4050 hold_y = engine_snapshot_mm.hold_y;
4051 pacman_nr = engine_snapshot_mm.pacman_nr;
4053 rotate_delay = engine_snapshot_mm.rotate_delay;
4054 pacman_delay = engine_snapshot_mm.pacman_delay;
4055 energy_delay = engine_snapshot_mm.energy_delay;
4056 overload_delay = engine_snapshot_mm.overload_delay;
4058 RedrawPlayfield_MM();
4061 static int getAngleFromTouchDelta(int dx, int dy, int base)
4063 double pi = 3.141592653;
4064 double rad = atan2((double)-dy, (double)dx);
4065 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4066 double deg = rad2 * 180.0 / pi;
4068 return (int)(deg * base / 360.0 + 0.5) % base;
4071 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4073 // calculate start (source) position to be at the middle of the tile
4074 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4075 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4076 int dx = dst_mx - src_mx;
4077 int dy = dst_my - src_my;
4086 if (!IN_LEV_FIELD(x, y))
4089 element = Tile[x][y];
4091 if (!IS_MCDUFFIN(element) &&
4092 !IS_MIRROR(element) &&
4093 !IS_BEAMER(element) &&
4094 !IS_POLAR(element) &&
4095 !IS_POLAR_CROSS(element) &&
4096 !IS_DF_MIRROR(element))
4099 angle_old = get_element_angle(element);
4101 if (IS_MCDUFFIN(element))
4103 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4104 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4105 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4106 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4109 else if (IS_MIRROR(element) ||
4110 IS_DF_MIRROR(element))
4112 for (i = 0; i < laser.num_damages; i++)
4114 if (laser.damage[i].x == x &&
4115 laser.damage[i].y == y &&
4116 ObjHit(x, y, HIT_POS_CENTER))
4118 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4119 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4126 if (angle_new == -1)
4128 if (IS_MIRROR(element) ||
4129 IS_DF_MIRROR(element) ||
4133 if (IS_POLAR_CROSS(element))
4136 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4139 button = (angle_new == angle_old ? 0 :
4140 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4141 MB_LEFTBUTTON : MB_RIGHTBUTTON);