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_ACTIVE ||
955 laser.dest_element_last == EL_GRAY_BALL_OPENING)
957 int x = laser.dest_element_last_x;
958 int y = laser.dest_element_last_y;
959 int element = laser.dest_element_last;
961 if (Tile[x][y] == element)
962 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
963 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
965 if (Tile[x][y] == EL_GRAY_BALL)
968 laser.dest_element_last = EL_EMPTY;
969 laser.dest_element_last_x = -1;
970 laser.dest_element_last_y = -1;
976 int element = EL_EMPTY;
977 int last_element = EL_EMPTY;
978 int end = 0, rf = laser.num_edges;
980 // do not scan laser again after the game was lost for whatever reason
981 if (game_mm.game_over)
984 // do not scan laser if fuse is off
988 DeactivateLaserTargetElement();
990 laser.overloaded = FALSE;
991 laser.stops_inside_element = FALSE;
993 DrawLaser(0, DL_LASER_ENABLED);
996 Debug("game:mm:ScanLaser",
997 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1005 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1008 laser.overloaded = TRUE;
1013 hit_mask = ScanPixel();
1016 Debug("game:mm:ScanLaser",
1017 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1021 // hit something -- check out what it was
1022 ELX = (LX + XS) / TILEX;
1023 ELY = (LY + YS) / TILEY;
1026 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1027 hit_mask, LX, LY, ELX, ELY);
1030 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1033 laser.dest_element = element;
1038 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1040 /* we have hit the top-right and bottom-left element --
1041 choose the bottom-left one */
1042 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1043 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1044 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1045 ELX = (LX - 2) / TILEX;
1046 ELY = (LY + 2) / TILEY;
1049 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1051 /* we have hit the top-left and bottom-right element --
1052 choose the top-left one */
1053 // !!! SEE ABOVE !!!
1054 ELX = (LX - 2) / TILEX;
1055 ELY = (LY - 2) / TILEY;
1059 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1060 hit_mask, LX, LY, ELX, ELY);
1063 last_element = element;
1065 element = Tile[ELX][ELY];
1066 laser.dest_element = element;
1069 Debug("game:mm:ScanLaser",
1070 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1073 LX % TILEX, LY % TILEY,
1078 if (!IN_LEV_FIELD(ELX, ELY))
1079 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1083 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1084 if (element == EL_EMPTY &&
1085 IS_GRID_STEEL(last_element) &&
1086 laser.current_angle % 4) // angle is not 90°
1087 element = last_element;
1089 if (element == EL_EMPTY)
1091 if (!HitOnlyAnEdge(hit_mask))
1094 else if (element == EL_FUSE_ON)
1096 if (HitPolarizer(element, hit_mask))
1099 else if (IS_GRID(element) || IS_DF_GRID(element))
1101 if (HitPolarizer(element, hit_mask))
1104 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1105 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1107 if (HitBlock(element, hit_mask))
1114 else if (IS_MCDUFFIN(element))
1116 if (HitLaserSource(element, hit_mask))
1119 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1120 IS_RECEIVER(element))
1122 if (HitLaserDestination(element, hit_mask))
1125 else if (IS_WALL(element))
1127 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1129 if (HitReflectingWalls(element, hit_mask))
1134 if (HitAbsorbingWalls(element, hit_mask))
1140 if (HitElement(element, hit_mask))
1145 DrawLaser(rf - 1, DL_LASER_ENABLED);
1146 rf = laser.num_edges;
1148 if (!IS_DF_WALL_STEEL(element))
1150 // only used for scanning DF steel walls; reset for all other elements
1158 if (laser.dest_element != Tile[ELX][ELY])
1160 Debug("game:mm:ScanLaser",
1161 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1162 laser.dest_element, Tile[ELX][ELY]);
1166 if (!end && !laser.stops_inside_element && !StepBehind())
1169 Debug("game:mm:ScanLaser", "Go one step back");
1175 AddLaserEdge(LX, LY);
1179 DrawLaser(rf - 1, DL_LASER_ENABLED);
1181 Ct = CT = FrameCounter;
1184 if (!IN_LEV_FIELD(ELX, ELY))
1185 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1189 static void ScanLaser_FromLastMirror(void)
1191 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1194 for (i = start_pos; i >= 0; i--)
1195 if (laser.damage[i].is_mirror)
1198 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1200 DrawLaser(start_edge, DL_LASER_DISABLED);
1205 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1211 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1212 start_edge, num_edges, mode);
1217 Warn("DrawLaserExt: start_edge < 0");
1224 Warn("DrawLaserExt: num_edges < 0");
1230 if (mode == DL_LASER_DISABLED)
1232 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1236 // now draw the laser to the backbuffer and (if enabled) to the screen
1237 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1239 redraw_mask |= REDRAW_FIELD;
1241 if (mode == DL_LASER_ENABLED)
1244 // after the laser was deleted, the "damaged" graphics must be restored
1245 if (laser.num_damages)
1247 int damage_start = 0;
1250 // determine the starting edge, from which graphics need to be restored
1253 for (i = 0; i < laser.num_damages; i++)
1255 if (laser.damage[i].edge == start_edge + 1)
1264 // restore graphics from this starting edge to the end of damage list
1265 for (i = damage_start; i < laser.num_damages; i++)
1267 int lx = laser.damage[i].x;
1268 int ly = laser.damage[i].y;
1269 int element = Tile[lx][ly];
1271 if (Hit[lx][ly] == laser.damage[i].edge)
1272 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1275 if (Box[lx][ly] == laser.damage[i].edge)
1278 if (IS_DRAWABLE(element))
1279 DrawField_MM(lx, ly);
1282 elx = laser.damage[damage_start].x;
1283 ely = laser.damage[damage_start].y;
1284 element = Tile[elx][ely];
1287 if (IS_BEAMER(element))
1291 for (i = 0; i < laser.num_beamers; i++)
1292 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1294 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1295 mode, elx, ely, Hit[elx][ely], start_edge);
1296 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1297 get_element_angle(element), laser.damage[damage_start].angle);
1301 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1302 laser.num_beamers > 0 &&
1303 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1305 // element is outgoing beamer
1306 laser.num_damages = damage_start + 1;
1308 if (IS_BEAMER(element))
1309 laser.current_angle = get_element_angle(element);
1313 // element is incoming beamer or other element
1314 laser.num_damages = damage_start;
1315 laser.current_angle = laser.damage[laser.num_damages].angle;
1320 // no damages but McDuffin himself (who needs to be redrawn anyway)
1322 elx = laser.start_edge.x;
1323 ely = laser.start_edge.y;
1324 element = Tile[elx][ely];
1327 laser.num_edges = start_edge + 1;
1328 if (start_edge == 0)
1329 laser.current_angle = laser.start_angle;
1331 LX = laser.edge[start_edge].x - cSX2;
1332 LY = laser.edge[start_edge].y - cSY2;
1333 XS = 2 * Step[laser.current_angle].x;
1334 YS = 2 * Step[laser.current_angle].y;
1337 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1343 if (IS_BEAMER(element) ||
1344 IS_FIBRE_OPTIC(element) ||
1345 IS_PACMAN(element) ||
1346 IS_POLAR(element) ||
1347 IS_POLAR_CROSS(element) ||
1348 element == EL_FUSE_ON)
1353 Debug("game:mm:DrawLaserExt", "element == %d", element);
1356 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1357 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1361 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1362 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1363 (laser.num_beamers == 0 ||
1364 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1366 // element is incoming beamer or other element
1367 step_size = -step_size;
1372 if (IS_BEAMER(element))
1373 Debug("game:mm:DrawLaserExt",
1374 "start_edge == %d, laser.beamer_edge == %d",
1375 start_edge, laser.beamer_edge);
1378 LX += step_size * XS;
1379 LY += step_size * YS;
1381 else if (element != EL_EMPTY)
1390 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1395 void DrawLaser(int start_edge, int mode)
1397 // do not draw laser if fuse is off
1398 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1401 if (mode == DL_LASER_DISABLED)
1402 DeactivateLaserTargetElement();
1404 if (laser.num_edges - start_edge < 0)
1406 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1411 // check if laser is interrupted by beamer element
1412 if (laser.num_beamers > 0 &&
1413 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1415 if (mode == DL_LASER_ENABLED)
1418 int tmp_start_edge = start_edge;
1420 // draw laser segments forward from the start to the last beamer
1421 for (i = 0; i < laser.num_beamers; i++)
1423 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1425 if (tmp_num_edges <= 0)
1429 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1430 i, laser.beamer_edge[i], tmp_start_edge);
1433 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1435 tmp_start_edge = laser.beamer_edge[i];
1438 // draw last segment from last beamer to the end
1439 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1445 int last_num_edges = laser.num_edges;
1446 int num_beamers = laser.num_beamers;
1448 // delete laser segments backward from the end to the first beamer
1449 for (i = num_beamers - 1; i >= 0; i--)
1451 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1453 if (laser.beamer_edge[i] - start_edge <= 0)
1456 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1458 last_num_edges = laser.beamer_edge[i];
1459 laser.num_beamers--;
1463 if (last_num_edges - start_edge <= 0)
1464 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1465 last_num_edges, start_edge);
1468 // special case when rotating first beamer: delete laser edge on beamer
1469 // (but do not start scanning on previous edge to prevent mirror sound)
1470 if (last_num_edges - start_edge == 1 && start_edge > 0)
1471 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1473 // delete first segment from start to the first beamer
1474 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1479 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1482 game_mm.laser_enabled = mode;
1485 void DrawLaser_MM(void)
1487 DrawLaser(0, game_mm.laser_enabled);
1490 boolean HitElement(int element, int hit_mask)
1492 if (HitOnlyAnEdge(hit_mask))
1495 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1496 element = MovingOrBlocked2Element_MM(ELX, ELY);
1499 Debug("game:mm:HitElement", "(1): element == %d", element);
1503 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1504 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1507 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1511 AddDamagedField(ELX, ELY);
1513 // this is more precise: check if laser would go through the center
1514 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1518 // prevent cutting through laser emitter with laser beam
1519 if (IS_LASER(element))
1522 // skip the whole element before continuing the scan
1530 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1532 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1534 /* skipping scan positions to the right and down skips one scan
1535 position too much, because this is only the top left scan position
1536 of totally four scan positions (plus one to the right, one to the
1537 bottom and one to the bottom right) */
1538 /* ... but only roll back scan position if more than one step done */
1548 Debug("game:mm:HitElement", "(2): element == %d", element);
1551 if (LX + 5 * XS < 0 ||
1561 Debug("game:mm:HitElement", "(3): element == %d", element);
1564 if (IS_POLAR(element) &&
1565 ((element - EL_POLAR_START) % 2 ||
1566 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1568 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1570 laser.num_damages--;
1575 if (IS_POLAR_CROSS(element) &&
1576 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1578 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1580 laser.num_damages--;
1585 if (!IS_BEAMER(element) &&
1586 !IS_FIBRE_OPTIC(element) &&
1587 !IS_GRID_WOOD(element) &&
1588 element != EL_FUEL_EMPTY)
1591 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1592 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1594 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1597 LX = ELX * TILEX + 14;
1598 LY = ELY * TILEY + 14;
1600 AddLaserEdge(LX, LY);
1603 if (IS_MIRROR(element) ||
1604 IS_MIRROR_FIXED(element) ||
1605 IS_POLAR(element) ||
1606 IS_POLAR_CROSS(element) ||
1607 IS_DF_MIRROR(element) ||
1608 IS_DF_MIRROR_AUTO(element) ||
1609 element == EL_PRISM ||
1610 element == EL_REFRACTOR)
1612 int current_angle = laser.current_angle;
1615 laser.num_damages--;
1617 AddDamagedField(ELX, ELY);
1619 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1622 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1624 if (IS_MIRROR(element) ||
1625 IS_MIRROR_FIXED(element) ||
1626 IS_DF_MIRROR(element) ||
1627 IS_DF_MIRROR_AUTO(element))
1628 laser.current_angle = get_mirrored_angle(laser.current_angle,
1629 get_element_angle(element));
1631 if (element == EL_PRISM || element == EL_REFRACTOR)
1632 laser.current_angle = RND(16);
1634 XS = 2 * Step[laser.current_angle].x;
1635 YS = 2 * Step[laser.current_angle].y;
1637 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1642 LX += step_size * XS;
1643 LY += step_size * YS;
1645 // draw sparkles on mirror
1646 if ((IS_MIRROR(element) ||
1647 IS_MIRROR_FIXED(element) ||
1648 element == EL_PRISM) &&
1649 current_angle != laser.current_angle)
1651 MovDelay[ELX][ELY] = 11; // start animation
1654 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1655 current_angle != laser.current_angle)
1656 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1659 (get_opposite_angle(laser.current_angle) ==
1660 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1662 return (laser.overloaded ? TRUE : FALSE);
1665 if (element == EL_FUEL_FULL)
1667 laser.stops_inside_element = TRUE;
1672 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1674 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1676 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1677 element == EL_MINE ? EL_MINE_ACTIVE :
1678 EL_GRAY_BALL_ACTIVE);
1680 laser.dest_element_last = Tile[ELX][ELY];
1681 laser.dest_element_last_x = ELX;
1682 laser.dest_element_last_y = ELY;
1684 if (element == EL_MINE)
1685 laser.overloaded = TRUE;
1688 if (element == EL_KETTLE ||
1689 element == EL_CELL ||
1690 element == EL_KEY ||
1691 element == EL_LIGHTBALL ||
1692 element == EL_PACMAN ||
1693 IS_PACMAN(element) ||
1694 IS_ENVELOPE(element))
1696 if (!IS_PACMAN(element) &&
1697 !IS_ENVELOPE(element))
1700 if (element == EL_PACMAN)
1703 if (element == EL_KETTLE || element == EL_CELL)
1705 if (game_mm.kettles_still_needed > 0)
1706 game_mm.kettles_still_needed--;
1708 game.snapshot.collected_item = TRUE;
1710 if (game_mm.kettles_still_needed == 0)
1714 DrawLaser(0, DL_LASER_ENABLED);
1717 else if (element == EL_KEY)
1721 else if (IS_PACMAN(element))
1723 DeletePacMan(ELX, ELY);
1725 else if (IS_ENVELOPE(element))
1727 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1730 RaiseScoreElement_MM(element);
1735 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1737 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1739 DrawLaser(0, DL_LASER_ENABLED);
1741 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1743 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1744 game_mm.lights_still_needed--;
1748 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1749 game_mm.lights_still_needed++;
1752 DrawField_MM(ELX, ELY);
1753 DrawLaser(0, DL_LASER_ENABLED);
1758 laser.stops_inside_element = TRUE;
1764 Debug("game:mm:HitElement", "(4): element == %d", element);
1767 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1768 laser.num_beamers < MAX_NUM_BEAMERS &&
1769 laser.beamer[BEAMER_NR(element)][1].num)
1771 int beamer_angle = get_element_angle(element);
1772 int beamer_nr = BEAMER_NR(element);
1776 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1779 laser.num_damages--;
1781 if (IS_FIBRE_OPTIC(element) ||
1782 laser.current_angle == get_opposite_angle(beamer_angle))
1786 LX = ELX * TILEX + 14;
1787 LY = ELY * TILEY + 14;
1789 AddLaserEdge(LX, LY);
1790 AddDamagedField(ELX, ELY);
1792 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1795 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1797 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1798 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1799 ELX = laser.beamer[beamer_nr][pos].x;
1800 ELY = laser.beamer[beamer_nr][pos].y;
1801 LX = ELX * TILEX + 14;
1802 LY = ELY * TILEY + 14;
1804 if (IS_BEAMER(element))
1806 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1807 XS = 2 * Step[laser.current_angle].x;
1808 YS = 2 * Step[laser.current_angle].y;
1811 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1813 AddLaserEdge(LX, LY);
1814 AddDamagedField(ELX, ELY);
1816 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1819 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1821 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1826 LX += step_size * XS;
1827 LY += step_size * YS;
1829 laser.num_beamers++;
1838 boolean HitOnlyAnEdge(int hit_mask)
1840 // check if the laser hit only the edge of an element and, if so, go on
1843 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1847 if ((hit_mask == HIT_MASK_TOPLEFT ||
1848 hit_mask == HIT_MASK_TOPRIGHT ||
1849 hit_mask == HIT_MASK_BOTTOMLEFT ||
1850 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1851 laser.current_angle % 4) // angle is not 90°
1855 if (hit_mask == HIT_MASK_TOPLEFT)
1860 else if (hit_mask == HIT_MASK_TOPRIGHT)
1865 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1870 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1876 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1882 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1889 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1895 boolean HitPolarizer(int element, int hit_mask)
1897 if (HitOnlyAnEdge(hit_mask))
1900 if (IS_DF_GRID(element))
1902 int grid_angle = get_element_angle(element);
1905 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1906 grid_angle, laser.current_angle);
1909 AddLaserEdge(LX, LY);
1910 AddDamagedField(ELX, ELY);
1913 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1915 if (laser.current_angle == grid_angle ||
1916 laser.current_angle == get_opposite_angle(grid_angle))
1918 // skip the whole element before continuing the scan
1924 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1926 if (LX/TILEX > ELX || LY/TILEY > ELY)
1928 /* skipping scan positions to the right and down skips one scan
1929 position too much, because this is only the top left scan position
1930 of totally four scan positions (plus one to the right, one to the
1931 bottom and one to the bottom right) */
1937 AddLaserEdge(LX, LY);
1943 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1945 LX / TILEX, LY / TILEY,
1946 LX % TILEX, LY % TILEY);
1951 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1953 return HitReflectingWalls(element, hit_mask);
1957 return HitAbsorbingWalls(element, hit_mask);
1960 else if (IS_GRID_STEEL(element))
1962 return HitReflectingWalls(element, hit_mask);
1964 else // IS_GRID_WOOD
1966 return HitAbsorbingWalls(element, hit_mask);
1972 boolean HitBlock(int element, int hit_mask)
1974 boolean check = FALSE;
1976 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1977 game_mm.num_keys == 0)
1980 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1983 int ex = ELX * TILEX + 14;
1984 int ey = ELY * TILEY + 14;
1988 for (i = 1; i < 32; i++)
1993 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1998 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1999 return HitAbsorbingWalls(element, hit_mask);
2003 AddLaserEdge(LX - XS, LY - YS);
2004 AddDamagedField(ELX, ELY);
2007 Box[ELX][ELY] = laser.num_edges;
2009 return HitReflectingWalls(element, hit_mask);
2012 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2014 int xs = XS / 2, ys = YS / 2;
2015 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2016 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2018 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2019 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2021 laser.overloaded = (element == EL_GATE_STONE);
2026 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2027 (hit_mask == HIT_MASK_TOP ||
2028 hit_mask == HIT_MASK_LEFT ||
2029 hit_mask == HIT_MASK_RIGHT ||
2030 hit_mask == HIT_MASK_BOTTOM))
2031 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2032 hit_mask == HIT_MASK_BOTTOM),
2033 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2034 hit_mask == HIT_MASK_RIGHT));
2035 AddLaserEdge(LX, LY);
2041 if (element == EL_GATE_STONE && Box[ELX][ELY])
2043 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2055 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2057 int xs = XS / 2, ys = YS / 2;
2058 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2059 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2061 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2062 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2064 laser.overloaded = (element == EL_BLOCK_STONE);
2069 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2070 (hit_mask == HIT_MASK_TOP ||
2071 hit_mask == HIT_MASK_LEFT ||
2072 hit_mask == HIT_MASK_RIGHT ||
2073 hit_mask == HIT_MASK_BOTTOM))
2074 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2075 hit_mask == HIT_MASK_BOTTOM),
2076 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2077 hit_mask == HIT_MASK_RIGHT));
2078 AddDamagedField(ELX, ELY);
2080 LX = ELX * TILEX + 14;
2081 LY = ELY * TILEY + 14;
2083 AddLaserEdge(LX, LY);
2085 laser.stops_inside_element = TRUE;
2093 boolean HitLaserSource(int element, int hit_mask)
2095 if (HitOnlyAnEdge(hit_mask))
2098 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2100 laser.overloaded = TRUE;
2105 boolean HitLaserDestination(int element, int hit_mask)
2107 if (HitOnlyAnEdge(hit_mask))
2110 if (element != EL_EXIT_OPEN &&
2111 !(IS_RECEIVER(element) &&
2112 game_mm.kettles_still_needed == 0 &&
2113 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2115 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2120 if (IS_RECEIVER(element) ||
2121 (IS_22_5_ANGLE(laser.current_angle) &&
2122 (ELX != (LX + 6 * XS) / TILEX ||
2123 ELY != (LY + 6 * YS) / TILEY ||
2132 LX = ELX * TILEX + 14;
2133 LY = ELY * TILEY + 14;
2135 laser.stops_inside_element = TRUE;
2138 AddLaserEdge(LX, LY);
2139 AddDamagedField(ELX, ELY);
2141 if (game_mm.lights_still_needed == 0)
2143 game_mm.level_solved = TRUE;
2145 SetTileCursorActive(FALSE);
2151 boolean HitReflectingWalls(int element, int hit_mask)
2153 // check if laser hits side of a wall with an angle that is not 90°
2154 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2155 hit_mask == HIT_MASK_LEFT ||
2156 hit_mask == HIT_MASK_RIGHT ||
2157 hit_mask == HIT_MASK_BOTTOM))
2159 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2164 if (!IS_DF_GRID(element))
2165 AddLaserEdge(LX, LY);
2167 // check if laser hits wall with an angle of 45°
2168 if (!IS_22_5_ANGLE(laser.current_angle))
2170 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2173 laser.current_angle = get_mirrored_angle(laser.current_angle,
2176 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2179 laser.current_angle = get_mirrored_angle(laser.current_angle,
2183 AddLaserEdge(LX, LY);
2185 XS = 2 * Step[laser.current_angle].x;
2186 YS = 2 * Step[laser.current_angle].y;
2190 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2192 laser.current_angle = get_mirrored_angle(laser.current_angle,
2197 if (!IS_DF_GRID(element))
2198 AddLaserEdge(LX, LY);
2203 if (!IS_DF_GRID(element))
2204 AddLaserEdge(LX, LY + YS / 2);
2207 if (!IS_DF_GRID(element))
2208 AddLaserEdge(LX, LY);
2211 YS = 2 * Step[laser.current_angle].y;
2215 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2217 laser.current_angle = get_mirrored_angle(laser.current_angle,
2222 if (!IS_DF_GRID(element))
2223 AddLaserEdge(LX, LY);
2228 if (!IS_DF_GRID(element))
2229 AddLaserEdge(LX + XS / 2, LY);
2232 if (!IS_DF_GRID(element))
2233 AddLaserEdge(LX, LY);
2236 XS = 2 * Step[laser.current_angle].x;
2242 // reflection at the edge of reflecting DF style wall
2243 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2245 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2246 hit_mask == HIT_MASK_TOPRIGHT) ||
2247 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2248 hit_mask == HIT_MASK_TOPLEFT) ||
2249 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2250 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2251 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2252 hit_mask == HIT_MASK_BOTTOMRIGHT))
2255 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2256 ANG_MIRROR_135 : ANG_MIRROR_45);
2258 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2260 AddDamagedField(ELX, ELY);
2261 AddLaserEdge(LX, LY);
2263 laser.current_angle = get_mirrored_angle(laser.current_angle,
2271 AddLaserEdge(LX, LY);
2277 // reflection inside an edge of reflecting DF style wall
2278 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2280 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2281 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2282 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2283 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2284 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2285 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2286 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2287 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2290 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2291 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2292 ANG_MIRROR_135 : ANG_MIRROR_45);
2294 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2297 AddDamagedField(ELX, ELY);
2300 AddLaserEdge(LX - XS, LY - YS);
2301 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2302 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2304 laser.current_angle = get_mirrored_angle(laser.current_angle,
2312 AddLaserEdge(LX, LY);
2318 // check if laser hits DF style wall with an angle of 90°
2319 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2321 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2322 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2323 (IS_VERT_ANGLE(laser.current_angle) &&
2324 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2326 // laser at last step touched nothing or the same side of the wall
2327 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2329 AddDamagedField(ELX, ELY);
2336 last_hit_mask = hit_mask;
2343 if (!HitOnlyAnEdge(hit_mask))
2345 laser.overloaded = TRUE;
2353 boolean HitAbsorbingWalls(int element, int hit_mask)
2355 if (HitOnlyAnEdge(hit_mask))
2359 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2361 AddLaserEdge(LX - XS, LY - YS);
2368 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2370 AddLaserEdge(LX - XS, LY - YS);
2376 if (IS_WALL_WOOD(element) ||
2377 IS_DF_WALL_WOOD(element) ||
2378 IS_GRID_WOOD(element) ||
2379 IS_GRID_WOOD_FIXED(element) ||
2380 IS_GRID_WOOD_AUTO(element) ||
2381 element == EL_FUSE_ON ||
2382 element == EL_BLOCK_WOOD ||
2383 element == EL_GATE_WOOD)
2385 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2390 if (IS_WALL_ICE(element))
2394 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2395 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2397 // check if laser hits wall with an angle of 90°
2398 if (IS_90_ANGLE(laser.current_angle))
2399 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2401 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2405 for (i = 0; i < 4; i++)
2407 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2408 mask = 15 - (8 >> i);
2409 else if (ABS(XS) == 4 &&
2411 (XS > 0) == (i % 2) &&
2412 (YS < 0) == (i / 2))
2413 mask = 3 + (i / 2) * 9;
2414 else if (ABS(YS) == 4 &&
2416 (XS < 0) == (i % 2) &&
2417 (YS > 0) == (i / 2))
2418 mask = 5 + (i % 2) * 5;
2422 laser.wall_mask = mask;
2424 else if (IS_WALL_AMOEBA(element))
2426 int elx = (LX - 2 * XS) / TILEX;
2427 int ely = (LY - 2 * YS) / TILEY;
2428 int element2 = Tile[elx][ely];
2431 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2433 laser.dest_element = EL_EMPTY;
2441 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2442 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2444 if (IS_90_ANGLE(laser.current_angle))
2445 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2447 laser.dest_element = element2 | EL_WALL_AMOEBA;
2449 laser.wall_mask = mask;
2455 static void OpenExit(int x, int y)
2459 if (!MovDelay[x][y]) // next animation frame
2460 MovDelay[x][y] = 4 * delay;
2462 if (MovDelay[x][y]) // wait some time before next frame
2467 phase = MovDelay[x][y] / delay;
2469 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2470 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2472 if (!MovDelay[x][y])
2474 Tile[x][y] = EL_EXIT_OPEN;
2480 static void OpenSurpriseBall(int x, int y)
2484 if (!MovDelay[x][y]) // next animation frame
2486 if (IS_WALL(Store[x][y]))
2488 DrawWalls_MM(x, y, Store[x][y]);
2490 // copy wall tile to spare bitmap for "melting" animation
2491 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2492 TILEX, TILEY, x * TILEX, y * TILEY);
2494 DrawElement_MM(x, y, EL_GRAY_BALL);
2497 MovDelay[x][y] = 50 * delay;
2500 if (MovDelay[x][y]) // wait some time before next frame
2504 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2508 int dx = RND(26), dy = RND(26);
2510 if (IS_WALL(Store[x][y]))
2512 // copy wall tile from spare bitmap for "melting" animation
2513 bitmap = bitmap_db_field;
2519 int graphic = el2gfx(Store[x][y]);
2521 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2524 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2525 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2527 laser.redraw = TRUE;
2529 MarkTileDirty(x, y);
2532 if (!MovDelay[x][y])
2534 Tile[x][y] = Store[x][y];
2535 Store[x][y] = Store2[x][y] = 0;
2536 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2538 InitField(x, y, FALSE);
2541 ScanLaser_FromLastMirror();
2546 static void OpenEnvelope(int x, int y)
2548 int num_frames = 8; // seven frames plus final empty space
2550 if (!MovDelay[x][y]) // next animation frame
2551 MovDelay[x][y] = num_frames;
2553 if (MovDelay[x][y]) // wait some time before next frame
2555 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2559 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2561 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2562 int frame = num_frames - MovDelay[x][y] - 1;
2564 DrawGraphicAnimation_MM(x, y, graphic, frame);
2566 laser.redraw = TRUE;
2569 if (MovDelay[x][y] == 0)
2571 Tile[x][y] = EL_EMPTY;
2577 ShowEnvelope_MM(nr);
2582 static void MeltIce(int x, int y)
2587 if (!MovDelay[x][y]) // next animation frame
2588 MovDelay[x][y] = frames * delay;
2590 if (MovDelay[x][y]) // wait some time before next frame
2593 int wall_mask = Store2[x][y];
2594 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2597 phase = frames - MovDelay[x][y] / delay - 1;
2599 if (!MovDelay[x][y])
2601 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2602 Store[x][y] = Store2[x][y] = 0;
2604 DrawWalls_MM(x, y, Tile[x][y]);
2606 if (Tile[x][y] == EL_WALL_ICE)
2607 Tile[x][y] = EL_EMPTY;
2609 ScanLaser_FromLastMirror();
2611 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2613 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2615 laser.redraw = TRUE;
2620 static void GrowAmoeba(int x, int y)
2625 if (!MovDelay[x][y]) // next animation frame
2626 MovDelay[x][y] = frames * delay;
2628 if (MovDelay[x][y]) // wait some time before next frame
2631 int wall_mask = Store2[x][y];
2632 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2635 phase = MovDelay[x][y] / delay;
2637 if (!MovDelay[x][y])
2639 Tile[x][y] = real_element;
2640 Store[x][y] = Store2[x][y] = 0;
2642 DrawWalls_MM(x, y, Tile[x][y]);
2643 DrawLaser(0, DL_LASER_ENABLED);
2645 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2647 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2652 static void DrawFieldAnimated_MM(int x, int y)
2656 laser.redraw = TRUE;
2659 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2661 int element = Tile[x][y];
2662 int graphic = el2gfx(element);
2664 if (!getGraphicInfo_NewFrame(x, y, graphic))
2669 laser.redraw = TRUE;
2672 static void DrawFieldTwinkle(int x, int y)
2674 if (MovDelay[x][y] != 0) // wait some time before next frame
2680 if (MovDelay[x][y] != 0)
2682 int graphic = IMG_TWINKLE_WHITE;
2683 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2685 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2688 laser.redraw = TRUE;
2692 static void Explode_MM(int x, int y, int phase, int mode)
2694 int num_phase = 9, delay = 2;
2695 int last_phase = num_phase * delay;
2696 int half_phase = (num_phase / 2) * delay;
2698 laser.redraw = TRUE;
2700 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2702 int center_element = Tile[x][y];
2704 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2706 // put moving element to center field (and let it explode there)
2707 center_element = MovingOrBlocked2Element_MM(x, y);
2708 RemoveMovingField_MM(x, y);
2710 Tile[x][y] = center_element;
2713 Store[x][y] = center_element;
2714 Store2[x][y] = mode;
2716 Tile[x][y] = EL_EXPLODING_OPAQUE;
2718 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2719 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2722 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2724 ExplodePhase[x][y] = 1;
2730 GfxFrame[x][y] = 0; // restart explosion animation
2732 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2734 if (phase == half_phase)
2736 Tile[x][y] = EL_EXPLODING_TRANSP;
2738 if (x == ELX && y == ELY)
2742 if (phase == last_phase)
2744 if (Store[x][y] == EL_BOMB_ACTIVE)
2746 DrawLaser(0, DL_LASER_DISABLED);
2749 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2751 GameOver_MM(GAME_OVER_DELAYED);
2753 laser.overloaded = FALSE;
2755 else if (IS_MCDUFFIN(Store[x][y]))
2757 GameOver_MM(GAME_OVER_BOMB);
2760 Tile[x][y] = EL_EMPTY;
2762 Store[x][y] = Store2[x][y] = 0;
2763 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2765 InitField(x, y, FALSE);
2768 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2770 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2771 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2773 DrawGraphicAnimation_MM(x, y, graphic, frame);
2775 MarkTileDirty(x, y);
2779 static void Bang_MM(int x, int y)
2781 int element = Tile[x][y];
2784 DrawLaser(0, DL_LASER_ENABLED);
2787 if (IS_PACMAN(element))
2788 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2789 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2790 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2791 else if (element == EL_KEY)
2792 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2794 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2796 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2799 void TurnRound(int x, int y)
2811 { 0, 0 }, { 0, 0 }, { 0, 0 },
2816 int left, right, back;
2820 { MV_DOWN, MV_UP, MV_RIGHT },
2821 { MV_UP, MV_DOWN, MV_LEFT },
2823 { MV_LEFT, MV_RIGHT, MV_DOWN },
2824 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2825 { MV_RIGHT, MV_LEFT, MV_UP }
2828 int element = Tile[x][y];
2829 int old_move_dir = MovDir[x][y];
2830 int right_dir = turn[old_move_dir].right;
2831 int back_dir = turn[old_move_dir].back;
2832 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2833 int right_x = x + right_dx, right_y = y + right_dy;
2835 if (element == EL_PACMAN)
2837 boolean can_turn_right = FALSE;
2839 if (IN_LEV_FIELD(right_x, right_y) &&
2840 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2841 can_turn_right = TRUE;
2844 MovDir[x][y] = right_dir;
2846 MovDir[x][y] = back_dir;
2852 static void StartMoving_MM(int x, int y)
2854 int element = Tile[x][y];
2859 if (CAN_MOVE(element))
2863 if (MovDelay[x][y]) // wait some time before next movement
2871 // now make next step
2873 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2875 if (element == EL_PACMAN &&
2876 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2877 !ObjHit(newx, newy, HIT_POS_CENTER))
2879 Store[newx][newy] = Tile[newx][newy];
2880 Tile[newx][newy] = EL_EMPTY;
2882 DrawField_MM(newx, newy);
2884 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2885 ObjHit(newx, newy, HIT_POS_CENTER))
2887 // object was running against a wall
2894 InitMovingField_MM(x, y, MovDir[x][y]);
2898 ContinueMoving_MM(x, y);
2901 static void ContinueMoving_MM(int x, int y)
2903 int element = Tile[x][y];
2904 int direction = MovDir[x][y];
2905 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2906 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2907 int horiz_move = (dx!=0);
2908 int newx = x + dx, newy = y + dy;
2909 int step = (horiz_move ? dx : dy) * TILEX / 8;
2911 MovPos[x][y] += step;
2913 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2915 Tile[x][y] = EL_EMPTY;
2916 Tile[newx][newy] = element;
2918 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2919 MovDelay[newx][newy] = 0;
2921 if (!CAN_MOVE(element))
2922 MovDir[newx][newy] = 0;
2925 DrawField_MM(newx, newy);
2927 Stop[newx][newy] = TRUE;
2929 if (element == EL_PACMAN)
2931 if (Store[newx][newy] == EL_BOMB)
2932 Bang_MM(newx, newy);
2934 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2935 (LX + 2 * XS) / TILEX == newx &&
2936 (LY + 2 * YS) / TILEY == newy)
2943 else // still moving on
2948 laser.redraw = TRUE;
2951 boolean ClickElement(int x, int y, int button)
2953 static DelayCounter click_delay = { CLICK_DELAY };
2954 static boolean new_button = TRUE;
2955 boolean element_clicked = FALSE;
2960 // initialize static variables
2961 click_delay.count = 0;
2962 click_delay.value = CLICK_DELAY;
2968 // do not rotate objects hit by the laser after the game was solved
2969 if (game_mm.level_solved && Hit[x][y])
2972 if (button == MB_RELEASED)
2975 click_delay.value = CLICK_DELAY;
2977 // release eventually hold auto-rotating mirror
2978 RotateMirror(x, y, MB_RELEASED);
2983 if (!FrameReached(&click_delay) && !new_button)
2986 if (button == MB_MIDDLEBUTTON) // middle button has no function
2989 if (!IN_LEV_FIELD(x, y))
2992 if (Tile[x][y] == EL_EMPTY)
2995 element = Tile[x][y];
2997 if (IS_MIRROR(element) ||
2998 IS_BEAMER(element) ||
2999 IS_POLAR(element) ||
3000 IS_POLAR_CROSS(element) ||
3001 IS_DF_MIRROR(element) ||
3002 IS_DF_MIRROR_AUTO(element))
3004 RotateMirror(x, y, button);
3006 element_clicked = TRUE;
3008 else if (IS_MCDUFFIN(element))
3010 if (!laser.fuse_off)
3012 DrawLaser(0, DL_LASER_DISABLED);
3019 element = get_rotated_element(element, BUTTON_ROTATION(button));
3020 laser.start_angle = get_element_angle(element);
3024 Tile[x][y] = element;
3031 if (!laser.fuse_off)
3034 element_clicked = TRUE;
3036 else if (element == EL_FUSE_ON && laser.fuse_off)
3038 if (x != laser.fuse_x || y != laser.fuse_y)
3041 laser.fuse_off = FALSE;
3042 laser.fuse_x = laser.fuse_y = -1;
3044 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3047 element_clicked = TRUE;
3049 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3051 laser.fuse_off = TRUE;
3054 laser.overloaded = FALSE;
3056 DrawLaser(0, DL_LASER_DISABLED);
3057 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3059 element_clicked = TRUE;
3061 else if (element == EL_LIGHTBALL)
3064 RaiseScoreElement_MM(element);
3065 DrawLaser(0, DL_LASER_ENABLED);
3067 element_clicked = TRUE;
3069 else if (IS_ENVELOPE(element))
3071 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3073 element_clicked = TRUE;
3076 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3079 return element_clicked;
3082 void RotateMirror(int x, int y, int button)
3084 if (button == MB_RELEASED)
3086 // release eventually hold auto-rotating mirror
3093 if (IS_MIRROR(Tile[x][y]) ||
3094 IS_POLAR_CROSS(Tile[x][y]) ||
3095 IS_POLAR(Tile[x][y]) ||
3096 IS_BEAMER(Tile[x][y]) ||
3097 IS_DF_MIRROR(Tile[x][y]) ||
3098 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3099 IS_GRID_WOOD_AUTO(Tile[x][y]))
3101 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3103 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3105 if (button == MB_LEFTBUTTON)
3107 // left mouse button only for manual adjustment, no auto-rotating;
3108 // freeze mirror for until mouse button released
3112 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3114 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3118 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3120 int edge = Hit[x][y];
3126 DrawLaser(edge - 1, DL_LASER_DISABLED);
3130 else if (ObjHit(x, y, HIT_POS_CENTER))
3132 int edge = Hit[x][y];
3136 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3141 DrawLaser(edge - 1, DL_LASER_DISABLED);
3148 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3153 if ((IS_BEAMER(Tile[x][y]) ||
3154 IS_POLAR(Tile[x][y]) ||
3155 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3157 if (IS_BEAMER(Tile[x][y]))
3160 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3161 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3174 DrawLaser(0, DL_LASER_ENABLED);
3178 static void AutoRotateMirrors(void)
3182 if (!FrameReached(&rotate_delay))
3185 for (x = 0; x < lev_fieldx; x++)
3187 for (y = 0; y < lev_fieldy; y++)
3189 int element = Tile[x][y];
3191 // do not rotate objects hit by the laser after the game was solved
3192 if (game_mm.level_solved && Hit[x][y])
3195 if (IS_DF_MIRROR_AUTO(element) ||
3196 IS_GRID_WOOD_AUTO(element) ||
3197 IS_GRID_STEEL_AUTO(element) ||
3198 element == EL_REFRACTOR)
3199 RotateMirror(x, y, MB_RIGHTBUTTON);
3204 boolean ObjHit(int obx, int oby, int bits)
3211 if (bits & HIT_POS_CENTER)
3213 if (CheckLaserPixel(cSX + obx + 15,
3218 if (bits & HIT_POS_EDGE)
3220 for (i = 0; i < 4; i++)
3221 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3222 cSY + oby + 31 * (i / 2)))
3226 if (bits & HIT_POS_BETWEEN)
3228 for (i = 0; i < 4; i++)
3229 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3230 cSY + 4 + oby + 22 * (i / 2)))
3237 void DeletePacMan(int px, int py)
3243 if (game_mm.num_pacman <= 1)
3245 game_mm.num_pacman = 0;
3249 for (i = 0; i < game_mm.num_pacman; i++)
3250 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3253 game_mm.num_pacman--;
3255 for (j = i; j < game_mm.num_pacman; j++)
3257 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3258 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3259 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3263 void ColorCycling(void)
3265 static int CC, Cc = 0;
3267 static int color, old = 0xF00, new = 0x010, mult = 1;
3268 static unsigned short red, green, blue;
3270 if (color_status == STATIC_COLORS)
3275 if (CC < Cc || CC > Cc + 2)
3279 color = old + new * mult;
3285 if (ABS(mult) == 16)
3295 red = 0x0e00 * ((color & 0xF00) >> 8);
3296 green = 0x0e00 * ((color & 0x0F0) >> 4);
3297 blue = 0x0e00 * ((color & 0x00F));
3298 SetRGB(pen_magicolor[0], red, green, blue);
3300 red = 0x1111 * ((color & 0xF00) >> 8);
3301 green = 0x1111 * ((color & 0x0F0) >> 4);
3302 blue = 0x1111 * ((color & 0x00F));
3303 SetRGB(pen_magicolor[1], red, green, blue);
3307 static void GameActions_MM_Ext(void)
3314 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3317 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3319 element = Tile[x][y];
3321 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3322 StartMoving_MM(x, y);
3323 else if (IS_MOVING(x, y))
3324 ContinueMoving_MM(x, y);
3325 else if (IS_EXPLODING(element))
3326 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3327 else if (element == EL_EXIT_OPENING)
3329 else if (element == EL_GRAY_BALL_OPENING)
3330 OpenSurpriseBall(x, y);
3331 else if (IS_ENVELOPE_OPENING(element))
3333 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3335 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3337 else if (IS_MIRROR(element) ||
3338 IS_MIRROR_FIXED(element) ||
3339 element == EL_PRISM)
3340 DrawFieldTwinkle(x, y);
3341 else if (element == EL_GRAY_BALL_ACTIVE ||
3342 element == EL_BOMB_ACTIVE ||
3343 element == EL_MINE_ACTIVE)
3344 DrawFieldAnimated_MM(x, y);
3345 else if (!IS_BLOCKED(x, y))
3346 DrawFieldAnimatedIfNeeded_MM(x, y);
3349 AutoRotateMirrors();
3352 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3354 // redraw after Explode_MM() ...
3356 DrawLaser(0, DL_LASER_ENABLED);
3357 laser.redraw = FALSE;
3362 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3366 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3368 DrawLaser(0, DL_LASER_DISABLED);
3373 // skip all following game actions if game is over
3374 if (game_mm.game_over)
3377 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3381 GameOver_MM(GAME_OVER_NO_ENERGY);
3386 if (FrameReached(&energy_delay))
3388 if (game_mm.energy_left > 0)
3389 game_mm.energy_left--;
3391 // when out of energy, wait another frame to play "out of time" sound
3394 element = laser.dest_element;
3397 if (element != Tile[ELX][ELY])
3399 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3400 element, Tile[ELX][ELY]);
3404 if (!laser.overloaded && laser.overload_value == 0 &&
3405 element != EL_BOMB &&
3406 element != EL_BOMB_ACTIVE &&
3407 element != EL_MINE &&
3408 element != EL_MINE_ACTIVE &&
3409 element != EL_GRAY_BALL &&
3410 element != EL_GRAY_BALL_ACTIVE &&
3411 element != EL_BLOCK_STONE &&
3412 element != EL_BLOCK_WOOD &&
3413 element != EL_FUSE_ON &&
3414 element != EL_FUEL_FULL &&
3415 !IS_WALL_ICE(element) &&
3416 !IS_WALL_AMOEBA(element))
3419 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3421 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3422 (!laser.overloaded && laser.overload_value > 0)) &&
3423 FrameReached(&overload_delay))
3425 if (laser.overloaded)
3426 laser.overload_value++;
3428 laser.overload_value--;
3430 if (game_mm.cheat_no_overload)
3432 laser.overloaded = FALSE;
3433 laser.overload_value = 0;
3436 game_mm.laser_overload_value = laser.overload_value;
3438 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3440 SetLaserColor(0xFF);
3442 DrawLaser(0, DL_LASER_ENABLED);
3445 if (!laser.overloaded)
3446 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3447 else if (setup.sound_loops)
3448 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3450 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3452 if (laser.overloaded)
3455 BlitBitmap(pix[PIX_DOOR], drawto,
3456 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3457 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3458 - laser.overload_value,
3459 OVERLOAD_XSIZE, laser.overload_value,
3460 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3461 - laser.overload_value);
3463 redraw_mask |= REDRAW_DOOR_1;
3468 BlitBitmap(pix[PIX_DOOR], drawto,
3469 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3470 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3471 DX_OVERLOAD, DY_OVERLOAD);
3473 redraw_mask |= REDRAW_DOOR_1;
3476 if (laser.overload_value == MAX_LASER_OVERLOAD)
3478 UpdateAndDisplayGameControlValues();
3482 GameOver_MM(GAME_OVER_OVERLOADED);
3493 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3495 if (game_mm.cheat_no_explosion)
3500 laser.dest_element = EL_EXPLODING_OPAQUE;
3505 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3507 laser.fuse_off = TRUE;
3511 DrawLaser(0, DL_LASER_DISABLED);
3512 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3515 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3517 if (!Store2[ELX][ELY]) // check if content element not yet determined
3519 int last_anim_random_frame = gfx.anim_random_frame;
3522 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3523 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3525 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3526 native_mm_level.ball_choice_mode, 0,
3527 game_mm.ball_choice_pos);
3529 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3530 gfx.anim_random_frame = last_anim_random_frame;
3532 game_mm.ball_choice_pos++;
3534 int new_element = native_mm_level.ball_content[element_pos];
3535 int new_element_unmapped = unmap_element(new_element);
3537 if (IS_WALL(new_element_unmapped))
3539 // always use completely filled wall element
3540 new_element = new_element_unmapped | 0x000f;
3542 else if (native_mm_level.rotate_ball_content &&
3543 get_num_elements(new_element) > 1)
3545 // randomly rotate newly created game element
3546 new_element = get_rotated_element(new_element, RND(16));
3549 Store[ELX][ELY] = new_element;
3550 Store2[ELX][ELY] = TRUE;
3553 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3555 laser.dest_element_last = Tile[ELX][ELY];
3565 element = EL_MIRROR_START + RND(16);
3571 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3578 element = (rnd == 0 ? EL_FUSE_ON :
3579 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3580 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3581 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3582 EL_MIRROR_FIXED_START + rnd - 25);
3587 graphic = el2gfx(element);
3589 for (i = 0; i < 50; i++)
3595 BlitBitmap(pix[PIX_BACK], drawto,
3596 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3597 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3598 SX + ELX * TILEX + x,
3599 SY + ELY * TILEY + y);
3601 MarkTileDirty(ELX, ELY);
3604 DrawLaser(0, DL_LASER_ENABLED);
3606 Delay_WithScreenUpdates(50);
3609 Tile[ELX][ELY] = element;
3610 DrawField_MM(ELX, ELY);
3613 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3616 // above stuff: GRAY BALL -> PRISM !!!
3618 LX = ELX * TILEX + 14;
3619 LY = ELY * TILEY + 14;
3620 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3627 laser.num_edges -= 2;
3628 laser.num_damages--;
3632 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3633 if (laser.damage[i].is_mirror)
3637 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3639 DrawLaser(0, DL_LASER_DISABLED);
3641 DrawLaser(0, DL_LASER_DISABLED);
3650 if (IS_WALL_ICE(element) && CT > 50)
3652 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3655 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3656 Store[ELX][ELY] = EL_WALL_ICE;
3657 Store2[ELX][ELY] = laser.wall_mask;
3659 laser.dest_element = Tile[ELX][ELY];
3664 for (i = 0; i < 5; i++)
3670 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3674 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3676 Delay_WithScreenUpdates(100);
3679 if (Tile[ELX][ELY] == EL_WALL_ICE)
3680 Tile[ELX][ELY] = EL_EMPTY;
3684 LX = laser.edge[laser.num_edges].x - cSX2;
3685 LY = laser.edge[laser.num_edges].y - cSY2;
3688 ScanLaser_FromLastMirror();
3693 if (IS_WALL_AMOEBA(element) && CT > 60)
3695 int k1, k2, k3, dx, dy, de, dm;
3696 int element2 = Tile[ELX][ELY];
3698 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3701 for (i = laser.num_damages - 1; i >= 0; i--)
3702 if (laser.damage[i].is_mirror)
3705 r = laser.num_edges;
3706 d = laser.num_damages;
3713 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3716 DrawLaser(0, DL_LASER_ENABLED);
3719 x = laser.damage[k1].x;
3720 y = laser.damage[k1].y;
3725 for (i = 0; i < 4; i++)
3727 if (laser.wall_mask & (1 << i))
3729 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3730 cSY + ELY * TILEY + 31 * (i / 2)))
3733 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3734 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3741 for (i = 0; i < 4; i++)
3743 if (laser.wall_mask & (1 << i))
3745 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3746 cSY + ELY * TILEY + 31 * (i / 2)))
3753 if (laser.num_beamers > 0 ||
3754 k1 < 1 || k2 < 4 || k3 < 4 ||
3755 CheckLaserPixel(cSX + ELX * TILEX + 14,
3756 cSY + ELY * TILEY + 14))
3758 laser.num_edges = r;
3759 laser.num_damages = d;
3761 DrawLaser(0, DL_LASER_DISABLED);
3764 Tile[ELX][ELY] = element | laser.wall_mask;
3768 de = Tile[ELX][ELY];
3769 dm = laser.wall_mask;
3773 int x = ELX, y = ELY;
3774 int wall_mask = laser.wall_mask;
3777 DrawLaser(0, DL_LASER_ENABLED);
3779 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3781 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3782 Store[x][y] = EL_WALL_AMOEBA;
3783 Store2[x][y] = wall_mask;
3789 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3791 DrawLaser(0, DL_LASER_ENABLED);
3793 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3795 for (i = 4; i >= 0; i--)
3797 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3800 Delay_WithScreenUpdates(20);
3803 DrawLaser(0, DL_LASER_ENABLED);
3808 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3809 laser.stops_inside_element && CT > native_mm_level.time_block)
3814 if (ABS(XS) > ABS(YS))
3821 for (i = 0; i < 4; i++)
3828 x = ELX + Step[k * 4].x;
3829 y = ELY + Step[k * 4].y;
3831 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3834 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3842 laser.overloaded = (element == EL_BLOCK_STONE);
3847 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3850 Tile[x][y] = element;
3852 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3855 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3857 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3858 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3866 if (element == EL_FUEL_FULL && CT > 10)
3868 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3869 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3871 for (i = start; i <= num_init_game_frames; i++)
3873 if (i == num_init_game_frames)
3874 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3875 else if (setup.sound_loops)
3876 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3878 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3880 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3882 UpdateAndDisplayGameControlValues();
3887 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3889 DrawField_MM(ELX, ELY);
3891 DrawLaser(0, DL_LASER_ENABLED);
3899 void GameActions_MM(struct MouseActionInfo action)
3901 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3902 boolean button_released = (action.button == MB_RELEASED);
3904 GameActions_MM_Ext();
3906 CheckSingleStepMode_MM(element_clicked, button_released);
3909 void MovePacMen(void)
3911 int mx, my, ox, oy, nx, ny;
3915 if (++pacman_nr >= game_mm.num_pacman)
3918 game_mm.pacman[pacman_nr].dir--;
3920 for (l = 1; l < 5; l++)
3922 game_mm.pacman[pacman_nr].dir++;
3924 if (game_mm.pacman[pacman_nr].dir > 4)
3925 game_mm.pacman[pacman_nr].dir = 1;
3927 if (game_mm.pacman[pacman_nr].dir % 2)
3930 my = game_mm.pacman[pacman_nr].dir - 2;
3935 mx = 3 - game_mm.pacman[pacman_nr].dir;
3938 ox = game_mm.pacman[pacman_nr].x;
3939 oy = game_mm.pacman[pacman_nr].y;
3942 element = Tile[nx][ny];
3944 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3947 if (!IS_EATABLE4PACMAN(element))
3950 if (ObjHit(nx, ny, HIT_POS_CENTER))
3953 Tile[ox][oy] = EL_EMPTY;
3955 EL_PACMAN_RIGHT - 1 +
3956 (game_mm.pacman[pacman_nr].dir - 1 +
3957 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3959 game_mm.pacman[pacman_nr].x = nx;
3960 game_mm.pacman[pacman_nr].y = ny;
3962 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3964 if (element != EL_EMPTY)
3966 int graphic = el2gfx(Tile[nx][ny]);
3971 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3974 ox = cSX + ox * TILEX;
3975 oy = cSY + oy * TILEY;
3977 for (i = 1; i < 33; i += 2)
3978 BlitBitmap(bitmap, window,
3979 src_x, src_y, TILEX, TILEY,
3980 ox + i * mx, oy + i * my);
3981 Ct = Ct + FrameCounter - CT;
3984 DrawField_MM(nx, ny);
3987 if (!laser.fuse_off)
3989 DrawLaser(0, DL_LASER_ENABLED);
3991 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3993 AddDamagedField(nx, ny);
3995 laser.damage[laser.num_damages - 1].edge = 0;
3999 if (element == EL_BOMB)
4000 DeletePacMan(nx, ny);
4002 if (IS_WALL_AMOEBA(element) &&
4003 (LX + 2 * XS) / TILEX == nx &&
4004 (LY + 2 * YS) / TILEY == ny)
4014 static void InitMovingField_MM(int x, int y, int direction)
4016 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4017 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4019 MovDir[x][y] = direction;
4020 MovDir[newx][newy] = direction;
4022 if (Tile[newx][newy] == EL_EMPTY)
4023 Tile[newx][newy] = EL_BLOCKED;
4026 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4028 int direction = MovDir[x][y];
4029 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4030 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4036 static void Blocked2Moving_MM(int x, int y,
4037 int *comes_from_x, int *comes_from_y)
4039 int oldx = x, oldy = y;
4040 int direction = MovDir[x][y];
4042 if (direction == MV_LEFT)
4044 else if (direction == MV_RIGHT)
4046 else if (direction == MV_UP)
4048 else if (direction == MV_DOWN)
4051 *comes_from_x = oldx;
4052 *comes_from_y = oldy;
4055 static int MovingOrBlocked2Element_MM(int x, int y)
4057 int element = Tile[x][y];
4059 if (element == EL_BLOCKED)
4063 Blocked2Moving_MM(x, y, &oldx, &oldy);
4065 return Tile[oldx][oldy];
4072 static void RemoveField(int x, int y)
4074 Tile[x][y] = EL_EMPTY;
4081 static void RemoveMovingField_MM(int x, int y)
4083 int oldx = x, oldy = y, newx = x, newy = y;
4085 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4088 if (IS_MOVING(x, y))
4090 Moving2Blocked_MM(x, y, &newx, &newy);
4091 if (Tile[newx][newy] != EL_BLOCKED)
4094 else if (Tile[x][y] == EL_BLOCKED)
4096 Blocked2Moving_MM(x, y, &oldx, &oldy);
4097 if (!IS_MOVING(oldx, oldy))
4101 Tile[oldx][oldy] = EL_EMPTY;
4102 Tile[newx][newy] = EL_EMPTY;
4103 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4104 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4106 DrawLevelField_MM(oldx, oldy);
4107 DrawLevelField_MM(newx, newy);
4110 void PlaySoundLevel(int x, int y, int sound_nr)
4112 int sx = SCREENX(x), sy = SCREENY(y);
4114 int silence_distance = 8;
4116 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4117 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4120 if (!IN_LEV_FIELD(x, y) ||
4121 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4122 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4125 volume = SOUND_MAX_VOLUME;
4128 stereo = (sx - SCR_FIELDX/2) * 12;
4130 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4131 if (stereo > SOUND_MAX_RIGHT)
4132 stereo = SOUND_MAX_RIGHT;
4133 if (stereo < SOUND_MAX_LEFT)
4134 stereo = SOUND_MAX_LEFT;
4137 if (!IN_SCR_FIELD(sx, sy))
4139 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4140 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4142 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4145 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4148 static void RaiseScore_MM(int value)
4150 game_mm.score += value;
4153 void RaiseScoreElement_MM(int element)
4158 case EL_PACMAN_RIGHT:
4160 case EL_PACMAN_LEFT:
4161 case EL_PACMAN_DOWN:
4162 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4166 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4171 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4175 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4184 // ----------------------------------------------------------------------------
4185 // Mirror Magic game engine snapshot handling functions
4186 // ----------------------------------------------------------------------------
4188 void SaveEngineSnapshotValues_MM(void)
4192 engine_snapshot_mm.game_mm = game_mm;
4193 engine_snapshot_mm.laser = laser;
4195 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4197 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4199 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4200 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4201 engine_snapshot_mm.Box[x][y] = Box[x][y];
4202 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4206 engine_snapshot_mm.LX = LX;
4207 engine_snapshot_mm.LY = LY;
4208 engine_snapshot_mm.XS = XS;
4209 engine_snapshot_mm.YS = YS;
4210 engine_snapshot_mm.ELX = ELX;
4211 engine_snapshot_mm.ELY = ELY;
4212 engine_snapshot_mm.CT = CT;
4213 engine_snapshot_mm.Ct = Ct;
4215 engine_snapshot_mm.last_LX = last_LX;
4216 engine_snapshot_mm.last_LY = last_LY;
4217 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4218 engine_snapshot_mm.hold_x = hold_x;
4219 engine_snapshot_mm.hold_y = hold_y;
4220 engine_snapshot_mm.pacman_nr = pacman_nr;
4222 engine_snapshot_mm.rotate_delay = rotate_delay;
4223 engine_snapshot_mm.pacman_delay = pacman_delay;
4224 engine_snapshot_mm.energy_delay = energy_delay;
4225 engine_snapshot_mm.overload_delay = overload_delay;
4228 void LoadEngineSnapshotValues_MM(void)
4232 // stored engine snapshot buffers already restored at this point
4234 game_mm = engine_snapshot_mm.game_mm;
4235 laser = engine_snapshot_mm.laser;
4237 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4239 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4241 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4242 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4243 Box[x][y] = engine_snapshot_mm.Box[x][y];
4244 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4248 LX = engine_snapshot_mm.LX;
4249 LY = engine_snapshot_mm.LY;
4250 XS = engine_snapshot_mm.XS;
4251 YS = engine_snapshot_mm.YS;
4252 ELX = engine_snapshot_mm.ELX;
4253 ELY = engine_snapshot_mm.ELY;
4254 CT = engine_snapshot_mm.CT;
4255 Ct = engine_snapshot_mm.Ct;
4257 last_LX = engine_snapshot_mm.last_LX;
4258 last_LY = engine_snapshot_mm.last_LY;
4259 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4260 hold_x = engine_snapshot_mm.hold_x;
4261 hold_y = engine_snapshot_mm.hold_y;
4262 pacman_nr = engine_snapshot_mm.pacman_nr;
4264 rotate_delay = engine_snapshot_mm.rotate_delay;
4265 pacman_delay = engine_snapshot_mm.pacman_delay;
4266 energy_delay = engine_snapshot_mm.energy_delay;
4267 overload_delay = engine_snapshot_mm.overload_delay;
4269 RedrawPlayfield_MM();
4272 static int getAngleFromTouchDelta(int dx, int dy, int base)
4274 double pi = 3.141592653;
4275 double rad = atan2((double)-dy, (double)dx);
4276 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4277 double deg = rad2 * 180.0 / pi;
4279 return (int)(deg * base / 360.0 + 0.5) % base;
4282 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4284 // calculate start (source) position to be at the middle of the tile
4285 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4286 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4287 int dx = dst_mx - src_mx;
4288 int dy = dst_my - src_my;
4297 if (!IN_LEV_FIELD(x, y))
4300 element = Tile[x][y];
4302 if (!IS_MCDUFFIN(element) &&
4303 !IS_MIRROR(element) &&
4304 !IS_BEAMER(element) &&
4305 !IS_POLAR(element) &&
4306 !IS_POLAR_CROSS(element) &&
4307 !IS_DF_MIRROR(element))
4310 angle_old = get_element_angle(element);
4312 if (IS_MCDUFFIN(element))
4314 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4315 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4316 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4317 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4320 else if (IS_MIRROR(element) ||
4321 IS_DF_MIRROR(element))
4323 for (i = 0; i < laser.num_damages; i++)
4325 if (laser.damage[i].x == x &&
4326 laser.damage[i].y == y &&
4327 ObjHit(x, y, HIT_POS_CENTER))
4329 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4330 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4337 if (angle_new == -1)
4339 if (IS_MIRROR(element) ||
4340 IS_DF_MIRROR(element) ||
4344 if (IS_POLAR_CROSS(element))
4347 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4350 button = (angle_new == angle_old ? 0 :
4351 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4352 MB_LEFTBUTTON : MB_RIGHTBUTTON);