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)
1499 // prevent cutting through laser emitter with laser beam
1500 if (IS_LASER(element))
1503 // skip the whole element before continuing the scan
1509 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1511 if (LX/TILEX > ELX || LY/TILEY > ELY)
1513 /* skipping scan positions to the right and down skips one scan
1514 position too much, because this is only the top left scan position
1515 of totally four scan positions (plus one to the right, one to the
1516 bottom and one to the bottom right) */
1526 Debug("game:mm:HitElement", "(2): element == %d", element);
1529 if (LX + 5 * XS < 0 ||
1539 Debug("game:mm:HitElement", "(3): element == %d", element);
1542 if (IS_POLAR(element) &&
1543 ((element - EL_POLAR_START) % 2 ||
1544 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1546 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1548 laser.num_damages--;
1553 if (IS_POLAR_CROSS(element) &&
1554 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1556 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1558 laser.num_damages--;
1563 if (!IS_BEAMER(element) &&
1564 !IS_FIBRE_OPTIC(element) &&
1565 !IS_GRID_WOOD(element) &&
1566 element != EL_FUEL_EMPTY)
1569 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1570 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1572 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1575 LX = ELX * TILEX + 14;
1576 LY = ELY * TILEY + 14;
1578 AddLaserEdge(LX, LY);
1581 if (IS_MIRROR(element) ||
1582 IS_MIRROR_FIXED(element) ||
1583 IS_POLAR(element) ||
1584 IS_POLAR_CROSS(element) ||
1585 IS_DF_MIRROR(element) ||
1586 IS_DF_MIRROR_AUTO(element) ||
1587 element == EL_PRISM ||
1588 element == EL_REFRACTOR)
1590 int current_angle = laser.current_angle;
1593 laser.num_damages--;
1595 AddDamagedField(ELX, ELY);
1597 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1600 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1602 if (IS_MIRROR(element) ||
1603 IS_MIRROR_FIXED(element) ||
1604 IS_DF_MIRROR(element) ||
1605 IS_DF_MIRROR_AUTO(element))
1606 laser.current_angle = get_mirrored_angle(laser.current_angle,
1607 get_element_angle(element));
1609 if (element == EL_PRISM || element == EL_REFRACTOR)
1610 laser.current_angle = RND(16);
1612 XS = 2 * Step[laser.current_angle].x;
1613 YS = 2 * Step[laser.current_angle].y;
1615 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1620 LX += step_size * XS;
1621 LY += step_size * YS;
1623 // draw sparkles on mirror
1624 if ((IS_MIRROR(element) ||
1625 IS_MIRROR_FIXED(element) ||
1626 element == EL_PRISM) &&
1627 current_angle != laser.current_angle)
1629 MovDelay[ELX][ELY] = 11; // start animation
1632 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1633 current_angle != laser.current_angle)
1634 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1637 (get_opposite_angle(laser.current_angle) ==
1638 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1640 return (laser.overloaded ? TRUE : FALSE);
1643 if (element == EL_FUEL_FULL)
1645 laser.stops_inside_element = TRUE;
1650 if (element == EL_BOMB || element == EL_MINE)
1652 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1654 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1656 laser.dest_element_last = Tile[ELX][ELY];
1657 laser.dest_element_last_x = ELX;
1658 laser.dest_element_last_y = ELY;
1660 if (element == EL_MINE)
1661 laser.overloaded = TRUE;
1664 if (element == EL_KETTLE ||
1665 element == EL_CELL ||
1666 element == EL_KEY ||
1667 element == EL_LIGHTBALL ||
1668 element == EL_PACMAN ||
1669 IS_PACMAN(element) ||
1670 IS_ENVELOPE(element))
1672 if (!IS_PACMAN(element) &&
1673 !IS_ENVELOPE(element))
1676 if (element == EL_PACMAN)
1679 if (element == EL_KETTLE || element == EL_CELL)
1681 if (game_mm.kettles_still_needed > 0)
1682 game_mm.kettles_still_needed--;
1684 game.snapshot.collected_item = TRUE;
1686 if (game_mm.kettles_still_needed == 0)
1690 DrawLaser(0, DL_LASER_ENABLED);
1693 else if (element == EL_KEY)
1697 else if (IS_PACMAN(element))
1699 DeletePacMan(ELX, ELY);
1701 else if (IS_ENVELOPE(element))
1703 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1706 RaiseScoreElement_MM(element);
1711 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1713 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1715 DrawLaser(0, DL_LASER_ENABLED);
1717 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1719 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1720 game_mm.lights_still_needed--;
1724 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1725 game_mm.lights_still_needed++;
1728 DrawField_MM(ELX, ELY);
1729 DrawLaser(0, DL_LASER_ENABLED);
1734 laser.stops_inside_element = TRUE;
1740 Debug("game:mm:HitElement", "(4): element == %d", element);
1743 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1744 laser.num_beamers < MAX_NUM_BEAMERS &&
1745 laser.beamer[BEAMER_NR(element)][1].num)
1747 int beamer_angle = get_element_angle(element);
1748 int beamer_nr = BEAMER_NR(element);
1752 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1755 laser.num_damages--;
1757 if (IS_FIBRE_OPTIC(element) ||
1758 laser.current_angle == get_opposite_angle(beamer_angle))
1762 LX = ELX * TILEX + 14;
1763 LY = ELY * TILEY + 14;
1765 AddLaserEdge(LX, LY);
1766 AddDamagedField(ELX, ELY);
1768 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1771 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1773 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1774 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1775 ELX = laser.beamer[beamer_nr][pos].x;
1776 ELY = laser.beamer[beamer_nr][pos].y;
1777 LX = ELX * TILEX + 14;
1778 LY = ELY * TILEY + 14;
1780 if (IS_BEAMER(element))
1782 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1783 XS = 2 * Step[laser.current_angle].x;
1784 YS = 2 * Step[laser.current_angle].y;
1787 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
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 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1802 LX += step_size * XS;
1803 LY += step_size * YS;
1805 laser.num_beamers++;
1814 boolean HitOnlyAnEdge(int hit_mask)
1816 // check if the laser hit only the edge of an element and, if so, go on
1819 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1823 if ((hit_mask == HIT_MASK_TOPLEFT ||
1824 hit_mask == HIT_MASK_TOPRIGHT ||
1825 hit_mask == HIT_MASK_BOTTOMLEFT ||
1826 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1827 laser.current_angle % 4) // angle is not 90°
1831 if (hit_mask == HIT_MASK_TOPLEFT)
1836 else if (hit_mask == HIT_MASK_TOPRIGHT)
1841 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1846 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1852 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1858 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1865 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1871 boolean HitPolarizer(int element, int hit_mask)
1873 if (HitOnlyAnEdge(hit_mask))
1876 if (IS_DF_GRID(element))
1878 int grid_angle = get_element_angle(element);
1881 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1882 grid_angle, laser.current_angle);
1885 AddLaserEdge(LX, LY);
1886 AddDamagedField(ELX, ELY);
1889 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1891 if (laser.current_angle == grid_angle ||
1892 laser.current_angle == get_opposite_angle(grid_angle))
1894 // skip the whole element before continuing the scan
1900 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1902 if (LX/TILEX > ELX || LY/TILEY > ELY)
1904 /* skipping scan positions to the right and down skips one scan
1905 position too much, because this is only the top left scan position
1906 of totally four scan positions (plus one to the right, one to the
1907 bottom and one to the bottom right) */
1913 AddLaserEdge(LX, LY);
1919 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1921 LX / TILEX, LY / TILEY,
1922 LX % TILEX, LY % TILEY);
1927 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1929 return HitReflectingWalls(element, hit_mask);
1933 return HitAbsorbingWalls(element, hit_mask);
1936 else if (IS_GRID_STEEL(element))
1938 return HitReflectingWalls(element, hit_mask);
1940 else // IS_GRID_WOOD
1942 return HitAbsorbingWalls(element, hit_mask);
1948 boolean HitBlock(int element, int hit_mask)
1950 boolean check = FALSE;
1952 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1953 game_mm.num_keys == 0)
1956 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1959 int ex = ELX * TILEX + 14;
1960 int ey = ELY * TILEY + 14;
1964 for (i = 1; i < 32; i++)
1969 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1974 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1975 return HitAbsorbingWalls(element, hit_mask);
1979 AddLaserEdge(LX - XS, LY - YS);
1980 AddDamagedField(ELX, ELY);
1983 Box[ELX][ELY] = laser.num_edges;
1985 return HitReflectingWalls(element, hit_mask);
1988 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1990 int xs = XS / 2, ys = YS / 2;
1991 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1992 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1994 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1995 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1997 laser.overloaded = (element == EL_GATE_STONE);
2002 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2003 (hit_mask == HIT_MASK_TOP ||
2004 hit_mask == HIT_MASK_LEFT ||
2005 hit_mask == HIT_MASK_RIGHT ||
2006 hit_mask == HIT_MASK_BOTTOM))
2007 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2008 hit_mask == HIT_MASK_BOTTOM),
2009 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2010 hit_mask == HIT_MASK_RIGHT));
2011 AddLaserEdge(LX, LY);
2017 if (element == EL_GATE_STONE && Box[ELX][ELY])
2019 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2031 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2033 int xs = XS / 2, ys = YS / 2;
2034 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2035 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2037 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2038 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2040 laser.overloaded = (element == EL_BLOCK_STONE);
2045 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2046 (hit_mask == HIT_MASK_TOP ||
2047 hit_mask == HIT_MASK_LEFT ||
2048 hit_mask == HIT_MASK_RIGHT ||
2049 hit_mask == HIT_MASK_BOTTOM))
2050 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2051 hit_mask == HIT_MASK_BOTTOM),
2052 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2053 hit_mask == HIT_MASK_RIGHT));
2054 AddDamagedField(ELX, ELY);
2056 LX = ELX * TILEX + 14;
2057 LY = ELY * TILEY + 14;
2059 AddLaserEdge(LX, LY);
2061 laser.stops_inside_element = TRUE;
2069 boolean HitLaserSource(int element, int hit_mask)
2071 if (HitOnlyAnEdge(hit_mask))
2074 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2076 laser.overloaded = TRUE;
2081 boolean HitLaserDestination(int element, int hit_mask)
2083 if (HitOnlyAnEdge(hit_mask))
2086 if (element != EL_EXIT_OPEN &&
2087 !(IS_RECEIVER(element) &&
2088 game_mm.kettles_still_needed == 0 &&
2089 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2091 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2096 if (IS_RECEIVER(element) ||
2097 (IS_22_5_ANGLE(laser.current_angle) &&
2098 (ELX != (LX + 6 * XS) / TILEX ||
2099 ELY != (LY + 6 * YS) / TILEY ||
2108 LX = ELX * TILEX + 14;
2109 LY = ELY * TILEY + 14;
2111 laser.stops_inside_element = TRUE;
2114 AddLaserEdge(LX, LY);
2115 AddDamagedField(ELX, ELY);
2117 if (game_mm.lights_still_needed == 0)
2119 game_mm.level_solved = TRUE;
2121 SetTileCursorActive(FALSE);
2127 boolean HitReflectingWalls(int element, int hit_mask)
2129 // check if laser hits side of a wall with an angle that is not 90°
2130 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2131 hit_mask == HIT_MASK_LEFT ||
2132 hit_mask == HIT_MASK_RIGHT ||
2133 hit_mask == HIT_MASK_BOTTOM))
2135 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2140 if (!IS_DF_GRID(element))
2141 AddLaserEdge(LX, LY);
2143 // check if laser hits wall with an angle of 45°
2144 if (!IS_22_5_ANGLE(laser.current_angle))
2146 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2149 laser.current_angle = get_mirrored_angle(laser.current_angle,
2152 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2155 laser.current_angle = get_mirrored_angle(laser.current_angle,
2159 AddLaserEdge(LX, LY);
2161 XS = 2 * Step[laser.current_angle].x;
2162 YS = 2 * Step[laser.current_angle].y;
2166 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2168 laser.current_angle = get_mirrored_angle(laser.current_angle,
2173 if (!IS_DF_GRID(element))
2174 AddLaserEdge(LX, LY);
2179 if (!IS_DF_GRID(element))
2180 AddLaserEdge(LX, LY + YS / 2);
2183 if (!IS_DF_GRID(element))
2184 AddLaserEdge(LX, LY);
2187 YS = 2 * Step[laser.current_angle].y;
2191 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2193 laser.current_angle = get_mirrored_angle(laser.current_angle,
2198 if (!IS_DF_GRID(element))
2199 AddLaserEdge(LX, LY);
2204 if (!IS_DF_GRID(element))
2205 AddLaserEdge(LX + XS / 2, LY);
2208 if (!IS_DF_GRID(element))
2209 AddLaserEdge(LX, LY);
2212 XS = 2 * Step[laser.current_angle].x;
2218 // reflection at the edge of reflecting DF style wall
2219 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2221 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2222 hit_mask == HIT_MASK_TOPRIGHT) ||
2223 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2224 hit_mask == HIT_MASK_TOPLEFT) ||
2225 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2226 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2227 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2228 hit_mask == HIT_MASK_BOTTOMRIGHT))
2231 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2232 ANG_MIRROR_135 : ANG_MIRROR_45);
2234 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2236 AddDamagedField(ELX, ELY);
2237 AddLaserEdge(LX, LY);
2239 laser.current_angle = get_mirrored_angle(laser.current_angle,
2247 AddLaserEdge(LX, LY);
2253 // reflection inside an edge of reflecting DF style wall
2254 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2256 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2257 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2258 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2259 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2260 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2261 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2262 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2263 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2266 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2267 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2268 ANG_MIRROR_135 : ANG_MIRROR_45);
2270 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2273 AddDamagedField(ELX, ELY);
2276 AddLaserEdge(LX - XS, LY - YS);
2277 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2278 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2280 laser.current_angle = get_mirrored_angle(laser.current_angle,
2288 AddLaserEdge(LX, LY);
2294 // check if laser hits DF style wall with an angle of 90°
2295 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2297 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2298 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2299 (IS_VERT_ANGLE(laser.current_angle) &&
2300 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2302 // laser at last step touched nothing or the same side of the wall
2303 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2305 AddDamagedField(ELX, ELY);
2312 last_hit_mask = hit_mask;
2319 if (!HitOnlyAnEdge(hit_mask))
2321 laser.overloaded = TRUE;
2329 boolean HitAbsorbingWalls(int element, int hit_mask)
2331 if (HitOnlyAnEdge(hit_mask))
2335 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2337 AddLaserEdge(LX - XS, LY - YS);
2344 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2346 AddLaserEdge(LX - XS, LY - YS);
2352 if (IS_WALL_WOOD(element) ||
2353 IS_DF_WALL_WOOD(element) ||
2354 IS_GRID_WOOD(element) ||
2355 IS_GRID_WOOD_FIXED(element) ||
2356 IS_GRID_WOOD_AUTO(element) ||
2357 element == EL_FUSE_ON ||
2358 element == EL_BLOCK_WOOD ||
2359 element == EL_GATE_WOOD)
2361 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2366 if (IS_WALL_ICE(element))
2370 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2371 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2373 // check if laser hits wall with an angle of 90°
2374 if (IS_90_ANGLE(laser.current_angle))
2375 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2377 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2381 for (i = 0; i < 4; i++)
2383 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2384 mask = 15 - (8 >> i);
2385 else if (ABS(XS) == 4 &&
2387 (XS > 0) == (i % 2) &&
2388 (YS < 0) == (i / 2))
2389 mask = 3 + (i / 2) * 9;
2390 else if (ABS(YS) == 4 &&
2392 (XS < 0) == (i % 2) &&
2393 (YS > 0) == (i / 2))
2394 mask = 5 + (i % 2) * 5;
2398 laser.wall_mask = mask;
2400 else if (IS_WALL_AMOEBA(element))
2402 int elx = (LX - 2 * XS) / TILEX;
2403 int ely = (LY - 2 * YS) / TILEY;
2404 int element2 = Tile[elx][ely];
2407 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2409 laser.dest_element = EL_EMPTY;
2417 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2418 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2420 if (IS_90_ANGLE(laser.current_angle))
2421 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2423 laser.dest_element = element2 | EL_WALL_AMOEBA;
2425 laser.wall_mask = mask;
2431 static void OpenExit(int x, int y)
2435 if (!MovDelay[x][y]) // next animation frame
2436 MovDelay[x][y] = 4 * delay;
2438 if (MovDelay[x][y]) // wait some time before next frame
2443 phase = MovDelay[x][y] / delay;
2445 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2446 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2448 if (!MovDelay[x][y])
2450 Tile[x][y] = EL_EXIT_OPEN;
2456 static void OpenSurpriseBall(int x, int y)
2460 if (!MovDelay[x][y]) // next animation frame
2461 MovDelay[x][y] = 50 * delay;
2463 if (MovDelay[x][y]) // wait some time before next frame
2467 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2470 int graphic = el2gfx(Store[x][y]);
2472 int dx = RND(26), dy = RND(26);
2474 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2476 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2477 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2479 laser.redraw = TRUE;
2481 MarkTileDirty(x, y);
2484 if (!MovDelay[x][y])
2486 Tile[x][y] = Store[x][y];
2487 Store[x][y] = Store2[x][y] = 0;
2488 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2490 InitField(x, y, FALSE);
2498 static void OpenEnvelope(int x, int y)
2500 int num_frames = 8; // seven frames plus final empty space
2502 if (!MovDelay[x][y]) // next animation frame
2503 MovDelay[x][y] = num_frames;
2505 if (MovDelay[x][y]) // wait some time before next frame
2507 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2511 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2513 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2514 int frame = num_frames - MovDelay[x][y] - 1;
2516 DrawGraphicAnimation_MM(x, y, graphic, frame);
2518 laser.redraw = TRUE;
2521 if (MovDelay[x][y] == 0)
2523 Tile[x][y] = EL_EMPTY;
2529 ShowEnvelope_MM(nr);
2534 static void MeltIce(int x, int y)
2539 if (!MovDelay[x][y]) // next animation frame
2540 MovDelay[x][y] = frames * delay;
2542 if (MovDelay[x][y]) // wait some time before next frame
2545 int wall_mask = Store2[x][y];
2546 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2549 phase = frames - MovDelay[x][y] / delay - 1;
2551 if (!MovDelay[x][y])
2555 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2556 Store[x][y] = Store2[x][y] = 0;
2558 DrawWalls_MM(x, y, Tile[x][y]);
2560 if (Tile[x][y] == EL_WALL_ICE)
2561 Tile[x][y] = EL_EMPTY;
2563 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2564 if (laser.damage[i].is_mirror)
2568 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2570 DrawLaser(0, DL_LASER_DISABLED);
2574 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2576 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2578 laser.redraw = TRUE;
2583 static void GrowAmoeba(int x, int y)
2588 if (!MovDelay[x][y]) // next animation frame
2589 MovDelay[x][y] = frames * delay;
2591 if (MovDelay[x][y]) // wait some time before next frame
2594 int wall_mask = Store2[x][y];
2595 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2598 phase = MovDelay[x][y] / delay;
2600 if (!MovDelay[x][y])
2602 Tile[x][y] = real_element;
2603 Store[x][y] = Store2[x][y] = 0;
2605 DrawWalls_MM(x, y, Tile[x][y]);
2606 DrawLaser(0, DL_LASER_ENABLED);
2608 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2610 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2615 static void DrawFieldAnimated_MM(int x, int y)
2619 laser.redraw = TRUE;
2622 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2624 int element = Tile[x][y];
2625 int graphic = el2gfx(element);
2627 if (!getGraphicInfo_NewFrame(x, y, graphic))
2632 laser.redraw = TRUE;
2635 static void DrawFieldTwinkle(int x, int y)
2637 if (MovDelay[x][y] != 0) // wait some time before next frame
2643 if (MovDelay[x][y] != 0)
2645 int graphic = IMG_TWINKLE_WHITE;
2646 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2648 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2651 laser.redraw = TRUE;
2655 static void Explode_MM(int x, int y, int phase, int mode)
2657 int num_phase = 9, delay = 2;
2658 int last_phase = num_phase * delay;
2659 int half_phase = (num_phase / 2) * delay;
2661 laser.redraw = TRUE;
2663 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2665 int center_element = Tile[x][y];
2667 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2669 // put moving element to center field (and let it explode there)
2670 center_element = MovingOrBlocked2Element_MM(x, y);
2671 RemoveMovingField_MM(x, y);
2673 Tile[x][y] = center_element;
2676 Store[x][y] = center_element;
2677 Store2[x][y] = mode;
2679 Tile[x][y] = EL_EXPLODING_OPAQUE;
2681 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2682 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2685 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2687 ExplodePhase[x][y] = 1;
2693 GfxFrame[x][y] = 0; // restart explosion animation
2695 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2697 if (phase == half_phase)
2699 Tile[x][y] = EL_EXPLODING_TRANSP;
2701 if (x == ELX && y == ELY)
2705 if (phase == last_phase)
2707 if (Store[x][y] == EL_BOMB_ACTIVE)
2709 DrawLaser(0, DL_LASER_DISABLED);
2712 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2714 GameOver_MM(GAME_OVER_DELAYED);
2716 laser.overloaded = FALSE;
2718 else if (IS_MCDUFFIN(Store[x][y]))
2720 GameOver_MM(GAME_OVER_BOMB);
2723 Tile[x][y] = EL_EMPTY;
2725 Store[x][y] = Store2[x][y] = 0;
2726 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2728 InitField(x, y, FALSE);
2731 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2733 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2734 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2736 DrawGraphicAnimation_MM(x, y, graphic, frame);
2738 MarkTileDirty(x, y);
2742 static void Bang_MM(int x, int y)
2744 int element = Tile[x][y];
2747 DrawLaser(0, DL_LASER_ENABLED);
2750 if (IS_PACMAN(element))
2751 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2752 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2753 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2754 else if (element == EL_KEY)
2755 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2757 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2759 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2762 void TurnRound(int x, int y)
2774 { 0, 0 }, { 0, 0 }, { 0, 0 },
2779 int left, right, back;
2783 { MV_DOWN, MV_UP, MV_RIGHT },
2784 { MV_UP, MV_DOWN, MV_LEFT },
2786 { MV_LEFT, MV_RIGHT, MV_DOWN },
2787 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2788 { MV_RIGHT, MV_LEFT, MV_UP }
2791 int element = Tile[x][y];
2792 int old_move_dir = MovDir[x][y];
2793 int right_dir = turn[old_move_dir].right;
2794 int back_dir = turn[old_move_dir].back;
2795 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2796 int right_x = x + right_dx, right_y = y + right_dy;
2798 if (element == EL_PACMAN)
2800 boolean can_turn_right = FALSE;
2802 if (IN_LEV_FIELD(right_x, right_y) &&
2803 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2804 can_turn_right = TRUE;
2807 MovDir[x][y] = right_dir;
2809 MovDir[x][y] = back_dir;
2815 static void StartMoving_MM(int x, int y)
2817 int element = Tile[x][y];
2822 if (CAN_MOVE(element))
2826 if (MovDelay[x][y]) // wait some time before next movement
2834 // now make next step
2836 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2838 if (element == EL_PACMAN &&
2839 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2840 !ObjHit(newx, newy, HIT_POS_CENTER))
2842 Store[newx][newy] = Tile[newx][newy];
2843 Tile[newx][newy] = EL_EMPTY;
2845 DrawField_MM(newx, newy);
2847 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2848 ObjHit(newx, newy, HIT_POS_CENTER))
2850 // object was running against a wall
2857 InitMovingField_MM(x, y, MovDir[x][y]);
2861 ContinueMoving_MM(x, y);
2864 static void ContinueMoving_MM(int x, int y)
2866 int element = Tile[x][y];
2867 int direction = MovDir[x][y];
2868 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2869 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2870 int horiz_move = (dx!=0);
2871 int newx = x + dx, newy = y + dy;
2872 int step = (horiz_move ? dx : dy) * TILEX / 8;
2874 MovPos[x][y] += step;
2876 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2878 Tile[x][y] = EL_EMPTY;
2879 Tile[newx][newy] = element;
2881 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2882 MovDelay[newx][newy] = 0;
2884 if (!CAN_MOVE(element))
2885 MovDir[newx][newy] = 0;
2888 DrawField_MM(newx, newy);
2890 Stop[newx][newy] = TRUE;
2892 if (element == EL_PACMAN)
2894 if (Store[newx][newy] == EL_BOMB)
2895 Bang_MM(newx, newy);
2897 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2898 (LX + 2 * XS) / TILEX == newx &&
2899 (LY + 2 * YS) / TILEY == newy)
2906 else // still moving on
2911 laser.redraw = TRUE;
2914 boolean ClickElement(int x, int y, int button)
2916 static DelayCounter click_delay = { CLICK_DELAY };
2917 static boolean new_button = TRUE;
2918 boolean element_clicked = FALSE;
2923 // initialize static variables
2924 click_delay.count = 0;
2925 click_delay.value = CLICK_DELAY;
2931 // do not rotate objects hit by the laser after the game was solved
2932 if (game_mm.level_solved && Hit[x][y])
2935 if (button == MB_RELEASED)
2938 click_delay.value = CLICK_DELAY;
2940 // release eventually hold auto-rotating mirror
2941 RotateMirror(x, y, MB_RELEASED);
2946 if (!FrameReached(&click_delay) && !new_button)
2949 if (button == MB_MIDDLEBUTTON) // middle button has no function
2952 if (!IN_LEV_FIELD(x, y))
2955 if (Tile[x][y] == EL_EMPTY)
2958 element = Tile[x][y];
2960 if (IS_MIRROR(element) ||
2961 IS_BEAMER(element) ||
2962 IS_POLAR(element) ||
2963 IS_POLAR_CROSS(element) ||
2964 IS_DF_MIRROR(element) ||
2965 IS_DF_MIRROR_AUTO(element))
2967 RotateMirror(x, y, button);
2969 element_clicked = TRUE;
2971 else if (IS_MCDUFFIN(element))
2973 if (!laser.fuse_off)
2975 DrawLaser(0, DL_LASER_DISABLED);
2982 element = get_rotated_element(element, BUTTON_ROTATION(button));
2983 laser.start_angle = get_element_angle(element);
2987 Tile[x][y] = element;
2994 if (!laser.fuse_off)
2997 element_clicked = TRUE;
2999 else if (element == EL_FUSE_ON && laser.fuse_off)
3001 if (x != laser.fuse_x || y != laser.fuse_y)
3004 laser.fuse_off = FALSE;
3005 laser.fuse_x = laser.fuse_y = -1;
3007 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3010 element_clicked = TRUE;
3012 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3014 laser.fuse_off = TRUE;
3017 laser.overloaded = FALSE;
3019 DrawLaser(0, DL_LASER_DISABLED);
3020 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3022 element_clicked = TRUE;
3024 else if (element == EL_LIGHTBALL)
3027 RaiseScoreElement_MM(element);
3028 DrawLaser(0, DL_LASER_ENABLED);
3030 element_clicked = TRUE;
3032 else if (IS_ENVELOPE(element))
3034 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3036 element_clicked = TRUE;
3039 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3042 return element_clicked;
3045 void RotateMirror(int x, int y, int button)
3047 if (button == MB_RELEASED)
3049 // release eventually hold auto-rotating mirror
3056 if (IS_MIRROR(Tile[x][y]) ||
3057 IS_POLAR_CROSS(Tile[x][y]) ||
3058 IS_POLAR(Tile[x][y]) ||
3059 IS_BEAMER(Tile[x][y]) ||
3060 IS_DF_MIRROR(Tile[x][y]) ||
3061 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3062 IS_GRID_WOOD_AUTO(Tile[x][y]))
3064 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3066 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3068 if (button == MB_LEFTBUTTON)
3070 // left mouse button only for manual adjustment, no auto-rotating;
3071 // freeze mirror for until mouse button released
3075 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3077 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3081 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3083 int edge = Hit[x][y];
3089 DrawLaser(edge - 1, DL_LASER_DISABLED);
3093 else if (ObjHit(x, y, HIT_POS_CENTER))
3095 int edge = Hit[x][y];
3099 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3104 DrawLaser(edge - 1, DL_LASER_DISABLED);
3111 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3116 if ((IS_BEAMER(Tile[x][y]) ||
3117 IS_POLAR(Tile[x][y]) ||
3118 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3120 if (IS_BEAMER(Tile[x][y]))
3123 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3124 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3137 DrawLaser(0, DL_LASER_ENABLED);
3141 static void AutoRotateMirrors(void)
3145 if (!FrameReached(&rotate_delay))
3148 for (x = 0; x < lev_fieldx; x++)
3150 for (y = 0; y < lev_fieldy; y++)
3152 int element = Tile[x][y];
3154 // do not rotate objects hit by the laser after the game was solved
3155 if (game_mm.level_solved && Hit[x][y])
3158 if (IS_DF_MIRROR_AUTO(element) ||
3159 IS_GRID_WOOD_AUTO(element) ||
3160 IS_GRID_STEEL_AUTO(element) ||
3161 element == EL_REFRACTOR)
3162 RotateMirror(x, y, MB_RIGHTBUTTON);
3167 boolean ObjHit(int obx, int oby, int bits)
3174 if (bits & HIT_POS_CENTER)
3176 if (CheckLaserPixel(cSX + obx + 15,
3181 if (bits & HIT_POS_EDGE)
3183 for (i = 0; i < 4; i++)
3184 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3185 cSY + oby + 31 * (i / 2)))
3189 if (bits & HIT_POS_BETWEEN)
3191 for (i = 0; i < 4; i++)
3192 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3193 cSY + 4 + oby + 22 * (i / 2)))
3200 void DeletePacMan(int px, int py)
3206 if (game_mm.num_pacman <= 1)
3208 game_mm.num_pacman = 0;
3212 for (i = 0; i < game_mm.num_pacman; i++)
3213 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3216 game_mm.num_pacman--;
3218 for (j = i; j < game_mm.num_pacman; j++)
3220 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3221 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3222 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3226 void ColorCycling(void)
3228 static int CC, Cc = 0;
3230 static int color, old = 0xF00, new = 0x010, mult = 1;
3231 static unsigned short red, green, blue;
3233 if (color_status == STATIC_COLORS)
3238 if (CC < Cc || CC > Cc + 2)
3242 color = old + new * mult;
3248 if (ABS(mult) == 16)
3258 red = 0x0e00 * ((color & 0xF00) >> 8);
3259 green = 0x0e00 * ((color & 0x0F0) >> 4);
3260 blue = 0x0e00 * ((color & 0x00F));
3261 SetRGB(pen_magicolor[0], red, green, blue);
3263 red = 0x1111 * ((color & 0xF00) >> 8);
3264 green = 0x1111 * ((color & 0x0F0) >> 4);
3265 blue = 0x1111 * ((color & 0x00F));
3266 SetRGB(pen_magicolor[1], red, green, blue);
3270 static void GameActions_MM_Ext(void)
3277 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3280 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3282 element = Tile[x][y];
3284 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3285 StartMoving_MM(x, y);
3286 else if (IS_MOVING(x, y))
3287 ContinueMoving_MM(x, y);
3288 else if (IS_EXPLODING(element))
3289 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3290 else if (element == EL_EXIT_OPENING)
3292 else if (element == EL_GRAY_BALL_OPENING)
3293 OpenSurpriseBall(x, y);
3294 else if (IS_ENVELOPE_OPENING(element))
3296 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3298 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3300 else if (IS_MIRROR(element) ||
3301 IS_MIRROR_FIXED(element) ||
3302 element == EL_PRISM)
3303 DrawFieldTwinkle(x, y);
3304 else if (element == EL_GRAY_BALL_OPENING ||
3305 element == EL_BOMB_ACTIVE ||
3306 element == EL_MINE_ACTIVE)
3307 DrawFieldAnimated_MM(x, y);
3308 else if (!IS_BLOCKED(x, y))
3309 DrawFieldAnimatedIfNeeded_MM(x, y);
3312 AutoRotateMirrors();
3315 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3317 // redraw after Explode_MM() ...
3319 DrawLaser(0, DL_LASER_ENABLED);
3320 laser.redraw = FALSE;
3325 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3329 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3331 DrawLaser(0, DL_LASER_DISABLED);
3336 // skip all following game actions if game is over
3337 if (game_mm.game_over)
3340 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3344 GameOver_MM(GAME_OVER_NO_ENERGY);
3349 if (FrameReached(&energy_delay))
3351 if (game_mm.energy_left > 0)
3352 game_mm.energy_left--;
3354 // when out of energy, wait another frame to play "out of time" sound
3357 element = laser.dest_element;
3360 if (element != Tile[ELX][ELY])
3362 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3363 element, Tile[ELX][ELY]);
3367 if (!laser.overloaded && laser.overload_value == 0 &&
3368 element != EL_BOMB &&
3369 element != EL_BOMB_ACTIVE &&
3370 element != EL_MINE &&
3371 element != EL_MINE_ACTIVE &&
3372 element != EL_BALL_GRAY &&
3373 element != EL_BLOCK_STONE &&
3374 element != EL_BLOCK_WOOD &&
3375 element != EL_FUSE_ON &&
3376 element != EL_FUEL_FULL &&
3377 !IS_WALL_ICE(element) &&
3378 !IS_WALL_AMOEBA(element))
3381 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3383 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3384 (!laser.overloaded && laser.overload_value > 0)) &&
3385 FrameReached(&overload_delay))
3387 if (laser.overloaded)
3388 laser.overload_value++;
3390 laser.overload_value--;
3392 if (game_mm.cheat_no_overload)
3394 laser.overloaded = FALSE;
3395 laser.overload_value = 0;
3398 game_mm.laser_overload_value = laser.overload_value;
3400 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3402 SetLaserColor(0xFF);
3404 DrawLaser(0, DL_LASER_ENABLED);
3407 if (!laser.overloaded)
3408 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3409 else if (setup.sound_loops)
3410 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3412 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3414 if (laser.overloaded)
3417 BlitBitmap(pix[PIX_DOOR], drawto,
3418 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3419 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3420 - laser.overload_value,
3421 OVERLOAD_XSIZE, laser.overload_value,
3422 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3423 - laser.overload_value);
3425 redraw_mask |= REDRAW_DOOR_1;
3430 BlitBitmap(pix[PIX_DOOR], drawto,
3431 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3432 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3433 DX_OVERLOAD, DY_OVERLOAD);
3435 redraw_mask |= REDRAW_DOOR_1;
3438 if (laser.overload_value == MAX_LASER_OVERLOAD)
3440 UpdateAndDisplayGameControlValues();
3444 GameOver_MM(GAME_OVER_OVERLOADED);
3455 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3457 if (game_mm.cheat_no_explosion)
3462 laser.dest_element = EL_EXPLODING_OPAQUE;
3467 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3469 laser.fuse_off = TRUE;
3473 DrawLaser(0, DL_LASER_DISABLED);
3474 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3477 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3479 if (!Store2[ELX][ELY]) // check if content element not yet determined
3481 int last_anim_random_frame = gfx.anim_random_frame;
3484 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3485 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3487 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3488 native_mm_level.ball_choice_mode, 0,
3489 game_mm.ball_choice_pos);
3491 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3492 gfx.anim_random_frame = last_anim_random_frame;
3494 game_mm.ball_choice_pos++;
3496 int new_element = native_mm_level.ball_content[element_pos];
3498 // randomly rotate newly created game element, if needed
3499 if (native_mm_level.rotate_ball_content)
3500 new_element = get_rotated_element(new_element, RND(16));
3502 Store[ELX][ELY] = new_element;
3503 Store2[ELX][ELY] = TRUE;
3506 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3508 // !!! CHECK AGAIN: Laser on Polarizer !!!
3511 laser.dest_element_last = Tile[ELX][ELY];
3512 laser.dest_element_last_x = ELX;
3513 laser.dest_element_last_y = ELY;
3523 element = EL_MIRROR_START + RND(16);
3529 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3536 element = (rnd == 0 ? EL_FUSE_ON :
3537 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3538 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3539 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3540 EL_MIRROR_FIXED_START + rnd - 25);
3545 graphic = el2gfx(element);
3547 for (i = 0; i < 50; i++)
3553 BlitBitmap(pix[PIX_BACK], drawto,
3554 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3555 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3556 SX + ELX * TILEX + x,
3557 SY + ELY * TILEY + y);
3559 MarkTileDirty(ELX, ELY);
3562 DrawLaser(0, DL_LASER_ENABLED);
3564 Delay_WithScreenUpdates(50);
3567 Tile[ELX][ELY] = element;
3568 DrawField_MM(ELX, ELY);
3571 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3574 // above stuff: GRAY BALL -> PRISM !!!
3576 LX = ELX * TILEX + 14;
3577 LY = ELY * TILEY + 14;
3578 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3585 laser.num_edges -= 2;
3586 laser.num_damages--;
3590 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3591 if (laser.damage[i].is_mirror)
3595 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3597 DrawLaser(0, DL_LASER_DISABLED);
3599 DrawLaser(0, DL_LASER_DISABLED);
3608 if (IS_WALL_ICE(element) && CT > 50)
3610 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3613 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3614 Store[ELX][ELY] = EL_WALL_ICE;
3615 Store2[ELX][ELY] = laser.wall_mask;
3617 laser.dest_element = Tile[ELX][ELY];
3622 for (i = 0; i < 5; i++)
3628 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3632 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3634 Delay_WithScreenUpdates(100);
3637 if (Tile[ELX][ELY] == EL_WALL_ICE)
3638 Tile[ELX][ELY] = EL_EMPTY;
3642 LX = laser.edge[laser.num_edges].x - cSX2;
3643 LY = laser.edge[laser.num_edges].y - cSY2;
3646 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3647 if (laser.damage[i].is_mirror)
3651 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3653 DrawLaser(0, DL_LASER_DISABLED);
3660 if (IS_WALL_AMOEBA(element) && CT > 60)
3662 int k1, k2, k3, dx, dy, de, dm;
3663 int element2 = Tile[ELX][ELY];
3665 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3668 for (i = laser.num_damages - 1; i >= 0; i--)
3669 if (laser.damage[i].is_mirror)
3672 r = laser.num_edges;
3673 d = laser.num_damages;
3680 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3683 DrawLaser(0, DL_LASER_ENABLED);
3686 x = laser.damage[k1].x;
3687 y = laser.damage[k1].y;
3692 for (i = 0; i < 4; i++)
3694 if (laser.wall_mask & (1 << i))
3696 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3697 cSY + ELY * TILEY + 31 * (i / 2)))
3700 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3701 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3708 for (i = 0; i < 4; i++)
3710 if (laser.wall_mask & (1 << i))
3712 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3713 cSY + ELY * TILEY + 31 * (i / 2)))
3720 if (laser.num_beamers > 0 ||
3721 k1 < 1 || k2 < 4 || k3 < 4 ||
3722 CheckLaserPixel(cSX + ELX * TILEX + 14,
3723 cSY + ELY * TILEY + 14))
3725 laser.num_edges = r;
3726 laser.num_damages = d;
3728 DrawLaser(0, DL_LASER_DISABLED);
3731 Tile[ELX][ELY] = element | laser.wall_mask;
3735 de = Tile[ELX][ELY];
3736 dm = laser.wall_mask;
3740 int x = ELX, y = ELY;
3741 int wall_mask = laser.wall_mask;
3744 DrawLaser(0, DL_LASER_ENABLED);
3746 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3748 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3749 Store[x][y] = EL_WALL_AMOEBA;
3750 Store2[x][y] = wall_mask;
3756 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3758 DrawLaser(0, DL_LASER_ENABLED);
3760 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3762 for (i = 4; i >= 0; i--)
3764 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3767 Delay_WithScreenUpdates(20);
3770 DrawLaser(0, DL_LASER_ENABLED);
3775 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3776 laser.stops_inside_element && CT > native_mm_level.time_block)
3781 if (ABS(XS) > ABS(YS))
3788 for (i = 0; i < 4; i++)
3795 x = ELX + Step[k * 4].x;
3796 y = ELY + Step[k * 4].y;
3798 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3801 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3809 laser.overloaded = (element == EL_BLOCK_STONE);
3814 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3817 Tile[x][y] = element;
3819 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3822 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3824 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3825 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3833 if (element == EL_FUEL_FULL && CT > 10)
3835 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3836 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3838 for (i = start; i <= num_init_game_frames; i++)
3840 if (i == num_init_game_frames)
3841 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3842 else if (setup.sound_loops)
3843 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3845 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3847 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3849 UpdateAndDisplayGameControlValues();
3854 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3856 DrawField_MM(ELX, ELY);
3858 DrawLaser(0, DL_LASER_ENABLED);
3866 void GameActions_MM(struct MouseActionInfo action)
3868 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3869 boolean button_released = (action.button == MB_RELEASED);
3871 GameActions_MM_Ext();
3873 CheckSingleStepMode_MM(element_clicked, button_released);
3876 void MovePacMen(void)
3878 int mx, my, ox, oy, nx, ny;
3882 if (++pacman_nr >= game_mm.num_pacman)
3885 game_mm.pacman[pacman_nr].dir--;
3887 for (l = 1; l < 5; l++)
3889 game_mm.pacman[pacman_nr].dir++;
3891 if (game_mm.pacman[pacman_nr].dir > 4)
3892 game_mm.pacman[pacman_nr].dir = 1;
3894 if (game_mm.pacman[pacman_nr].dir % 2)
3897 my = game_mm.pacman[pacman_nr].dir - 2;
3902 mx = 3 - game_mm.pacman[pacman_nr].dir;
3905 ox = game_mm.pacman[pacman_nr].x;
3906 oy = game_mm.pacman[pacman_nr].y;
3909 element = Tile[nx][ny];
3911 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3914 if (!IS_EATABLE4PACMAN(element))
3917 if (ObjHit(nx, ny, HIT_POS_CENTER))
3920 Tile[ox][oy] = EL_EMPTY;
3922 EL_PACMAN_RIGHT - 1 +
3923 (game_mm.pacman[pacman_nr].dir - 1 +
3924 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3926 game_mm.pacman[pacman_nr].x = nx;
3927 game_mm.pacman[pacman_nr].y = ny;
3929 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3931 if (element != EL_EMPTY)
3933 int graphic = el2gfx(Tile[nx][ny]);
3938 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3941 ox = cSX + ox * TILEX;
3942 oy = cSY + oy * TILEY;
3944 for (i = 1; i < 33; i += 2)
3945 BlitBitmap(bitmap, window,
3946 src_x, src_y, TILEX, TILEY,
3947 ox + i * mx, oy + i * my);
3948 Ct = Ct + FrameCounter - CT;
3951 DrawField_MM(nx, ny);
3954 if (!laser.fuse_off)
3956 DrawLaser(0, DL_LASER_ENABLED);
3958 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3960 AddDamagedField(nx, ny);
3962 laser.damage[laser.num_damages - 1].edge = 0;
3966 if (element == EL_BOMB)
3967 DeletePacMan(nx, ny);
3969 if (IS_WALL_AMOEBA(element) &&
3970 (LX + 2 * XS) / TILEX == nx &&
3971 (LY + 2 * YS) / TILEY == ny)
3981 static void InitMovingField_MM(int x, int y, int direction)
3983 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3984 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3986 MovDir[x][y] = direction;
3987 MovDir[newx][newy] = direction;
3989 if (Tile[newx][newy] == EL_EMPTY)
3990 Tile[newx][newy] = EL_BLOCKED;
3993 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3995 int direction = MovDir[x][y];
3996 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3997 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4003 static void Blocked2Moving_MM(int x, int y,
4004 int *comes_from_x, int *comes_from_y)
4006 int oldx = x, oldy = y;
4007 int direction = MovDir[x][y];
4009 if (direction == MV_LEFT)
4011 else if (direction == MV_RIGHT)
4013 else if (direction == MV_UP)
4015 else if (direction == MV_DOWN)
4018 *comes_from_x = oldx;
4019 *comes_from_y = oldy;
4022 static int MovingOrBlocked2Element_MM(int x, int y)
4024 int element = Tile[x][y];
4026 if (element == EL_BLOCKED)
4030 Blocked2Moving_MM(x, y, &oldx, &oldy);
4032 return Tile[oldx][oldy];
4039 static void RemoveField(int x, int y)
4041 Tile[x][y] = EL_EMPTY;
4048 static void RemoveMovingField_MM(int x, int y)
4050 int oldx = x, oldy = y, newx = x, newy = y;
4052 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4055 if (IS_MOVING(x, y))
4057 Moving2Blocked_MM(x, y, &newx, &newy);
4058 if (Tile[newx][newy] != EL_BLOCKED)
4061 else if (Tile[x][y] == EL_BLOCKED)
4063 Blocked2Moving_MM(x, y, &oldx, &oldy);
4064 if (!IS_MOVING(oldx, oldy))
4068 Tile[oldx][oldy] = EL_EMPTY;
4069 Tile[newx][newy] = EL_EMPTY;
4070 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4071 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4073 DrawLevelField_MM(oldx, oldy);
4074 DrawLevelField_MM(newx, newy);
4077 void PlaySoundLevel(int x, int y, int sound_nr)
4079 int sx = SCREENX(x), sy = SCREENY(y);
4081 int silence_distance = 8;
4083 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4084 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4087 if (!IN_LEV_FIELD(x, y) ||
4088 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4089 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4092 volume = SOUND_MAX_VOLUME;
4095 stereo = (sx - SCR_FIELDX/2) * 12;
4097 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4098 if (stereo > SOUND_MAX_RIGHT)
4099 stereo = SOUND_MAX_RIGHT;
4100 if (stereo < SOUND_MAX_LEFT)
4101 stereo = SOUND_MAX_LEFT;
4104 if (!IN_SCR_FIELD(sx, sy))
4106 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4107 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4109 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4112 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4115 static void RaiseScore_MM(int value)
4117 game_mm.score += value;
4120 void RaiseScoreElement_MM(int element)
4125 case EL_PACMAN_RIGHT:
4127 case EL_PACMAN_LEFT:
4128 case EL_PACMAN_DOWN:
4129 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4133 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4138 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4142 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4151 // ----------------------------------------------------------------------------
4152 // Mirror Magic game engine snapshot handling functions
4153 // ----------------------------------------------------------------------------
4155 void SaveEngineSnapshotValues_MM(void)
4159 engine_snapshot_mm.game_mm = game_mm;
4160 engine_snapshot_mm.laser = laser;
4162 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4164 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4166 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4167 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4168 engine_snapshot_mm.Box[x][y] = Box[x][y];
4169 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4173 engine_snapshot_mm.LX = LX;
4174 engine_snapshot_mm.LY = LY;
4175 engine_snapshot_mm.XS = XS;
4176 engine_snapshot_mm.YS = YS;
4177 engine_snapshot_mm.ELX = ELX;
4178 engine_snapshot_mm.ELY = ELY;
4179 engine_snapshot_mm.CT = CT;
4180 engine_snapshot_mm.Ct = Ct;
4182 engine_snapshot_mm.last_LX = last_LX;
4183 engine_snapshot_mm.last_LY = last_LY;
4184 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4185 engine_snapshot_mm.hold_x = hold_x;
4186 engine_snapshot_mm.hold_y = hold_y;
4187 engine_snapshot_mm.pacman_nr = pacman_nr;
4189 engine_snapshot_mm.rotate_delay = rotate_delay;
4190 engine_snapshot_mm.pacman_delay = pacman_delay;
4191 engine_snapshot_mm.energy_delay = energy_delay;
4192 engine_snapshot_mm.overload_delay = overload_delay;
4195 void LoadEngineSnapshotValues_MM(void)
4199 // stored engine snapshot buffers already restored at this point
4201 game_mm = engine_snapshot_mm.game_mm;
4202 laser = engine_snapshot_mm.laser;
4204 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4206 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4208 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4209 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4210 Box[x][y] = engine_snapshot_mm.Box[x][y];
4211 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4215 LX = engine_snapshot_mm.LX;
4216 LY = engine_snapshot_mm.LY;
4217 XS = engine_snapshot_mm.XS;
4218 YS = engine_snapshot_mm.YS;
4219 ELX = engine_snapshot_mm.ELX;
4220 ELY = engine_snapshot_mm.ELY;
4221 CT = engine_snapshot_mm.CT;
4222 Ct = engine_snapshot_mm.Ct;
4224 last_LX = engine_snapshot_mm.last_LX;
4225 last_LY = engine_snapshot_mm.last_LY;
4226 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4227 hold_x = engine_snapshot_mm.hold_x;
4228 hold_y = engine_snapshot_mm.hold_y;
4229 pacman_nr = engine_snapshot_mm.pacman_nr;
4231 rotate_delay = engine_snapshot_mm.rotate_delay;
4232 pacman_delay = engine_snapshot_mm.pacman_delay;
4233 energy_delay = engine_snapshot_mm.energy_delay;
4234 overload_delay = engine_snapshot_mm.overload_delay;
4236 RedrawPlayfield_MM();
4239 static int getAngleFromTouchDelta(int dx, int dy, int base)
4241 double pi = 3.141592653;
4242 double rad = atan2((double)-dy, (double)dx);
4243 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4244 double deg = rad2 * 180.0 / pi;
4246 return (int)(deg * base / 360.0 + 0.5) % base;
4249 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4251 // calculate start (source) position to be at the middle of the tile
4252 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4253 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4254 int dx = dst_mx - src_mx;
4255 int dy = dst_my - src_my;
4264 if (!IN_LEV_FIELD(x, y))
4267 element = Tile[x][y];
4269 if (!IS_MCDUFFIN(element) &&
4270 !IS_MIRROR(element) &&
4271 !IS_BEAMER(element) &&
4272 !IS_POLAR(element) &&
4273 !IS_POLAR_CROSS(element) &&
4274 !IS_DF_MIRROR(element))
4277 angle_old = get_element_angle(element);
4279 if (IS_MCDUFFIN(element))
4281 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4282 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4283 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4284 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4287 else if (IS_MIRROR(element) ||
4288 IS_DF_MIRROR(element))
4290 for (i = 0; i < laser.num_damages; i++)
4292 if (laser.damage[i].x == x &&
4293 laser.damage[i].y == y &&
4294 ObjHit(x, y, HIT_POS_CENTER))
4296 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4297 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4304 if (angle_new == -1)
4306 if (IS_MIRROR(element) ||
4307 IS_DF_MIRROR(element) ||
4311 if (IS_POLAR_CROSS(element))
4314 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4317 button = (angle_new == angle_old ? 0 :
4318 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4319 MB_LEFTBUTTON : MB_RIGHTBUTTON);