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 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1054 /* we have hit the top-right and bottom-left element --
1055 choose the bottom-left one */
1056 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1057 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1058 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1059 ELX = (LX - 2) / TILEX;
1060 ELY = (LY + 2) / TILEY;
1063 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1065 /* we have hit the top-left and bottom-right element --
1066 choose the top-left one */
1067 // !!! SEE ABOVE !!!
1068 ELX = (LX - 2) / TILEX;
1069 ELY = (LY - 2) / TILEY;
1073 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1074 hit_mask, LX, LY, ELX, ELY);
1077 last_element = element;
1079 element = Tile[ELX][ELY];
1080 laser.dest_element = element;
1083 Debug("game:mm:ScanLaser",
1084 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1087 LX % TILEX, LY % TILEY,
1092 if (!IN_LEV_FIELD(ELX, ELY))
1093 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1097 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1098 if (element == EL_EMPTY &&
1099 IS_GRID_STEEL(last_element) &&
1100 laser.current_angle % 4) // angle is not 90°
1101 element = last_element;
1103 if (element == EL_EMPTY)
1105 if (!HitOnlyAnEdge(hit_mask))
1108 else if (element == EL_FUSE_ON)
1110 if (HitPolarizer(element, hit_mask))
1113 else if (IS_GRID(element) || IS_DF_GRID(element))
1115 if (HitPolarizer(element, hit_mask))
1118 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1119 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1121 if (HitBlock(element, hit_mask))
1128 else if (IS_MCDUFFIN(element))
1130 if (HitLaserSource(element, hit_mask))
1133 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1134 IS_RECEIVER(element))
1136 if (HitLaserDestination(element, hit_mask))
1139 else if (IS_WALL(element))
1141 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1143 if (HitReflectingWalls(element, hit_mask))
1148 if (HitAbsorbingWalls(element, hit_mask))
1154 if (HitElement(element, hit_mask))
1159 DrawLaser(rf - 1, DL_LASER_ENABLED);
1160 rf = laser.num_edges;
1162 if (!IS_DF_WALL_STEEL(element))
1164 // only used for scanning DF steel walls; reset for all other elements
1172 if (laser.dest_element != Tile[ELX][ELY])
1174 Debug("game:mm:ScanLaser",
1175 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1176 laser.dest_element, Tile[ELX][ELY]);
1180 if (!end && !laser.stops_inside_element && !StepBehind())
1183 Debug("game:mm:ScanLaser", "Go one step back");
1189 AddLaserEdge(LX, LY);
1193 DrawLaser(rf - 1, DL_LASER_ENABLED);
1195 Ct = CT = FrameCounter;
1198 if (!IN_LEV_FIELD(ELX, ELY))
1199 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1203 static void ScanLaser_FromLastMirror(void)
1205 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1208 for (i = start_pos; i >= 0; i--)
1209 if (laser.damage[i].is_mirror)
1212 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1214 DrawLaser(start_edge, DL_LASER_DISABLED);
1219 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1225 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1226 start_edge, num_edges, mode);
1231 Warn("DrawLaserExt: start_edge < 0");
1238 Warn("DrawLaserExt: num_edges < 0");
1244 if (mode == DL_LASER_DISABLED)
1246 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1250 // now draw the laser to the backbuffer and (if enabled) to the screen
1251 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1253 redraw_mask |= REDRAW_FIELD;
1255 if (mode == DL_LASER_ENABLED)
1258 // after the laser was deleted, the "damaged" graphics must be restored
1259 if (laser.num_damages)
1261 int damage_start = 0;
1264 // determine the starting edge, from which graphics need to be restored
1267 for (i = 0; i < laser.num_damages; i++)
1269 if (laser.damage[i].edge == start_edge + 1)
1278 // restore graphics from this starting edge to the end of damage list
1279 for (i = damage_start; i < laser.num_damages; i++)
1281 int lx = laser.damage[i].x;
1282 int ly = laser.damage[i].y;
1283 int element = Tile[lx][ly];
1285 if (Hit[lx][ly] == laser.damage[i].edge)
1286 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1289 if (Box[lx][ly] == laser.damage[i].edge)
1292 if (IS_DRAWABLE(element))
1293 DrawField_MM(lx, ly);
1296 elx = laser.damage[damage_start].x;
1297 ely = laser.damage[damage_start].y;
1298 element = Tile[elx][ely];
1301 if (IS_BEAMER(element))
1305 for (i = 0; i < laser.num_beamers; i++)
1306 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1308 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1309 mode, elx, ely, Hit[elx][ely], start_edge);
1310 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1311 get_element_angle(element), laser.damage[damage_start].angle);
1315 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1316 laser.num_beamers > 0 &&
1317 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1319 // element is outgoing beamer
1320 laser.num_damages = damage_start + 1;
1322 if (IS_BEAMER(element))
1323 laser.current_angle = get_element_angle(element);
1327 // element is incoming beamer or other element
1328 laser.num_damages = damage_start;
1329 laser.current_angle = laser.damage[laser.num_damages].angle;
1334 // no damages but McDuffin himself (who needs to be redrawn anyway)
1336 elx = laser.start_edge.x;
1337 ely = laser.start_edge.y;
1338 element = Tile[elx][ely];
1341 laser.num_edges = start_edge + 1;
1342 if (start_edge == 0)
1343 laser.current_angle = laser.start_angle;
1345 LX = laser.edge[start_edge].x - cSX2;
1346 LY = laser.edge[start_edge].y - cSY2;
1347 XS = 2 * Step[laser.current_angle].x;
1348 YS = 2 * Step[laser.current_angle].y;
1351 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1357 if (IS_BEAMER(element) ||
1358 IS_FIBRE_OPTIC(element) ||
1359 IS_PACMAN(element) ||
1360 IS_POLAR(element) ||
1361 IS_POLAR_CROSS(element) ||
1362 element == EL_FUSE_ON)
1367 Debug("game:mm:DrawLaserExt", "element == %d", element);
1370 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1371 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1375 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1376 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1377 (laser.num_beamers == 0 ||
1378 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1380 // element is incoming beamer or other element
1381 step_size = -step_size;
1386 if (IS_BEAMER(element))
1387 Debug("game:mm:DrawLaserExt",
1388 "start_edge == %d, laser.beamer_edge == %d",
1389 start_edge, laser.beamer_edge);
1392 LX += step_size * XS;
1393 LY += step_size * YS;
1395 else if (element != EL_EMPTY)
1404 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1409 void DrawLaser(int start_edge, int mode)
1411 // do not draw laser if fuse is off
1412 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1415 if (mode == DL_LASER_DISABLED)
1416 DeactivateLaserTargetElement();
1418 if (laser.num_edges - start_edge < 0)
1420 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1425 // check if laser is interrupted by beamer element
1426 if (laser.num_beamers > 0 &&
1427 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1429 if (mode == DL_LASER_ENABLED)
1432 int tmp_start_edge = start_edge;
1434 // draw laser segments forward from the start to the last beamer
1435 for (i = 0; i < laser.num_beamers; i++)
1437 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1439 if (tmp_num_edges <= 0)
1443 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1444 i, laser.beamer_edge[i], tmp_start_edge);
1447 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1449 tmp_start_edge = laser.beamer_edge[i];
1452 // draw last segment from last beamer to the end
1453 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1459 int last_num_edges = laser.num_edges;
1460 int num_beamers = laser.num_beamers;
1462 // delete laser segments backward from the end to the first beamer
1463 for (i = num_beamers - 1; i >= 0; i--)
1465 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1467 if (laser.beamer_edge[i] - start_edge <= 0)
1470 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1472 last_num_edges = laser.beamer_edge[i];
1473 laser.num_beamers--;
1477 if (last_num_edges - start_edge <= 0)
1478 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1479 last_num_edges, start_edge);
1482 // special case when rotating first beamer: delete laser edge on beamer
1483 // (but do not start scanning on previous edge to prevent mirror sound)
1484 if (last_num_edges - start_edge == 1 && start_edge > 0)
1485 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1487 // delete first segment from start to the first beamer
1488 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1493 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1496 game_mm.laser_enabled = mode;
1499 void DrawLaser_MM(void)
1501 DrawLaser(0, game_mm.laser_enabled);
1504 static boolean HitElement(int element, int hit_mask)
1506 if (HitOnlyAnEdge(hit_mask))
1509 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1510 element = MovingOrBlocked2Element_MM(ELX, ELY);
1513 Debug("game:mm:HitElement", "(1): element == %d", element);
1517 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1518 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1521 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1525 AddDamagedField(ELX, ELY);
1527 // this is more precise: check if laser would go through the center
1528 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1532 // prevent cutting through laser emitter with laser beam
1533 if (IS_LASER(element))
1536 // skip the whole element before continuing the scan
1544 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1546 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1548 /* skipping scan positions to the right and down skips one scan
1549 position too much, because this is only the top left scan position
1550 of totally four scan positions (plus one to the right, one to the
1551 bottom and one to the bottom right) */
1552 /* ... but only roll back scan position if more than one step done */
1562 Debug("game:mm:HitElement", "(2): element == %d", element);
1565 if (LX + 5 * XS < 0 ||
1575 Debug("game:mm:HitElement", "(3): element == %d", element);
1578 if (IS_POLAR(element) &&
1579 ((element - EL_POLAR_START) % 2 ||
1580 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1582 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1584 laser.num_damages--;
1589 if (IS_POLAR_CROSS(element) &&
1590 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1592 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1594 laser.num_damages--;
1599 if (!IS_BEAMER(element) &&
1600 !IS_FIBRE_OPTIC(element) &&
1601 !IS_GRID_WOOD(element) &&
1602 element != EL_FUEL_EMPTY)
1605 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1606 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1608 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1611 LX = ELX * TILEX + 14;
1612 LY = ELY * TILEY + 14;
1614 AddLaserEdge(LX, LY);
1617 if (IS_MIRROR(element) ||
1618 IS_MIRROR_FIXED(element) ||
1619 IS_POLAR(element) ||
1620 IS_POLAR_CROSS(element) ||
1621 IS_DF_MIRROR(element) ||
1622 IS_DF_MIRROR_AUTO(element) ||
1623 element == EL_PRISM ||
1624 element == EL_REFRACTOR)
1626 int current_angle = laser.current_angle;
1629 laser.num_damages--;
1631 AddDamagedField(ELX, ELY);
1633 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1636 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1638 if (IS_MIRROR(element) ||
1639 IS_MIRROR_FIXED(element) ||
1640 IS_DF_MIRROR(element) ||
1641 IS_DF_MIRROR_AUTO(element))
1642 laser.current_angle = get_mirrored_angle(laser.current_angle,
1643 get_element_angle(element));
1645 if (element == EL_PRISM || element == EL_REFRACTOR)
1646 laser.current_angle = RND(16);
1648 XS = 2 * Step[laser.current_angle].x;
1649 YS = 2 * Step[laser.current_angle].y;
1651 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1656 LX += step_size * XS;
1657 LY += step_size * YS;
1659 // draw sparkles on mirror
1660 if ((IS_MIRROR(element) ||
1661 IS_MIRROR_FIXED(element) ||
1662 element == EL_PRISM) &&
1663 current_angle != laser.current_angle)
1665 MovDelay[ELX][ELY] = 11; // start animation
1668 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1669 current_angle != laser.current_angle)
1670 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1673 (get_opposite_angle(laser.current_angle) ==
1674 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1676 return (laser.overloaded ? TRUE : FALSE);
1679 if (element == EL_FUEL_FULL)
1681 laser.stops_inside_element = TRUE;
1686 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1688 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1690 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1691 element == EL_MINE ? EL_MINE_ACTIVE :
1692 EL_GRAY_BALL_ACTIVE);
1694 laser.dest_element_last = Tile[ELX][ELY];
1695 laser.dest_element_last_x = ELX;
1696 laser.dest_element_last_y = ELY;
1698 if (element == EL_MINE)
1699 laser.overloaded = TRUE;
1702 if (element == EL_KETTLE ||
1703 element == EL_CELL ||
1704 element == EL_KEY ||
1705 element == EL_LIGHTBALL ||
1706 element == EL_PACMAN ||
1707 IS_PACMAN(element) ||
1708 IS_ENVELOPE(element))
1710 if (!IS_PACMAN(element) &&
1711 !IS_ENVELOPE(element))
1714 if (element == EL_PACMAN)
1717 if (element == EL_KETTLE || element == EL_CELL)
1719 if (game_mm.kettles_still_needed > 0)
1720 game_mm.kettles_still_needed--;
1722 game.snapshot.collected_item = TRUE;
1724 if (game_mm.kettles_still_needed == 0)
1728 DrawLaser(0, DL_LASER_ENABLED);
1731 else if (element == EL_KEY)
1735 else if (IS_PACMAN(element))
1737 DeletePacMan(ELX, ELY);
1739 else if (IS_ENVELOPE(element))
1741 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1744 RaiseScoreElement_MM(element);
1749 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1751 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1753 DrawLaser(0, DL_LASER_ENABLED);
1755 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1757 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1758 game_mm.lights_still_needed--;
1762 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1763 game_mm.lights_still_needed++;
1766 DrawField_MM(ELX, ELY);
1767 DrawLaser(0, DL_LASER_ENABLED);
1772 laser.stops_inside_element = TRUE;
1778 Debug("game:mm:HitElement", "(4): element == %d", element);
1781 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1782 laser.num_beamers < MAX_NUM_BEAMERS &&
1783 laser.beamer[BEAMER_NR(element)][1].num)
1785 int beamer_angle = get_element_angle(element);
1786 int beamer_nr = BEAMER_NR(element);
1790 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1793 laser.num_damages--;
1795 if (IS_FIBRE_OPTIC(element) ||
1796 laser.current_angle == get_opposite_angle(beamer_angle))
1800 LX = ELX * TILEX + 14;
1801 LY = ELY * TILEY + 14;
1803 AddLaserEdge(LX, LY);
1804 AddDamagedField(ELX, ELY);
1806 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1809 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1811 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1812 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1813 ELX = laser.beamer[beamer_nr][pos].x;
1814 ELY = laser.beamer[beamer_nr][pos].y;
1815 LX = ELX * TILEX + 14;
1816 LY = ELY * TILEY + 14;
1818 if (IS_BEAMER(element))
1820 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1821 XS = 2 * Step[laser.current_angle].x;
1822 YS = 2 * Step[laser.current_angle].y;
1825 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1827 AddLaserEdge(LX, LY);
1828 AddDamagedField(ELX, ELY);
1830 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1833 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1835 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1840 LX += step_size * XS;
1841 LY += step_size * YS;
1843 laser.num_beamers++;
1852 static boolean HitOnlyAnEdge(int hit_mask)
1854 // check if the laser hit only the edge of an element and, if so, go on
1857 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1861 if ((hit_mask == HIT_MASK_TOPLEFT ||
1862 hit_mask == HIT_MASK_TOPRIGHT ||
1863 hit_mask == HIT_MASK_BOTTOMLEFT ||
1864 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1865 laser.current_angle % 4) // angle is not 90°
1869 if (hit_mask == HIT_MASK_TOPLEFT)
1874 else if (hit_mask == HIT_MASK_TOPRIGHT)
1879 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1884 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1890 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1896 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1903 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1909 static boolean HitPolarizer(int element, int hit_mask)
1911 if (HitOnlyAnEdge(hit_mask))
1914 if (IS_DF_GRID(element))
1916 int grid_angle = get_element_angle(element);
1919 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1920 grid_angle, laser.current_angle);
1923 AddLaserEdge(LX, LY);
1924 AddDamagedField(ELX, ELY);
1927 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1929 if (laser.current_angle == grid_angle ||
1930 laser.current_angle == get_opposite_angle(grid_angle))
1932 // skip the whole element before continuing the scan
1938 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1940 if (LX/TILEX > ELX || LY/TILEY > ELY)
1942 /* skipping scan positions to the right and down skips one scan
1943 position too much, because this is only the top left scan position
1944 of totally four scan positions (plus one to the right, one to the
1945 bottom and one to the bottom right) */
1951 AddLaserEdge(LX, LY);
1957 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1959 LX / TILEX, LY / TILEY,
1960 LX % TILEX, LY % TILEY);
1965 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1967 return HitReflectingWalls(element, hit_mask);
1971 return HitAbsorbingWalls(element, hit_mask);
1974 else if (IS_GRID_STEEL(element))
1976 return HitReflectingWalls(element, hit_mask);
1978 else // IS_GRID_WOOD
1980 return HitAbsorbingWalls(element, hit_mask);
1986 static boolean HitBlock(int element, int hit_mask)
1988 boolean check = FALSE;
1990 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1991 game_mm.num_keys == 0)
1994 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1997 int ex = ELX * TILEX + 14;
1998 int ey = ELY * TILEY + 14;
2002 for (i = 1; i < 32; i++)
2007 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2012 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2013 return HitAbsorbingWalls(element, hit_mask);
2017 AddLaserEdge(LX - XS, LY - YS);
2018 AddDamagedField(ELX, ELY);
2021 Box[ELX][ELY] = laser.num_edges;
2023 return HitReflectingWalls(element, hit_mask);
2026 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2028 int xs = XS / 2, ys = YS / 2;
2029 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2030 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2032 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2033 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2035 laser.overloaded = (element == EL_GATE_STONE);
2040 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2041 (hit_mask == HIT_MASK_TOP ||
2042 hit_mask == HIT_MASK_LEFT ||
2043 hit_mask == HIT_MASK_RIGHT ||
2044 hit_mask == HIT_MASK_BOTTOM))
2045 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2046 hit_mask == HIT_MASK_BOTTOM),
2047 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2048 hit_mask == HIT_MASK_RIGHT));
2049 AddLaserEdge(LX, LY);
2055 if (element == EL_GATE_STONE && Box[ELX][ELY])
2057 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2069 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2071 int xs = XS / 2, ys = YS / 2;
2072 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2073 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2075 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2076 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2078 laser.overloaded = (element == EL_BLOCK_STONE);
2083 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2084 (hit_mask == HIT_MASK_TOP ||
2085 hit_mask == HIT_MASK_LEFT ||
2086 hit_mask == HIT_MASK_RIGHT ||
2087 hit_mask == HIT_MASK_BOTTOM))
2088 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2089 hit_mask == HIT_MASK_BOTTOM),
2090 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2091 hit_mask == HIT_MASK_RIGHT));
2092 AddDamagedField(ELX, ELY);
2094 LX = ELX * TILEX + 14;
2095 LY = ELY * TILEY + 14;
2097 AddLaserEdge(LX, LY);
2099 laser.stops_inside_element = TRUE;
2107 static boolean HitLaserSource(int element, int hit_mask)
2109 if (HitOnlyAnEdge(hit_mask))
2112 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2114 laser.overloaded = TRUE;
2119 static boolean HitLaserDestination(int element, int hit_mask)
2121 if (HitOnlyAnEdge(hit_mask))
2124 if (element != EL_EXIT_OPEN &&
2125 !(IS_RECEIVER(element) &&
2126 game_mm.kettles_still_needed == 0 &&
2127 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2129 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2134 if (IS_RECEIVER(element) ||
2135 (IS_22_5_ANGLE(laser.current_angle) &&
2136 (ELX != (LX + 6 * XS) / TILEX ||
2137 ELY != (LY + 6 * YS) / TILEY ||
2146 LX = ELX * TILEX + 14;
2147 LY = ELY * TILEY + 14;
2149 laser.stops_inside_element = TRUE;
2152 AddLaserEdge(LX, LY);
2153 AddDamagedField(ELX, ELY);
2155 if (game_mm.lights_still_needed == 0)
2157 game_mm.level_solved = TRUE;
2159 SetTileCursorActive(FALSE);
2165 static boolean HitReflectingWalls(int element, int hit_mask)
2167 // check if laser hits side of a wall with an angle that is not 90°
2168 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2169 hit_mask == HIT_MASK_LEFT ||
2170 hit_mask == HIT_MASK_RIGHT ||
2171 hit_mask == HIT_MASK_BOTTOM))
2173 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2178 if (!IS_DF_GRID(element))
2179 AddLaserEdge(LX, LY);
2181 // check if laser hits wall with an angle of 45°
2182 if (!IS_22_5_ANGLE(laser.current_angle))
2184 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2187 laser.current_angle = get_mirrored_angle(laser.current_angle,
2190 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2193 laser.current_angle = get_mirrored_angle(laser.current_angle,
2197 AddLaserEdge(LX, LY);
2199 XS = 2 * Step[laser.current_angle].x;
2200 YS = 2 * Step[laser.current_angle].y;
2204 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2206 laser.current_angle = get_mirrored_angle(laser.current_angle,
2211 if (!IS_DF_GRID(element))
2212 AddLaserEdge(LX, LY);
2217 if (!IS_DF_GRID(element))
2218 AddLaserEdge(LX, LY + YS / 2);
2221 if (!IS_DF_GRID(element))
2222 AddLaserEdge(LX, LY);
2225 YS = 2 * Step[laser.current_angle].y;
2229 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
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 + XS / 2, LY);
2246 if (!IS_DF_GRID(element))
2247 AddLaserEdge(LX, LY);
2250 XS = 2 * Step[laser.current_angle].x;
2256 // reflection at the edge of reflecting DF style wall
2257 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2259 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2260 hit_mask == HIT_MASK_TOPRIGHT) ||
2261 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2262 hit_mask == HIT_MASK_TOPLEFT) ||
2263 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2264 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2265 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2266 hit_mask == HIT_MASK_BOTTOMRIGHT))
2269 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2270 ANG_MIRROR_135 : ANG_MIRROR_45);
2272 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2274 AddDamagedField(ELX, ELY);
2275 AddLaserEdge(LX, LY);
2277 laser.current_angle = get_mirrored_angle(laser.current_angle,
2285 AddLaserEdge(LX, LY);
2291 // reflection inside an edge of reflecting DF style wall
2292 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2294 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2295 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2296 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2297 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2298 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2299 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2300 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2301 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2304 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2305 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2306 ANG_MIRROR_135 : ANG_MIRROR_45);
2308 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2311 AddDamagedField(ELX, ELY);
2314 AddLaserEdge(LX - XS, LY - YS);
2315 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2316 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2318 laser.current_angle = get_mirrored_angle(laser.current_angle,
2326 AddLaserEdge(LX, LY);
2332 // check if laser hits DF style wall with an angle of 90°
2333 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2335 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2336 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2337 (IS_VERT_ANGLE(laser.current_angle) &&
2338 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2340 // laser at last step touched nothing or the same side of the wall
2341 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2343 AddDamagedField(ELX, ELY);
2350 last_hit_mask = hit_mask;
2357 if (!HitOnlyAnEdge(hit_mask))
2359 laser.overloaded = TRUE;
2367 static boolean HitAbsorbingWalls(int element, int hit_mask)
2369 if (HitOnlyAnEdge(hit_mask))
2373 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2375 AddLaserEdge(LX - XS, LY - YS);
2382 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2384 AddLaserEdge(LX - XS, LY - YS);
2390 if (IS_WALL_WOOD(element) ||
2391 IS_DF_WALL_WOOD(element) ||
2392 IS_GRID_WOOD(element) ||
2393 IS_GRID_WOOD_FIXED(element) ||
2394 IS_GRID_WOOD_AUTO(element) ||
2395 element == EL_FUSE_ON ||
2396 element == EL_BLOCK_WOOD ||
2397 element == EL_GATE_WOOD)
2399 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2404 if (IS_WALL_ICE(element))
2408 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2409 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2411 // check if laser hits wall with an angle of 90°
2412 if (IS_90_ANGLE(laser.current_angle))
2413 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2415 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2419 for (i = 0; i < 4; i++)
2421 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2422 mask = 15 - (8 >> i);
2423 else if (ABS(XS) == 4 &&
2425 (XS > 0) == (i % 2) &&
2426 (YS < 0) == (i / 2))
2427 mask = 3 + (i / 2) * 9;
2428 else if (ABS(YS) == 4 &&
2430 (XS < 0) == (i % 2) &&
2431 (YS > 0) == (i / 2))
2432 mask = 5 + (i % 2) * 5;
2436 laser.wall_mask = mask;
2438 else if (IS_WALL_AMOEBA(element))
2440 int elx = (LX - 2 * XS) / TILEX;
2441 int ely = (LY - 2 * YS) / TILEY;
2442 int element2 = Tile[elx][ely];
2445 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2447 laser.dest_element = EL_EMPTY;
2455 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2456 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2458 if (IS_90_ANGLE(laser.current_angle))
2459 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2461 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2463 laser.wall_mask = mask;
2469 static void OpenExit(int x, int y)
2473 if (!MovDelay[x][y]) // next animation frame
2474 MovDelay[x][y] = 4 * delay;
2476 if (MovDelay[x][y]) // wait some time before next frame
2481 phase = MovDelay[x][y] / delay;
2483 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2484 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2486 if (!MovDelay[x][y])
2488 Tile[x][y] = EL_EXIT_OPEN;
2494 static void OpenGrayBall(int x, int y)
2498 if (!MovDelay[x][y]) // next animation frame
2500 if (IS_WALL(Store[x][y]))
2502 DrawWalls_MM(x, y, Store[x][y]);
2504 // copy wall tile to spare bitmap for "melting" animation
2505 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2506 TILEX, TILEY, x * TILEX, y * TILEY);
2508 DrawElement_MM(x, y, EL_GRAY_BALL);
2511 MovDelay[x][y] = 50 * delay;
2514 if (MovDelay[x][y]) // wait some time before next frame
2518 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2522 int dx = RND(26), dy = RND(26);
2524 if (IS_WALL(Store[x][y]))
2526 // copy wall tile from spare bitmap for "melting" animation
2527 bitmap = bitmap_db_field;
2533 int graphic = el2gfx(Store[x][y]);
2535 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2538 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2539 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2541 laser.redraw = TRUE;
2543 MarkTileDirty(x, y);
2546 if (!MovDelay[x][y])
2548 Tile[x][y] = Store[x][y];
2549 Store[x][y] = Store2[x][y] = 0;
2550 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2552 InitField(x, y, FALSE);
2555 ScanLaser_FromLastMirror();
2560 static void OpenEnvelope(int x, int y)
2562 int num_frames = 8; // seven frames plus final empty space
2564 if (!MovDelay[x][y]) // next animation frame
2565 MovDelay[x][y] = num_frames;
2567 if (MovDelay[x][y]) // wait some time before next frame
2569 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2573 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2575 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2576 int frame = num_frames - MovDelay[x][y] - 1;
2578 DrawGraphicAnimation_MM(x, y, graphic, frame);
2580 laser.redraw = TRUE;
2583 if (MovDelay[x][y] == 0)
2585 Tile[x][y] = EL_EMPTY;
2591 ShowEnvelope_MM(nr);
2596 static void MeltIce(int x, int y)
2601 if (!MovDelay[x][y]) // next animation frame
2602 MovDelay[x][y] = frames * delay;
2604 if (MovDelay[x][y]) // wait some time before next frame
2607 int wall_mask = Store2[x][y];
2608 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2611 phase = frames - MovDelay[x][y] / delay - 1;
2613 if (!MovDelay[x][y])
2615 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2616 Store[x][y] = Store2[x][y] = 0;
2618 DrawWalls_MM(x, y, Tile[x][y]);
2620 if (Tile[x][y] == EL_WALL_ICE_BASE)
2621 Tile[x][y] = EL_EMPTY;
2623 ScanLaser_FromLastMirror();
2625 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2627 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2629 laser.redraw = TRUE;
2634 static void GrowAmoeba(int x, int y)
2639 if (!MovDelay[x][y]) // next animation frame
2640 MovDelay[x][y] = frames * delay;
2642 if (MovDelay[x][y]) // wait some time before next frame
2645 int wall_mask = Store2[x][y];
2646 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2649 phase = MovDelay[x][y] / delay;
2651 if (!MovDelay[x][y])
2653 Tile[x][y] = real_element;
2654 Store[x][y] = Store2[x][y] = 0;
2656 DrawWalls_MM(x, y, Tile[x][y]);
2657 DrawLaser(0, DL_LASER_ENABLED);
2659 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2661 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2666 static void DrawFieldAnimated_MM(int x, int y)
2670 laser.redraw = TRUE;
2673 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2675 int element = Tile[x][y];
2676 int graphic = el2gfx(element);
2678 if (!getGraphicInfo_NewFrame(x, y, graphic))
2683 laser.redraw = TRUE;
2686 static void DrawFieldTwinkle(int x, int y)
2688 if (MovDelay[x][y] != 0) // wait some time before next frame
2694 if (MovDelay[x][y] != 0)
2696 int graphic = IMG_TWINKLE_WHITE;
2697 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2699 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2702 laser.redraw = TRUE;
2706 static void Explode_MM(int x, int y, int phase, int mode)
2708 int num_phase = 9, delay = 2;
2709 int last_phase = num_phase * delay;
2710 int half_phase = (num_phase / 2) * delay;
2712 laser.redraw = TRUE;
2714 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2716 int center_element = Tile[x][y];
2718 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2720 // put moving element to center field (and let it explode there)
2721 center_element = MovingOrBlocked2Element_MM(x, y);
2722 RemoveMovingField_MM(x, y);
2724 Tile[x][y] = center_element;
2727 Store[x][y] = center_element;
2728 Store2[x][y] = mode;
2730 Tile[x][y] = EL_EXPLODING_OPAQUE;
2732 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2733 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2736 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2738 ExplodePhase[x][y] = 1;
2744 GfxFrame[x][y] = 0; // restart explosion animation
2746 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2748 if (phase == half_phase)
2750 Tile[x][y] = EL_EXPLODING_TRANSP;
2752 if (x == ELX && y == ELY)
2756 if (phase == last_phase)
2758 if (Store[x][y] == EL_BOMB_ACTIVE)
2760 DrawLaser(0, DL_LASER_DISABLED);
2763 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2765 GameOver_MM(GAME_OVER_DELAYED);
2767 laser.overloaded = FALSE;
2769 else if (IS_MCDUFFIN(Store[x][y]))
2771 GameOver_MM(GAME_OVER_BOMB);
2774 Tile[x][y] = EL_EMPTY;
2776 Store[x][y] = Store2[x][y] = 0;
2777 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2779 InitField(x, y, FALSE);
2782 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2784 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2785 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2787 DrawGraphicAnimation_MM(x, y, graphic, frame);
2789 MarkTileDirty(x, y);
2793 static void Bang_MM(int x, int y)
2795 int element = Tile[x][y];
2797 if (IS_PACMAN(element))
2798 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2799 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2800 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2801 else if (element == EL_KEY)
2802 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2804 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2806 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2809 static void TurnRound(int x, int y)
2821 { 0, 0 }, { 0, 0 }, { 0, 0 },
2826 int left, right, back;
2830 { MV_DOWN, MV_UP, MV_RIGHT },
2831 { MV_UP, MV_DOWN, MV_LEFT },
2833 { MV_LEFT, MV_RIGHT, MV_DOWN },
2834 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2835 { MV_RIGHT, MV_LEFT, MV_UP }
2838 int element = Tile[x][y];
2839 int old_move_dir = MovDir[x][y];
2840 int right_dir = turn[old_move_dir].right;
2841 int back_dir = turn[old_move_dir].back;
2842 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2843 int right_x = x + right_dx, right_y = y + right_dy;
2845 if (element == EL_PACMAN)
2847 boolean can_turn_right = FALSE;
2849 if (IN_LEV_FIELD(right_x, right_y) &&
2850 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2851 can_turn_right = TRUE;
2854 MovDir[x][y] = right_dir;
2856 MovDir[x][y] = back_dir;
2862 static void StartMoving_MM(int x, int y)
2864 int element = Tile[x][y];
2869 if (CAN_MOVE(element))
2873 if (MovDelay[x][y]) // wait some time before next movement
2881 // now make next step
2883 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2885 if (element == EL_PACMAN &&
2886 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2887 !ObjHit(newx, newy, HIT_POS_CENTER))
2889 Store[newx][newy] = Tile[newx][newy];
2890 Tile[newx][newy] = EL_EMPTY;
2892 DrawField_MM(newx, newy);
2894 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2895 ObjHit(newx, newy, HIT_POS_CENTER))
2897 // object was running against a wall
2904 InitMovingField_MM(x, y, MovDir[x][y]);
2908 ContinueMoving_MM(x, y);
2911 static void ContinueMoving_MM(int x, int y)
2913 int element = Tile[x][y];
2914 int direction = MovDir[x][y];
2915 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2916 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2917 int horiz_move = (dx!=0);
2918 int newx = x + dx, newy = y + dy;
2919 int step = (horiz_move ? dx : dy) * TILEX / 8;
2921 MovPos[x][y] += step;
2923 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2925 Tile[x][y] = EL_EMPTY;
2926 Tile[newx][newy] = element;
2928 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2929 MovDelay[newx][newy] = 0;
2931 if (!CAN_MOVE(element))
2932 MovDir[newx][newy] = 0;
2935 DrawField_MM(newx, newy);
2937 Stop[newx][newy] = TRUE;
2939 if (element == EL_PACMAN)
2941 if (Store[newx][newy] == EL_BOMB)
2942 Bang_MM(newx, newy);
2944 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2945 (LX + 2 * XS) / TILEX == newx &&
2946 (LY + 2 * YS) / TILEY == newy)
2953 else // still moving on
2958 laser.redraw = TRUE;
2961 boolean ClickElement(int x, int y, int button)
2963 static DelayCounter click_delay = { CLICK_DELAY };
2964 static boolean new_button = TRUE;
2965 boolean element_clicked = FALSE;
2970 // initialize static variables
2971 click_delay.count = 0;
2972 click_delay.value = CLICK_DELAY;
2978 // do not rotate objects hit by the laser after the game was solved
2979 if (game_mm.level_solved && Hit[x][y])
2982 if (button == MB_RELEASED)
2985 click_delay.value = CLICK_DELAY;
2987 // release eventually hold auto-rotating mirror
2988 RotateMirror(x, y, MB_RELEASED);
2993 if (!FrameReached(&click_delay) && !new_button)
2996 if (button == MB_MIDDLEBUTTON) // middle button has no function
2999 if (!IN_LEV_FIELD(x, y))
3002 if (Tile[x][y] == EL_EMPTY)
3005 element = Tile[x][y];
3007 if (IS_MIRROR(element) ||
3008 IS_BEAMER(element) ||
3009 IS_POLAR(element) ||
3010 IS_POLAR_CROSS(element) ||
3011 IS_DF_MIRROR(element) ||
3012 IS_DF_MIRROR_AUTO(element))
3014 RotateMirror(x, y, button);
3016 element_clicked = TRUE;
3018 else if (IS_MCDUFFIN(element))
3020 if (!laser.fuse_off)
3022 DrawLaser(0, DL_LASER_DISABLED);
3029 element = get_rotated_element(element, BUTTON_ROTATION(button));
3030 laser.start_angle = get_element_angle(element);
3034 Tile[x][y] = element;
3041 if (!laser.fuse_off)
3044 element_clicked = TRUE;
3046 else if (element == EL_FUSE_ON && laser.fuse_off)
3048 if (x != laser.fuse_x || y != laser.fuse_y)
3051 laser.fuse_off = FALSE;
3052 laser.fuse_x = laser.fuse_y = -1;
3054 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3057 element_clicked = TRUE;
3059 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3061 laser.fuse_off = TRUE;
3064 laser.overloaded = FALSE;
3066 DrawLaser(0, DL_LASER_DISABLED);
3067 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3069 element_clicked = TRUE;
3071 else if (element == EL_LIGHTBALL)
3074 RaiseScoreElement_MM(element);
3075 DrawLaser(0, DL_LASER_ENABLED);
3077 element_clicked = TRUE;
3079 else if (IS_ENVELOPE(element))
3081 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3083 element_clicked = TRUE;
3086 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3089 return element_clicked;
3092 static void RotateMirror(int x, int y, int button)
3094 if (button == MB_RELEASED)
3096 // release eventually hold auto-rotating mirror
3103 if (IS_MIRROR(Tile[x][y]) ||
3104 IS_POLAR_CROSS(Tile[x][y]) ||
3105 IS_POLAR(Tile[x][y]) ||
3106 IS_BEAMER(Tile[x][y]) ||
3107 IS_DF_MIRROR(Tile[x][y]) ||
3108 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3109 IS_GRID_WOOD_AUTO(Tile[x][y]))
3111 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3113 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3115 if (button == MB_LEFTBUTTON)
3117 // left mouse button only for manual adjustment, no auto-rotating;
3118 // freeze mirror for until mouse button released
3122 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3124 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3128 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3130 int edge = Hit[x][y];
3136 DrawLaser(edge - 1, DL_LASER_DISABLED);
3140 else if (ObjHit(x, y, HIT_POS_CENTER))
3142 int edge = Hit[x][y];
3146 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3151 DrawLaser(edge - 1, DL_LASER_DISABLED);
3158 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3163 if ((IS_BEAMER(Tile[x][y]) ||
3164 IS_POLAR(Tile[x][y]) ||
3165 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3167 if (IS_BEAMER(Tile[x][y]))
3170 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3171 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3184 DrawLaser(0, DL_LASER_ENABLED);
3188 static void AutoRotateMirrors(void)
3192 if (!FrameReached(&rotate_delay))
3195 for (x = 0; x < lev_fieldx; x++)
3197 for (y = 0; y < lev_fieldy; y++)
3199 int element = Tile[x][y];
3201 // do not rotate objects hit by the laser after the game was solved
3202 if (game_mm.level_solved && Hit[x][y])
3205 if (IS_DF_MIRROR_AUTO(element) ||
3206 IS_GRID_WOOD_AUTO(element) ||
3207 IS_GRID_STEEL_AUTO(element) ||
3208 element == EL_REFRACTOR)
3209 RotateMirror(x, y, MB_RIGHTBUTTON);
3214 static boolean ObjHit(int obx, int oby, int bits)
3221 if (bits & HIT_POS_CENTER)
3223 if (CheckLaserPixel(cSX + obx + 15,
3228 if (bits & HIT_POS_EDGE)
3230 for (i = 0; i < 4; i++)
3231 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3232 cSY + oby + 31 * (i / 2)))
3236 if (bits & HIT_POS_BETWEEN)
3238 for (i = 0; i < 4; i++)
3239 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3240 cSY + 4 + oby + 22 * (i / 2)))
3247 static void DeletePacMan(int px, int py)
3253 if (game_mm.num_pacman <= 1)
3255 game_mm.num_pacman = 0;
3259 for (i = 0; i < game_mm.num_pacman; i++)
3260 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3263 game_mm.num_pacman--;
3265 for (j = i; j < game_mm.num_pacman; j++)
3267 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3268 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3269 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3273 static void GameActions_MM_Ext(void)
3280 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3283 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3285 element = Tile[x][y];
3287 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3288 StartMoving_MM(x, y);
3289 else if (IS_MOVING(x, y))
3290 ContinueMoving_MM(x, y);
3291 else if (IS_EXPLODING(element))
3292 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3293 else if (element == EL_EXIT_OPENING)
3295 else if (element == EL_GRAY_BALL_OPENING)
3297 else if (IS_ENVELOPE_OPENING(element))
3299 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3301 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3303 else if (IS_MIRROR(element) ||
3304 IS_MIRROR_FIXED(element) ||
3305 element == EL_PRISM)
3306 DrawFieldTwinkle(x, y);
3307 else if (element == EL_GRAY_BALL_ACTIVE ||
3308 element == EL_BOMB_ACTIVE ||
3309 element == EL_MINE_ACTIVE)
3310 DrawFieldAnimated_MM(x, y);
3311 else if (!IS_BLOCKED(x, y))
3312 DrawFieldAnimatedIfNeeded_MM(x, y);
3315 AutoRotateMirrors();
3318 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3320 // redraw after Explode_MM() ...
3322 DrawLaser(0, DL_LASER_ENABLED);
3323 laser.redraw = FALSE;
3328 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3332 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3334 DrawLaser(0, DL_LASER_DISABLED);
3339 // skip all following game actions if game is over
3340 if (game_mm.game_over)
3343 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3347 GameOver_MM(GAME_OVER_NO_ENERGY);
3352 if (FrameReached(&energy_delay))
3354 if (game_mm.energy_left > 0)
3355 game_mm.energy_left--;
3357 // when out of energy, wait another frame to play "out of time" sound
3360 element = laser.dest_element;
3363 if (element != Tile[ELX][ELY])
3365 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3366 element, Tile[ELX][ELY]);
3370 if (!laser.overloaded && laser.overload_value == 0 &&
3371 element != EL_BOMB &&
3372 element != EL_BOMB_ACTIVE &&
3373 element != EL_MINE &&
3374 element != EL_MINE_ACTIVE &&
3375 element != EL_GRAY_BALL &&
3376 element != EL_GRAY_BALL_ACTIVE &&
3377 element != EL_BLOCK_STONE &&
3378 element != EL_BLOCK_WOOD &&
3379 element != EL_FUSE_ON &&
3380 element != EL_FUEL_FULL &&
3381 !IS_WALL_ICE(element) &&
3382 !IS_WALL_AMOEBA(element))
3385 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3387 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3388 (!laser.overloaded && laser.overload_value > 0)) &&
3389 FrameReached(&overload_delay))
3391 if (laser.overloaded)
3392 laser.overload_value++;
3394 laser.overload_value--;
3396 if (game_mm.cheat_no_overload)
3398 laser.overloaded = FALSE;
3399 laser.overload_value = 0;
3402 game_mm.laser_overload_value = laser.overload_value;
3404 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3406 SetLaserColor(0xFF);
3408 DrawLaser(0, DL_LASER_ENABLED);
3411 if (!laser.overloaded)
3412 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3413 else if (setup.sound_loops)
3414 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3416 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3418 if (laser.overload_value == MAX_LASER_OVERLOAD)
3420 UpdateAndDisplayGameControlValues();
3424 GameOver_MM(GAME_OVER_OVERLOADED);
3435 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3437 if (game_mm.cheat_no_explosion)
3442 laser.dest_element = EL_EXPLODING_OPAQUE;
3447 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3449 laser.fuse_off = TRUE;
3453 DrawLaser(0, DL_LASER_DISABLED);
3454 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3457 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3459 if (!Store2[ELX][ELY]) // check if content element not yet determined
3461 int last_anim_random_frame = gfx.anim_random_frame;
3464 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3465 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3467 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3468 native_mm_level.ball_choice_mode, 0,
3469 game_mm.ball_choice_pos);
3471 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3472 gfx.anim_random_frame = last_anim_random_frame;
3474 game_mm.ball_choice_pos++;
3476 int new_element = native_mm_level.ball_content[element_pos];
3477 int new_element_base = map_wall_to_base_element(new_element);
3479 if (IS_WALL(new_element_base))
3481 // always use completely filled wall element
3482 new_element = new_element_base | 0x000f;
3484 else if (native_mm_level.rotate_ball_content &&
3485 get_num_elements(new_element) > 1)
3487 // randomly rotate newly created game element
3488 new_element = get_rotated_element(new_element, RND(16));
3491 Store[ELX][ELY] = new_element;
3492 Store2[ELX][ELY] = TRUE;
3495 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3497 laser.dest_element_last = Tile[ELX][ELY];
3502 if (IS_WALL_ICE(element) && CT > 50)
3504 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3506 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3507 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3508 Store2[ELX][ELY] = laser.wall_mask;
3510 laser.dest_element = Tile[ELX][ELY];
3515 if (IS_WALL_AMOEBA(element) && CT > 60)
3518 int element2 = Tile[ELX][ELY];
3520 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3523 for (i = laser.num_damages - 1; i >= 0; i--)
3524 if (laser.damage[i].is_mirror)
3527 r = laser.num_edges;
3528 d = laser.num_damages;
3535 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3538 DrawLaser(0, DL_LASER_ENABLED);
3541 x = laser.damage[k1].x;
3542 y = laser.damage[k1].y;
3547 for (i = 0; i < 4; i++)
3549 if (laser.wall_mask & (1 << i))
3551 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3552 cSY + ELY * TILEY + 31 * (i / 2)))
3555 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3556 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3563 for (i = 0; i < 4; i++)
3565 if (laser.wall_mask & (1 << i))
3567 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3568 cSY + ELY * TILEY + 31 * (i / 2)))
3575 if (laser.num_beamers > 0 ||
3576 k1 < 1 || k2 < 4 || k3 < 4 ||
3577 CheckLaserPixel(cSX + ELX * TILEX + 14,
3578 cSY + ELY * TILEY + 14))
3580 laser.num_edges = r;
3581 laser.num_damages = d;
3583 DrawLaser(0, DL_LASER_DISABLED);
3586 Tile[ELX][ELY] = element | laser.wall_mask;
3588 int x = ELX, y = ELY;
3589 int wall_mask = laser.wall_mask;
3592 DrawLaser(0, DL_LASER_ENABLED);
3594 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3596 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3597 Store[x][y] = EL_WALL_AMOEBA_BASE;
3598 Store2[x][y] = wall_mask;
3603 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3604 laser.stops_inside_element && CT > native_mm_level.time_block)
3609 if (ABS(XS) > ABS(YS))
3616 for (i = 0; i < 4; i++)
3623 x = ELX + Step[k * 4].x;
3624 y = ELY + Step[k * 4].y;
3626 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3629 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3637 laser.overloaded = (element == EL_BLOCK_STONE);
3642 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3645 Tile[x][y] = element;
3647 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3650 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3652 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3653 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3661 if (element == EL_FUEL_FULL && CT > 10)
3663 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3664 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3666 for (i = start; i <= num_init_game_frames; i++)
3668 if (i == num_init_game_frames)
3669 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3670 else if (setup.sound_loops)
3671 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3673 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3675 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3677 UpdateAndDisplayGameControlValues();
3682 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3684 DrawField_MM(ELX, ELY);
3686 DrawLaser(0, DL_LASER_ENABLED);
3692 void GameActions_MM(struct MouseActionInfo action)
3694 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3695 boolean button_released = (action.button == MB_RELEASED);
3697 GameActions_MM_Ext();
3699 CheckSingleStepMode_MM(element_clicked, button_released);
3702 static void MovePacMen(void)
3704 int mx, my, ox, oy, nx, ny;
3708 if (++pacman_nr >= game_mm.num_pacman)
3711 game_mm.pacman[pacman_nr].dir--;
3713 for (l = 1; l < 5; l++)
3715 game_mm.pacman[pacman_nr].dir++;
3717 if (game_mm.pacman[pacman_nr].dir > 4)
3718 game_mm.pacman[pacman_nr].dir = 1;
3720 if (game_mm.pacman[pacman_nr].dir % 2)
3723 my = game_mm.pacman[pacman_nr].dir - 2;
3728 mx = 3 - game_mm.pacman[pacman_nr].dir;
3731 ox = game_mm.pacman[pacman_nr].x;
3732 oy = game_mm.pacman[pacman_nr].y;
3735 element = Tile[nx][ny];
3737 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3740 if (!IS_EATABLE4PACMAN(element))
3743 if (ObjHit(nx, ny, HIT_POS_CENTER))
3746 Tile[ox][oy] = EL_EMPTY;
3748 EL_PACMAN_RIGHT - 1 +
3749 (game_mm.pacman[pacman_nr].dir - 1 +
3750 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3752 game_mm.pacman[pacman_nr].x = nx;
3753 game_mm.pacman[pacman_nr].y = ny;
3755 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3757 if (element != EL_EMPTY)
3759 int graphic = el2gfx(Tile[nx][ny]);
3764 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3767 ox = cSX + ox * TILEX;
3768 oy = cSY + oy * TILEY;
3770 for (i = 1; i < 33; i += 2)
3771 BlitBitmap(bitmap, window,
3772 src_x, src_y, TILEX, TILEY,
3773 ox + i * mx, oy + i * my);
3774 Ct = Ct + FrameCounter - CT;
3777 DrawField_MM(nx, ny);
3780 if (!laser.fuse_off)
3782 DrawLaser(0, DL_LASER_ENABLED);
3784 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3786 AddDamagedField(nx, ny);
3788 laser.damage[laser.num_damages - 1].edge = 0;
3792 if (element == EL_BOMB)
3793 DeletePacMan(nx, ny);
3795 if (IS_WALL_AMOEBA(element) &&
3796 (LX + 2 * XS) / TILEX == nx &&
3797 (LY + 2 * YS) / TILEY == ny)
3807 static void InitMovingField_MM(int x, int y, int direction)
3809 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3810 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3812 MovDir[x][y] = direction;
3813 MovDir[newx][newy] = direction;
3815 if (Tile[newx][newy] == EL_EMPTY)
3816 Tile[newx][newy] = EL_BLOCKED;
3819 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3821 int direction = MovDir[x][y];
3822 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3823 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3829 static void Blocked2Moving_MM(int x, int y,
3830 int *comes_from_x, int *comes_from_y)
3832 int oldx = x, oldy = y;
3833 int direction = MovDir[x][y];
3835 if (direction == MV_LEFT)
3837 else if (direction == MV_RIGHT)
3839 else if (direction == MV_UP)
3841 else if (direction == MV_DOWN)
3844 *comes_from_x = oldx;
3845 *comes_from_y = oldy;
3848 static int MovingOrBlocked2Element_MM(int x, int y)
3850 int element = Tile[x][y];
3852 if (element == EL_BLOCKED)
3856 Blocked2Moving_MM(x, y, &oldx, &oldy);
3858 return Tile[oldx][oldy];
3864 static void RemoveMovingField_MM(int x, int y)
3866 int oldx = x, oldy = y, newx = x, newy = y;
3868 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3871 if (IS_MOVING(x, y))
3873 Moving2Blocked_MM(x, y, &newx, &newy);
3874 if (Tile[newx][newy] != EL_BLOCKED)
3877 else if (Tile[x][y] == EL_BLOCKED)
3879 Blocked2Moving_MM(x, y, &oldx, &oldy);
3880 if (!IS_MOVING(oldx, oldy))
3884 Tile[oldx][oldy] = EL_EMPTY;
3885 Tile[newx][newy] = EL_EMPTY;
3886 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3887 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3889 DrawLevelField_MM(oldx, oldy);
3890 DrawLevelField_MM(newx, newy);
3893 static void RaiseScore_MM(int value)
3895 game_mm.score += value;
3898 void RaiseScoreElement_MM(int element)
3903 case EL_PACMAN_RIGHT:
3905 case EL_PACMAN_LEFT:
3906 case EL_PACMAN_DOWN:
3907 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3911 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3916 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3920 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3929 // ----------------------------------------------------------------------------
3930 // Mirror Magic game engine snapshot handling functions
3931 // ----------------------------------------------------------------------------
3933 void SaveEngineSnapshotValues_MM(void)
3937 engine_snapshot_mm.game_mm = game_mm;
3938 engine_snapshot_mm.laser = laser;
3940 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3942 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3944 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3945 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3946 engine_snapshot_mm.Box[x][y] = Box[x][y];
3947 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3951 engine_snapshot_mm.LX = LX;
3952 engine_snapshot_mm.LY = LY;
3953 engine_snapshot_mm.XS = XS;
3954 engine_snapshot_mm.YS = YS;
3955 engine_snapshot_mm.ELX = ELX;
3956 engine_snapshot_mm.ELY = ELY;
3957 engine_snapshot_mm.CT = CT;
3958 engine_snapshot_mm.Ct = Ct;
3960 engine_snapshot_mm.last_LX = last_LX;
3961 engine_snapshot_mm.last_LY = last_LY;
3962 engine_snapshot_mm.last_hit_mask = last_hit_mask;
3963 engine_snapshot_mm.hold_x = hold_x;
3964 engine_snapshot_mm.hold_y = hold_y;
3965 engine_snapshot_mm.pacman_nr = pacman_nr;
3967 engine_snapshot_mm.rotate_delay = rotate_delay;
3968 engine_snapshot_mm.pacman_delay = pacman_delay;
3969 engine_snapshot_mm.energy_delay = energy_delay;
3970 engine_snapshot_mm.overload_delay = overload_delay;
3973 void LoadEngineSnapshotValues_MM(void)
3977 // stored engine snapshot buffers already restored at this point
3979 game_mm = engine_snapshot_mm.game_mm;
3980 laser = engine_snapshot_mm.laser;
3982 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3984 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3986 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
3987 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
3988 Box[x][y] = engine_snapshot_mm.Box[x][y];
3989 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
3993 LX = engine_snapshot_mm.LX;
3994 LY = engine_snapshot_mm.LY;
3995 XS = engine_snapshot_mm.XS;
3996 YS = engine_snapshot_mm.YS;
3997 ELX = engine_snapshot_mm.ELX;
3998 ELY = engine_snapshot_mm.ELY;
3999 CT = engine_snapshot_mm.CT;
4000 Ct = engine_snapshot_mm.Ct;
4002 last_LX = engine_snapshot_mm.last_LX;
4003 last_LY = engine_snapshot_mm.last_LY;
4004 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4005 hold_x = engine_snapshot_mm.hold_x;
4006 hold_y = engine_snapshot_mm.hold_y;
4007 pacman_nr = engine_snapshot_mm.pacman_nr;
4009 rotate_delay = engine_snapshot_mm.rotate_delay;
4010 pacman_delay = engine_snapshot_mm.pacman_delay;
4011 energy_delay = engine_snapshot_mm.energy_delay;
4012 overload_delay = engine_snapshot_mm.overload_delay;
4014 RedrawPlayfield_MM();
4017 static int getAngleFromTouchDelta(int dx, int dy, int base)
4019 double pi = 3.141592653;
4020 double rad = atan2((double)-dy, (double)dx);
4021 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4022 double deg = rad2 * 180.0 / pi;
4024 return (int)(deg * base / 360.0 + 0.5) % base;
4027 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4029 // calculate start (source) position to be at the middle of the tile
4030 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4031 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4032 int dx = dst_mx - src_mx;
4033 int dy = dst_my - src_my;
4042 if (!IN_LEV_FIELD(x, y))
4045 element = Tile[x][y];
4047 if (!IS_MCDUFFIN(element) &&
4048 !IS_MIRROR(element) &&
4049 !IS_BEAMER(element) &&
4050 !IS_POLAR(element) &&
4051 !IS_POLAR_CROSS(element) &&
4052 !IS_DF_MIRROR(element))
4055 angle_old = get_element_angle(element);
4057 if (IS_MCDUFFIN(element))
4059 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4060 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4061 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4062 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4065 else if (IS_MIRROR(element) ||
4066 IS_DF_MIRROR(element))
4068 for (i = 0; i < laser.num_damages; i++)
4070 if (laser.damage[i].x == x &&
4071 laser.damage[i].y == y &&
4072 ObjHit(x, y, HIT_POS_CENTER))
4074 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4075 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4082 if (angle_new == -1)
4084 if (IS_MIRROR(element) ||
4085 IS_DF_MIRROR(element) ||
4089 if (IS_POLAR_CROSS(element))
4092 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4095 button = (angle_new == angle_old ? 0 :
4096 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4097 MB_LEFTBUTTON : MB_RIGHTBUTTON);