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 Store[x][y] = center_element;
2672 Store2[x][y] = mode;
2674 Tile[x][y] = EL_EXPLODING_OPAQUE;
2676 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2677 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2680 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2682 ExplodePhase[x][y] = 1;
2688 GfxFrame[x][y] = 0; // restart explosion animation
2690 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2692 if (phase == half_phase)
2694 Tile[x][y] = EL_EXPLODING_TRANSP;
2696 if (x == ELX && y == ELY)
2700 if (phase == last_phase)
2702 if (Store[x][y] == EL_BOMB_ACTIVE)
2704 DrawLaser(0, DL_LASER_DISABLED);
2707 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2709 GameOver_MM(GAME_OVER_DELAYED);
2711 laser.overloaded = FALSE;
2713 else if (IS_MCDUFFIN(Store[x][y]))
2715 GameOver_MM(GAME_OVER_BOMB);
2718 Tile[x][y] = EL_EMPTY;
2720 Store[x][y] = Store2[x][y] = 0;
2721 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2726 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2728 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2729 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2731 DrawGraphicAnimation_MM(x, y, graphic, frame);
2733 MarkTileDirty(x, y);
2737 static void Bang_MM(int x, int y)
2739 int element = Tile[x][y];
2742 DrawLaser(0, DL_LASER_ENABLED);
2745 if (IS_PACMAN(element))
2746 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2747 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2748 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2749 else if (element == EL_KEY)
2750 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2752 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2754 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2757 void TurnRound(int x, int y)
2769 { 0, 0 }, { 0, 0 }, { 0, 0 },
2774 int left, right, back;
2778 { MV_DOWN, MV_UP, MV_RIGHT },
2779 { MV_UP, MV_DOWN, MV_LEFT },
2781 { MV_LEFT, MV_RIGHT, MV_DOWN },
2782 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2783 { MV_RIGHT, MV_LEFT, MV_UP }
2786 int element = Tile[x][y];
2787 int old_move_dir = MovDir[x][y];
2788 int right_dir = turn[old_move_dir].right;
2789 int back_dir = turn[old_move_dir].back;
2790 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2791 int right_x = x + right_dx, right_y = y + right_dy;
2793 if (element == EL_PACMAN)
2795 boolean can_turn_right = FALSE;
2797 if (IN_LEV_FIELD(right_x, right_y) &&
2798 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2799 can_turn_right = TRUE;
2802 MovDir[x][y] = right_dir;
2804 MovDir[x][y] = back_dir;
2810 static void StartMoving_MM(int x, int y)
2812 int element = Tile[x][y];
2817 if (CAN_MOVE(element))
2821 if (MovDelay[x][y]) // wait some time before next movement
2829 // now make next step
2831 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2833 if (element == EL_PACMAN &&
2834 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2835 !ObjHit(newx, newy, HIT_POS_CENTER))
2837 Store[newx][newy] = Tile[newx][newy];
2838 Tile[newx][newy] = EL_EMPTY;
2840 DrawField_MM(newx, newy);
2842 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2843 ObjHit(newx, newy, HIT_POS_CENTER))
2845 // object was running against a wall
2852 InitMovingField_MM(x, y, MovDir[x][y]);
2856 ContinueMoving_MM(x, y);
2859 static void ContinueMoving_MM(int x, int y)
2861 int element = Tile[x][y];
2862 int direction = MovDir[x][y];
2863 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2864 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2865 int horiz_move = (dx!=0);
2866 int newx = x + dx, newy = y + dy;
2867 int step = (horiz_move ? dx : dy) * TILEX / 8;
2869 MovPos[x][y] += step;
2871 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2873 Tile[x][y] = EL_EMPTY;
2874 Tile[newx][newy] = element;
2876 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2877 MovDelay[newx][newy] = 0;
2879 if (!CAN_MOVE(element))
2880 MovDir[newx][newy] = 0;
2883 DrawField_MM(newx, newy);
2885 Stop[newx][newy] = TRUE;
2887 if (element == EL_PACMAN)
2889 if (Store[newx][newy] == EL_BOMB)
2890 Bang_MM(newx, newy);
2892 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2893 (LX + 2 * XS) / TILEX == newx &&
2894 (LY + 2 * YS) / TILEY == newy)
2901 else // still moving on
2906 laser.redraw = TRUE;
2909 boolean ClickElement(int x, int y, int button)
2911 static DelayCounter click_delay = { CLICK_DELAY };
2912 static boolean new_button = TRUE;
2913 boolean element_clicked = FALSE;
2918 // initialize static variables
2919 click_delay.count = 0;
2920 click_delay.value = CLICK_DELAY;
2926 // do not rotate objects hit by the laser after the game was solved
2927 if (game_mm.level_solved && Hit[x][y])
2930 if (button == MB_RELEASED)
2933 click_delay.value = CLICK_DELAY;
2935 // release eventually hold auto-rotating mirror
2936 RotateMirror(x, y, MB_RELEASED);
2941 if (!FrameReached(&click_delay) && !new_button)
2944 if (button == MB_MIDDLEBUTTON) // middle button has no function
2947 if (!IN_LEV_FIELD(x, y))
2950 if (Tile[x][y] == EL_EMPTY)
2953 element = Tile[x][y];
2955 if (IS_MIRROR(element) ||
2956 IS_BEAMER(element) ||
2957 IS_POLAR(element) ||
2958 IS_POLAR_CROSS(element) ||
2959 IS_DF_MIRROR(element) ||
2960 IS_DF_MIRROR_AUTO(element))
2962 RotateMirror(x, y, button);
2964 element_clicked = TRUE;
2966 else if (IS_MCDUFFIN(element))
2968 if (!laser.fuse_off)
2970 DrawLaser(0, DL_LASER_DISABLED);
2977 element = get_rotated_element(element, BUTTON_ROTATION(button));
2978 laser.start_angle = get_element_angle(element);
2982 Tile[x][y] = element;
2989 if (!laser.fuse_off)
2992 element_clicked = TRUE;
2994 else if (element == EL_FUSE_ON && laser.fuse_off)
2996 if (x != laser.fuse_x || y != laser.fuse_y)
2999 laser.fuse_off = FALSE;
3000 laser.fuse_x = laser.fuse_y = -1;
3002 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3005 element_clicked = TRUE;
3007 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3009 laser.fuse_off = TRUE;
3012 laser.overloaded = FALSE;
3014 DrawLaser(0, DL_LASER_DISABLED);
3015 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3017 element_clicked = TRUE;
3019 else if (element == EL_LIGHTBALL)
3022 RaiseScoreElement_MM(element);
3023 DrawLaser(0, DL_LASER_ENABLED);
3025 element_clicked = TRUE;
3027 else if (IS_ENVELOPE(element))
3029 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3031 element_clicked = TRUE;
3034 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3037 return element_clicked;
3040 void RotateMirror(int x, int y, int button)
3042 if (button == MB_RELEASED)
3044 // release eventually hold auto-rotating mirror
3051 if (IS_MIRROR(Tile[x][y]) ||
3052 IS_POLAR_CROSS(Tile[x][y]) ||
3053 IS_POLAR(Tile[x][y]) ||
3054 IS_BEAMER(Tile[x][y]) ||
3055 IS_DF_MIRROR(Tile[x][y]) ||
3056 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3057 IS_GRID_WOOD_AUTO(Tile[x][y]))
3059 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3061 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3063 if (button == MB_LEFTBUTTON)
3065 // left mouse button only for manual adjustment, no auto-rotating;
3066 // freeze mirror for until mouse button released
3070 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3072 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3076 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3078 int edge = Hit[x][y];
3084 DrawLaser(edge - 1, DL_LASER_DISABLED);
3088 else if (ObjHit(x, y, HIT_POS_CENTER))
3090 int edge = Hit[x][y];
3094 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3099 DrawLaser(edge - 1, DL_LASER_DISABLED);
3106 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3111 if ((IS_BEAMER(Tile[x][y]) ||
3112 IS_POLAR(Tile[x][y]) ||
3113 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3115 if (IS_BEAMER(Tile[x][y]))
3118 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3119 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3132 DrawLaser(0, DL_LASER_ENABLED);
3136 static void AutoRotateMirrors(void)
3140 if (!FrameReached(&rotate_delay))
3143 for (x = 0; x < lev_fieldx; x++)
3145 for (y = 0; y < lev_fieldy; y++)
3147 int element = Tile[x][y];
3149 // do not rotate objects hit by the laser after the game was solved
3150 if (game_mm.level_solved && Hit[x][y])
3153 if (IS_DF_MIRROR_AUTO(element) ||
3154 IS_GRID_WOOD_AUTO(element) ||
3155 IS_GRID_STEEL_AUTO(element) ||
3156 element == EL_REFRACTOR)
3157 RotateMirror(x, y, MB_RIGHTBUTTON);
3162 boolean ObjHit(int obx, int oby, int bits)
3169 if (bits & HIT_POS_CENTER)
3171 if (CheckLaserPixel(cSX + obx + 15,
3176 if (bits & HIT_POS_EDGE)
3178 for (i = 0; i < 4; i++)
3179 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3180 cSY + oby + 31 * (i / 2)))
3184 if (bits & HIT_POS_BETWEEN)
3186 for (i = 0; i < 4; i++)
3187 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3188 cSY + 4 + oby + 22 * (i / 2)))
3195 void DeletePacMan(int px, int py)
3201 if (game_mm.num_pacman <= 1)
3203 game_mm.num_pacman = 0;
3207 for (i = 0; i < game_mm.num_pacman; i++)
3208 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3211 game_mm.num_pacman--;
3213 for (j = i; j < game_mm.num_pacman; j++)
3215 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3216 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3217 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3221 void ColorCycling(void)
3223 static int CC, Cc = 0;
3225 static int color, old = 0xF00, new = 0x010, mult = 1;
3226 static unsigned short red, green, blue;
3228 if (color_status == STATIC_COLORS)
3233 if (CC < Cc || CC > Cc + 2)
3237 color = old + new * mult;
3243 if (ABS(mult) == 16)
3253 red = 0x0e00 * ((color & 0xF00) >> 8);
3254 green = 0x0e00 * ((color & 0x0F0) >> 4);
3255 blue = 0x0e00 * ((color & 0x00F));
3256 SetRGB(pen_magicolor[0], red, green, blue);
3258 red = 0x1111 * ((color & 0xF00) >> 8);
3259 green = 0x1111 * ((color & 0x0F0) >> 4);
3260 blue = 0x1111 * ((color & 0x00F));
3261 SetRGB(pen_magicolor[1], red, green, blue);
3265 static void GameActions_MM_Ext(void)
3272 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3275 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3277 element = Tile[x][y];
3279 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3280 StartMoving_MM(x, y);
3281 else if (IS_MOVING(x, y))
3282 ContinueMoving_MM(x, y);
3283 else if (IS_EXPLODING(element))
3284 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3285 else if (element == EL_EXIT_OPENING)
3287 else if (element == EL_GRAY_BALL_OPENING)
3288 OpenSurpriseBall(x, y);
3289 else if (IS_ENVELOPE_OPENING(element))
3291 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3293 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3295 else if (IS_MIRROR(element) ||
3296 IS_MIRROR_FIXED(element) ||
3297 element == EL_PRISM)
3298 DrawFieldTwinkle(x, y);
3299 else if (element == EL_GRAY_BALL_OPENING ||
3300 element == EL_BOMB_ACTIVE ||
3301 element == EL_MINE_ACTIVE)
3302 DrawFieldAnimated_MM(x, y);
3303 else if (!IS_BLOCKED(x, y))
3304 DrawFieldAnimatedIfNeeded_MM(x, y);
3307 AutoRotateMirrors();
3310 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3312 // redraw after Explode_MM() ...
3314 DrawLaser(0, DL_LASER_ENABLED);
3315 laser.redraw = FALSE;
3320 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3324 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3326 DrawLaser(0, DL_LASER_DISABLED);
3331 // skip all following game actions if game is over
3332 if (game_mm.game_over)
3335 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3339 GameOver_MM(GAME_OVER_NO_ENERGY);
3344 if (FrameReached(&energy_delay))
3346 if (game_mm.energy_left > 0)
3347 game_mm.energy_left--;
3349 // when out of energy, wait another frame to play "out of time" sound
3352 element = laser.dest_element;
3355 if (element != Tile[ELX][ELY])
3357 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3358 element, Tile[ELX][ELY]);
3362 if (!laser.overloaded && laser.overload_value == 0 &&
3363 element != EL_BOMB &&
3364 element != EL_BOMB_ACTIVE &&
3365 element != EL_MINE &&
3366 element != EL_MINE_ACTIVE &&
3367 element != EL_BALL_GRAY &&
3368 element != EL_BLOCK_STONE &&
3369 element != EL_BLOCK_WOOD &&
3370 element != EL_FUSE_ON &&
3371 element != EL_FUEL_FULL &&
3372 !IS_WALL_ICE(element) &&
3373 !IS_WALL_AMOEBA(element))
3376 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3378 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3379 (!laser.overloaded && laser.overload_value > 0)) &&
3380 FrameReached(&overload_delay))
3382 if (laser.overloaded)
3383 laser.overload_value++;
3385 laser.overload_value--;
3387 if (game_mm.cheat_no_overload)
3389 laser.overloaded = FALSE;
3390 laser.overload_value = 0;
3393 game_mm.laser_overload_value = laser.overload_value;
3395 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3397 SetLaserColor(0xFF);
3399 DrawLaser(0, DL_LASER_ENABLED);
3402 if (!laser.overloaded)
3403 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3404 else if (setup.sound_loops)
3405 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3407 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3409 if (laser.overloaded)
3412 BlitBitmap(pix[PIX_DOOR], drawto,
3413 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3414 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3415 - laser.overload_value,
3416 OVERLOAD_XSIZE, laser.overload_value,
3417 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3418 - laser.overload_value);
3420 redraw_mask |= REDRAW_DOOR_1;
3425 BlitBitmap(pix[PIX_DOOR], drawto,
3426 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3427 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3428 DX_OVERLOAD, DY_OVERLOAD);
3430 redraw_mask |= REDRAW_DOOR_1;
3433 if (laser.overload_value == MAX_LASER_OVERLOAD)
3435 UpdateAndDisplayGameControlValues();
3439 GameOver_MM(GAME_OVER_OVERLOADED);
3450 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3452 if (game_mm.cheat_no_explosion)
3457 laser.dest_element = EL_EXPLODING_OPAQUE;
3462 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3464 laser.fuse_off = TRUE;
3468 DrawLaser(0, DL_LASER_DISABLED);
3469 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3472 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3474 if (!Store2[ELX][ELY]) // check if content element not yet determined
3476 int last_anim_random_frame = gfx.anim_random_frame;
3479 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3480 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3482 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3483 native_mm_level.ball_choice_mode, 0,
3484 game_mm.ball_choice_pos);
3486 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3487 gfx.anim_random_frame = last_anim_random_frame;
3489 game_mm.ball_choice_pos++;
3491 int new_element = native_mm_level.ball_content[element_pos];
3493 // randomly rotate newly created game element, if needed
3494 if (native_mm_level.rotate_ball_content)
3495 new_element = get_rotated_element(new_element, RND(16));
3497 Store[ELX][ELY] = new_element;
3498 Store2[ELX][ELY] = TRUE;
3501 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3503 // !!! CHECK AGAIN: Laser on Polarizer !!!
3506 laser.dest_element_last = Tile[ELX][ELY];
3507 laser.dest_element_last_x = ELX;
3508 laser.dest_element_last_y = ELY;
3518 element = EL_MIRROR_START + RND(16);
3524 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3531 element = (rnd == 0 ? EL_FUSE_ON :
3532 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3533 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3534 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3535 EL_MIRROR_FIXED_START + rnd - 25);
3540 graphic = el2gfx(element);
3542 for (i = 0; i < 50; i++)
3548 BlitBitmap(pix[PIX_BACK], drawto,
3549 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3550 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3551 SX + ELX * TILEX + x,
3552 SY + ELY * TILEY + y);
3554 MarkTileDirty(ELX, ELY);
3557 DrawLaser(0, DL_LASER_ENABLED);
3559 Delay_WithScreenUpdates(50);
3562 Tile[ELX][ELY] = element;
3563 DrawField_MM(ELX, ELY);
3566 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3569 // above stuff: GRAY BALL -> PRISM !!!
3571 LX = ELX * TILEX + 14;
3572 LY = ELY * TILEY + 14;
3573 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3580 laser.num_edges -= 2;
3581 laser.num_damages--;
3585 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3586 if (laser.damage[i].is_mirror)
3590 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3592 DrawLaser(0, DL_LASER_DISABLED);
3594 DrawLaser(0, DL_LASER_DISABLED);
3603 if (IS_WALL_ICE(element) && CT > 50)
3605 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3608 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3609 Store[ELX][ELY] = EL_WALL_ICE;
3610 Store2[ELX][ELY] = laser.wall_mask;
3612 laser.dest_element = Tile[ELX][ELY];
3617 for (i = 0; i < 5; i++)
3623 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3627 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3629 Delay_WithScreenUpdates(100);
3632 if (Tile[ELX][ELY] == EL_WALL_ICE)
3633 Tile[ELX][ELY] = EL_EMPTY;
3637 LX = laser.edge[laser.num_edges].x - cSX2;
3638 LY = laser.edge[laser.num_edges].y - cSY2;
3641 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3642 if (laser.damage[i].is_mirror)
3646 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3648 DrawLaser(0, DL_LASER_DISABLED);
3655 if (IS_WALL_AMOEBA(element) && CT > 60)
3657 int k1, k2, k3, dx, dy, de, dm;
3658 int element2 = Tile[ELX][ELY];
3660 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3663 for (i = laser.num_damages - 1; i >= 0; i--)
3664 if (laser.damage[i].is_mirror)
3667 r = laser.num_edges;
3668 d = laser.num_damages;
3675 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3678 DrawLaser(0, DL_LASER_ENABLED);
3681 x = laser.damage[k1].x;
3682 y = laser.damage[k1].y;
3687 for (i = 0; i < 4; i++)
3689 if (laser.wall_mask & (1 << i))
3691 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3692 cSY + ELY * TILEY + 31 * (i / 2)))
3695 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3696 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3703 for (i = 0; i < 4; i++)
3705 if (laser.wall_mask & (1 << i))
3707 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3708 cSY + ELY * TILEY + 31 * (i / 2)))
3715 if (laser.num_beamers > 0 ||
3716 k1 < 1 || k2 < 4 || k3 < 4 ||
3717 CheckLaserPixel(cSX + ELX * TILEX + 14,
3718 cSY + ELY * TILEY + 14))
3720 laser.num_edges = r;
3721 laser.num_damages = d;
3723 DrawLaser(0, DL_LASER_DISABLED);
3726 Tile[ELX][ELY] = element | laser.wall_mask;
3730 de = Tile[ELX][ELY];
3731 dm = laser.wall_mask;
3735 int x = ELX, y = ELY;
3736 int wall_mask = laser.wall_mask;
3739 DrawLaser(0, DL_LASER_ENABLED);
3741 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3743 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3744 Store[x][y] = EL_WALL_AMOEBA;
3745 Store2[x][y] = wall_mask;
3751 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3753 DrawLaser(0, DL_LASER_ENABLED);
3755 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3757 for (i = 4; i >= 0; i--)
3759 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3762 Delay_WithScreenUpdates(20);
3765 DrawLaser(0, DL_LASER_ENABLED);
3770 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3771 laser.stops_inside_element && CT > native_mm_level.time_block)
3776 if (ABS(XS) > ABS(YS))
3783 for (i = 0; i < 4; i++)
3790 x = ELX + Step[k * 4].x;
3791 y = ELY + Step[k * 4].y;
3793 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3796 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3804 laser.overloaded = (element == EL_BLOCK_STONE);
3809 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3812 Tile[x][y] = element;
3814 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3817 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3819 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3820 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3828 if (element == EL_FUEL_FULL && CT > 10)
3830 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3831 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3833 for (i = start; i <= num_init_game_frames; i++)
3835 if (i == num_init_game_frames)
3836 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3837 else if (setup.sound_loops)
3838 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3840 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3842 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3844 UpdateAndDisplayGameControlValues();
3849 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3851 DrawField_MM(ELX, ELY);
3853 DrawLaser(0, DL_LASER_ENABLED);
3861 void GameActions_MM(struct MouseActionInfo action)
3863 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3864 boolean button_released = (action.button == MB_RELEASED);
3866 GameActions_MM_Ext();
3868 CheckSingleStepMode_MM(element_clicked, button_released);
3871 void MovePacMen(void)
3873 int mx, my, ox, oy, nx, ny;
3877 if (++pacman_nr >= game_mm.num_pacman)
3880 game_mm.pacman[pacman_nr].dir--;
3882 for (l = 1; l < 5; l++)
3884 game_mm.pacman[pacman_nr].dir++;
3886 if (game_mm.pacman[pacman_nr].dir > 4)
3887 game_mm.pacman[pacman_nr].dir = 1;
3889 if (game_mm.pacman[pacman_nr].dir % 2)
3892 my = game_mm.pacman[pacman_nr].dir - 2;
3897 mx = 3 - game_mm.pacman[pacman_nr].dir;
3900 ox = game_mm.pacman[pacman_nr].x;
3901 oy = game_mm.pacman[pacman_nr].y;
3904 element = Tile[nx][ny];
3906 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3909 if (!IS_EATABLE4PACMAN(element))
3912 if (ObjHit(nx, ny, HIT_POS_CENTER))
3915 Tile[ox][oy] = EL_EMPTY;
3917 EL_PACMAN_RIGHT - 1 +
3918 (game_mm.pacman[pacman_nr].dir - 1 +
3919 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3921 game_mm.pacman[pacman_nr].x = nx;
3922 game_mm.pacman[pacman_nr].y = ny;
3924 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3926 if (element != EL_EMPTY)
3928 int graphic = el2gfx(Tile[nx][ny]);
3933 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3936 ox = cSX + ox * TILEX;
3937 oy = cSY + oy * TILEY;
3939 for (i = 1; i < 33; i += 2)
3940 BlitBitmap(bitmap, window,
3941 src_x, src_y, TILEX, TILEY,
3942 ox + i * mx, oy + i * my);
3943 Ct = Ct + FrameCounter - CT;
3946 DrawField_MM(nx, ny);
3949 if (!laser.fuse_off)
3951 DrawLaser(0, DL_LASER_ENABLED);
3953 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3955 AddDamagedField(nx, ny);
3957 laser.damage[laser.num_damages - 1].edge = 0;
3961 if (element == EL_BOMB)
3962 DeletePacMan(nx, ny);
3964 if (IS_WALL_AMOEBA(element) &&
3965 (LX + 2 * XS) / TILEX == nx &&
3966 (LY + 2 * YS) / TILEY == ny)
3976 static void InitMovingField_MM(int x, int y, int direction)
3978 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3979 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3981 MovDir[x][y] = direction;
3982 MovDir[newx][newy] = direction;
3984 if (Tile[newx][newy] == EL_EMPTY)
3985 Tile[newx][newy] = EL_BLOCKED;
3988 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3990 int direction = MovDir[x][y];
3991 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3992 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3998 static void Blocked2Moving_MM(int x, int y,
3999 int *comes_from_x, int *comes_from_y)
4001 int oldx = x, oldy = y;
4002 int direction = MovDir[x][y];
4004 if (direction == MV_LEFT)
4006 else if (direction == MV_RIGHT)
4008 else if (direction == MV_UP)
4010 else if (direction == MV_DOWN)
4013 *comes_from_x = oldx;
4014 *comes_from_y = oldy;
4017 static int MovingOrBlocked2Element_MM(int x, int y)
4019 int element = Tile[x][y];
4021 if (element == EL_BLOCKED)
4025 Blocked2Moving_MM(x, y, &oldx, &oldy);
4027 return Tile[oldx][oldy];
4034 static void RemoveField(int x, int y)
4036 Tile[x][y] = EL_EMPTY;
4043 static void RemoveMovingField_MM(int x, int y)
4045 int oldx = x, oldy = y, newx = x, newy = y;
4047 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4050 if (IS_MOVING(x, y))
4052 Moving2Blocked_MM(x, y, &newx, &newy);
4053 if (Tile[newx][newy] != EL_BLOCKED)
4056 else if (Tile[x][y] == EL_BLOCKED)
4058 Blocked2Moving_MM(x, y, &oldx, &oldy);
4059 if (!IS_MOVING(oldx, oldy))
4063 Tile[oldx][oldy] = EL_EMPTY;
4064 Tile[newx][newy] = EL_EMPTY;
4065 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4066 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4068 DrawLevelField_MM(oldx, oldy);
4069 DrawLevelField_MM(newx, newy);
4072 void PlaySoundLevel(int x, int y, int sound_nr)
4074 int sx = SCREENX(x), sy = SCREENY(y);
4076 int silence_distance = 8;
4078 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4079 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4082 if (!IN_LEV_FIELD(x, y) ||
4083 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4084 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4087 volume = SOUND_MAX_VOLUME;
4090 stereo = (sx - SCR_FIELDX/2) * 12;
4092 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4093 if (stereo > SOUND_MAX_RIGHT)
4094 stereo = SOUND_MAX_RIGHT;
4095 if (stereo < SOUND_MAX_LEFT)
4096 stereo = SOUND_MAX_LEFT;
4099 if (!IN_SCR_FIELD(sx, sy))
4101 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4102 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4104 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4107 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4110 static void RaiseScore_MM(int value)
4112 game_mm.score += value;
4115 void RaiseScoreElement_MM(int element)
4120 case EL_PACMAN_RIGHT:
4122 case EL_PACMAN_LEFT:
4123 case EL_PACMAN_DOWN:
4124 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4128 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4133 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4137 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4146 // ----------------------------------------------------------------------------
4147 // Mirror Magic game engine snapshot handling functions
4148 // ----------------------------------------------------------------------------
4150 void SaveEngineSnapshotValues_MM(void)
4154 engine_snapshot_mm.game_mm = game_mm;
4155 engine_snapshot_mm.laser = laser;
4157 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4159 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4161 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4162 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4163 engine_snapshot_mm.Box[x][y] = Box[x][y];
4164 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4168 engine_snapshot_mm.LX = LX;
4169 engine_snapshot_mm.LY = LY;
4170 engine_snapshot_mm.XS = XS;
4171 engine_snapshot_mm.YS = YS;
4172 engine_snapshot_mm.ELX = ELX;
4173 engine_snapshot_mm.ELY = ELY;
4174 engine_snapshot_mm.CT = CT;
4175 engine_snapshot_mm.Ct = Ct;
4177 engine_snapshot_mm.last_LX = last_LX;
4178 engine_snapshot_mm.last_LY = last_LY;
4179 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4180 engine_snapshot_mm.hold_x = hold_x;
4181 engine_snapshot_mm.hold_y = hold_y;
4182 engine_snapshot_mm.pacman_nr = pacman_nr;
4184 engine_snapshot_mm.rotate_delay = rotate_delay;
4185 engine_snapshot_mm.pacman_delay = pacman_delay;
4186 engine_snapshot_mm.energy_delay = energy_delay;
4187 engine_snapshot_mm.overload_delay = overload_delay;
4190 void LoadEngineSnapshotValues_MM(void)
4194 // stored engine snapshot buffers already restored at this point
4196 game_mm = engine_snapshot_mm.game_mm;
4197 laser = engine_snapshot_mm.laser;
4199 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4201 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4203 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4204 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4205 Box[x][y] = engine_snapshot_mm.Box[x][y];
4206 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4210 LX = engine_snapshot_mm.LX;
4211 LY = engine_snapshot_mm.LY;
4212 XS = engine_snapshot_mm.XS;
4213 YS = engine_snapshot_mm.YS;
4214 ELX = engine_snapshot_mm.ELX;
4215 ELY = engine_snapshot_mm.ELY;
4216 CT = engine_snapshot_mm.CT;
4217 Ct = engine_snapshot_mm.Ct;
4219 last_LX = engine_snapshot_mm.last_LX;
4220 last_LY = engine_snapshot_mm.last_LY;
4221 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4222 hold_x = engine_snapshot_mm.hold_x;
4223 hold_y = engine_snapshot_mm.hold_y;
4224 pacman_nr = engine_snapshot_mm.pacman_nr;
4226 rotate_delay = engine_snapshot_mm.rotate_delay;
4227 pacman_delay = engine_snapshot_mm.pacman_delay;
4228 energy_delay = engine_snapshot_mm.energy_delay;
4229 overload_delay = engine_snapshot_mm.overload_delay;
4231 RedrawPlayfield_MM();
4234 static int getAngleFromTouchDelta(int dx, int dy, int base)
4236 double pi = 3.141592653;
4237 double rad = atan2((double)-dy, (double)dx);
4238 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4239 double deg = rad2 * 180.0 / pi;
4241 return (int)(deg * base / 360.0 + 0.5) % base;
4244 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4246 // calculate start (source) position to be at the middle of the tile
4247 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4248 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4249 int dx = dst_mx - src_mx;
4250 int dy = dst_my - src_my;
4259 if (!IN_LEV_FIELD(x, y))
4262 element = Tile[x][y];
4264 if (!IS_MCDUFFIN(element) &&
4265 !IS_MIRROR(element) &&
4266 !IS_BEAMER(element) &&
4267 !IS_POLAR(element) &&
4268 !IS_POLAR_CROSS(element) &&
4269 !IS_DF_MIRROR(element))
4272 angle_old = get_element_angle(element);
4274 if (IS_MCDUFFIN(element))
4276 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4277 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4278 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4279 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4282 else if (IS_MIRROR(element) ||
4283 IS_DF_MIRROR(element))
4285 for (i = 0; i < laser.num_damages; i++)
4287 if (laser.damage[i].x == x &&
4288 laser.damage[i].y == y &&
4289 ObjHit(x, y, HIT_POS_CENTER))
4291 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4292 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4299 if (angle_new == -1)
4301 if (IS_MIRROR(element) ||
4302 IS_DF_MIRROR(element) ||
4306 if (IS_POLAR_CROSS(element))
4309 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4312 button = (angle_new == angle_old ? 0 :
4313 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4314 MB_LEFTBUTTON : MB_RIGHTBUTTON);