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, boolean init_game)
468 int element = Tile[x][y];
473 Tile[x][y] = EL_EMPTY;
478 if (init_game && 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))
540 laser.start_edge.x = x;
541 laser.start_edge.y = y;
542 laser.start_angle = get_element_angle(element);
545 if (IS_MCDUFFIN(element))
547 game_mm.laser_red = native_mm_level.mm_laser_red;
548 game_mm.laser_green = native_mm_level.mm_laser_green;
549 game_mm.laser_blue = native_mm_level.mm_laser_blue;
553 game_mm.laser_red = native_mm_level.df_laser_red;
554 game_mm.laser_green = native_mm_level.df_laser_green;
555 game_mm.laser_blue = native_mm_level.df_laser_blue;
563 static void InitCycleElements_RotateSingleStep(void)
567 if (game_mm.num_cycle == 0) // no elements to cycle
570 for (i = 0; i < game_mm.num_cycle; i++)
572 int x = game_mm.cycle[i].x;
573 int y = game_mm.cycle[i].y;
574 int step = SIGN(game_mm.cycle[i].steps);
575 int last_element = Tile[x][y];
576 int next_element = get_rotated_element(last_element, step);
578 if (!game_mm.cycle[i].steps)
581 Tile[x][y] = next_element;
583 game_mm.cycle[i].steps -= step;
587 static void InitLaser(void)
589 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
590 int step = (IS_LASER(start_element) ? 4 : 0);
592 LX = laser.start_edge.x * TILEX;
593 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
596 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
598 LY = laser.start_edge.y * TILEY;
599 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
600 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
604 XS = 2 * Step[laser.start_angle].x;
605 YS = 2 * Step[laser.start_angle].y;
607 laser.current_angle = laser.start_angle;
609 laser.num_damages = 0;
611 laser.num_beamers = 0;
612 laser.beamer_edge[0] = 0;
614 laser.dest_element = EL_EMPTY;
617 AddLaserEdge(LX, LY); // set laser starting edge
622 void InitGameEngine_MM(void)
628 // initialize laser bitmap to current playfield (screen) size
629 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
630 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
634 // set global game control values
635 game_mm.num_cycle = 0;
636 game_mm.num_pacman = 0;
639 game_mm.energy_left = 0; // later set to "native_mm_level.time"
640 game_mm.kettles_still_needed =
641 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
642 game_mm.lights_still_needed = 0;
643 game_mm.num_keys = 0;
644 game_mm.ball_choice_pos = 0;
646 game_mm.laser_red = FALSE;
647 game_mm.laser_green = FALSE;
648 game_mm.laser_blue = TRUE;
650 game_mm.level_solved = FALSE;
651 game_mm.game_over = FALSE;
652 game_mm.game_over_cause = 0;
654 game_mm.laser_overload_value = 0;
655 game_mm.laser_enabled = FALSE;
657 // set global laser control values (must be set before "InitLaser()")
658 laser.start_edge.x = 0;
659 laser.start_edge.y = 0;
660 laser.start_angle = 0;
662 for (i = 0; i < MAX_NUM_BEAMERS; i++)
663 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
665 laser.overloaded = FALSE;
666 laser.overload_value = 0;
667 laser.fuse_off = FALSE;
668 laser.fuse_x = laser.fuse_y = -1;
670 laser.dest_element = EL_EMPTY;
671 laser.dest_element_last = EL_EMPTY;
672 laser.dest_element_last_x = -1;
673 laser.dest_element_last_y = -1;
687 rotate_delay.count = 0;
688 pacman_delay.count = 0;
689 energy_delay.count = 0;
690 overload_delay.count = 0;
692 ClickElement(-1, -1, -1);
694 for (x = 0; x < lev_fieldx; x++)
696 for (y = 0; y < lev_fieldy; y++)
698 Tile[x][y] = Ur[x][y];
699 Hit[x][y] = Box[x][y] = 0;
701 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
702 Store[x][y] = Store2[x][y] = 0;
705 InitField(x, y, TRUE);
712 void InitGameActions_MM(void)
714 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
715 int cycle_steps_done = 0;
720 for (i = 0; i <= num_init_game_frames; i++)
722 if (i == num_init_game_frames)
723 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
724 else if (setup.sound_loops)
725 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
727 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
729 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
731 UpdateAndDisplayGameControlValues();
733 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
735 InitCycleElements_RotateSingleStep();
740 AdvanceFrameCounter();
750 if (setup.quick_doors)
757 if (game_mm.kettles_still_needed == 0)
760 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
761 SetTileCursorActive(TRUE);
763 // restart all delay counters after initially cycling game elements
764 ResetFrameCounter(&rotate_delay);
765 ResetFrameCounter(&pacman_delay);
766 ResetFrameCounter(&energy_delay);
767 ResetFrameCounter(&overload_delay);
770 static void FadeOutLaser(void)
774 for (i = 15; i >= 0; i--)
776 SetLaserColor(0x11 * i);
778 DrawLaser(0, DL_LASER_ENABLED);
781 Delay_WithScreenUpdates(50);
784 DrawLaser(0, DL_LASER_DISABLED);
786 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
789 static void GameOver_MM(int game_over_cause)
791 // do not handle game over if request dialog is already active
792 if (game.request_active)
795 game_mm.game_over = TRUE;
796 game_mm.game_over_cause = game_over_cause;
798 if (setup.ask_on_game_over)
799 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
800 "Bomb killed Mc Duffin! Play it again?" :
801 game_over_cause == GAME_OVER_NO_ENERGY ?
802 "Out of magic energy! Play it again?" :
803 game_over_cause == GAME_OVER_OVERLOADED ?
804 "Magic spell hit Mc Duffin! Play it again?" :
807 SetTileCursorActive(FALSE);
810 void AddLaserEdge(int lx, int ly)
815 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
817 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
822 laser.edge[laser.num_edges].x = cSX2 + lx;
823 laser.edge[laser.num_edges].y = cSY2 + ly;
829 void AddDamagedField(int ex, int ey)
831 // prevent adding the same field position again
832 if (laser.num_damages > 0 &&
833 laser.damage[laser.num_damages - 1].x == ex &&
834 laser.damage[laser.num_damages - 1].y == ey &&
835 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
838 laser.damage[laser.num_damages].is_mirror = FALSE;
839 laser.damage[laser.num_damages].angle = laser.current_angle;
840 laser.damage[laser.num_damages].edge = laser.num_edges;
841 laser.damage[laser.num_damages].x = ex;
842 laser.damage[laser.num_damages].y = ey;
846 static boolean StepBehind(void)
852 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
853 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
855 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
861 static int getMaskFromElement(int element)
863 if (IS_GRID(element))
864 return MM_MASK_GRID_1 + get_element_phase(element);
865 else if (IS_MCDUFFIN(element))
866 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
867 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
868 return MM_MASK_RECTANGLE;
870 return MM_MASK_CIRCLE;
873 static int ScanPixel(void)
878 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
879 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
882 // follow laser beam until it hits something (at least the screen border)
883 while (hit_mask == HIT_MASK_NO_HIT)
889 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
890 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
892 Debug("game:mm:ScanPixel", "touched screen border!");
898 for (i = 0; i < 4; i++)
900 int px = LX + (i % 2) * 2;
901 int py = LY + (i / 2) * 2;
904 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
905 int ly = (py + TILEY) / TILEY - 1; // negative values!
908 if (IN_LEV_FIELD(lx, ly))
910 int element = Tile[lx][ly];
912 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
916 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
918 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
920 pixel = ((element & (1 << pos)) ? 1 : 0);
924 int pos = getMaskFromElement(element);
926 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
931 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
932 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
935 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
936 hit_mask |= (1 << i);
939 if (hit_mask == HIT_MASK_NO_HIT)
941 // hit nothing -- go on with another step
950 static void DeactivateLaserTargetElement(void)
952 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
953 laser.dest_element_last == EL_MINE_ACTIVE ||
954 laser.dest_element_last == EL_GRAY_BALL_OPENING)
956 int x = laser.dest_element_last_x;
957 int y = laser.dest_element_last_y;
958 int element = laser.dest_element_last;
960 if (Tile[x][y] == element)
961 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
962 element == EL_MINE_ACTIVE ? EL_MINE : EL_BALL_GRAY);
964 if (Tile[x][y] == EL_BALL_GRAY)
967 laser.dest_element_last = EL_EMPTY;
968 laser.dest_element_last_x = -1;
969 laser.dest_element_last_y = -1;
975 int element = EL_EMPTY;
976 int last_element = EL_EMPTY;
977 int end = 0, rf = laser.num_edges;
979 // do not scan laser again after the game was lost for whatever reason
980 if (game_mm.game_over)
983 // do not scan laser if fuse is off
987 DeactivateLaserTargetElement();
989 laser.overloaded = FALSE;
990 laser.stops_inside_element = FALSE;
992 DrawLaser(0, DL_LASER_ENABLED);
995 Debug("game:mm:ScanLaser",
996 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1004 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1007 laser.overloaded = TRUE;
1012 hit_mask = ScanPixel();
1015 Debug("game:mm:ScanLaser",
1016 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1020 // hit something -- check out what it was
1021 ELX = (LX + XS) / TILEX;
1022 ELY = (LY + YS) / TILEY;
1025 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1026 hit_mask, LX, LY, ELX, ELY);
1029 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1032 laser.dest_element = element;
1037 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1039 /* we have hit the top-right and bottom-left element --
1040 choose the bottom-left one */
1041 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1042 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1043 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1044 ELX = (LX - 2) / TILEX;
1045 ELY = (LY + 2) / TILEY;
1048 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1050 /* we have hit the top-left and bottom-right element --
1051 choose the top-left one */
1052 // !!! SEE ABOVE !!!
1053 ELX = (LX - 2) / TILEX;
1054 ELY = (LY - 2) / TILEY;
1058 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1059 hit_mask, LX, LY, ELX, ELY);
1062 last_element = element;
1064 element = Tile[ELX][ELY];
1065 laser.dest_element = element;
1068 Debug("game:mm:ScanLaser",
1069 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1072 LX % TILEX, LY % TILEY,
1077 if (!IN_LEV_FIELD(ELX, ELY))
1078 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1082 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1083 if (element == EL_EMPTY &&
1084 IS_GRID_STEEL(last_element) &&
1085 laser.current_angle % 4) // angle is not 90°
1086 element = last_element;
1088 if (element == EL_EMPTY)
1090 if (!HitOnlyAnEdge(hit_mask))
1093 else if (element == EL_FUSE_ON)
1095 if (HitPolarizer(element, hit_mask))
1098 else if (IS_GRID(element) || IS_DF_GRID(element))
1100 if (HitPolarizer(element, hit_mask))
1103 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1104 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1106 if (HitBlock(element, hit_mask))
1113 else if (IS_MCDUFFIN(element))
1115 if (HitLaserSource(element, hit_mask))
1118 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1119 IS_RECEIVER(element))
1121 if (HitLaserDestination(element, hit_mask))
1124 else if (IS_WALL(element))
1126 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1128 if (HitReflectingWalls(element, hit_mask))
1133 if (HitAbsorbingWalls(element, hit_mask))
1139 if (HitElement(element, hit_mask))
1144 DrawLaser(rf - 1, DL_LASER_ENABLED);
1145 rf = laser.num_edges;
1147 if (!IS_DF_WALL_STEEL(element))
1149 // only used for scanning DF steel walls; reset for all other elements
1157 if (laser.dest_element != Tile[ELX][ELY])
1159 Debug("game:mm:ScanLaser",
1160 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1161 laser.dest_element, Tile[ELX][ELY]);
1165 if (!end && !laser.stops_inside_element && !StepBehind())
1168 Debug("game:mm:ScanLaser", "Go one step back");
1174 AddLaserEdge(LX, LY);
1178 DrawLaser(rf - 1, DL_LASER_ENABLED);
1180 Ct = CT = FrameCounter;
1183 if (!IN_LEV_FIELD(ELX, ELY))
1184 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1188 static void ScanLaser_FromLastMirror(void)
1190 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1193 for (i = start_pos; i >= 0; i--)
1194 if (laser.damage[i].is_mirror)
1197 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1199 DrawLaser(start_edge, DL_LASER_DISABLED);
1204 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1210 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1211 start_edge, num_edges, mode);
1216 Warn("DrawLaserExt: start_edge < 0");
1223 Warn("DrawLaserExt: num_edges < 0");
1229 if (mode == DL_LASER_DISABLED)
1231 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1235 // now draw the laser to the backbuffer and (if enabled) to the screen
1236 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1238 redraw_mask |= REDRAW_FIELD;
1240 if (mode == DL_LASER_ENABLED)
1243 // after the laser was deleted, the "damaged" graphics must be restored
1244 if (laser.num_damages)
1246 int damage_start = 0;
1249 // determine the starting edge, from which graphics need to be restored
1252 for (i = 0; i < laser.num_damages; i++)
1254 if (laser.damage[i].edge == start_edge + 1)
1263 // restore graphics from this starting edge to the end of damage list
1264 for (i = damage_start; i < laser.num_damages; i++)
1266 int lx = laser.damage[i].x;
1267 int ly = laser.damage[i].y;
1268 int element = Tile[lx][ly];
1270 if (Hit[lx][ly] == laser.damage[i].edge)
1271 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1274 if (Box[lx][ly] == laser.damage[i].edge)
1277 if (IS_DRAWABLE(element))
1278 DrawField_MM(lx, ly);
1281 elx = laser.damage[damage_start].x;
1282 ely = laser.damage[damage_start].y;
1283 element = Tile[elx][ely];
1286 if (IS_BEAMER(element))
1290 for (i = 0; i < laser.num_beamers; i++)
1291 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1293 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1294 mode, elx, ely, Hit[elx][ely], start_edge);
1295 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1296 get_element_angle(element), laser.damage[damage_start].angle);
1300 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1301 laser.num_beamers > 0 &&
1302 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1304 // element is outgoing beamer
1305 laser.num_damages = damage_start + 1;
1307 if (IS_BEAMER(element))
1308 laser.current_angle = get_element_angle(element);
1312 // element is incoming beamer or other element
1313 laser.num_damages = damage_start;
1314 laser.current_angle = laser.damage[laser.num_damages].angle;
1319 // no damages but McDuffin himself (who needs to be redrawn anyway)
1321 elx = laser.start_edge.x;
1322 ely = laser.start_edge.y;
1323 element = Tile[elx][ely];
1326 laser.num_edges = start_edge + 1;
1327 if (start_edge == 0)
1328 laser.current_angle = laser.start_angle;
1330 LX = laser.edge[start_edge].x - cSX2;
1331 LY = laser.edge[start_edge].y - cSY2;
1332 XS = 2 * Step[laser.current_angle].x;
1333 YS = 2 * Step[laser.current_angle].y;
1336 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1342 if (IS_BEAMER(element) ||
1343 IS_FIBRE_OPTIC(element) ||
1344 IS_PACMAN(element) ||
1345 IS_POLAR(element) ||
1346 IS_POLAR_CROSS(element) ||
1347 element == EL_FUSE_ON)
1352 Debug("game:mm:DrawLaserExt", "element == %d", element);
1355 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1356 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1360 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1361 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1362 (laser.num_beamers == 0 ||
1363 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1365 // element is incoming beamer or other element
1366 step_size = -step_size;
1371 if (IS_BEAMER(element))
1372 Debug("game:mm:DrawLaserExt",
1373 "start_edge == %d, laser.beamer_edge == %d",
1374 start_edge, laser.beamer_edge);
1377 LX += step_size * XS;
1378 LY += step_size * YS;
1380 else if (element != EL_EMPTY)
1389 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1394 void DrawLaser(int start_edge, int mode)
1396 // do not draw laser if fuse is off
1397 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1400 if (mode == DL_LASER_DISABLED)
1401 DeactivateLaserTargetElement();
1403 if (laser.num_edges - start_edge < 0)
1405 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1410 // check if laser is interrupted by beamer element
1411 if (laser.num_beamers > 0 &&
1412 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1414 if (mode == DL_LASER_ENABLED)
1417 int tmp_start_edge = start_edge;
1419 // draw laser segments forward from the start to the last beamer
1420 for (i = 0; i < laser.num_beamers; i++)
1422 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1424 if (tmp_num_edges <= 0)
1428 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1429 i, laser.beamer_edge[i], tmp_start_edge);
1432 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1434 tmp_start_edge = laser.beamer_edge[i];
1437 // draw last segment from last beamer to the end
1438 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1444 int last_num_edges = laser.num_edges;
1445 int num_beamers = laser.num_beamers;
1447 // delete laser segments backward from the end to the first beamer
1448 for (i = num_beamers - 1; i >= 0; i--)
1450 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1452 if (laser.beamer_edge[i] - start_edge <= 0)
1455 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1457 last_num_edges = laser.beamer_edge[i];
1458 laser.num_beamers--;
1462 if (last_num_edges - start_edge <= 0)
1463 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1464 last_num_edges, start_edge);
1467 // special case when rotating first beamer: delete laser edge on beamer
1468 // (but do not start scanning on previous edge to prevent mirror sound)
1469 if (last_num_edges - start_edge == 1 && start_edge > 0)
1470 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1472 // delete first segment from start to the first beamer
1473 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1478 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1481 game_mm.laser_enabled = mode;
1484 void DrawLaser_MM(void)
1486 DrawLaser(0, game_mm.laser_enabled);
1489 boolean HitElement(int element, int hit_mask)
1491 if (HitOnlyAnEdge(hit_mask))
1494 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1495 element = MovingOrBlocked2Element_MM(ELX, ELY);
1498 Debug("game:mm:HitElement", "(1): element == %d", element);
1502 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1503 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1506 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1510 AddDamagedField(ELX, ELY);
1512 // this is more precise: check if laser would go through the center
1513 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1517 // prevent cutting through laser emitter with laser beam
1518 if (IS_LASER(element))
1521 // skip the whole element before continuing the scan
1529 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1531 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1533 /* skipping scan positions to the right and down skips one scan
1534 position too much, because this is only the top left scan position
1535 of totally four scan positions (plus one to the right, one to the
1536 bottom and one to the bottom right) */
1537 /* ... but only roll back scan position if more than one step done */
1547 Debug("game:mm:HitElement", "(2): element == %d", element);
1550 if (LX + 5 * XS < 0 ||
1560 Debug("game:mm:HitElement", "(3): element == %d", element);
1563 if (IS_POLAR(element) &&
1564 ((element - EL_POLAR_START) % 2 ||
1565 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1567 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1569 laser.num_damages--;
1574 if (IS_POLAR_CROSS(element) &&
1575 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1577 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1579 laser.num_damages--;
1584 if (!IS_BEAMER(element) &&
1585 !IS_FIBRE_OPTIC(element) &&
1586 !IS_GRID_WOOD(element) &&
1587 element != EL_FUEL_EMPTY)
1590 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1591 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1593 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1596 LX = ELX * TILEX + 14;
1597 LY = ELY * TILEY + 14;
1599 AddLaserEdge(LX, LY);
1602 if (IS_MIRROR(element) ||
1603 IS_MIRROR_FIXED(element) ||
1604 IS_POLAR(element) ||
1605 IS_POLAR_CROSS(element) ||
1606 IS_DF_MIRROR(element) ||
1607 IS_DF_MIRROR_AUTO(element) ||
1608 element == EL_PRISM ||
1609 element == EL_REFRACTOR)
1611 int current_angle = laser.current_angle;
1614 laser.num_damages--;
1616 AddDamagedField(ELX, ELY);
1618 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1621 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1623 if (IS_MIRROR(element) ||
1624 IS_MIRROR_FIXED(element) ||
1625 IS_DF_MIRROR(element) ||
1626 IS_DF_MIRROR_AUTO(element))
1627 laser.current_angle = get_mirrored_angle(laser.current_angle,
1628 get_element_angle(element));
1630 if (element == EL_PRISM || element == EL_REFRACTOR)
1631 laser.current_angle = RND(16);
1633 XS = 2 * Step[laser.current_angle].x;
1634 YS = 2 * Step[laser.current_angle].y;
1636 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1641 LX += step_size * XS;
1642 LY += step_size * YS;
1644 // draw sparkles on mirror
1645 if ((IS_MIRROR(element) ||
1646 IS_MIRROR_FIXED(element) ||
1647 element == EL_PRISM) &&
1648 current_angle != laser.current_angle)
1650 MovDelay[ELX][ELY] = 11; // start animation
1653 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1654 current_angle != laser.current_angle)
1655 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1658 (get_opposite_angle(laser.current_angle) ==
1659 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1661 return (laser.overloaded ? TRUE : FALSE);
1664 if (element == EL_FUEL_FULL)
1666 laser.stops_inside_element = TRUE;
1671 if (element == EL_BOMB || element == EL_MINE)
1673 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1675 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1677 laser.dest_element_last = Tile[ELX][ELY];
1678 laser.dest_element_last_x = ELX;
1679 laser.dest_element_last_y = ELY;
1681 if (element == EL_MINE)
1682 laser.overloaded = TRUE;
1685 if (element == EL_KETTLE ||
1686 element == EL_CELL ||
1687 element == EL_KEY ||
1688 element == EL_LIGHTBALL ||
1689 element == EL_PACMAN ||
1690 IS_PACMAN(element) ||
1691 IS_ENVELOPE(element))
1693 if (!IS_PACMAN(element) &&
1694 !IS_ENVELOPE(element))
1697 if (element == EL_PACMAN)
1700 if (element == EL_KETTLE || element == EL_CELL)
1702 if (game_mm.kettles_still_needed > 0)
1703 game_mm.kettles_still_needed--;
1705 game.snapshot.collected_item = TRUE;
1707 if (game_mm.kettles_still_needed == 0)
1711 DrawLaser(0, DL_LASER_ENABLED);
1714 else if (element == EL_KEY)
1718 else if (IS_PACMAN(element))
1720 DeletePacMan(ELX, ELY);
1722 else if (IS_ENVELOPE(element))
1724 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1727 RaiseScoreElement_MM(element);
1732 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1734 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1736 DrawLaser(0, DL_LASER_ENABLED);
1738 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1740 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1741 game_mm.lights_still_needed--;
1745 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1746 game_mm.lights_still_needed++;
1749 DrawField_MM(ELX, ELY);
1750 DrawLaser(0, DL_LASER_ENABLED);
1755 laser.stops_inside_element = TRUE;
1761 Debug("game:mm:HitElement", "(4): element == %d", element);
1764 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1765 laser.num_beamers < MAX_NUM_BEAMERS &&
1766 laser.beamer[BEAMER_NR(element)][1].num)
1768 int beamer_angle = get_element_angle(element);
1769 int beamer_nr = BEAMER_NR(element);
1773 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1776 laser.num_damages--;
1778 if (IS_FIBRE_OPTIC(element) ||
1779 laser.current_angle == get_opposite_angle(beamer_angle))
1783 LX = ELX * TILEX + 14;
1784 LY = ELY * TILEY + 14;
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 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1795 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1796 ELX = laser.beamer[beamer_nr][pos].x;
1797 ELY = laser.beamer[beamer_nr][pos].y;
1798 LX = ELX * TILEX + 14;
1799 LY = ELY * TILEY + 14;
1801 if (IS_BEAMER(element))
1803 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1804 XS = 2 * Step[laser.current_angle].x;
1805 YS = 2 * Step[laser.current_angle].y;
1808 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1810 AddLaserEdge(LX, LY);
1811 AddDamagedField(ELX, ELY);
1813 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1816 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1818 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1823 LX += step_size * XS;
1824 LY += step_size * YS;
1826 laser.num_beamers++;
1835 boolean HitOnlyAnEdge(int hit_mask)
1837 // check if the laser hit only the edge of an element and, if so, go on
1840 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1844 if ((hit_mask == HIT_MASK_TOPLEFT ||
1845 hit_mask == HIT_MASK_TOPRIGHT ||
1846 hit_mask == HIT_MASK_BOTTOMLEFT ||
1847 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1848 laser.current_angle % 4) // angle is not 90°
1852 if (hit_mask == HIT_MASK_TOPLEFT)
1857 else if (hit_mask == HIT_MASK_TOPRIGHT)
1862 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1867 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1873 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1879 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1886 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1892 boolean HitPolarizer(int element, int hit_mask)
1894 if (HitOnlyAnEdge(hit_mask))
1897 if (IS_DF_GRID(element))
1899 int grid_angle = get_element_angle(element);
1902 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1903 grid_angle, laser.current_angle);
1906 AddLaserEdge(LX, LY);
1907 AddDamagedField(ELX, ELY);
1910 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1912 if (laser.current_angle == grid_angle ||
1913 laser.current_angle == get_opposite_angle(grid_angle))
1915 // skip the whole element before continuing the scan
1921 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1923 if (LX/TILEX > ELX || LY/TILEY > ELY)
1925 /* skipping scan positions to the right and down skips one scan
1926 position too much, because this is only the top left scan position
1927 of totally four scan positions (plus one to the right, one to the
1928 bottom and one to the bottom right) */
1934 AddLaserEdge(LX, LY);
1940 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1942 LX / TILEX, LY / TILEY,
1943 LX % TILEX, LY % TILEY);
1948 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1950 return HitReflectingWalls(element, hit_mask);
1954 return HitAbsorbingWalls(element, hit_mask);
1957 else if (IS_GRID_STEEL(element))
1959 return HitReflectingWalls(element, hit_mask);
1961 else // IS_GRID_WOOD
1963 return HitAbsorbingWalls(element, hit_mask);
1969 boolean HitBlock(int element, int hit_mask)
1971 boolean check = FALSE;
1973 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1974 game_mm.num_keys == 0)
1977 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1980 int ex = ELX * TILEX + 14;
1981 int ey = ELY * TILEY + 14;
1985 for (i = 1; i < 32; i++)
1990 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1995 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1996 return HitAbsorbingWalls(element, hit_mask);
2000 AddLaserEdge(LX - XS, LY - YS);
2001 AddDamagedField(ELX, ELY);
2004 Box[ELX][ELY] = laser.num_edges;
2006 return HitReflectingWalls(element, hit_mask);
2009 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2011 int xs = XS / 2, ys = YS / 2;
2012 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2013 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2015 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2016 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2018 laser.overloaded = (element == EL_GATE_STONE);
2023 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2024 (hit_mask == HIT_MASK_TOP ||
2025 hit_mask == HIT_MASK_LEFT ||
2026 hit_mask == HIT_MASK_RIGHT ||
2027 hit_mask == HIT_MASK_BOTTOM))
2028 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2029 hit_mask == HIT_MASK_BOTTOM),
2030 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2031 hit_mask == HIT_MASK_RIGHT));
2032 AddLaserEdge(LX, LY);
2038 if (element == EL_GATE_STONE && Box[ELX][ELY])
2040 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2052 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2054 int xs = XS / 2, ys = YS / 2;
2055 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2056 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2058 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2059 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2061 laser.overloaded = (element == EL_BLOCK_STONE);
2066 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2067 (hit_mask == HIT_MASK_TOP ||
2068 hit_mask == HIT_MASK_LEFT ||
2069 hit_mask == HIT_MASK_RIGHT ||
2070 hit_mask == HIT_MASK_BOTTOM))
2071 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2072 hit_mask == HIT_MASK_BOTTOM),
2073 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2074 hit_mask == HIT_MASK_RIGHT));
2075 AddDamagedField(ELX, ELY);
2077 LX = ELX * TILEX + 14;
2078 LY = ELY * TILEY + 14;
2080 AddLaserEdge(LX, LY);
2082 laser.stops_inside_element = TRUE;
2090 boolean HitLaserSource(int element, int hit_mask)
2092 if (HitOnlyAnEdge(hit_mask))
2095 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2097 laser.overloaded = TRUE;
2102 boolean HitLaserDestination(int element, int hit_mask)
2104 if (HitOnlyAnEdge(hit_mask))
2107 if (element != EL_EXIT_OPEN &&
2108 !(IS_RECEIVER(element) &&
2109 game_mm.kettles_still_needed == 0 &&
2110 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2112 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2117 if (IS_RECEIVER(element) ||
2118 (IS_22_5_ANGLE(laser.current_angle) &&
2119 (ELX != (LX + 6 * XS) / TILEX ||
2120 ELY != (LY + 6 * YS) / TILEY ||
2129 LX = ELX * TILEX + 14;
2130 LY = ELY * TILEY + 14;
2132 laser.stops_inside_element = TRUE;
2135 AddLaserEdge(LX, LY);
2136 AddDamagedField(ELX, ELY);
2138 if (game_mm.lights_still_needed == 0)
2140 game_mm.level_solved = TRUE;
2142 SetTileCursorActive(FALSE);
2148 boolean HitReflectingWalls(int element, int hit_mask)
2150 // check if laser hits side of a wall with an angle that is not 90°
2151 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2152 hit_mask == HIT_MASK_LEFT ||
2153 hit_mask == HIT_MASK_RIGHT ||
2154 hit_mask == HIT_MASK_BOTTOM))
2156 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2161 if (!IS_DF_GRID(element))
2162 AddLaserEdge(LX, LY);
2164 // check if laser hits wall with an angle of 45°
2165 if (!IS_22_5_ANGLE(laser.current_angle))
2167 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2170 laser.current_angle = get_mirrored_angle(laser.current_angle,
2173 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2176 laser.current_angle = get_mirrored_angle(laser.current_angle,
2180 AddLaserEdge(LX, LY);
2182 XS = 2 * Step[laser.current_angle].x;
2183 YS = 2 * Step[laser.current_angle].y;
2187 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2189 laser.current_angle = get_mirrored_angle(laser.current_angle,
2194 if (!IS_DF_GRID(element))
2195 AddLaserEdge(LX, LY);
2200 if (!IS_DF_GRID(element))
2201 AddLaserEdge(LX, LY + YS / 2);
2204 if (!IS_DF_GRID(element))
2205 AddLaserEdge(LX, LY);
2208 YS = 2 * Step[laser.current_angle].y;
2212 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2214 laser.current_angle = get_mirrored_angle(laser.current_angle,
2219 if (!IS_DF_GRID(element))
2220 AddLaserEdge(LX, LY);
2225 if (!IS_DF_GRID(element))
2226 AddLaserEdge(LX + XS / 2, LY);
2229 if (!IS_DF_GRID(element))
2230 AddLaserEdge(LX, LY);
2233 XS = 2 * Step[laser.current_angle].x;
2239 // reflection at the edge of reflecting DF style wall
2240 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2242 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2243 hit_mask == HIT_MASK_TOPRIGHT) ||
2244 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2245 hit_mask == HIT_MASK_TOPLEFT) ||
2246 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2247 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2248 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2249 hit_mask == HIT_MASK_BOTTOMRIGHT))
2252 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2253 ANG_MIRROR_135 : ANG_MIRROR_45);
2255 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2257 AddDamagedField(ELX, ELY);
2258 AddLaserEdge(LX, LY);
2260 laser.current_angle = get_mirrored_angle(laser.current_angle,
2268 AddLaserEdge(LX, LY);
2274 // reflection inside an edge of reflecting DF style wall
2275 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2277 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2278 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2279 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2280 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2281 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2282 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2283 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2284 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2287 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2288 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2289 ANG_MIRROR_135 : ANG_MIRROR_45);
2291 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2294 AddDamagedField(ELX, ELY);
2297 AddLaserEdge(LX - XS, LY - YS);
2298 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2299 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2301 laser.current_angle = get_mirrored_angle(laser.current_angle,
2309 AddLaserEdge(LX, LY);
2315 // check if laser hits DF style wall with an angle of 90°
2316 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2318 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2319 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2320 (IS_VERT_ANGLE(laser.current_angle) &&
2321 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2323 // laser at last step touched nothing or the same side of the wall
2324 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2326 AddDamagedField(ELX, ELY);
2333 last_hit_mask = hit_mask;
2340 if (!HitOnlyAnEdge(hit_mask))
2342 laser.overloaded = TRUE;
2350 boolean HitAbsorbingWalls(int element, int hit_mask)
2352 if (HitOnlyAnEdge(hit_mask))
2356 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2358 AddLaserEdge(LX - XS, LY - YS);
2365 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2367 AddLaserEdge(LX - XS, LY - YS);
2373 if (IS_WALL_WOOD(element) ||
2374 IS_DF_WALL_WOOD(element) ||
2375 IS_GRID_WOOD(element) ||
2376 IS_GRID_WOOD_FIXED(element) ||
2377 IS_GRID_WOOD_AUTO(element) ||
2378 element == EL_FUSE_ON ||
2379 element == EL_BLOCK_WOOD ||
2380 element == EL_GATE_WOOD)
2382 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2387 if (IS_WALL_ICE(element))
2391 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2392 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2394 // check if laser hits wall with an angle of 90°
2395 if (IS_90_ANGLE(laser.current_angle))
2396 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2398 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2402 for (i = 0; i < 4; i++)
2404 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2405 mask = 15 - (8 >> i);
2406 else if (ABS(XS) == 4 &&
2408 (XS > 0) == (i % 2) &&
2409 (YS < 0) == (i / 2))
2410 mask = 3 + (i / 2) * 9;
2411 else if (ABS(YS) == 4 &&
2413 (XS < 0) == (i % 2) &&
2414 (YS > 0) == (i / 2))
2415 mask = 5 + (i % 2) * 5;
2419 laser.wall_mask = mask;
2421 else if (IS_WALL_AMOEBA(element))
2423 int elx = (LX - 2 * XS) / TILEX;
2424 int ely = (LY - 2 * YS) / TILEY;
2425 int element2 = Tile[elx][ely];
2428 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2430 laser.dest_element = EL_EMPTY;
2438 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2439 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2441 if (IS_90_ANGLE(laser.current_angle))
2442 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2444 laser.dest_element = element2 | EL_WALL_AMOEBA;
2446 laser.wall_mask = mask;
2452 static void OpenExit(int x, int y)
2456 if (!MovDelay[x][y]) // next animation frame
2457 MovDelay[x][y] = 4 * delay;
2459 if (MovDelay[x][y]) // wait some time before next frame
2464 phase = MovDelay[x][y] / delay;
2466 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2467 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2469 if (!MovDelay[x][y])
2471 Tile[x][y] = EL_EXIT_OPEN;
2477 static void OpenSurpriseBall(int x, int y)
2481 if (!MovDelay[x][y]) // next animation frame
2483 if (IS_WALL(Store[x][y]))
2485 DrawWalls_MM(x, y, Store[x][y]);
2487 // copy wall tile to spare bitmap for "melting" animation
2488 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2489 TILEX, TILEY, x * TILEX, y * TILEY);
2491 DrawElement_MM(x, y, EL_BALL_GRAY);
2494 MovDelay[x][y] = 50 * delay;
2497 if (MovDelay[x][y]) // wait some time before next frame
2501 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2505 int dx = RND(26), dy = RND(26);
2507 if (IS_WALL(Store[x][y]))
2509 // copy wall tile from spare bitmap for "melting" animation
2510 bitmap = bitmap_db_field;
2516 int graphic = el2gfx(Store[x][y]);
2518 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2521 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2522 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2524 laser.redraw = TRUE;
2526 MarkTileDirty(x, y);
2529 if (!MovDelay[x][y])
2531 Tile[x][y] = Store[x][y];
2532 Store[x][y] = Store2[x][y] = 0;
2533 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2535 InitField(x, y, FALSE);
2538 ScanLaser_FromLastMirror();
2543 static void OpenEnvelope(int x, int y)
2545 int num_frames = 8; // seven frames plus final empty space
2547 if (!MovDelay[x][y]) // next animation frame
2548 MovDelay[x][y] = num_frames;
2550 if (MovDelay[x][y]) // wait some time before next frame
2552 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2556 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2558 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2559 int frame = num_frames - MovDelay[x][y] - 1;
2561 DrawGraphicAnimation_MM(x, y, graphic, frame);
2563 laser.redraw = TRUE;
2566 if (MovDelay[x][y] == 0)
2568 Tile[x][y] = EL_EMPTY;
2574 ShowEnvelope_MM(nr);
2579 static void MeltIce(int x, int y)
2584 if (!MovDelay[x][y]) // next animation frame
2585 MovDelay[x][y] = frames * delay;
2587 if (MovDelay[x][y]) // wait some time before next frame
2590 int wall_mask = Store2[x][y];
2591 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2594 phase = frames - MovDelay[x][y] / delay - 1;
2596 if (!MovDelay[x][y])
2598 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2599 Store[x][y] = Store2[x][y] = 0;
2601 DrawWalls_MM(x, y, Tile[x][y]);
2603 if (Tile[x][y] == EL_WALL_ICE)
2604 Tile[x][y] = EL_EMPTY;
2606 ScanLaser_FromLastMirror();
2608 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2610 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2612 laser.redraw = TRUE;
2617 static void GrowAmoeba(int x, int y)
2622 if (!MovDelay[x][y]) // next animation frame
2623 MovDelay[x][y] = frames * delay;
2625 if (MovDelay[x][y]) // wait some time before next frame
2628 int wall_mask = Store2[x][y];
2629 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2632 phase = MovDelay[x][y] / delay;
2634 if (!MovDelay[x][y])
2636 Tile[x][y] = real_element;
2637 Store[x][y] = Store2[x][y] = 0;
2639 DrawWalls_MM(x, y, Tile[x][y]);
2640 DrawLaser(0, DL_LASER_ENABLED);
2642 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2644 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2649 static void DrawFieldAnimated_MM(int x, int y)
2653 laser.redraw = TRUE;
2656 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2658 int element = Tile[x][y];
2659 int graphic = el2gfx(element);
2661 if (!getGraphicInfo_NewFrame(x, y, graphic))
2666 laser.redraw = TRUE;
2669 static void DrawFieldTwinkle(int x, int y)
2671 if (MovDelay[x][y] != 0) // wait some time before next frame
2677 if (MovDelay[x][y] != 0)
2679 int graphic = IMG_TWINKLE_WHITE;
2680 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2682 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2685 laser.redraw = TRUE;
2689 static void Explode_MM(int x, int y, int phase, int mode)
2691 int num_phase = 9, delay = 2;
2692 int last_phase = num_phase * delay;
2693 int half_phase = (num_phase / 2) * delay;
2695 laser.redraw = TRUE;
2697 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2699 int center_element = Tile[x][y];
2701 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2703 // put moving element to center field (and let it explode there)
2704 center_element = MovingOrBlocked2Element_MM(x, y);
2705 RemoveMovingField_MM(x, y);
2707 Tile[x][y] = center_element;
2710 Store[x][y] = center_element;
2711 Store2[x][y] = mode;
2713 Tile[x][y] = EL_EXPLODING_OPAQUE;
2715 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2716 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2719 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2721 ExplodePhase[x][y] = 1;
2727 GfxFrame[x][y] = 0; // restart explosion animation
2729 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2731 if (phase == half_phase)
2733 Tile[x][y] = EL_EXPLODING_TRANSP;
2735 if (x == ELX && y == ELY)
2739 if (phase == last_phase)
2741 if (Store[x][y] == EL_BOMB_ACTIVE)
2743 DrawLaser(0, DL_LASER_DISABLED);
2746 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2748 GameOver_MM(GAME_OVER_DELAYED);
2750 laser.overloaded = FALSE;
2752 else if (IS_MCDUFFIN(Store[x][y]))
2754 GameOver_MM(GAME_OVER_BOMB);
2757 Tile[x][y] = EL_EMPTY;
2759 Store[x][y] = Store2[x][y] = 0;
2760 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2762 InitField(x, y, FALSE);
2765 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2767 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2768 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2770 DrawGraphicAnimation_MM(x, y, graphic, frame);
2772 MarkTileDirty(x, y);
2776 static void Bang_MM(int x, int y)
2778 int element = Tile[x][y];
2781 DrawLaser(0, DL_LASER_ENABLED);
2784 if (IS_PACMAN(element))
2785 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2786 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2787 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2788 else if (element == EL_KEY)
2789 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2791 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2793 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2796 void TurnRound(int x, int y)
2808 { 0, 0 }, { 0, 0 }, { 0, 0 },
2813 int left, right, back;
2817 { MV_DOWN, MV_UP, MV_RIGHT },
2818 { MV_UP, MV_DOWN, MV_LEFT },
2820 { MV_LEFT, MV_RIGHT, MV_DOWN },
2821 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2822 { MV_RIGHT, MV_LEFT, MV_UP }
2825 int element = Tile[x][y];
2826 int old_move_dir = MovDir[x][y];
2827 int right_dir = turn[old_move_dir].right;
2828 int back_dir = turn[old_move_dir].back;
2829 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2830 int right_x = x + right_dx, right_y = y + right_dy;
2832 if (element == EL_PACMAN)
2834 boolean can_turn_right = FALSE;
2836 if (IN_LEV_FIELD(right_x, right_y) &&
2837 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2838 can_turn_right = TRUE;
2841 MovDir[x][y] = right_dir;
2843 MovDir[x][y] = back_dir;
2849 static void StartMoving_MM(int x, int y)
2851 int element = Tile[x][y];
2856 if (CAN_MOVE(element))
2860 if (MovDelay[x][y]) // wait some time before next movement
2868 // now make next step
2870 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2872 if (element == EL_PACMAN &&
2873 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2874 !ObjHit(newx, newy, HIT_POS_CENTER))
2876 Store[newx][newy] = Tile[newx][newy];
2877 Tile[newx][newy] = EL_EMPTY;
2879 DrawField_MM(newx, newy);
2881 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2882 ObjHit(newx, newy, HIT_POS_CENTER))
2884 // object was running against a wall
2891 InitMovingField_MM(x, y, MovDir[x][y]);
2895 ContinueMoving_MM(x, y);
2898 static void ContinueMoving_MM(int x, int y)
2900 int element = Tile[x][y];
2901 int direction = MovDir[x][y];
2902 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2903 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2904 int horiz_move = (dx!=0);
2905 int newx = x + dx, newy = y + dy;
2906 int step = (horiz_move ? dx : dy) * TILEX / 8;
2908 MovPos[x][y] += step;
2910 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2912 Tile[x][y] = EL_EMPTY;
2913 Tile[newx][newy] = element;
2915 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2916 MovDelay[newx][newy] = 0;
2918 if (!CAN_MOVE(element))
2919 MovDir[newx][newy] = 0;
2922 DrawField_MM(newx, newy);
2924 Stop[newx][newy] = TRUE;
2926 if (element == EL_PACMAN)
2928 if (Store[newx][newy] == EL_BOMB)
2929 Bang_MM(newx, newy);
2931 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2932 (LX + 2 * XS) / TILEX == newx &&
2933 (LY + 2 * YS) / TILEY == newy)
2940 else // still moving on
2945 laser.redraw = TRUE;
2948 boolean ClickElement(int x, int y, int button)
2950 static DelayCounter click_delay = { CLICK_DELAY };
2951 static boolean new_button = TRUE;
2952 boolean element_clicked = FALSE;
2957 // initialize static variables
2958 click_delay.count = 0;
2959 click_delay.value = CLICK_DELAY;
2965 // do not rotate objects hit by the laser after the game was solved
2966 if (game_mm.level_solved && Hit[x][y])
2969 if (button == MB_RELEASED)
2972 click_delay.value = CLICK_DELAY;
2974 // release eventually hold auto-rotating mirror
2975 RotateMirror(x, y, MB_RELEASED);
2980 if (!FrameReached(&click_delay) && !new_button)
2983 if (button == MB_MIDDLEBUTTON) // middle button has no function
2986 if (!IN_LEV_FIELD(x, y))
2989 if (Tile[x][y] == EL_EMPTY)
2992 element = Tile[x][y];
2994 if (IS_MIRROR(element) ||
2995 IS_BEAMER(element) ||
2996 IS_POLAR(element) ||
2997 IS_POLAR_CROSS(element) ||
2998 IS_DF_MIRROR(element) ||
2999 IS_DF_MIRROR_AUTO(element))
3001 RotateMirror(x, y, button);
3003 element_clicked = TRUE;
3005 else if (IS_MCDUFFIN(element))
3007 if (!laser.fuse_off)
3009 DrawLaser(0, DL_LASER_DISABLED);
3016 element = get_rotated_element(element, BUTTON_ROTATION(button));
3017 laser.start_angle = get_element_angle(element);
3021 Tile[x][y] = element;
3028 if (!laser.fuse_off)
3031 element_clicked = TRUE;
3033 else if (element == EL_FUSE_ON && laser.fuse_off)
3035 if (x != laser.fuse_x || y != laser.fuse_y)
3038 laser.fuse_off = FALSE;
3039 laser.fuse_x = laser.fuse_y = -1;
3041 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3044 element_clicked = TRUE;
3046 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3048 laser.fuse_off = TRUE;
3051 laser.overloaded = FALSE;
3053 DrawLaser(0, DL_LASER_DISABLED);
3054 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3056 element_clicked = TRUE;
3058 else if (element == EL_LIGHTBALL)
3061 RaiseScoreElement_MM(element);
3062 DrawLaser(0, DL_LASER_ENABLED);
3064 element_clicked = TRUE;
3066 else if (IS_ENVELOPE(element))
3068 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3070 element_clicked = TRUE;
3073 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3076 return element_clicked;
3079 void RotateMirror(int x, int y, int button)
3081 if (button == MB_RELEASED)
3083 // release eventually hold auto-rotating mirror
3090 if (IS_MIRROR(Tile[x][y]) ||
3091 IS_POLAR_CROSS(Tile[x][y]) ||
3092 IS_POLAR(Tile[x][y]) ||
3093 IS_BEAMER(Tile[x][y]) ||
3094 IS_DF_MIRROR(Tile[x][y]) ||
3095 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3096 IS_GRID_WOOD_AUTO(Tile[x][y]))
3098 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3100 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3102 if (button == MB_LEFTBUTTON)
3104 // left mouse button only for manual adjustment, no auto-rotating;
3105 // freeze mirror for until mouse button released
3109 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3111 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3115 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3117 int edge = Hit[x][y];
3123 DrawLaser(edge - 1, DL_LASER_DISABLED);
3127 else if (ObjHit(x, y, HIT_POS_CENTER))
3129 int edge = Hit[x][y];
3133 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3138 DrawLaser(edge - 1, DL_LASER_DISABLED);
3145 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3150 if ((IS_BEAMER(Tile[x][y]) ||
3151 IS_POLAR(Tile[x][y]) ||
3152 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3154 if (IS_BEAMER(Tile[x][y]))
3157 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3158 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3171 DrawLaser(0, DL_LASER_ENABLED);
3175 static void AutoRotateMirrors(void)
3179 if (!FrameReached(&rotate_delay))
3182 for (x = 0; x < lev_fieldx; x++)
3184 for (y = 0; y < lev_fieldy; y++)
3186 int element = Tile[x][y];
3188 // do not rotate objects hit by the laser after the game was solved
3189 if (game_mm.level_solved && Hit[x][y])
3192 if (IS_DF_MIRROR_AUTO(element) ||
3193 IS_GRID_WOOD_AUTO(element) ||
3194 IS_GRID_STEEL_AUTO(element) ||
3195 element == EL_REFRACTOR)
3196 RotateMirror(x, y, MB_RIGHTBUTTON);
3201 boolean ObjHit(int obx, int oby, int bits)
3208 if (bits & HIT_POS_CENTER)
3210 if (CheckLaserPixel(cSX + obx + 15,
3215 if (bits & HIT_POS_EDGE)
3217 for (i = 0; i < 4; i++)
3218 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3219 cSY + oby + 31 * (i / 2)))
3223 if (bits & HIT_POS_BETWEEN)
3225 for (i = 0; i < 4; i++)
3226 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3227 cSY + 4 + oby + 22 * (i / 2)))
3234 void DeletePacMan(int px, int py)
3240 if (game_mm.num_pacman <= 1)
3242 game_mm.num_pacman = 0;
3246 for (i = 0; i < game_mm.num_pacman; i++)
3247 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3250 game_mm.num_pacman--;
3252 for (j = i; j < game_mm.num_pacman; j++)
3254 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3255 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3256 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3260 void ColorCycling(void)
3262 static int CC, Cc = 0;
3264 static int color, old = 0xF00, new = 0x010, mult = 1;
3265 static unsigned short red, green, blue;
3267 if (color_status == STATIC_COLORS)
3272 if (CC < Cc || CC > Cc + 2)
3276 color = old + new * mult;
3282 if (ABS(mult) == 16)
3292 red = 0x0e00 * ((color & 0xF00) >> 8);
3293 green = 0x0e00 * ((color & 0x0F0) >> 4);
3294 blue = 0x0e00 * ((color & 0x00F));
3295 SetRGB(pen_magicolor[0], red, green, blue);
3297 red = 0x1111 * ((color & 0xF00) >> 8);
3298 green = 0x1111 * ((color & 0x0F0) >> 4);
3299 blue = 0x1111 * ((color & 0x00F));
3300 SetRGB(pen_magicolor[1], red, green, blue);
3304 static void GameActions_MM_Ext(void)
3311 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3314 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3316 element = Tile[x][y];
3318 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3319 StartMoving_MM(x, y);
3320 else if (IS_MOVING(x, y))
3321 ContinueMoving_MM(x, y);
3322 else if (IS_EXPLODING(element))
3323 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3324 else if (element == EL_EXIT_OPENING)
3326 else if (element == EL_GRAY_BALL_OPENING)
3327 OpenSurpriseBall(x, y);
3328 else if (IS_ENVELOPE_OPENING(element))
3330 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3332 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3334 else if (IS_MIRROR(element) ||
3335 IS_MIRROR_FIXED(element) ||
3336 element == EL_PRISM)
3337 DrawFieldTwinkle(x, y);
3338 else if (element == EL_GRAY_BALL_OPENING ||
3339 element == EL_BOMB_ACTIVE ||
3340 element == EL_MINE_ACTIVE)
3341 DrawFieldAnimated_MM(x, y);
3342 else if (!IS_BLOCKED(x, y))
3343 DrawFieldAnimatedIfNeeded_MM(x, y);
3346 AutoRotateMirrors();
3349 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3351 // redraw after Explode_MM() ...
3353 DrawLaser(0, DL_LASER_ENABLED);
3354 laser.redraw = FALSE;
3359 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3363 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3365 DrawLaser(0, DL_LASER_DISABLED);
3370 // skip all following game actions if game is over
3371 if (game_mm.game_over)
3374 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3378 GameOver_MM(GAME_OVER_NO_ENERGY);
3383 if (FrameReached(&energy_delay))
3385 if (game_mm.energy_left > 0)
3386 game_mm.energy_left--;
3388 // when out of energy, wait another frame to play "out of time" sound
3391 element = laser.dest_element;
3394 if (element != Tile[ELX][ELY])
3396 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3397 element, Tile[ELX][ELY]);
3401 if (!laser.overloaded && laser.overload_value == 0 &&
3402 element != EL_BOMB &&
3403 element != EL_BOMB_ACTIVE &&
3404 element != EL_MINE &&
3405 element != EL_MINE_ACTIVE &&
3406 element != EL_BALL_GRAY &&
3407 element != EL_BLOCK_STONE &&
3408 element != EL_BLOCK_WOOD &&
3409 element != EL_FUSE_ON &&
3410 element != EL_FUEL_FULL &&
3411 !IS_WALL_ICE(element) &&
3412 !IS_WALL_AMOEBA(element))
3415 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3417 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3418 (!laser.overloaded && laser.overload_value > 0)) &&
3419 FrameReached(&overload_delay))
3421 if (laser.overloaded)
3422 laser.overload_value++;
3424 laser.overload_value--;
3426 if (game_mm.cheat_no_overload)
3428 laser.overloaded = FALSE;
3429 laser.overload_value = 0;
3432 game_mm.laser_overload_value = laser.overload_value;
3434 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3436 SetLaserColor(0xFF);
3438 DrawLaser(0, DL_LASER_ENABLED);
3441 if (!laser.overloaded)
3442 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3443 else if (setup.sound_loops)
3444 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3446 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3448 if (laser.overloaded)
3451 BlitBitmap(pix[PIX_DOOR], drawto,
3452 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3453 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3454 - laser.overload_value,
3455 OVERLOAD_XSIZE, laser.overload_value,
3456 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3457 - laser.overload_value);
3459 redraw_mask |= REDRAW_DOOR_1;
3464 BlitBitmap(pix[PIX_DOOR], drawto,
3465 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3466 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3467 DX_OVERLOAD, DY_OVERLOAD);
3469 redraw_mask |= REDRAW_DOOR_1;
3472 if (laser.overload_value == MAX_LASER_OVERLOAD)
3474 UpdateAndDisplayGameControlValues();
3478 GameOver_MM(GAME_OVER_OVERLOADED);
3489 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3491 if (game_mm.cheat_no_explosion)
3496 laser.dest_element = EL_EXPLODING_OPAQUE;
3501 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3503 laser.fuse_off = TRUE;
3507 DrawLaser(0, DL_LASER_DISABLED);
3508 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3511 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3513 if (!Store2[ELX][ELY]) // check if content element not yet determined
3515 int last_anim_random_frame = gfx.anim_random_frame;
3518 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3519 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3521 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3522 native_mm_level.ball_choice_mode, 0,
3523 game_mm.ball_choice_pos);
3525 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3526 gfx.anim_random_frame = last_anim_random_frame;
3528 game_mm.ball_choice_pos++;
3530 int new_element = native_mm_level.ball_content[element_pos];
3531 int new_element_unmapped = unmap_element(new_element);
3533 if (IS_WALL(new_element_unmapped))
3535 // always use completely filled wall element
3536 new_element = new_element_unmapped | 0x000f;
3538 else if (native_mm_level.rotate_ball_content &&
3539 get_num_elements(new_element) > 1)
3541 // randomly rotate newly created game element
3542 new_element = get_rotated_element(new_element, RND(16));
3545 Store[ELX][ELY] = new_element;
3546 Store2[ELX][ELY] = TRUE;
3549 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3551 // !!! CHECK AGAIN: Laser on Polarizer !!!
3554 laser.dest_element_last = Tile[ELX][ELY];
3555 laser.dest_element_last_x = ELX;
3556 laser.dest_element_last_y = ELY;
3566 element = EL_MIRROR_START + RND(16);
3572 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3579 element = (rnd == 0 ? EL_FUSE_ON :
3580 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3581 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3582 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3583 EL_MIRROR_FIXED_START + rnd - 25);
3588 graphic = el2gfx(element);
3590 for (i = 0; i < 50; i++)
3596 BlitBitmap(pix[PIX_BACK], drawto,
3597 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3598 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3599 SX + ELX * TILEX + x,
3600 SY + ELY * TILEY + y);
3602 MarkTileDirty(ELX, ELY);
3605 DrawLaser(0, DL_LASER_ENABLED);
3607 Delay_WithScreenUpdates(50);
3610 Tile[ELX][ELY] = element;
3611 DrawField_MM(ELX, ELY);
3614 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3617 // above stuff: GRAY BALL -> PRISM !!!
3619 LX = ELX * TILEX + 14;
3620 LY = ELY * TILEY + 14;
3621 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3628 laser.num_edges -= 2;
3629 laser.num_damages--;
3633 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3634 if (laser.damage[i].is_mirror)
3638 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3640 DrawLaser(0, DL_LASER_DISABLED);
3642 DrawLaser(0, DL_LASER_DISABLED);
3651 if (IS_WALL_ICE(element) && CT > 50)
3653 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3656 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3657 Store[ELX][ELY] = EL_WALL_ICE;
3658 Store2[ELX][ELY] = laser.wall_mask;
3660 laser.dest_element = Tile[ELX][ELY];
3665 for (i = 0; i < 5; i++)
3671 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3675 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3677 Delay_WithScreenUpdates(100);
3680 if (Tile[ELX][ELY] == EL_WALL_ICE)
3681 Tile[ELX][ELY] = EL_EMPTY;
3685 LX = laser.edge[laser.num_edges].x - cSX2;
3686 LY = laser.edge[laser.num_edges].y - cSY2;
3689 ScanLaser_FromLastMirror();
3694 if (IS_WALL_AMOEBA(element) && CT > 60)
3696 int k1, k2, k3, dx, dy, de, dm;
3697 int element2 = Tile[ELX][ELY];
3699 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3702 for (i = laser.num_damages - 1; i >= 0; i--)
3703 if (laser.damage[i].is_mirror)
3706 r = laser.num_edges;
3707 d = laser.num_damages;
3714 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3717 DrawLaser(0, DL_LASER_ENABLED);
3720 x = laser.damage[k1].x;
3721 y = laser.damage[k1].y;
3726 for (i = 0; i < 4; i++)
3728 if (laser.wall_mask & (1 << i))
3730 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3731 cSY + ELY * TILEY + 31 * (i / 2)))
3734 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3735 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3742 for (i = 0; i < 4; i++)
3744 if (laser.wall_mask & (1 << i))
3746 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3747 cSY + ELY * TILEY + 31 * (i / 2)))
3754 if (laser.num_beamers > 0 ||
3755 k1 < 1 || k2 < 4 || k3 < 4 ||
3756 CheckLaserPixel(cSX + ELX * TILEX + 14,
3757 cSY + ELY * TILEY + 14))
3759 laser.num_edges = r;
3760 laser.num_damages = d;
3762 DrawLaser(0, DL_LASER_DISABLED);
3765 Tile[ELX][ELY] = element | laser.wall_mask;
3769 de = Tile[ELX][ELY];
3770 dm = laser.wall_mask;
3774 int x = ELX, y = ELY;
3775 int wall_mask = laser.wall_mask;
3778 DrawLaser(0, DL_LASER_ENABLED);
3780 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3782 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3783 Store[x][y] = EL_WALL_AMOEBA;
3784 Store2[x][y] = wall_mask;
3790 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3792 DrawLaser(0, DL_LASER_ENABLED);
3794 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3796 for (i = 4; i >= 0; i--)
3798 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3801 Delay_WithScreenUpdates(20);
3804 DrawLaser(0, DL_LASER_ENABLED);
3809 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3810 laser.stops_inside_element && CT > native_mm_level.time_block)
3815 if (ABS(XS) > ABS(YS))
3822 for (i = 0; i < 4; i++)
3829 x = ELX + Step[k * 4].x;
3830 y = ELY + Step[k * 4].y;
3832 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3835 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3843 laser.overloaded = (element == EL_BLOCK_STONE);
3848 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3851 Tile[x][y] = element;
3853 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3856 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3858 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3859 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3867 if (element == EL_FUEL_FULL && CT > 10)
3869 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3870 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3872 for (i = start; i <= num_init_game_frames; i++)
3874 if (i == num_init_game_frames)
3875 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3876 else if (setup.sound_loops)
3877 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3879 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3881 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3883 UpdateAndDisplayGameControlValues();
3888 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3890 DrawField_MM(ELX, ELY);
3892 DrawLaser(0, DL_LASER_ENABLED);
3900 void GameActions_MM(struct MouseActionInfo action)
3902 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3903 boolean button_released = (action.button == MB_RELEASED);
3905 GameActions_MM_Ext();
3907 CheckSingleStepMode_MM(element_clicked, button_released);
3910 void MovePacMen(void)
3912 int mx, my, ox, oy, nx, ny;
3916 if (++pacman_nr >= game_mm.num_pacman)
3919 game_mm.pacman[pacman_nr].dir--;
3921 for (l = 1; l < 5; l++)
3923 game_mm.pacman[pacman_nr].dir++;
3925 if (game_mm.pacman[pacman_nr].dir > 4)
3926 game_mm.pacman[pacman_nr].dir = 1;
3928 if (game_mm.pacman[pacman_nr].dir % 2)
3931 my = game_mm.pacman[pacman_nr].dir - 2;
3936 mx = 3 - game_mm.pacman[pacman_nr].dir;
3939 ox = game_mm.pacman[pacman_nr].x;
3940 oy = game_mm.pacman[pacman_nr].y;
3943 element = Tile[nx][ny];
3945 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3948 if (!IS_EATABLE4PACMAN(element))
3951 if (ObjHit(nx, ny, HIT_POS_CENTER))
3954 Tile[ox][oy] = EL_EMPTY;
3956 EL_PACMAN_RIGHT - 1 +
3957 (game_mm.pacman[pacman_nr].dir - 1 +
3958 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3960 game_mm.pacman[pacman_nr].x = nx;
3961 game_mm.pacman[pacman_nr].y = ny;
3963 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3965 if (element != EL_EMPTY)
3967 int graphic = el2gfx(Tile[nx][ny]);
3972 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3975 ox = cSX + ox * TILEX;
3976 oy = cSY + oy * TILEY;
3978 for (i = 1; i < 33; i += 2)
3979 BlitBitmap(bitmap, window,
3980 src_x, src_y, TILEX, TILEY,
3981 ox + i * mx, oy + i * my);
3982 Ct = Ct + FrameCounter - CT;
3985 DrawField_MM(nx, ny);
3988 if (!laser.fuse_off)
3990 DrawLaser(0, DL_LASER_ENABLED);
3992 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3994 AddDamagedField(nx, ny);
3996 laser.damage[laser.num_damages - 1].edge = 0;
4000 if (element == EL_BOMB)
4001 DeletePacMan(nx, ny);
4003 if (IS_WALL_AMOEBA(element) &&
4004 (LX + 2 * XS) / TILEX == nx &&
4005 (LY + 2 * YS) / TILEY == ny)
4015 static void InitMovingField_MM(int x, int y, int direction)
4017 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4018 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4020 MovDir[x][y] = direction;
4021 MovDir[newx][newy] = direction;
4023 if (Tile[newx][newy] == EL_EMPTY)
4024 Tile[newx][newy] = EL_BLOCKED;
4027 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4029 int direction = MovDir[x][y];
4030 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4031 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4037 static void Blocked2Moving_MM(int x, int y,
4038 int *comes_from_x, int *comes_from_y)
4040 int oldx = x, oldy = y;
4041 int direction = MovDir[x][y];
4043 if (direction == MV_LEFT)
4045 else if (direction == MV_RIGHT)
4047 else if (direction == MV_UP)
4049 else if (direction == MV_DOWN)
4052 *comes_from_x = oldx;
4053 *comes_from_y = oldy;
4056 static int MovingOrBlocked2Element_MM(int x, int y)
4058 int element = Tile[x][y];
4060 if (element == EL_BLOCKED)
4064 Blocked2Moving_MM(x, y, &oldx, &oldy);
4066 return Tile[oldx][oldy];
4073 static void RemoveField(int x, int y)
4075 Tile[x][y] = EL_EMPTY;
4082 static void RemoveMovingField_MM(int x, int y)
4084 int oldx = x, oldy = y, newx = x, newy = y;
4086 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4089 if (IS_MOVING(x, y))
4091 Moving2Blocked_MM(x, y, &newx, &newy);
4092 if (Tile[newx][newy] != EL_BLOCKED)
4095 else if (Tile[x][y] == EL_BLOCKED)
4097 Blocked2Moving_MM(x, y, &oldx, &oldy);
4098 if (!IS_MOVING(oldx, oldy))
4102 Tile[oldx][oldy] = EL_EMPTY;
4103 Tile[newx][newy] = EL_EMPTY;
4104 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4105 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4107 DrawLevelField_MM(oldx, oldy);
4108 DrawLevelField_MM(newx, newy);
4111 void PlaySoundLevel(int x, int y, int sound_nr)
4113 int sx = SCREENX(x), sy = SCREENY(y);
4115 int silence_distance = 8;
4117 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4118 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4121 if (!IN_LEV_FIELD(x, y) ||
4122 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4123 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4126 volume = SOUND_MAX_VOLUME;
4129 stereo = (sx - SCR_FIELDX/2) * 12;
4131 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4132 if (stereo > SOUND_MAX_RIGHT)
4133 stereo = SOUND_MAX_RIGHT;
4134 if (stereo < SOUND_MAX_LEFT)
4135 stereo = SOUND_MAX_LEFT;
4138 if (!IN_SCR_FIELD(sx, sy))
4140 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4141 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4143 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4146 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4149 static void RaiseScore_MM(int value)
4151 game_mm.score += value;
4154 void RaiseScoreElement_MM(int element)
4159 case EL_PACMAN_RIGHT:
4161 case EL_PACMAN_LEFT:
4162 case EL_PACMAN_DOWN:
4163 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4167 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4172 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4176 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4185 // ----------------------------------------------------------------------------
4186 // Mirror Magic game engine snapshot handling functions
4187 // ----------------------------------------------------------------------------
4189 void SaveEngineSnapshotValues_MM(void)
4193 engine_snapshot_mm.game_mm = game_mm;
4194 engine_snapshot_mm.laser = laser;
4196 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4198 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4200 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4201 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4202 engine_snapshot_mm.Box[x][y] = Box[x][y];
4203 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4207 engine_snapshot_mm.LX = LX;
4208 engine_snapshot_mm.LY = LY;
4209 engine_snapshot_mm.XS = XS;
4210 engine_snapshot_mm.YS = YS;
4211 engine_snapshot_mm.ELX = ELX;
4212 engine_snapshot_mm.ELY = ELY;
4213 engine_snapshot_mm.CT = CT;
4214 engine_snapshot_mm.Ct = Ct;
4216 engine_snapshot_mm.last_LX = last_LX;
4217 engine_snapshot_mm.last_LY = last_LY;
4218 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4219 engine_snapshot_mm.hold_x = hold_x;
4220 engine_snapshot_mm.hold_y = hold_y;
4221 engine_snapshot_mm.pacman_nr = pacman_nr;
4223 engine_snapshot_mm.rotate_delay = rotate_delay;
4224 engine_snapshot_mm.pacman_delay = pacman_delay;
4225 engine_snapshot_mm.energy_delay = energy_delay;
4226 engine_snapshot_mm.overload_delay = overload_delay;
4229 void LoadEngineSnapshotValues_MM(void)
4233 // stored engine snapshot buffers already restored at this point
4235 game_mm = engine_snapshot_mm.game_mm;
4236 laser = engine_snapshot_mm.laser;
4238 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4240 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4242 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4243 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4244 Box[x][y] = engine_snapshot_mm.Box[x][y];
4245 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4249 LX = engine_snapshot_mm.LX;
4250 LY = engine_snapshot_mm.LY;
4251 XS = engine_snapshot_mm.XS;
4252 YS = engine_snapshot_mm.YS;
4253 ELX = engine_snapshot_mm.ELX;
4254 ELY = engine_snapshot_mm.ELY;
4255 CT = engine_snapshot_mm.CT;
4256 Ct = engine_snapshot_mm.Ct;
4258 last_LX = engine_snapshot_mm.last_LX;
4259 last_LY = engine_snapshot_mm.last_LY;
4260 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4261 hold_x = engine_snapshot_mm.hold_x;
4262 hold_y = engine_snapshot_mm.hold_y;
4263 pacman_nr = engine_snapshot_mm.pacman_nr;
4265 rotate_delay = engine_snapshot_mm.rotate_delay;
4266 pacman_delay = engine_snapshot_mm.pacman_delay;
4267 energy_delay = engine_snapshot_mm.energy_delay;
4268 overload_delay = engine_snapshot_mm.overload_delay;
4270 RedrawPlayfield_MM();
4273 static int getAngleFromTouchDelta(int dx, int dy, int base)
4275 double pi = 3.141592653;
4276 double rad = atan2((double)-dy, (double)dx);
4277 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4278 double deg = rad2 * 180.0 / pi;
4280 return (int)(deg * base / 360.0 + 0.5) % base;
4283 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4285 // calculate start (source) position to be at the middle of the tile
4286 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4287 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4288 int dx = dst_mx - src_mx;
4289 int dy = dst_my - src_my;
4298 if (!IN_LEV_FIELD(x, y))
4301 element = Tile[x][y];
4303 if (!IS_MCDUFFIN(element) &&
4304 !IS_MIRROR(element) &&
4305 !IS_BEAMER(element) &&
4306 !IS_POLAR(element) &&
4307 !IS_POLAR_CROSS(element) &&
4308 !IS_DF_MIRROR(element))
4311 angle_old = get_element_angle(element);
4313 if (IS_MCDUFFIN(element))
4315 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4316 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4317 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4318 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4321 else if (IS_MIRROR(element) ||
4322 IS_DF_MIRROR(element))
4324 for (i = 0; i < laser.num_damages; i++)
4326 if (laser.damage[i].x == x &&
4327 laser.damage[i].y == y &&
4328 ObjHit(x, y, HIT_POS_CENTER))
4330 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4331 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4338 if (angle_new == -1)
4340 if (IS_MIRROR(element) ||
4341 IS_DF_MIRROR(element) ||
4345 if (IS_POLAR_CROSS(element))
4348 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4351 button = (angle_new == angle_old ? 0 :
4352 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4353 MB_LEFTBUTTON : MB_RIGHTBUTTON);