1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
100 static void Moving2Blocked_MM(int, int, int *, int *);
102 // bitmap for laser beam detection
103 static Bitmap *laser_bitmap = NULL;
105 // variables for laser control
106 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
107 static int hold_x = -1, hold_y = -1;
109 // variables for pacman control
110 static int pacman_nr = -1;
112 // various game engine delay counters
113 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
114 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
115 static DelayCounter energy_delay = { ENERGY_DELAY };
116 static DelayCounter overload_delay = { 0 };
118 // element mask positions for scanning pixels of MM elements
119 #define MM_MASK_MCDUFFIN_RIGHT 0
120 #define MM_MASK_MCDUFFIN_UP 1
121 #define MM_MASK_MCDUFFIN_LEFT 2
122 #define MM_MASK_MCDUFFIN_DOWN 3
123 #define MM_MASK_GRID_1 4
124 #define MM_MASK_GRID_2 5
125 #define MM_MASK_GRID_3 6
126 #define MM_MASK_GRID_4 7
127 #define MM_MASK_RECTANGLE 8
128 #define MM_MASK_CIRCLE 9
130 #define NUM_MM_MASKS 10
132 // element masks for scanning pixels of MM elements
133 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
317 static int get_element_angle(int element)
319 int element_phase = get_element_phase(element);
321 if (IS_MIRROR_FIXED(element) ||
322 IS_MCDUFFIN(element) ||
324 IS_RECEIVER(element))
325 return 4 * element_phase;
327 return element_phase;
330 static int get_opposite_angle(int angle)
332 int opposite_angle = angle + ANG_RAY_180;
334 // make sure "opposite_angle" is in valid interval [0, 15]
335 return (opposite_angle + 16) % 16;
338 static int get_mirrored_angle(int laser_angle, int mirror_angle)
340 int reflected_angle = 16 - laser_angle + mirror_angle;
342 // make sure "reflected_angle" is in valid interval [0, 15]
343 return (reflected_angle + 16) % 16;
346 static void DrawLaserLines(struct XY *points, int num_points, int mode)
348 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
349 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
351 DrawLines(drawto, points, num_points, pixel_drawto);
355 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
360 static boolean CheckLaserPixel(int x, int y)
366 pixel = ReadPixel(laser_bitmap, x, y);
370 return (pixel == WHITE_PIXEL);
373 static void CheckExitMM(void)
375 int exit_element = EL_EMPTY;
379 static int xy[4][2] =
387 for (y = 0; y < lev_fieldy; y++)
389 for (x = 0; x < lev_fieldx; x++)
391 if (Tile[x][y] == EL_EXIT_CLOSED)
393 // initiate opening animation of exit door
394 Tile[x][y] = EL_EXIT_OPENING;
396 exit_element = EL_EXIT_OPEN;
400 else if (IS_RECEIVER(Tile[x][y]))
402 // remove field that blocks receiver
403 int phase = Tile[x][y] - EL_RECEIVER_START;
404 int blocking_x, blocking_y;
406 blocking_x = x + xy[phase][0];
407 blocking_y = y + xy[phase][1];
409 if (IN_LEV_FIELD(blocking_x, blocking_y))
411 Tile[blocking_x][blocking_y] = EL_EMPTY;
413 DrawField_MM(blocking_x, blocking_y);
416 exit_element = EL_RECEIVER;
423 if (exit_element != EL_EMPTY)
424 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
427 static void SetLaserColor(int brightness)
429 int color_min = 0x00;
430 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
431 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
432 int color_down = color_max - color_up;
435 GetPixelFromRGB(window,
436 (game_mm.laser_red ? color_max : color_up),
437 (game_mm.laser_green ? color_down : color_min),
438 (game_mm.laser_blue ? color_down : color_min));
441 static void InitMovDir_MM(int x, int y)
443 int element = Tile[x][y];
444 static int direction[3][4] =
446 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
447 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
448 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
453 case EL_PACMAN_RIGHT:
457 Tile[x][y] = EL_PACMAN;
458 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
466 static void InitField(int x, int y)
468 int element = Tile[x][y];
473 Tile[x][y] = EL_EMPTY;
478 if (native_mm_level.auto_count_kettles)
479 game_mm.kettles_still_needed++;
482 case EL_LIGHTBULB_OFF:
483 game_mm.lights_still_needed++;
487 if (IS_MIRROR(element) ||
488 IS_BEAMER_OLD(element) ||
489 IS_BEAMER(element) ||
491 IS_POLAR_CROSS(element) ||
492 IS_DF_MIRROR(element) ||
493 IS_DF_MIRROR_AUTO(element) ||
494 IS_GRID_STEEL_AUTO(element) ||
495 IS_GRID_WOOD_AUTO(element) ||
496 IS_FIBRE_OPTIC(element))
498 if (IS_BEAMER_OLD(element))
500 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
501 element = Tile[x][y];
504 if (!IS_FIBRE_OPTIC(element))
506 static int steps_grid_auto = 0;
508 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
509 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
511 if (IS_GRID_STEEL_AUTO(element) ||
512 IS_GRID_WOOD_AUTO(element))
513 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
515 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
517 game_mm.cycle[game_mm.num_cycle].x = x;
518 game_mm.cycle[game_mm.num_cycle].y = y;
522 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
524 int beamer_nr = BEAMER_NR(element);
525 int nr = laser.beamer[beamer_nr][0].num;
527 laser.beamer[beamer_nr][nr].x = x;
528 laser.beamer[beamer_nr][nr].y = y;
529 laser.beamer[beamer_nr][nr].num = 1;
532 else if (IS_PACMAN(element))
536 else if (IS_MCDUFFIN(element) || IS_LASER(element))
538 laser.start_edge.x = x;
539 laser.start_edge.y = y;
540 laser.start_angle = get_element_angle(element);
542 if (IS_MCDUFFIN(element))
544 game_mm.laser_red = native_mm_level.mm_laser_red;
545 game_mm.laser_green = native_mm_level.mm_laser_green;
546 game_mm.laser_blue = native_mm_level.mm_laser_blue;
550 game_mm.laser_red = native_mm_level.df_laser_red;
551 game_mm.laser_green = native_mm_level.df_laser_green;
552 game_mm.laser_blue = native_mm_level.df_laser_blue;
560 static void InitCycleElements_RotateSingleStep(void)
564 if (game_mm.num_cycle == 0) // no elements to cycle
567 for (i = 0; i < game_mm.num_cycle; i++)
569 int x = game_mm.cycle[i].x;
570 int y = game_mm.cycle[i].y;
571 int step = SIGN(game_mm.cycle[i].steps);
572 int last_element = Tile[x][y];
573 int next_element = get_rotated_element(last_element, step);
575 if (!game_mm.cycle[i].steps)
578 Tile[x][y] = next_element;
580 game_mm.cycle[i].steps -= step;
584 static void InitLaser(void)
586 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
587 int step = (IS_LASER(start_element) ? 4 : 0);
589 LX = laser.start_edge.x * TILEX;
590 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
593 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
595 LY = laser.start_edge.y * TILEY;
596 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
597 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
601 XS = 2 * Step[laser.start_angle].x;
602 YS = 2 * Step[laser.start_angle].y;
604 laser.current_angle = laser.start_angle;
606 laser.num_damages = 0;
608 laser.num_beamers = 0;
609 laser.beamer_edge[0] = 0;
611 laser.dest_element = EL_EMPTY;
614 AddLaserEdge(LX, LY); // set laser starting edge
619 void InitGameEngine_MM(void)
625 // initialize laser bitmap to current playfield (screen) size
626 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
627 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
631 // set global game control values
632 game_mm.num_cycle = 0;
633 game_mm.num_pacman = 0;
636 game_mm.energy_left = 0; // later set to "native_mm_level.time"
637 game_mm.kettles_still_needed =
638 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
639 game_mm.lights_still_needed = 0;
640 game_mm.num_keys = 0;
641 game_mm.ball_choice_pos = 0;
643 game_mm.laser_red = FALSE;
644 game_mm.laser_green = FALSE;
645 game_mm.laser_blue = TRUE;
647 game_mm.level_solved = FALSE;
648 game_mm.game_over = FALSE;
649 game_mm.game_over_cause = 0;
651 game_mm.laser_overload_value = 0;
652 game_mm.laser_enabled = FALSE;
654 // set global laser control values (must be set before "InitLaser()")
655 laser.start_edge.x = 0;
656 laser.start_edge.y = 0;
657 laser.start_angle = 0;
659 for (i = 0; i < MAX_NUM_BEAMERS; i++)
660 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
662 laser.overloaded = FALSE;
663 laser.overload_value = 0;
664 laser.fuse_off = FALSE;
665 laser.fuse_x = laser.fuse_y = -1;
667 laser.dest_element = EL_EMPTY;
668 laser.dest_element_last = EL_EMPTY;
669 laser.dest_element_last_x = -1;
670 laser.dest_element_last_y = -1;
684 rotate_delay.count = 0;
685 pacman_delay.count = 0;
686 energy_delay.count = 0;
687 overload_delay.count = 0;
689 ClickElement(-1, -1, -1);
691 for (x = 0; x < lev_fieldx; x++)
693 for (y = 0; y < lev_fieldy; y++)
695 Tile[x][y] = Ur[x][y];
696 Hit[x][y] = Box[x][y] = 0;
698 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
699 Store[x][y] = Store2[x][y] = 0;
709 void InitGameActions_MM(void)
711 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
712 int cycle_steps_done = 0;
717 for (i = 0; i <= num_init_game_frames; i++)
719 if (i == num_init_game_frames)
720 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
721 else if (setup.sound_loops)
722 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
724 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
726 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
728 UpdateAndDisplayGameControlValues();
730 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
732 InitCycleElements_RotateSingleStep();
737 AdvanceFrameCounter();
747 if (setup.quick_doors)
754 if (game_mm.kettles_still_needed == 0)
757 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
758 SetTileCursorActive(TRUE);
760 // restart all delay counters after initially cycling game elements
761 ResetFrameCounter(&rotate_delay);
762 ResetFrameCounter(&pacman_delay);
763 ResetFrameCounter(&energy_delay);
764 ResetFrameCounter(&overload_delay);
767 static void FadeOutLaser(void)
771 for (i = 15; i >= 0; i--)
773 SetLaserColor(0x11 * i);
775 DrawLaser(0, DL_LASER_ENABLED);
778 Delay_WithScreenUpdates(50);
781 DrawLaser(0, DL_LASER_DISABLED);
783 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
786 static void GameOver_MM(int game_over_cause)
788 // do not handle game over if request dialog is already active
789 if (game.request_active)
792 game_mm.game_over = TRUE;
793 game_mm.game_over_cause = game_over_cause;
795 if (setup.ask_on_game_over)
796 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
797 "Bomb killed Mc Duffin! Play it again?" :
798 game_over_cause == GAME_OVER_NO_ENERGY ?
799 "Out of magic energy! Play it again?" :
800 game_over_cause == GAME_OVER_OVERLOADED ?
801 "Magic spell hit Mc Duffin! Play it again?" :
804 SetTileCursorActive(FALSE);
807 void AddLaserEdge(int lx, int ly)
812 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
814 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
819 laser.edge[laser.num_edges].x = cSX2 + lx;
820 laser.edge[laser.num_edges].y = cSY2 + ly;
826 void AddDamagedField(int ex, int ey)
828 // prevent adding the same field position again
829 if (laser.num_damages > 0 &&
830 laser.damage[laser.num_damages - 1].x == ex &&
831 laser.damage[laser.num_damages - 1].y == ey &&
832 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
835 laser.damage[laser.num_damages].is_mirror = FALSE;
836 laser.damage[laser.num_damages].angle = laser.current_angle;
837 laser.damage[laser.num_damages].edge = laser.num_edges;
838 laser.damage[laser.num_damages].x = ex;
839 laser.damage[laser.num_damages].y = ey;
843 static boolean StepBehind(void)
849 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
850 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
852 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
858 static int getMaskFromElement(int element)
860 if (IS_GRID(element))
861 return MM_MASK_GRID_1 + get_element_phase(element);
862 else if (IS_MCDUFFIN(element))
863 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
864 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
865 return MM_MASK_RECTANGLE;
867 return MM_MASK_CIRCLE;
870 static int ScanPixel(void)
875 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
876 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
879 // follow laser beam until it hits something (at least the screen border)
880 while (hit_mask == HIT_MASK_NO_HIT)
886 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
887 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
889 Debug("game:mm:ScanPixel", "touched screen border!");
895 for (i = 0; i < 4; i++)
897 int px = LX + (i % 2) * 2;
898 int py = LY + (i / 2) * 2;
901 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
902 int ly = (py + TILEY) / TILEY - 1; // negative values!
905 if (IN_LEV_FIELD(lx, ly))
907 int element = Tile[lx][ly];
909 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
913 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
915 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
917 pixel = ((element & (1 << pos)) ? 1 : 0);
921 int pos = getMaskFromElement(element);
923 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
928 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
929 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
932 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
933 hit_mask |= (1 << i);
936 if (hit_mask == HIT_MASK_NO_HIT)
938 // hit nothing -- go on with another step
947 static void DeactivateLaserTargetElement(void)
949 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
950 laser.dest_element_last == EL_MINE_ACTIVE ||
951 laser.dest_element_last == EL_GRAY_BALL_OPENING)
953 int x = laser.dest_element_last_x;
954 int y = laser.dest_element_last_y;
955 int element = laser.dest_element_last;
957 if (Tile[x][y] == element)
958 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
959 element == EL_MINE_ACTIVE ? EL_MINE : EL_BALL_GRAY);
961 if (Tile[x][y] == EL_BALL_GRAY)
964 laser.dest_element_last = EL_EMPTY;
965 laser.dest_element_last_x = -1;
966 laser.dest_element_last_y = -1;
972 int element = EL_EMPTY;
973 int last_element = EL_EMPTY;
974 int end = 0, rf = laser.num_edges;
976 // do not scan laser again after the game was lost for whatever reason
977 if (game_mm.game_over)
980 // do not scan laser if fuse is off
984 DeactivateLaserTargetElement();
986 laser.overloaded = FALSE;
987 laser.stops_inside_element = FALSE;
989 DrawLaser(0, DL_LASER_ENABLED);
992 Debug("game:mm:ScanLaser",
993 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1001 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1004 laser.overloaded = TRUE;
1009 hit_mask = ScanPixel();
1012 Debug("game:mm:ScanLaser",
1013 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1017 // hit something -- check out what it was
1018 ELX = (LX + XS) / TILEX;
1019 ELY = (LY + YS) / TILEY;
1022 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1023 hit_mask, LX, LY, ELX, ELY);
1026 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1029 laser.dest_element = element;
1034 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1036 /* we have hit the top-right and bottom-left element --
1037 choose the bottom-left one */
1038 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1039 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1040 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1041 ELX = (LX - 2) / TILEX;
1042 ELY = (LY + 2) / TILEY;
1045 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1047 /* we have hit the top-left and bottom-right element --
1048 choose the top-left one */
1049 // !!! SEE ABOVE !!!
1050 ELX = (LX - 2) / TILEX;
1051 ELY = (LY - 2) / TILEY;
1055 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1056 hit_mask, LX, LY, ELX, ELY);
1059 last_element = element;
1061 element = Tile[ELX][ELY];
1062 laser.dest_element = element;
1065 Debug("game:mm:ScanLaser",
1066 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1069 LX % TILEX, LY % TILEY,
1074 if (!IN_LEV_FIELD(ELX, ELY))
1075 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1079 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1080 if (element == EL_EMPTY &&
1081 IS_GRID_STEEL(last_element) &&
1082 laser.current_angle % 4) // angle is not 90°
1083 element = last_element;
1085 if (element == EL_EMPTY)
1087 if (!HitOnlyAnEdge(hit_mask))
1090 else if (element == EL_FUSE_ON)
1092 if (HitPolarizer(element, hit_mask))
1095 else if (IS_GRID(element) || IS_DF_GRID(element))
1097 if (HitPolarizer(element, hit_mask))
1100 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1101 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1103 if (HitBlock(element, hit_mask))
1110 else if (IS_MCDUFFIN(element))
1112 if (HitLaserSource(element, hit_mask))
1115 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1116 IS_RECEIVER(element))
1118 if (HitLaserDestination(element, hit_mask))
1121 else if (IS_WALL(element))
1123 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1125 if (HitReflectingWalls(element, hit_mask))
1130 if (HitAbsorbingWalls(element, hit_mask))
1136 if (HitElement(element, hit_mask))
1141 DrawLaser(rf - 1, DL_LASER_ENABLED);
1142 rf = laser.num_edges;
1144 if (!IS_DF_WALL_STEEL(element))
1146 // only used for scanning DF steel walls; reset for all other elements
1154 if (laser.dest_element != Tile[ELX][ELY])
1156 Debug("game:mm:ScanLaser",
1157 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1158 laser.dest_element, Tile[ELX][ELY]);
1162 if (!end && !laser.stops_inside_element && !StepBehind())
1165 Debug("game:mm:ScanLaser", "Go one step back");
1171 AddLaserEdge(LX, LY);
1175 DrawLaser(rf - 1, DL_LASER_ENABLED);
1177 Ct = CT = FrameCounter;
1180 if (!IN_LEV_FIELD(ELX, ELY))
1181 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1185 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1191 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1192 start_edge, num_edges, mode);
1197 Warn("DrawLaserExt: start_edge < 0");
1204 Warn("DrawLaserExt: num_edges < 0");
1210 if (mode == DL_LASER_DISABLED)
1212 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1216 // now draw the laser to the backbuffer and (if enabled) to the screen
1217 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1219 redraw_mask |= REDRAW_FIELD;
1221 if (mode == DL_LASER_ENABLED)
1224 // after the laser was deleted, the "damaged" graphics must be restored
1225 if (laser.num_damages)
1227 int damage_start = 0;
1230 // determine the starting edge, from which graphics need to be restored
1233 for (i = 0; i < laser.num_damages; i++)
1235 if (laser.damage[i].edge == start_edge + 1)
1244 // restore graphics from this starting edge to the end of damage list
1245 for (i = damage_start; i < laser.num_damages; i++)
1247 int lx = laser.damage[i].x;
1248 int ly = laser.damage[i].y;
1249 int element = Tile[lx][ly];
1251 if (Hit[lx][ly] == laser.damage[i].edge)
1252 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1255 if (Box[lx][ly] == laser.damage[i].edge)
1258 if (IS_DRAWABLE(element))
1259 DrawField_MM(lx, ly);
1262 elx = laser.damage[damage_start].x;
1263 ely = laser.damage[damage_start].y;
1264 element = Tile[elx][ely];
1267 if (IS_BEAMER(element))
1271 for (i = 0; i < laser.num_beamers; i++)
1272 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1274 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1275 mode, elx, ely, Hit[elx][ely], start_edge);
1276 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1277 get_element_angle(element), laser.damage[damage_start].angle);
1281 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1282 laser.num_beamers > 0 &&
1283 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1285 // element is outgoing beamer
1286 laser.num_damages = damage_start + 1;
1288 if (IS_BEAMER(element))
1289 laser.current_angle = get_element_angle(element);
1293 // element is incoming beamer or other element
1294 laser.num_damages = damage_start;
1295 laser.current_angle = laser.damage[laser.num_damages].angle;
1300 // no damages but McDuffin himself (who needs to be redrawn anyway)
1302 elx = laser.start_edge.x;
1303 ely = laser.start_edge.y;
1304 element = Tile[elx][ely];
1307 laser.num_edges = start_edge + 1;
1308 if (start_edge == 0)
1309 laser.current_angle = laser.start_angle;
1311 LX = laser.edge[start_edge].x - cSX2;
1312 LY = laser.edge[start_edge].y - cSY2;
1313 XS = 2 * Step[laser.current_angle].x;
1314 YS = 2 * Step[laser.current_angle].y;
1317 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1323 if (IS_BEAMER(element) ||
1324 IS_FIBRE_OPTIC(element) ||
1325 IS_PACMAN(element) ||
1326 IS_POLAR(element) ||
1327 IS_POLAR_CROSS(element) ||
1328 element == EL_FUSE_ON)
1333 Debug("game:mm:DrawLaserExt", "element == %d", element);
1336 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1337 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1341 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1342 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1343 (laser.num_beamers == 0 ||
1344 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1346 // element is incoming beamer or other element
1347 step_size = -step_size;
1352 if (IS_BEAMER(element))
1353 Debug("game:mm:DrawLaserExt",
1354 "start_edge == %d, laser.beamer_edge == %d",
1355 start_edge, laser.beamer_edge);
1358 LX += step_size * XS;
1359 LY += step_size * YS;
1361 else if (element != EL_EMPTY)
1370 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1375 void DrawLaser(int start_edge, int mode)
1377 // do not draw laser if fuse is off
1378 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1381 if (mode == DL_LASER_DISABLED)
1382 DeactivateLaserTargetElement();
1384 if (laser.num_edges - start_edge < 0)
1386 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1391 // check if laser is interrupted by beamer element
1392 if (laser.num_beamers > 0 &&
1393 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1395 if (mode == DL_LASER_ENABLED)
1398 int tmp_start_edge = start_edge;
1400 // draw laser segments forward from the start to the last beamer
1401 for (i = 0; i < laser.num_beamers; i++)
1403 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1405 if (tmp_num_edges <= 0)
1409 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1410 i, laser.beamer_edge[i], tmp_start_edge);
1413 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1415 tmp_start_edge = laser.beamer_edge[i];
1418 // draw last segment from last beamer to the end
1419 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1425 int last_num_edges = laser.num_edges;
1426 int num_beamers = laser.num_beamers;
1428 // delete laser segments backward from the end to the first beamer
1429 for (i = num_beamers - 1; i >= 0; i--)
1431 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1433 if (laser.beamer_edge[i] - start_edge <= 0)
1436 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1438 last_num_edges = laser.beamer_edge[i];
1439 laser.num_beamers--;
1443 if (last_num_edges - start_edge <= 0)
1444 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1445 last_num_edges, start_edge);
1448 // special case when rotating first beamer: delete laser edge on beamer
1449 // (but do not start scanning on previous edge to prevent mirror sound)
1450 if (last_num_edges - start_edge == 1 && start_edge > 0)
1451 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1453 // delete first segment from start to the first beamer
1454 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1459 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1462 game_mm.laser_enabled = mode;
1465 void DrawLaser_MM(void)
1467 DrawLaser(0, game_mm.laser_enabled);
1470 boolean HitElement(int element, int hit_mask)
1472 if (HitOnlyAnEdge(hit_mask))
1475 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1476 element = MovingOrBlocked2Element_MM(ELX, ELY);
1479 Debug("game:mm:HitElement", "(1): element == %d", element);
1483 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1484 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1487 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1491 AddDamagedField(ELX, ELY);
1493 // this is more precise: check if laser would go through the center
1494 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1496 // prevent cutting through laser emitter with laser beam
1497 if (IS_LASER(element))
1500 // skip the whole element before continuing the scan
1506 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1508 if (LX/TILEX > ELX || LY/TILEY > ELY)
1510 /* skipping scan positions to the right and down skips one scan
1511 position too much, because this is only the top left scan position
1512 of totally four scan positions (plus one to the right, one to the
1513 bottom and one to the bottom right) */
1523 Debug("game:mm:HitElement", "(2): element == %d", element);
1526 if (LX + 5 * XS < 0 ||
1536 Debug("game:mm:HitElement", "(3): element == %d", element);
1539 if (IS_POLAR(element) &&
1540 ((element - EL_POLAR_START) % 2 ||
1541 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1543 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1545 laser.num_damages--;
1550 if (IS_POLAR_CROSS(element) &&
1551 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1553 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1555 laser.num_damages--;
1560 if (!IS_BEAMER(element) &&
1561 !IS_FIBRE_OPTIC(element) &&
1562 !IS_GRID_WOOD(element) &&
1563 element != EL_FUEL_EMPTY)
1566 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1567 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1569 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1572 LX = ELX * TILEX + 14;
1573 LY = ELY * TILEY + 14;
1575 AddLaserEdge(LX, LY);
1578 if (IS_MIRROR(element) ||
1579 IS_MIRROR_FIXED(element) ||
1580 IS_POLAR(element) ||
1581 IS_POLAR_CROSS(element) ||
1582 IS_DF_MIRROR(element) ||
1583 IS_DF_MIRROR_AUTO(element) ||
1584 element == EL_PRISM ||
1585 element == EL_REFRACTOR)
1587 int current_angle = laser.current_angle;
1590 laser.num_damages--;
1592 AddDamagedField(ELX, ELY);
1594 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1597 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1599 if (IS_MIRROR(element) ||
1600 IS_MIRROR_FIXED(element) ||
1601 IS_DF_MIRROR(element) ||
1602 IS_DF_MIRROR_AUTO(element))
1603 laser.current_angle = get_mirrored_angle(laser.current_angle,
1604 get_element_angle(element));
1606 if (element == EL_PRISM || element == EL_REFRACTOR)
1607 laser.current_angle = RND(16);
1609 XS = 2 * Step[laser.current_angle].x;
1610 YS = 2 * Step[laser.current_angle].y;
1612 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1617 LX += step_size * XS;
1618 LY += step_size * YS;
1620 // draw sparkles on mirror
1621 if ((IS_MIRROR(element) ||
1622 IS_MIRROR_FIXED(element) ||
1623 element == EL_PRISM) &&
1624 current_angle != laser.current_angle)
1626 MovDelay[ELX][ELY] = 11; // start animation
1629 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1630 current_angle != laser.current_angle)
1631 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1634 (get_opposite_angle(laser.current_angle) ==
1635 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1637 return (laser.overloaded ? TRUE : FALSE);
1640 if (element == EL_FUEL_FULL)
1642 laser.stops_inside_element = TRUE;
1647 if (element == EL_BOMB || element == EL_MINE)
1649 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1651 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1653 laser.dest_element_last = Tile[ELX][ELY];
1654 laser.dest_element_last_x = ELX;
1655 laser.dest_element_last_y = ELY;
1657 if (element == EL_MINE)
1658 laser.overloaded = TRUE;
1661 if (element == EL_KETTLE ||
1662 element == EL_CELL ||
1663 element == EL_KEY ||
1664 element == EL_LIGHTBALL ||
1665 element == EL_PACMAN ||
1666 IS_PACMAN(element) ||
1667 IS_ENVELOPE(element))
1669 if (!IS_PACMAN(element) &&
1670 !IS_ENVELOPE(element))
1673 if (element == EL_PACMAN)
1676 if (element == EL_KETTLE || element == EL_CELL)
1678 if (game_mm.kettles_still_needed > 0)
1679 game_mm.kettles_still_needed--;
1681 game.snapshot.collected_item = TRUE;
1683 if (game_mm.kettles_still_needed == 0)
1687 DrawLaser(0, DL_LASER_ENABLED);
1690 else if (element == EL_KEY)
1694 else if (IS_PACMAN(element))
1696 DeletePacMan(ELX, ELY);
1698 else if (IS_ENVELOPE(element))
1700 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1703 RaiseScoreElement_MM(element);
1708 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1710 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1712 DrawLaser(0, DL_LASER_ENABLED);
1714 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1716 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1717 game_mm.lights_still_needed--;
1721 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1722 game_mm.lights_still_needed++;
1725 DrawField_MM(ELX, ELY);
1726 DrawLaser(0, DL_LASER_ENABLED);
1731 laser.stops_inside_element = TRUE;
1737 Debug("game:mm:HitElement", "(4): element == %d", element);
1740 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1741 laser.num_beamers < MAX_NUM_BEAMERS &&
1742 laser.beamer[BEAMER_NR(element)][1].num)
1744 int beamer_angle = get_element_angle(element);
1745 int beamer_nr = BEAMER_NR(element);
1749 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1752 laser.num_damages--;
1754 if (IS_FIBRE_OPTIC(element) ||
1755 laser.current_angle == get_opposite_angle(beamer_angle))
1759 LX = ELX * TILEX + 14;
1760 LY = ELY * TILEY + 14;
1762 AddLaserEdge(LX, LY);
1763 AddDamagedField(ELX, ELY);
1765 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1768 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1770 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1771 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1772 ELX = laser.beamer[beamer_nr][pos].x;
1773 ELY = laser.beamer[beamer_nr][pos].y;
1774 LX = ELX * TILEX + 14;
1775 LY = ELY * TILEY + 14;
1777 if (IS_BEAMER(element))
1779 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1780 XS = 2 * Step[laser.current_angle].x;
1781 YS = 2 * Step[laser.current_angle].y;
1784 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1786 AddLaserEdge(LX, LY);
1787 AddDamagedField(ELX, ELY);
1789 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1792 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1794 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1799 LX += step_size * XS;
1800 LY += step_size * YS;
1802 laser.num_beamers++;
1811 boolean HitOnlyAnEdge(int hit_mask)
1813 // check if the laser hit only the edge of an element and, if so, go on
1816 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1820 if ((hit_mask == HIT_MASK_TOPLEFT ||
1821 hit_mask == HIT_MASK_TOPRIGHT ||
1822 hit_mask == HIT_MASK_BOTTOMLEFT ||
1823 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1824 laser.current_angle % 4) // angle is not 90°
1828 if (hit_mask == HIT_MASK_TOPLEFT)
1833 else if (hit_mask == HIT_MASK_TOPRIGHT)
1838 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1843 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1849 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1855 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1862 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1868 boolean HitPolarizer(int element, int hit_mask)
1870 if (HitOnlyAnEdge(hit_mask))
1873 if (IS_DF_GRID(element))
1875 int grid_angle = get_element_angle(element);
1878 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1879 grid_angle, laser.current_angle);
1882 AddLaserEdge(LX, LY);
1883 AddDamagedField(ELX, ELY);
1886 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1888 if (laser.current_angle == grid_angle ||
1889 laser.current_angle == get_opposite_angle(grid_angle))
1891 // skip the whole element before continuing the scan
1897 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1899 if (LX/TILEX > ELX || LY/TILEY > ELY)
1901 /* skipping scan positions to the right and down skips one scan
1902 position too much, because this is only the top left scan position
1903 of totally four scan positions (plus one to the right, one to the
1904 bottom and one to the bottom right) */
1910 AddLaserEdge(LX, LY);
1916 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1918 LX / TILEX, LY / TILEY,
1919 LX % TILEX, LY % TILEY);
1924 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1926 return HitReflectingWalls(element, hit_mask);
1930 return HitAbsorbingWalls(element, hit_mask);
1933 else if (IS_GRID_STEEL(element))
1935 return HitReflectingWalls(element, hit_mask);
1937 else // IS_GRID_WOOD
1939 return HitAbsorbingWalls(element, hit_mask);
1945 boolean HitBlock(int element, int hit_mask)
1947 boolean check = FALSE;
1949 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1950 game_mm.num_keys == 0)
1953 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1956 int ex = ELX * TILEX + 14;
1957 int ey = ELY * TILEY + 14;
1961 for (i = 1; i < 32; i++)
1966 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1971 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1972 return HitAbsorbingWalls(element, hit_mask);
1976 AddLaserEdge(LX - XS, LY - YS);
1977 AddDamagedField(ELX, ELY);
1980 Box[ELX][ELY] = laser.num_edges;
1982 return HitReflectingWalls(element, hit_mask);
1985 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1987 int xs = XS / 2, ys = YS / 2;
1988 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1989 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1991 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1992 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1994 laser.overloaded = (element == EL_GATE_STONE);
1999 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2000 (hit_mask == HIT_MASK_TOP ||
2001 hit_mask == HIT_MASK_LEFT ||
2002 hit_mask == HIT_MASK_RIGHT ||
2003 hit_mask == HIT_MASK_BOTTOM))
2004 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2005 hit_mask == HIT_MASK_BOTTOM),
2006 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2007 hit_mask == HIT_MASK_RIGHT));
2008 AddLaserEdge(LX, LY);
2014 if (element == EL_GATE_STONE && Box[ELX][ELY])
2016 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2028 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2030 int xs = XS / 2, ys = YS / 2;
2031 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2032 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2034 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2035 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2037 laser.overloaded = (element == EL_BLOCK_STONE);
2042 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2043 (hit_mask == HIT_MASK_TOP ||
2044 hit_mask == HIT_MASK_LEFT ||
2045 hit_mask == HIT_MASK_RIGHT ||
2046 hit_mask == HIT_MASK_BOTTOM))
2047 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2048 hit_mask == HIT_MASK_BOTTOM),
2049 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2050 hit_mask == HIT_MASK_RIGHT));
2051 AddDamagedField(ELX, ELY);
2053 LX = ELX * TILEX + 14;
2054 LY = ELY * TILEY + 14;
2056 AddLaserEdge(LX, LY);
2058 laser.stops_inside_element = TRUE;
2066 boolean HitLaserSource(int element, int hit_mask)
2068 if (HitOnlyAnEdge(hit_mask))
2071 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2073 laser.overloaded = TRUE;
2078 boolean HitLaserDestination(int element, int hit_mask)
2080 if (HitOnlyAnEdge(hit_mask))
2083 if (element != EL_EXIT_OPEN &&
2084 !(IS_RECEIVER(element) &&
2085 game_mm.kettles_still_needed == 0 &&
2086 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2088 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2093 if (IS_RECEIVER(element) ||
2094 (IS_22_5_ANGLE(laser.current_angle) &&
2095 (ELX != (LX + 6 * XS) / TILEX ||
2096 ELY != (LY + 6 * YS) / TILEY ||
2105 LX = ELX * TILEX + 14;
2106 LY = ELY * TILEY + 14;
2108 laser.stops_inside_element = TRUE;
2111 AddLaserEdge(LX, LY);
2112 AddDamagedField(ELX, ELY);
2114 if (game_mm.lights_still_needed == 0)
2116 game_mm.level_solved = TRUE;
2118 SetTileCursorActive(FALSE);
2124 boolean HitReflectingWalls(int element, int hit_mask)
2126 // check if laser hits side of a wall with an angle that is not 90°
2127 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2128 hit_mask == HIT_MASK_LEFT ||
2129 hit_mask == HIT_MASK_RIGHT ||
2130 hit_mask == HIT_MASK_BOTTOM))
2132 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2137 if (!IS_DF_GRID(element))
2138 AddLaserEdge(LX, LY);
2140 // check if laser hits wall with an angle of 45°
2141 if (!IS_22_5_ANGLE(laser.current_angle))
2143 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2146 laser.current_angle = get_mirrored_angle(laser.current_angle,
2149 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2152 laser.current_angle = get_mirrored_angle(laser.current_angle,
2156 AddLaserEdge(LX, LY);
2158 XS = 2 * Step[laser.current_angle].x;
2159 YS = 2 * Step[laser.current_angle].y;
2163 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2165 laser.current_angle = get_mirrored_angle(laser.current_angle,
2170 if (!IS_DF_GRID(element))
2171 AddLaserEdge(LX, LY);
2176 if (!IS_DF_GRID(element))
2177 AddLaserEdge(LX, LY + YS / 2);
2180 if (!IS_DF_GRID(element))
2181 AddLaserEdge(LX, LY);
2184 YS = 2 * Step[laser.current_angle].y;
2188 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2190 laser.current_angle = get_mirrored_angle(laser.current_angle,
2195 if (!IS_DF_GRID(element))
2196 AddLaserEdge(LX, LY);
2201 if (!IS_DF_GRID(element))
2202 AddLaserEdge(LX + XS / 2, LY);
2205 if (!IS_DF_GRID(element))
2206 AddLaserEdge(LX, LY);
2209 XS = 2 * Step[laser.current_angle].x;
2215 // reflection at the edge of reflecting DF style wall
2216 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2218 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2219 hit_mask == HIT_MASK_TOPRIGHT) ||
2220 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2221 hit_mask == HIT_MASK_TOPLEFT) ||
2222 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2223 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2224 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2225 hit_mask == HIT_MASK_BOTTOMRIGHT))
2228 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2229 ANG_MIRROR_135 : ANG_MIRROR_45);
2231 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2233 AddDamagedField(ELX, ELY);
2234 AddLaserEdge(LX, LY);
2236 laser.current_angle = get_mirrored_angle(laser.current_angle,
2244 AddLaserEdge(LX, LY);
2250 // reflection inside an edge of reflecting DF style wall
2251 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2253 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2254 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2255 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2256 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2257 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2258 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2259 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2260 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2263 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2264 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2265 ANG_MIRROR_135 : ANG_MIRROR_45);
2267 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2270 AddDamagedField(ELX, ELY);
2273 AddLaserEdge(LX - XS, LY - YS);
2274 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2275 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2277 laser.current_angle = get_mirrored_angle(laser.current_angle,
2285 AddLaserEdge(LX, LY);
2291 // check if laser hits DF style wall with an angle of 90°
2292 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2294 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2295 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2296 (IS_VERT_ANGLE(laser.current_angle) &&
2297 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2299 // laser at last step touched nothing or the same side of the wall
2300 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2302 AddDamagedField(ELX, ELY);
2309 last_hit_mask = hit_mask;
2316 if (!HitOnlyAnEdge(hit_mask))
2318 laser.overloaded = TRUE;
2326 boolean HitAbsorbingWalls(int element, int hit_mask)
2328 if (HitOnlyAnEdge(hit_mask))
2332 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2334 AddLaserEdge(LX - XS, LY - YS);
2341 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2343 AddLaserEdge(LX - XS, LY - YS);
2349 if (IS_WALL_WOOD(element) ||
2350 IS_DF_WALL_WOOD(element) ||
2351 IS_GRID_WOOD(element) ||
2352 IS_GRID_WOOD_FIXED(element) ||
2353 IS_GRID_WOOD_AUTO(element) ||
2354 element == EL_FUSE_ON ||
2355 element == EL_BLOCK_WOOD ||
2356 element == EL_GATE_WOOD)
2358 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2363 if (IS_WALL_ICE(element))
2367 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2368 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2370 // check if laser hits wall with an angle of 90°
2371 if (IS_90_ANGLE(laser.current_angle))
2372 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2374 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2378 for (i = 0; i < 4; i++)
2380 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2381 mask = 15 - (8 >> i);
2382 else if (ABS(XS) == 4 &&
2384 (XS > 0) == (i % 2) &&
2385 (YS < 0) == (i / 2))
2386 mask = 3 + (i / 2) * 9;
2387 else if (ABS(YS) == 4 &&
2389 (XS < 0) == (i % 2) &&
2390 (YS > 0) == (i / 2))
2391 mask = 5 + (i % 2) * 5;
2395 laser.wall_mask = mask;
2397 else if (IS_WALL_AMOEBA(element))
2399 int elx = (LX - 2 * XS) / TILEX;
2400 int ely = (LY - 2 * YS) / TILEY;
2401 int element2 = Tile[elx][ely];
2404 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2406 laser.dest_element = EL_EMPTY;
2414 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2415 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2417 if (IS_90_ANGLE(laser.current_angle))
2418 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2420 laser.dest_element = element2 | EL_WALL_AMOEBA;
2422 laser.wall_mask = mask;
2428 static void OpenExit(int x, int y)
2432 if (!MovDelay[x][y]) // next animation frame
2433 MovDelay[x][y] = 4 * delay;
2435 if (MovDelay[x][y]) // wait some time before next frame
2440 phase = MovDelay[x][y] / delay;
2442 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2443 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2445 if (!MovDelay[x][y])
2447 Tile[x][y] = EL_EXIT_OPEN;
2453 static void OpenSurpriseBall(int x, int y)
2457 if (!MovDelay[x][y]) // next animation frame
2458 MovDelay[x][y] = 50 * delay;
2460 if (MovDelay[x][y]) // wait some time before next frame
2464 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2467 int graphic = el2gfx(Store[x][y]);
2469 int dx = RND(26), dy = RND(26);
2471 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2473 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2474 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2476 laser.redraw = TRUE;
2478 MarkTileDirty(x, y);
2481 if (!MovDelay[x][y])
2483 Tile[x][y] = Store[x][y];
2484 Store[x][y] = Store2[x][y] = 0;
2493 static void OpenEnvelope(int x, int y)
2495 int num_frames = 8; // seven frames plus final empty space
2497 if (!MovDelay[x][y]) // next animation frame
2498 MovDelay[x][y] = num_frames;
2500 if (MovDelay[x][y]) // wait some time before next frame
2502 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2506 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2508 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2509 int frame = num_frames - MovDelay[x][y] - 1;
2511 DrawGraphicAnimation_MM(x, y, graphic, frame);
2513 laser.redraw = TRUE;
2516 if (MovDelay[x][y] == 0)
2518 Tile[x][y] = EL_EMPTY;
2524 ShowEnvelope_MM(nr);
2529 static void MeltIce(int x, int y)
2534 if (!MovDelay[x][y]) // next animation frame
2535 MovDelay[x][y] = frames * delay;
2537 if (MovDelay[x][y]) // wait some time before next frame
2540 int wall_mask = Store2[x][y];
2541 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2544 phase = frames - MovDelay[x][y] / delay - 1;
2546 if (!MovDelay[x][y])
2550 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2551 Store[x][y] = Store2[x][y] = 0;
2553 DrawWalls_MM(x, y, Tile[x][y]);
2555 if (Tile[x][y] == EL_WALL_ICE)
2556 Tile[x][y] = EL_EMPTY;
2558 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2559 if (laser.damage[i].is_mirror)
2563 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2565 DrawLaser(0, DL_LASER_DISABLED);
2569 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2571 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2573 laser.redraw = TRUE;
2578 static void GrowAmoeba(int x, int y)
2583 if (!MovDelay[x][y]) // next animation frame
2584 MovDelay[x][y] = frames * delay;
2586 if (MovDelay[x][y]) // wait some time before next frame
2589 int wall_mask = Store2[x][y];
2590 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2593 phase = MovDelay[x][y] / delay;
2595 if (!MovDelay[x][y])
2597 Tile[x][y] = real_element;
2598 Store[x][y] = Store2[x][y] = 0;
2600 DrawWalls_MM(x, y, Tile[x][y]);
2601 DrawLaser(0, DL_LASER_ENABLED);
2603 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2605 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2610 static void DrawFieldAnimated_MM(int x, int y)
2614 laser.redraw = TRUE;
2617 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2619 int element = Tile[x][y];
2620 int graphic = el2gfx(element);
2622 if (!getGraphicInfo_NewFrame(x, y, graphic))
2627 laser.redraw = TRUE;
2630 static void DrawFieldTwinkle(int x, int y)
2632 if (MovDelay[x][y] != 0) // wait some time before next frame
2638 if (MovDelay[x][y] != 0)
2640 int graphic = IMG_TWINKLE_WHITE;
2641 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2643 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2646 laser.redraw = TRUE;
2650 static void Explode_MM(int x, int y, int phase, int mode)
2652 int num_phase = 9, delay = 2;
2653 int last_phase = num_phase * delay;
2654 int half_phase = (num_phase / 2) * delay;
2656 laser.redraw = TRUE;
2658 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2660 int center_element = Tile[x][y];
2662 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2664 // put moving element to center field (and let it explode there)
2665 center_element = MovingOrBlocked2Element_MM(x, y);
2666 RemoveMovingField_MM(x, y);
2668 Tile[x][y] = center_element;
2671 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2672 Store[x][y] = center_element;
2674 Store[x][y] = EL_EMPTY;
2676 Store2[x][y] = mode;
2678 Tile[x][y] = EL_EXPLODING_OPAQUE;
2679 GfxElement[x][y] = center_element;
2681 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2683 ExplodePhase[x][y] = 1;
2689 GfxFrame[x][y] = 0; // restart explosion animation
2691 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2693 if (phase == half_phase)
2695 Tile[x][y] = EL_EXPLODING_TRANSP;
2697 if (x == ELX && y == ELY)
2701 if (phase == last_phase)
2703 if (Store[x][y] == EL_BOMB_ACTIVE)
2705 DrawLaser(0, DL_LASER_DISABLED);
2708 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2709 Store[x][y] = EL_EMPTY;
2711 GameOver_MM(GAME_OVER_DELAYED);
2713 laser.overloaded = FALSE;
2715 else if (IS_MCDUFFIN(Store[x][y]))
2717 Store[x][y] = EL_EMPTY;
2719 GameOver_MM(GAME_OVER_BOMB);
2722 Tile[x][y] = Store[x][y];
2723 Store[x][y] = Store2[x][y] = 0;
2724 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2729 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2731 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2732 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2734 DrawGraphicAnimation_MM(x, y, graphic, frame);
2736 MarkTileDirty(x, y);
2740 static void Bang_MM(int x, int y)
2742 int element = Tile[x][y];
2745 DrawLaser(0, DL_LASER_ENABLED);
2748 if (IS_PACMAN(element))
2749 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2750 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2751 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2752 else if (element == EL_KEY)
2753 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2755 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2757 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2760 void TurnRound(int x, int y)
2772 { 0, 0 }, { 0, 0 }, { 0, 0 },
2777 int left, right, back;
2781 { MV_DOWN, MV_UP, MV_RIGHT },
2782 { MV_UP, MV_DOWN, MV_LEFT },
2784 { MV_LEFT, MV_RIGHT, MV_DOWN },
2785 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2786 { MV_RIGHT, MV_LEFT, MV_UP }
2789 int element = Tile[x][y];
2790 int old_move_dir = MovDir[x][y];
2791 int right_dir = turn[old_move_dir].right;
2792 int back_dir = turn[old_move_dir].back;
2793 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2794 int right_x = x + right_dx, right_y = y + right_dy;
2796 if (element == EL_PACMAN)
2798 boolean can_turn_right = FALSE;
2800 if (IN_LEV_FIELD(right_x, right_y) &&
2801 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2802 can_turn_right = TRUE;
2805 MovDir[x][y] = right_dir;
2807 MovDir[x][y] = back_dir;
2813 static void StartMoving_MM(int x, int y)
2815 int element = Tile[x][y];
2820 if (CAN_MOVE(element))
2824 if (MovDelay[x][y]) // wait some time before next movement
2832 // now make next step
2834 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2836 if (element == EL_PACMAN &&
2837 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2838 !ObjHit(newx, newy, HIT_POS_CENTER))
2840 Store[newx][newy] = Tile[newx][newy];
2841 Tile[newx][newy] = EL_EMPTY;
2843 DrawField_MM(newx, newy);
2845 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2846 ObjHit(newx, newy, HIT_POS_CENTER))
2848 // object was running against a wall
2855 InitMovingField_MM(x, y, MovDir[x][y]);
2859 ContinueMoving_MM(x, y);
2862 static void ContinueMoving_MM(int x, int y)
2864 int element = Tile[x][y];
2865 int direction = MovDir[x][y];
2866 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2867 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2868 int horiz_move = (dx!=0);
2869 int newx = x + dx, newy = y + dy;
2870 int step = (horiz_move ? dx : dy) * TILEX / 8;
2872 MovPos[x][y] += step;
2874 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2876 Tile[x][y] = EL_EMPTY;
2877 Tile[newx][newy] = element;
2879 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2880 MovDelay[newx][newy] = 0;
2882 if (!CAN_MOVE(element))
2883 MovDir[newx][newy] = 0;
2886 DrawField_MM(newx, newy);
2888 Stop[newx][newy] = TRUE;
2890 if (element == EL_PACMAN)
2892 if (Store[newx][newy] == EL_BOMB)
2893 Bang_MM(newx, newy);
2895 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2896 (LX + 2 * XS) / TILEX == newx &&
2897 (LY + 2 * YS) / TILEY == newy)
2904 else // still moving on
2909 laser.redraw = TRUE;
2912 boolean ClickElement(int x, int y, int button)
2914 static DelayCounter click_delay = { CLICK_DELAY };
2915 static boolean new_button = TRUE;
2916 boolean element_clicked = FALSE;
2921 // initialize static variables
2922 click_delay.count = 0;
2923 click_delay.value = CLICK_DELAY;
2929 // do not rotate objects hit by the laser after the game was solved
2930 if (game_mm.level_solved && Hit[x][y])
2933 if (button == MB_RELEASED)
2936 click_delay.value = CLICK_DELAY;
2938 // release eventually hold auto-rotating mirror
2939 RotateMirror(x, y, MB_RELEASED);
2944 if (!FrameReached(&click_delay) && !new_button)
2947 if (button == MB_MIDDLEBUTTON) // middle button has no function
2950 if (!IN_LEV_FIELD(x, y))
2953 if (Tile[x][y] == EL_EMPTY)
2956 element = Tile[x][y];
2958 if (IS_MIRROR(element) ||
2959 IS_BEAMER(element) ||
2960 IS_POLAR(element) ||
2961 IS_POLAR_CROSS(element) ||
2962 IS_DF_MIRROR(element) ||
2963 IS_DF_MIRROR_AUTO(element))
2965 RotateMirror(x, y, button);
2967 element_clicked = TRUE;
2969 else if (IS_MCDUFFIN(element))
2971 if (!laser.fuse_off)
2973 DrawLaser(0, DL_LASER_DISABLED);
2980 element = get_rotated_element(element, BUTTON_ROTATION(button));
2981 laser.start_angle = get_element_angle(element);
2985 Tile[x][y] = element;
2992 if (!laser.fuse_off)
2995 element_clicked = TRUE;
2997 else if (element == EL_FUSE_ON && laser.fuse_off)
2999 if (x != laser.fuse_x || y != laser.fuse_y)
3002 laser.fuse_off = FALSE;
3003 laser.fuse_x = laser.fuse_y = -1;
3005 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3008 element_clicked = TRUE;
3010 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3012 laser.fuse_off = TRUE;
3015 laser.overloaded = FALSE;
3017 DrawLaser(0, DL_LASER_DISABLED);
3018 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3020 element_clicked = TRUE;
3022 else if (element == EL_LIGHTBALL)
3025 RaiseScoreElement_MM(element);
3026 DrawLaser(0, DL_LASER_ENABLED);
3028 element_clicked = TRUE;
3030 else if (IS_ENVELOPE(element))
3032 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3034 element_clicked = TRUE;
3037 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3040 return element_clicked;
3043 void RotateMirror(int x, int y, int button)
3045 if (button == MB_RELEASED)
3047 // release eventually hold auto-rotating mirror
3054 if (IS_MIRROR(Tile[x][y]) ||
3055 IS_POLAR_CROSS(Tile[x][y]) ||
3056 IS_POLAR(Tile[x][y]) ||
3057 IS_BEAMER(Tile[x][y]) ||
3058 IS_DF_MIRROR(Tile[x][y]) ||
3059 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3060 IS_GRID_WOOD_AUTO(Tile[x][y]))
3062 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3064 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3066 if (button == MB_LEFTBUTTON)
3068 // left mouse button only for manual adjustment, no auto-rotating;
3069 // freeze mirror for until mouse button released
3073 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3075 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3079 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3081 int edge = Hit[x][y];
3087 DrawLaser(edge - 1, DL_LASER_DISABLED);
3091 else if (ObjHit(x, y, HIT_POS_CENTER))
3093 int edge = Hit[x][y];
3097 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3102 DrawLaser(edge - 1, DL_LASER_DISABLED);
3109 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3114 if ((IS_BEAMER(Tile[x][y]) ||
3115 IS_POLAR(Tile[x][y]) ||
3116 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3118 if (IS_BEAMER(Tile[x][y]))
3121 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3122 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3135 DrawLaser(0, DL_LASER_ENABLED);
3139 static void AutoRotateMirrors(void)
3143 if (!FrameReached(&rotate_delay))
3146 for (x = 0; x < lev_fieldx; x++)
3148 for (y = 0; y < lev_fieldy; y++)
3150 int element = Tile[x][y];
3152 // do not rotate objects hit by the laser after the game was solved
3153 if (game_mm.level_solved && Hit[x][y])
3156 if (IS_DF_MIRROR_AUTO(element) ||
3157 IS_GRID_WOOD_AUTO(element) ||
3158 IS_GRID_STEEL_AUTO(element) ||
3159 element == EL_REFRACTOR)
3160 RotateMirror(x, y, MB_RIGHTBUTTON);
3165 boolean ObjHit(int obx, int oby, int bits)
3172 if (bits & HIT_POS_CENTER)
3174 if (CheckLaserPixel(cSX + obx + 15,
3179 if (bits & HIT_POS_EDGE)
3181 for (i = 0; i < 4; i++)
3182 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3183 cSY + oby + 31 * (i / 2)))
3187 if (bits & HIT_POS_BETWEEN)
3189 for (i = 0; i < 4; i++)
3190 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3191 cSY + 4 + oby + 22 * (i / 2)))
3198 void DeletePacMan(int px, int py)
3204 if (game_mm.num_pacman <= 1)
3206 game_mm.num_pacman = 0;
3210 for (i = 0; i < game_mm.num_pacman; i++)
3211 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3214 game_mm.num_pacman--;
3216 for (j = i; j < game_mm.num_pacman; j++)
3218 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3219 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3220 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3224 void ColorCycling(void)
3226 static int CC, Cc = 0;
3228 static int color, old = 0xF00, new = 0x010, mult = 1;
3229 static unsigned short red, green, blue;
3231 if (color_status == STATIC_COLORS)
3236 if (CC < Cc || CC > Cc + 2)
3240 color = old + new * mult;
3246 if (ABS(mult) == 16)
3256 red = 0x0e00 * ((color & 0xF00) >> 8);
3257 green = 0x0e00 * ((color & 0x0F0) >> 4);
3258 blue = 0x0e00 * ((color & 0x00F));
3259 SetRGB(pen_magicolor[0], red, green, blue);
3261 red = 0x1111 * ((color & 0xF00) >> 8);
3262 green = 0x1111 * ((color & 0x0F0) >> 4);
3263 blue = 0x1111 * ((color & 0x00F));
3264 SetRGB(pen_magicolor[1], red, green, blue);
3268 static void GameActions_MM_Ext(void)
3275 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3278 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3280 element = Tile[x][y];
3282 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3283 StartMoving_MM(x, y);
3284 else if (IS_MOVING(x, y))
3285 ContinueMoving_MM(x, y);
3286 else if (IS_EXPLODING(element))
3287 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3288 else if (element == EL_EXIT_OPENING)
3290 else if (element == EL_GRAY_BALL_OPENING)
3291 OpenSurpriseBall(x, y);
3292 else if (IS_ENVELOPE_OPENING(element))
3294 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3296 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3298 else if (IS_MIRROR(element) ||
3299 IS_MIRROR_FIXED(element) ||
3300 element == EL_PRISM)
3301 DrawFieldTwinkle(x, y);
3302 else if (element == EL_GRAY_BALL_OPENING ||
3303 element == EL_BOMB_ACTIVE ||
3304 element == EL_MINE_ACTIVE)
3305 DrawFieldAnimated_MM(x, y);
3306 else if (!IS_BLOCKED(x, y))
3307 DrawFieldAnimatedIfNeeded_MM(x, y);
3310 AutoRotateMirrors();
3313 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3315 // redraw after Explode_MM() ...
3317 DrawLaser(0, DL_LASER_ENABLED);
3318 laser.redraw = FALSE;
3323 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3327 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3329 DrawLaser(0, DL_LASER_DISABLED);
3334 // skip all following game actions if game is over
3335 if (game_mm.game_over)
3338 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3342 GameOver_MM(GAME_OVER_NO_ENERGY);
3347 if (FrameReached(&energy_delay))
3349 if (game_mm.energy_left > 0)
3350 game_mm.energy_left--;
3352 // when out of energy, wait another frame to play "out of time" sound
3355 element = laser.dest_element;
3358 if (element != Tile[ELX][ELY])
3360 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3361 element, Tile[ELX][ELY]);
3365 if (!laser.overloaded && laser.overload_value == 0 &&
3366 element != EL_BOMB &&
3367 element != EL_BOMB_ACTIVE &&
3368 element != EL_MINE &&
3369 element != EL_MINE_ACTIVE &&
3370 element != EL_BALL_GRAY &&
3371 element != EL_BLOCK_STONE &&
3372 element != EL_BLOCK_WOOD &&
3373 element != EL_FUSE_ON &&
3374 element != EL_FUEL_FULL &&
3375 !IS_WALL_ICE(element) &&
3376 !IS_WALL_AMOEBA(element))
3379 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3381 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3382 (!laser.overloaded && laser.overload_value > 0)) &&
3383 FrameReached(&overload_delay))
3385 if (laser.overloaded)
3386 laser.overload_value++;
3388 laser.overload_value--;
3390 if (game_mm.cheat_no_overload)
3392 laser.overloaded = FALSE;
3393 laser.overload_value = 0;
3396 game_mm.laser_overload_value = laser.overload_value;
3398 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3400 SetLaserColor(0xFF);
3402 DrawLaser(0, DL_LASER_ENABLED);
3405 if (!laser.overloaded)
3406 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3407 else if (setup.sound_loops)
3408 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3410 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3412 if (laser.overloaded)
3415 BlitBitmap(pix[PIX_DOOR], drawto,
3416 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3417 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3418 - laser.overload_value,
3419 OVERLOAD_XSIZE, laser.overload_value,
3420 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3421 - laser.overload_value);
3423 redraw_mask |= REDRAW_DOOR_1;
3428 BlitBitmap(pix[PIX_DOOR], drawto,
3429 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3430 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3431 DX_OVERLOAD, DY_OVERLOAD);
3433 redraw_mask |= REDRAW_DOOR_1;
3436 if (laser.overload_value == MAX_LASER_OVERLOAD)
3438 UpdateAndDisplayGameControlValues();
3442 GameOver_MM(GAME_OVER_OVERLOADED);
3453 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3455 if (game_mm.cheat_no_explosion)
3460 laser.dest_element = EL_EXPLODING_OPAQUE;
3465 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3467 laser.fuse_off = TRUE;
3471 DrawLaser(0, DL_LASER_DISABLED);
3472 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3475 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3477 if (!Store2[ELX][ELY]) // check if content element not yet determined
3479 int last_anim_random_frame = gfx.anim_random_frame;
3482 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3483 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3485 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3486 native_mm_level.ball_choice_mode, 0,
3487 game_mm.ball_choice_pos);
3489 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3490 gfx.anim_random_frame = last_anim_random_frame;
3492 game_mm.ball_choice_pos++;
3494 int new_element = native_mm_level.ball_content[element_pos];
3496 Store[ELX][ELY] = get_rotated_element(new_element, RND(16));
3497 Store2[ELX][ELY] = TRUE;
3500 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3502 // !!! CHECK AGAIN: Laser on Polarizer !!!
3505 laser.dest_element_last = Tile[ELX][ELY];
3506 laser.dest_element_last_x = ELX;
3507 laser.dest_element_last_y = ELY;
3517 element = EL_MIRROR_START + RND(16);
3523 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3530 element = (rnd == 0 ? EL_FUSE_ON :
3531 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3532 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3533 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3534 EL_MIRROR_FIXED_START + rnd - 25);
3539 graphic = el2gfx(element);
3541 for (i = 0; i < 50; i++)
3547 BlitBitmap(pix[PIX_BACK], drawto,
3548 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3549 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3550 SX + ELX * TILEX + x,
3551 SY + ELY * TILEY + y);
3553 MarkTileDirty(ELX, ELY);
3556 DrawLaser(0, DL_LASER_ENABLED);
3558 Delay_WithScreenUpdates(50);
3561 Tile[ELX][ELY] = element;
3562 DrawField_MM(ELX, ELY);
3565 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3568 // above stuff: GRAY BALL -> PRISM !!!
3570 LX = ELX * TILEX + 14;
3571 LY = ELY * TILEY + 14;
3572 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3579 laser.num_edges -= 2;
3580 laser.num_damages--;
3584 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3585 if (laser.damage[i].is_mirror)
3589 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3591 DrawLaser(0, DL_LASER_DISABLED);
3593 DrawLaser(0, DL_LASER_DISABLED);
3602 if (IS_WALL_ICE(element) && CT > 50)
3604 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3607 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3608 Store[ELX][ELY] = EL_WALL_ICE;
3609 Store2[ELX][ELY] = laser.wall_mask;
3611 laser.dest_element = Tile[ELX][ELY];
3616 for (i = 0; i < 5; i++)
3622 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3626 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3628 Delay_WithScreenUpdates(100);
3631 if (Tile[ELX][ELY] == EL_WALL_ICE)
3632 Tile[ELX][ELY] = EL_EMPTY;
3636 LX = laser.edge[laser.num_edges].x - cSX2;
3637 LY = laser.edge[laser.num_edges].y - cSY2;
3640 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3641 if (laser.damage[i].is_mirror)
3645 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3647 DrawLaser(0, DL_LASER_DISABLED);
3654 if (IS_WALL_AMOEBA(element) && CT > 60)
3656 int k1, k2, k3, dx, dy, de, dm;
3657 int element2 = Tile[ELX][ELY];
3659 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3662 for (i = laser.num_damages - 1; i >= 0; i--)
3663 if (laser.damage[i].is_mirror)
3666 r = laser.num_edges;
3667 d = laser.num_damages;
3674 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3677 DrawLaser(0, DL_LASER_ENABLED);
3680 x = laser.damage[k1].x;
3681 y = laser.damage[k1].y;
3686 for (i = 0; i < 4; i++)
3688 if (laser.wall_mask & (1 << i))
3690 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3691 cSY + ELY * TILEY + 31 * (i / 2)))
3694 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3695 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3702 for (i = 0; i < 4; i++)
3704 if (laser.wall_mask & (1 << i))
3706 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3707 cSY + ELY * TILEY + 31 * (i / 2)))
3714 if (laser.num_beamers > 0 ||
3715 k1 < 1 || k2 < 4 || k3 < 4 ||
3716 CheckLaserPixel(cSX + ELX * TILEX + 14,
3717 cSY + ELY * TILEY + 14))
3719 laser.num_edges = r;
3720 laser.num_damages = d;
3722 DrawLaser(0, DL_LASER_DISABLED);
3725 Tile[ELX][ELY] = element | laser.wall_mask;
3729 de = Tile[ELX][ELY];
3730 dm = laser.wall_mask;
3734 int x = ELX, y = ELY;
3735 int wall_mask = laser.wall_mask;
3738 DrawLaser(0, DL_LASER_ENABLED);
3740 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3742 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3743 Store[x][y] = EL_WALL_AMOEBA;
3744 Store2[x][y] = wall_mask;
3750 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3752 DrawLaser(0, DL_LASER_ENABLED);
3754 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3756 for (i = 4; i >= 0; i--)
3758 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3761 Delay_WithScreenUpdates(20);
3764 DrawLaser(0, DL_LASER_ENABLED);
3769 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3770 laser.stops_inside_element && CT > native_mm_level.time_block)
3775 if (ABS(XS) > ABS(YS))
3782 for (i = 0; i < 4; i++)
3789 x = ELX + Step[k * 4].x;
3790 y = ELY + Step[k * 4].y;
3792 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3795 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3803 laser.overloaded = (element == EL_BLOCK_STONE);
3808 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3811 Tile[x][y] = element;
3813 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3816 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3818 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3819 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3827 if (element == EL_FUEL_FULL && CT > 10)
3829 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3830 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3832 for (i = start; i <= num_init_game_frames; i++)
3834 if (i == num_init_game_frames)
3835 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3836 else if (setup.sound_loops)
3837 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3839 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3841 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3843 UpdateAndDisplayGameControlValues();
3848 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3850 DrawField_MM(ELX, ELY);
3852 DrawLaser(0, DL_LASER_ENABLED);
3860 void GameActions_MM(struct MouseActionInfo action)
3862 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3863 boolean button_released = (action.button == MB_RELEASED);
3865 GameActions_MM_Ext();
3867 CheckSingleStepMode_MM(element_clicked, button_released);
3870 void MovePacMen(void)
3872 int mx, my, ox, oy, nx, ny;
3876 if (++pacman_nr >= game_mm.num_pacman)
3879 game_mm.pacman[pacman_nr].dir--;
3881 for (l = 1; l < 5; l++)
3883 game_mm.pacman[pacman_nr].dir++;
3885 if (game_mm.pacman[pacman_nr].dir > 4)
3886 game_mm.pacman[pacman_nr].dir = 1;
3888 if (game_mm.pacman[pacman_nr].dir % 2)
3891 my = game_mm.pacman[pacman_nr].dir - 2;
3896 mx = 3 - game_mm.pacman[pacman_nr].dir;
3899 ox = game_mm.pacman[pacman_nr].x;
3900 oy = game_mm.pacman[pacman_nr].y;
3903 element = Tile[nx][ny];
3905 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3908 if (!IS_EATABLE4PACMAN(element))
3911 if (ObjHit(nx, ny, HIT_POS_CENTER))
3914 Tile[ox][oy] = EL_EMPTY;
3916 EL_PACMAN_RIGHT - 1 +
3917 (game_mm.pacman[pacman_nr].dir - 1 +
3918 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3920 game_mm.pacman[pacman_nr].x = nx;
3921 game_mm.pacman[pacman_nr].y = ny;
3923 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3925 if (element != EL_EMPTY)
3927 int graphic = el2gfx(Tile[nx][ny]);
3932 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3935 ox = cSX + ox * TILEX;
3936 oy = cSY + oy * TILEY;
3938 for (i = 1; i < 33; i += 2)
3939 BlitBitmap(bitmap, window,
3940 src_x, src_y, TILEX, TILEY,
3941 ox + i * mx, oy + i * my);
3942 Ct = Ct + FrameCounter - CT;
3945 DrawField_MM(nx, ny);
3948 if (!laser.fuse_off)
3950 DrawLaser(0, DL_LASER_ENABLED);
3952 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3954 AddDamagedField(nx, ny);
3956 laser.damage[laser.num_damages - 1].edge = 0;
3960 if (element == EL_BOMB)
3961 DeletePacMan(nx, ny);
3963 if (IS_WALL_AMOEBA(element) &&
3964 (LX + 2 * XS) / TILEX == nx &&
3965 (LY + 2 * YS) / TILEY == ny)
3975 static void InitMovingField_MM(int x, int y, int direction)
3977 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3978 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3980 MovDir[x][y] = direction;
3981 MovDir[newx][newy] = direction;
3983 if (Tile[newx][newy] == EL_EMPTY)
3984 Tile[newx][newy] = EL_BLOCKED;
3987 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3989 int direction = MovDir[x][y];
3990 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3991 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3997 static void Blocked2Moving_MM(int x, int y,
3998 int *comes_from_x, int *comes_from_y)
4000 int oldx = x, oldy = y;
4001 int direction = MovDir[x][y];
4003 if (direction == MV_LEFT)
4005 else if (direction == MV_RIGHT)
4007 else if (direction == MV_UP)
4009 else if (direction == MV_DOWN)
4012 *comes_from_x = oldx;
4013 *comes_from_y = oldy;
4016 static int MovingOrBlocked2Element_MM(int x, int y)
4018 int element = Tile[x][y];
4020 if (element == EL_BLOCKED)
4024 Blocked2Moving_MM(x, y, &oldx, &oldy);
4026 return Tile[oldx][oldy];
4033 static void RemoveField(int x, int y)
4035 Tile[x][y] = EL_EMPTY;
4042 static void RemoveMovingField_MM(int x, int y)
4044 int oldx = x, oldy = y, newx = x, newy = y;
4046 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4049 if (IS_MOVING(x, y))
4051 Moving2Blocked_MM(x, y, &newx, &newy);
4052 if (Tile[newx][newy] != EL_BLOCKED)
4055 else if (Tile[x][y] == EL_BLOCKED)
4057 Blocked2Moving_MM(x, y, &oldx, &oldy);
4058 if (!IS_MOVING(oldx, oldy))
4062 Tile[oldx][oldy] = EL_EMPTY;
4063 Tile[newx][newy] = EL_EMPTY;
4064 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4065 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4067 DrawLevelField_MM(oldx, oldy);
4068 DrawLevelField_MM(newx, newy);
4071 void PlaySoundLevel(int x, int y, int sound_nr)
4073 int sx = SCREENX(x), sy = SCREENY(y);
4075 int silence_distance = 8;
4077 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4078 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4081 if (!IN_LEV_FIELD(x, y) ||
4082 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4083 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4086 volume = SOUND_MAX_VOLUME;
4089 stereo = (sx - SCR_FIELDX/2) * 12;
4091 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4092 if (stereo > SOUND_MAX_RIGHT)
4093 stereo = SOUND_MAX_RIGHT;
4094 if (stereo < SOUND_MAX_LEFT)
4095 stereo = SOUND_MAX_LEFT;
4098 if (!IN_SCR_FIELD(sx, sy))
4100 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4101 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4103 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4106 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4109 static void RaiseScore_MM(int value)
4111 game_mm.score += value;
4114 void RaiseScoreElement_MM(int element)
4119 case EL_PACMAN_RIGHT:
4121 case EL_PACMAN_LEFT:
4122 case EL_PACMAN_DOWN:
4123 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4127 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4132 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4136 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4145 // ----------------------------------------------------------------------------
4146 // Mirror Magic game engine snapshot handling functions
4147 // ----------------------------------------------------------------------------
4149 void SaveEngineSnapshotValues_MM(void)
4153 engine_snapshot_mm.game_mm = game_mm;
4154 engine_snapshot_mm.laser = laser;
4156 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4158 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4160 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4161 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4162 engine_snapshot_mm.Box[x][y] = Box[x][y];
4163 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4167 engine_snapshot_mm.LX = LX;
4168 engine_snapshot_mm.LY = LY;
4169 engine_snapshot_mm.XS = XS;
4170 engine_snapshot_mm.YS = YS;
4171 engine_snapshot_mm.ELX = ELX;
4172 engine_snapshot_mm.ELY = ELY;
4173 engine_snapshot_mm.CT = CT;
4174 engine_snapshot_mm.Ct = Ct;
4176 engine_snapshot_mm.last_LX = last_LX;
4177 engine_snapshot_mm.last_LY = last_LY;
4178 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4179 engine_snapshot_mm.hold_x = hold_x;
4180 engine_snapshot_mm.hold_y = hold_y;
4181 engine_snapshot_mm.pacman_nr = pacman_nr;
4183 engine_snapshot_mm.rotate_delay = rotate_delay;
4184 engine_snapshot_mm.pacman_delay = pacman_delay;
4185 engine_snapshot_mm.energy_delay = energy_delay;
4186 engine_snapshot_mm.overload_delay = overload_delay;
4189 void LoadEngineSnapshotValues_MM(void)
4193 // stored engine snapshot buffers already restored at this point
4195 game_mm = engine_snapshot_mm.game_mm;
4196 laser = engine_snapshot_mm.laser;
4198 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4200 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4202 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4203 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4204 Box[x][y] = engine_snapshot_mm.Box[x][y];
4205 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4209 LX = engine_snapshot_mm.LX;
4210 LY = engine_snapshot_mm.LY;
4211 XS = engine_snapshot_mm.XS;
4212 YS = engine_snapshot_mm.YS;
4213 ELX = engine_snapshot_mm.ELX;
4214 ELY = engine_snapshot_mm.ELY;
4215 CT = engine_snapshot_mm.CT;
4216 Ct = engine_snapshot_mm.Ct;
4218 last_LX = engine_snapshot_mm.last_LX;
4219 last_LY = engine_snapshot_mm.last_LY;
4220 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4221 hold_x = engine_snapshot_mm.hold_x;
4222 hold_y = engine_snapshot_mm.hold_y;
4223 pacman_nr = engine_snapshot_mm.pacman_nr;
4225 rotate_delay = engine_snapshot_mm.rotate_delay;
4226 pacman_delay = engine_snapshot_mm.pacman_delay;
4227 energy_delay = engine_snapshot_mm.energy_delay;
4228 overload_delay = engine_snapshot_mm.overload_delay;
4230 RedrawPlayfield_MM();
4233 static int getAngleFromTouchDelta(int dx, int dy, int base)
4235 double pi = 3.141592653;
4236 double rad = atan2((double)-dy, (double)dx);
4237 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4238 double deg = rad2 * 180.0 / pi;
4240 return (int)(deg * base / 360.0 + 0.5) % base;
4243 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4245 // calculate start (source) position to be at the middle of the tile
4246 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4247 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4248 int dx = dst_mx - src_mx;
4249 int dy = dst_my - src_my;
4258 if (!IN_LEV_FIELD(x, y))
4261 element = Tile[x][y];
4263 if (!IS_MCDUFFIN(element) &&
4264 !IS_MIRROR(element) &&
4265 !IS_BEAMER(element) &&
4266 !IS_POLAR(element) &&
4267 !IS_POLAR_CROSS(element) &&
4268 !IS_DF_MIRROR(element))
4271 angle_old = get_element_angle(element);
4273 if (IS_MCDUFFIN(element))
4275 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4276 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4277 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4278 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4281 else if (IS_MIRROR(element) ||
4282 IS_DF_MIRROR(element))
4284 for (i = 0; i < laser.num_damages; i++)
4286 if (laser.damage[i].x == x &&
4287 laser.damage[i].y == y &&
4288 ObjHit(x, y, HIT_POS_CENTER))
4290 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4291 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4298 if (angle_new == -1)
4300 if (IS_MIRROR(element) ||
4301 IS_DF_MIRROR(element) ||
4305 if (IS_POLAR_CROSS(element))
4308 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4311 button = (angle_new == angle_old ? 0 :
4312 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4313 MB_LEFTBUTTON : MB_RIGHTBUTTON);