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 MarkTileDirty(x, y);
2479 if (!MovDelay[x][y])
2481 Tile[x][y] = Store[x][y];
2482 Store[x][y] = Store2[x][y] = 0;
2491 static void OpenEnvelope(int x, int y)
2493 int num_frames = 8; // seven frames plus final empty space
2495 if (!MovDelay[x][y]) // next animation frame
2496 MovDelay[x][y] = num_frames;
2498 if (MovDelay[x][y]) // wait some time before next frame
2500 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2504 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2506 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2507 int frame = num_frames - MovDelay[x][y] - 1;
2509 DrawGraphicAnimation_MM(x, y, graphic, frame);
2511 laser.redraw = TRUE;
2514 if (MovDelay[x][y] == 0)
2516 Tile[x][y] = EL_EMPTY;
2522 ShowEnvelope_MM(nr);
2527 static void MeltIce(int x, int y)
2532 if (!MovDelay[x][y]) // next animation frame
2533 MovDelay[x][y] = frames * delay;
2535 if (MovDelay[x][y]) // wait some time before next frame
2538 int wall_mask = Store2[x][y];
2539 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2542 phase = frames - MovDelay[x][y] / delay - 1;
2544 if (!MovDelay[x][y])
2548 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2549 Store[x][y] = Store2[x][y] = 0;
2551 DrawWalls_MM(x, y, Tile[x][y]);
2553 if (Tile[x][y] == EL_WALL_ICE)
2554 Tile[x][y] = EL_EMPTY;
2556 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2557 if (laser.damage[i].is_mirror)
2561 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2563 DrawLaser(0, DL_LASER_DISABLED);
2567 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2569 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2571 laser.redraw = TRUE;
2576 static void GrowAmoeba(int x, int y)
2581 if (!MovDelay[x][y]) // next animation frame
2582 MovDelay[x][y] = frames * delay;
2584 if (MovDelay[x][y]) // wait some time before next frame
2587 int wall_mask = Store2[x][y];
2588 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2591 phase = MovDelay[x][y] / delay;
2593 if (!MovDelay[x][y])
2595 Tile[x][y] = real_element;
2596 Store[x][y] = Store2[x][y] = 0;
2598 DrawWalls_MM(x, y, Tile[x][y]);
2599 DrawLaser(0, DL_LASER_ENABLED);
2601 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2603 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2608 static void DrawFieldAnimated_MM(int x, int y)
2612 laser.redraw = TRUE;
2615 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2617 int element = Tile[x][y];
2618 int graphic = el2gfx(element);
2620 if (!getGraphicInfo_NewFrame(x, y, graphic))
2625 laser.redraw = TRUE;
2628 static void DrawFieldTwinkle(int x, int y)
2630 if (MovDelay[x][y] != 0) // wait some time before next frame
2636 if (MovDelay[x][y] != 0)
2638 int graphic = IMG_TWINKLE_WHITE;
2639 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2641 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2644 laser.redraw = TRUE;
2648 static void Explode_MM(int x, int y, int phase, int mode)
2650 int num_phase = 9, delay = 2;
2651 int last_phase = num_phase * delay;
2652 int half_phase = (num_phase / 2) * delay;
2654 laser.redraw = TRUE;
2656 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2658 int center_element = Tile[x][y];
2660 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2662 // put moving element to center field (and let it explode there)
2663 center_element = MovingOrBlocked2Element_MM(x, y);
2664 RemoveMovingField_MM(x, y);
2666 Tile[x][y] = center_element;
2669 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2670 Store[x][y] = center_element;
2672 Store[x][y] = EL_EMPTY;
2674 Store2[x][y] = mode;
2676 Tile[x][y] = EL_EXPLODING_OPAQUE;
2677 GfxElement[x][y] = center_element;
2679 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2681 ExplodePhase[x][y] = 1;
2687 GfxFrame[x][y] = 0; // restart explosion animation
2689 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2691 if (phase == half_phase)
2693 Tile[x][y] = EL_EXPLODING_TRANSP;
2695 if (x == ELX && y == ELY)
2699 if (phase == last_phase)
2701 if (Store[x][y] == EL_BOMB_ACTIVE)
2703 DrawLaser(0, DL_LASER_DISABLED);
2706 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2707 Store[x][y] = EL_EMPTY;
2709 GameOver_MM(GAME_OVER_DELAYED);
2711 laser.overloaded = FALSE;
2713 else if (IS_MCDUFFIN(Store[x][y]))
2715 Store[x][y] = EL_EMPTY;
2717 GameOver_MM(GAME_OVER_BOMB);
2720 Tile[x][y] = Store[x][y];
2721 Store[x][y] = Store2[x][y] = 0;
2722 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2727 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2729 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2730 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2732 DrawGraphicAnimation_MM(x, y, graphic, frame);
2734 MarkTileDirty(x, y);
2738 static void Bang_MM(int x, int y)
2740 int element = Tile[x][y];
2743 DrawLaser(0, DL_LASER_ENABLED);
2746 if (IS_PACMAN(element))
2747 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2748 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2749 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2750 else if (element == EL_KEY)
2751 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2753 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2755 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2758 void TurnRound(int x, int y)
2770 { 0, 0 }, { 0, 0 }, { 0, 0 },
2775 int left, right, back;
2779 { MV_DOWN, MV_UP, MV_RIGHT },
2780 { MV_UP, MV_DOWN, MV_LEFT },
2782 { MV_LEFT, MV_RIGHT, MV_DOWN },
2783 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2784 { MV_RIGHT, MV_LEFT, MV_UP }
2787 int element = Tile[x][y];
2788 int old_move_dir = MovDir[x][y];
2789 int right_dir = turn[old_move_dir].right;
2790 int back_dir = turn[old_move_dir].back;
2791 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2792 int right_x = x + right_dx, right_y = y + right_dy;
2794 if (element == EL_PACMAN)
2796 boolean can_turn_right = FALSE;
2798 if (IN_LEV_FIELD(right_x, right_y) &&
2799 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2800 can_turn_right = TRUE;
2803 MovDir[x][y] = right_dir;
2805 MovDir[x][y] = back_dir;
2811 static void StartMoving_MM(int x, int y)
2813 int element = Tile[x][y];
2818 if (CAN_MOVE(element))
2822 if (MovDelay[x][y]) // wait some time before next movement
2830 // now make next step
2832 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2834 if (element == EL_PACMAN &&
2835 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2836 !ObjHit(newx, newy, HIT_POS_CENTER))
2838 Store[newx][newy] = Tile[newx][newy];
2839 Tile[newx][newy] = EL_EMPTY;
2841 DrawField_MM(newx, newy);
2843 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2844 ObjHit(newx, newy, HIT_POS_CENTER))
2846 // object was running against a wall
2853 InitMovingField_MM(x, y, MovDir[x][y]);
2857 ContinueMoving_MM(x, y);
2860 static void ContinueMoving_MM(int x, int y)
2862 int element = Tile[x][y];
2863 int direction = MovDir[x][y];
2864 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2865 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2866 int horiz_move = (dx!=0);
2867 int newx = x + dx, newy = y + dy;
2868 int step = (horiz_move ? dx : dy) * TILEX / 8;
2870 MovPos[x][y] += step;
2872 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2874 Tile[x][y] = EL_EMPTY;
2875 Tile[newx][newy] = element;
2877 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2878 MovDelay[newx][newy] = 0;
2880 if (!CAN_MOVE(element))
2881 MovDir[newx][newy] = 0;
2884 DrawField_MM(newx, newy);
2886 Stop[newx][newy] = TRUE;
2888 if (element == EL_PACMAN)
2890 if (Store[newx][newy] == EL_BOMB)
2891 Bang_MM(newx, newy);
2893 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2894 (LX + 2 * XS) / TILEX == newx &&
2895 (LY + 2 * YS) / TILEY == newy)
2902 else // still moving on
2907 laser.redraw = TRUE;
2910 boolean ClickElement(int x, int y, int button)
2912 static DelayCounter click_delay = { CLICK_DELAY };
2913 static boolean new_button = TRUE;
2914 boolean element_clicked = FALSE;
2919 // initialize static variables
2920 click_delay.count = 0;
2921 click_delay.value = CLICK_DELAY;
2927 // do not rotate objects hit by the laser after the game was solved
2928 if (game_mm.level_solved && Hit[x][y])
2931 if (button == MB_RELEASED)
2934 click_delay.value = CLICK_DELAY;
2936 // release eventually hold auto-rotating mirror
2937 RotateMirror(x, y, MB_RELEASED);
2942 if (!FrameReached(&click_delay) && !new_button)
2945 if (button == MB_MIDDLEBUTTON) // middle button has no function
2948 if (!IN_LEV_FIELD(x, y))
2951 if (Tile[x][y] == EL_EMPTY)
2954 element = Tile[x][y];
2956 if (IS_MIRROR(element) ||
2957 IS_BEAMER(element) ||
2958 IS_POLAR(element) ||
2959 IS_POLAR_CROSS(element) ||
2960 IS_DF_MIRROR(element) ||
2961 IS_DF_MIRROR_AUTO(element))
2963 RotateMirror(x, y, button);
2965 element_clicked = TRUE;
2967 else if (IS_MCDUFFIN(element))
2969 if (!laser.fuse_off)
2971 DrawLaser(0, DL_LASER_DISABLED);
2978 element = get_rotated_element(element, BUTTON_ROTATION(button));
2979 laser.start_angle = get_element_angle(element);
2983 Tile[x][y] = element;
2990 if (!laser.fuse_off)
2993 element_clicked = TRUE;
2995 else if (element == EL_FUSE_ON && laser.fuse_off)
2997 if (x != laser.fuse_x || y != laser.fuse_y)
3000 laser.fuse_off = FALSE;
3001 laser.fuse_x = laser.fuse_y = -1;
3003 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3006 element_clicked = TRUE;
3008 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3010 laser.fuse_off = TRUE;
3013 laser.overloaded = FALSE;
3015 DrawLaser(0, DL_LASER_DISABLED);
3016 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3018 element_clicked = TRUE;
3020 else if (element == EL_LIGHTBALL)
3023 RaiseScoreElement_MM(element);
3024 DrawLaser(0, DL_LASER_ENABLED);
3026 element_clicked = TRUE;
3028 else if (IS_ENVELOPE(element))
3030 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3032 element_clicked = TRUE;
3035 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3038 return element_clicked;
3041 void RotateMirror(int x, int y, int button)
3043 if (button == MB_RELEASED)
3045 // release eventually hold auto-rotating mirror
3052 if (IS_MIRROR(Tile[x][y]) ||
3053 IS_POLAR_CROSS(Tile[x][y]) ||
3054 IS_POLAR(Tile[x][y]) ||
3055 IS_BEAMER(Tile[x][y]) ||
3056 IS_DF_MIRROR(Tile[x][y]) ||
3057 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3058 IS_GRID_WOOD_AUTO(Tile[x][y]))
3060 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3062 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3064 if (button == MB_LEFTBUTTON)
3066 // left mouse button only for manual adjustment, no auto-rotating;
3067 // freeze mirror for until mouse button released
3071 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3073 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3077 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3079 int edge = Hit[x][y];
3085 DrawLaser(edge - 1, DL_LASER_DISABLED);
3089 else if (ObjHit(x, y, HIT_POS_CENTER))
3091 int edge = Hit[x][y];
3095 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3100 DrawLaser(edge - 1, DL_LASER_DISABLED);
3107 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3112 if ((IS_BEAMER(Tile[x][y]) ||
3113 IS_POLAR(Tile[x][y]) ||
3114 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3116 if (IS_BEAMER(Tile[x][y]))
3119 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3120 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3133 DrawLaser(0, DL_LASER_ENABLED);
3137 static void AutoRotateMirrors(void)
3141 if (!FrameReached(&rotate_delay))
3144 for (x = 0; x < lev_fieldx; x++)
3146 for (y = 0; y < lev_fieldy; y++)
3148 int element = Tile[x][y];
3150 // do not rotate objects hit by the laser after the game was solved
3151 if (game_mm.level_solved && Hit[x][y])
3154 if (IS_DF_MIRROR_AUTO(element) ||
3155 IS_GRID_WOOD_AUTO(element) ||
3156 IS_GRID_STEEL_AUTO(element) ||
3157 element == EL_REFRACTOR)
3158 RotateMirror(x, y, MB_RIGHTBUTTON);
3163 boolean ObjHit(int obx, int oby, int bits)
3170 if (bits & HIT_POS_CENTER)
3172 if (CheckLaserPixel(cSX + obx + 15,
3177 if (bits & HIT_POS_EDGE)
3179 for (i = 0; i < 4; i++)
3180 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3181 cSY + oby + 31 * (i / 2)))
3185 if (bits & HIT_POS_BETWEEN)
3187 for (i = 0; i < 4; i++)
3188 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3189 cSY + 4 + oby + 22 * (i / 2)))
3196 void DeletePacMan(int px, int py)
3202 if (game_mm.num_pacman <= 1)
3204 game_mm.num_pacman = 0;
3208 for (i = 0; i < game_mm.num_pacman; i++)
3209 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3212 game_mm.num_pacman--;
3214 for (j = i; j < game_mm.num_pacman; j++)
3216 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3217 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3218 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3222 void ColorCycling(void)
3224 static int CC, Cc = 0;
3226 static int color, old = 0xF00, new = 0x010, mult = 1;
3227 static unsigned short red, green, blue;
3229 if (color_status == STATIC_COLORS)
3234 if (CC < Cc || CC > Cc + 2)
3238 color = old + new * mult;
3244 if (ABS(mult) == 16)
3254 red = 0x0e00 * ((color & 0xF00) >> 8);
3255 green = 0x0e00 * ((color & 0x0F0) >> 4);
3256 blue = 0x0e00 * ((color & 0x00F));
3257 SetRGB(pen_magicolor[0], red, green, blue);
3259 red = 0x1111 * ((color & 0xF00) >> 8);
3260 green = 0x1111 * ((color & 0x0F0) >> 4);
3261 blue = 0x1111 * ((color & 0x00F));
3262 SetRGB(pen_magicolor[1], red, green, blue);
3266 static void GameActions_MM_Ext(void)
3273 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3276 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3278 element = Tile[x][y];
3280 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3281 StartMoving_MM(x, y);
3282 else if (IS_MOVING(x, y))
3283 ContinueMoving_MM(x, y);
3284 else if (IS_EXPLODING(element))
3285 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3286 else if (element == EL_EXIT_OPENING)
3288 else if (element == EL_GRAY_BALL_OPENING)
3289 OpenSurpriseBall(x, y);
3290 else if (IS_ENVELOPE_OPENING(element))
3292 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3294 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3296 else if (IS_MIRROR(element) ||
3297 IS_MIRROR_FIXED(element) ||
3298 element == EL_PRISM)
3299 DrawFieldTwinkle(x, y);
3300 else if (element == EL_GRAY_BALL_OPENING ||
3301 element == EL_BOMB_ACTIVE ||
3302 element == EL_MINE_ACTIVE)
3303 DrawFieldAnimated_MM(x, y);
3304 else if (!IS_BLOCKED(x, y))
3305 DrawFieldAnimatedIfNeeded_MM(x, y);
3308 AutoRotateMirrors();
3311 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3313 // redraw after Explode_MM() ...
3315 DrawLaser(0, DL_LASER_ENABLED);
3316 laser.redraw = FALSE;
3321 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3325 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3327 DrawLaser(0, DL_LASER_DISABLED);
3332 // skip all following game actions if game is over
3333 if (game_mm.game_over)
3336 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3340 GameOver_MM(GAME_OVER_NO_ENERGY);
3345 if (FrameReached(&energy_delay))
3347 if (game_mm.energy_left > 0)
3348 game_mm.energy_left--;
3350 // when out of energy, wait another frame to play "out of time" sound
3353 element = laser.dest_element;
3356 if (element != Tile[ELX][ELY])
3358 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3359 element, Tile[ELX][ELY]);
3363 if (!laser.overloaded && laser.overload_value == 0 &&
3364 element != EL_BOMB &&
3365 element != EL_BOMB_ACTIVE &&
3366 element != EL_MINE &&
3367 element != EL_MINE_ACTIVE &&
3368 element != EL_BALL_GRAY &&
3369 element != EL_BLOCK_STONE &&
3370 element != EL_BLOCK_WOOD &&
3371 element != EL_FUSE_ON &&
3372 element != EL_FUEL_FULL &&
3373 !IS_WALL_ICE(element) &&
3374 !IS_WALL_AMOEBA(element))
3377 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3379 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3380 (!laser.overloaded && laser.overload_value > 0)) &&
3381 FrameReached(&overload_delay))
3383 if (laser.overloaded)
3384 laser.overload_value++;
3386 laser.overload_value--;
3388 if (game_mm.cheat_no_overload)
3390 laser.overloaded = FALSE;
3391 laser.overload_value = 0;
3394 game_mm.laser_overload_value = laser.overload_value;
3396 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3398 SetLaserColor(0xFF);
3400 DrawLaser(0, DL_LASER_ENABLED);
3403 if (!laser.overloaded)
3404 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3405 else if (setup.sound_loops)
3406 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3408 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3410 if (laser.overloaded)
3413 BlitBitmap(pix[PIX_DOOR], drawto,
3414 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3415 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3416 - laser.overload_value,
3417 OVERLOAD_XSIZE, laser.overload_value,
3418 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3419 - laser.overload_value);
3421 redraw_mask |= REDRAW_DOOR_1;
3426 BlitBitmap(pix[PIX_DOOR], drawto,
3427 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3428 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3429 DX_OVERLOAD, DY_OVERLOAD);
3431 redraw_mask |= REDRAW_DOOR_1;
3434 if (laser.overload_value == MAX_LASER_OVERLOAD)
3436 UpdateAndDisplayGameControlValues();
3440 GameOver_MM(GAME_OVER_OVERLOADED);
3451 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3453 if (game_mm.cheat_no_explosion)
3458 laser.dest_element = EL_EXPLODING_OPAQUE;
3463 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3465 laser.fuse_off = TRUE;
3469 DrawLaser(0, DL_LASER_DISABLED);
3470 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3473 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3475 if (!Store2[ELX][ELY]) // check if content element not yet determined
3477 int last_anim_random_frame = gfx.anim_random_frame;
3480 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3481 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3483 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3484 native_mm_level.ball_choice_mode, 0,
3485 game_mm.ball_choice_pos);
3487 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3488 gfx.anim_random_frame = last_anim_random_frame;
3490 game_mm.ball_choice_pos++;
3492 int new_element = native_mm_level.ball_content[element_pos];
3494 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3495 Store2[ELX][ELY] = TRUE;
3498 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3500 // !!! CHECK AGAIN: Laser on Polarizer !!!
3503 laser.dest_element_last = Tile[ELX][ELY];
3504 laser.dest_element_last_x = ELX;
3505 laser.dest_element_last_y = ELY;
3515 element = EL_MIRROR_START + RND(16);
3521 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3528 element = (rnd == 0 ? EL_FUSE_ON :
3529 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3530 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3531 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3532 EL_MIRROR_FIXED_START + rnd - 25);
3537 graphic = el2gfx(element);
3539 for (i = 0; i < 50; i++)
3545 BlitBitmap(pix[PIX_BACK], drawto,
3546 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3547 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3548 SX + ELX * TILEX + x,
3549 SY + ELY * TILEY + y);
3551 MarkTileDirty(ELX, ELY);
3554 DrawLaser(0, DL_LASER_ENABLED);
3556 Delay_WithScreenUpdates(50);
3559 Tile[ELX][ELY] = element;
3560 DrawField_MM(ELX, ELY);
3563 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3566 // above stuff: GRAY BALL -> PRISM !!!
3568 LX = ELX * TILEX + 14;
3569 LY = ELY * TILEY + 14;
3570 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3577 laser.num_edges -= 2;
3578 laser.num_damages--;
3582 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3583 if (laser.damage[i].is_mirror)
3587 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3589 DrawLaser(0, DL_LASER_DISABLED);
3591 DrawLaser(0, DL_LASER_DISABLED);
3600 if (IS_WALL_ICE(element) && CT > 50)
3602 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3605 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3606 Store[ELX][ELY] = EL_WALL_ICE;
3607 Store2[ELX][ELY] = laser.wall_mask;
3609 laser.dest_element = Tile[ELX][ELY];
3614 for (i = 0; i < 5; i++)
3620 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3624 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3626 Delay_WithScreenUpdates(100);
3629 if (Tile[ELX][ELY] == EL_WALL_ICE)
3630 Tile[ELX][ELY] = EL_EMPTY;
3634 LX = laser.edge[laser.num_edges].x - cSX2;
3635 LY = laser.edge[laser.num_edges].y - cSY2;
3638 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3639 if (laser.damage[i].is_mirror)
3643 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3645 DrawLaser(0, DL_LASER_DISABLED);
3652 if (IS_WALL_AMOEBA(element) && CT > 60)
3654 int k1, k2, k3, dx, dy, de, dm;
3655 int element2 = Tile[ELX][ELY];
3657 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3660 for (i = laser.num_damages - 1; i >= 0; i--)
3661 if (laser.damage[i].is_mirror)
3664 r = laser.num_edges;
3665 d = laser.num_damages;
3672 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3675 DrawLaser(0, DL_LASER_ENABLED);
3678 x = laser.damage[k1].x;
3679 y = laser.damage[k1].y;
3684 for (i = 0; i < 4; i++)
3686 if (laser.wall_mask & (1 << i))
3688 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3689 cSY + ELY * TILEY + 31 * (i / 2)))
3692 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3693 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3700 for (i = 0; i < 4; i++)
3702 if (laser.wall_mask & (1 << i))
3704 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3705 cSY + ELY * TILEY + 31 * (i / 2)))
3712 if (laser.num_beamers > 0 ||
3713 k1 < 1 || k2 < 4 || k3 < 4 ||
3714 CheckLaserPixel(cSX + ELX * TILEX + 14,
3715 cSY + ELY * TILEY + 14))
3717 laser.num_edges = r;
3718 laser.num_damages = d;
3720 DrawLaser(0, DL_LASER_DISABLED);
3723 Tile[ELX][ELY] = element | laser.wall_mask;
3727 de = Tile[ELX][ELY];
3728 dm = laser.wall_mask;
3732 int x = ELX, y = ELY;
3733 int wall_mask = laser.wall_mask;
3736 DrawLaser(0, DL_LASER_ENABLED);
3738 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3740 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3741 Store[x][y] = EL_WALL_AMOEBA;
3742 Store2[x][y] = wall_mask;
3748 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3750 DrawLaser(0, DL_LASER_ENABLED);
3752 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3754 for (i = 4; i >= 0; i--)
3756 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3759 Delay_WithScreenUpdates(20);
3762 DrawLaser(0, DL_LASER_ENABLED);
3767 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3768 laser.stops_inside_element && CT > native_mm_level.time_block)
3773 if (ABS(XS) > ABS(YS))
3780 for (i = 0; i < 4; i++)
3787 x = ELX + Step[k * 4].x;
3788 y = ELY + Step[k * 4].y;
3790 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3793 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3801 laser.overloaded = (element == EL_BLOCK_STONE);
3806 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3809 Tile[x][y] = element;
3811 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3814 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3816 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3817 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3825 if (element == EL_FUEL_FULL && CT > 10)
3827 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3828 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3830 for (i = start; i <= num_init_game_frames; i++)
3832 if (i == num_init_game_frames)
3833 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3834 else if (setup.sound_loops)
3835 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3837 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3839 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3841 UpdateAndDisplayGameControlValues();
3846 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3848 DrawField_MM(ELX, ELY);
3850 DrawLaser(0, DL_LASER_ENABLED);
3858 void GameActions_MM(struct MouseActionInfo action)
3860 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3861 boolean button_released = (action.button == MB_RELEASED);
3863 GameActions_MM_Ext();
3865 CheckSingleStepMode_MM(element_clicked, button_released);
3868 void MovePacMen(void)
3870 int mx, my, ox, oy, nx, ny;
3874 if (++pacman_nr >= game_mm.num_pacman)
3877 game_mm.pacman[pacman_nr].dir--;
3879 for (l = 1; l < 5; l++)
3881 game_mm.pacman[pacman_nr].dir++;
3883 if (game_mm.pacman[pacman_nr].dir > 4)
3884 game_mm.pacman[pacman_nr].dir = 1;
3886 if (game_mm.pacman[pacman_nr].dir % 2)
3889 my = game_mm.pacman[pacman_nr].dir - 2;
3894 mx = 3 - game_mm.pacman[pacman_nr].dir;
3897 ox = game_mm.pacman[pacman_nr].x;
3898 oy = game_mm.pacman[pacman_nr].y;
3901 element = Tile[nx][ny];
3903 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3906 if (!IS_EATABLE4PACMAN(element))
3909 if (ObjHit(nx, ny, HIT_POS_CENTER))
3912 Tile[ox][oy] = EL_EMPTY;
3914 EL_PACMAN_RIGHT - 1 +
3915 (game_mm.pacman[pacman_nr].dir - 1 +
3916 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3918 game_mm.pacman[pacman_nr].x = nx;
3919 game_mm.pacman[pacman_nr].y = ny;
3921 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3923 if (element != EL_EMPTY)
3925 int graphic = el2gfx(Tile[nx][ny]);
3930 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3933 ox = cSX + ox * TILEX;
3934 oy = cSY + oy * TILEY;
3936 for (i = 1; i < 33; i += 2)
3937 BlitBitmap(bitmap, window,
3938 src_x, src_y, TILEX, TILEY,
3939 ox + i * mx, oy + i * my);
3940 Ct = Ct + FrameCounter - CT;
3943 DrawField_MM(nx, ny);
3946 if (!laser.fuse_off)
3948 DrawLaser(0, DL_LASER_ENABLED);
3950 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3952 AddDamagedField(nx, ny);
3954 laser.damage[laser.num_damages - 1].edge = 0;
3958 if (element == EL_BOMB)
3959 DeletePacMan(nx, ny);
3961 if (IS_WALL_AMOEBA(element) &&
3962 (LX + 2 * XS) / TILEX == nx &&
3963 (LY + 2 * YS) / TILEY == ny)
3973 static void InitMovingField_MM(int x, int y, int direction)
3975 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3976 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3978 MovDir[x][y] = direction;
3979 MovDir[newx][newy] = direction;
3981 if (Tile[newx][newy] == EL_EMPTY)
3982 Tile[newx][newy] = EL_BLOCKED;
3985 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3987 int direction = MovDir[x][y];
3988 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3989 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3995 static void Blocked2Moving_MM(int x, int y,
3996 int *comes_from_x, int *comes_from_y)
3998 int oldx = x, oldy = y;
3999 int direction = MovDir[x][y];
4001 if (direction == MV_LEFT)
4003 else if (direction == MV_RIGHT)
4005 else if (direction == MV_UP)
4007 else if (direction == MV_DOWN)
4010 *comes_from_x = oldx;
4011 *comes_from_y = oldy;
4014 static int MovingOrBlocked2Element_MM(int x, int y)
4016 int element = Tile[x][y];
4018 if (element == EL_BLOCKED)
4022 Blocked2Moving_MM(x, y, &oldx, &oldy);
4024 return Tile[oldx][oldy];
4031 static void RemoveField(int x, int y)
4033 Tile[x][y] = EL_EMPTY;
4040 static void RemoveMovingField_MM(int x, int y)
4042 int oldx = x, oldy = y, newx = x, newy = y;
4044 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4047 if (IS_MOVING(x, y))
4049 Moving2Blocked_MM(x, y, &newx, &newy);
4050 if (Tile[newx][newy] != EL_BLOCKED)
4053 else if (Tile[x][y] == EL_BLOCKED)
4055 Blocked2Moving_MM(x, y, &oldx, &oldy);
4056 if (!IS_MOVING(oldx, oldy))
4060 Tile[oldx][oldy] = EL_EMPTY;
4061 Tile[newx][newy] = EL_EMPTY;
4062 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4063 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4065 DrawLevelField_MM(oldx, oldy);
4066 DrawLevelField_MM(newx, newy);
4069 void PlaySoundLevel(int x, int y, int sound_nr)
4071 int sx = SCREENX(x), sy = SCREENY(y);
4073 int silence_distance = 8;
4075 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4076 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4079 if (!IN_LEV_FIELD(x, y) ||
4080 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4081 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4084 volume = SOUND_MAX_VOLUME;
4087 stereo = (sx - SCR_FIELDX/2) * 12;
4089 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4090 if (stereo > SOUND_MAX_RIGHT)
4091 stereo = SOUND_MAX_RIGHT;
4092 if (stereo < SOUND_MAX_LEFT)
4093 stereo = SOUND_MAX_LEFT;
4096 if (!IN_SCR_FIELD(sx, sy))
4098 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4099 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4101 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4104 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4107 static void RaiseScore_MM(int value)
4109 game_mm.score += value;
4112 void RaiseScoreElement_MM(int element)
4117 case EL_PACMAN_RIGHT:
4119 case EL_PACMAN_LEFT:
4120 case EL_PACMAN_DOWN:
4121 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4125 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4130 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4134 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4143 // ----------------------------------------------------------------------------
4144 // Mirror Magic game engine snapshot handling functions
4145 // ----------------------------------------------------------------------------
4147 void SaveEngineSnapshotValues_MM(void)
4151 engine_snapshot_mm.game_mm = game_mm;
4152 engine_snapshot_mm.laser = laser;
4154 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4156 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4158 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4159 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4160 engine_snapshot_mm.Box[x][y] = Box[x][y];
4161 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4165 engine_snapshot_mm.LX = LX;
4166 engine_snapshot_mm.LY = LY;
4167 engine_snapshot_mm.XS = XS;
4168 engine_snapshot_mm.YS = YS;
4169 engine_snapshot_mm.ELX = ELX;
4170 engine_snapshot_mm.ELY = ELY;
4171 engine_snapshot_mm.CT = CT;
4172 engine_snapshot_mm.Ct = Ct;
4174 engine_snapshot_mm.last_LX = last_LX;
4175 engine_snapshot_mm.last_LY = last_LY;
4176 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4177 engine_snapshot_mm.hold_x = hold_x;
4178 engine_snapshot_mm.hold_y = hold_y;
4179 engine_snapshot_mm.pacman_nr = pacman_nr;
4181 engine_snapshot_mm.rotate_delay = rotate_delay;
4182 engine_snapshot_mm.pacman_delay = pacman_delay;
4183 engine_snapshot_mm.energy_delay = energy_delay;
4184 engine_snapshot_mm.overload_delay = overload_delay;
4187 void LoadEngineSnapshotValues_MM(void)
4191 // stored engine snapshot buffers already restored at this point
4193 game_mm = engine_snapshot_mm.game_mm;
4194 laser = engine_snapshot_mm.laser;
4196 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4198 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4200 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4201 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4202 Box[x][y] = engine_snapshot_mm.Box[x][y];
4203 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4207 LX = engine_snapshot_mm.LX;
4208 LY = engine_snapshot_mm.LY;
4209 XS = engine_snapshot_mm.XS;
4210 YS = engine_snapshot_mm.YS;
4211 ELX = engine_snapshot_mm.ELX;
4212 ELY = engine_snapshot_mm.ELY;
4213 CT = engine_snapshot_mm.CT;
4214 Ct = engine_snapshot_mm.Ct;
4216 last_LX = engine_snapshot_mm.last_LX;
4217 last_LY = engine_snapshot_mm.last_LY;
4218 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4219 hold_x = engine_snapshot_mm.hold_x;
4220 hold_y = engine_snapshot_mm.hold_y;
4221 pacman_nr = engine_snapshot_mm.pacman_nr;
4223 rotate_delay = engine_snapshot_mm.rotate_delay;
4224 pacman_delay = engine_snapshot_mm.pacman_delay;
4225 energy_delay = engine_snapshot_mm.energy_delay;
4226 overload_delay = engine_snapshot_mm.overload_delay;
4228 RedrawPlayfield_MM();
4231 static int getAngleFromTouchDelta(int dx, int dy, int base)
4233 double pi = 3.141592653;
4234 double rad = atan2((double)-dy, (double)dx);
4235 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4236 double deg = rad2 * 180.0 / pi;
4238 return (int)(deg * base / 360.0 + 0.5) % base;
4241 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4243 // calculate start (source) position to be at the middle of the tile
4244 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4245 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4246 int dx = dst_mx - src_mx;
4247 int dy = dst_my - src_my;
4256 if (!IN_LEV_FIELD(x, y))
4259 element = Tile[x][y];
4261 if (!IS_MCDUFFIN(element) &&
4262 !IS_MIRROR(element) &&
4263 !IS_BEAMER(element) &&
4264 !IS_POLAR(element) &&
4265 !IS_POLAR_CROSS(element) &&
4266 !IS_DF_MIRROR(element))
4269 angle_old = get_element_angle(element);
4271 if (IS_MCDUFFIN(element))
4273 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4274 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4275 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4276 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4279 else if (IS_MIRROR(element) ||
4280 IS_DF_MIRROR(element))
4282 for (i = 0; i < laser.num_damages; i++)
4284 if (laser.damage[i].x == x &&
4285 laser.damage[i].y == y &&
4286 ObjHit(x, y, HIT_POS_CENTER))
4288 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4289 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4296 if (angle_new == -1)
4298 if (IS_MIRROR(element) ||
4299 IS_DF_MIRROR(element) ||
4303 if (IS_POLAR_CROSS(element))
4306 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4309 button = (angle_new == angle_old ? 0 :
4310 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4311 MB_LEFTBUTTON : MB_RIGHTBUTTON);