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;
2715 laser.redraw = TRUE;
2717 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2719 center_element = Tile[x][y];
2721 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2723 // put moving element to center field (and let it explode there)
2724 center_element = MovingOrBlocked2Element_MM(x, y);
2725 RemoveMovingField_MM(x, y);
2727 Tile[x][y] = center_element;
2730 Store[x][y] = EL_EMPTY;
2731 Store2[x][y] = center_element;
2733 Tile[x][y] = EL_EXPLODING_OPAQUE;
2735 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2736 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2739 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2741 ExplodePhase[x][y] = 1;
2747 GfxFrame[x][y] = 0; // restart explosion animation
2749 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2751 center_element = Store2[x][y];
2753 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2755 Tile[x][y] = EL_EXPLODING_TRANSP;
2757 if (x == ELX && y == ELY)
2761 if (phase == last_phase)
2763 if (center_element == EL_BOMB_ACTIVE)
2765 DrawLaser(0, DL_LASER_DISABLED);
2768 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2770 GameOver_MM(GAME_OVER_DELAYED);
2772 laser.overloaded = FALSE;
2774 else if (IS_MCDUFFIN(center_element))
2776 GameOver_MM(GAME_OVER_BOMB);
2779 Tile[x][y] = Store[x][y];
2781 Store[x][y] = Store2[x][y] = 0;
2782 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2784 InitField(x, y, FALSE);
2787 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2789 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2790 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2792 DrawGraphicAnimation_MM(x, y, graphic, frame);
2794 MarkTileDirty(x, y);
2798 static void Bang_MM(int x, int y)
2800 int element = Tile[x][y];
2802 if (IS_PACMAN(element))
2803 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2804 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2805 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2806 else if (element == EL_KEY)
2807 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2809 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2811 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2814 static void TurnRound(int x, int y)
2826 { 0, 0 }, { 0, 0 }, { 0, 0 },
2831 int left, right, back;
2835 { MV_DOWN, MV_UP, MV_RIGHT },
2836 { MV_UP, MV_DOWN, MV_LEFT },
2838 { MV_LEFT, MV_RIGHT, MV_DOWN },
2839 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2840 { MV_RIGHT, MV_LEFT, MV_UP }
2843 int element = Tile[x][y];
2844 int old_move_dir = MovDir[x][y];
2845 int right_dir = turn[old_move_dir].right;
2846 int back_dir = turn[old_move_dir].back;
2847 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2848 int right_x = x + right_dx, right_y = y + right_dy;
2850 if (element == EL_PACMAN)
2852 boolean can_turn_right = FALSE;
2854 if (IN_LEV_FIELD(right_x, right_y) &&
2855 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2856 can_turn_right = TRUE;
2859 MovDir[x][y] = right_dir;
2861 MovDir[x][y] = back_dir;
2867 static void StartMoving_MM(int x, int y)
2869 int element = Tile[x][y];
2874 if (CAN_MOVE(element))
2878 if (MovDelay[x][y]) // wait some time before next movement
2886 // now make next step
2888 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2890 if (element == EL_PACMAN &&
2891 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2892 !ObjHit(newx, newy, HIT_POS_CENTER))
2894 Store[newx][newy] = Tile[newx][newy];
2895 Tile[newx][newy] = EL_EMPTY;
2897 DrawField_MM(newx, newy);
2899 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2900 ObjHit(newx, newy, HIT_POS_CENTER))
2902 // object was running against a wall
2909 InitMovingField_MM(x, y, MovDir[x][y]);
2913 ContinueMoving_MM(x, y);
2916 static void ContinueMoving_MM(int x, int y)
2918 int element = Tile[x][y];
2919 int direction = MovDir[x][y];
2920 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2921 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2922 int horiz_move = (dx!=0);
2923 int newx = x + dx, newy = y + dy;
2924 int step = (horiz_move ? dx : dy) * TILEX / 8;
2926 MovPos[x][y] += step;
2928 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2930 Tile[x][y] = EL_EMPTY;
2931 Tile[newx][newy] = element;
2933 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2934 MovDelay[newx][newy] = 0;
2936 if (!CAN_MOVE(element))
2937 MovDir[newx][newy] = 0;
2940 DrawField_MM(newx, newy);
2942 Stop[newx][newy] = TRUE;
2944 if (element == EL_PACMAN)
2946 if (Store[newx][newy] == EL_BOMB)
2947 Bang_MM(newx, newy);
2949 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2950 (LX + 2 * XS) / TILEX == newx &&
2951 (LY + 2 * YS) / TILEY == newy)
2958 else // still moving on
2963 laser.redraw = TRUE;
2966 boolean ClickElement(int x, int y, int button)
2968 static DelayCounter click_delay = { CLICK_DELAY };
2969 static boolean new_button = TRUE;
2970 boolean element_clicked = FALSE;
2975 // initialize static variables
2976 click_delay.count = 0;
2977 click_delay.value = CLICK_DELAY;
2983 // do not rotate objects hit by the laser after the game was solved
2984 if (game_mm.level_solved && Hit[x][y])
2987 if (button == MB_RELEASED)
2990 click_delay.value = CLICK_DELAY;
2992 // release eventually hold auto-rotating mirror
2993 RotateMirror(x, y, MB_RELEASED);
2998 if (!FrameReached(&click_delay) && !new_button)
3001 if (button == MB_MIDDLEBUTTON) // middle button has no function
3004 if (!IN_LEV_FIELD(x, y))
3007 if (Tile[x][y] == EL_EMPTY)
3010 element = Tile[x][y];
3012 if (IS_MIRROR(element) ||
3013 IS_BEAMER(element) ||
3014 IS_POLAR(element) ||
3015 IS_POLAR_CROSS(element) ||
3016 IS_DF_MIRROR(element) ||
3017 IS_DF_MIRROR_AUTO(element))
3019 RotateMirror(x, y, button);
3021 element_clicked = TRUE;
3023 else if (IS_MCDUFFIN(element))
3025 if (!laser.fuse_off)
3027 DrawLaser(0, DL_LASER_DISABLED);
3034 element = get_rotated_element(element, BUTTON_ROTATION(button));
3035 laser.start_angle = get_element_angle(element);
3039 Tile[x][y] = element;
3046 if (!laser.fuse_off)
3049 element_clicked = TRUE;
3051 else if (element == EL_FUSE_ON && laser.fuse_off)
3053 if (x != laser.fuse_x || y != laser.fuse_y)
3056 laser.fuse_off = FALSE;
3057 laser.fuse_x = laser.fuse_y = -1;
3059 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3062 element_clicked = TRUE;
3064 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3066 laser.fuse_off = TRUE;
3069 laser.overloaded = FALSE;
3071 DrawLaser(0, DL_LASER_DISABLED);
3072 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3074 element_clicked = TRUE;
3076 else if (element == EL_LIGHTBALL)
3079 RaiseScoreElement_MM(element);
3080 DrawLaser(0, DL_LASER_ENABLED);
3082 element_clicked = TRUE;
3084 else if (IS_ENVELOPE(element))
3086 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3088 element_clicked = TRUE;
3091 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3094 return element_clicked;
3097 static void RotateMirror(int x, int y, int button)
3099 if (button == MB_RELEASED)
3101 // release eventually hold auto-rotating mirror
3108 if (IS_MIRROR(Tile[x][y]) ||
3109 IS_POLAR_CROSS(Tile[x][y]) ||
3110 IS_POLAR(Tile[x][y]) ||
3111 IS_BEAMER(Tile[x][y]) ||
3112 IS_DF_MIRROR(Tile[x][y]) ||
3113 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3114 IS_GRID_WOOD_AUTO(Tile[x][y]))
3116 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3118 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3120 if (button == MB_LEFTBUTTON)
3122 // left mouse button only for manual adjustment, no auto-rotating;
3123 // freeze mirror for until mouse button released
3127 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3129 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3133 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3135 int edge = Hit[x][y];
3141 DrawLaser(edge - 1, DL_LASER_DISABLED);
3145 else if (ObjHit(x, y, HIT_POS_CENTER))
3147 int edge = Hit[x][y];
3151 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3156 DrawLaser(edge - 1, DL_LASER_DISABLED);
3163 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3168 if ((IS_BEAMER(Tile[x][y]) ||
3169 IS_POLAR(Tile[x][y]) ||
3170 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3172 if (IS_BEAMER(Tile[x][y]))
3175 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3176 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3189 DrawLaser(0, DL_LASER_ENABLED);
3193 static void AutoRotateMirrors(void)
3197 if (!FrameReached(&rotate_delay))
3200 for (x = 0; x < lev_fieldx; x++)
3202 for (y = 0; y < lev_fieldy; y++)
3204 int element = Tile[x][y];
3206 // do not rotate objects hit by the laser after the game was solved
3207 if (game_mm.level_solved && Hit[x][y])
3210 if (IS_DF_MIRROR_AUTO(element) ||
3211 IS_GRID_WOOD_AUTO(element) ||
3212 IS_GRID_STEEL_AUTO(element) ||
3213 element == EL_REFRACTOR)
3214 RotateMirror(x, y, MB_RIGHTBUTTON);
3219 static boolean ObjHit(int obx, int oby, int bits)
3226 if (bits & HIT_POS_CENTER)
3228 if (CheckLaserPixel(cSX + obx + 15,
3233 if (bits & HIT_POS_EDGE)
3235 for (i = 0; i < 4; i++)
3236 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3237 cSY + oby + 31 * (i / 2)))
3241 if (bits & HIT_POS_BETWEEN)
3243 for (i = 0; i < 4; i++)
3244 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3245 cSY + 4 + oby + 22 * (i / 2)))
3252 static void DeletePacMan(int px, int py)
3258 if (game_mm.num_pacman <= 1)
3260 game_mm.num_pacman = 0;
3264 for (i = 0; i < game_mm.num_pacman; i++)
3265 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3268 game_mm.num_pacman--;
3270 for (j = i; j < game_mm.num_pacman; j++)
3272 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3273 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3274 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3278 static void GameActions_MM_Ext(void)
3285 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3288 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3290 element = Tile[x][y];
3292 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3293 StartMoving_MM(x, y);
3294 else if (IS_MOVING(x, y))
3295 ContinueMoving_MM(x, y);
3296 else if (IS_EXPLODING(element))
3297 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3298 else if (element == EL_EXIT_OPENING)
3300 else if (element == EL_GRAY_BALL_OPENING)
3302 else if (IS_ENVELOPE_OPENING(element))
3304 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3306 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3308 else if (IS_MIRROR(element) ||
3309 IS_MIRROR_FIXED(element) ||
3310 element == EL_PRISM)
3311 DrawFieldTwinkle(x, y);
3312 else if (element == EL_GRAY_BALL_ACTIVE ||
3313 element == EL_BOMB_ACTIVE ||
3314 element == EL_MINE_ACTIVE)
3315 DrawFieldAnimated_MM(x, y);
3316 else if (!IS_BLOCKED(x, y))
3317 DrawFieldAnimatedIfNeeded_MM(x, y);
3320 AutoRotateMirrors();
3323 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3325 // redraw after Explode_MM() ...
3327 DrawLaser(0, DL_LASER_ENABLED);
3328 laser.redraw = FALSE;
3333 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3337 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3339 DrawLaser(0, DL_LASER_DISABLED);
3344 // skip all following game actions if game is over
3345 if (game_mm.game_over)
3348 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3352 GameOver_MM(GAME_OVER_NO_ENERGY);
3357 if (FrameReached(&energy_delay))
3359 if (game_mm.energy_left > 0)
3360 game_mm.energy_left--;
3362 // when out of energy, wait another frame to play "out of time" sound
3365 element = laser.dest_element;
3368 if (element != Tile[ELX][ELY])
3370 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3371 element, Tile[ELX][ELY]);
3375 if (!laser.overloaded && laser.overload_value == 0 &&
3376 element != EL_BOMB &&
3377 element != EL_BOMB_ACTIVE &&
3378 element != EL_MINE &&
3379 element != EL_MINE_ACTIVE &&
3380 element != EL_GRAY_BALL &&
3381 element != EL_GRAY_BALL_ACTIVE &&
3382 element != EL_BLOCK_STONE &&
3383 element != EL_BLOCK_WOOD &&
3384 element != EL_FUSE_ON &&
3385 element != EL_FUEL_FULL &&
3386 !IS_WALL_ICE(element) &&
3387 !IS_WALL_AMOEBA(element))
3390 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3392 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3393 (!laser.overloaded && laser.overload_value > 0)) &&
3394 FrameReached(&overload_delay))
3396 if (laser.overloaded)
3397 laser.overload_value++;
3399 laser.overload_value--;
3401 if (game_mm.cheat_no_overload)
3403 laser.overloaded = FALSE;
3404 laser.overload_value = 0;
3407 game_mm.laser_overload_value = laser.overload_value;
3409 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3411 SetLaserColor(0xFF);
3413 DrawLaser(0, DL_LASER_ENABLED);
3416 if (!laser.overloaded)
3417 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3418 else if (setup.sound_loops)
3419 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3421 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3423 if (laser.overload_value == MAX_LASER_OVERLOAD)
3425 UpdateAndDisplayGameControlValues();
3429 GameOver_MM(GAME_OVER_OVERLOADED);
3440 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3442 if (game_mm.cheat_no_explosion)
3447 laser.dest_element = EL_EXPLODING_OPAQUE;
3452 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3454 laser.fuse_off = TRUE;
3458 DrawLaser(0, DL_LASER_DISABLED);
3459 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3462 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3464 if (!Store2[ELX][ELY]) // check if content element not yet determined
3466 int last_anim_random_frame = gfx.anim_random_frame;
3469 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3470 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3472 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3473 native_mm_level.ball_choice_mode, 0,
3474 game_mm.ball_choice_pos);
3476 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3477 gfx.anim_random_frame = last_anim_random_frame;
3479 game_mm.ball_choice_pos++;
3481 int new_element = native_mm_level.ball_content[element_pos];
3482 int new_element_base = map_wall_to_base_element(new_element);
3484 if (IS_WALL(new_element_base))
3486 // always use completely filled wall element
3487 new_element = new_element_base | 0x000f;
3489 else if (native_mm_level.rotate_ball_content &&
3490 get_num_elements(new_element) > 1)
3492 // randomly rotate newly created game element
3493 new_element = get_rotated_element(new_element, RND(16));
3496 Store[ELX][ELY] = new_element;
3497 Store2[ELX][ELY] = TRUE;
3500 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3502 laser.dest_element_last = Tile[ELX][ELY];
3507 if (IS_WALL_ICE(element) && CT > 50)
3509 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3511 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3512 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3513 Store2[ELX][ELY] = laser.wall_mask;
3515 laser.dest_element = Tile[ELX][ELY];
3520 if (IS_WALL_AMOEBA(element) && CT > 60)
3523 int element2 = Tile[ELX][ELY];
3525 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3528 for (i = laser.num_damages - 1; i >= 0; i--)
3529 if (laser.damage[i].is_mirror)
3532 r = laser.num_edges;
3533 d = laser.num_damages;
3540 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3543 DrawLaser(0, DL_LASER_ENABLED);
3546 x = laser.damage[k1].x;
3547 y = laser.damage[k1].y;
3552 for (i = 0; i < 4; i++)
3554 if (laser.wall_mask & (1 << i))
3556 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3557 cSY + ELY * TILEY + 31 * (i / 2)))
3560 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3561 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3568 for (i = 0; i < 4; i++)
3570 if (laser.wall_mask & (1 << i))
3572 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3573 cSY + ELY * TILEY + 31 * (i / 2)))
3580 if (laser.num_beamers > 0 ||
3581 k1 < 1 || k2 < 4 || k3 < 4 ||
3582 CheckLaserPixel(cSX + ELX * TILEX + 14,
3583 cSY + ELY * TILEY + 14))
3585 laser.num_edges = r;
3586 laser.num_damages = d;
3588 DrawLaser(0, DL_LASER_DISABLED);
3591 Tile[ELX][ELY] = element | laser.wall_mask;
3593 int x = ELX, y = ELY;
3594 int wall_mask = laser.wall_mask;
3597 DrawLaser(0, DL_LASER_ENABLED);
3599 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3601 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3602 Store[x][y] = EL_WALL_AMOEBA_BASE;
3603 Store2[x][y] = wall_mask;
3608 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3609 laser.stops_inside_element && CT > native_mm_level.time_block)
3614 if (ABS(XS) > ABS(YS))
3621 for (i = 0; i < 4; i++)
3628 x = ELX + Step[k * 4].x;
3629 y = ELY + Step[k * 4].y;
3631 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3634 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3642 laser.overloaded = (element == EL_BLOCK_STONE);
3647 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3650 Tile[x][y] = element;
3652 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3655 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3657 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3658 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3666 if (element == EL_FUEL_FULL && CT > 10)
3668 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3669 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3671 for (i = start; i <= num_init_game_frames; i++)
3673 if (i == num_init_game_frames)
3674 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3675 else if (setup.sound_loops)
3676 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3678 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3680 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3682 UpdateAndDisplayGameControlValues();
3687 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3689 DrawField_MM(ELX, ELY);
3691 DrawLaser(0, DL_LASER_ENABLED);
3697 void GameActions_MM(struct MouseActionInfo action)
3699 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3700 boolean button_released = (action.button == MB_RELEASED);
3702 GameActions_MM_Ext();
3704 CheckSingleStepMode_MM(element_clicked, button_released);
3707 static void MovePacMen(void)
3709 int mx, my, ox, oy, nx, ny;
3713 if (++pacman_nr >= game_mm.num_pacman)
3716 game_mm.pacman[pacman_nr].dir--;
3718 for (l = 1; l < 5; l++)
3720 game_mm.pacman[pacman_nr].dir++;
3722 if (game_mm.pacman[pacman_nr].dir > 4)
3723 game_mm.pacman[pacman_nr].dir = 1;
3725 if (game_mm.pacman[pacman_nr].dir % 2)
3728 my = game_mm.pacman[pacman_nr].dir - 2;
3733 mx = 3 - game_mm.pacman[pacman_nr].dir;
3736 ox = game_mm.pacman[pacman_nr].x;
3737 oy = game_mm.pacman[pacman_nr].y;
3740 element = Tile[nx][ny];
3742 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3745 if (!IS_EATABLE4PACMAN(element))
3748 if (ObjHit(nx, ny, HIT_POS_CENTER))
3751 Tile[ox][oy] = EL_EMPTY;
3753 EL_PACMAN_RIGHT - 1 +
3754 (game_mm.pacman[pacman_nr].dir - 1 +
3755 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3757 game_mm.pacman[pacman_nr].x = nx;
3758 game_mm.pacman[pacman_nr].y = ny;
3760 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3762 if (element != EL_EMPTY)
3764 int graphic = el2gfx(Tile[nx][ny]);
3769 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3772 ox = cSX + ox * TILEX;
3773 oy = cSY + oy * TILEY;
3775 for (i = 1; i < 33; i += 2)
3776 BlitBitmap(bitmap, window,
3777 src_x, src_y, TILEX, TILEY,
3778 ox + i * mx, oy + i * my);
3779 Ct = Ct + FrameCounter - CT;
3782 DrawField_MM(nx, ny);
3785 if (!laser.fuse_off)
3787 DrawLaser(0, DL_LASER_ENABLED);
3789 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3791 AddDamagedField(nx, ny);
3793 laser.damage[laser.num_damages - 1].edge = 0;
3797 if (element == EL_BOMB)
3798 DeletePacMan(nx, ny);
3800 if (IS_WALL_AMOEBA(element) &&
3801 (LX + 2 * XS) / TILEX == nx &&
3802 (LY + 2 * YS) / TILEY == ny)
3812 static void InitMovingField_MM(int x, int y, int direction)
3814 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3815 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3817 MovDir[x][y] = direction;
3818 MovDir[newx][newy] = direction;
3820 if (Tile[newx][newy] == EL_EMPTY)
3821 Tile[newx][newy] = EL_BLOCKED;
3824 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3826 int direction = MovDir[x][y];
3827 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3828 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3834 static void Blocked2Moving_MM(int x, int y,
3835 int *comes_from_x, int *comes_from_y)
3837 int oldx = x, oldy = y;
3838 int direction = MovDir[x][y];
3840 if (direction == MV_LEFT)
3842 else if (direction == MV_RIGHT)
3844 else if (direction == MV_UP)
3846 else if (direction == MV_DOWN)
3849 *comes_from_x = oldx;
3850 *comes_from_y = oldy;
3853 static int MovingOrBlocked2Element_MM(int x, int y)
3855 int element = Tile[x][y];
3857 if (element == EL_BLOCKED)
3861 Blocked2Moving_MM(x, y, &oldx, &oldy);
3863 return Tile[oldx][oldy];
3869 static void RemoveMovingField_MM(int x, int y)
3871 int oldx = x, oldy = y, newx = x, newy = y;
3873 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3876 if (IS_MOVING(x, y))
3878 Moving2Blocked_MM(x, y, &newx, &newy);
3879 if (Tile[newx][newy] != EL_BLOCKED)
3882 else if (Tile[x][y] == EL_BLOCKED)
3884 Blocked2Moving_MM(x, y, &oldx, &oldy);
3885 if (!IS_MOVING(oldx, oldy))
3889 Tile[oldx][oldy] = EL_EMPTY;
3890 Tile[newx][newy] = EL_EMPTY;
3891 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3892 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3894 DrawLevelField_MM(oldx, oldy);
3895 DrawLevelField_MM(newx, newy);
3898 static void RaiseScore_MM(int value)
3900 game_mm.score += value;
3903 void RaiseScoreElement_MM(int element)
3908 case EL_PACMAN_RIGHT:
3910 case EL_PACMAN_LEFT:
3911 case EL_PACMAN_DOWN:
3912 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3916 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3921 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3925 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3934 // ----------------------------------------------------------------------------
3935 // Mirror Magic game engine snapshot handling functions
3936 // ----------------------------------------------------------------------------
3938 void SaveEngineSnapshotValues_MM(void)
3942 engine_snapshot_mm.game_mm = game_mm;
3943 engine_snapshot_mm.laser = laser;
3945 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3947 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3949 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3950 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3951 engine_snapshot_mm.Box[x][y] = Box[x][y];
3952 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3956 engine_snapshot_mm.LX = LX;
3957 engine_snapshot_mm.LY = LY;
3958 engine_snapshot_mm.XS = XS;
3959 engine_snapshot_mm.YS = YS;
3960 engine_snapshot_mm.ELX = ELX;
3961 engine_snapshot_mm.ELY = ELY;
3962 engine_snapshot_mm.CT = CT;
3963 engine_snapshot_mm.Ct = Ct;
3965 engine_snapshot_mm.last_LX = last_LX;
3966 engine_snapshot_mm.last_LY = last_LY;
3967 engine_snapshot_mm.last_hit_mask = last_hit_mask;
3968 engine_snapshot_mm.hold_x = hold_x;
3969 engine_snapshot_mm.hold_y = hold_y;
3970 engine_snapshot_mm.pacman_nr = pacman_nr;
3972 engine_snapshot_mm.rotate_delay = rotate_delay;
3973 engine_snapshot_mm.pacman_delay = pacman_delay;
3974 engine_snapshot_mm.energy_delay = energy_delay;
3975 engine_snapshot_mm.overload_delay = overload_delay;
3978 void LoadEngineSnapshotValues_MM(void)
3982 // stored engine snapshot buffers already restored at this point
3984 game_mm = engine_snapshot_mm.game_mm;
3985 laser = engine_snapshot_mm.laser;
3987 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3989 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3991 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
3992 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
3993 Box[x][y] = engine_snapshot_mm.Box[x][y];
3994 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
3998 LX = engine_snapshot_mm.LX;
3999 LY = engine_snapshot_mm.LY;
4000 XS = engine_snapshot_mm.XS;
4001 YS = engine_snapshot_mm.YS;
4002 ELX = engine_snapshot_mm.ELX;
4003 ELY = engine_snapshot_mm.ELY;
4004 CT = engine_snapshot_mm.CT;
4005 Ct = engine_snapshot_mm.Ct;
4007 last_LX = engine_snapshot_mm.last_LX;
4008 last_LY = engine_snapshot_mm.last_LY;
4009 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4010 hold_x = engine_snapshot_mm.hold_x;
4011 hold_y = engine_snapshot_mm.hold_y;
4012 pacman_nr = engine_snapshot_mm.pacman_nr;
4014 rotate_delay = engine_snapshot_mm.rotate_delay;
4015 pacman_delay = engine_snapshot_mm.pacman_delay;
4016 energy_delay = engine_snapshot_mm.energy_delay;
4017 overload_delay = engine_snapshot_mm.overload_delay;
4019 RedrawPlayfield_MM();
4022 static int getAngleFromTouchDelta(int dx, int dy, int base)
4024 double pi = 3.141592653;
4025 double rad = atan2((double)-dy, (double)dx);
4026 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4027 double deg = rad2 * 180.0 / pi;
4029 return (int)(deg * base / 360.0 + 0.5) % base;
4032 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4034 // calculate start (source) position to be at the middle of the tile
4035 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4036 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4037 int dx = dst_mx - src_mx;
4038 int dy = dst_my - src_my;
4047 if (!IN_LEV_FIELD(x, y))
4050 element = Tile[x][y];
4052 if (!IS_MCDUFFIN(element) &&
4053 !IS_MIRROR(element) &&
4054 !IS_BEAMER(element) &&
4055 !IS_POLAR(element) &&
4056 !IS_POLAR_CROSS(element) &&
4057 !IS_DF_MIRROR(element))
4060 angle_old = get_element_angle(element);
4062 if (IS_MCDUFFIN(element))
4064 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4065 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4066 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4067 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4070 else if (IS_MIRROR(element) ||
4071 IS_DF_MIRROR(element))
4073 for (i = 0; i < laser.num_damages; i++)
4075 if (laser.damage[i].x == x &&
4076 laser.damage[i].y == y &&
4077 ObjHit(x, y, HIT_POS_CENTER))
4079 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4080 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4087 if (angle_new == -1)
4089 if (IS_MIRROR(element) ||
4090 IS_DF_MIRROR(element) ||
4094 if (IS_POLAR_CROSS(element))
4097 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4100 button = (angle_new == angle_old ? 0 :
4101 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4102 MB_LEFTBUTTON : MB_RIGHTBUTTON);