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 if (center_element != EL_GRAY_BALL_ACTIVE)
2731 Store[x][y] = EL_EMPTY;
2732 Store2[x][y] = center_element;
2734 Tile[x][y] = EL_EXPLODING_OPAQUE;
2736 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2737 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2738 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2741 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2743 ExplodePhase[x][y] = 1;
2749 GfxFrame[x][y] = 0; // restart explosion animation
2751 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2753 center_element = Store2[x][y];
2755 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2757 Tile[x][y] = EL_EXPLODING_TRANSP;
2759 if (x == ELX && y == ELY)
2763 if (phase == last_phase)
2765 if (center_element == EL_BOMB_ACTIVE)
2767 DrawLaser(0, DL_LASER_DISABLED);
2770 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2772 GameOver_MM(GAME_OVER_DELAYED);
2774 laser.overloaded = FALSE;
2776 else if (IS_MCDUFFIN(center_element))
2778 GameOver_MM(GAME_OVER_BOMB);
2781 Tile[x][y] = Store[x][y];
2783 Store[x][y] = Store2[x][y] = 0;
2784 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2786 InitField(x, y, FALSE);
2789 if (center_element == EL_GRAY_BALL_ACTIVE)
2790 ScanLaser_FromLastMirror();
2792 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2794 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2795 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2797 DrawGraphicAnimation_MM(x, y, graphic, frame);
2799 MarkTileDirty(x, y);
2803 static void Bang_MM(int x, int y)
2805 int element = Tile[x][y];
2807 if (IS_PACMAN(element))
2808 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2809 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2810 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2811 else if (element == EL_KEY)
2812 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2814 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2816 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2819 static void TurnRound(int x, int y)
2831 { 0, 0 }, { 0, 0 }, { 0, 0 },
2836 int left, right, back;
2840 { MV_DOWN, MV_UP, MV_RIGHT },
2841 { MV_UP, MV_DOWN, MV_LEFT },
2843 { MV_LEFT, MV_RIGHT, MV_DOWN },
2844 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2845 { MV_RIGHT, MV_LEFT, MV_UP }
2848 int element = Tile[x][y];
2849 int old_move_dir = MovDir[x][y];
2850 int right_dir = turn[old_move_dir].right;
2851 int back_dir = turn[old_move_dir].back;
2852 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2853 int right_x = x + right_dx, right_y = y + right_dy;
2855 if (element == EL_PACMAN)
2857 boolean can_turn_right = FALSE;
2859 if (IN_LEV_FIELD(right_x, right_y) &&
2860 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2861 can_turn_right = TRUE;
2864 MovDir[x][y] = right_dir;
2866 MovDir[x][y] = back_dir;
2872 static void StartMoving_MM(int x, int y)
2874 int element = Tile[x][y];
2879 if (CAN_MOVE(element))
2883 if (MovDelay[x][y]) // wait some time before next movement
2891 // now make next step
2893 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2895 if (element == EL_PACMAN &&
2896 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2897 !ObjHit(newx, newy, HIT_POS_CENTER))
2899 Store[newx][newy] = Tile[newx][newy];
2900 Tile[newx][newy] = EL_EMPTY;
2902 DrawField_MM(newx, newy);
2904 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2905 ObjHit(newx, newy, HIT_POS_CENTER))
2907 // object was running against a wall
2914 InitMovingField_MM(x, y, MovDir[x][y]);
2918 ContinueMoving_MM(x, y);
2921 static void ContinueMoving_MM(int x, int y)
2923 int element = Tile[x][y];
2924 int direction = MovDir[x][y];
2925 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2926 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2927 int horiz_move = (dx!=0);
2928 int newx = x + dx, newy = y + dy;
2929 int step = (horiz_move ? dx : dy) * TILEX / 8;
2931 MovPos[x][y] += step;
2933 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2935 Tile[x][y] = EL_EMPTY;
2936 Tile[newx][newy] = element;
2938 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2939 MovDelay[newx][newy] = 0;
2941 if (!CAN_MOVE(element))
2942 MovDir[newx][newy] = 0;
2945 DrawField_MM(newx, newy);
2947 Stop[newx][newy] = TRUE;
2949 if (element == EL_PACMAN)
2951 if (Store[newx][newy] == EL_BOMB)
2952 Bang_MM(newx, newy);
2954 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2955 (LX + 2 * XS) / TILEX == newx &&
2956 (LY + 2 * YS) / TILEY == newy)
2963 else // still moving on
2968 laser.redraw = TRUE;
2971 boolean ClickElement(int x, int y, int button)
2973 static DelayCounter click_delay = { CLICK_DELAY };
2974 static boolean new_button = TRUE;
2975 boolean element_clicked = FALSE;
2980 // initialize static variables
2981 click_delay.count = 0;
2982 click_delay.value = CLICK_DELAY;
2988 // do not rotate objects hit by the laser after the game was solved
2989 if (game_mm.level_solved && Hit[x][y])
2992 if (button == MB_RELEASED)
2995 click_delay.value = CLICK_DELAY;
2997 // release eventually hold auto-rotating mirror
2998 RotateMirror(x, y, MB_RELEASED);
3003 if (!FrameReached(&click_delay) && !new_button)
3006 if (button == MB_MIDDLEBUTTON) // middle button has no function
3009 if (!IN_LEV_FIELD(x, y))
3012 if (Tile[x][y] == EL_EMPTY)
3015 element = Tile[x][y];
3017 if (IS_MIRROR(element) ||
3018 IS_BEAMER(element) ||
3019 IS_POLAR(element) ||
3020 IS_POLAR_CROSS(element) ||
3021 IS_DF_MIRROR(element) ||
3022 IS_DF_MIRROR_AUTO(element))
3024 RotateMirror(x, y, button);
3026 element_clicked = TRUE;
3028 else if (IS_MCDUFFIN(element))
3030 if (!laser.fuse_off)
3032 DrawLaser(0, DL_LASER_DISABLED);
3039 element = get_rotated_element(element, BUTTON_ROTATION(button));
3040 laser.start_angle = get_element_angle(element);
3044 Tile[x][y] = element;
3051 if (!laser.fuse_off)
3054 element_clicked = TRUE;
3056 else if (element == EL_FUSE_ON && laser.fuse_off)
3058 if (x != laser.fuse_x || y != laser.fuse_y)
3061 laser.fuse_off = FALSE;
3062 laser.fuse_x = laser.fuse_y = -1;
3064 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3067 element_clicked = TRUE;
3069 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3071 laser.fuse_off = TRUE;
3074 laser.overloaded = FALSE;
3076 DrawLaser(0, DL_LASER_DISABLED);
3077 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3079 element_clicked = TRUE;
3081 else if (element == EL_LIGHTBALL)
3084 RaiseScoreElement_MM(element);
3085 DrawLaser(0, DL_LASER_ENABLED);
3087 element_clicked = TRUE;
3089 else if (IS_ENVELOPE(element))
3091 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3093 element_clicked = TRUE;
3096 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3099 return element_clicked;
3102 static void RotateMirror(int x, int y, int button)
3104 if (button == MB_RELEASED)
3106 // release eventually hold auto-rotating mirror
3113 if (IS_MIRROR(Tile[x][y]) ||
3114 IS_POLAR_CROSS(Tile[x][y]) ||
3115 IS_POLAR(Tile[x][y]) ||
3116 IS_BEAMER(Tile[x][y]) ||
3117 IS_DF_MIRROR(Tile[x][y]) ||
3118 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3119 IS_GRID_WOOD_AUTO(Tile[x][y]))
3121 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3123 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3125 if (button == MB_LEFTBUTTON)
3127 // left mouse button only for manual adjustment, no auto-rotating;
3128 // freeze mirror for until mouse button released
3132 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3134 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3138 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3140 int edge = Hit[x][y];
3146 DrawLaser(edge - 1, DL_LASER_DISABLED);
3150 else if (ObjHit(x, y, HIT_POS_CENTER))
3152 int edge = Hit[x][y];
3156 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3161 DrawLaser(edge - 1, DL_LASER_DISABLED);
3168 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3173 if ((IS_BEAMER(Tile[x][y]) ||
3174 IS_POLAR(Tile[x][y]) ||
3175 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3177 if (IS_BEAMER(Tile[x][y]))
3180 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3181 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3194 DrawLaser(0, DL_LASER_ENABLED);
3198 static void AutoRotateMirrors(void)
3202 if (!FrameReached(&rotate_delay))
3205 for (x = 0; x < lev_fieldx; x++)
3207 for (y = 0; y < lev_fieldy; y++)
3209 int element = Tile[x][y];
3211 // do not rotate objects hit by the laser after the game was solved
3212 if (game_mm.level_solved && Hit[x][y])
3215 if (IS_DF_MIRROR_AUTO(element) ||
3216 IS_GRID_WOOD_AUTO(element) ||
3217 IS_GRID_STEEL_AUTO(element) ||
3218 element == EL_REFRACTOR)
3219 RotateMirror(x, y, MB_RIGHTBUTTON);
3224 static boolean ObjHit(int obx, int oby, int bits)
3231 if (bits & HIT_POS_CENTER)
3233 if (CheckLaserPixel(cSX + obx + 15,
3238 if (bits & HIT_POS_EDGE)
3240 for (i = 0; i < 4; i++)
3241 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3242 cSY + oby + 31 * (i / 2)))
3246 if (bits & HIT_POS_BETWEEN)
3248 for (i = 0; i < 4; i++)
3249 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3250 cSY + 4 + oby + 22 * (i / 2)))
3257 static void DeletePacMan(int px, int py)
3263 if (game_mm.num_pacman <= 1)
3265 game_mm.num_pacman = 0;
3269 for (i = 0; i < game_mm.num_pacman; i++)
3270 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3273 game_mm.num_pacman--;
3275 for (j = i; j < game_mm.num_pacman; j++)
3277 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3278 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3279 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3283 static void GameActions_MM_Ext(void)
3290 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3293 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3295 element = Tile[x][y];
3297 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3298 StartMoving_MM(x, y);
3299 else if (IS_MOVING(x, y))
3300 ContinueMoving_MM(x, y);
3301 else if (IS_EXPLODING(element))
3302 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3303 else if (element == EL_EXIT_OPENING)
3305 else if (element == EL_GRAY_BALL_OPENING)
3307 else if (IS_ENVELOPE_OPENING(element))
3309 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3311 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3313 else if (IS_MIRROR(element) ||
3314 IS_MIRROR_FIXED(element) ||
3315 element == EL_PRISM)
3316 DrawFieldTwinkle(x, y);
3317 else if (element == EL_GRAY_BALL_ACTIVE ||
3318 element == EL_BOMB_ACTIVE ||
3319 element == EL_MINE_ACTIVE)
3320 DrawFieldAnimated_MM(x, y);
3321 else if (!IS_BLOCKED(x, y))
3322 DrawFieldAnimatedIfNeeded_MM(x, y);
3325 AutoRotateMirrors();
3328 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3330 // redraw after Explode_MM() ...
3332 DrawLaser(0, DL_LASER_ENABLED);
3333 laser.redraw = FALSE;
3338 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3342 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3344 DrawLaser(0, DL_LASER_DISABLED);
3349 // skip all following game actions if game is over
3350 if (game_mm.game_over)
3353 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3357 GameOver_MM(GAME_OVER_NO_ENERGY);
3362 if (FrameReached(&energy_delay))
3364 if (game_mm.energy_left > 0)
3365 game_mm.energy_left--;
3367 // when out of energy, wait another frame to play "out of time" sound
3370 element = laser.dest_element;
3373 if (element != Tile[ELX][ELY])
3375 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3376 element, Tile[ELX][ELY]);
3380 if (!laser.overloaded && laser.overload_value == 0 &&
3381 element != EL_BOMB &&
3382 element != EL_BOMB_ACTIVE &&
3383 element != EL_MINE &&
3384 element != EL_MINE_ACTIVE &&
3385 element != EL_GRAY_BALL &&
3386 element != EL_GRAY_BALL_ACTIVE &&
3387 element != EL_BLOCK_STONE &&
3388 element != EL_BLOCK_WOOD &&
3389 element != EL_FUSE_ON &&
3390 element != EL_FUEL_FULL &&
3391 !IS_WALL_ICE(element) &&
3392 !IS_WALL_AMOEBA(element))
3395 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3397 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3398 (!laser.overloaded && laser.overload_value > 0)) &&
3399 FrameReached(&overload_delay))
3401 if (laser.overloaded)
3402 laser.overload_value++;
3404 laser.overload_value--;
3406 if (game_mm.cheat_no_overload)
3408 laser.overloaded = FALSE;
3409 laser.overload_value = 0;
3412 game_mm.laser_overload_value = laser.overload_value;
3414 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3416 SetLaserColor(0xFF);
3418 DrawLaser(0, DL_LASER_ENABLED);
3421 if (!laser.overloaded)
3422 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3423 else if (setup.sound_loops)
3424 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3426 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3428 if (laser.overload_value == MAX_LASER_OVERLOAD)
3430 UpdateAndDisplayGameControlValues();
3434 GameOver_MM(GAME_OVER_OVERLOADED);
3445 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3447 if (game_mm.cheat_no_explosion)
3452 laser.dest_element = EL_EXPLODING_OPAQUE;
3457 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3459 laser.fuse_off = TRUE;
3463 DrawLaser(0, DL_LASER_DISABLED);
3464 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3467 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3469 if (!Store2[ELX][ELY]) // check if content element not yet determined
3471 int last_anim_random_frame = gfx.anim_random_frame;
3474 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3475 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3477 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3478 native_mm_level.ball_choice_mode, 0,
3479 game_mm.ball_choice_pos);
3481 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3482 gfx.anim_random_frame = last_anim_random_frame;
3484 game_mm.ball_choice_pos++;
3486 int new_element = native_mm_level.ball_content[element_pos];
3487 int new_element_base = map_wall_to_base_element(new_element);
3489 if (IS_WALL(new_element_base))
3491 // always use completely filled wall element
3492 new_element = new_element_base | 0x000f;
3494 else if (native_mm_level.rotate_ball_content &&
3495 get_num_elements(new_element) > 1)
3497 // randomly rotate newly created game element
3498 new_element = get_rotated_element(new_element, RND(16));
3501 Store[ELX][ELY] = new_element;
3502 Store2[ELX][ELY] = TRUE;
3505 if (native_mm_level.explode_ball)
3508 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3510 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3515 if (IS_WALL_ICE(element) && CT > 50)
3517 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3519 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3520 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3521 Store2[ELX][ELY] = laser.wall_mask;
3523 laser.dest_element = Tile[ELX][ELY];
3528 if (IS_WALL_AMOEBA(element) && CT > 60)
3531 int element2 = Tile[ELX][ELY];
3533 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3536 for (i = laser.num_damages - 1; i >= 0; i--)
3537 if (laser.damage[i].is_mirror)
3540 r = laser.num_edges;
3541 d = laser.num_damages;
3548 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3551 DrawLaser(0, DL_LASER_ENABLED);
3554 x = laser.damage[k1].x;
3555 y = laser.damage[k1].y;
3560 for (i = 0; i < 4; i++)
3562 if (laser.wall_mask & (1 << i))
3564 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3565 cSY + ELY * TILEY + 31 * (i / 2)))
3568 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3569 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3576 for (i = 0; i < 4; i++)
3578 if (laser.wall_mask & (1 << i))
3580 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3581 cSY + ELY * TILEY + 31 * (i / 2)))
3588 if (laser.num_beamers > 0 ||
3589 k1 < 1 || k2 < 4 || k3 < 4 ||
3590 CheckLaserPixel(cSX + ELX * TILEX + 14,
3591 cSY + ELY * TILEY + 14))
3593 laser.num_edges = r;
3594 laser.num_damages = d;
3596 DrawLaser(0, DL_LASER_DISABLED);
3599 Tile[ELX][ELY] = element | laser.wall_mask;
3601 int x = ELX, y = ELY;
3602 int wall_mask = laser.wall_mask;
3605 DrawLaser(0, DL_LASER_ENABLED);
3607 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3609 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3610 Store[x][y] = EL_WALL_AMOEBA_BASE;
3611 Store2[x][y] = wall_mask;
3616 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3617 laser.stops_inside_element && CT > native_mm_level.time_block)
3622 if (ABS(XS) > ABS(YS))
3629 for (i = 0; i < 4; i++)
3636 x = ELX + Step[k * 4].x;
3637 y = ELY + Step[k * 4].y;
3639 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3642 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3650 laser.overloaded = (element == EL_BLOCK_STONE);
3655 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3658 Tile[x][y] = element;
3660 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3663 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3665 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3666 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3674 if (element == EL_FUEL_FULL && CT > 10)
3676 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3677 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3679 for (i = start; i <= num_init_game_frames; i++)
3681 if (i == num_init_game_frames)
3682 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3683 else if (setup.sound_loops)
3684 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3686 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3688 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3690 UpdateAndDisplayGameControlValues();
3695 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3697 DrawField_MM(ELX, ELY);
3699 DrawLaser(0, DL_LASER_ENABLED);
3705 void GameActions_MM(struct MouseActionInfo action)
3707 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3708 boolean button_released = (action.button == MB_RELEASED);
3710 GameActions_MM_Ext();
3712 CheckSingleStepMode_MM(element_clicked, button_released);
3715 static void MovePacMen(void)
3717 int mx, my, ox, oy, nx, ny;
3721 if (++pacman_nr >= game_mm.num_pacman)
3724 game_mm.pacman[pacman_nr].dir--;
3726 for (l = 1; l < 5; l++)
3728 game_mm.pacman[pacman_nr].dir++;
3730 if (game_mm.pacman[pacman_nr].dir > 4)
3731 game_mm.pacman[pacman_nr].dir = 1;
3733 if (game_mm.pacman[pacman_nr].dir % 2)
3736 my = game_mm.pacman[pacman_nr].dir - 2;
3741 mx = 3 - game_mm.pacman[pacman_nr].dir;
3744 ox = game_mm.pacman[pacman_nr].x;
3745 oy = game_mm.pacman[pacman_nr].y;
3748 element = Tile[nx][ny];
3750 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3753 if (!IS_EATABLE4PACMAN(element))
3756 if (ObjHit(nx, ny, HIT_POS_CENTER))
3759 Tile[ox][oy] = EL_EMPTY;
3761 EL_PACMAN_RIGHT - 1 +
3762 (game_mm.pacman[pacman_nr].dir - 1 +
3763 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3765 game_mm.pacman[pacman_nr].x = nx;
3766 game_mm.pacman[pacman_nr].y = ny;
3768 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3770 if (element != EL_EMPTY)
3772 int graphic = el2gfx(Tile[nx][ny]);
3777 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3780 ox = cSX + ox * TILEX;
3781 oy = cSY + oy * TILEY;
3783 for (i = 1; i < 33; i += 2)
3784 BlitBitmap(bitmap, window,
3785 src_x, src_y, TILEX, TILEY,
3786 ox + i * mx, oy + i * my);
3787 Ct = Ct + FrameCounter - CT;
3790 DrawField_MM(nx, ny);
3793 if (!laser.fuse_off)
3795 DrawLaser(0, DL_LASER_ENABLED);
3797 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3799 AddDamagedField(nx, ny);
3801 laser.damage[laser.num_damages - 1].edge = 0;
3805 if (element == EL_BOMB)
3806 DeletePacMan(nx, ny);
3808 if (IS_WALL_AMOEBA(element) &&
3809 (LX + 2 * XS) / TILEX == nx &&
3810 (LY + 2 * YS) / TILEY == ny)
3820 static void InitMovingField_MM(int x, int y, int direction)
3822 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3823 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3825 MovDir[x][y] = direction;
3826 MovDir[newx][newy] = direction;
3828 if (Tile[newx][newy] == EL_EMPTY)
3829 Tile[newx][newy] = EL_BLOCKED;
3832 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3834 int direction = MovDir[x][y];
3835 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3836 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3842 static void Blocked2Moving_MM(int x, int y,
3843 int *comes_from_x, int *comes_from_y)
3845 int oldx = x, oldy = y;
3846 int direction = MovDir[x][y];
3848 if (direction == MV_LEFT)
3850 else if (direction == MV_RIGHT)
3852 else if (direction == MV_UP)
3854 else if (direction == MV_DOWN)
3857 *comes_from_x = oldx;
3858 *comes_from_y = oldy;
3861 static int MovingOrBlocked2Element_MM(int x, int y)
3863 int element = Tile[x][y];
3865 if (element == EL_BLOCKED)
3869 Blocked2Moving_MM(x, y, &oldx, &oldy);
3871 return Tile[oldx][oldy];
3877 static void RemoveMovingField_MM(int x, int y)
3879 int oldx = x, oldy = y, newx = x, newy = y;
3881 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3884 if (IS_MOVING(x, y))
3886 Moving2Blocked_MM(x, y, &newx, &newy);
3887 if (Tile[newx][newy] != EL_BLOCKED)
3890 else if (Tile[x][y] == EL_BLOCKED)
3892 Blocked2Moving_MM(x, y, &oldx, &oldy);
3893 if (!IS_MOVING(oldx, oldy))
3897 Tile[oldx][oldy] = EL_EMPTY;
3898 Tile[newx][newy] = EL_EMPTY;
3899 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3900 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3902 DrawLevelField_MM(oldx, oldy);
3903 DrawLevelField_MM(newx, newy);
3906 static void RaiseScore_MM(int value)
3908 game_mm.score += value;
3911 void RaiseScoreElement_MM(int element)
3916 case EL_PACMAN_RIGHT:
3918 case EL_PACMAN_LEFT:
3919 case EL_PACMAN_DOWN:
3920 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3924 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3929 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3933 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3942 // ----------------------------------------------------------------------------
3943 // Mirror Magic game engine snapshot handling functions
3944 // ----------------------------------------------------------------------------
3946 void SaveEngineSnapshotValues_MM(void)
3950 engine_snapshot_mm.game_mm = game_mm;
3951 engine_snapshot_mm.laser = laser;
3953 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3955 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3957 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3958 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3959 engine_snapshot_mm.Box[x][y] = Box[x][y];
3960 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3964 engine_snapshot_mm.LX = LX;
3965 engine_snapshot_mm.LY = LY;
3966 engine_snapshot_mm.XS = XS;
3967 engine_snapshot_mm.YS = YS;
3968 engine_snapshot_mm.ELX = ELX;
3969 engine_snapshot_mm.ELY = ELY;
3970 engine_snapshot_mm.CT = CT;
3971 engine_snapshot_mm.Ct = Ct;
3973 engine_snapshot_mm.last_LX = last_LX;
3974 engine_snapshot_mm.last_LY = last_LY;
3975 engine_snapshot_mm.last_hit_mask = last_hit_mask;
3976 engine_snapshot_mm.hold_x = hold_x;
3977 engine_snapshot_mm.hold_y = hold_y;
3978 engine_snapshot_mm.pacman_nr = pacman_nr;
3980 engine_snapshot_mm.rotate_delay = rotate_delay;
3981 engine_snapshot_mm.pacman_delay = pacman_delay;
3982 engine_snapshot_mm.energy_delay = energy_delay;
3983 engine_snapshot_mm.overload_delay = overload_delay;
3986 void LoadEngineSnapshotValues_MM(void)
3990 // stored engine snapshot buffers already restored at this point
3992 game_mm = engine_snapshot_mm.game_mm;
3993 laser = engine_snapshot_mm.laser;
3995 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3997 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3999 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4000 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4001 Box[x][y] = engine_snapshot_mm.Box[x][y];
4002 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4006 LX = engine_snapshot_mm.LX;
4007 LY = engine_snapshot_mm.LY;
4008 XS = engine_snapshot_mm.XS;
4009 YS = engine_snapshot_mm.YS;
4010 ELX = engine_snapshot_mm.ELX;
4011 ELY = engine_snapshot_mm.ELY;
4012 CT = engine_snapshot_mm.CT;
4013 Ct = engine_snapshot_mm.Ct;
4015 last_LX = engine_snapshot_mm.last_LX;
4016 last_LY = engine_snapshot_mm.last_LY;
4017 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4018 hold_x = engine_snapshot_mm.hold_x;
4019 hold_y = engine_snapshot_mm.hold_y;
4020 pacman_nr = engine_snapshot_mm.pacman_nr;
4022 rotate_delay = engine_snapshot_mm.rotate_delay;
4023 pacman_delay = engine_snapshot_mm.pacman_delay;
4024 energy_delay = engine_snapshot_mm.energy_delay;
4025 overload_delay = engine_snapshot_mm.overload_delay;
4027 RedrawPlayfield_MM();
4030 static int getAngleFromTouchDelta(int dx, int dy, int base)
4032 double pi = 3.141592653;
4033 double rad = atan2((double)-dy, (double)dx);
4034 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4035 double deg = rad2 * 180.0 / pi;
4037 return (int)(deg * base / 360.0 + 0.5) % base;
4040 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4042 // calculate start (source) position to be at the middle of the tile
4043 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4044 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4045 int dx = dst_mx - src_mx;
4046 int dy = dst_my - src_my;
4055 if (!IN_LEV_FIELD(x, y))
4058 element = Tile[x][y];
4060 if (!IS_MCDUFFIN(element) &&
4061 !IS_MIRROR(element) &&
4062 !IS_BEAMER(element) &&
4063 !IS_POLAR(element) &&
4064 !IS_POLAR_CROSS(element) &&
4065 !IS_DF_MIRROR(element))
4068 angle_old = get_element_angle(element);
4070 if (IS_MCDUFFIN(element))
4072 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4073 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4074 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4075 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4078 else if (IS_MIRROR(element) ||
4079 IS_DF_MIRROR(element))
4081 for (i = 0; i < laser.num_damages; i++)
4083 if (laser.damage[i].x == x &&
4084 laser.damage[i].y == y &&
4085 ObjHit(x, y, HIT_POS_CENTER))
4087 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4088 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4095 if (angle_new == -1)
4097 if (IS_MIRROR(element) ||
4098 IS_DF_MIRROR(element) ||
4102 if (IS_POLAR_CROSS(element))
4105 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4108 button = (angle_new == angle_old ? 0 :
4109 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4110 MB_LEFTBUTTON : MB_RIGHTBUTTON);