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 DrawLaserExt(int start_edge, int num_edges, int mode)
1194 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1195 start_edge, num_edges, mode);
1200 Warn("DrawLaserExt: start_edge < 0");
1207 Warn("DrawLaserExt: num_edges < 0");
1213 if (mode == DL_LASER_DISABLED)
1215 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1219 // now draw the laser to the backbuffer and (if enabled) to the screen
1220 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1222 redraw_mask |= REDRAW_FIELD;
1224 if (mode == DL_LASER_ENABLED)
1227 // after the laser was deleted, the "damaged" graphics must be restored
1228 if (laser.num_damages)
1230 int damage_start = 0;
1233 // determine the starting edge, from which graphics need to be restored
1236 for (i = 0; i < laser.num_damages; i++)
1238 if (laser.damage[i].edge == start_edge + 1)
1247 // restore graphics from this starting edge to the end of damage list
1248 for (i = damage_start; i < laser.num_damages; i++)
1250 int lx = laser.damage[i].x;
1251 int ly = laser.damage[i].y;
1252 int element = Tile[lx][ly];
1254 if (Hit[lx][ly] == laser.damage[i].edge)
1255 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1258 if (Box[lx][ly] == laser.damage[i].edge)
1261 if (IS_DRAWABLE(element))
1262 DrawField_MM(lx, ly);
1265 elx = laser.damage[damage_start].x;
1266 ely = laser.damage[damage_start].y;
1267 element = Tile[elx][ely];
1270 if (IS_BEAMER(element))
1274 for (i = 0; i < laser.num_beamers; i++)
1275 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1277 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1278 mode, elx, ely, Hit[elx][ely], start_edge);
1279 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1280 get_element_angle(element), laser.damage[damage_start].angle);
1284 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1285 laser.num_beamers > 0 &&
1286 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1288 // element is outgoing beamer
1289 laser.num_damages = damage_start + 1;
1291 if (IS_BEAMER(element))
1292 laser.current_angle = get_element_angle(element);
1296 // element is incoming beamer or other element
1297 laser.num_damages = damage_start;
1298 laser.current_angle = laser.damage[laser.num_damages].angle;
1303 // no damages but McDuffin himself (who needs to be redrawn anyway)
1305 elx = laser.start_edge.x;
1306 ely = laser.start_edge.y;
1307 element = Tile[elx][ely];
1310 laser.num_edges = start_edge + 1;
1311 if (start_edge == 0)
1312 laser.current_angle = laser.start_angle;
1314 LX = laser.edge[start_edge].x - cSX2;
1315 LY = laser.edge[start_edge].y - cSY2;
1316 XS = 2 * Step[laser.current_angle].x;
1317 YS = 2 * Step[laser.current_angle].y;
1320 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1326 if (IS_BEAMER(element) ||
1327 IS_FIBRE_OPTIC(element) ||
1328 IS_PACMAN(element) ||
1329 IS_POLAR(element) ||
1330 IS_POLAR_CROSS(element) ||
1331 element == EL_FUSE_ON)
1336 Debug("game:mm:DrawLaserExt", "element == %d", element);
1339 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1340 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1344 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1345 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1346 (laser.num_beamers == 0 ||
1347 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1349 // element is incoming beamer or other element
1350 step_size = -step_size;
1355 if (IS_BEAMER(element))
1356 Debug("game:mm:DrawLaserExt",
1357 "start_edge == %d, laser.beamer_edge == %d",
1358 start_edge, laser.beamer_edge);
1361 LX += step_size * XS;
1362 LY += step_size * YS;
1364 else if (element != EL_EMPTY)
1373 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1378 void DrawLaser(int start_edge, int mode)
1380 // do not draw laser if fuse is off
1381 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1384 if (mode == DL_LASER_DISABLED)
1385 DeactivateLaserTargetElement();
1387 if (laser.num_edges - start_edge < 0)
1389 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1394 // check if laser is interrupted by beamer element
1395 if (laser.num_beamers > 0 &&
1396 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1398 if (mode == DL_LASER_ENABLED)
1401 int tmp_start_edge = start_edge;
1403 // draw laser segments forward from the start to the last beamer
1404 for (i = 0; i < laser.num_beamers; i++)
1406 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1408 if (tmp_num_edges <= 0)
1412 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1413 i, laser.beamer_edge[i], tmp_start_edge);
1416 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1418 tmp_start_edge = laser.beamer_edge[i];
1421 // draw last segment from last beamer to the end
1422 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1428 int last_num_edges = laser.num_edges;
1429 int num_beamers = laser.num_beamers;
1431 // delete laser segments backward from the end to the first beamer
1432 for (i = num_beamers - 1; i >= 0; i--)
1434 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1436 if (laser.beamer_edge[i] - start_edge <= 0)
1439 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1441 last_num_edges = laser.beamer_edge[i];
1442 laser.num_beamers--;
1446 if (last_num_edges - start_edge <= 0)
1447 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1448 last_num_edges, start_edge);
1451 // special case when rotating first beamer: delete laser edge on beamer
1452 // (but do not start scanning on previous edge to prevent mirror sound)
1453 if (last_num_edges - start_edge == 1 && start_edge > 0)
1454 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1456 // delete first segment from start to the first beamer
1457 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1462 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1465 game_mm.laser_enabled = mode;
1468 void DrawLaser_MM(void)
1470 DrawLaser(0, game_mm.laser_enabled);
1473 boolean HitElement(int element, int hit_mask)
1475 if (HitOnlyAnEdge(hit_mask))
1478 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1479 element = MovingOrBlocked2Element_MM(ELX, ELY);
1482 Debug("game:mm:HitElement", "(1): element == %d", element);
1486 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1487 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1490 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1494 AddDamagedField(ELX, ELY);
1496 // this is more precise: check if laser would go through the center
1497 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1501 // prevent cutting through laser emitter with laser beam
1502 if (IS_LASER(element))
1505 // skip the whole element before continuing the scan
1513 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1515 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1517 /* skipping scan positions to the right and down skips one scan
1518 position too much, because this is only the top left scan position
1519 of totally four scan positions (plus one to the right, one to the
1520 bottom and one to the bottom right) */
1521 /* ... but only roll back scan position if more than one step done */
1531 Debug("game:mm:HitElement", "(2): element == %d", element);
1534 if (LX + 5 * XS < 0 ||
1544 Debug("game:mm:HitElement", "(3): element == %d", element);
1547 if (IS_POLAR(element) &&
1548 ((element - EL_POLAR_START) % 2 ||
1549 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1551 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1553 laser.num_damages--;
1558 if (IS_POLAR_CROSS(element) &&
1559 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1561 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1563 laser.num_damages--;
1568 if (!IS_BEAMER(element) &&
1569 !IS_FIBRE_OPTIC(element) &&
1570 !IS_GRID_WOOD(element) &&
1571 element != EL_FUEL_EMPTY)
1574 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1575 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1577 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1580 LX = ELX * TILEX + 14;
1581 LY = ELY * TILEY + 14;
1583 AddLaserEdge(LX, LY);
1586 if (IS_MIRROR(element) ||
1587 IS_MIRROR_FIXED(element) ||
1588 IS_POLAR(element) ||
1589 IS_POLAR_CROSS(element) ||
1590 IS_DF_MIRROR(element) ||
1591 IS_DF_MIRROR_AUTO(element) ||
1592 element == EL_PRISM ||
1593 element == EL_REFRACTOR)
1595 int current_angle = laser.current_angle;
1598 laser.num_damages--;
1600 AddDamagedField(ELX, ELY);
1602 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1605 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1607 if (IS_MIRROR(element) ||
1608 IS_MIRROR_FIXED(element) ||
1609 IS_DF_MIRROR(element) ||
1610 IS_DF_MIRROR_AUTO(element))
1611 laser.current_angle = get_mirrored_angle(laser.current_angle,
1612 get_element_angle(element));
1614 if (element == EL_PRISM || element == EL_REFRACTOR)
1615 laser.current_angle = RND(16);
1617 XS = 2 * Step[laser.current_angle].x;
1618 YS = 2 * Step[laser.current_angle].y;
1620 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1625 LX += step_size * XS;
1626 LY += step_size * YS;
1628 // draw sparkles on mirror
1629 if ((IS_MIRROR(element) ||
1630 IS_MIRROR_FIXED(element) ||
1631 element == EL_PRISM) &&
1632 current_angle != laser.current_angle)
1634 MovDelay[ELX][ELY] = 11; // start animation
1637 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1638 current_angle != laser.current_angle)
1639 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1642 (get_opposite_angle(laser.current_angle) ==
1643 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1645 return (laser.overloaded ? TRUE : FALSE);
1648 if (element == EL_FUEL_FULL)
1650 laser.stops_inside_element = TRUE;
1655 if (element == EL_BOMB || element == EL_MINE)
1657 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1659 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1661 laser.dest_element_last = Tile[ELX][ELY];
1662 laser.dest_element_last_x = ELX;
1663 laser.dest_element_last_y = ELY;
1665 if (element == EL_MINE)
1666 laser.overloaded = TRUE;
1669 if (element == EL_KETTLE ||
1670 element == EL_CELL ||
1671 element == EL_KEY ||
1672 element == EL_LIGHTBALL ||
1673 element == EL_PACMAN ||
1674 IS_PACMAN(element) ||
1675 IS_ENVELOPE(element))
1677 if (!IS_PACMAN(element) &&
1678 !IS_ENVELOPE(element))
1681 if (element == EL_PACMAN)
1684 if (element == EL_KETTLE || element == EL_CELL)
1686 if (game_mm.kettles_still_needed > 0)
1687 game_mm.kettles_still_needed--;
1689 game.snapshot.collected_item = TRUE;
1691 if (game_mm.kettles_still_needed == 0)
1695 DrawLaser(0, DL_LASER_ENABLED);
1698 else if (element == EL_KEY)
1702 else if (IS_PACMAN(element))
1704 DeletePacMan(ELX, ELY);
1706 else if (IS_ENVELOPE(element))
1708 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1711 RaiseScoreElement_MM(element);
1716 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1718 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1720 DrawLaser(0, DL_LASER_ENABLED);
1722 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1724 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1725 game_mm.lights_still_needed--;
1729 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1730 game_mm.lights_still_needed++;
1733 DrawField_MM(ELX, ELY);
1734 DrawLaser(0, DL_LASER_ENABLED);
1739 laser.stops_inside_element = TRUE;
1745 Debug("game:mm:HitElement", "(4): element == %d", element);
1748 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1749 laser.num_beamers < MAX_NUM_BEAMERS &&
1750 laser.beamer[BEAMER_NR(element)][1].num)
1752 int beamer_angle = get_element_angle(element);
1753 int beamer_nr = BEAMER_NR(element);
1757 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1760 laser.num_damages--;
1762 if (IS_FIBRE_OPTIC(element) ||
1763 laser.current_angle == get_opposite_angle(beamer_angle))
1767 LX = ELX * TILEX + 14;
1768 LY = ELY * TILEY + 14;
1770 AddLaserEdge(LX, LY);
1771 AddDamagedField(ELX, ELY);
1773 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1776 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1778 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1779 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1780 ELX = laser.beamer[beamer_nr][pos].x;
1781 ELY = laser.beamer[beamer_nr][pos].y;
1782 LX = ELX * TILEX + 14;
1783 LY = ELY * TILEY + 14;
1785 if (IS_BEAMER(element))
1787 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1788 XS = 2 * Step[laser.current_angle].x;
1789 YS = 2 * Step[laser.current_angle].y;
1792 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1794 AddLaserEdge(LX, LY);
1795 AddDamagedField(ELX, ELY);
1797 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1800 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1802 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1807 LX += step_size * XS;
1808 LY += step_size * YS;
1810 laser.num_beamers++;
1819 boolean HitOnlyAnEdge(int hit_mask)
1821 // check if the laser hit only the edge of an element and, if so, go on
1824 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1828 if ((hit_mask == HIT_MASK_TOPLEFT ||
1829 hit_mask == HIT_MASK_TOPRIGHT ||
1830 hit_mask == HIT_MASK_BOTTOMLEFT ||
1831 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1832 laser.current_angle % 4) // angle is not 90°
1836 if (hit_mask == HIT_MASK_TOPLEFT)
1841 else if (hit_mask == HIT_MASK_TOPRIGHT)
1846 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1851 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1857 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1863 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1870 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1876 boolean HitPolarizer(int element, int hit_mask)
1878 if (HitOnlyAnEdge(hit_mask))
1881 if (IS_DF_GRID(element))
1883 int grid_angle = get_element_angle(element);
1886 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1887 grid_angle, laser.current_angle);
1890 AddLaserEdge(LX, LY);
1891 AddDamagedField(ELX, ELY);
1894 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1896 if (laser.current_angle == grid_angle ||
1897 laser.current_angle == get_opposite_angle(grid_angle))
1899 // skip the whole element before continuing the scan
1905 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1907 if (LX/TILEX > ELX || LY/TILEY > ELY)
1909 /* skipping scan positions to the right and down skips one scan
1910 position too much, because this is only the top left scan position
1911 of totally four scan positions (plus one to the right, one to the
1912 bottom and one to the bottom right) */
1918 AddLaserEdge(LX, LY);
1924 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1926 LX / TILEX, LY / TILEY,
1927 LX % TILEX, LY % TILEY);
1932 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1934 return HitReflectingWalls(element, hit_mask);
1938 return HitAbsorbingWalls(element, hit_mask);
1941 else if (IS_GRID_STEEL(element))
1943 return HitReflectingWalls(element, hit_mask);
1945 else // IS_GRID_WOOD
1947 return HitAbsorbingWalls(element, hit_mask);
1953 boolean HitBlock(int element, int hit_mask)
1955 boolean check = FALSE;
1957 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1958 game_mm.num_keys == 0)
1961 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1964 int ex = ELX * TILEX + 14;
1965 int ey = ELY * TILEY + 14;
1969 for (i = 1; i < 32; i++)
1974 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1979 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1980 return HitAbsorbingWalls(element, hit_mask);
1984 AddLaserEdge(LX - XS, LY - YS);
1985 AddDamagedField(ELX, ELY);
1988 Box[ELX][ELY] = laser.num_edges;
1990 return HitReflectingWalls(element, hit_mask);
1993 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1995 int xs = XS / 2, ys = YS / 2;
1996 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1997 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1999 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2000 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2002 laser.overloaded = (element == EL_GATE_STONE);
2007 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2008 (hit_mask == HIT_MASK_TOP ||
2009 hit_mask == HIT_MASK_LEFT ||
2010 hit_mask == HIT_MASK_RIGHT ||
2011 hit_mask == HIT_MASK_BOTTOM))
2012 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2013 hit_mask == HIT_MASK_BOTTOM),
2014 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2015 hit_mask == HIT_MASK_RIGHT));
2016 AddLaserEdge(LX, LY);
2022 if (element == EL_GATE_STONE && Box[ELX][ELY])
2024 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2036 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2038 int xs = XS / 2, ys = YS / 2;
2039 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2040 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2042 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2043 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2045 laser.overloaded = (element == EL_BLOCK_STONE);
2050 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2051 (hit_mask == HIT_MASK_TOP ||
2052 hit_mask == HIT_MASK_LEFT ||
2053 hit_mask == HIT_MASK_RIGHT ||
2054 hit_mask == HIT_MASK_BOTTOM))
2055 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2056 hit_mask == HIT_MASK_BOTTOM),
2057 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2058 hit_mask == HIT_MASK_RIGHT));
2059 AddDamagedField(ELX, ELY);
2061 LX = ELX * TILEX + 14;
2062 LY = ELY * TILEY + 14;
2064 AddLaserEdge(LX, LY);
2066 laser.stops_inside_element = TRUE;
2074 boolean HitLaserSource(int element, int hit_mask)
2076 if (HitOnlyAnEdge(hit_mask))
2079 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2081 laser.overloaded = TRUE;
2086 boolean HitLaserDestination(int element, int hit_mask)
2088 if (HitOnlyAnEdge(hit_mask))
2091 if (element != EL_EXIT_OPEN &&
2092 !(IS_RECEIVER(element) &&
2093 game_mm.kettles_still_needed == 0 &&
2094 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2096 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2101 if (IS_RECEIVER(element) ||
2102 (IS_22_5_ANGLE(laser.current_angle) &&
2103 (ELX != (LX + 6 * XS) / TILEX ||
2104 ELY != (LY + 6 * YS) / TILEY ||
2113 LX = ELX * TILEX + 14;
2114 LY = ELY * TILEY + 14;
2116 laser.stops_inside_element = TRUE;
2119 AddLaserEdge(LX, LY);
2120 AddDamagedField(ELX, ELY);
2122 if (game_mm.lights_still_needed == 0)
2124 game_mm.level_solved = TRUE;
2126 SetTileCursorActive(FALSE);
2132 boolean HitReflectingWalls(int element, int hit_mask)
2134 // check if laser hits side of a wall with an angle that is not 90°
2135 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2136 hit_mask == HIT_MASK_LEFT ||
2137 hit_mask == HIT_MASK_RIGHT ||
2138 hit_mask == HIT_MASK_BOTTOM))
2140 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2145 if (!IS_DF_GRID(element))
2146 AddLaserEdge(LX, LY);
2148 // check if laser hits wall with an angle of 45°
2149 if (!IS_22_5_ANGLE(laser.current_angle))
2151 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2154 laser.current_angle = get_mirrored_angle(laser.current_angle,
2157 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2160 laser.current_angle = get_mirrored_angle(laser.current_angle,
2164 AddLaserEdge(LX, LY);
2166 XS = 2 * Step[laser.current_angle].x;
2167 YS = 2 * Step[laser.current_angle].y;
2171 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2173 laser.current_angle = get_mirrored_angle(laser.current_angle,
2178 if (!IS_DF_GRID(element))
2179 AddLaserEdge(LX, LY);
2184 if (!IS_DF_GRID(element))
2185 AddLaserEdge(LX, LY + YS / 2);
2188 if (!IS_DF_GRID(element))
2189 AddLaserEdge(LX, LY);
2192 YS = 2 * Step[laser.current_angle].y;
2196 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2198 laser.current_angle = get_mirrored_angle(laser.current_angle,
2203 if (!IS_DF_GRID(element))
2204 AddLaserEdge(LX, LY);
2209 if (!IS_DF_GRID(element))
2210 AddLaserEdge(LX + XS / 2, LY);
2213 if (!IS_DF_GRID(element))
2214 AddLaserEdge(LX, LY);
2217 XS = 2 * Step[laser.current_angle].x;
2223 // reflection at the edge of reflecting DF style wall
2224 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2226 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2227 hit_mask == HIT_MASK_TOPRIGHT) ||
2228 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2229 hit_mask == HIT_MASK_TOPLEFT) ||
2230 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2231 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2232 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2233 hit_mask == HIT_MASK_BOTTOMRIGHT))
2236 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2237 ANG_MIRROR_135 : ANG_MIRROR_45);
2239 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2241 AddDamagedField(ELX, ELY);
2242 AddLaserEdge(LX, LY);
2244 laser.current_angle = get_mirrored_angle(laser.current_angle,
2252 AddLaserEdge(LX, LY);
2258 // reflection inside an edge of reflecting DF style wall
2259 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2261 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2262 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2263 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2264 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2265 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2266 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2267 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2268 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2271 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2272 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2273 ANG_MIRROR_135 : ANG_MIRROR_45);
2275 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2278 AddDamagedField(ELX, ELY);
2281 AddLaserEdge(LX - XS, LY - YS);
2282 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2283 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2285 laser.current_angle = get_mirrored_angle(laser.current_angle,
2293 AddLaserEdge(LX, LY);
2299 // check if laser hits DF style wall with an angle of 90°
2300 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2302 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2303 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2304 (IS_VERT_ANGLE(laser.current_angle) &&
2305 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2307 // laser at last step touched nothing or the same side of the wall
2308 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2310 AddDamagedField(ELX, ELY);
2317 last_hit_mask = hit_mask;
2324 if (!HitOnlyAnEdge(hit_mask))
2326 laser.overloaded = TRUE;
2334 boolean HitAbsorbingWalls(int element, int hit_mask)
2336 if (HitOnlyAnEdge(hit_mask))
2340 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2342 AddLaserEdge(LX - XS, LY - YS);
2349 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2351 AddLaserEdge(LX - XS, LY - YS);
2357 if (IS_WALL_WOOD(element) ||
2358 IS_DF_WALL_WOOD(element) ||
2359 IS_GRID_WOOD(element) ||
2360 IS_GRID_WOOD_FIXED(element) ||
2361 IS_GRID_WOOD_AUTO(element) ||
2362 element == EL_FUSE_ON ||
2363 element == EL_BLOCK_WOOD ||
2364 element == EL_GATE_WOOD)
2366 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2371 if (IS_WALL_ICE(element))
2375 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2376 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2378 // check if laser hits wall with an angle of 90°
2379 if (IS_90_ANGLE(laser.current_angle))
2380 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2382 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2386 for (i = 0; i < 4; i++)
2388 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2389 mask = 15 - (8 >> i);
2390 else if (ABS(XS) == 4 &&
2392 (XS > 0) == (i % 2) &&
2393 (YS < 0) == (i / 2))
2394 mask = 3 + (i / 2) * 9;
2395 else if (ABS(YS) == 4 &&
2397 (XS < 0) == (i % 2) &&
2398 (YS > 0) == (i / 2))
2399 mask = 5 + (i % 2) * 5;
2403 laser.wall_mask = mask;
2405 else if (IS_WALL_AMOEBA(element))
2407 int elx = (LX - 2 * XS) / TILEX;
2408 int ely = (LY - 2 * YS) / TILEY;
2409 int element2 = Tile[elx][ely];
2412 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2414 laser.dest_element = EL_EMPTY;
2422 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2423 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2425 if (IS_90_ANGLE(laser.current_angle))
2426 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2428 laser.dest_element = element2 | EL_WALL_AMOEBA;
2430 laser.wall_mask = mask;
2436 static void OpenExit(int x, int y)
2440 if (!MovDelay[x][y]) // next animation frame
2441 MovDelay[x][y] = 4 * delay;
2443 if (MovDelay[x][y]) // wait some time before next frame
2448 phase = MovDelay[x][y] / delay;
2450 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2451 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2453 if (!MovDelay[x][y])
2455 Tile[x][y] = EL_EXIT_OPEN;
2461 static void OpenSurpriseBall(int x, int y)
2465 if (!MovDelay[x][y]) // next animation frame
2467 if (IS_WALL(Store[x][y]))
2469 DrawWalls_MM(x, y, Store[x][y]);
2471 // copy wall tile to spare bitmap for "melting" animation
2472 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2473 TILEX, TILEY, x * TILEX, y * TILEY);
2475 DrawElement_MM(x, y, EL_BALL_GRAY);
2478 MovDelay[x][y] = 50 * delay;
2481 if (MovDelay[x][y]) // wait some time before next frame
2485 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2489 int dx = RND(26), dy = RND(26);
2491 if (IS_WALL(Store[x][y]))
2493 // copy wall tile from spare bitmap for "melting" animation
2494 bitmap = bitmap_db_field;
2500 int graphic = el2gfx(Store[x][y]);
2502 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2505 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2506 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2508 laser.redraw = TRUE;
2510 MarkTileDirty(x, y);
2513 if (!MovDelay[x][y])
2517 Tile[x][y] = Store[x][y];
2518 Store[x][y] = Store2[x][y] = 0;
2519 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2521 InitField(x, y, FALSE);
2524 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2525 if (laser.damage[i].is_mirror)
2529 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2531 DrawLaser(0, DL_LASER_DISABLED);
2538 static void OpenEnvelope(int x, int y)
2540 int num_frames = 8; // seven frames plus final empty space
2542 if (!MovDelay[x][y]) // next animation frame
2543 MovDelay[x][y] = num_frames;
2545 if (MovDelay[x][y]) // wait some time before next frame
2547 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2551 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2553 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2554 int frame = num_frames - MovDelay[x][y] - 1;
2556 DrawGraphicAnimation_MM(x, y, graphic, frame);
2558 laser.redraw = TRUE;
2561 if (MovDelay[x][y] == 0)
2563 Tile[x][y] = EL_EMPTY;
2569 ShowEnvelope_MM(nr);
2574 static void MeltIce(int x, int y)
2579 if (!MovDelay[x][y]) // next animation frame
2580 MovDelay[x][y] = frames * delay;
2582 if (MovDelay[x][y]) // wait some time before next frame
2585 int wall_mask = Store2[x][y];
2586 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2589 phase = frames - MovDelay[x][y] / delay - 1;
2591 if (!MovDelay[x][y])
2595 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2596 Store[x][y] = Store2[x][y] = 0;
2598 DrawWalls_MM(x, y, Tile[x][y]);
2600 if (Tile[x][y] == EL_WALL_ICE)
2601 Tile[x][y] = EL_EMPTY;
2603 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2604 if (laser.damage[i].is_mirror)
2608 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2610 DrawLaser(0, DL_LASER_DISABLED);
2614 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2616 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2618 laser.redraw = TRUE;
2623 static void GrowAmoeba(int x, int y)
2628 if (!MovDelay[x][y]) // next animation frame
2629 MovDelay[x][y] = frames * delay;
2631 if (MovDelay[x][y]) // wait some time before next frame
2634 int wall_mask = Store2[x][y];
2635 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2638 phase = MovDelay[x][y] / delay;
2640 if (!MovDelay[x][y])
2642 Tile[x][y] = real_element;
2643 Store[x][y] = Store2[x][y] = 0;
2645 DrawWalls_MM(x, y, Tile[x][y]);
2646 DrawLaser(0, DL_LASER_ENABLED);
2648 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2650 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2655 static void DrawFieldAnimated_MM(int x, int y)
2659 laser.redraw = TRUE;
2662 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2664 int element = Tile[x][y];
2665 int graphic = el2gfx(element);
2667 if (!getGraphicInfo_NewFrame(x, y, graphic))
2672 laser.redraw = TRUE;
2675 static void DrawFieldTwinkle(int x, int y)
2677 if (MovDelay[x][y] != 0) // wait some time before next frame
2683 if (MovDelay[x][y] != 0)
2685 int graphic = IMG_TWINKLE_WHITE;
2686 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2688 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2691 laser.redraw = TRUE;
2695 static void Explode_MM(int x, int y, int phase, int mode)
2697 int num_phase = 9, delay = 2;
2698 int last_phase = num_phase * delay;
2699 int half_phase = (num_phase / 2) * delay;
2701 laser.redraw = TRUE;
2703 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2705 int center_element = Tile[x][y];
2707 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2709 // put moving element to center field (and let it explode there)
2710 center_element = MovingOrBlocked2Element_MM(x, y);
2711 RemoveMovingField_MM(x, y);
2713 Tile[x][y] = center_element;
2716 Store[x][y] = center_element;
2717 Store2[x][y] = mode;
2719 Tile[x][y] = EL_EXPLODING_OPAQUE;
2721 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2722 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2725 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2727 ExplodePhase[x][y] = 1;
2733 GfxFrame[x][y] = 0; // restart explosion animation
2735 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2737 if (phase == half_phase)
2739 Tile[x][y] = EL_EXPLODING_TRANSP;
2741 if (x == ELX && y == ELY)
2745 if (phase == last_phase)
2747 if (Store[x][y] == EL_BOMB_ACTIVE)
2749 DrawLaser(0, DL_LASER_DISABLED);
2752 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2754 GameOver_MM(GAME_OVER_DELAYED);
2756 laser.overloaded = FALSE;
2758 else if (IS_MCDUFFIN(Store[x][y]))
2760 GameOver_MM(GAME_OVER_BOMB);
2763 Tile[x][y] = EL_EMPTY;
2765 Store[x][y] = Store2[x][y] = 0;
2766 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2768 InitField(x, y, FALSE);
2771 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2773 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2774 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2776 DrawGraphicAnimation_MM(x, y, graphic, frame);
2778 MarkTileDirty(x, y);
2782 static void Bang_MM(int x, int y)
2784 int element = Tile[x][y];
2787 DrawLaser(0, DL_LASER_ENABLED);
2790 if (IS_PACMAN(element))
2791 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2792 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2793 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2794 else if (element == EL_KEY)
2795 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2797 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2799 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2802 void TurnRound(int x, int y)
2814 { 0, 0 }, { 0, 0 }, { 0, 0 },
2819 int left, right, back;
2823 { MV_DOWN, MV_UP, MV_RIGHT },
2824 { MV_UP, MV_DOWN, MV_LEFT },
2826 { MV_LEFT, MV_RIGHT, MV_DOWN },
2827 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2828 { MV_RIGHT, MV_LEFT, MV_UP }
2831 int element = Tile[x][y];
2832 int old_move_dir = MovDir[x][y];
2833 int right_dir = turn[old_move_dir].right;
2834 int back_dir = turn[old_move_dir].back;
2835 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2836 int right_x = x + right_dx, right_y = y + right_dy;
2838 if (element == EL_PACMAN)
2840 boolean can_turn_right = FALSE;
2842 if (IN_LEV_FIELD(right_x, right_y) &&
2843 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2844 can_turn_right = TRUE;
2847 MovDir[x][y] = right_dir;
2849 MovDir[x][y] = back_dir;
2855 static void StartMoving_MM(int x, int y)
2857 int element = Tile[x][y];
2862 if (CAN_MOVE(element))
2866 if (MovDelay[x][y]) // wait some time before next movement
2874 // now make next step
2876 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2878 if (element == EL_PACMAN &&
2879 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2880 !ObjHit(newx, newy, HIT_POS_CENTER))
2882 Store[newx][newy] = Tile[newx][newy];
2883 Tile[newx][newy] = EL_EMPTY;
2885 DrawField_MM(newx, newy);
2887 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2888 ObjHit(newx, newy, HIT_POS_CENTER))
2890 // object was running against a wall
2897 InitMovingField_MM(x, y, MovDir[x][y]);
2901 ContinueMoving_MM(x, y);
2904 static void ContinueMoving_MM(int x, int y)
2906 int element = Tile[x][y];
2907 int direction = MovDir[x][y];
2908 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2909 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2910 int horiz_move = (dx!=0);
2911 int newx = x + dx, newy = y + dy;
2912 int step = (horiz_move ? dx : dy) * TILEX / 8;
2914 MovPos[x][y] += step;
2916 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2918 Tile[x][y] = EL_EMPTY;
2919 Tile[newx][newy] = element;
2921 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2922 MovDelay[newx][newy] = 0;
2924 if (!CAN_MOVE(element))
2925 MovDir[newx][newy] = 0;
2928 DrawField_MM(newx, newy);
2930 Stop[newx][newy] = TRUE;
2932 if (element == EL_PACMAN)
2934 if (Store[newx][newy] == EL_BOMB)
2935 Bang_MM(newx, newy);
2937 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2938 (LX + 2 * XS) / TILEX == newx &&
2939 (LY + 2 * YS) / TILEY == newy)
2946 else // still moving on
2951 laser.redraw = TRUE;
2954 boolean ClickElement(int x, int y, int button)
2956 static DelayCounter click_delay = { CLICK_DELAY };
2957 static boolean new_button = TRUE;
2958 boolean element_clicked = FALSE;
2963 // initialize static variables
2964 click_delay.count = 0;
2965 click_delay.value = CLICK_DELAY;
2971 // do not rotate objects hit by the laser after the game was solved
2972 if (game_mm.level_solved && Hit[x][y])
2975 if (button == MB_RELEASED)
2978 click_delay.value = CLICK_DELAY;
2980 // release eventually hold auto-rotating mirror
2981 RotateMirror(x, y, MB_RELEASED);
2986 if (!FrameReached(&click_delay) && !new_button)
2989 if (button == MB_MIDDLEBUTTON) // middle button has no function
2992 if (!IN_LEV_FIELD(x, y))
2995 if (Tile[x][y] == EL_EMPTY)
2998 element = Tile[x][y];
3000 if (IS_MIRROR(element) ||
3001 IS_BEAMER(element) ||
3002 IS_POLAR(element) ||
3003 IS_POLAR_CROSS(element) ||
3004 IS_DF_MIRROR(element) ||
3005 IS_DF_MIRROR_AUTO(element))
3007 RotateMirror(x, y, button);
3009 element_clicked = TRUE;
3011 else if (IS_MCDUFFIN(element))
3013 if (!laser.fuse_off)
3015 DrawLaser(0, DL_LASER_DISABLED);
3022 element = get_rotated_element(element, BUTTON_ROTATION(button));
3023 laser.start_angle = get_element_angle(element);
3027 Tile[x][y] = element;
3034 if (!laser.fuse_off)
3037 element_clicked = TRUE;
3039 else if (element == EL_FUSE_ON && laser.fuse_off)
3041 if (x != laser.fuse_x || y != laser.fuse_y)
3044 laser.fuse_off = FALSE;
3045 laser.fuse_x = laser.fuse_y = -1;
3047 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3050 element_clicked = TRUE;
3052 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3054 laser.fuse_off = TRUE;
3057 laser.overloaded = FALSE;
3059 DrawLaser(0, DL_LASER_DISABLED);
3060 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3062 element_clicked = TRUE;
3064 else if (element == EL_LIGHTBALL)
3067 RaiseScoreElement_MM(element);
3068 DrawLaser(0, DL_LASER_ENABLED);
3070 element_clicked = TRUE;
3072 else if (IS_ENVELOPE(element))
3074 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3076 element_clicked = TRUE;
3079 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3082 return element_clicked;
3085 void RotateMirror(int x, int y, int button)
3087 if (button == MB_RELEASED)
3089 // release eventually hold auto-rotating mirror
3096 if (IS_MIRROR(Tile[x][y]) ||
3097 IS_POLAR_CROSS(Tile[x][y]) ||
3098 IS_POLAR(Tile[x][y]) ||
3099 IS_BEAMER(Tile[x][y]) ||
3100 IS_DF_MIRROR(Tile[x][y]) ||
3101 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3102 IS_GRID_WOOD_AUTO(Tile[x][y]))
3104 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3106 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3108 if (button == MB_LEFTBUTTON)
3110 // left mouse button only for manual adjustment, no auto-rotating;
3111 // freeze mirror for until mouse button released
3115 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3117 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3121 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3123 int edge = Hit[x][y];
3129 DrawLaser(edge - 1, DL_LASER_DISABLED);
3133 else if (ObjHit(x, y, HIT_POS_CENTER))
3135 int edge = Hit[x][y];
3139 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3144 DrawLaser(edge - 1, DL_LASER_DISABLED);
3151 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3156 if ((IS_BEAMER(Tile[x][y]) ||
3157 IS_POLAR(Tile[x][y]) ||
3158 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3160 if (IS_BEAMER(Tile[x][y]))
3163 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3164 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3177 DrawLaser(0, DL_LASER_ENABLED);
3181 static void AutoRotateMirrors(void)
3185 if (!FrameReached(&rotate_delay))
3188 for (x = 0; x < lev_fieldx; x++)
3190 for (y = 0; y < lev_fieldy; y++)
3192 int element = Tile[x][y];
3194 // do not rotate objects hit by the laser after the game was solved
3195 if (game_mm.level_solved && Hit[x][y])
3198 if (IS_DF_MIRROR_AUTO(element) ||
3199 IS_GRID_WOOD_AUTO(element) ||
3200 IS_GRID_STEEL_AUTO(element) ||
3201 element == EL_REFRACTOR)
3202 RotateMirror(x, y, MB_RIGHTBUTTON);
3207 boolean ObjHit(int obx, int oby, int bits)
3214 if (bits & HIT_POS_CENTER)
3216 if (CheckLaserPixel(cSX + obx + 15,
3221 if (bits & HIT_POS_EDGE)
3223 for (i = 0; i < 4; i++)
3224 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3225 cSY + oby + 31 * (i / 2)))
3229 if (bits & HIT_POS_BETWEEN)
3231 for (i = 0; i < 4; i++)
3232 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3233 cSY + 4 + oby + 22 * (i / 2)))
3240 void DeletePacMan(int px, int py)
3246 if (game_mm.num_pacman <= 1)
3248 game_mm.num_pacman = 0;
3252 for (i = 0; i < game_mm.num_pacman; i++)
3253 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3256 game_mm.num_pacman--;
3258 for (j = i; j < game_mm.num_pacman; j++)
3260 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3261 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3262 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3266 void ColorCycling(void)
3268 static int CC, Cc = 0;
3270 static int color, old = 0xF00, new = 0x010, mult = 1;
3271 static unsigned short red, green, blue;
3273 if (color_status == STATIC_COLORS)
3278 if (CC < Cc || CC > Cc + 2)
3282 color = old + new * mult;
3288 if (ABS(mult) == 16)
3298 red = 0x0e00 * ((color & 0xF00) >> 8);
3299 green = 0x0e00 * ((color & 0x0F0) >> 4);
3300 blue = 0x0e00 * ((color & 0x00F));
3301 SetRGB(pen_magicolor[0], red, green, blue);
3303 red = 0x1111 * ((color & 0xF00) >> 8);
3304 green = 0x1111 * ((color & 0x0F0) >> 4);
3305 blue = 0x1111 * ((color & 0x00F));
3306 SetRGB(pen_magicolor[1], red, green, blue);
3310 static void GameActions_MM_Ext(void)
3317 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3320 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3322 element = Tile[x][y];
3324 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3325 StartMoving_MM(x, y);
3326 else if (IS_MOVING(x, y))
3327 ContinueMoving_MM(x, y);
3328 else if (IS_EXPLODING(element))
3329 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3330 else if (element == EL_EXIT_OPENING)
3332 else if (element == EL_GRAY_BALL_OPENING)
3333 OpenSurpriseBall(x, y);
3334 else if (IS_ENVELOPE_OPENING(element))
3336 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3338 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3340 else if (IS_MIRROR(element) ||
3341 IS_MIRROR_FIXED(element) ||
3342 element == EL_PRISM)
3343 DrawFieldTwinkle(x, y);
3344 else if (element == EL_GRAY_BALL_OPENING ||
3345 element == EL_BOMB_ACTIVE ||
3346 element == EL_MINE_ACTIVE)
3347 DrawFieldAnimated_MM(x, y);
3348 else if (!IS_BLOCKED(x, y))
3349 DrawFieldAnimatedIfNeeded_MM(x, y);
3352 AutoRotateMirrors();
3355 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3357 // redraw after Explode_MM() ...
3359 DrawLaser(0, DL_LASER_ENABLED);
3360 laser.redraw = FALSE;
3365 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3369 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3371 DrawLaser(0, DL_LASER_DISABLED);
3376 // skip all following game actions if game is over
3377 if (game_mm.game_over)
3380 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3384 GameOver_MM(GAME_OVER_NO_ENERGY);
3389 if (FrameReached(&energy_delay))
3391 if (game_mm.energy_left > 0)
3392 game_mm.energy_left--;
3394 // when out of energy, wait another frame to play "out of time" sound
3397 element = laser.dest_element;
3400 if (element != Tile[ELX][ELY])
3402 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3403 element, Tile[ELX][ELY]);
3407 if (!laser.overloaded && laser.overload_value == 0 &&
3408 element != EL_BOMB &&
3409 element != EL_BOMB_ACTIVE &&
3410 element != EL_MINE &&
3411 element != EL_MINE_ACTIVE &&
3412 element != EL_BALL_GRAY &&
3413 element != EL_BLOCK_STONE &&
3414 element != EL_BLOCK_WOOD &&
3415 element != EL_FUSE_ON &&
3416 element != EL_FUEL_FULL &&
3417 !IS_WALL_ICE(element) &&
3418 !IS_WALL_AMOEBA(element))
3421 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3423 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3424 (!laser.overloaded && laser.overload_value > 0)) &&
3425 FrameReached(&overload_delay))
3427 if (laser.overloaded)
3428 laser.overload_value++;
3430 laser.overload_value--;
3432 if (game_mm.cheat_no_overload)
3434 laser.overloaded = FALSE;
3435 laser.overload_value = 0;
3438 game_mm.laser_overload_value = laser.overload_value;
3440 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3442 SetLaserColor(0xFF);
3444 DrawLaser(0, DL_LASER_ENABLED);
3447 if (!laser.overloaded)
3448 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3449 else if (setup.sound_loops)
3450 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3452 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3454 if (laser.overloaded)
3457 BlitBitmap(pix[PIX_DOOR], drawto,
3458 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3459 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3460 - laser.overload_value,
3461 OVERLOAD_XSIZE, laser.overload_value,
3462 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3463 - laser.overload_value);
3465 redraw_mask |= REDRAW_DOOR_1;
3470 BlitBitmap(pix[PIX_DOOR], drawto,
3471 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3472 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3473 DX_OVERLOAD, DY_OVERLOAD);
3475 redraw_mask |= REDRAW_DOOR_1;
3478 if (laser.overload_value == MAX_LASER_OVERLOAD)
3480 UpdateAndDisplayGameControlValues();
3484 GameOver_MM(GAME_OVER_OVERLOADED);
3495 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3497 if (game_mm.cheat_no_explosion)
3502 laser.dest_element = EL_EXPLODING_OPAQUE;
3507 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3509 laser.fuse_off = TRUE;
3513 DrawLaser(0, DL_LASER_DISABLED);
3514 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3517 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3519 if (!Store2[ELX][ELY]) // check if content element not yet determined
3521 int last_anim_random_frame = gfx.anim_random_frame;
3524 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3525 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3527 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3528 native_mm_level.ball_choice_mode, 0,
3529 game_mm.ball_choice_pos);
3531 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3532 gfx.anim_random_frame = last_anim_random_frame;
3534 game_mm.ball_choice_pos++;
3536 int new_element = native_mm_level.ball_content[element_pos];
3537 int new_element_unmapped = unmap_element(new_element);
3539 if (IS_WALL(new_element_unmapped))
3541 // always use completely filled wall element
3542 new_element = new_element_unmapped | 0x000f;
3544 else if (native_mm_level.rotate_ball_content &&
3545 get_num_elements(new_element) > 1)
3547 // randomly rotate newly created game element
3548 new_element = get_rotated_element(new_element, RND(16));
3551 Store[ELX][ELY] = new_element;
3552 Store2[ELX][ELY] = TRUE;
3555 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3557 // !!! CHECK AGAIN: Laser on Polarizer !!!
3560 laser.dest_element_last = Tile[ELX][ELY];
3561 laser.dest_element_last_x = ELX;
3562 laser.dest_element_last_y = ELY;
3572 element = EL_MIRROR_START + RND(16);
3578 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3585 element = (rnd == 0 ? EL_FUSE_ON :
3586 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3587 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3588 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3589 EL_MIRROR_FIXED_START + rnd - 25);
3594 graphic = el2gfx(element);
3596 for (i = 0; i < 50; i++)
3602 BlitBitmap(pix[PIX_BACK], drawto,
3603 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3604 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3605 SX + ELX * TILEX + x,
3606 SY + ELY * TILEY + y);
3608 MarkTileDirty(ELX, ELY);
3611 DrawLaser(0, DL_LASER_ENABLED);
3613 Delay_WithScreenUpdates(50);
3616 Tile[ELX][ELY] = element;
3617 DrawField_MM(ELX, ELY);
3620 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3623 // above stuff: GRAY BALL -> PRISM !!!
3625 LX = ELX * TILEX + 14;
3626 LY = ELY * TILEY + 14;
3627 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3634 laser.num_edges -= 2;
3635 laser.num_damages--;
3639 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3640 if (laser.damage[i].is_mirror)
3644 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3646 DrawLaser(0, DL_LASER_DISABLED);
3648 DrawLaser(0, DL_LASER_DISABLED);
3657 if (IS_WALL_ICE(element) && CT > 50)
3659 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3662 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3663 Store[ELX][ELY] = EL_WALL_ICE;
3664 Store2[ELX][ELY] = laser.wall_mask;
3666 laser.dest_element = Tile[ELX][ELY];
3671 for (i = 0; i < 5; i++)
3677 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3681 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3683 Delay_WithScreenUpdates(100);
3686 if (Tile[ELX][ELY] == EL_WALL_ICE)
3687 Tile[ELX][ELY] = EL_EMPTY;
3691 LX = laser.edge[laser.num_edges].x - cSX2;
3692 LY = laser.edge[laser.num_edges].y - cSY2;
3695 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3696 if (laser.damage[i].is_mirror)
3700 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3702 DrawLaser(0, DL_LASER_DISABLED);
3709 if (IS_WALL_AMOEBA(element) && CT > 60)
3711 int k1, k2, k3, dx, dy, de, dm;
3712 int element2 = Tile[ELX][ELY];
3714 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3717 for (i = laser.num_damages - 1; i >= 0; i--)
3718 if (laser.damage[i].is_mirror)
3721 r = laser.num_edges;
3722 d = laser.num_damages;
3729 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3732 DrawLaser(0, DL_LASER_ENABLED);
3735 x = laser.damage[k1].x;
3736 y = laser.damage[k1].y;
3741 for (i = 0; i < 4; i++)
3743 if (laser.wall_mask & (1 << i))
3745 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3746 cSY + ELY * TILEY + 31 * (i / 2)))
3749 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3750 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3757 for (i = 0; i < 4; i++)
3759 if (laser.wall_mask & (1 << i))
3761 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3762 cSY + ELY * TILEY + 31 * (i / 2)))
3769 if (laser.num_beamers > 0 ||
3770 k1 < 1 || k2 < 4 || k3 < 4 ||
3771 CheckLaserPixel(cSX + ELX * TILEX + 14,
3772 cSY + ELY * TILEY + 14))
3774 laser.num_edges = r;
3775 laser.num_damages = d;
3777 DrawLaser(0, DL_LASER_DISABLED);
3780 Tile[ELX][ELY] = element | laser.wall_mask;
3784 de = Tile[ELX][ELY];
3785 dm = laser.wall_mask;
3789 int x = ELX, y = ELY;
3790 int wall_mask = laser.wall_mask;
3793 DrawLaser(0, DL_LASER_ENABLED);
3795 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3797 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3798 Store[x][y] = EL_WALL_AMOEBA;
3799 Store2[x][y] = wall_mask;
3805 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3807 DrawLaser(0, DL_LASER_ENABLED);
3809 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3811 for (i = 4; i >= 0; i--)
3813 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3816 Delay_WithScreenUpdates(20);
3819 DrawLaser(0, DL_LASER_ENABLED);
3824 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3825 laser.stops_inside_element && CT > native_mm_level.time_block)
3830 if (ABS(XS) > ABS(YS))
3837 for (i = 0; i < 4; i++)
3844 x = ELX + Step[k * 4].x;
3845 y = ELY + Step[k * 4].y;
3847 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3850 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3858 laser.overloaded = (element == EL_BLOCK_STONE);
3863 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3866 Tile[x][y] = element;
3868 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3871 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3873 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3874 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3882 if (element == EL_FUEL_FULL && CT > 10)
3884 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3885 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3887 for (i = start; i <= num_init_game_frames; i++)
3889 if (i == num_init_game_frames)
3890 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3891 else if (setup.sound_loops)
3892 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3894 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3896 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3898 UpdateAndDisplayGameControlValues();
3903 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3905 DrawField_MM(ELX, ELY);
3907 DrawLaser(0, DL_LASER_ENABLED);
3915 void GameActions_MM(struct MouseActionInfo action)
3917 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3918 boolean button_released = (action.button == MB_RELEASED);
3920 GameActions_MM_Ext();
3922 CheckSingleStepMode_MM(element_clicked, button_released);
3925 void MovePacMen(void)
3927 int mx, my, ox, oy, nx, ny;
3931 if (++pacman_nr >= game_mm.num_pacman)
3934 game_mm.pacman[pacman_nr].dir--;
3936 for (l = 1; l < 5; l++)
3938 game_mm.pacman[pacman_nr].dir++;
3940 if (game_mm.pacman[pacman_nr].dir > 4)
3941 game_mm.pacman[pacman_nr].dir = 1;
3943 if (game_mm.pacman[pacman_nr].dir % 2)
3946 my = game_mm.pacman[pacman_nr].dir - 2;
3951 mx = 3 - game_mm.pacman[pacman_nr].dir;
3954 ox = game_mm.pacman[pacman_nr].x;
3955 oy = game_mm.pacman[pacman_nr].y;
3958 element = Tile[nx][ny];
3960 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3963 if (!IS_EATABLE4PACMAN(element))
3966 if (ObjHit(nx, ny, HIT_POS_CENTER))
3969 Tile[ox][oy] = EL_EMPTY;
3971 EL_PACMAN_RIGHT - 1 +
3972 (game_mm.pacman[pacman_nr].dir - 1 +
3973 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3975 game_mm.pacman[pacman_nr].x = nx;
3976 game_mm.pacman[pacman_nr].y = ny;
3978 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3980 if (element != EL_EMPTY)
3982 int graphic = el2gfx(Tile[nx][ny]);
3987 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3990 ox = cSX + ox * TILEX;
3991 oy = cSY + oy * TILEY;
3993 for (i = 1; i < 33; i += 2)
3994 BlitBitmap(bitmap, window,
3995 src_x, src_y, TILEX, TILEY,
3996 ox + i * mx, oy + i * my);
3997 Ct = Ct + FrameCounter - CT;
4000 DrawField_MM(nx, ny);
4003 if (!laser.fuse_off)
4005 DrawLaser(0, DL_LASER_ENABLED);
4007 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
4009 AddDamagedField(nx, ny);
4011 laser.damage[laser.num_damages - 1].edge = 0;
4015 if (element == EL_BOMB)
4016 DeletePacMan(nx, ny);
4018 if (IS_WALL_AMOEBA(element) &&
4019 (LX + 2 * XS) / TILEX == nx &&
4020 (LY + 2 * YS) / TILEY == ny)
4030 static void InitMovingField_MM(int x, int y, int direction)
4032 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4033 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4035 MovDir[x][y] = direction;
4036 MovDir[newx][newy] = direction;
4038 if (Tile[newx][newy] == EL_EMPTY)
4039 Tile[newx][newy] = EL_BLOCKED;
4042 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4044 int direction = MovDir[x][y];
4045 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4046 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4052 static void Blocked2Moving_MM(int x, int y,
4053 int *comes_from_x, int *comes_from_y)
4055 int oldx = x, oldy = y;
4056 int direction = MovDir[x][y];
4058 if (direction == MV_LEFT)
4060 else if (direction == MV_RIGHT)
4062 else if (direction == MV_UP)
4064 else if (direction == MV_DOWN)
4067 *comes_from_x = oldx;
4068 *comes_from_y = oldy;
4071 static int MovingOrBlocked2Element_MM(int x, int y)
4073 int element = Tile[x][y];
4075 if (element == EL_BLOCKED)
4079 Blocked2Moving_MM(x, y, &oldx, &oldy);
4081 return Tile[oldx][oldy];
4088 static void RemoveField(int x, int y)
4090 Tile[x][y] = EL_EMPTY;
4097 static void RemoveMovingField_MM(int x, int y)
4099 int oldx = x, oldy = y, newx = x, newy = y;
4101 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4104 if (IS_MOVING(x, y))
4106 Moving2Blocked_MM(x, y, &newx, &newy);
4107 if (Tile[newx][newy] != EL_BLOCKED)
4110 else if (Tile[x][y] == EL_BLOCKED)
4112 Blocked2Moving_MM(x, y, &oldx, &oldy);
4113 if (!IS_MOVING(oldx, oldy))
4117 Tile[oldx][oldy] = EL_EMPTY;
4118 Tile[newx][newy] = EL_EMPTY;
4119 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4120 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4122 DrawLevelField_MM(oldx, oldy);
4123 DrawLevelField_MM(newx, newy);
4126 void PlaySoundLevel(int x, int y, int sound_nr)
4128 int sx = SCREENX(x), sy = SCREENY(y);
4130 int silence_distance = 8;
4132 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4133 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4136 if (!IN_LEV_FIELD(x, y) ||
4137 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4138 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4141 volume = SOUND_MAX_VOLUME;
4144 stereo = (sx - SCR_FIELDX/2) * 12;
4146 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4147 if (stereo > SOUND_MAX_RIGHT)
4148 stereo = SOUND_MAX_RIGHT;
4149 if (stereo < SOUND_MAX_LEFT)
4150 stereo = SOUND_MAX_LEFT;
4153 if (!IN_SCR_FIELD(sx, sy))
4155 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4156 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4158 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4161 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4164 static void RaiseScore_MM(int value)
4166 game_mm.score += value;
4169 void RaiseScoreElement_MM(int element)
4174 case EL_PACMAN_RIGHT:
4176 case EL_PACMAN_LEFT:
4177 case EL_PACMAN_DOWN:
4178 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4182 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4187 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4191 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4200 // ----------------------------------------------------------------------------
4201 // Mirror Magic game engine snapshot handling functions
4202 // ----------------------------------------------------------------------------
4204 void SaveEngineSnapshotValues_MM(void)
4208 engine_snapshot_mm.game_mm = game_mm;
4209 engine_snapshot_mm.laser = laser;
4211 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4213 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4215 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4216 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4217 engine_snapshot_mm.Box[x][y] = Box[x][y];
4218 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4222 engine_snapshot_mm.LX = LX;
4223 engine_snapshot_mm.LY = LY;
4224 engine_snapshot_mm.XS = XS;
4225 engine_snapshot_mm.YS = YS;
4226 engine_snapshot_mm.ELX = ELX;
4227 engine_snapshot_mm.ELY = ELY;
4228 engine_snapshot_mm.CT = CT;
4229 engine_snapshot_mm.Ct = Ct;
4231 engine_snapshot_mm.last_LX = last_LX;
4232 engine_snapshot_mm.last_LY = last_LY;
4233 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4234 engine_snapshot_mm.hold_x = hold_x;
4235 engine_snapshot_mm.hold_y = hold_y;
4236 engine_snapshot_mm.pacman_nr = pacman_nr;
4238 engine_snapshot_mm.rotate_delay = rotate_delay;
4239 engine_snapshot_mm.pacman_delay = pacman_delay;
4240 engine_snapshot_mm.energy_delay = energy_delay;
4241 engine_snapshot_mm.overload_delay = overload_delay;
4244 void LoadEngineSnapshotValues_MM(void)
4248 // stored engine snapshot buffers already restored at this point
4250 game_mm = engine_snapshot_mm.game_mm;
4251 laser = engine_snapshot_mm.laser;
4253 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4255 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4257 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4258 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4259 Box[x][y] = engine_snapshot_mm.Box[x][y];
4260 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4264 LX = engine_snapshot_mm.LX;
4265 LY = engine_snapshot_mm.LY;
4266 XS = engine_snapshot_mm.XS;
4267 YS = engine_snapshot_mm.YS;
4268 ELX = engine_snapshot_mm.ELX;
4269 ELY = engine_snapshot_mm.ELY;
4270 CT = engine_snapshot_mm.CT;
4271 Ct = engine_snapshot_mm.Ct;
4273 last_LX = engine_snapshot_mm.last_LX;
4274 last_LY = engine_snapshot_mm.last_LY;
4275 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4276 hold_x = engine_snapshot_mm.hold_x;
4277 hold_y = engine_snapshot_mm.hold_y;
4278 pacman_nr = engine_snapshot_mm.pacman_nr;
4280 rotate_delay = engine_snapshot_mm.rotate_delay;
4281 pacman_delay = engine_snapshot_mm.pacman_delay;
4282 energy_delay = engine_snapshot_mm.energy_delay;
4283 overload_delay = engine_snapshot_mm.overload_delay;
4285 RedrawPlayfield_MM();
4288 static int getAngleFromTouchDelta(int dx, int dy, int base)
4290 double pi = 3.141592653;
4291 double rad = atan2((double)-dy, (double)dx);
4292 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4293 double deg = rad2 * 180.0 / pi;
4295 return (int)(deg * base / 360.0 + 0.5) % base;
4298 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4300 // calculate start (source) position to be at the middle of the tile
4301 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4302 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4303 int dx = dst_mx - src_mx;
4304 int dy = dst_my - src_my;
4313 if (!IN_LEV_FIELD(x, y))
4316 element = Tile[x][y];
4318 if (!IS_MCDUFFIN(element) &&
4319 !IS_MIRROR(element) &&
4320 !IS_BEAMER(element) &&
4321 !IS_POLAR(element) &&
4322 !IS_POLAR_CROSS(element) &&
4323 !IS_DF_MIRROR(element))
4326 angle_old = get_element_angle(element);
4328 if (IS_MCDUFFIN(element))
4330 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4331 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4332 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4333 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4336 else if (IS_MIRROR(element) ||
4337 IS_DF_MIRROR(element))
4339 for (i = 0; i < laser.num_damages; i++)
4341 if (laser.damage[i].x == x &&
4342 laser.damage[i].y == y &&
4343 ObjHit(x, y, HIT_POS_CENTER))
4345 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4346 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4353 if (angle_new == -1)
4355 if (IS_MIRROR(element) ||
4356 IS_DF_MIRROR(element) ||
4360 if (IS_POLAR_CROSS(element))
4363 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4366 button = (angle_new == angle_old ? 0 :
4367 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4368 MB_LEFTBUTTON : MB_RIGHTBUTTON);