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 GfxFrame[ELX][ELY] = 0; // restart animation
1696 laser.dest_element_last = Tile[ELX][ELY];
1697 laser.dest_element_last_x = ELX;
1698 laser.dest_element_last_y = ELY;
1700 if (element == EL_MINE)
1701 laser.overloaded = TRUE;
1704 if (element == EL_KETTLE ||
1705 element == EL_CELL ||
1706 element == EL_KEY ||
1707 element == EL_LIGHTBALL ||
1708 element == EL_PACMAN ||
1709 IS_PACMAN(element) ||
1710 IS_ENVELOPE(element))
1712 if (!IS_PACMAN(element) &&
1713 !IS_ENVELOPE(element))
1716 if (element == EL_PACMAN)
1719 if (element == EL_KETTLE || element == EL_CELL)
1721 if (game_mm.kettles_still_needed > 0)
1722 game_mm.kettles_still_needed--;
1724 game.snapshot.collected_item = TRUE;
1726 if (game_mm.kettles_still_needed == 0)
1730 DrawLaser(0, DL_LASER_ENABLED);
1733 else if (element == EL_KEY)
1737 else if (IS_PACMAN(element))
1739 DeletePacMan(ELX, ELY);
1741 else if (IS_ENVELOPE(element))
1743 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1746 RaiseScoreElement_MM(element);
1751 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1753 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1755 DrawLaser(0, DL_LASER_ENABLED);
1757 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1759 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1760 game_mm.lights_still_needed--;
1764 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1765 game_mm.lights_still_needed++;
1768 DrawField_MM(ELX, ELY);
1769 DrawLaser(0, DL_LASER_ENABLED);
1774 laser.stops_inside_element = TRUE;
1780 Debug("game:mm:HitElement", "(4): element == %d", element);
1783 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1784 laser.num_beamers < MAX_NUM_BEAMERS &&
1785 laser.beamer[BEAMER_NR(element)][1].num)
1787 int beamer_angle = get_element_angle(element);
1788 int beamer_nr = BEAMER_NR(element);
1792 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1795 laser.num_damages--;
1797 if (IS_FIBRE_OPTIC(element) ||
1798 laser.current_angle == get_opposite_angle(beamer_angle))
1802 LX = ELX * TILEX + 14;
1803 LY = ELY * TILEY + 14;
1805 AddLaserEdge(LX, LY);
1806 AddDamagedField(ELX, ELY);
1808 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1811 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1813 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1814 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1815 ELX = laser.beamer[beamer_nr][pos].x;
1816 ELY = laser.beamer[beamer_nr][pos].y;
1817 LX = ELX * TILEX + 14;
1818 LY = ELY * TILEY + 14;
1820 if (IS_BEAMER(element))
1822 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1823 XS = 2 * Step[laser.current_angle].x;
1824 YS = 2 * Step[laser.current_angle].y;
1827 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1829 AddLaserEdge(LX, LY);
1830 AddDamagedField(ELX, ELY);
1832 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1835 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1837 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1842 LX += step_size * XS;
1843 LY += step_size * YS;
1845 laser.num_beamers++;
1854 static boolean HitOnlyAnEdge(int hit_mask)
1856 // check if the laser hit only the edge of an element and, if so, go on
1859 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1863 if ((hit_mask == HIT_MASK_TOPLEFT ||
1864 hit_mask == HIT_MASK_TOPRIGHT ||
1865 hit_mask == HIT_MASK_BOTTOMLEFT ||
1866 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1867 laser.current_angle % 4) // angle is not 90°
1871 if (hit_mask == HIT_MASK_TOPLEFT)
1876 else if (hit_mask == HIT_MASK_TOPRIGHT)
1881 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1886 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1892 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1898 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1905 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1911 static boolean HitPolarizer(int element, int hit_mask)
1913 if (HitOnlyAnEdge(hit_mask))
1916 if (IS_DF_GRID(element))
1918 int grid_angle = get_element_angle(element);
1921 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1922 grid_angle, laser.current_angle);
1925 AddLaserEdge(LX, LY);
1926 AddDamagedField(ELX, ELY);
1929 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1931 if (laser.current_angle == grid_angle ||
1932 laser.current_angle == get_opposite_angle(grid_angle))
1934 // skip the whole element before continuing the scan
1940 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1942 if (LX/TILEX > ELX || LY/TILEY > ELY)
1944 /* skipping scan positions to the right and down skips one scan
1945 position too much, because this is only the top left scan position
1946 of totally four scan positions (plus one to the right, one to the
1947 bottom and one to the bottom right) */
1953 AddLaserEdge(LX, LY);
1959 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1961 LX / TILEX, LY / TILEY,
1962 LX % TILEX, LY % TILEY);
1967 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1969 return HitReflectingWalls(element, hit_mask);
1973 return HitAbsorbingWalls(element, hit_mask);
1976 else if (IS_GRID_STEEL(element))
1978 return HitReflectingWalls(element, hit_mask);
1980 else // IS_GRID_WOOD
1982 return HitAbsorbingWalls(element, hit_mask);
1988 static boolean HitBlock(int element, int hit_mask)
1990 boolean check = FALSE;
1992 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1993 game_mm.num_keys == 0)
1996 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1999 int ex = ELX * TILEX + 14;
2000 int ey = ELY * TILEY + 14;
2004 for (i = 1; i < 32; i++)
2009 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2014 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2015 return HitAbsorbingWalls(element, hit_mask);
2019 AddLaserEdge(LX - XS, LY - YS);
2020 AddDamagedField(ELX, ELY);
2023 Box[ELX][ELY] = laser.num_edges;
2025 return HitReflectingWalls(element, hit_mask);
2028 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2030 int xs = XS / 2, ys = YS / 2;
2031 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2032 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2034 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2035 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2037 laser.overloaded = (element == EL_GATE_STONE);
2042 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2043 (hit_mask == HIT_MASK_TOP ||
2044 hit_mask == HIT_MASK_LEFT ||
2045 hit_mask == HIT_MASK_RIGHT ||
2046 hit_mask == HIT_MASK_BOTTOM))
2047 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2048 hit_mask == HIT_MASK_BOTTOM),
2049 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2050 hit_mask == HIT_MASK_RIGHT));
2051 AddLaserEdge(LX, LY);
2057 if (element == EL_GATE_STONE && Box[ELX][ELY])
2059 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2071 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2073 int xs = XS / 2, ys = YS / 2;
2074 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2075 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2077 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2078 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2080 laser.overloaded = (element == EL_BLOCK_STONE);
2085 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2086 (hit_mask == HIT_MASK_TOP ||
2087 hit_mask == HIT_MASK_LEFT ||
2088 hit_mask == HIT_MASK_RIGHT ||
2089 hit_mask == HIT_MASK_BOTTOM))
2090 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2091 hit_mask == HIT_MASK_BOTTOM),
2092 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2093 hit_mask == HIT_MASK_RIGHT));
2094 AddDamagedField(ELX, ELY);
2096 LX = ELX * TILEX + 14;
2097 LY = ELY * TILEY + 14;
2099 AddLaserEdge(LX, LY);
2101 laser.stops_inside_element = TRUE;
2109 static boolean HitLaserSource(int element, int hit_mask)
2111 if (HitOnlyAnEdge(hit_mask))
2114 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2116 laser.overloaded = TRUE;
2121 static boolean HitLaserDestination(int element, int hit_mask)
2123 if (HitOnlyAnEdge(hit_mask))
2126 if (element != EL_EXIT_OPEN &&
2127 !(IS_RECEIVER(element) &&
2128 game_mm.kettles_still_needed == 0 &&
2129 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2131 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2136 if (IS_RECEIVER(element) ||
2137 (IS_22_5_ANGLE(laser.current_angle) &&
2138 (ELX != (LX + 6 * XS) / TILEX ||
2139 ELY != (LY + 6 * YS) / TILEY ||
2148 LX = ELX * TILEX + 14;
2149 LY = ELY * TILEY + 14;
2151 laser.stops_inside_element = TRUE;
2154 AddLaserEdge(LX, LY);
2155 AddDamagedField(ELX, ELY);
2157 if (game_mm.lights_still_needed == 0)
2159 game_mm.level_solved = TRUE;
2161 SetTileCursorActive(FALSE);
2167 static boolean HitReflectingWalls(int element, int hit_mask)
2169 // check if laser hits side of a wall with an angle that is not 90°
2170 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2171 hit_mask == HIT_MASK_LEFT ||
2172 hit_mask == HIT_MASK_RIGHT ||
2173 hit_mask == HIT_MASK_BOTTOM))
2175 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2180 if (!IS_DF_GRID(element))
2181 AddLaserEdge(LX, LY);
2183 // check if laser hits wall with an angle of 45°
2184 if (!IS_22_5_ANGLE(laser.current_angle))
2186 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2189 laser.current_angle = get_mirrored_angle(laser.current_angle,
2192 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2195 laser.current_angle = get_mirrored_angle(laser.current_angle,
2199 AddLaserEdge(LX, LY);
2201 XS = 2 * Step[laser.current_angle].x;
2202 YS = 2 * Step[laser.current_angle].y;
2206 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2208 laser.current_angle = get_mirrored_angle(laser.current_angle,
2213 if (!IS_DF_GRID(element))
2214 AddLaserEdge(LX, LY);
2219 if (!IS_DF_GRID(element))
2220 AddLaserEdge(LX, LY + YS / 2);
2223 if (!IS_DF_GRID(element))
2224 AddLaserEdge(LX, LY);
2227 YS = 2 * Step[laser.current_angle].y;
2231 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2233 laser.current_angle = get_mirrored_angle(laser.current_angle,
2238 if (!IS_DF_GRID(element))
2239 AddLaserEdge(LX, LY);
2244 if (!IS_DF_GRID(element))
2245 AddLaserEdge(LX + XS / 2, LY);
2248 if (!IS_DF_GRID(element))
2249 AddLaserEdge(LX, LY);
2252 XS = 2 * Step[laser.current_angle].x;
2258 // reflection at the edge of reflecting DF style wall
2259 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2261 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2262 hit_mask == HIT_MASK_TOPRIGHT) ||
2263 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2264 hit_mask == HIT_MASK_TOPLEFT) ||
2265 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2266 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2267 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2268 hit_mask == HIT_MASK_BOTTOMRIGHT))
2271 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2272 ANG_MIRROR_135 : ANG_MIRROR_45);
2274 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2276 AddDamagedField(ELX, ELY);
2277 AddLaserEdge(LX, LY);
2279 laser.current_angle = get_mirrored_angle(laser.current_angle,
2287 AddLaserEdge(LX, LY);
2293 // reflection inside an edge of reflecting DF style wall
2294 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2296 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2297 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2298 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2299 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2300 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2301 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2302 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2303 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2306 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2307 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2308 ANG_MIRROR_135 : ANG_MIRROR_45);
2310 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2313 AddDamagedField(ELX, ELY);
2316 AddLaserEdge(LX - XS, LY - YS);
2317 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2318 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2320 laser.current_angle = get_mirrored_angle(laser.current_angle,
2328 AddLaserEdge(LX, LY);
2334 // check if laser hits DF style wall with an angle of 90°
2335 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2337 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2338 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2339 (IS_VERT_ANGLE(laser.current_angle) &&
2340 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2342 // laser at last step touched nothing or the same side of the wall
2343 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2345 AddDamagedField(ELX, ELY);
2352 last_hit_mask = hit_mask;
2359 if (!HitOnlyAnEdge(hit_mask))
2361 laser.overloaded = TRUE;
2369 static boolean HitAbsorbingWalls(int element, int hit_mask)
2371 if (HitOnlyAnEdge(hit_mask))
2375 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2377 AddLaserEdge(LX - XS, LY - YS);
2384 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2386 AddLaserEdge(LX - XS, LY - YS);
2392 if (IS_WALL_WOOD(element) ||
2393 IS_DF_WALL_WOOD(element) ||
2394 IS_GRID_WOOD(element) ||
2395 IS_GRID_WOOD_FIXED(element) ||
2396 IS_GRID_WOOD_AUTO(element) ||
2397 element == EL_FUSE_ON ||
2398 element == EL_BLOCK_WOOD ||
2399 element == EL_GATE_WOOD)
2401 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2406 if (IS_WALL_ICE(element))
2410 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2411 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2413 // check if laser hits wall with an angle of 90°
2414 if (IS_90_ANGLE(laser.current_angle))
2415 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2417 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2421 for (i = 0; i < 4; i++)
2423 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2424 mask = 15 - (8 >> i);
2425 else if (ABS(XS) == 4 &&
2427 (XS > 0) == (i % 2) &&
2428 (YS < 0) == (i / 2))
2429 mask = 3 + (i / 2) * 9;
2430 else if (ABS(YS) == 4 &&
2432 (XS < 0) == (i % 2) &&
2433 (YS > 0) == (i / 2))
2434 mask = 5 + (i % 2) * 5;
2438 laser.wall_mask = mask;
2440 else if (IS_WALL_AMOEBA(element))
2442 int elx = (LX - 2 * XS) / TILEX;
2443 int ely = (LY - 2 * YS) / TILEY;
2444 int element2 = Tile[elx][ely];
2447 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2449 laser.dest_element = EL_EMPTY;
2457 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2458 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2460 if (IS_90_ANGLE(laser.current_angle))
2461 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2463 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2465 laser.wall_mask = mask;
2471 static void OpenExit(int x, int y)
2475 if (!MovDelay[x][y]) // next animation frame
2476 MovDelay[x][y] = 4 * delay;
2478 if (MovDelay[x][y]) // wait some time before next frame
2483 phase = MovDelay[x][y] / delay;
2485 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2486 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2488 if (!MovDelay[x][y])
2490 Tile[x][y] = EL_EXIT_OPEN;
2496 static void OpenGrayBall(int x, int y)
2500 if (!MovDelay[x][y]) // next animation frame
2502 if (IS_WALL(Store[x][y]))
2504 DrawWalls_MM(x, y, Store[x][y]);
2506 // copy wall tile to spare bitmap for "melting" animation
2507 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2508 TILEX, TILEY, x * TILEX, y * TILEY);
2510 DrawElement_MM(x, y, EL_GRAY_BALL);
2513 MovDelay[x][y] = 50 * delay;
2516 if (MovDelay[x][y]) // wait some time before next frame
2520 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2524 int dx = RND(26), dy = RND(26);
2526 if (IS_WALL(Store[x][y]))
2528 // copy wall tile from spare bitmap for "melting" animation
2529 bitmap = bitmap_db_field;
2535 int graphic = el2gfx(Store[x][y]);
2537 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2540 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2541 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2543 laser.redraw = TRUE;
2545 MarkTileDirty(x, y);
2548 if (!MovDelay[x][y])
2550 Tile[x][y] = Store[x][y];
2551 Store[x][y] = Store2[x][y] = 0;
2552 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2554 InitField(x, y, FALSE);
2557 ScanLaser_FromLastMirror();
2562 static void OpenEnvelope(int x, int y)
2564 int num_frames = 8; // seven frames plus final empty space
2566 if (!MovDelay[x][y]) // next animation frame
2567 MovDelay[x][y] = num_frames;
2569 if (MovDelay[x][y]) // wait some time before next frame
2571 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2575 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2577 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2578 int frame = num_frames - MovDelay[x][y] - 1;
2580 DrawGraphicAnimation_MM(x, y, graphic, frame);
2582 laser.redraw = TRUE;
2585 if (MovDelay[x][y] == 0)
2587 Tile[x][y] = EL_EMPTY;
2593 ShowEnvelope_MM(nr);
2598 static void MeltIce(int x, int y)
2603 if (!MovDelay[x][y]) // next animation frame
2604 MovDelay[x][y] = frames * delay;
2606 if (MovDelay[x][y]) // wait some time before next frame
2609 int wall_mask = Store2[x][y];
2610 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2613 phase = frames - MovDelay[x][y] / delay - 1;
2615 if (!MovDelay[x][y])
2617 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2618 Store[x][y] = Store2[x][y] = 0;
2620 DrawWalls_MM(x, y, Tile[x][y]);
2622 if (Tile[x][y] == EL_WALL_ICE_BASE)
2623 Tile[x][y] = EL_EMPTY;
2625 ScanLaser_FromLastMirror();
2627 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2629 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2631 laser.redraw = TRUE;
2636 static void GrowAmoeba(int x, int y)
2641 if (!MovDelay[x][y]) // next animation frame
2642 MovDelay[x][y] = frames * delay;
2644 if (MovDelay[x][y]) // wait some time before next frame
2647 int wall_mask = Store2[x][y];
2648 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2651 phase = MovDelay[x][y] / delay;
2653 if (!MovDelay[x][y])
2655 Tile[x][y] = real_element;
2656 Store[x][y] = Store2[x][y] = 0;
2658 DrawWalls_MM(x, y, Tile[x][y]);
2659 DrawLaser(0, DL_LASER_ENABLED);
2661 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2663 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2668 static void DrawFieldAnimated_MM(int x, int y)
2672 laser.redraw = TRUE;
2675 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2677 int element = Tile[x][y];
2678 int graphic = el2gfx(element);
2680 if (!getGraphicInfo_NewFrame(x, y, graphic))
2685 laser.redraw = TRUE;
2688 static void DrawFieldTwinkle(int x, int y)
2690 if (MovDelay[x][y] != 0) // wait some time before next frame
2696 if (MovDelay[x][y] != 0)
2698 int graphic = IMG_TWINKLE_WHITE;
2699 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2701 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2704 laser.redraw = TRUE;
2708 static void Explode_MM(int x, int y, int phase, int mode)
2710 int num_phase = 9, delay = 2;
2711 int last_phase = num_phase * delay;
2712 int half_phase = (num_phase / 2) * delay;
2714 laser.redraw = TRUE;
2716 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2718 int center_element = Tile[x][y];
2720 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2722 // put moving element to center field (and let it explode there)
2723 center_element = MovingOrBlocked2Element_MM(x, y);
2724 RemoveMovingField_MM(x, y);
2726 Tile[x][y] = center_element;
2729 Store[x][y] = center_element;
2730 Store2[x][y] = mode;
2732 Tile[x][y] = EL_EXPLODING_OPAQUE;
2734 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2735 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2738 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2740 ExplodePhase[x][y] = 1;
2746 GfxFrame[x][y] = 0; // restart explosion animation
2748 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2750 if (phase == half_phase)
2752 Tile[x][y] = EL_EXPLODING_TRANSP;
2754 if (x == ELX && y == ELY)
2758 if (phase == last_phase)
2760 if (Store[x][y] == EL_BOMB_ACTIVE)
2762 DrawLaser(0, DL_LASER_DISABLED);
2765 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2767 GameOver_MM(GAME_OVER_DELAYED);
2769 laser.overloaded = FALSE;
2771 else if (IS_MCDUFFIN(Store[x][y]))
2773 GameOver_MM(GAME_OVER_BOMB);
2776 Tile[x][y] = EL_EMPTY;
2778 Store[x][y] = Store2[x][y] = 0;
2779 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2781 InitField(x, y, FALSE);
2784 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2786 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2787 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2789 DrawGraphicAnimation_MM(x, y, graphic, frame);
2791 MarkTileDirty(x, y);
2795 static void Bang_MM(int x, int y)
2797 int element = Tile[x][y];
2799 if (IS_PACMAN(element))
2800 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2801 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2802 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2803 else if (element == EL_KEY)
2804 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2806 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2808 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2811 static void TurnRound(int x, int y)
2823 { 0, 0 }, { 0, 0 }, { 0, 0 },
2828 int left, right, back;
2832 { MV_DOWN, MV_UP, MV_RIGHT },
2833 { MV_UP, MV_DOWN, MV_LEFT },
2835 { MV_LEFT, MV_RIGHT, MV_DOWN },
2836 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2837 { MV_RIGHT, MV_LEFT, MV_UP }
2840 int element = Tile[x][y];
2841 int old_move_dir = MovDir[x][y];
2842 int right_dir = turn[old_move_dir].right;
2843 int back_dir = turn[old_move_dir].back;
2844 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2845 int right_x = x + right_dx, right_y = y + right_dy;
2847 if (element == EL_PACMAN)
2849 boolean can_turn_right = FALSE;
2851 if (IN_LEV_FIELD(right_x, right_y) &&
2852 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2853 can_turn_right = TRUE;
2856 MovDir[x][y] = right_dir;
2858 MovDir[x][y] = back_dir;
2864 static void StartMoving_MM(int x, int y)
2866 int element = Tile[x][y];
2871 if (CAN_MOVE(element))
2875 if (MovDelay[x][y]) // wait some time before next movement
2883 // now make next step
2885 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2887 if (element == EL_PACMAN &&
2888 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2889 !ObjHit(newx, newy, HIT_POS_CENTER))
2891 Store[newx][newy] = Tile[newx][newy];
2892 Tile[newx][newy] = EL_EMPTY;
2894 DrawField_MM(newx, newy);
2896 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2897 ObjHit(newx, newy, HIT_POS_CENTER))
2899 // object was running against a wall
2906 InitMovingField_MM(x, y, MovDir[x][y]);
2910 ContinueMoving_MM(x, y);
2913 static void ContinueMoving_MM(int x, int y)
2915 int element = Tile[x][y];
2916 int direction = MovDir[x][y];
2917 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2918 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2919 int horiz_move = (dx!=0);
2920 int newx = x + dx, newy = y + dy;
2921 int step = (horiz_move ? dx : dy) * TILEX / 8;
2923 MovPos[x][y] += step;
2925 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2927 Tile[x][y] = EL_EMPTY;
2928 Tile[newx][newy] = element;
2930 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2931 MovDelay[newx][newy] = 0;
2933 if (!CAN_MOVE(element))
2934 MovDir[newx][newy] = 0;
2937 DrawField_MM(newx, newy);
2939 Stop[newx][newy] = TRUE;
2941 if (element == EL_PACMAN)
2943 if (Store[newx][newy] == EL_BOMB)
2944 Bang_MM(newx, newy);
2946 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2947 (LX + 2 * XS) / TILEX == newx &&
2948 (LY + 2 * YS) / TILEY == newy)
2955 else // still moving on
2960 laser.redraw = TRUE;
2963 boolean ClickElement(int x, int y, int button)
2965 static DelayCounter click_delay = { CLICK_DELAY };
2966 static boolean new_button = TRUE;
2967 boolean element_clicked = FALSE;
2972 // initialize static variables
2973 click_delay.count = 0;
2974 click_delay.value = CLICK_DELAY;
2980 // do not rotate objects hit by the laser after the game was solved
2981 if (game_mm.level_solved && Hit[x][y])
2984 if (button == MB_RELEASED)
2987 click_delay.value = CLICK_DELAY;
2989 // release eventually hold auto-rotating mirror
2990 RotateMirror(x, y, MB_RELEASED);
2995 if (!FrameReached(&click_delay) && !new_button)
2998 if (button == MB_MIDDLEBUTTON) // middle button has no function
3001 if (!IN_LEV_FIELD(x, y))
3004 if (Tile[x][y] == EL_EMPTY)
3007 element = Tile[x][y];
3009 if (IS_MIRROR(element) ||
3010 IS_BEAMER(element) ||
3011 IS_POLAR(element) ||
3012 IS_POLAR_CROSS(element) ||
3013 IS_DF_MIRROR(element) ||
3014 IS_DF_MIRROR_AUTO(element))
3016 RotateMirror(x, y, button);
3018 element_clicked = TRUE;
3020 else if (IS_MCDUFFIN(element))
3022 if (!laser.fuse_off)
3024 DrawLaser(0, DL_LASER_DISABLED);
3031 element = get_rotated_element(element, BUTTON_ROTATION(button));
3032 laser.start_angle = get_element_angle(element);
3036 Tile[x][y] = element;
3043 if (!laser.fuse_off)
3046 element_clicked = TRUE;
3048 else if (element == EL_FUSE_ON && laser.fuse_off)
3050 if (x != laser.fuse_x || y != laser.fuse_y)
3053 laser.fuse_off = FALSE;
3054 laser.fuse_x = laser.fuse_y = -1;
3056 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3059 element_clicked = TRUE;
3061 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3063 laser.fuse_off = TRUE;
3066 laser.overloaded = FALSE;
3068 DrawLaser(0, DL_LASER_DISABLED);
3069 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3071 element_clicked = TRUE;
3073 else if (element == EL_LIGHTBALL)
3076 RaiseScoreElement_MM(element);
3077 DrawLaser(0, DL_LASER_ENABLED);
3079 element_clicked = TRUE;
3081 else if (IS_ENVELOPE(element))
3083 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3085 element_clicked = TRUE;
3088 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3091 return element_clicked;
3094 static void RotateMirror(int x, int y, int button)
3096 if (button == MB_RELEASED)
3098 // release eventually hold auto-rotating mirror
3105 if (IS_MIRROR(Tile[x][y]) ||
3106 IS_POLAR_CROSS(Tile[x][y]) ||
3107 IS_POLAR(Tile[x][y]) ||
3108 IS_BEAMER(Tile[x][y]) ||
3109 IS_DF_MIRROR(Tile[x][y]) ||
3110 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3111 IS_GRID_WOOD_AUTO(Tile[x][y]))
3113 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3115 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3117 if (button == MB_LEFTBUTTON)
3119 // left mouse button only for manual adjustment, no auto-rotating;
3120 // freeze mirror for until mouse button released
3124 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3126 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3130 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3132 int edge = Hit[x][y];
3138 DrawLaser(edge - 1, DL_LASER_DISABLED);
3142 else if (ObjHit(x, y, HIT_POS_CENTER))
3144 int edge = Hit[x][y];
3148 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3153 DrawLaser(edge - 1, DL_LASER_DISABLED);
3160 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3165 if ((IS_BEAMER(Tile[x][y]) ||
3166 IS_POLAR(Tile[x][y]) ||
3167 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3169 if (IS_BEAMER(Tile[x][y]))
3172 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3173 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3186 DrawLaser(0, DL_LASER_ENABLED);
3190 static void AutoRotateMirrors(void)
3194 if (!FrameReached(&rotate_delay))
3197 for (x = 0; x < lev_fieldx; x++)
3199 for (y = 0; y < lev_fieldy; y++)
3201 int element = Tile[x][y];
3203 // do not rotate objects hit by the laser after the game was solved
3204 if (game_mm.level_solved && Hit[x][y])
3207 if (IS_DF_MIRROR_AUTO(element) ||
3208 IS_GRID_WOOD_AUTO(element) ||
3209 IS_GRID_STEEL_AUTO(element) ||
3210 element == EL_REFRACTOR)
3211 RotateMirror(x, y, MB_RIGHTBUTTON);
3216 static boolean ObjHit(int obx, int oby, int bits)
3223 if (bits & HIT_POS_CENTER)
3225 if (CheckLaserPixel(cSX + obx + 15,
3230 if (bits & HIT_POS_EDGE)
3232 for (i = 0; i < 4; i++)
3233 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3234 cSY + oby + 31 * (i / 2)))
3238 if (bits & HIT_POS_BETWEEN)
3240 for (i = 0; i < 4; i++)
3241 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3242 cSY + 4 + oby + 22 * (i / 2)))
3249 static void DeletePacMan(int px, int py)
3255 if (game_mm.num_pacman <= 1)
3257 game_mm.num_pacman = 0;
3261 for (i = 0; i < game_mm.num_pacman; i++)
3262 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3265 game_mm.num_pacman--;
3267 for (j = i; j < game_mm.num_pacman; j++)
3269 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3270 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3271 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3275 static void GameActions_MM_Ext(void)
3282 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3285 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3287 element = Tile[x][y];
3289 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3290 StartMoving_MM(x, y);
3291 else if (IS_MOVING(x, y))
3292 ContinueMoving_MM(x, y);
3293 else if (IS_EXPLODING(element))
3294 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3295 else if (element == EL_EXIT_OPENING)
3297 else if (element == EL_GRAY_BALL_OPENING)
3299 else if (IS_ENVELOPE_OPENING(element))
3301 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3303 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3305 else if (IS_MIRROR(element) ||
3306 IS_MIRROR_FIXED(element) ||
3307 element == EL_PRISM)
3308 DrawFieldTwinkle(x, y);
3309 else if (element == EL_GRAY_BALL_ACTIVE ||
3310 element == EL_BOMB_ACTIVE ||
3311 element == EL_MINE_ACTIVE)
3312 DrawFieldAnimated_MM(x, y);
3313 else if (!IS_BLOCKED(x, y))
3314 DrawFieldAnimatedIfNeeded_MM(x, y);
3317 AutoRotateMirrors();
3320 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3322 // redraw after Explode_MM() ...
3324 DrawLaser(0, DL_LASER_ENABLED);
3325 laser.redraw = FALSE;
3330 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3334 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3336 DrawLaser(0, DL_LASER_DISABLED);
3341 // skip all following game actions if game is over
3342 if (game_mm.game_over)
3345 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3349 GameOver_MM(GAME_OVER_NO_ENERGY);
3354 if (FrameReached(&energy_delay))
3356 if (game_mm.energy_left > 0)
3357 game_mm.energy_left--;
3359 // when out of energy, wait another frame to play "out of time" sound
3362 element = laser.dest_element;
3365 if (element != Tile[ELX][ELY])
3367 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3368 element, Tile[ELX][ELY]);
3372 if (!laser.overloaded && laser.overload_value == 0 &&
3373 element != EL_BOMB &&
3374 element != EL_BOMB_ACTIVE &&
3375 element != EL_MINE &&
3376 element != EL_MINE_ACTIVE &&
3377 element != EL_GRAY_BALL &&
3378 element != EL_GRAY_BALL_ACTIVE &&
3379 element != EL_BLOCK_STONE &&
3380 element != EL_BLOCK_WOOD &&
3381 element != EL_FUSE_ON &&
3382 element != EL_FUEL_FULL &&
3383 !IS_WALL_ICE(element) &&
3384 !IS_WALL_AMOEBA(element))
3387 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3389 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3390 (!laser.overloaded && laser.overload_value > 0)) &&
3391 FrameReached(&overload_delay))
3393 if (laser.overloaded)
3394 laser.overload_value++;
3396 laser.overload_value--;
3398 if (game_mm.cheat_no_overload)
3400 laser.overloaded = FALSE;
3401 laser.overload_value = 0;
3404 game_mm.laser_overload_value = laser.overload_value;
3406 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3408 SetLaserColor(0xFF);
3410 DrawLaser(0, DL_LASER_ENABLED);
3413 if (!laser.overloaded)
3414 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3415 else if (setup.sound_loops)
3416 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3418 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3420 if (laser.overload_value == MAX_LASER_OVERLOAD)
3422 UpdateAndDisplayGameControlValues();
3426 GameOver_MM(GAME_OVER_OVERLOADED);
3437 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3439 if (game_mm.cheat_no_explosion)
3444 laser.dest_element = EL_EXPLODING_OPAQUE;
3449 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3451 laser.fuse_off = TRUE;
3455 DrawLaser(0, DL_LASER_DISABLED);
3456 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3459 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3461 if (!Store2[ELX][ELY]) // check if content element not yet determined
3463 int last_anim_random_frame = gfx.anim_random_frame;
3466 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3467 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3469 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3470 native_mm_level.ball_choice_mode, 0,
3471 game_mm.ball_choice_pos);
3473 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3474 gfx.anim_random_frame = last_anim_random_frame;
3476 game_mm.ball_choice_pos++;
3478 int new_element = native_mm_level.ball_content[element_pos];
3479 int new_element_base = map_wall_to_base_element(new_element);
3481 if (IS_WALL(new_element_base))
3483 // always use completely filled wall element
3484 new_element = new_element_base | 0x000f;
3486 else if (native_mm_level.rotate_ball_content &&
3487 get_num_elements(new_element) > 1)
3489 // randomly rotate newly created game element
3490 new_element = get_rotated_element(new_element, RND(16));
3493 Store[ELX][ELY] = new_element;
3494 Store2[ELX][ELY] = TRUE;
3497 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3499 laser.dest_element_last = Tile[ELX][ELY];
3504 if (IS_WALL_ICE(element) && CT > 50)
3506 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3508 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3509 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3510 Store2[ELX][ELY] = laser.wall_mask;
3512 laser.dest_element = Tile[ELX][ELY];
3517 if (IS_WALL_AMOEBA(element) && CT > 60)
3520 int element2 = Tile[ELX][ELY];
3522 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3525 for (i = laser.num_damages - 1; i >= 0; i--)
3526 if (laser.damage[i].is_mirror)
3529 r = laser.num_edges;
3530 d = laser.num_damages;
3537 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3540 DrawLaser(0, DL_LASER_ENABLED);
3543 x = laser.damage[k1].x;
3544 y = laser.damage[k1].y;
3549 for (i = 0; i < 4; i++)
3551 if (laser.wall_mask & (1 << i))
3553 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3554 cSY + ELY * TILEY + 31 * (i / 2)))
3557 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3558 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3565 for (i = 0; i < 4; i++)
3567 if (laser.wall_mask & (1 << i))
3569 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3570 cSY + ELY * TILEY + 31 * (i / 2)))
3577 if (laser.num_beamers > 0 ||
3578 k1 < 1 || k2 < 4 || k3 < 4 ||
3579 CheckLaserPixel(cSX + ELX * TILEX + 14,
3580 cSY + ELY * TILEY + 14))
3582 laser.num_edges = r;
3583 laser.num_damages = d;
3585 DrawLaser(0, DL_LASER_DISABLED);
3588 Tile[ELX][ELY] = element | laser.wall_mask;
3590 int x = ELX, y = ELY;
3591 int wall_mask = laser.wall_mask;
3594 DrawLaser(0, DL_LASER_ENABLED);
3596 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3598 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3599 Store[x][y] = EL_WALL_AMOEBA_BASE;
3600 Store2[x][y] = wall_mask;
3605 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3606 laser.stops_inside_element && CT > native_mm_level.time_block)
3611 if (ABS(XS) > ABS(YS))
3618 for (i = 0; i < 4; i++)
3625 x = ELX + Step[k * 4].x;
3626 y = ELY + Step[k * 4].y;
3628 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3631 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3639 laser.overloaded = (element == EL_BLOCK_STONE);
3644 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3647 Tile[x][y] = element;
3649 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3652 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3654 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3655 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3663 if (element == EL_FUEL_FULL && CT > 10)
3665 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3666 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3668 for (i = start; i <= num_init_game_frames; i++)
3670 if (i == num_init_game_frames)
3671 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3672 else if (setup.sound_loops)
3673 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3675 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3677 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3679 UpdateAndDisplayGameControlValues();
3684 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3686 DrawField_MM(ELX, ELY);
3688 DrawLaser(0, DL_LASER_ENABLED);
3694 void GameActions_MM(struct MouseActionInfo action)
3696 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3697 boolean button_released = (action.button == MB_RELEASED);
3699 GameActions_MM_Ext();
3701 CheckSingleStepMode_MM(element_clicked, button_released);
3704 static void MovePacMen(void)
3706 int mx, my, ox, oy, nx, ny;
3710 if (++pacman_nr >= game_mm.num_pacman)
3713 game_mm.pacman[pacman_nr].dir--;
3715 for (l = 1; l < 5; l++)
3717 game_mm.pacman[pacman_nr].dir++;
3719 if (game_mm.pacman[pacman_nr].dir > 4)
3720 game_mm.pacman[pacman_nr].dir = 1;
3722 if (game_mm.pacman[pacman_nr].dir % 2)
3725 my = game_mm.pacman[pacman_nr].dir - 2;
3730 mx = 3 - game_mm.pacman[pacman_nr].dir;
3733 ox = game_mm.pacman[pacman_nr].x;
3734 oy = game_mm.pacman[pacman_nr].y;
3737 element = Tile[nx][ny];
3739 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3742 if (!IS_EATABLE4PACMAN(element))
3745 if (ObjHit(nx, ny, HIT_POS_CENTER))
3748 Tile[ox][oy] = EL_EMPTY;
3750 EL_PACMAN_RIGHT - 1 +
3751 (game_mm.pacman[pacman_nr].dir - 1 +
3752 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3754 game_mm.pacman[pacman_nr].x = nx;
3755 game_mm.pacman[pacman_nr].y = ny;
3757 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3759 if (element != EL_EMPTY)
3761 int graphic = el2gfx(Tile[nx][ny]);
3766 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3769 ox = cSX + ox * TILEX;
3770 oy = cSY + oy * TILEY;
3772 for (i = 1; i < 33; i += 2)
3773 BlitBitmap(bitmap, window,
3774 src_x, src_y, TILEX, TILEY,
3775 ox + i * mx, oy + i * my);
3776 Ct = Ct + FrameCounter - CT;
3779 DrawField_MM(nx, ny);
3782 if (!laser.fuse_off)
3784 DrawLaser(0, DL_LASER_ENABLED);
3786 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3788 AddDamagedField(nx, ny);
3790 laser.damage[laser.num_damages - 1].edge = 0;
3794 if (element == EL_BOMB)
3795 DeletePacMan(nx, ny);
3797 if (IS_WALL_AMOEBA(element) &&
3798 (LX + 2 * XS) / TILEX == nx &&
3799 (LY + 2 * YS) / TILEY == ny)
3809 static void InitMovingField_MM(int x, int y, int direction)
3811 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3812 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3814 MovDir[x][y] = direction;
3815 MovDir[newx][newy] = direction;
3817 if (Tile[newx][newy] == EL_EMPTY)
3818 Tile[newx][newy] = EL_BLOCKED;
3821 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3823 int direction = MovDir[x][y];
3824 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3825 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3831 static void Blocked2Moving_MM(int x, int y,
3832 int *comes_from_x, int *comes_from_y)
3834 int oldx = x, oldy = y;
3835 int direction = MovDir[x][y];
3837 if (direction == MV_LEFT)
3839 else if (direction == MV_RIGHT)
3841 else if (direction == MV_UP)
3843 else if (direction == MV_DOWN)
3846 *comes_from_x = oldx;
3847 *comes_from_y = oldy;
3850 static int MovingOrBlocked2Element_MM(int x, int y)
3852 int element = Tile[x][y];
3854 if (element == EL_BLOCKED)
3858 Blocked2Moving_MM(x, y, &oldx, &oldy);
3860 return Tile[oldx][oldy];
3866 static void RemoveMovingField_MM(int x, int y)
3868 int oldx = x, oldy = y, newx = x, newy = y;
3870 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3873 if (IS_MOVING(x, y))
3875 Moving2Blocked_MM(x, y, &newx, &newy);
3876 if (Tile[newx][newy] != EL_BLOCKED)
3879 else if (Tile[x][y] == EL_BLOCKED)
3881 Blocked2Moving_MM(x, y, &oldx, &oldy);
3882 if (!IS_MOVING(oldx, oldy))
3886 Tile[oldx][oldy] = EL_EMPTY;
3887 Tile[newx][newy] = EL_EMPTY;
3888 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3889 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3891 DrawLevelField_MM(oldx, oldy);
3892 DrawLevelField_MM(newx, newy);
3895 static void RaiseScore_MM(int value)
3897 game_mm.score += value;
3900 void RaiseScoreElement_MM(int element)
3905 case EL_PACMAN_RIGHT:
3907 case EL_PACMAN_LEFT:
3908 case EL_PACMAN_DOWN:
3909 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3913 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3918 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3922 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3931 // ----------------------------------------------------------------------------
3932 // Mirror Magic game engine snapshot handling functions
3933 // ----------------------------------------------------------------------------
3935 void SaveEngineSnapshotValues_MM(void)
3939 engine_snapshot_mm.game_mm = game_mm;
3940 engine_snapshot_mm.laser = laser;
3942 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3944 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3946 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3947 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3948 engine_snapshot_mm.Box[x][y] = Box[x][y];
3949 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3953 engine_snapshot_mm.LX = LX;
3954 engine_snapshot_mm.LY = LY;
3955 engine_snapshot_mm.XS = XS;
3956 engine_snapshot_mm.YS = YS;
3957 engine_snapshot_mm.ELX = ELX;
3958 engine_snapshot_mm.ELY = ELY;
3959 engine_snapshot_mm.CT = CT;
3960 engine_snapshot_mm.Ct = Ct;
3962 engine_snapshot_mm.last_LX = last_LX;
3963 engine_snapshot_mm.last_LY = last_LY;
3964 engine_snapshot_mm.last_hit_mask = last_hit_mask;
3965 engine_snapshot_mm.hold_x = hold_x;
3966 engine_snapshot_mm.hold_y = hold_y;
3967 engine_snapshot_mm.pacman_nr = pacman_nr;
3969 engine_snapshot_mm.rotate_delay = rotate_delay;
3970 engine_snapshot_mm.pacman_delay = pacman_delay;
3971 engine_snapshot_mm.energy_delay = energy_delay;
3972 engine_snapshot_mm.overload_delay = overload_delay;
3975 void LoadEngineSnapshotValues_MM(void)
3979 // stored engine snapshot buffers already restored at this point
3981 game_mm = engine_snapshot_mm.game_mm;
3982 laser = engine_snapshot_mm.laser;
3984 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3986 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3988 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
3989 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
3990 Box[x][y] = engine_snapshot_mm.Box[x][y];
3991 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
3995 LX = engine_snapshot_mm.LX;
3996 LY = engine_snapshot_mm.LY;
3997 XS = engine_snapshot_mm.XS;
3998 YS = engine_snapshot_mm.YS;
3999 ELX = engine_snapshot_mm.ELX;
4000 ELY = engine_snapshot_mm.ELY;
4001 CT = engine_snapshot_mm.CT;
4002 Ct = engine_snapshot_mm.Ct;
4004 last_LX = engine_snapshot_mm.last_LX;
4005 last_LY = engine_snapshot_mm.last_LY;
4006 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4007 hold_x = engine_snapshot_mm.hold_x;
4008 hold_y = engine_snapshot_mm.hold_y;
4009 pacman_nr = engine_snapshot_mm.pacman_nr;
4011 rotate_delay = engine_snapshot_mm.rotate_delay;
4012 pacman_delay = engine_snapshot_mm.pacman_delay;
4013 energy_delay = engine_snapshot_mm.energy_delay;
4014 overload_delay = engine_snapshot_mm.overload_delay;
4016 RedrawPlayfield_MM();
4019 static int getAngleFromTouchDelta(int dx, int dy, int base)
4021 double pi = 3.141592653;
4022 double rad = atan2((double)-dy, (double)dx);
4023 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4024 double deg = rad2 * 180.0 / pi;
4026 return (int)(deg * base / 360.0 + 0.5) % base;
4029 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4031 // calculate start (source) position to be at the middle of the tile
4032 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4033 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4034 int dx = dst_mx - src_mx;
4035 int dy = dst_my - src_my;
4044 if (!IN_LEV_FIELD(x, y))
4047 element = Tile[x][y];
4049 if (!IS_MCDUFFIN(element) &&
4050 !IS_MIRROR(element) &&
4051 !IS_BEAMER(element) &&
4052 !IS_POLAR(element) &&
4053 !IS_POLAR_CROSS(element) &&
4054 !IS_DF_MIRROR(element))
4057 angle_old = get_element_angle(element);
4059 if (IS_MCDUFFIN(element))
4061 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4062 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4063 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4064 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4067 else if (IS_MIRROR(element) ||
4068 IS_DF_MIRROR(element))
4070 for (i = 0; i < laser.num_damages; i++)
4072 if (laser.damage[i].x == x &&
4073 laser.damage[i].y == y &&
4074 ObjHit(x, y, HIT_POS_CENTER))
4076 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4077 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4084 if (angle_new == -1)
4086 if (IS_MIRROR(element) ||
4087 IS_DF_MIRROR(element) ||
4091 if (IS_POLAR_CROSS(element))
4094 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4097 button = (angle_new == angle_old ? 0 :
4098 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4099 MB_LEFTBUTTON : MB_RIGHTBUTTON);