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 ||
1668 if (!IS_PACMAN(element))
1671 if (element == EL_PACMAN)
1674 if (element == EL_KETTLE || element == EL_CELL)
1676 if (game_mm.kettles_still_needed > 0)
1677 game_mm.kettles_still_needed--;
1679 game.snapshot.collected_item = TRUE;
1681 if (game_mm.kettles_still_needed == 0)
1685 DrawLaser(0, DL_LASER_ENABLED);
1688 else if (element == EL_KEY)
1692 else if (IS_PACMAN(element))
1694 DeletePacMan(ELX, ELY);
1697 RaiseScoreElement_MM(element);
1702 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1704 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1706 DrawLaser(0, DL_LASER_ENABLED);
1708 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1710 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1711 game_mm.lights_still_needed--;
1715 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1716 game_mm.lights_still_needed++;
1719 DrawField_MM(ELX, ELY);
1720 DrawLaser(0, DL_LASER_ENABLED);
1725 laser.stops_inside_element = TRUE;
1731 Debug("game:mm:HitElement", "(4): element == %d", element);
1734 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1735 laser.num_beamers < MAX_NUM_BEAMERS &&
1736 laser.beamer[BEAMER_NR(element)][1].num)
1738 int beamer_angle = get_element_angle(element);
1739 int beamer_nr = BEAMER_NR(element);
1743 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1746 laser.num_damages--;
1748 if (IS_FIBRE_OPTIC(element) ||
1749 laser.current_angle == get_opposite_angle(beamer_angle))
1753 LX = ELX * TILEX + 14;
1754 LY = ELY * TILEY + 14;
1756 AddLaserEdge(LX, LY);
1757 AddDamagedField(ELX, ELY);
1759 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1762 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1764 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1765 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1766 ELX = laser.beamer[beamer_nr][pos].x;
1767 ELY = laser.beamer[beamer_nr][pos].y;
1768 LX = ELX * TILEX + 14;
1769 LY = ELY * TILEY + 14;
1771 if (IS_BEAMER(element))
1773 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1774 XS = 2 * Step[laser.current_angle].x;
1775 YS = 2 * Step[laser.current_angle].y;
1778 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1780 AddLaserEdge(LX, LY);
1781 AddDamagedField(ELX, ELY);
1783 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1786 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1788 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1793 LX += step_size * XS;
1794 LY += step_size * YS;
1796 laser.num_beamers++;
1805 boolean HitOnlyAnEdge(int hit_mask)
1807 // check if the laser hit only the edge of an element and, if so, go on
1810 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1814 if ((hit_mask == HIT_MASK_TOPLEFT ||
1815 hit_mask == HIT_MASK_TOPRIGHT ||
1816 hit_mask == HIT_MASK_BOTTOMLEFT ||
1817 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1818 laser.current_angle % 4) // angle is not 90°
1822 if (hit_mask == HIT_MASK_TOPLEFT)
1827 else if (hit_mask == HIT_MASK_TOPRIGHT)
1832 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1837 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1843 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1849 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1856 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1862 boolean HitPolarizer(int element, int hit_mask)
1864 if (HitOnlyAnEdge(hit_mask))
1867 if (IS_DF_GRID(element))
1869 int grid_angle = get_element_angle(element);
1872 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1873 grid_angle, laser.current_angle);
1876 AddLaserEdge(LX, LY);
1877 AddDamagedField(ELX, ELY);
1880 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1882 if (laser.current_angle == grid_angle ||
1883 laser.current_angle == get_opposite_angle(grid_angle))
1885 // skip the whole element before continuing the scan
1891 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1893 if (LX/TILEX > ELX || LY/TILEY > ELY)
1895 /* skipping scan positions to the right and down skips one scan
1896 position too much, because this is only the top left scan position
1897 of totally four scan positions (plus one to the right, one to the
1898 bottom and one to the bottom right) */
1904 AddLaserEdge(LX, LY);
1910 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1912 LX / TILEX, LY / TILEY,
1913 LX % TILEX, LY % TILEY);
1918 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1920 return HitReflectingWalls(element, hit_mask);
1924 return HitAbsorbingWalls(element, hit_mask);
1927 else if (IS_GRID_STEEL(element))
1929 return HitReflectingWalls(element, hit_mask);
1931 else // IS_GRID_WOOD
1933 return HitAbsorbingWalls(element, hit_mask);
1939 boolean HitBlock(int element, int hit_mask)
1941 boolean check = FALSE;
1943 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1944 game_mm.num_keys == 0)
1947 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1950 int ex = ELX * TILEX + 14;
1951 int ey = ELY * TILEY + 14;
1955 for (i = 1; i < 32; i++)
1960 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1965 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1966 return HitAbsorbingWalls(element, hit_mask);
1970 AddLaserEdge(LX - XS, LY - YS);
1971 AddDamagedField(ELX, ELY);
1974 Box[ELX][ELY] = laser.num_edges;
1976 return HitReflectingWalls(element, hit_mask);
1979 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1981 int xs = XS / 2, ys = YS / 2;
1982 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1983 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1985 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1986 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1988 laser.overloaded = (element == EL_GATE_STONE);
1993 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1994 (hit_mask == HIT_MASK_TOP ||
1995 hit_mask == HIT_MASK_LEFT ||
1996 hit_mask == HIT_MASK_RIGHT ||
1997 hit_mask == HIT_MASK_BOTTOM))
1998 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1999 hit_mask == HIT_MASK_BOTTOM),
2000 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2001 hit_mask == HIT_MASK_RIGHT));
2002 AddLaserEdge(LX, LY);
2008 if (element == EL_GATE_STONE && Box[ELX][ELY])
2010 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2022 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2024 int xs = XS / 2, ys = YS / 2;
2025 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2026 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2028 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2029 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2031 laser.overloaded = (element == EL_BLOCK_STONE);
2036 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2037 (hit_mask == HIT_MASK_TOP ||
2038 hit_mask == HIT_MASK_LEFT ||
2039 hit_mask == HIT_MASK_RIGHT ||
2040 hit_mask == HIT_MASK_BOTTOM))
2041 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2042 hit_mask == HIT_MASK_BOTTOM),
2043 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2044 hit_mask == HIT_MASK_RIGHT));
2045 AddDamagedField(ELX, ELY);
2047 LX = ELX * TILEX + 14;
2048 LY = ELY * TILEY + 14;
2050 AddLaserEdge(LX, LY);
2052 laser.stops_inside_element = TRUE;
2060 boolean HitLaserSource(int element, int hit_mask)
2062 if (HitOnlyAnEdge(hit_mask))
2065 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2067 laser.overloaded = TRUE;
2072 boolean HitLaserDestination(int element, int hit_mask)
2074 if (HitOnlyAnEdge(hit_mask))
2077 if (element != EL_EXIT_OPEN &&
2078 !(IS_RECEIVER(element) &&
2079 game_mm.kettles_still_needed == 0 &&
2080 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2082 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2087 if (IS_RECEIVER(element) ||
2088 (IS_22_5_ANGLE(laser.current_angle) &&
2089 (ELX != (LX + 6 * XS) / TILEX ||
2090 ELY != (LY + 6 * YS) / TILEY ||
2099 LX = ELX * TILEX + 14;
2100 LY = ELY * TILEY + 14;
2102 laser.stops_inside_element = TRUE;
2105 AddLaserEdge(LX, LY);
2106 AddDamagedField(ELX, ELY);
2108 if (game_mm.lights_still_needed == 0)
2110 game_mm.level_solved = TRUE;
2112 SetTileCursorActive(FALSE);
2118 boolean HitReflectingWalls(int element, int hit_mask)
2120 // check if laser hits side of a wall with an angle that is not 90°
2121 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2122 hit_mask == HIT_MASK_LEFT ||
2123 hit_mask == HIT_MASK_RIGHT ||
2124 hit_mask == HIT_MASK_BOTTOM))
2126 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2131 if (!IS_DF_GRID(element))
2132 AddLaserEdge(LX, LY);
2134 // check if laser hits wall with an angle of 45°
2135 if (!IS_22_5_ANGLE(laser.current_angle))
2137 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2140 laser.current_angle = get_mirrored_angle(laser.current_angle,
2143 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2146 laser.current_angle = get_mirrored_angle(laser.current_angle,
2150 AddLaserEdge(LX, LY);
2152 XS = 2 * Step[laser.current_angle].x;
2153 YS = 2 * Step[laser.current_angle].y;
2157 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2159 laser.current_angle = get_mirrored_angle(laser.current_angle,
2164 if (!IS_DF_GRID(element))
2165 AddLaserEdge(LX, LY);
2170 if (!IS_DF_GRID(element))
2171 AddLaserEdge(LX, LY + YS / 2);
2174 if (!IS_DF_GRID(element))
2175 AddLaserEdge(LX, LY);
2178 YS = 2 * Step[laser.current_angle].y;
2182 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2184 laser.current_angle = get_mirrored_angle(laser.current_angle,
2189 if (!IS_DF_GRID(element))
2190 AddLaserEdge(LX, LY);
2195 if (!IS_DF_GRID(element))
2196 AddLaserEdge(LX + XS / 2, LY);
2199 if (!IS_DF_GRID(element))
2200 AddLaserEdge(LX, LY);
2203 XS = 2 * Step[laser.current_angle].x;
2209 // reflection at the edge of reflecting DF style wall
2210 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2212 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2213 hit_mask == HIT_MASK_TOPRIGHT) ||
2214 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2215 hit_mask == HIT_MASK_TOPLEFT) ||
2216 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2217 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2218 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2219 hit_mask == HIT_MASK_BOTTOMRIGHT))
2222 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2223 ANG_MIRROR_135 : ANG_MIRROR_45);
2225 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2227 AddDamagedField(ELX, ELY);
2228 AddLaserEdge(LX, LY);
2230 laser.current_angle = get_mirrored_angle(laser.current_angle,
2238 AddLaserEdge(LX, LY);
2244 // reflection inside an edge of reflecting DF style wall
2245 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2247 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2248 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2249 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2250 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2251 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2252 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2253 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2254 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2257 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2258 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2259 ANG_MIRROR_135 : ANG_MIRROR_45);
2261 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2264 AddDamagedField(ELX, ELY);
2267 AddLaserEdge(LX - XS, LY - YS);
2268 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2269 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2271 laser.current_angle = get_mirrored_angle(laser.current_angle,
2279 AddLaserEdge(LX, LY);
2285 // check if laser hits DF style wall with an angle of 90°
2286 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2288 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2289 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2290 (IS_VERT_ANGLE(laser.current_angle) &&
2291 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2293 // laser at last step touched nothing or the same side of the wall
2294 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2296 AddDamagedField(ELX, ELY);
2303 last_hit_mask = hit_mask;
2310 if (!HitOnlyAnEdge(hit_mask))
2312 laser.overloaded = TRUE;
2320 boolean HitAbsorbingWalls(int element, int hit_mask)
2322 if (HitOnlyAnEdge(hit_mask))
2326 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2328 AddLaserEdge(LX - XS, LY - YS);
2335 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2337 AddLaserEdge(LX - XS, LY - YS);
2343 if (IS_WALL_WOOD(element) ||
2344 IS_DF_WALL_WOOD(element) ||
2345 IS_GRID_WOOD(element) ||
2346 IS_GRID_WOOD_FIXED(element) ||
2347 IS_GRID_WOOD_AUTO(element) ||
2348 element == EL_FUSE_ON ||
2349 element == EL_BLOCK_WOOD ||
2350 element == EL_GATE_WOOD)
2352 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2357 if (IS_WALL_ICE(element))
2361 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2362 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2364 // check if laser hits wall with an angle of 90°
2365 if (IS_90_ANGLE(laser.current_angle))
2366 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2368 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2372 for (i = 0; i < 4; i++)
2374 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2375 mask = 15 - (8 >> i);
2376 else if (ABS(XS) == 4 &&
2378 (XS > 0) == (i % 2) &&
2379 (YS < 0) == (i / 2))
2380 mask = 3 + (i / 2) * 9;
2381 else if (ABS(YS) == 4 &&
2383 (XS < 0) == (i % 2) &&
2384 (YS > 0) == (i / 2))
2385 mask = 5 + (i % 2) * 5;
2389 laser.wall_mask = mask;
2391 else if (IS_WALL_AMOEBA(element))
2393 int elx = (LX - 2 * XS) / TILEX;
2394 int ely = (LY - 2 * YS) / TILEY;
2395 int element2 = Tile[elx][ely];
2398 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2400 laser.dest_element = EL_EMPTY;
2408 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2409 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2411 if (IS_90_ANGLE(laser.current_angle))
2412 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2414 laser.dest_element = element2 | EL_WALL_AMOEBA;
2416 laser.wall_mask = mask;
2422 static void OpenExit(int x, int y)
2426 if (!MovDelay[x][y]) // next animation frame
2427 MovDelay[x][y] = 4 * delay;
2429 if (MovDelay[x][y]) // wait some time before next frame
2434 phase = MovDelay[x][y] / delay;
2436 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2437 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2439 if (!MovDelay[x][y])
2441 Tile[x][y] = EL_EXIT_OPEN;
2447 static void OpenSurpriseBall(int x, int y)
2451 if (!MovDelay[x][y]) // next animation frame
2452 MovDelay[x][y] = 50 * delay;
2454 if (MovDelay[x][y]) // wait some time before next frame
2458 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2461 int graphic = el2gfx(Store[x][y]);
2463 int dx = RND(26), dy = RND(26);
2465 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2467 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2468 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2470 MarkTileDirty(x, y);
2473 if (!MovDelay[x][y])
2475 Tile[x][y] = Store[x][y];
2476 Store[x][y] = Store2[x][y] = 0;
2485 static void MeltIce(int x, int y)
2490 if (!MovDelay[x][y]) // next animation frame
2491 MovDelay[x][y] = frames * delay;
2493 if (MovDelay[x][y]) // wait some time before next frame
2496 int wall_mask = Store2[x][y];
2497 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2500 phase = frames - MovDelay[x][y] / delay - 1;
2502 if (!MovDelay[x][y])
2506 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2507 Store[x][y] = Store2[x][y] = 0;
2509 DrawWalls_MM(x, y, Tile[x][y]);
2511 if (Tile[x][y] == EL_WALL_ICE)
2512 Tile[x][y] = EL_EMPTY;
2514 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2515 if (laser.damage[i].is_mirror)
2519 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2521 DrawLaser(0, DL_LASER_DISABLED);
2525 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2527 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2529 laser.redraw = TRUE;
2534 static void GrowAmoeba(int x, int y)
2539 if (!MovDelay[x][y]) // next animation frame
2540 MovDelay[x][y] = frames * delay;
2542 if (MovDelay[x][y]) // wait some time before next frame
2545 int wall_mask = Store2[x][y];
2546 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2549 phase = MovDelay[x][y] / delay;
2551 if (!MovDelay[x][y])
2553 Tile[x][y] = real_element;
2554 Store[x][y] = Store2[x][y] = 0;
2556 DrawWalls_MM(x, y, Tile[x][y]);
2557 DrawLaser(0, DL_LASER_ENABLED);
2559 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2561 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2566 static void DrawFieldAnimated_MM(int x, int y)
2570 laser.redraw = TRUE;
2573 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2575 int element = Tile[x][y];
2576 int graphic = el2gfx(element);
2578 if (!getGraphicInfo_NewFrame(x, y, graphic))
2583 laser.redraw = TRUE;
2586 static void DrawFieldTwinkle(int x, int y)
2588 if (MovDelay[x][y] != 0) // wait some time before next frame
2594 if (MovDelay[x][y] != 0)
2596 int graphic = IMG_TWINKLE_WHITE;
2597 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2599 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2602 laser.redraw = TRUE;
2606 static void Explode_MM(int x, int y, int phase, int mode)
2608 int num_phase = 9, delay = 2;
2609 int last_phase = num_phase * delay;
2610 int half_phase = (num_phase / 2) * delay;
2612 laser.redraw = TRUE;
2614 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2616 int center_element = Tile[x][y];
2618 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2620 // put moving element to center field (and let it explode there)
2621 center_element = MovingOrBlocked2Element_MM(x, y);
2622 RemoveMovingField_MM(x, y);
2624 Tile[x][y] = center_element;
2627 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2628 Store[x][y] = center_element;
2630 Store[x][y] = EL_EMPTY;
2632 Store2[x][y] = mode;
2634 Tile[x][y] = EL_EXPLODING_OPAQUE;
2635 GfxElement[x][y] = center_element;
2637 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2639 ExplodePhase[x][y] = 1;
2645 GfxFrame[x][y] = 0; // restart explosion animation
2647 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2649 if (phase == half_phase)
2651 Tile[x][y] = EL_EXPLODING_TRANSP;
2653 if (x == ELX && y == ELY)
2657 if (phase == last_phase)
2659 if (Store[x][y] == EL_BOMB_ACTIVE)
2661 DrawLaser(0, DL_LASER_DISABLED);
2664 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2665 Store[x][y] = EL_EMPTY;
2667 GameOver_MM(GAME_OVER_DELAYED);
2669 laser.overloaded = FALSE;
2671 else if (IS_MCDUFFIN(Store[x][y]))
2673 Store[x][y] = EL_EMPTY;
2675 GameOver_MM(GAME_OVER_BOMB);
2678 Tile[x][y] = Store[x][y];
2679 Store[x][y] = Store2[x][y] = 0;
2680 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2685 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2687 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2688 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2690 DrawGraphicAnimation_MM(x, y, graphic, frame);
2692 MarkTileDirty(x, y);
2696 static void Bang_MM(int x, int y)
2698 int element = Tile[x][y];
2701 DrawLaser(0, DL_LASER_ENABLED);
2704 if (IS_PACMAN(element))
2705 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2706 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2707 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2708 else if (element == EL_KEY)
2709 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2711 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2713 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2716 void TurnRound(int x, int y)
2728 { 0, 0 }, { 0, 0 }, { 0, 0 },
2733 int left, right, back;
2737 { MV_DOWN, MV_UP, MV_RIGHT },
2738 { MV_UP, MV_DOWN, MV_LEFT },
2740 { MV_LEFT, MV_RIGHT, MV_DOWN },
2741 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2742 { MV_RIGHT, MV_LEFT, MV_UP }
2745 int element = Tile[x][y];
2746 int old_move_dir = MovDir[x][y];
2747 int right_dir = turn[old_move_dir].right;
2748 int back_dir = turn[old_move_dir].back;
2749 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2750 int right_x = x + right_dx, right_y = y + right_dy;
2752 if (element == EL_PACMAN)
2754 boolean can_turn_right = FALSE;
2756 if (IN_LEV_FIELD(right_x, right_y) &&
2757 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2758 can_turn_right = TRUE;
2761 MovDir[x][y] = right_dir;
2763 MovDir[x][y] = back_dir;
2769 static void StartMoving_MM(int x, int y)
2771 int element = Tile[x][y];
2776 if (CAN_MOVE(element))
2780 if (MovDelay[x][y]) // wait some time before next movement
2788 // now make next step
2790 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2792 if (element == EL_PACMAN &&
2793 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2794 !ObjHit(newx, newy, HIT_POS_CENTER))
2796 Store[newx][newy] = Tile[newx][newy];
2797 Tile[newx][newy] = EL_EMPTY;
2799 DrawField_MM(newx, newy);
2801 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2802 ObjHit(newx, newy, HIT_POS_CENTER))
2804 // object was running against a wall
2811 InitMovingField_MM(x, y, MovDir[x][y]);
2815 ContinueMoving_MM(x, y);
2818 static void ContinueMoving_MM(int x, int y)
2820 int element = Tile[x][y];
2821 int direction = MovDir[x][y];
2822 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2823 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2824 int horiz_move = (dx!=0);
2825 int newx = x + dx, newy = y + dy;
2826 int step = (horiz_move ? dx : dy) * TILEX / 8;
2828 MovPos[x][y] += step;
2830 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2832 Tile[x][y] = EL_EMPTY;
2833 Tile[newx][newy] = element;
2835 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2836 MovDelay[newx][newy] = 0;
2838 if (!CAN_MOVE(element))
2839 MovDir[newx][newy] = 0;
2842 DrawField_MM(newx, newy);
2844 Stop[newx][newy] = TRUE;
2846 if (element == EL_PACMAN)
2848 if (Store[newx][newy] == EL_BOMB)
2849 Bang_MM(newx, newy);
2851 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2852 (LX + 2 * XS) / TILEX == newx &&
2853 (LY + 2 * YS) / TILEY == newy)
2860 else // still moving on
2865 laser.redraw = TRUE;
2868 boolean ClickElement(int x, int y, int button)
2870 static DelayCounter click_delay = { CLICK_DELAY };
2871 static boolean new_button = TRUE;
2872 boolean element_clicked = FALSE;
2877 // initialize static variables
2878 click_delay.count = 0;
2879 click_delay.value = CLICK_DELAY;
2885 // do not rotate objects hit by the laser after the game was solved
2886 if (game_mm.level_solved && Hit[x][y])
2889 if (button == MB_RELEASED)
2892 click_delay.value = CLICK_DELAY;
2894 // release eventually hold auto-rotating mirror
2895 RotateMirror(x, y, MB_RELEASED);
2900 if (!FrameReached(&click_delay) && !new_button)
2903 if (button == MB_MIDDLEBUTTON) // middle button has no function
2906 if (!IN_LEV_FIELD(x, y))
2909 if (Tile[x][y] == EL_EMPTY)
2912 element = Tile[x][y];
2914 if (IS_MIRROR(element) ||
2915 IS_BEAMER(element) ||
2916 IS_POLAR(element) ||
2917 IS_POLAR_CROSS(element) ||
2918 IS_DF_MIRROR(element) ||
2919 IS_DF_MIRROR_AUTO(element))
2921 RotateMirror(x, y, button);
2923 element_clicked = TRUE;
2925 else if (IS_MCDUFFIN(element))
2927 if (!laser.fuse_off)
2929 DrawLaser(0, DL_LASER_DISABLED);
2936 element = get_rotated_element(element, BUTTON_ROTATION(button));
2937 laser.start_angle = get_element_angle(element);
2941 Tile[x][y] = element;
2948 if (!laser.fuse_off)
2951 element_clicked = TRUE;
2953 else if (element == EL_FUSE_ON && laser.fuse_off)
2955 if (x != laser.fuse_x || y != laser.fuse_y)
2958 laser.fuse_off = FALSE;
2959 laser.fuse_x = laser.fuse_y = -1;
2961 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2964 element_clicked = TRUE;
2966 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2968 laser.fuse_off = TRUE;
2971 laser.overloaded = FALSE;
2973 DrawLaser(0, DL_LASER_DISABLED);
2974 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2976 element_clicked = TRUE;
2978 else if (element == EL_LIGHTBALL)
2981 RaiseScoreElement_MM(element);
2982 DrawLaser(0, DL_LASER_ENABLED);
2984 element_clicked = TRUE;
2987 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2990 return element_clicked;
2993 void RotateMirror(int x, int y, int button)
2995 if (button == MB_RELEASED)
2997 // release eventually hold auto-rotating mirror
3004 if (IS_MIRROR(Tile[x][y]) ||
3005 IS_POLAR_CROSS(Tile[x][y]) ||
3006 IS_POLAR(Tile[x][y]) ||
3007 IS_BEAMER(Tile[x][y]) ||
3008 IS_DF_MIRROR(Tile[x][y]) ||
3009 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3010 IS_GRID_WOOD_AUTO(Tile[x][y]))
3012 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3014 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3016 if (button == MB_LEFTBUTTON)
3018 // left mouse button only for manual adjustment, no auto-rotating;
3019 // freeze mirror for until mouse button released
3023 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3025 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3029 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3031 int edge = Hit[x][y];
3037 DrawLaser(edge - 1, DL_LASER_DISABLED);
3041 else if (ObjHit(x, y, HIT_POS_CENTER))
3043 int edge = Hit[x][y];
3047 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3052 DrawLaser(edge - 1, DL_LASER_DISABLED);
3059 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3064 if ((IS_BEAMER(Tile[x][y]) ||
3065 IS_POLAR(Tile[x][y]) ||
3066 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3068 if (IS_BEAMER(Tile[x][y]))
3071 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3072 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3085 DrawLaser(0, DL_LASER_ENABLED);
3089 static void AutoRotateMirrors(void)
3093 if (!FrameReached(&rotate_delay))
3096 for (x = 0; x < lev_fieldx; x++)
3098 for (y = 0; y < lev_fieldy; y++)
3100 int element = Tile[x][y];
3102 // do not rotate objects hit by the laser after the game was solved
3103 if (game_mm.level_solved && Hit[x][y])
3106 if (IS_DF_MIRROR_AUTO(element) ||
3107 IS_GRID_WOOD_AUTO(element) ||
3108 IS_GRID_STEEL_AUTO(element) ||
3109 element == EL_REFRACTOR)
3110 RotateMirror(x, y, MB_RIGHTBUTTON);
3115 boolean ObjHit(int obx, int oby, int bits)
3122 if (bits & HIT_POS_CENTER)
3124 if (CheckLaserPixel(cSX + obx + 15,
3129 if (bits & HIT_POS_EDGE)
3131 for (i = 0; i < 4; i++)
3132 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3133 cSY + oby + 31 * (i / 2)))
3137 if (bits & HIT_POS_BETWEEN)
3139 for (i = 0; i < 4; i++)
3140 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3141 cSY + 4 + oby + 22 * (i / 2)))
3148 void DeletePacMan(int px, int py)
3154 if (game_mm.num_pacman <= 1)
3156 game_mm.num_pacman = 0;
3160 for (i = 0; i < game_mm.num_pacman; i++)
3161 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3164 game_mm.num_pacman--;
3166 for (j = i; j < game_mm.num_pacman; j++)
3168 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3169 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3170 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3174 void ColorCycling(void)
3176 static int CC, Cc = 0;
3178 static int color, old = 0xF00, new = 0x010, mult = 1;
3179 static unsigned short red, green, blue;
3181 if (color_status == STATIC_COLORS)
3186 if (CC < Cc || CC > Cc + 2)
3190 color = old + new * mult;
3196 if (ABS(mult) == 16)
3206 red = 0x0e00 * ((color & 0xF00) >> 8);
3207 green = 0x0e00 * ((color & 0x0F0) >> 4);
3208 blue = 0x0e00 * ((color & 0x00F));
3209 SetRGB(pen_magicolor[0], red, green, blue);
3211 red = 0x1111 * ((color & 0xF00) >> 8);
3212 green = 0x1111 * ((color & 0x0F0) >> 4);
3213 blue = 0x1111 * ((color & 0x00F));
3214 SetRGB(pen_magicolor[1], red, green, blue);
3218 static void GameActions_MM_Ext(void)
3225 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3228 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3230 element = Tile[x][y];
3232 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3233 StartMoving_MM(x, y);
3234 else if (IS_MOVING(x, y))
3235 ContinueMoving_MM(x, y);
3236 else if (IS_EXPLODING(element))
3237 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3238 else if (element == EL_EXIT_OPENING)
3240 else if (element == EL_GRAY_BALL_OPENING)
3241 OpenSurpriseBall(x, y);
3242 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3244 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3246 else if (IS_MIRROR(element) ||
3247 IS_MIRROR_FIXED(element) ||
3248 element == EL_PRISM)
3249 DrawFieldTwinkle(x, y);
3250 else if (element == EL_GRAY_BALL_OPENING ||
3251 element == EL_BOMB_ACTIVE ||
3252 element == EL_MINE_ACTIVE)
3253 DrawFieldAnimated_MM(x, y);
3254 else if (!IS_BLOCKED(x, y))
3255 DrawFieldAnimatedIfNeeded_MM(x, y);
3258 AutoRotateMirrors();
3261 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3263 // redraw after Explode_MM() ...
3265 DrawLaser(0, DL_LASER_ENABLED);
3266 laser.redraw = FALSE;
3271 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3275 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3277 DrawLaser(0, DL_LASER_DISABLED);
3282 // skip all following game actions if game is over
3283 if (game_mm.game_over)
3286 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3290 GameOver_MM(GAME_OVER_NO_ENERGY);
3295 if (FrameReached(&energy_delay))
3297 if (game_mm.energy_left > 0)
3298 game_mm.energy_left--;
3300 // when out of energy, wait another frame to play "out of time" sound
3303 element = laser.dest_element;
3306 if (element != Tile[ELX][ELY])
3308 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3309 element, Tile[ELX][ELY]);
3313 if (!laser.overloaded && laser.overload_value == 0 &&
3314 element != EL_BOMB &&
3315 element != EL_BOMB_ACTIVE &&
3316 element != EL_MINE &&
3317 element != EL_MINE_ACTIVE &&
3318 element != EL_BALL_GRAY &&
3319 element != EL_BLOCK_STONE &&
3320 element != EL_BLOCK_WOOD &&
3321 element != EL_FUSE_ON &&
3322 element != EL_FUEL_FULL &&
3323 !IS_WALL_ICE(element) &&
3324 !IS_WALL_AMOEBA(element))
3327 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3329 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3330 (!laser.overloaded && laser.overload_value > 0)) &&
3331 FrameReached(&overload_delay))
3333 if (laser.overloaded)
3334 laser.overload_value++;
3336 laser.overload_value--;
3338 if (game_mm.cheat_no_overload)
3340 laser.overloaded = FALSE;
3341 laser.overload_value = 0;
3344 game_mm.laser_overload_value = laser.overload_value;
3346 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3348 SetLaserColor(0xFF);
3350 DrawLaser(0, DL_LASER_ENABLED);
3353 if (!laser.overloaded)
3354 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3355 else if (setup.sound_loops)
3356 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3358 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3360 if (laser.overloaded)
3363 BlitBitmap(pix[PIX_DOOR], drawto,
3364 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3365 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3366 - laser.overload_value,
3367 OVERLOAD_XSIZE, laser.overload_value,
3368 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3369 - laser.overload_value);
3371 redraw_mask |= REDRAW_DOOR_1;
3376 BlitBitmap(pix[PIX_DOOR], drawto,
3377 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3378 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3379 DX_OVERLOAD, DY_OVERLOAD);
3381 redraw_mask |= REDRAW_DOOR_1;
3384 if (laser.overload_value == MAX_LASER_OVERLOAD)
3386 UpdateAndDisplayGameControlValues();
3390 GameOver_MM(GAME_OVER_OVERLOADED);
3401 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3403 if (game_mm.cheat_no_explosion)
3408 laser.dest_element = EL_EXPLODING_OPAQUE;
3413 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3415 laser.fuse_off = TRUE;
3419 DrawLaser(0, DL_LASER_DISABLED);
3420 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3423 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3425 if (!Store2[ELX][ELY]) // check if content element not yet determined
3427 int last_anim_random_frame = gfx.anim_random_frame;
3430 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3431 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3433 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3434 native_mm_level.ball_choice_mode, 0,
3435 game_mm.ball_choice_pos);
3437 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3438 gfx.anim_random_frame = last_anim_random_frame;
3440 game_mm.ball_choice_pos++;
3442 int new_element = native_mm_level.ball_content[element_pos];
3444 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3445 Store2[ELX][ELY] = TRUE;
3448 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3450 // !!! CHECK AGAIN: Laser on Polarizer !!!
3453 laser.dest_element_last = Tile[ELX][ELY];
3454 laser.dest_element_last_x = ELX;
3455 laser.dest_element_last_y = ELY;
3465 element = EL_MIRROR_START + RND(16);
3471 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3478 element = (rnd == 0 ? EL_FUSE_ON :
3479 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3480 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3481 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3482 EL_MIRROR_FIXED_START + rnd - 25);
3487 graphic = el2gfx(element);
3489 for (i = 0; i < 50; i++)
3495 BlitBitmap(pix[PIX_BACK], drawto,
3496 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3497 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3498 SX + ELX * TILEX + x,
3499 SY + ELY * TILEY + y);
3501 MarkTileDirty(ELX, ELY);
3504 DrawLaser(0, DL_LASER_ENABLED);
3506 Delay_WithScreenUpdates(50);
3509 Tile[ELX][ELY] = element;
3510 DrawField_MM(ELX, ELY);
3513 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3516 // above stuff: GRAY BALL -> PRISM !!!
3518 LX = ELX * TILEX + 14;
3519 LY = ELY * TILEY + 14;
3520 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3527 laser.num_edges -= 2;
3528 laser.num_damages--;
3532 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3533 if (laser.damage[i].is_mirror)
3537 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3539 DrawLaser(0, DL_LASER_DISABLED);
3541 DrawLaser(0, DL_LASER_DISABLED);
3550 if (IS_WALL_ICE(element) && CT > 50)
3552 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3555 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3556 Store[ELX][ELY] = EL_WALL_ICE;
3557 Store2[ELX][ELY] = laser.wall_mask;
3559 laser.dest_element = Tile[ELX][ELY];
3564 for (i = 0; i < 5; i++)
3570 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3574 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3576 Delay_WithScreenUpdates(100);
3579 if (Tile[ELX][ELY] == EL_WALL_ICE)
3580 Tile[ELX][ELY] = EL_EMPTY;
3584 LX = laser.edge[laser.num_edges].x - cSX2;
3585 LY = laser.edge[laser.num_edges].y - cSY2;
3588 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3589 if (laser.damage[i].is_mirror)
3593 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3595 DrawLaser(0, DL_LASER_DISABLED);
3602 if (IS_WALL_AMOEBA(element) && CT > 60)
3604 int k1, k2, k3, dx, dy, de, dm;
3605 int element2 = Tile[ELX][ELY];
3607 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3610 for (i = laser.num_damages - 1; i >= 0; i--)
3611 if (laser.damage[i].is_mirror)
3614 r = laser.num_edges;
3615 d = laser.num_damages;
3622 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3625 DrawLaser(0, DL_LASER_ENABLED);
3628 x = laser.damage[k1].x;
3629 y = laser.damage[k1].y;
3634 for (i = 0; i < 4; i++)
3636 if (laser.wall_mask & (1 << i))
3638 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3639 cSY + ELY * TILEY + 31 * (i / 2)))
3642 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3643 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3650 for (i = 0; i < 4; i++)
3652 if (laser.wall_mask & (1 << i))
3654 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3655 cSY + ELY * TILEY + 31 * (i / 2)))
3662 if (laser.num_beamers > 0 ||
3663 k1 < 1 || k2 < 4 || k3 < 4 ||
3664 CheckLaserPixel(cSX + ELX * TILEX + 14,
3665 cSY + ELY * TILEY + 14))
3667 laser.num_edges = r;
3668 laser.num_damages = d;
3670 DrawLaser(0, DL_LASER_DISABLED);
3673 Tile[ELX][ELY] = element | laser.wall_mask;
3677 de = Tile[ELX][ELY];
3678 dm = laser.wall_mask;
3682 int x = ELX, y = ELY;
3683 int wall_mask = laser.wall_mask;
3686 DrawLaser(0, DL_LASER_ENABLED);
3688 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3690 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3691 Store[x][y] = EL_WALL_AMOEBA;
3692 Store2[x][y] = wall_mask;
3698 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3700 DrawLaser(0, DL_LASER_ENABLED);
3702 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3704 for (i = 4; i >= 0; i--)
3706 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3709 Delay_WithScreenUpdates(20);
3712 DrawLaser(0, DL_LASER_ENABLED);
3717 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3718 laser.stops_inside_element && CT > native_mm_level.time_block)
3723 if (ABS(XS) > ABS(YS))
3730 for (i = 0; i < 4; i++)
3737 x = ELX + Step[k * 4].x;
3738 y = ELY + Step[k * 4].y;
3740 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3743 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3751 laser.overloaded = (element == EL_BLOCK_STONE);
3756 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3759 Tile[x][y] = element;
3761 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3764 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3766 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3767 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3775 if (element == EL_FUEL_FULL && CT > 10)
3777 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3778 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3780 for (i = start; i <= num_init_game_frames; i++)
3782 if (i == num_init_game_frames)
3783 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3784 else if (setup.sound_loops)
3785 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3787 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3789 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3791 UpdateAndDisplayGameControlValues();
3796 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3798 DrawField_MM(ELX, ELY);
3800 DrawLaser(0, DL_LASER_ENABLED);
3808 void GameActions_MM(struct MouseActionInfo action)
3810 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3811 boolean button_released = (action.button == MB_RELEASED);
3813 GameActions_MM_Ext();
3815 CheckSingleStepMode_MM(element_clicked, button_released);
3818 void MovePacMen(void)
3820 int mx, my, ox, oy, nx, ny;
3824 if (++pacman_nr >= game_mm.num_pacman)
3827 game_mm.pacman[pacman_nr].dir--;
3829 for (l = 1; l < 5; l++)
3831 game_mm.pacman[pacman_nr].dir++;
3833 if (game_mm.pacman[pacman_nr].dir > 4)
3834 game_mm.pacman[pacman_nr].dir = 1;
3836 if (game_mm.pacman[pacman_nr].dir % 2)
3839 my = game_mm.pacman[pacman_nr].dir - 2;
3844 mx = 3 - game_mm.pacman[pacman_nr].dir;
3847 ox = game_mm.pacman[pacman_nr].x;
3848 oy = game_mm.pacman[pacman_nr].y;
3851 element = Tile[nx][ny];
3853 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3856 if (!IS_EATABLE4PACMAN(element))
3859 if (ObjHit(nx, ny, HIT_POS_CENTER))
3862 Tile[ox][oy] = EL_EMPTY;
3864 EL_PACMAN_RIGHT - 1 +
3865 (game_mm.pacman[pacman_nr].dir - 1 +
3866 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3868 game_mm.pacman[pacman_nr].x = nx;
3869 game_mm.pacman[pacman_nr].y = ny;
3871 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3873 if (element != EL_EMPTY)
3875 int graphic = el2gfx(Tile[nx][ny]);
3880 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3883 ox = cSX + ox * TILEX;
3884 oy = cSY + oy * TILEY;
3886 for (i = 1; i < 33; i += 2)
3887 BlitBitmap(bitmap, window,
3888 src_x, src_y, TILEX, TILEY,
3889 ox + i * mx, oy + i * my);
3890 Ct = Ct + FrameCounter - CT;
3893 DrawField_MM(nx, ny);
3896 if (!laser.fuse_off)
3898 DrawLaser(0, DL_LASER_ENABLED);
3900 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3902 AddDamagedField(nx, ny);
3904 laser.damage[laser.num_damages - 1].edge = 0;
3908 if (element == EL_BOMB)
3909 DeletePacMan(nx, ny);
3911 if (IS_WALL_AMOEBA(element) &&
3912 (LX + 2 * XS) / TILEX == nx &&
3913 (LY + 2 * YS) / TILEY == ny)
3923 static void InitMovingField_MM(int x, int y, int direction)
3925 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3926 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3928 MovDir[x][y] = direction;
3929 MovDir[newx][newy] = direction;
3931 if (Tile[newx][newy] == EL_EMPTY)
3932 Tile[newx][newy] = EL_BLOCKED;
3935 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3937 int direction = MovDir[x][y];
3938 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3939 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3945 static void Blocked2Moving_MM(int x, int y,
3946 int *comes_from_x, int *comes_from_y)
3948 int oldx = x, oldy = y;
3949 int direction = MovDir[x][y];
3951 if (direction == MV_LEFT)
3953 else if (direction == MV_RIGHT)
3955 else if (direction == MV_UP)
3957 else if (direction == MV_DOWN)
3960 *comes_from_x = oldx;
3961 *comes_from_y = oldy;
3964 static int MovingOrBlocked2Element_MM(int x, int y)
3966 int element = Tile[x][y];
3968 if (element == EL_BLOCKED)
3972 Blocked2Moving_MM(x, y, &oldx, &oldy);
3974 return Tile[oldx][oldy];
3981 static void RemoveField(int x, int y)
3983 Tile[x][y] = EL_EMPTY;
3990 static void RemoveMovingField_MM(int x, int y)
3992 int oldx = x, oldy = y, newx = x, newy = y;
3994 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3997 if (IS_MOVING(x, y))
3999 Moving2Blocked_MM(x, y, &newx, &newy);
4000 if (Tile[newx][newy] != EL_BLOCKED)
4003 else if (Tile[x][y] == EL_BLOCKED)
4005 Blocked2Moving_MM(x, y, &oldx, &oldy);
4006 if (!IS_MOVING(oldx, oldy))
4010 Tile[oldx][oldy] = EL_EMPTY;
4011 Tile[newx][newy] = EL_EMPTY;
4012 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4013 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4015 DrawLevelField_MM(oldx, oldy);
4016 DrawLevelField_MM(newx, newy);
4019 void PlaySoundLevel(int x, int y, int sound_nr)
4021 int sx = SCREENX(x), sy = SCREENY(y);
4023 int silence_distance = 8;
4025 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4026 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4029 if (!IN_LEV_FIELD(x, y) ||
4030 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4031 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4034 volume = SOUND_MAX_VOLUME;
4037 stereo = (sx - SCR_FIELDX/2) * 12;
4039 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4040 if (stereo > SOUND_MAX_RIGHT)
4041 stereo = SOUND_MAX_RIGHT;
4042 if (stereo < SOUND_MAX_LEFT)
4043 stereo = SOUND_MAX_LEFT;
4046 if (!IN_SCR_FIELD(sx, sy))
4048 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4049 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4051 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4054 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4057 static void RaiseScore_MM(int value)
4059 game_mm.score += value;
4062 void RaiseScoreElement_MM(int element)
4067 case EL_PACMAN_RIGHT:
4069 case EL_PACMAN_LEFT:
4070 case EL_PACMAN_DOWN:
4071 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4075 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4080 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4084 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4093 // ----------------------------------------------------------------------------
4094 // Mirror Magic game engine snapshot handling functions
4095 // ----------------------------------------------------------------------------
4097 void SaveEngineSnapshotValues_MM(void)
4101 engine_snapshot_mm.game_mm = game_mm;
4102 engine_snapshot_mm.laser = laser;
4104 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4106 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4108 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4109 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4110 engine_snapshot_mm.Box[x][y] = Box[x][y];
4111 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4115 engine_snapshot_mm.LX = LX;
4116 engine_snapshot_mm.LY = LY;
4117 engine_snapshot_mm.XS = XS;
4118 engine_snapshot_mm.YS = YS;
4119 engine_snapshot_mm.ELX = ELX;
4120 engine_snapshot_mm.ELY = ELY;
4121 engine_snapshot_mm.CT = CT;
4122 engine_snapshot_mm.Ct = Ct;
4124 engine_snapshot_mm.last_LX = last_LX;
4125 engine_snapshot_mm.last_LY = last_LY;
4126 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4127 engine_snapshot_mm.hold_x = hold_x;
4128 engine_snapshot_mm.hold_y = hold_y;
4129 engine_snapshot_mm.pacman_nr = pacman_nr;
4131 engine_snapshot_mm.rotate_delay = rotate_delay;
4132 engine_snapshot_mm.pacman_delay = pacman_delay;
4133 engine_snapshot_mm.energy_delay = energy_delay;
4134 engine_snapshot_mm.overload_delay = overload_delay;
4137 void LoadEngineSnapshotValues_MM(void)
4141 // stored engine snapshot buffers already restored at this point
4143 game_mm = engine_snapshot_mm.game_mm;
4144 laser = engine_snapshot_mm.laser;
4146 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4148 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4150 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4151 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4152 Box[x][y] = engine_snapshot_mm.Box[x][y];
4153 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4157 LX = engine_snapshot_mm.LX;
4158 LY = engine_snapshot_mm.LY;
4159 XS = engine_snapshot_mm.XS;
4160 YS = engine_snapshot_mm.YS;
4161 ELX = engine_snapshot_mm.ELX;
4162 ELY = engine_snapshot_mm.ELY;
4163 CT = engine_snapshot_mm.CT;
4164 Ct = engine_snapshot_mm.Ct;
4166 last_LX = engine_snapshot_mm.last_LX;
4167 last_LY = engine_snapshot_mm.last_LY;
4168 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4169 hold_x = engine_snapshot_mm.hold_x;
4170 hold_y = engine_snapshot_mm.hold_y;
4171 pacman_nr = engine_snapshot_mm.pacman_nr;
4173 rotate_delay = engine_snapshot_mm.rotate_delay;
4174 pacman_delay = engine_snapshot_mm.pacman_delay;
4175 energy_delay = engine_snapshot_mm.energy_delay;
4176 overload_delay = engine_snapshot_mm.overload_delay;
4178 RedrawPlayfield_MM();
4181 static int getAngleFromTouchDelta(int dx, int dy, int base)
4183 double pi = 3.141592653;
4184 double rad = atan2((double)-dy, (double)dx);
4185 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4186 double deg = rad2 * 180.0 / pi;
4188 return (int)(deg * base / 360.0 + 0.5) % base;
4191 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4193 // calculate start (source) position to be at the middle of the tile
4194 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4195 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4196 int dx = dst_mx - src_mx;
4197 int dy = dst_my - src_my;
4206 if (!IN_LEV_FIELD(x, y))
4209 element = Tile[x][y];
4211 if (!IS_MCDUFFIN(element) &&
4212 !IS_MIRROR(element) &&
4213 !IS_BEAMER(element) &&
4214 !IS_POLAR(element) &&
4215 !IS_POLAR_CROSS(element) &&
4216 !IS_DF_MIRROR(element))
4219 angle_old = get_element_angle(element);
4221 if (IS_MCDUFFIN(element))
4223 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4224 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4225 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4226 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4229 else if (IS_MIRROR(element) ||
4230 IS_DF_MIRROR(element))
4232 for (i = 0; i < laser.num_damages; i++)
4234 if (laser.damage[i].x == x &&
4235 laser.damage[i].y == y &&
4236 ObjHit(x, y, HIT_POS_CENTER))
4238 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4239 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4246 if (angle_new == -1)
4248 if (IS_MIRROR(element) ||
4249 IS_DF_MIRROR(element) ||
4253 if (IS_POLAR_CROSS(element))
4256 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4259 button = (angle_new == angle_old ? 0 :
4260 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4261 MB_LEFTBUTTON : MB_RIGHTBUTTON);