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 // bitmap for laser beam detection
103 static Bitmap *laser_bitmap = NULL;
105 // variables for laser control
106 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
107 static int hold_x = -1, hold_y = -1;
109 // variables for pacman control
110 static int pacman_nr = -1;
112 // various game engine delay counters
113 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
114 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
115 static DelayCounter energy_delay = { ENERGY_DELAY };
116 static DelayCounter overload_delay = { 0 };
118 // element mask positions for scanning pixels of MM elements
119 #define MM_MASK_MCDUFFIN_RIGHT 0
120 #define MM_MASK_MCDUFFIN_UP 1
121 #define MM_MASK_MCDUFFIN_LEFT 2
122 #define MM_MASK_MCDUFFIN_DOWN 3
123 #define MM_MASK_GRID_1 4
124 #define MM_MASK_GRID_2 5
125 #define MM_MASK_GRID_3 6
126 #define MM_MASK_GRID_4 7
127 #define MM_MASK_RECTANGLE 8
128 #define MM_MASK_CIRCLE 9
130 #define NUM_MM_MASKS 10
132 // element masks for scanning pixels of MM elements
133 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
317 static int get_element_angle(int element)
319 int element_phase = get_element_phase(element);
321 if (IS_MIRROR_FIXED(element) ||
322 IS_MCDUFFIN(element) ||
324 IS_RECEIVER(element))
325 return 4 * element_phase;
327 return element_phase;
330 static int get_opposite_angle(int angle)
332 int opposite_angle = angle + ANG_RAY_180;
334 // make sure "opposite_angle" is in valid interval [0, 15]
335 return (opposite_angle + 16) % 16;
338 static int get_mirrored_angle(int laser_angle, int mirror_angle)
340 int reflected_angle = 16 - laser_angle + mirror_angle;
342 // make sure "reflected_angle" is in valid interval [0, 15]
343 return (reflected_angle + 16) % 16;
346 static void DrawLaserLines(struct XY *points, int num_points, int mode)
348 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
349 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
351 DrawLines(drawto, points, num_points, pixel_drawto);
355 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
360 static boolean CheckLaserPixel(int x, int y)
366 pixel = ReadPixel(laser_bitmap, x, y);
370 return (pixel == WHITE_PIXEL);
373 static void CheckExitMM(void)
375 int exit_element = EL_EMPTY;
379 static int xy[4][2] =
387 for (y = 0; y < lev_fieldy; y++)
389 for (x = 0; x < lev_fieldx; x++)
391 if (Tile[x][y] == EL_EXIT_CLOSED)
393 // initiate opening animation of exit door
394 Tile[x][y] = EL_EXIT_OPENING;
396 exit_element = EL_EXIT_OPEN;
400 else if (IS_RECEIVER(Tile[x][y]))
402 // remove field that blocks receiver
403 int phase = Tile[x][y] - EL_RECEIVER_START;
404 int blocking_x, blocking_y;
406 blocking_x = x + xy[phase][0];
407 blocking_y = y + xy[phase][1];
409 if (IN_LEV_FIELD(blocking_x, blocking_y))
411 Tile[blocking_x][blocking_y] = EL_EMPTY;
413 DrawField_MM(blocking_x, blocking_y);
416 exit_element = EL_RECEIVER;
423 if (exit_element != EL_EMPTY)
424 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
427 static void SetLaserColor(int brightness)
429 int color_min = 0x00;
430 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
431 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
432 int color_down = color_max - color_up;
435 GetPixelFromRGB(window,
436 (game_mm.laser_red ? color_max : color_up),
437 (game_mm.laser_green ? color_down : color_min),
438 (game_mm.laser_blue ? color_down : color_min));
441 static void InitMovDir_MM(int x, int y)
443 int element = Tile[x][y];
444 static int direction[3][4] =
446 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
447 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
448 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
453 case EL_PACMAN_RIGHT:
457 Tile[x][y] = EL_PACMAN;
458 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
466 static void InitField(int x, int y)
468 int element = Tile[x][y];
473 Tile[x][y] = EL_EMPTY;
478 if (native_mm_level.auto_count_kettles)
479 game_mm.kettles_still_needed++;
482 case EL_LIGHTBULB_OFF:
483 game_mm.lights_still_needed++;
487 if (IS_MIRROR(element) ||
488 IS_BEAMER_OLD(element) ||
489 IS_BEAMER(element) ||
491 IS_POLAR_CROSS(element) ||
492 IS_DF_MIRROR(element) ||
493 IS_DF_MIRROR_AUTO(element) ||
494 IS_GRID_STEEL_AUTO(element) ||
495 IS_GRID_WOOD_AUTO(element) ||
496 IS_FIBRE_OPTIC(element))
498 if (IS_BEAMER_OLD(element))
500 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
501 element = Tile[x][y];
504 if (!IS_FIBRE_OPTIC(element))
506 static int steps_grid_auto = 0;
508 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
509 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
511 if (IS_GRID_STEEL_AUTO(element) ||
512 IS_GRID_WOOD_AUTO(element))
513 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
515 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
517 game_mm.cycle[game_mm.num_cycle].x = x;
518 game_mm.cycle[game_mm.num_cycle].y = y;
522 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
524 int beamer_nr = BEAMER_NR(element);
525 int nr = laser.beamer[beamer_nr][0].num;
527 laser.beamer[beamer_nr][nr].x = x;
528 laser.beamer[beamer_nr][nr].y = y;
529 laser.beamer[beamer_nr][nr].num = 1;
532 else if (IS_PACMAN(element))
536 else if (IS_MCDUFFIN(element) || IS_LASER(element))
538 laser.start_edge.x = x;
539 laser.start_edge.y = y;
540 laser.start_angle = get_element_angle(element);
542 if (IS_MCDUFFIN(element))
544 game_mm.laser_red = native_mm_level.mm_laser_red;
545 game_mm.laser_green = native_mm_level.mm_laser_green;
546 game_mm.laser_blue = native_mm_level.mm_laser_blue;
550 game_mm.laser_red = native_mm_level.df_laser_red;
551 game_mm.laser_green = native_mm_level.df_laser_green;
552 game_mm.laser_blue = native_mm_level.df_laser_blue;
560 static void InitCycleElements_RotateSingleStep(void)
564 if (game_mm.num_cycle == 0) // no elements to cycle
567 for (i = 0; i < game_mm.num_cycle; i++)
569 int x = game_mm.cycle[i].x;
570 int y = game_mm.cycle[i].y;
571 int step = SIGN(game_mm.cycle[i].steps);
572 int last_element = Tile[x][y];
573 int next_element = get_rotated_element(last_element, step);
575 if (!game_mm.cycle[i].steps)
578 Tile[x][y] = next_element;
580 game_mm.cycle[i].steps -= step;
584 static void InitLaser(void)
586 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
587 int step = (IS_LASER(start_element) ? 4 : 0);
589 LX = laser.start_edge.x * TILEX;
590 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
593 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
595 LY = laser.start_edge.y * TILEY;
596 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
597 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
601 XS = 2 * Step[laser.start_angle].x;
602 YS = 2 * Step[laser.start_angle].y;
604 laser.current_angle = laser.start_angle;
606 laser.num_damages = 0;
608 laser.num_beamers = 0;
609 laser.beamer_edge[0] = 0;
611 laser.dest_element = EL_EMPTY;
614 AddLaserEdge(LX, LY); // set laser starting edge
619 void InitGameEngine_MM(void)
625 // initialize laser bitmap to current playfield (screen) size
626 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
627 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
631 // set global game control values
632 game_mm.num_cycle = 0;
633 game_mm.num_pacman = 0;
636 game_mm.energy_left = 0; // later set to "native_mm_level.time"
637 game_mm.kettles_still_needed =
638 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
639 game_mm.lights_still_needed = 0;
640 game_mm.num_keys = 0;
641 game_mm.ball_choice_pos = 0;
643 game_mm.laser_red = FALSE;
644 game_mm.laser_green = FALSE;
645 game_mm.laser_blue = TRUE;
647 game_mm.level_solved = FALSE;
648 game_mm.game_over = FALSE;
649 game_mm.game_over_cause = 0;
651 game_mm.laser_overload_value = 0;
652 game_mm.laser_enabled = FALSE;
654 // set global laser control values (must be set before "InitLaser()")
655 laser.start_edge.x = 0;
656 laser.start_edge.y = 0;
657 laser.start_angle = 0;
659 for (i = 0; i < MAX_NUM_BEAMERS; i++)
660 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
662 laser.overloaded = FALSE;
663 laser.overload_value = 0;
664 laser.fuse_off = FALSE;
665 laser.fuse_x = laser.fuse_y = -1;
667 laser.dest_element = EL_EMPTY;
668 laser.dest_element_last = EL_EMPTY;
669 laser.dest_element_last_x = -1;
670 laser.dest_element_last_y = -1;
684 rotate_delay.count = 0;
685 pacman_delay.count = 0;
686 energy_delay.count = 0;
687 overload_delay.count = 0;
689 ClickElement(-1, -1, -1);
691 for (x = 0; x < lev_fieldx; x++)
693 for (y = 0; y < lev_fieldy; y++)
695 Tile[x][y] = Ur[x][y];
696 Hit[x][y] = Box[x][y] = 0;
698 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
699 Store[x][y] = Store2[x][y] = 0;
709 void InitGameActions_MM(void)
711 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
712 int cycle_steps_done = 0;
717 for (i = 0; i <= num_init_game_frames; i++)
719 if (i == num_init_game_frames)
720 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
721 else if (setup.sound_loops)
722 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
724 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
726 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
728 UpdateAndDisplayGameControlValues();
730 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
732 InitCycleElements_RotateSingleStep();
737 AdvanceFrameCounter();
747 if (setup.quick_doors)
754 if (game_mm.kettles_still_needed == 0)
757 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
758 SetTileCursorActive(TRUE);
760 // restart all delay counters after initially cycling game elements
761 ResetFrameCounter(&rotate_delay);
762 ResetFrameCounter(&pacman_delay);
763 ResetFrameCounter(&energy_delay);
764 ResetFrameCounter(&overload_delay);
767 static void FadeOutLaser(void)
771 for (i = 15; i >= 0; i--)
773 SetLaserColor(0x11 * i);
775 DrawLaser(0, DL_LASER_ENABLED);
778 Delay_WithScreenUpdates(50);
781 DrawLaser(0, DL_LASER_DISABLED);
783 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
786 static void GameOver_MM(int game_over_cause)
788 // do not handle game over if request dialog is already active
789 if (game.request_active)
792 game_mm.game_over = TRUE;
793 game_mm.game_over_cause = game_over_cause;
795 if (setup.ask_on_game_over)
796 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
797 "Bomb killed Mc Duffin! Play it again?" :
798 game_over_cause == GAME_OVER_NO_ENERGY ?
799 "Out of magic energy! Play it again?" :
800 game_over_cause == GAME_OVER_OVERLOADED ?
801 "Magic spell hit Mc Duffin! Play it again?" :
804 SetTileCursorActive(FALSE);
807 void AddLaserEdge(int lx, int ly)
812 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
814 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
819 laser.edge[laser.num_edges].x = cSX2 + lx;
820 laser.edge[laser.num_edges].y = cSY2 + ly;
826 void AddDamagedField(int ex, int ey)
828 // prevent adding the same field position again
829 if (laser.num_damages > 0 &&
830 laser.damage[laser.num_damages - 1].x == ex &&
831 laser.damage[laser.num_damages - 1].y == ey &&
832 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
835 laser.damage[laser.num_damages].is_mirror = FALSE;
836 laser.damage[laser.num_damages].angle = laser.current_angle;
837 laser.damage[laser.num_damages].edge = laser.num_edges;
838 laser.damage[laser.num_damages].x = ex;
839 laser.damage[laser.num_damages].y = ey;
843 static boolean StepBehind(void)
849 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
850 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
852 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
858 static int getMaskFromElement(int element)
860 if (IS_GRID(element))
861 return MM_MASK_GRID_1 + get_element_phase(element);
862 else if (IS_MCDUFFIN(element))
863 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
864 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
865 return MM_MASK_RECTANGLE;
867 return MM_MASK_CIRCLE;
870 static int ScanPixel(void)
875 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
876 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
879 // follow laser beam until it hits something (at least the screen border)
880 while (hit_mask == HIT_MASK_NO_HIT)
886 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
887 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
889 Debug("game:mm:ScanPixel", "touched screen border!");
895 for (i = 0; i < 4; i++)
897 int px = LX + (i % 2) * 2;
898 int py = LY + (i / 2) * 2;
901 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
902 int ly = (py + TILEY) / TILEY - 1; // negative values!
905 if (IN_LEV_FIELD(lx, ly))
907 int element = Tile[lx][ly];
909 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
913 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
915 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
917 pixel = ((element & (1 << pos)) ? 1 : 0);
921 int pos = getMaskFromElement(element);
923 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
928 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
929 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
932 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
933 hit_mask |= (1 << i);
936 if (hit_mask == HIT_MASK_NO_HIT)
938 // hit nothing -- go on with another step
947 static void DeactivateLaserTargetElement(void)
949 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
950 laser.dest_element_last == EL_MINE_ACTIVE ||
951 laser.dest_element_last == EL_GRAY_BALL_OPENING)
953 int x = laser.dest_element_last_x;
954 int y = laser.dest_element_last_y;
955 int element = laser.dest_element_last;
957 if (Tile[x][y] == element)
958 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
959 element == EL_MINE_ACTIVE ? EL_MINE : EL_BALL_GRAY);
961 if (Tile[x][y] == EL_BALL_GRAY)
964 laser.dest_element_last = EL_EMPTY;
965 laser.dest_element_last_x = -1;
966 laser.dest_element_last_y = -1;
972 int element = EL_EMPTY;
973 int last_element = EL_EMPTY;
974 int end = 0, rf = laser.num_edges;
976 // do not scan laser again after the game was lost for whatever reason
977 if (game_mm.game_over)
980 // do not scan laser if fuse is off
984 DeactivateLaserTargetElement();
986 laser.overloaded = FALSE;
987 laser.stops_inside_element = FALSE;
989 DrawLaser(0, DL_LASER_ENABLED);
992 Debug("game:mm:ScanLaser",
993 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1001 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1004 laser.overloaded = TRUE;
1009 hit_mask = ScanPixel();
1012 Debug("game:mm:ScanLaser",
1013 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1017 // hit something -- check out what it was
1018 ELX = (LX + XS) / TILEX;
1019 ELY = (LY + YS) / TILEY;
1022 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1023 hit_mask, LX, LY, ELX, ELY);
1026 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1029 laser.dest_element = element;
1034 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1036 /* we have hit the top-right and bottom-left element --
1037 choose the bottom-left one */
1038 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1039 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1040 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1041 ELX = (LX - 2) / TILEX;
1042 ELY = (LY + 2) / TILEY;
1045 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1047 /* we have hit the top-left and bottom-right element --
1048 choose the top-left one */
1049 // !!! SEE ABOVE !!!
1050 ELX = (LX - 2) / TILEX;
1051 ELY = (LY - 2) / TILEY;
1055 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1056 hit_mask, LX, LY, ELX, ELY);
1059 last_element = element;
1061 element = Tile[ELX][ELY];
1062 laser.dest_element = element;
1065 Debug("game:mm:ScanLaser",
1066 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1069 LX % TILEX, LY % TILEY,
1074 if (!IN_LEV_FIELD(ELX, ELY))
1075 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1079 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1080 if (element == EL_EMPTY &&
1081 IS_GRID_STEEL(last_element) &&
1082 laser.current_angle % 4) // angle is not 90°
1083 element = last_element;
1085 if (element == EL_EMPTY)
1087 if (!HitOnlyAnEdge(hit_mask))
1090 else if (element == EL_FUSE_ON)
1092 if (HitPolarizer(element, hit_mask))
1095 else if (IS_GRID(element) || IS_DF_GRID(element))
1097 if (HitPolarizer(element, hit_mask))
1100 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1101 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1103 if (HitBlock(element, hit_mask))
1110 else if (IS_MCDUFFIN(element))
1112 if (HitLaserSource(element, hit_mask))
1115 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1116 IS_RECEIVER(element))
1118 if (HitLaserDestination(element, hit_mask))
1121 else if (IS_WALL(element))
1123 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1125 if (HitReflectingWalls(element, hit_mask))
1130 if (HitAbsorbingWalls(element, hit_mask))
1136 if (HitElement(element, hit_mask))
1141 DrawLaser(rf - 1, DL_LASER_ENABLED);
1142 rf = laser.num_edges;
1144 if (!IS_DF_WALL_STEEL(element))
1146 // only used for scanning DF steel walls; reset for all other elements
1154 if (laser.dest_element != Tile[ELX][ELY])
1156 Debug("game:mm:ScanLaser",
1157 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1158 laser.dest_element, Tile[ELX][ELY]);
1162 if (!end && !laser.stops_inside_element && !StepBehind())
1165 Debug("game:mm:ScanLaser", "Go one step back");
1171 AddLaserEdge(LX, LY);
1175 DrawLaser(rf - 1, DL_LASER_ENABLED);
1177 Ct = CT = FrameCounter;
1180 if (!IN_LEV_FIELD(ELX, ELY))
1181 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1185 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1191 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1192 start_edge, num_edges, mode);
1197 Warn("DrawLaserExt: start_edge < 0");
1204 Warn("DrawLaserExt: num_edges < 0");
1210 if (mode == DL_LASER_DISABLED)
1212 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1216 // now draw the laser to the backbuffer and (if enabled) to the screen
1217 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1219 redraw_mask |= REDRAW_FIELD;
1221 if (mode == DL_LASER_ENABLED)
1224 // after the laser was deleted, the "damaged" graphics must be restored
1225 if (laser.num_damages)
1227 int damage_start = 0;
1230 // determine the starting edge, from which graphics need to be restored
1233 for (i = 0; i < laser.num_damages; i++)
1235 if (laser.damage[i].edge == start_edge + 1)
1244 // restore graphics from this starting edge to the end of damage list
1245 for (i = damage_start; i < laser.num_damages; i++)
1247 int lx = laser.damage[i].x;
1248 int ly = laser.damage[i].y;
1249 int element = Tile[lx][ly];
1251 if (Hit[lx][ly] == laser.damage[i].edge)
1252 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1255 if (Box[lx][ly] == laser.damage[i].edge)
1258 if (IS_DRAWABLE(element))
1259 DrawField_MM(lx, ly);
1262 elx = laser.damage[damage_start].x;
1263 ely = laser.damage[damage_start].y;
1264 element = Tile[elx][ely];
1267 if (IS_BEAMER(element))
1271 for (i = 0; i < laser.num_beamers; i++)
1272 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1274 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1275 mode, elx, ely, Hit[elx][ely], start_edge);
1276 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1277 get_element_angle(element), laser.damage[damage_start].angle);
1281 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1282 laser.num_beamers > 0 &&
1283 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1285 // element is outgoing beamer
1286 laser.num_damages = damage_start + 1;
1288 if (IS_BEAMER(element))
1289 laser.current_angle = get_element_angle(element);
1293 // element is incoming beamer or other element
1294 laser.num_damages = damage_start;
1295 laser.current_angle = laser.damage[laser.num_damages].angle;
1300 // no damages but McDuffin himself (who needs to be redrawn anyway)
1302 elx = laser.start_edge.x;
1303 ely = laser.start_edge.y;
1304 element = Tile[elx][ely];
1307 laser.num_edges = start_edge + 1;
1308 if (start_edge == 0)
1309 laser.current_angle = laser.start_angle;
1311 LX = laser.edge[start_edge].x - cSX2;
1312 LY = laser.edge[start_edge].y - cSY2;
1313 XS = 2 * Step[laser.current_angle].x;
1314 YS = 2 * Step[laser.current_angle].y;
1317 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1323 if (IS_BEAMER(element) ||
1324 IS_FIBRE_OPTIC(element) ||
1325 IS_PACMAN(element) ||
1326 IS_POLAR(element) ||
1327 IS_POLAR_CROSS(element) ||
1328 element == EL_FUSE_ON)
1333 Debug("game:mm:DrawLaserExt", "element == %d", element);
1336 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1337 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1341 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1342 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1343 (laser.num_beamers == 0 ||
1344 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1346 // element is incoming beamer or other element
1347 step_size = -step_size;
1352 if (IS_BEAMER(element))
1353 Debug("game:mm:DrawLaserExt",
1354 "start_edge == %d, laser.beamer_edge == %d",
1355 start_edge, laser.beamer_edge);
1358 LX += step_size * XS;
1359 LY += step_size * YS;
1361 else if (element != EL_EMPTY)
1370 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1375 void DrawLaser(int start_edge, int mode)
1377 // do not draw laser if fuse is off
1378 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1381 if (mode == DL_LASER_DISABLED)
1382 DeactivateLaserTargetElement();
1384 if (laser.num_edges - start_edge < 0)
1386 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1391 // check if laser is interrupted by beamer element
1392 if (laser.num_beamers > 0 &&
1393 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1395 if (mode == DL_LASER_ENABLED)
1398 int tmp_start_edge = start_edge;
1400 // draw laser segments forward from the start to the last beamer
1401 for (i = 0; i < laser.num_beamers; i++)
1403 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1405 if (tmp_num_edges <= 0)
1409 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1410 i, laser.beamer_edge[i], tmp_start_edge);
1413 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1415 tmp_start_edge = laser.beamer_edge[i];
1418 // draw last segment from last beamer to the end
1419 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1425 int last_num_edges = laser.num_edges;
1426 int num_beamers = laser.num_beamers;
1428 // delete laser segments backward from the end to the first beamer
1429 for (i = num_beamers - 1; i >= 0; i--)
1431 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1433 if (laser.beamer_edge[i] - start_edge <= 0)
1436 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1438 last_num_edges = laser.beamer_edge[i];
1439 laser.num_beamers--;
1443 if (last_num_edges - start_edge <= 0)
1444 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1445 last_num_edges, start_edge);
1448 // special case when rotating first beamer: delete laser edge on beamer
1449 // (but do not start scanning on previous edge to prevent mirror sound)
1450 if (last_num_edges - start_edge == 1 && start_edge > 0)
1451 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1453 // delete first segment from start to the first beamer
1454 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1459 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1462 game_mm.laser_enabled = mode;
1465 void DrawLaser_MM(void)
1467 DrawLaser(0, game_mm.laser_enabled);
1470 boolean HitElement(int element, int hit_mask)
1472 if (HitOnlyAnEdge(hit_mask))
1475 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1476 element = MovingOrBlocked2Element_MM(ELX, ELY);
1479 Debug("game:mm:HitElement", "(1): element == %d", element);
1483 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1484 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1487 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1491 AddDamagedField(ELX, ELY);
1493 // this is more precise: check if laser would go through the center
1494 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1496 // prevent cutting through laser emitter with laser beam
1497 if (IS_LASER(element))
1500 // skip the whole element before continuing the scan
1506 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1508 if (LX/TILEX > ELX || LY/TILEY > ELY)
1510 /* skipping scan positions to the right and down skips one scan
1511 position too much, because this is only the top left scan position
1512 of totally four scan positions (plus one to the right, one to the
1513 bottom and one to the bottom right) */
1523 Debug("game:mm:HitElement", "(2): element == %d", element);
1526 if (LX + 5 * XS < 0 ||
1536 Debug("game:mm:HitElement", "(3): element == %d", element);
1539 if (IS_POLAR(element) &&
1540 ((element - EL_POLAR_START) % 2 ||
1541 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1543 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1545 laser.num_damages--;
1550 if (IS_POLAR_CROSS(element) &&
1551 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1553 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1555 laser.num_damages--;
1560 if (!IS_BEAMER(element) &&
1561 !IS_FIBRE_OPTIC(element) &&
1562 !IS_GRID_WOOD(element) &&
1563 element != EL_FUEL_EMPTY)
1566 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1567 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1569 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1572 LX = ELX * TILEX + 14;
1573 LY = ELY * TILEY + 14;
1575 AddLaserEdge(LX, LY);
1578 if (IS_MIRROR(element) ||
1579 IS_MIRROR_FIXED(element) ||
1580 IS_POLAR(element) ||
1581 IS_POLAR_CROSS(element) ||
1582 IS_DF_MIRROR(element) ||
1583 IS_DF_MIRROR_AUTO(element) ||
1584 element == EL_PRISM ||
1585 element == EL_REFRACTOR)
1587 int current_angle = laser.current_angle;
1590 laser.num_damages--;
1592 AddDamagedField(ELX, ELY);
1594 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1597 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1599 if (IS_MIRROR(element) ||
1600 IS_MIRROR_FIXED(element) ||
1601 IS_DF_MIRROR(element) ||
1602 IS_DF_MIRROR_AUTO(element))
1603 laser.current_angle = get_mirrored_angle(laser.current_angle,
1604 get_element_angle(element));
1606 if (element == EL_PRISM || element == EL_REFRACTOR)
1607 laser.current_angle = RND(16);
1609 XS = 2 * Step[laser.current_angle].x;
1610 YS = 2 * Step[laser.current_angle].y;
1612 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1617 LX += step_size * XS;
1618 LY += step_size * YS;
1620 // draw sparkles on mirror
1621 if ((IS_MIRROR(element) ||
1622 IS_MIRROR_FIXED(element) ||
1623 element == EL_PRISM) &&
1624 current_angle != laser.current_angle)
1626 MovDelay[ELX][ELY] = 11; // start animation
1629 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1630 current_angle != laser.current_angle)
1631 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1634 (get_opposite_angle(laser.current_angle) ==
1635 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1637 return (laser.overloaded ? TRUE : FALSE);
1640 if (element == EL_FUEL_FULL)
1642 laser.stops_inside_element = TRUE;
1647 if (element == EL_BOMB || element == EL_MINE)
1649 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1651 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1653 laser.dest_element_last = Tile[ELX][ELY];
1654 laser.dest_element_last_x = ELX;
1655 laser.dest_element_last_y = ELY;
1657 if (element == EL_MINE)
1658 laser.overloaded = TRUE;
1661 if (element == EL_KETTLE ||
1662 element == EL_CELL ||
1663 element == EL_KEY ||
1664 element == EL_LIGHTBALL ||
1665 element == EL_PACMAN ||
1666 IS_PACMAN(element) ||
1667 IS_ENVELOPE(element))
1669 if (!IS_PACMAN(element) &&
1670 !IS_ENVELOPE(element))
1673 if (element == EL_PACMAN)
1676 if (element == EL_KETTLE || element == EL_CELL)
1678 if (game_mm.kettles_still_needed > 0)
1679 game_mm.kettles_still_needed--;
1681 game.snapshot.collected_item = TRUE;
1683 if (game_mm.kettles_still_needed == 0)
1687 DrawLaser(0, DL_LASER_ENABLED);
1690 else if (element == EL_KEY)
1694 else if (IS_PACMAN(element))
1696 DeletePacMan(ELX, ELY);
1698 else if (IS_ENVELOPE(element))
1700 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1703 RaiseScoreElement_MM(element);
1708 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1710 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1712 DrawLaser(0, DL_LASER_ENABLED);
1714 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1716 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1717 game_mm.lights_still_needed--;
1721 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1722 game_mm.lights_still_needed++;
1725 DrawField_MM(ELX, ELY);
1726 DrawLaser(0, DL_LASER_ENABLED);
1731 laser.stops_inside_element = TRUE;
1737 Debug("game:mm:HitElement", "(4): element == %d", element);
1740 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1741 laser.num_beamers < MAX_NUM_BEAMERS &&
1742 laser.beamer[BEAMER_NR(element)][1].num)
1744 int beamer_angle = get_element_angle(element);
1745 int beamer_nr = BEAMER_NR(element);
1749 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1752 laser.num_damages--;
1754 if (IS_FIBRE_OPTIC(element) ||
1755 laser.current_angle == get_opposite_angle(beamer_angle))
1759 LX = ELX * TILEX + 14;
1760 LY = ELY * TILEY + 14;
1762 AddLaserEdge(LX, LY);
1763 AddDamagedField(ELX, ELY);
1765 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1768 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1770 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1771 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1772 ELX = laser.beamer[beamer_nr][pos].x;
1773 ELY = laser.beamer[beamer_nr][pos].y;
1774 LX = ELX * TILEX + 14;
1775 LY = ELY * TILEY + 14;
1777 if (IS_BEAMER(element))
1779 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1780 XS = 2 * Step[laser.current_angle].x;
1781 YS = 2 * Step[laser.current_angle].y;
1784 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1786 AddLaserEdge(LX, LY);
1787 AddDamagedField(ELX, ELY);
1789 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1792 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1794 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1799 LX += step_size * XS;
1800 LY += step_size * YS;
1802 laser.num_beamers++;
1811 boolean HitOnlyAnEdge(int hit_mask)
1813 // check if the laser hit only the edge of an element and, if so, go on
1816 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1820 if ((hit_mask == HIT_MASK_TOPLEFT ||
1821 hit_mask == HIT_MASK_TOPRIGHT ||
1822 hit_mask == HIT_MASK_BOTTOMLEFT ||
1823 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1824 laser.current_angle % 4) // angle is not 90°
1828 if (hit_mask == HIT_MASK_TOPLEFT)
1833 else if (hit_mask == HIT_MASK_TOPRIGHT)
1838 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1843 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1849 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1855 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1862 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1868 boolean HitPolarizer(int element, int hit_mask)
1870 if (HitOnlyAnEdge(hit_mask))
1873 if (IS_DF_GRID(element))
1875 int grid_angle = get_element_angle(element);
1878 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1879 grid_angle, laser.current_angle);
1882 AddLaserEdge(LX, LY);
1883 AddDamagedField(ELX, ELY);
1886 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1888 if (laser.current_angle == grid_angle ||
1889 laser.current_angle == get_opposite_angle(grid_angle))
1891 // skip the whole element before continuing the scan
1897 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1899 if (LX/TILEX > ELX || LY/TILEY > ELY)
1901 /* skipping scan positions to the right and down skips one scan
1902 position too much, because this is only the top left scan position
1903 of totally four scan positions (plus one to the right, one to the
1904 bottom and one to the bottom right) */
1910 AddLaserEdge(LX, LY);
1916 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1918 LX / TILEX, LY / TILEY,
1919 LX % TILEX, LY % TILEY);
1924 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1926 return HitReflectingWalls(element, hit_mask);
1930 return HitAbsorbingWalls(element, hit_mask);
1933 else if (IS_GRID_STEEL(element))
1935 return HitReflectingWalls(element, hit_mask);
1937 else // IS_GRID_WOOD
1939 return HitAbsorbingWalls(element, hit_mask);
1945 boolean HitBlock(int element, int hit_mask)
1947 boolean check = FALSE;
1949 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1950 game_mm.num_keys == 0)
1953 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1956 int ex = ELX * TILEX + 14;
1957 int ey = ELY * TILEY + 14;
1961 for (i = 1; i < 32; i++)
1966 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1971 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1972 return HitAbsorbingWalls(element, hit_mask);
1976 AddLaserEdge(LX - XS, LY - YS);
1977 AddDamagedField(ELX, ELY);
1980 Box[ELX][ELY] = laser.num_edges;
1982 return HitReflectingWalls(element, hit_mask);
1985 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1987 int xs = XS / 2, ys = YS / 2;
1988 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1989 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1991 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1992 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1994 laser.overloaded = (element == EL_GATE_STONE);
1999 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2000 (hit_mask == HIT_MASK_TOP ||
2001 hit_mask == HIT_MASK_LEFT ||
2002 hit_mask == HIT_MASK_RIGHT ||
2003 hit_mask == HIT_MASK_BOTTOM))
2004 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2005 hit_mask == HIT_MASK_BOTTOM),
2006 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2007 hit_mask == HIT_MASK_RIGHT));
2008 AddLaserEdge(LX, LY);
2014 if (element == EL_GATE_STONE && Box[ELX][ELY])
2016 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2028 if (element == EL_BLOCK_STONE || element == EL_BLOCK_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_BLOCK_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 AddDamagedField(ELX, ELY);
2053 LX = ELX * TILEX + 14;
2054 LY = ELY * TILEY + 14;
2056 AddLaserEdge(LX, LY);
2058 laser.stops_inside_element = TRUE;
2066 boolean HitLaserSource(int element, int hit_mask)
2068 if (HitOnlyAnEdge(hit_mask))
2071 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2073 laser.overloaded = TRUE;
2078 boolean HitLaserDestination(int element, int hit_mask)
2080 if (HitOnlyAnEdge(hit_mask))
2083 if (element != EL_EXIT_OPEN &&
2084 !(IS_RECEIVER(element) &&
2085 game_mm.kettles_still_needed == 0 &&
2086 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2088 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2093 if (IS_RECEIVER(element) ||
2094 (IS_22_5_ANGLE(laser.current_angle) &&
2095 (ELX != (LX + 6 * XS) / TILEX ||
2096 ELY != (LY + 6 * YS) / TILEY ||
2105 LX = ELX * TILEX + 14;
2106 LY = ELY * TILEY + 14;
2108 laser.stops_inside_element = TRUE;
2111 AddLaserEdge(LX, LY);
2112 AddDamagedField(ELX, ELY);
2114 if (game_mm.lights_still_needed == 0)
2116 game_mm.level_solved = TRUE;
2118 SetTileCursorActive(FALSE);
2124 boolean HitReflectingWalls(int element, int hit_mask)
2126 // check if laser hits side of a wall with an angle that is not 90°
2127 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2128 hit_mask == HIT_MASK_LEFT ||
2129 hit_mask == HIT_MASK_RIGHT ||
2130 hit_mask == HIT_MASK_BOTTOM))
2132 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2137 if (!IS_DF_GRID(element))
2138 AddLaserEdge(LX, LY);
2140 // check if laser hits wall with an angle of 45°
2141 if (!IS_22_5_ANGLE(laser.current_angle))
2143 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2146 laser.current_angle = get_mirrored_angle(laser.current_angle,
2149 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2152 laser.current_angle = get_mirrored_angle(laser.current_angle,
2156 AddLaserEdge(LX, LY);
2158 XS = 2 * Step[laser.current_angle].x;
2159 YS = 2 * Step[laser.current_angle].y;
2163 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2165 laser.current_angle = get_mirrored_angle(laser.current_angle,
2170 if (!IS_DF_GRID(element))
2171 AddLaserEdge(LX, LY);
2176 if (!IS_DF_GRID(element))
2177 AddLaserEdge(LX, LY + YS / 2);
2180 if (!IS_DF_GRID(element))
2181 AddLaserEdge(LX, LY);
2184 YS = 2 * Step[laser.current_angle].y;
2188 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2190 laser.current_angle = get_mirrored_angle(laser.current_angle,
2195 if (!IS_DF_GRID(element))
2196 AddLaserEdge(LX, LY);
2201 if (!IS_DF_GRID(element))
2202 AddLaserEdge(LX + XS / 2, LY);
2205 if (!IS_DF_GRID(element))
2206 AddLaserEdge(LX, LY);
2209 XS = 2 * Step[laser.current_angle].x;
2215 // reflection at the edge of reflecting DF style wall
2216 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2218 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2219 hit_mask == HIT_MASK_TOPRIGHT) ||
2220 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2221 hit_mask == HIT_MASK_TOPLEFT) ||
2222 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2223 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2224 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2225 hit_mask == HIT_MASK_BOTTOMRIGHT))
2228 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2229 ANG_MIRROR_135 : ANG_MIRROR_45);
2231 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2233 AddDamagedField(ELX, ELY);
2234 AddLaserEdge(LX, LY);
2236 laser.current_angle = get_mirrored_angle(laser.current_angle,
2244 AddLaserEdge(LX, LY);
2250 // reflection inside an edge of reflecting DF style wall
2251 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2253 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2254 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2255 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2256 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2257 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2258 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2259 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2260 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2263 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2264 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2265 ANG_MIRROR_135 : ANG_MIRROR_45);
2267 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2270 AddDamagedField(ELX, ELY);
2273 AddLaserEdge(LX - XS, LY - YS);
2274 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2275 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2277 laser.current_angle = get_mirrored_angle(laser.current_angle,
2285 AddLaserEdge(LX, LY);
2291 // check if laser hits DF style wall with an angle of 90°
2292 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2294 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2295 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2296 (IS_VERT_ANGLE(laser.current_angle) &&
2297 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2299 // laser at last step touched nothing or the same side of the wall
2300 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2302 AddDamagedField(ELX, ELY);
2309 last_hit_mask = hit_mask;
2316 if (!HitOnlyAnEdge(hit_mask))
2318 laser.overloaded = TRUE;
2326 boolean HitAbsorbingWalls(int element, int hit_mask)
2328 if (HitOnlyAnEdge(hit_mask))
2332 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2334 AddLaserEdge(LX - XS, LY - YS);
2341 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2343 AddLaserEdge(LX - XS, LY - YS);
2349 if (IS_WALL_WOOD(element) ||
2350 IS_DF_WALL_WOOD(element) ||
2351 IS_GRID_WOOD(element) ||
2352 IS_GRID_WOOD_FIXED(element) ||
2353 IS_GRID_WOOD_AUTO(element) ||
2354 element == EL_FUSE_ON ||
2355 element == EL_BLOCK_WOOD ||
2356 element == EL_GATE_WOOD)
2358 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2363 if (IS_WALL_ICE(element))
2367 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2368 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2370 // check if laser hits wall with an angle of 90°
2371 if (IS_90_ANGLE(laser.current_angle))
2372 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2374 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2378 for (i = 0; i < 4; i++)
2380 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2381 mask = 15 - (8 >> i);
2382 else if (ABS(XS) == 4 &&
2384 (XS > 0) == (i % 2) &&
2385 (YS < 0) == (i / 2))
2386 mask = 3 + (i / 2) * 9;
2387 else if (ABS(YS) == 4 &&
2389 (XS < 0) == (i % 2) &&
2390 (YS > 0) == (i / 2))
2391 mask = 5 + (i % 2) * 5;
2395 laser.wall_mask = mask;
2397 else if (IS_WALL_AMOEBA(element))
2399 int elx = (LX - 2 * XS) / TILEX;
2400 int ely = (LY - 2 * YS) / TILEY;
2401 int element2 = Tile[elx][ely];
2404 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2406 laser.dest_element = EL_EMPTY;
2414 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2415 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2417 if (IS_90_ANGLE(laser.current_angle))
2418 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2420 laser.dest_element = element2 | EL_WALL_AMOEBA;
2422 laser.wall_mask = mask;
2428 static void OpenExit(int x, int y)
2432 if (!MovDelay[x][y]) // next animation frame
2433 MovDelay[x][y] = 4 * delay;
2435 if (MovDelay[x][y]) // wait some time before next frame
2440 phase = MovDelay[x][y] / delay;
2442 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2443 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2445 if (!MovDelay[x][y])
2447 Tile[x][y] = EL_EXIT_OPEN;
2453 static void OpenSurpriseBall(int x, int y)
2457 if (!MovDelay[x][y]) // next animation frame
2458 MovDelay[x][y] = 50 * delay;
2460 if (MovDelay[x][y]) // wait some time before next frame
2464 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2467 int graphic = el2gfx(Store[x][y]);
2469 int dx = RND(26), dy = RND(26);
2471 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2473 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2474 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2476 laser.redraw = TRUE;
2478 MarkTileDirty(x, y);
2481 if (!MovDelay[x][y])
2483 Tile[x][y] = Store[x][y];
2484 Store[x][y] = Store2[x][y] = 0;
2493 static void OpenEnvelope(int x, int y)
2495 int num_frames = 8; // seven frames plus final empty space
2497 if (!MovDelay[x][y]) // next animation frame
2498 MovDelay[x][y] = num_frames;
2500 if (MovDelay[x][y]) // wait some time before next frame
2502 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2506 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2508 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2509 int frame = num_frames - MovDelay[x][y] - 1;
2511 DrawGraphicAnimation_MM(x, y, graphic, frame);
2513 laser.redraw = TRUE;
2516 if (MovDelay[x][y] == 0)
2518 Tile[x][y] = EL_EMPTY;
2524 ShowEnvelope_MM(nr);
2529 static void MeltIce(int x, int y)
2534 if (!MovDelay[x][y]) // next animation frame
2535 MovDelay[x][y] = frames * delay;
2537 if (MovDelay[x][y]) // wait some time before next frame
2540 int wall_mask = Store2[x][y];
2541 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2544 phase = frames - MovDelay[x][y] / delay - 1;
2546 if (!MovDelay[x][y])
2550 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2551 Store[x][y] = Store2[x][y] = 0;
2553 DrawWalls_MM(x, y, Tile[x][y]);
2555 if (Tile[x][y] == EL_WALL_ICE)
2556 Tile[x][y] = EL_EMPTY;
2558 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2559 if (laser.damage[i].is_mirror)
2563 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2565 DrawLaser(0, DL_LASER_DISABLED);
2569 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2571 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2573 laser.redraw = TRUE;
2578 static void GrowAmoeba(int x, int y)
2583 if (!MovDelay[x][y]) // next animation frame
2584 MovDelay[x][y] = frames * delay;
2586 if (MovDelay[x][y]) // wait some time before next frame
2589 int wall_mask = Store2[x][y];
2590 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2593 phase = MovDelay[x][y] / delay;
2595 if (!MovDelay[x][y])
2597 Tile[x][y] = real_element;
2598 Store[x][y] = Store2[x][y] = 0;
2600 DrawWalls_MM(x, y, Tile[x][y]);
2601 DrawLaser(0, DL_LASER_ENABLED);
2603 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2605 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2610 static void DrawFieldAnimated_MM(int x, int y)
2614 laser.redraw = TRUE;
2617 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2619 int element = Tile[x][y];
2620 int graphic = el2gfx(element);
2622 if (!getGraphicInfo_NewFrame(x, y, graphic))
2627 laser.redraw = TRUE;
2630 static void DrawFieldTwinkle(int x, int y)
2632 if (MovDelay[x][y] != 0) // wait some time before next frame
2638 if (MovDelay[x][y] != 0)
2640 int graphic = IMG_TWINKLE_WHITE;
2641 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2643 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2646 laser.redraw = TRUE;
2650 static void Explode_MM(int x, int y, int phase, int mode)
2652 int num_phase = 9, delay = 2;
2653 int last_phase = num_phase * delay;
2654 int half_phase = (num_phase / 2) * delay;
2656 laser.redraw = TRUE;
2658 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2660 int center_element = Tile[x][y];
2662 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2664 // put moving element to center field (and let it explode there)
2665 center_element = MovingOrBlocked2Element_MM(x, y);
2666 RemoveMovingField_MM(x, y);
2668 Tile[x][y] = center_element;
2671 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2672 Store[x][y] = center_element;
2674 Store[x][y] = EL_EMPTY;
2676 Store2[x][y] = mode;
2678 Tile[x][y] = EL_EXPLODING_OPAQUE;
2680 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2681 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2684 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2686 ExplodePhase[x][y] = 1;
2692 GfxFrame[x][y] = 0; // restart explosion animation
2694 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2696 if (phase == half_phase)
2698 Tile[x][y] = EL_EXPLODING_TRANSP;
2700 if (x == ELX && y == ELY)
2704 if (phase == last_phase)
2706 if (Store[x][y] == EL_BOMB_ACTIVE)
2708 DrawLaser(0, DL_LASER_DISABLED);
2711 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2712 Store[x][y] = EL_EMPTY;
2714 GameOver_MM(GAME_OVER_DELAYED);
2716 laser.overloaded = FALSE;
2718 else if (IS_MCDUFFIN(Store[x][y]))
2720 Store[x][y] = EL_EMPTY;
2722 GameOver_MM(GAME_OVER_BOMB);
2725 Tile[x][y] = Store[x][y];
2726 Store[x][y] = Store2[x][y] = 0;
2727 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2732 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2734 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2735 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2737 DrawGraphicAnimation_MM(x, y, graphic, frame);
2739 MarkTileDirty(x, y);
2743 static void Bang_MM(int x, int y)
2745 int element = Tile[x][y];
2748 DrawLaser(0, DL_LASER_ENABLED);
2751 if (IS_PACMAN(element))
2752 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2753 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2754 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2755 else if (element == EL_KEY)
2756 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2758 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2760 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2763 void TurnRound(int x, int y)
2775 { 0, 0 }, { 0, 0 }, { 0, 0 },
2780 int left, right, back;
2784 { MV_DOWN, MV_UP, MV_RIGHT },
2785 { MV_UP, MV_DOWN, MV_LEFT },
2787 { MV_LEFT, MV_RIGHT, MV_DOWN },
2788 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2789 { MV_RIGHT, MV_LEFT, MV_UP }
2792 int element = Tile[x][y];
2793 int old_move_dir = MovDir[x][y];
2794 int right_dir = turn[old_move_dir].right;
2795 int back_dir = turn[old_move_dir].back;
2796 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2797 int right_x = x + right_dx, right_y = y + right_dy;
2799 if (element == EL_PACMAN)
2801 boolean can_turn_right = FALSE;
2803 if (IN_LEV_FIELD(right_x, right_y) &&
2804 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2805 can_turn_right = TRUE;
2808 MovDir[x][y] = right_dir;
2810 MovDir[x][y] = back_dir;
2816 static void StartMoving_MM(int x, int y)
2818 int element = Tile[x][y];
2823 if (CAN_MOVE(element))
2827 if (MovDelay[x][y]) // wait some time before next movement
2835 // now make next step
2837 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2839 if (element == EL_PACMAN &&
2840 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2841 !ObjHit(newx, newy, HIT_POS_CENTER))
2843 Store[newx][newy] = Tile[newx][newy];
2844 Tile[newx][newy] = EL_EMPTY;
2846 DrawField_MM(newx, newy);
2848 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2849 ObjHit(newx, newy, HIT_POS_CENTER))
2851 // object was running against a wall
2858 InitMovingField_MM(x, y, MovDir[x][y]);
2862 ContinueMoving_MM(x, y);
2865 static void ContinueMoving_MM(int x, int y)
2867 int element = Tile[x][y];
2868 int direction = MovDir[x][y];
2869 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2870 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2871 int horiz_move = (dx!=0);
2872 int newx = x + dx, newy = y + dy;
2873 int step = (horiz_move ? dx : dy) * TILEX / 8;
2875 MovPos[x][y] += step;
2877 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2879 Tile[x][y] = EL_EMPTY;
2880 Tile[newx][newy] = element;
2882 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2883 MovDelay[newx][newy] = 0;
2885 if (!CAN_MOVE(element))
2886 MovDir[newx][newy] = 0;
2889 DrawField_MM(newx, newy);
2891 Stop[newx][newy] = TRUE;
2893 if (element == EL_PACMAN)
2895 if (Store[newx][newy] == EL_BOMB)
2896 Bang_MM(newx, newy);
2898 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2899 (LX + 2 * XS) / TILEX == newx &&
2900 (LY + 2 * YS) / TILEY == newy)
2907 else // still moving on
2912 laser.redraw = TRUE;
2915 boolean ClickElement(int x, int y, int button)
2917 static DelayCounter click_delay = { CLICK_DELAY };
2918 static boolean new_button = TRUE;
2919 boolean element_clicked = FALSE;
2924 // initialize static variables
2925 click_delay.count = 0;
2926 click_delay.value = CLICK_DELAY;
2932 // do not rotate objects hit by the laser after the game was solved
2933 if (game_mm.level_solved && Hit[x][y])
2936 if (button == MB_RELEASED)
2939 click_delay.value = CLICK_DELAY;
2941 // release eventually hold auto-rotating mirror
2942 RotateMirror(x, y, MB_RELEASED);
2947 if (!FrameReached(&click_delay) && !new_button)
2950 if (button == MB_MIDDLEBUTTON) // middle button has no function
2953 if (!IN_LEV_FIELD(x, y))
2956 if (Tile[x][y] == EL_EMPTY)
2959 element = Tile[x][y];
2961 if (IS_MIRROR(element) ||
2962 IS_BEAMER(element) ||
2963 IS_POLAR(element) ||
2964 IS_POLAR_CROSS(element) ||
2965 IS_DF_MIRROR(element) ||
2966 IS_DF_MIRROR_AUTO(element))
2968 RotateMirror(x, y, button);
2970 element_clicked = TRUE;
2972 else if (IS_MCDUFFIN(element))
2974 if (!laser.fuse_off)
2976 DrawLaser(0, DL_LASER_DISABLED);
2983 element = get_rotated_element(element, BUTTON_ROTATION(button));
2984 laser.start_angle = get_element_angle(element);
2988 Tile[x][y] = element;
2995 if (!laser.fuse_off)
2998 element_clicked = TRUE;
3000 else if (element == EL_FUSE_ON && laser.fuse_off)
3002 if (x != laser.fuse_x || y != laser.fuse_y)
3005 laser.fuse_off = FALSE;
3006 laser.fuse_x = laser.fuse_y = -1;
3008 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3011 element_clicked = TRUE;
3013 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3015 laser.fuse_off = TRUE;
3018 laser.overloaded = FALSE;
3020 DrawLaser(0, DL_LASER_DISABLED);
3021 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3023 element_clicked = TRUE;
3025 else if (element == EL_LIGHTBALL)
3028 RaiseScoreElement_MM(element);
3029 DrawLaser(0, DL_LASER_ENABLED);
3031 element_clicked = TRUE;
3033 else if (IS_ENVELOPE(element))
3035 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3037 element_clicked = TRUE;
3040 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3043 return element_clicked;
3046 void RotateMirror(int x, int y, int button)
3048 if (button == MB_RELEASED)
3050 // release eventually hold auto-rotating mirror
3057 if (IS_MIRROR(Tile[x][y]) ||
3058 IS_POLAR_CROSS(Tile[x][y]) ||
3059 IS_POLAR(Tile[x][y]) ||
3060 IS_BEAMER(Tile[x][y]) ||
3061 IS_DF_MIRROR(Tile[x][y]) ||
3062 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3063 IS_GRID_WOOD_AUTO(Tile[x][y]))
3065 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3067 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3069 if (button == MB_LEFTBUTTON)
3071 // left mouse button only for manual adjustment, no auto-rotating;
3072 // freeze mirror for until mouse button released
3076 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3078 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3082 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3084 int edge = Hit[x][y];
3090 DrawLaser(edge - 1, DL_LASER_DISABLED);
3094 else if (ObjHit(x, y, HIT_POS_CENTER))
3096 int edge = Hit[x][y];
3100 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3105 DrawLaser(edge - 1, DL_LASER_DISABLED);
3112 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3117 if ((IS_BEAMER(Tile[x][y]) ||
3118 IS_POLAR(Tile[x][y]) ||
3119 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3121 if (IS_BEAMER(Tile[x][y]))
3124 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3125 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3138 DrawLaser(0, DL_LASER_ENABLED);
3142 static void AutoRotateMirrors(void)
3146 if (!FrameReached(&rotate_delay))
3149 for (x = 0; x < lev_fieldx; x++)
3151 for (y = 0; y < lev_fieldy; y++)
3153 int element = Tile[x][y];
3155 // do not rotate objects hit by the laser after the game was solved
3156 if (game_mm.level_solved && Hit[x][y])
3159 if (IS_DF_MIRROR_AUTO(element) ||
3160 IS_GRID_WOOD_AUTO(element) ||
3161 IS_GRID_STEEL_AUTO(element) ||
3162 element == EL_REFRACTOR)
3163 RotateMirror(x, y, MB_RIGHTBUTTON);
3168 boolean ObjHit(int obx, int oby, int bits)
3175 if (bits & HIT_POS_CENTER)
3177 if (CheckLaserPixel(cSX + obx + 15,
3182 if (bits & HIT_POS_EDGE)
3184 for (i = 0; i < 4; i++)
3185 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3186 cSY + oby + 31 * (i / 2)))
3190 if (bits & HIT_POS_BETWEEN)
3192 for (i = 0; i < 4; i++)
3193 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3194 cSY + 4 + oby + 22 * (i / 2)))
3201 void DeletePacMan(int px, int py)
3207 if (game_mm.num_pacman <= 1)
3209 game_mm.num_pacman = 0;
3213 for (i = 0; i < game_mm.num_pacman; i++)
3214 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3217 game_mm.num_pacman--;
3219 for (j = i; j < game_mm.num_pacman; j++)
3221 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3222 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3223 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3227 void ColorCycling(void)
3229 static int CC, Cc = 0;
3231 static int color, old = 0xF00, new = 0x010, mult = 1;
3232 static unsigned short red, green, blue;
3234 if (color_status == STATIC_COLORS)
3239 if (CC < Cc || CC > Cc + 2)
3243 color = old + new * mult;
3249 if (ABS(mult) == 16)
3259 red = 0x0e00 * ((color & 0xF00) >> 8);
3260 green = 0x0e00 * ((color & 0x0F0) >> 4);
3261 blue = 0x0e00 * ((color & 0x00F));
3262 SetRGB(pen_magicolor[0], red, green, blue);
3264 red = 0x1111 * ((color & 0xF00) >> 8);
3265 green = 0x1111 * ((color & 0x0F0) >> 4);
3266 blue = 0x1111 * ((color & 0x00F));
3267 SetRGB(pen_magicolor[1], red, green, blue);
3271 static void GameActions_MM_Ext(void)
3278 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3281 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3283 element = Tile[x][y];
3285 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3286 StartMoving_MM(x, y);
3287 else if (IS_MOVING(x, y))
3288 ContinueMoving_MM(x, y);
3289 else if (IS_EXPLODING(element))
3290 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3291 else if (element == EL_EXIT_OPENING)
3293 else if (element == EL_GRAY_BALL_OPENING)
3294 OpenSurpriseBall(x, y);
3295 else if (IS_ENVELOPE_OPENING(element))
3297 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3299 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3301 else if (IS_MIRROR(element) ||
3302 IS_MIRROR_FIXED(element) ||
3303 element == EL_PRISM)
3304 DrawFieldTwinkle(x, y);
3305 else if (element == EL_GRAY_BALL_OPENING ||
3306 element == EL_BOMB_ACTIVE ||
3307 element == EL_MINE_ACTIVE)
3308 DrawFieldAnimated_MM(x, y);
3309 else if (!IS_BLOCKED(x, y))
3310 DrawFieldAnimatedIfNeeded_MM(x, y);
3313 AutoRotateMirrors();
3316 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3318 // redraw after Explode_MM() ...
3320 DrawLaser(0, DL_LASER_ENABLED);
3321 laser.redraw = FALSE;
3326 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3330 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3332 DrawLaser(0, DL_LASER_DISABLED);
3337 // skip all following game actions if game is over
3338 if (game_mm.game_over)
3341 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3345 GameOver_MM(GAME_OVER_NO_ENERGY);
3350 if (FrameReached(&energy_delay))
3352 if (game_mm.energy_left > 0)
3353 game_mm.energy_left--;
3355 // when out of energy, wait another frame to play "out of time" sound
3358 element = laser.dest_element;
3361 if (element != Tile[ELX][ELY])
3363 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3364 element, Tile[ELX][ELY]);
3368 if (!laser.overloaded && laser.overload_value == 0 &&
3369 element != EL_BOMB &&
3370 element != EL_BOMB_ACTIVE &&
3371 element != EL_MINE &&
3372 element != EL_MINE_ACTIVE &&
3373 element != EL_BALL_GRAY &&
3374 element != EL_BLOCK_STONE &&
3375 element != EL_BLOCK_WOOD &&
3376 element != EL_FUSE_ON &&
3377 element != EL_FUEL_FULL &&
3378 !IS_WALL_ICE(element) &&
3379 !IS_WALL_AMOEBA(element))
3382 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3384 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3385 (!laser.overloaded && laser.overload_value > 0)) &&
3386 FrameReached(&overload_delay))
3388 if (laser.overloaded)
3389 laser.overload_value++;
3391 laser.overload_value--;
3393 if (game_mm.cheat_no_overload)
3395 laser.overloaded = FALSE;
3396 laser.overload_value = 0;
3399 game_mm.laser_overload_value = laser.overload_value;
3401 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3403 SetLaserColor(0xFF);
3405 DrawLaser(0, DL_LASER_ENABLED);
3408 if (!laser.overloaded)
3409 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3410 else if (setup.sound_loops)
3411 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3413 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3415 if (laser.overloaded)
3418 BlitBitmap(pix[PIX_DOOR], drawto,
3419 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3420 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3421 - laser.overload_value,
3422 OVERLOAD_XSIZE, laser.overload_value,
3423 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3424 - laser.overload_value);
3426 redraw_mask |= REDRAW_DOOR_1;
3431 BlitBitmap(pix[PIX_DOOR], drawto,
3432 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3433 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3434 DX_OVERLOAD, DY_OVERLOAD);
3436 redraw_mask |= REDRAW_DOOR_1;
3439 if (laser.overload_value == MAX_LASER_OVERLOAD)
3441 UpdateAndDisplayGameControlValues();
3445 GameOver_MM(GAME_OVER_OVERLOADED);
3456 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3458 if (game_mm.cheat_no_explosion)
3463 laser.dest_element = EL_EXPLODING_OPAQUE;
3468 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3470 laser.fuse_off = TRUE;
3474 DrawLaser(0, DL_LASER_DISABLED);
3475 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3478 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3480 if (!Store2[ELX][ELY]) // check if content element not yet determined
3482 int last_anim_random_frame = gfx.anim_random_frame;
3485 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3486 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3488 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3489 native_mm_level.ball_choice_mode, 0,
3490 game_mm.ball_choice_pos);
3492 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3493 gfx.anim_random_frame = last_anim_random_frame;
3495 game_mm.ball_choice_pos++;
3497 int new_element = native_mm_level.ball_content[element_pos];
3499 // randomly rotate newly created game element, if needed
3500 if (native_mm_level.rotate_ball_content)
3501 new_element = get_rotated_element(new_element, RND(16));
3503 Store[ELX][ELY] = new_element;
3504 Store2[ELX][ELY] = TRUE;
3507 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3509 // !!! CHECK AGAIN: Laser on Polarizer !!!
3512 laser.dest_element_last = Tile[ELX][ELY];
3513 laser.dest_element_last_x = ELX;
3514 laser.dest_element_last_y = ELY;
3524 element = EL_MIRROR_START + RND(16);
3530 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3537 element = (rnd == 0 ? EL_FUSE_ON :
3538 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3539 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3540 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3541 EL_MIRROR_FIXED_START + rnd - 25);
3546 graphic = el2gfx(element);
3548 for (i = 0; i < 50; i++)
3554 BlitBitmap(pix[PIX_BACK], drawto,
3555 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3556 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3557 SX + ELX * TILEX + x,
3558 SY + ELY * TILEY + y);
3560 MarkTileDirty(ELX, ELY);
3563 DrawLaser(0, DL_LASER_ENABLED);
3565 Delay_WithScreenUpdates(50);
3568 Tile[ELX][ELY] = element;
3569 DrawField_MM(ELX, ELY);
3572 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3575 // above stuff: GRAY BALL -> PRISM !!!
3577 LX = ELX * TILEX + 14;
3578 LY = ELY * TILEY + 14;
3579 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3586 laser.num_edges -= 2;
3587 laser.num_damages--;
3591 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3592 if (laser.damage[i].is_mirror)
3596 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3598 DrawLaser(0, DL_LASER_DISABLED);
3600 DrawLaser(0, DL_LASER_DISABLED);
3609 if (IS_WALL_ICE(element) && CT > 50)
3611 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3614 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3615 Store[ELX][ELY] = EL_WALL_ICE;
3616 Store2[ELX][ELY] = laser.wall_mask;
3618 laser.dest_element = Tile[ELX][ELY];
3623 for (i = 0; i < 5; i++)
3629 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3633 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3635 Delay_WithScreenUpdates(100);
3638 if (Tile[ELX][ELY] == EL_WALL_ICE)
3639 Tile[ELX][ELY] = EL_EMPTY;
3643 LX = laser.edge[laser.num_edges].x - cSX2;
3644 LY = laser.edge[laser.num_edges].y - cSY2;
3647 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3648 if (laser.damage[i].is_mirror)
3652 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3654 DrawLaser(0, DL_LASER_DISABLED);
3661 if (IS_WALL_AMOEBA(element) && CT > 60)
3663 int k1, k2, k3, dx, dy, de, dm;
3664 int element2 = Tile[ELX][ELY];
3666 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3669 for (i = laser.num_damages - 1; i >= 0; i--)
3670 if (laser.damage[i].is_mirror)
3673 r = laser.num_edges;
3674 d = laser.num_damages;
3681 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3684 DrawLaser(0, DL_LASER_ENABLED);
3687 x = laser.damage[k1].x;
3688 y = laser.damage[k1].y;
3693 for (i = 0; i < 4; i++)
3695 if (laser.wall_mask & (1 << i))
3697 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3698 cSY + ELY * TILEY + 31 * (i / 2)))
3701 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3702 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3709 for (i = 0; i < 4; i++)
3711 if (laser.wall_mask & (1 << i))
3713 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3714 cSY + ELY * TILEY + 31 * (i / 2)))
3721 if (laser.num_beamers > 0 ||
3722 k1 < 1 || k2 < 4 || k3 < 4 ||
3723 CheckLaserPixel(cSX + ELX * TILEX + 14,
3724 cSY + ELY * TILEY + 14))
3726 laser.num_edges = r;
3727 laser.num_damages = d;
3729 DrawLaser(0, DL_LASER_DISABLED);
3732 Tile[ELX][ELY] = element | laser.wall_mask;
3736 de = Tile[ELX][ELY];
3737 dm = laser.wall_mask;
3741 int x = ELX, y = ELY;
3742 int wall_mask = laser.wall_mask;
3745 DrawLaser(0, DL_LASER_ENABLED);
3747 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3749 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3750 Store[x][y] = EL_WALL_AMOEBA;
3751 Store2[x][y] = wall_mask;
3757 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3759 DrawLaser(0, DL_LASER_ENABLED);
3761 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3763 for (i = 4; i >= 0; i--)
3765 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3768 Delay_WithScreenUpdates(20);
3771 DrawLaser(0, DL_LASER_ENABLED);
3776 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3777 laser.stops_inside_element && CT > native_mm_level.time_block)
3782 if (ABS(XS) > ABS(YS))
3789 for (i = 0; i < 4; i++)
3796 x = ELX + Step[k * 4].x;
3797 y = ELY + Step[k * 4].y;
3799 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3802 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3810 laser.overloaded = (element == EL_BLOCK_STONE);
3815 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3818 Tile[x][y] = element;
3820 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3823 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3825 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3826 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3834 if (element == EL_FUEL_FULL && CT > 10)
3836 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3837 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3839 for (i = start; i <= num_init_game_frames; i++)
3841 if (i == num_init_game_frames)
3842 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3843 else if (setup.sound_loops)
3844 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3846 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3848 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3850 UpdateAndDisplayGameControlValues();
3855 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3857 DrawField_MM(ELX, ELY);
3859 DrawLaser(0, DL_LASER_ENABLED);
3867 void GameActions_MM(struct MouseActionInfo action)
3869 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3870 boolean button_released = (action.button == MB_RELEASED);
3872 GameActions_MM_Ext();
3874 CheckSingleStepMode_MM(element_clicked, button_released);
3877 void MovePacMen(void)
3879 int mx, my, ox, oy, nx, ny;
3883 if (++pacman_nr >= game_mm.num_pacman)
3886 game_mm.pacman[pacman_nr].dir--;
3888 for (l = 1; l < 5; l++)
3890 game_mm.pacman[pacman_nr].dir++;
3892 if (game_mm.pacman[pacman_nr].dir > 4)
3893 game_mm.pacman[pacman_nr].dir = 1;
3895 if (game_mm.pacman[pacman_nr].dir % 2)
3898 my = game_mm.pacman[pacman_nr].dir - 2;
3903 mx = 3 - game_mm.pacman[pacman_nr].dir;
3906 ox = game_mm.pacman[pacman_nr].x;
3907 oy = game_mm.pacman[pacman_nr].y;
3910 element = Tile[nx][ny];
3912 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3915 if (!IS_EATABLE4PACMAN(element))
3918 if (ObjHit(nx, ny, HIT_POS_CENTER))
3921 Tile[ox][oy] = EL_EMPTY;
3923 EL_PACMAN_RIGHT - 1 +
3924 (game_mm.pacman[pacman_nr].dir - 1 +
3925 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3927 game_mm.pacman[pacman_nr].x = nx;
3928 game_mm.pacman[pacman_nr].y = ny;
3930 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3932 if (element != EL_EMPTY)
3934 int graphic = el2gfx(Tile[nx][ny]);
3939 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3942 ox = cSX + ox * TILEX;
3943 oy = cSY + oy * TILEY;
3945 for (i = 1; i < 33; i += 2)
3946 BlitBitmap(bitmap, window,
3947 src_x, src_y, TILEX, TILEY,
3948 ox + i * mx, oy + i * my);
3949 Ct = Ct + FrameCounter - CT;
3952 DrawField_MM(nx, ny);
3955 if (!laser.fuse_off)
3957 DrawLaser(0, DL_LASER_ENABLED);
3959 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3961 AddDamagedField(nx, ny);
3963 laser.damage[laser.num_damages - 1].edge = 0;
3967 if (element == EL_BOMB)
3968 DeletePacMan(nx, ny);
3970 if (IS_WALL_AMOEBA(element) &&
3971 (LX + 2 * XS) / TILEX == nx &&
3972 (LY + 2 * YS) / TILEY == ny)
3982 static void InitMovingField_MM(int x, int y, int direction)
3984 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3985 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3987 MovDir[x][y] = direction;
3988 MovDir[newx][newy] = direction;
3990 if (Tile[newx][newy] == EL_EMPTY)
3991 Tile[newx][newy] = EL_BLOCKED;
3994 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3996 int direction = MovDir[x][y];
3997 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3998 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4004 static void Blocked2Moving_MM(int x, int y,
4005 int *comes_from_x, int *comes_from_y)
4007 int oldx = x, oldy = y;
4008 int direction = MovDir[x][y];
4010 if (direction == MV_LEFT)
4012 else if (direction == MV_RIGHT)
4014 else if (direction == MV_UP)
4016 else if (direction == MV_DOWN)
4019 *comes_from_x = oldx;
4020 *comes_from_y = oldy;
4023 static int MovingOrBlocked2Element_MM(int x, int y)
4025 int element = Tile[x][y];
4027 if (element == EL_BLOCKED)
4031 Blocked2Moving_MM(x, y, &oldx, &oldy);
4033 return Tile[oldx][oldy];
4040 static void RemoveField(int x, int y)
4042 Tile[x][y] = EL_EMPTY;
4049 static void RemoveMovingField_MM(int x, int y)
4051 int oldx = x, oldy = y, newx = x, newy = y;
4053 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4056 if (IS_MOVING(x, y))
4058 Moving2Blocked_MM(x, y, &newx, &newy);
4059 if (Tile[newx][newy] != EL_BLOCKED)
4062 else if (Tile[x][y] == EL_BLOCKED)
4064 Blocked2Moving_MM(x, y, &oldx, &oldy);
4065 if (!IS_MOVING(oldx, oldy))
4069 Tile[oldx][oldy] = EL_EMPTY;
4070 Tile[newx][newy] = EL_EMPTY;
4071 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4072 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4074 DrawLevelField_MM(oldx, oldy);
4075 DrawLevelField_MM(newx, newy);
4078 void PlaySoundLevel(int x, int y, int sound_nr)
4080 int sx = SCREENX(x), sy = SCREENY(y);
4082 int silence_distance = 8;
4084 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4085 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4088 if (!IN_LEV_FIELD(x, y) ||
4089 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4090 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4093 volume = SOUND_MAX_VOLUME;
4096 stereo = (sx - SCR_FIELDX/2) * 12;
4098 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4099 if (stereo > SOUND_MAX_RIGHT)
4100 stereo = SOUND_MAX_RIGHT;
4101 if (stereo < SOUND_MAX_LEFT)
4102 stereo = SOUND_MAX_LEFT;
4105 if (!IN_SCR_FIELD(sx, sy))
4107 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4108 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4110 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4113 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4116 static void RaiseScore_MM(int value)
4118 game_mm.score += value;
4121 void RaiseScoreElement_MM(int element)
4126 case EL_PACMAN_RIGHT:
4128 case EL_PACMAN_LEFT:
4129 case EL_PACMAN_DOWN:
4130 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4134 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4139 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4143 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4152 // ----------------------------------------------------------------------------
4153 // Mirror Magic game engine snapshot handling functions
4154 // ----------------------------------------------------------------------------
4156 void SaveEngineSnapshotValues_MM(void)
4160 engine_snapshot_mm.game_mm = game_mm;
4161 engine_snapshot_mm.laser = laser;
4163 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4165 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4167 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4168 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4169 engine_snapshot_mm.Box[x][y] = Box[x][y];
4170 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4174 engine_snapshot_mm.LX = LX;
4175 engine_snapshot_mm.LY = LY;
4176 engine_snapshot_mm.XS = XS;
4177 engine_snapshot_mm.YS = YS;
4178 engine_snapshot_mm.ELX = ELX;
4179 engine_snapshot_mm.ELY = ELY;
4180 engine_snapshot_mm.CT = CT;
4181 engine_snapshot_mm.Ct = Ct;
4183 engine_snapshot_mm.last_LX = last_LX;
4184 engine_snapshot_mm.last_LY = last_LY;
4185 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4186 engine_snapshot_mm.hold_x = hold_x;
4187 engine_snapshot_mm.hold_y = hold_y;
4188 engine_snapshot_mm.pacman_nr = pacman_nr;
4190 engine_snapshot_mm.rotate_delay = rotate_delay;
4191 engine_snapshot_mm.pacman_delay = pacman_delay;
4192 engine_snapshot_mm.energy_delay = energy_delay;
4193 engine_snapshot_mm.overload_delay = overload_delay;
4196 void LoadEngineSnapshotValues_MM(void)
4200 // stored engine snapshot buffers already restored at this point
4202 game_mm = engine_snapshot_mm.game_mm;
4203 laser = engine_snapshot_mm.laser;
4205 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4207 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4209 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4210 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4211 Box[x][y] = engine_snapshot_mm.Box[x][y];
4212 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4216 LX = engine_snapshot_mm.LX;
4217 LY = engine_snapshot_mm.LY;
4218 XS = engine_snapshot_mm.XS;
4219 YS = engine_snapshot_mm.YS;
4220 ELX = engine_snapshot_mm.ELX;
4221 ELY = engine_snapshot_mm.ELY;
4222 CT = engine_snapshot_mm.CT;
4223 Ct = engine_snapshot_mm.Ct;
4225 last_LX = engine_snapshot_mm.last_LX;
4226 last_LY = engine_snapshot_mm.last_LY;
4227 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4228 hold_x = engine_snapshot_mm.hold_x;
4229 hold_y = engine_snapshot_mm.hold_y;
4230 pacman_nr = engine_snapshot_mm.pacman_nr;
4232 rotate_delay = engine_snapshot_mm.rotate_delay;
4233 pacman_delay = engine_snapshot_mm.pacman_delay;
4234 energy_delay = engine_snapshot_mm.energy_delay;
4235 overload_delay = engine_snapshot_mm.overload_delay;
4237 RedrawPlayfield_MM();
4240 static int getAngleFromTouchDelta(int dx, int dy, int base)
4242 double pi = 3.141592653;
4243 double rad = atan2((double)-dy, (double)dx);
4244 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4245 double deg = rad2 * 180.0 / pi;
4247 return (int)(deg * base / 360.0 + 0.5) % base;
4250 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4252 // calculate start (source) position to be at the middle of the tile
4253 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4254 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4255 int dx = dst_mx - src_mx;
4256 int dy = dst_my - src_my;
4265 if (!IN_LEV_FIELD(x, y))
4268 element = Tile[x][y];
4270 if (!IS_MCDUFFIN(element) &&
4271 !IS_MIRROR(element) &&
4272 !IS_BEAMER(element) &&
4273 !IS_POLAR(element) &&
4274 !IS_POLAR_CROSS(element) &&
4275 !IS_DF_MIRROR(element))
4278 angle_old = get_element_angle(element);
4280 if (IS_MCDUFFIN(element))
4282 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4283 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4284 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4285 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4288 else if (IS_MIRROR(element) ||
4289 IS_DF_MIRROR(element))
4291 for (i = 0; i < laser.num_damages; i++)
4293 if (laser.damage[i].x == x &&
4294 laser.damage[i].y == y &&
4295 ObjHit(x, y, HIT_POS_CENTER))
4297 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4298 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4305 if (angle_new == -1)
4307 if (IS_MIRROR(element) ||
4308 IS_DF_MIRROR(element) ||
4312 if (IS_POLAR_CROSS(element))
4315 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4318 button = (angle_new == angle_old ? 0 :
4319 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4320 MB_LEFTBUTTON : MB_RIGHTBUTTON);