1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
100 static void Moving2Blocked_MM(int, int, int *, int *);
102 // bitmap for laser beam detection
103 static Bitmap *laser_bitmap = NULL;
105 // variables for laser control
106 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
107 static int hold_x = -1, hold_y = -1;
109 // variables for pacman control
110 static int pacman_nr = -1;
112 // various game engine delay counters
113 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
114 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
115 static DelayCounter energy_delay = { ENERGY_DELAY };
116 static DelayCounter overload_delay = { 0 };
118 // element mask positions for scanning pixels of MM elements
119 #define MM_MASK_MCDUFFIN_RIGHT 0
120 #define MM_MASK_MCDUFFIN_UP 1
121 #define MM_MASK_MCDUFFIN_LEFT 2
122 #define MM_MASK_MCDUFFIN_DOWN 3
123 #define MM_MASK_GRID_1 4
124 #define MM_MASK_GRID_2 5
125 #define MM_MASK_GRID_3 6
126 #define MM_MASK_GRID_4 7
127 #define MM_MASK_RECTANGLE 8
128 #define MM_MASK_CIRCLE 9
130 #define NUM_MM_MASKS 10
132 // element masks for scanning pixels of MM elements
133 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
317 static int get_element_angle(int element)
319 int element_phase = get_element_phase(element);
321 if (IS_MIRROR_FIXED(element) ||
322 IS_MCDUFFIN(element) ||
324 IS_RECEIVER(element))
325 return 4 * element_phase;
327 return element_phase;
330 static int get_opposite_angle(int angle)
332 int opposite_angle = angle + ANG_RAY_180;
334 // make sure "opposite_angle" is in valid interval [0, 15]
335 return (opposite_angle + 16) % 16;
338 static int get_mirrored_angle(int laser_angle, int mirror_angle)
340 int reflected_angle = 16 - laser_angle + mirror_angle;
342 // make sure "reflected_angle" is in valid interval [0, 15]
343 return (reflected_angle + 16) % 16;
346 static void DrawLaserLines(struct XY *points, int num_points, int mode)
348 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
349 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
351 DrawLines(drawto, points, num_points, pixel_drawto);
355 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
360 static boolean CheckLaserPixel(int x, int y)
366 pixel = ReadPixel(laser_bitmap, x, y);
370 return (pixel == WHITE_PIXEL);
373 static void CheckExitMM(void)
375 int exit_element = EL_EMPTY;
379 static int xy[4][2] =
387 for (y = 0; y < lev_fieldy; y++)
389 for (x = 0; x < lev_fieldx; x++)
391 if (Tile[x][y] == EL_EXIT_CLOSED)
393 // initiate opening animation of exit door
394 Tile[x][y] = EL_EXIT_OPENING;
396 exit_element = EL_EXIT_OPEN;
400 else if (IS_RECEIVER(Tile[x][y]))
402 // remove field that blocks receiver
403 int phase = Tile[x][y] - EL_RECEIVER_START;
404 int blocking_x, blocking_y;
406 blocking_x = x + xy[phase][0];
407 blocking_y = y + xy[phase][1];
409 if (IN_LEV_FIELD(blocking_x, blocking_y))
411 Tile[blocking_x][blocking_y] = EL_EMPTY;
413 DrawField_MM(blocking_x, blocking_y);
416 exit_element = EL_RECEIVER;
423 if (exit_element != EL_EMPTY)
424 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
427 static void SetLaserColor(int brightness)
429 int color_min = 0x00;
430 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
431 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
432 int color_down = color_max - color_up;
435 GetPixelFromRGB(window,
436 (game_mm.laser_red ? color_max : color_up),
437 (game_mm.laser_green ? color_down : color_min),
438 (game_mm.laser_blue ? color_down : color_min));
441 static void InitMovDir_MM(int x, int y)
443 int element = Tile[x][y];
444 static int direction[3][4] =
446 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
447 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
448 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
453 case EL_PACMAN_RIGHT:
457 Tile[x][y] = EL_PACMAN;
458 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
466 static void InitField(int x, int y, boolean init_game)
468 int element = Tile[x][y];
473 Tile[x][y] = EL_EMPTY;
478 if (init_game && native_mm_level.auto_count_kettles)
479 game_mm.kettles_still_needed++;
482 case EL_LIGHTBULB_OFF:
483 game_mm.lights_still_needed++;
487 if (IS_MIRROR(element) ||
488 IS_BEAMER_OLD(element) ||
489 IS_BEAMER(element) ||
491 IS_POLAR_CROSS(element) ||
492 IS_DF_MIRROR(element) ||
493 IS_DF_MIRROR_AUTO(element) ||
494 IS_GRID_STEEL_AUTO(element) ||
495 IS_GRID_WOOD_AUTO(element) ||
496 IS_FIBRE_OPTIC(element))
498 if (IS_BEAMER_OLD(element))
500 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
501 element = Tile[x][y];
504 if (!IS_FIBRE_OPTIC(element))
506 static int steps_grid_auto = 0;
508 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
509 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
511 if (IS_GRID_STEEL_AUTO(element) ||
512 IS_GRID_WOOD_AUTO(element))
513 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
515 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
517 game_mm.cycle[game_mm.num_cycle].x = x;
518 game_mm.cycle[game_mm.num_cycle].y = y;
522 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
524 int beamer_nr = BEAMER_NR(element);
525 int nr = laser.beamer[beamer_nr][0].num;
527 laser.beamer[beamer_nr][nr].x = x;
528 laser.beamer[beamer_nr][nr].y = y;
529 laser.beamer[beamer_nr][nr].num = 1;
532 else if (IS_PACMAN(element))
536 else if (IS_MCDUFFIN(element) || IS_LASER(element))
540 laser.start_edge.x = x;
541 laser.start_edge.y = y;
542 laser.start_angle = get_element_angle(element);
545 if (IS_MCDUFFIN(element))
547 game_mm.laser_red = native_mm_level.mm_laser_red;
548 game_mm.laser_green = native_mm_level.mm_laser_green;
549 game_mm.laser_blue = native_mm_level.mm_laser_blue;
553 game_mm.laser_red = native_mm_level.df_laser_red;
554 game_mm.laser_green = native_mm_level.df_laser_green;
555 game_mm.laser_blue = native_mm_level.df_laser_blue;
563 static void InitCycleElements_RotateSingleStep(void)
567 if (game_mm.num_cycle == 0) // no elements to cycle
570 for (i = 0; i < game_mm.num_cycle; i++)
572 int x = game_mm.cycle[i].x;
573 int y = game_mm.cycle[i].y;
574 int step = SIGN(game_mm.cycle[i].steps);
575 int last_element = Tile[x][y];
576 int next_element = get_rotated_element(last_element, step);
578 if (!game_mm.cycle[i].steps)
581 Tile[x][y] = next_element;
583 game_mm.cycle[i].steps -= step;
587 static void InitLaser(void)
589 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
590 int step = (IS_LASER(start_element) ? 4 : 0);
592 LX = laser.start_edge.x * TILEX;
593 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
596 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
598 LY = laser.start_edge.y * TILEY;
599 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
600 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
604 XS = 2 * Step[laser.start_angle].x;
605 YS = 2 * Step[laser.start_angle].y;
607 laser.current_angle = laser.start_angle;
609 laser.num_damages = 0;
611 laser.num_beamers = 0;
612 laser.beamer_edge[0] = 0;
614 laser.dest_element = EL_EMPTY;
617 AddLaserEdge(LX, LY); // set laser starting edge
622 void InitGameEngine_MM(void)
628 // initialize laser bitmap to current playfield (screen) size
629 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
630 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
634 // set global game control values
635 game_mm.num_cycle = 0;
636 game_mm.num_pacman = 0;
639 game_mm.energy_left = 0; // later set to "native_mm_level.time"
640 game_mm.kettles_still_needed =
641 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
642 game_mm.lights_still_needed = 0;
643 game_mm.num_keys = 0;
644 game_mm.ball_choice_pos = 0;
646 game_mm.laser_red = FALSE;
647 game_mm.laser_green = FALSE;
648 game_mm.laser_blue = TRUE;
650 game_mm.level_solved = FALSE;
651 game_mm.game_over = FALSE;
652 game_mm.game_over_cause = 0;
654 game_mm.laser_overload_value = 0;
655 game_mm.laser_enabled = FALSE;
657 // set global laser control values (must be set before "InitLaser()")
658 laser.start_edge.x = 0;
659 laser.start_edge.y = 0;
660 laser.start_angle = 0;
662 for (i = 0; i < MAX_NUM_BEAMERS; i++)
663 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
665 laser.overloaded = FALSE;
666 laser.overload_value = 0;
667 laser.fuse_off = FALSE;
668 laser.fuse_x = laser.fuse_y = -1;
670 laser.dest_element = EL_EMPTY;
671 laser.dest_element_last = EL_EMPTY;
672 laser.dest_element_last_x = -1;
673 laser.dest_element_last_y = -1;
687 rotate_delay.count = 0;
688 pacman_delay.count = 0;
689 energy_delay.count = 0;
690 overload_delay.count = 0;
692 ClickElement(-1, -1, -1);
694 for (x = 0; x < lev_fieldx; x++)
696 for (y = 0; y < lev_fieldy; y++)
698 Tile[x][y] = Ur[x][y];
699 Hit[x][y] = Box[x][y] = 0;
701 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
702 Store[x][y] = Store2[x][y] = 0;
705 InitField(x, y, TRUE);
712 void InitGameActions_MM(void)
714 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
715 int cycle_steps_done = 0;
720 for (i = 0; i <= num_init_game_frames; i++)
722 if (i == num_init_game_frames)
723 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
724 else if (setup.sound_loops)
725 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
727 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
729 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
731 UpdateAndDisplayGameControlValues();
733 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
735 InitCycleElements_RotateSingleStep();
740 AdvanceFrameCounter();
750 if (setup.quick_doors)
757 if (game_mm.kettles_still_needed == 0)
760 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
761 SetTileCursorActive(TRUE);
763 // restart all delay counters after initially cycling game elements
764 ResetFrameCounter(&rotate_delay);
765 ResetFrameCounter(&pacman_delay);
766 ResetFrameCounter(&energy_delay);
767 ResetFrameCounter(&overload_delay);
770 static void FadeOutLaser(void)
774 for (i = 15; i >= 0; i--)
776 SetLaserColor(0x11 * i);
778 DrawLaser(0, DL_LASER_ENABLED);
781 Delay_WithScreenUpdates(50);
784 DrawLaser(0, DL_LASER_DISABLED);
786 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
789 static void GameOver_MM(int game_over_cause)
791 // do not handle game over if request dialog is already active
792 if (game.request_active)
795 game_mm.game_over = TRUE;
796 game_mm.game_over_cause = game_over_cause;
798 if (setup.ask_on_game_over)
799 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
800 "Bomb killed Mc Duffin! Play it again?" :
801 game_over_cause == GAME_OVER_NO_ENERGY ?
802 "Out of magic energy! Play it again?" :
803 game_over_cause == GAME_OVER_OVERLOADED ?
804 "Magic spell hit Mc Duffin! Play it again?" :
807 SetTileCursorActive(FALSE);
810 void AddLaserEdge(int lx, int ly)
815 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
817 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
822 laser.edge[laser.num_edges].x = cSX2 + lx;
823 laser.edge[laser.num_edges].y = cSY2 + ly;
829 void AddDamagedField(int ex, int ey)
831 // prevent adding the same field position again
832 if (laser.num_damages > 0 &&
833 laser.damage[laser.num_damages - 1].x == ex &&
834 laser.damage[laser.num_damages - 1].y == ey &&
835 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
838 laser.damage[laser.num_damages].is_mirror = FALSE;
839 laser.damage[laser.num_damages].angle = laser.current_angle;
840 laser.damage[laser.num_damages].edge = laser.num_edges;
841 laser.damage[laser.num_damages].x = ex;
842 laser.damage[laser.num_damages].y = ey;
846 static boolean StepBehind(void)
852 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
853 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
855 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
861 static int getMaskFromElement(int element)
863 if (IS_GRID(element))
864 return MM_MASK_GRID_1 + get_element_phase(element);
865 else if (IS_MCDUFFIN(element))
866 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
867 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
868 return MM_MASK_RECTANGLE;
870 return MM_MASK_CIRCLE;
873 static int ScanPixel(void)
878 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
879 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
882 // follow laser beam until it hits something (at least the screen border)
883 while (hit_mask == HIT_MASK_NO_HIT)
889 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
890 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
892 Debug("game:mm:ScanPixel", "touched screen border!");
898 for (i = 0; i < 4; i++)
900 int px = LX + (i % 2) * 2;
901 int py = LY + (i / 2) * 2;
904 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
905 int ly = (py + TILEY) / TILEY - 1; // negative values!
908 if (IN_LEV_FIELD(lx, ly))
910 int element = Tile[lx][ly];
912 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
916 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
918 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
920 pixel = ((element & (1 << pos)) ? 1 : 0);
924 int pos = getMaskFromElement(element);
926 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
931 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
932 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
935 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
936 hit_mask |= (1 << i);
939 if (hit_mask == HIT_MASK_NO_HIT)
941 // hit nothing -- go on with another step
950 static void DeactivateLaserTargetElement(void)
952 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
953 laser.dest_element_last == EL_MINE_ACTIVE ||
954 laser.dest_element_last == EL_GRAY_BALL_OPENING)
956 int x = laser.dest_element_last_x;
957 int y = laser.dest_element_last_y;
958 int element = laser.dest_element_last;
960 if (Tile[x][y] == element)
961 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
962 element == EL_MINE_ACTIVE ? EL_MINE : EL_BALL_GRAY);
964 if (Tile[x][y] == EL_BALL_GRAY)
967 laser.dest_element_last = EL_EMPTY;
968 laser.dest_element_last_x = -1;
969 laser.dest_element_last_y = -1;
975 int element = EL_EMPTY;
976 int last_element = EL_EMPTY;
977 int end = 0, rf = laser.num_edges;
979 // do not scan laser again after the game was lost for whatever reason
980 if (game_mm.game_over)
983 // do not scan laser if fuse is off
987 DeactivateLaserTargetElement();
989 laser.overloaded = FALSE;
990 laser.stops_inside_element = FALSE;
992 DrawLaser(0, DL_LASER_ENABLED);
995 Debug("game:mm:ScanLaser",
996 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1004 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1007 laser.overloaded = TRUE;
1012 hit_mask = ScanPixel();
1015 Debug("game:mm:ScanLaser",
1016 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1020 // hit something -- check out what it was
1021 ELX = (LX + XS) / TILEX;
1022 ELY = (LY + YS) / TILEY;
1025 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1026 hit_mask, LX, LY, ELX, ELY);
1029 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1032 laser.dest_element = element;
1037 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1039 /* we have hit the top-right and bottom-left element --
1040 choose the bottom-left one */
1041 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1042 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1043 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1044 ELX = (LX - 2) / TILEX;
1045 ELY = (LY + 2) / TILEY;
1048 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1050 /* we have hit the top-left and bottom-right element --
1051 choose the top-left one */
1052 // !!! SEE ABOVE !!!
1053 ELX = (LX - 2) / TILEX;
1054 ELY = (LY - 2) / TILEY;
1058 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1059 hit_mask, LX, LY, ELX, ELY);
1062 last_element = element;
1064 element = Tile[ELX][ELY];
1065 laser.dest_element = element;
1068 Debug("game:mm:ScanLaser",
1069 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1072 LX % TILEX, LY % TILEY,
1077 if (!IN_LEV_FIELD(ELX, ELY))
1078 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1082 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1083 if (element == EL_EMPTY &&
1084 IS_GRID_STEEL(last_element) &&
1085 laser.current_angle % 4) // angle is not 90°
1086 element = last_element;
1088 if (element == EL_EMPTY)
1090 if (!HitOnlyAnEdge(hit_mask))
1093 else if (element == EL_FUSE_ON)
1095 if (HitPolarizer(element, hit_mask))
1098 else if (IS_GRID(element) || IS_DF_GRID(element))
1100 if (HitPolarizer(element, hit_mask))
1103 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1104 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1106 if (HitBlock(element, hit_mask))
1113 else if (IS_MCDUFFIN(element))
1115 if (HitLaserSource(element, hit_mask))
1118 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1119 IS_RECEIVER(element))
1121 if (HitLaserDestination(element, hit_mask))
1124 else if (IS_WALL(element))
1126 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1128 if (HitReflectingWalls(element, hit_mask))
1133 if (HitAbsorbingWalls(element, hit_mask))
1139 if (HitElement(element, hit_mask))
1144 DrawLaser(rf - 1, DL_LASER_ENABLED);
1145 rf = laser.num_edges;
1147 if (!IS_DF_WALL_STEEL(element))
1149 // only used for scanning DF steel walls; reset for all other elements
1157 if (laser.dest_element != Tile[ELX][ELY])
1159 Debug("game:mm:ScanLaser",
1160 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1161 laser.dest_element, Tile[ELX][ELY]);
1165 if (!end && !laser.stops_inside_element && !StepBehind())
1168 Debug("game:mm:ScanLaser", "Go one step back");
1174 AddLaserEdge(LX, LY);
1178 DrawLaser(rf - 1, DL_LASER_ENABLED);
1180 Ct = CT = FrameCounter;
1183 if (!IN_LEV_FIELD(ELX, ELY))
1184 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1188 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1194 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1195 start_edge, num_edges, mode);
1200 Warn("DrawLaserExt: start_edge < 0");
1207 Warn("DrawLaserExt: num_edges < 0");
1213 if (mode == DL_LASER_DISABLED)
1215 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1219 // now draw the laser to the backbuffer and (if enabled) to the screen
1220 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1222 redraw_mask |= REDRAW_FIELD;
1224 if (mode == DL_LASER_ENABLED)
1227 // after the laser was deleted, the "damaged" graphics must be restored
1228 if (laser.num_damages)
1230 int damage_start = 0;
1233 // determine the starting edge, from which graphics need to be restored
1236 for (i = 0; i < laser.num_damages; i++)
1238 if (laser.damage[i].edge == start_edge + 1)
1247 // restore graphics from this starting edge to the end of damage list
1248 for (i = damage_start; i < laser.num_damages; i++)
1250 int lx = laser.damage[i].x;
1251 int ly = laser.damage[i].y;
1252 int element = Tile[lx][ly];
1254 if (Hit[lx][ly] == laser.damage[i].edge)
1255 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1258 if (Box[lx][ly] == laser.damage[i].edge)
1261 if (IS_DRAWABLE(element))
1262 DrawField_MM(lx, ly);
1265 elx = laser.damage[damage_start].x;
1266 ely = laser.damage[damage_start].y;
1267 element = Tile[elx][ely];
1270 if (IS_BEAMER(element))
1274 for (i = 0; i < laser.num_beamers; i++)
1275 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1277 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1278 mode, elx, ely, Hit[elx][ely], start_edge);
1279 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1280 get_element_angle(element), laser.damage[damage_start].angle);
1284 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1285 laser.num_beamers > 0 &&
1286 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1288 // element is outgoing beamer
1289 laser.num_damages = damage_start + 1;
1291 if (IS_BEAMER(element))
1292 laser.current_angle = get_element_angle(element);
1296 // element is incoming beamer or other element
1297 laser.num_damages = damage_start;
1298 laser.current_angle = laser.damage[laser.num_damages].angle;
1303 // no damages but McDuffin himself (who needs to be redrawn anyway)
1305 elx = laser.start_edge.x;
1306 ely = laser.start_edge.y;
1307 element = Tile[elx][ely];
1310 laser.num_edges = start_edge + 1;
1311 if (start_edge == 0)
1312 laser.current_angle = laser.start_angle;
1314 LX = laser.edge[start_edge].x - cSX2;
1315 LY = laser.edge[start_edge].y - cSY2;
1316 XS = 2 * Step[laser.current_angle].x;
1317 YS = 2 * Step[laser.current_angle].y;
1320 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1326 if (IS_BEAMER(element) ||
1327 IS_FIBRE_OPTIC(element) ||
1328 IS_PACMAN(element) ||
1329 IS_POLAR(element) ||
1330 IS_POLAR_CROSS(element) ||
1331 element == EL_FUSE_ON)
1336 Debug("game:mm:DrawLaserExt", "element == %d", element);
1339 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1340 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1344 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1345 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1346 (laser.num_beamers == 0 ||
1347 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1349 // element is incoming beamer or other element
1350 step_size = -step_size;
1355 if (IS_BEAMER(element))
1356 Debug("game:mm:DrawLaserExt",
1357 "start_edge == %d, laser.beamer_edge == %d",
1358 start_edge, laser.beamer_edge);
1361 LX += step_size * XS;
1362 LY += step_size * YS;
1364 else if (element != EL_EMPTY)
1373 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1378 void DrawLaser(int start_edge, int mode)
1380 // do not draw laser if fuse is off
1381 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1384 if (mode == DL_LASER_DISABLED)
1385 DeactivateLaserTargetElement();
1387 if (laser.num_edges - start_edge < 0)
1389 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1394 // check if laser is interrupted by beamer element
1395 if (laser.num_beamers > 0 &&
1396 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1398 if (mode == DL_LASER_ENABLED)
1401 int tmp_start_edge = start_edge;
1403 // draw laser segments forward from the start to the last beamer
1404 for (i = 0; i < laser.num_beamers; i++)
1406 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1408 if (tmp_num_edges <= 0)
1412 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1413 i, laser.beamer_edge[i], tmp_start_edge);
1416 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1418 tmp_start_edge = laser.beamer_edge[i];
1421 // draw last segment from last beamer to the end
1422 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1428 int last_num_edges = laser.num_edges;
1429 int num_beamers = laser.num_beamers;
1431 // delete laser segments backward from the end to the first beamer
1432 for (i = num_beamers - 1; i >= 0; i--)
1434 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1436 if (laser.beamer_edge[i] - start_edge <= 0)
1439 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1441 last_num_edges = laser.beamer_edge[i];
1442 laser.num_beamers--;
1446 if (last_num_edges - start_edge <= 0)
1447 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1448 last_num_edges, start_edge);
1451 // special case when rotating first beamer: delete laser edge on beamer
1452 // (but do not start scanning on previous edge to prevent mirror sound)
1453 if (last_num_edges - start_edge == 1 && start_edge > 0)
1454 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1456 // delete first segment from start to the first beamer
1457 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1462 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1465 game_mm.laser_enabled = mode;
1468 void DrawLaser_MM(void)
1470 DrawLaser(0, game_mm.laser_enabled);
1473 boolean HitElement(int element, int hit_mask)
1475 if (HitOnlyAnEdge(hit_mask))
1478 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1479 element = MovingOrBlocked2Element_MM(ELX, ELY);
1482 Debug("game:mm:HitElement", "(1): element == %d", element);
1486 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1487 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1490 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1494 AddDamagedField(ELX, ELY);
1496 // this is more precise: check if laser would go through the center
1497 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1501 // prevent cutting through laser emitter with laser beam
1502 if (IS_LASER(element))
1505 // skip the whole element before continuing the scan
1513 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1515 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1517 /* skipping scan positions to the right and down skips one scan
1518 position too much, because this is only the top left scan position
1519 of totally four scan positions (plus one to the right, one to the
1520 bottom and one to the bottom right) */
1521 /* ... but only roll back scan position if more than one step done */
1531 Debug("game:mm:HitElement", "(2): element == %d", element);
1534 if (LX + 5 * XS < 0 ||
1544 Debug("game:mm:HitElement", "(3): element == %d", element);
1547 if (IS_POLAR(element) &&
1548 ((element - EL_POLAR_START) % 2 ||
1549 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1551 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1553 laser.num_damages--;
1558 if (IS_POLAR_CROSS(element) &&
1559 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1561 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1563 laser.num_damages--;
1568 if (!IS_BEAMER(element) &&
1569 !IS_FIBRE_OPTIC(element) &&
1570 !IS_GRID_WOOD(element) &&
1571 element != EL_FUEL_EMPTY)
1574 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1575 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1577 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1580 LX = ELX * TILEX + 14;
1581 LY = ELY * TILEY + 14;
1583 AddLaserEdge(LX, LY);
1586 if (IS_MIRROR(element) ||
1587 IS_MIRROR_FIXED(element) ||
1588 IS_POLAR(element) ||
1589 IS_POLAR_CROSS(element) ||
1590 IS_DF_MIRROR(element) ||
1591 IS_DF_MIRROR_AUTO(element) ||
1592 element == EL_PRISM ||
1593 element == EL_REFRACTOR)
1595 int current_angle = laser.current_angle;
1598 laser.num_damages--;
1600 AddDamagedField(ELX, ELY);
1602 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1605 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1607 if (IS_MIRROR(element) ||
1608 IS_MIRROR_FIXED(element) ||
1609 IS_DF_MIRROR(element) ||
1610 IS_DF_MIRROR_AUTO(element))
1611 laser.current_angle = get_mirrored_angle(laser.current_angle,
1612 get_element_angle(element));
1614 if (element == EL_PRISM || element == EL_REFRACTOR)
1615 laser.current_angle = RND(16);
1617 XS = 2 * Step[laser.current_angle].x;
1618 YS = 2 * Step[laser.current_angle].y;
1620 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1625 LX += step_size * XS;
1626 LY += step_size * YS;
1628 // draw sparkles on mirror
1629 if ((IS_MIRROR(element) ||
1630 IS_MIRROR_FIXED(element) ||
1631 element == EL_PRISM) &&
1632 current_angle != laser.current_angle)
1634 MovDelay[ELX][ELY] = 11; // start animation
1637 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1638 current_angle != laser.current_angle)
1639 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1642 (get_opposite_angle(laser.current_angle) ==
1643 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1645 return (laser.overloaded ? TRUE : FALSE);
1648 if (element == EL_FUEL_FULL)
1650 laser.stops_inside_element = TRUE;
1655 if (element == EL_BOMB || element == EL_MINE)
1657 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1659 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1661 laser.dest_element_last = Tile[ELX][ELY];
1662 laser.dest_element_last_x = ELX;
1663 laser.dest_element_last_y = ELY;
1665 if (element == EL_MINE)
1666 laser.overloaded = TRUE;
1669 if (element == EL_KETTLE ||
1670 element == EL_CELL ||
1671 element == EL_KEY ||
1672 element == EL_LIGHTBALL ||
1673 element == EL_PACMAN ||
1674 IS_PACMAN(element) ||
1675 IS_ENVELOPE(element))
1677 if (!IS_PACMAN(element) &&
1678 !IS_ENVELOPE(element))
1681 if (element == EL_PACMAN)
1684 if (element == EL_KETTLE || element == EL_CELL)
1686 if (game_mm.kettles_still_needed > 0)
1687 game_mm.kettles_still_needed--;
1689 game.snapshot.collected_item = TRUE;
1691 if (game_mm.kettles_still_needed == 0)
1695 DrawLaser(0, DL_LASER_ENABLED);
1698 else if (element == EL_KEY)
1702 else if (IS_PACMAN(element))
1704 DeletePacMan(ELX, ELY);
1706 else if (IS_ENVELOPE(element))
1708 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1711 RaiseScoreElement_MM(element);
1716 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1718 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1720 DrawLaser(0, DL_LASER_ENABLED);
1722 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1724 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1725 game_mm.lights_still_needed--;
1729 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1730 game_mm.lights_still_needed++;
1733 DrawField_MM(ELX, ELY);
1734 DrawLaser(0, DL_LASER_ENABLED);
1739 laser.stops_inside_element = TRUE;
1745 Debug("game:mm:HitElement", "(4): element == %d", element);
1748 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1749 laser.num_beamers < MAX_NUM_BEAMERS &&
1750 laser.beamer[BEAMER_NR(element)][1].num)
1752 int beamer_angle = get_element_angle(element);
1753 int beamer_nr = BEAMER_NR(element);
1757 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1760 laser.num_damages--;
1762 if (IS_FIBRE_OPTIC(element) ||
1763 laser.current_angle == get_opposite_angle(beamer_angle))
1767 LX = ELX * TILEX + 14;
1768 LY = ELY * TILEY + 14;
1770 AddLaserEdge(LX, LY);
1771 AddDamagedField(ELX, ELY);
1773 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1776 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1778 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1779 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1780 ELX = laser.beamer[beamer_nr][pos].x;
1781 ELY = laser.beamer[beamer_nr][pos].y;
1782 LX = ELX * TILEX + 14;
1783 LY = ELY * TILEY + 14;
1785 if (IS_BEAMER(element))
1787 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1788 XS = 2 * Step[laser.current_angle].x;
1789 YS = 2 * Step[laser.current_angle].y;
1792 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1794 AddLaserEdge(LX, LY);
1795 AddDamagedField(ELX, ELY);
1797 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1800 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1802 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1807 LX += step_size * XS;
1808 LY += step_size * YS;
1810 laser.num_beamers++;
1819 boolean HitOnlyAnEdge(int hit_mask)
1821 // check if the laser hit only the edge of an element and, if so, go on
1824 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1828 if ((hit_mask == HIT_MASK_TOPLEFT ||
1829 hit_mask == HIT_MASK_TOPRIGHT ||
1830 hit_mask == HIT_MASK_BOTTOMLEFT ||
1831 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1832 laser.current_angle % 4) // angle is not 90°
1836 if (hit_mask == HIT_MASK_TOPLEFT)
1841 else if (hit_mask == HIT_MASK_TOPRIGHT)
1846 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1851 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1857 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1863 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1870 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1876 boolean HitPolarizer(int element, int hit_mask)
1878 if (HitOnlyAnEdge(hit_mask))
1881 if (IS_DF_GRID(element))
1883 int grid_angle = get_element_angle(element);
1886 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1887 grid_angle, laser.current_angle);
1890 AddLaserEdge(LX, LY);
1891 AddDamagedField(ELX, ELY);
1894 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1896 if (laser.current_angle == grid_angle ||
1897 laser.current_angle == get_opposite_angle(grid_angle))
1899 // skip the whole element before continuing the scan
1905 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1907 if (LX/TILEX > ELX || LY/TILEY > ELY)
1909 /* skipping scan positions to the right and down skips one scan
1910 position too much, because this is only the top left scan position
1911 of totally four scan positions (plus one to the right, one to the
1912 bottom and one to the bottom right) */
1918 AddLaserEdge(LX, LY);
1924 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1926 LX / TILEX, LY / TILEY,
1927 LX % TILEX, LY % TILEY);
1932 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1934 return HitReflectingWalls(element, hit_mask);
1938 return HitAbsorbingWalls(element, hit_mask);
1941 else if (IS_GRID_STEEL(element))
1943 return HitReflectingWalls(element, hit_mask);
1945 else // IS_GRID_WOOD
1947 return HitAbsorbingWalls(element, hit_mask);
1953 boolean HitBlock(int element, int hit_mask)
1955 boolean check = FALSE;
1957 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1958 game_mm.num_keys == 0)
1961 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1964 int ex = ELX * TILEX + 14;
1965 int ey = ELY * TILEY + 14;
1969 for (i = 1; i < 32; i++)
1974 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1979 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1980 return HitAbsorbingWalls(element, hit_mask);
1984 AddLaserEdge(LX - XS, LY - YS);
1985 AddDamagedField(ELX, ELY);
1988 Box[ELX][ELY] = laser.num_edges;
1990 return HitReflectingWalls(element, hit_mask);
1993 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1995 int xs = XS / 2, ys = YS / 2;
1996 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1997 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1999 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2000 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2002 laser.overloaded = (element == EL_GATE_STONE);
2007 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2008 (hit_mask == HIT_MASK_TOP ||
2009 hit_mask == HIT_MASK_LEFT ||
2010 hit_mask == HIT_MASK_RIGHT ||
2011 hit_mask == HIT_MASK_BOTTOM))
2012 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2013 hit_mask == HIT_MASK_BOTTOM),
2014 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2015 hit_mask == HIT_MASK_RIGHT));
2016 AddLaserEdge(LX, LY);
2022 if (element == EL_GATE_STONE && Box[ELX][ELY])
2024 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2036 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2038 int xs = XS / 2, ys = YS / 2;
2039 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2040 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2042 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2043 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2045 laser.overloaded = (element == EL_BLOCK_STONE);
2050 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2051 (hit_mask == HIT_MASK_TOP ||
2052 hit_mask == HIT_MASK_LEFT ||
2053 hit_mask == HIT_MASK_RIGHT ||
2054 hit_mask == HIT_MASK_BOTTOM))
2055 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2056 hit_mask == HIT_MASK_BOTTOM),
2057 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2058 hit_mask == HIT_MASK_RIGHT));
2059 AddDamagedField(ELX, ELY);
2061 LX = ELX * TILEX + 14;
2062 LY = ELY * TILEY + 14;
2064 AddLaserEdge(LX, LY);
2066 laser.stops_inside_element = TRUE;
2074 boolean HitLaserSource(int element, int hit_mask)
2076 if (HitOnlyAnEdge(hit_mask))
2079 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2081 laser.overloaded = TRUE;
2086 boolean HitLaserDestination(int element, int hit_mask)
2088 if (HitOnlyAnEdge(hit_mask))
2091 if (element != EL_EXIT_OPEN &&
2092 !(IS_RECEIVER(element) &&
2093 game_mm.kettles_still_needed == 0 &&
2094 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2096 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2101 if (IS_RECEIVER(element) ||
2102 (IS_22_5_ANGLE(laser.current_angle) &&
2103 (ELX != (LX + 6 * XS) / TILEX ||
2104 ELY != (LY + 6 * YS) / TILEY ||
2113 LX = ELX * TILEX + 14;
2114 LY = ELY * TILEY + 14;
2116 laser.stops_inside_element = TRUE;
2119 AddLaserEdge(LX, LY);
2120 AddDamagedField(ELX, ELY);
2122 if (game_mm.lights_still_needed == 0)
2124 game_mm.level_solved = TRUE;
2126 SetTileCursorActive(FALSE);
2132 boolean HitReflectingWalls(int element, int hit_mask)
2134 // check if laser hits side of a wall with an angle that is not 90°
2135 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2136 hit_mask == HIT_MASK_LEFT ||
2137 hit_mask == HIT_MASK_RIGHT ||
2138 hit_mask == HIT_MASK_BOTTOM))
2140 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2145 if (!IS_DF_GRID(element))
2146 AddLaserEdge(LX, LY);
2148 // check if laser hits wall with an angle of 45°
2149 if (!IS_22_5_ANGLE(laser.current_angle))
2151 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2154 laser.current_angle = get_mirrored_angle(laser.current_angle,
2157 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2160 laser.current_angle = get_mirrored_angle(laser.current_angle,
2164 AddLaserEdge(LX, LY);
2166 XS = 2 * Step[laser.current_angle].x;
2167 YS = 2 * Step[laser.current_angle].y;
2171 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2173 laser.current_angle = get_mirrored_angle(laser.current_angle,
2178 if (!IS_DF_GRID(element))
2179 AddLaserEdge(LX, LY);
2184 if (!IS_DF_GRID(element))
2185 AddLaserEdge(LX, LY + YS / 2);
2188 if (!IS_DF_GRID(element))
2189 AddLaserEdge(LX, LY);
2192 YS = 2 * Step[laser.current_angle].y;
2196 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2198 laser.current_angle = get_mirrored_angle(laser.current_angle,
2203 if (!IS_DF_GRID(element))
2204 AddLaserEdge(LX, LY);
2209 if (!IS_DF_GRID(element))
2210 AddLaserEdge(LX + XS / 2, LY);
2213 if (!IS_DF_GRID(element))
2214 AddLaserEdge(LX, LY);
2217 XS = 2 * Step[laser.current_angle].x;
2223 // reflection at the edge of reflecting DF style wall
2224 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2226 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2227 hit_mask == HIT_MASK_TOPRIGHT) ||
2228 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2229 hit_mask == HIT_MASK_TOPLEFT) ||
2230 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2231 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2232 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2233 hit_mask == HIT_MASK_BOTTOMRIGHT))
2236 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2237 ANG_MIRROR_135 : ANG_MIRROR_45);
2239 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2241 AddDamagedField(ELX, ELY);
2242 AddLaserEdge(LX, LY);
2244 laser.current_angle = get_mirrored_angle(laser.current_angle,
2252 AddLaserEdge(LX, LY);
2258 // reflection inside an edge of reflecting DF style wall
2259 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2261 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2262 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2263 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2264 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2265 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2266 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2267 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2268 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2271 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2272 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2273 ANG_MIRROR_135 : ANG_MIRROR_45);
2275 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2278 AddDamagedField(ELX, ELY);
2281 AddLaserEdge(LX - XS, LY - YS);
2282 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2283 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2285 laser.current_angle = get_mirrored_angle(laser.current_angle,
2293 AddLaserEdge(LX, LY);
2299 // check if laser hits DF style wall with an angle of 90°
2300 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2302 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2303 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2304 (IS_VERT_ANGLE(laser.current_angle) &&
2305 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2307 // laser at last step touched nothing or the same side of the wall
2308 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2310 AddDamagedField(ELX, ELY);
2317 last_hit_mask = hit_mask;
2324 if (!HitOnlyAnEdge(hit_mask))
2326 laser.overloaded = TRUE;
2334 boolean HitAbsorbingWalls(int element, int hit_mask)
2336 if (HitOnlyAnEdge(hit_mask))
2340 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2342 AddLaserEdge(LX - XS, LY - YS);
2349 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2351 AddLaserEdge(LX - XS, LY - YS);
2357 if (IS_WALL_WOOD(element) ||
2358 IS_DF_WALL_WOOD(element) ||
2359 IS_GRID_WOOD(element) ||
2360 IS_GRID_WOOD_FIXED(element) ||
2361 IS_GRID_WOOD_AUTO(element) ||
2362 element == EL_FUSE_ON ||
2363 element == EL_BLOCK_WOOD ||
2364 element == EL_GATE_WOOD)
2366 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2371 if (IS_WALL_ICE(element))
2375 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2376 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2378 // check if laser hits wall with an angle of 90°
2379 if (IS_90_ANGLE(laser.current_angle))
2380 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2382 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2386 for (i = 0; i < 4; i++)
2388 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2389 mask = 15 - (8 >> i);
2390 else if (ABS(XS) == 4 &&
2392 (XS > 0) == (i % 2) &&
2393 (YS < 0) == (i / 2))
2394 mask = 3 + (i / 2) * 9;
2395 else if (ABS(YS) == 4 &&
2397 (XS < 0) == (i % 2) &&
2398 (YS > 0) == (i / 2))
2399 mask = 5 + (i % 2) * 5;
2403 laser.wall_mask = mask;
2405 else if (IS_WALL_AMOEBA(element))
2407 int elx = (LX - 2 * XS) / TILEX;
2408 int ely = (LY - 2 * YS) / TILEY;
2409 int element2 = Tile[elx][ely];
2412 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2414 laser.dest_element = EL_EMPTY;
2422 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2423 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2425 if (IS_90_ANGLE(laser.current_angle))
2426 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2428 laser.dest_element = element2 | EL_WALL_AMOEBA;
2430 laser.wall_mask = mask;
2436 static void OpenExit(int x, int y)
2440 if (!MovDelay[x][y]) // next animation frame
2441 MovDelay[x][y] = 4 * delay;
2443 if (MovDelay[x][y]) // wait some time before next frame
2448 phase = MovDelay[x][y] / delay;
2450 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2451 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2453 if (!MovDelay[x][y])
2455 Tile[x][y] = EL_EXIT_OPEN;
2461 static void OpenSurpriseBall(int x, int y)
2465 if (!MovDelay[x][y]) // next animation frame
2466 MovDelay[x][y] = 50 * delay;
2468 if (MovDelay[x][y]) // wait some time before next frame
2472 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2475 int graphic = el2gfx(Store[x][y]);
2477 int dx = RND(26), dy = RND(26);
2479 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2481 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2482 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2484 laser.redraw = TRUE;
2486 MarkTileDirty(x, y);
2489 if (!MovDelay[x][y])
2491 Tile[x][y] = Store[x][y];
2492 Store[x][y] = Store2[x][y] = 0;
2493 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2495 InitField(x, y, FALSE);
2503 static void OpenEnvelope(int x, int y)
2505 int num_frames = 8; // seven frames plus final empty space
2507 if (!MovDelay[x][y]) // next animation frame
2508 MovDelay[x][y] = num_frames;
2510 if (MovDelay[x][y]) // wait some time before next frame
2512 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2516 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2518 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2519 int frame = num_frames - MovDelay[x][y] - 1;
2521 DrawGraphicAnimation_MM(x, y, graphic, frame);
2523 laser.redraw = TRUE;
2526 if (MovDelay[x][y] == 0)
2528 Tile[x][y] = EL_EMPTY;
2534 ShowEnvelope_MM(nr);
2539 static void MeltIce(int x, int y)
2544 if (!MovDelay[x][y]) // next animation frame
2545 MovDelay[x][y] = frames * delay;
2547 if (MovDelay[x][y]) // wait some time before next frame
2550 int wall_mask = Store2[x][y];
2551 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2554 phase = frames - MovDelay[x][y] / delay - 1;
2556 if (!MovDelay[x][y])
2560 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2561 Store[x][y] = Store2[x][y] = 0;
2563 DrawWalls_MM(x, y, Tile[x][y]);
2565 if (Tile[x][y] == EL_WALL_ICE)
2566 Tile[x][y] = EL_EMPTY;
2568 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2569 if (laser.damage[i].is_mirror)
2573 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2575 DrawLaser(0, DL_LASER_DISABLED);
2579 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2581 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2583 laser.redraw = TRUE;
2588 static void GrowAmoeba(int x, int y)
2593 if (!MovDelay[x][y]) // next animation frame
2594 MovDelay[x][y] = frames * delay;
2596 if (MovDelay[x][y]) // wait some time before next frame
2599 int wall_mask = Store2[x][y];
2600 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2603 phase = MovDelay[x][y] / delay;
2605 if (!MovDelay[x][y])
2607 Tile[x][y] = real_element;
2608 Store[x][y] = Store2[x][y] = 0;
2610 DrawWalls_MM(x, y, Tile[x][y]);
2611 DrawLaser(0, DL_LASER_ENABLED);
2613 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2615 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2620 static void DrawFieldAnimated_MM(int x, int y)
2624 laser.redraw = TRUE;
2627 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2629 int element = Tile[x][y];
2630 int graphic = el2gfx(element);
2632 if (!getGraphicInfo_NewFrame(x, y, graphic))
2637 laser.redraw = TRUE;
2640 static void DrawFieldTwinkle(int x, int y)
2642 if (MovDelay[x][y] != 0) // wait some time before next frame
2648 if (MovDelay[x][y] != 0)
2650 int graphic = IMG_TWINKLE_WHITE;
2651 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2653 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2656 laser.redraw = TRUE;
2660 static void Explode_MM(int x, int y, int phase, int mode)
2662 int num_phase = 9, delay = 2;
2663 int last_phase = num_phase * delay;
2664 int half_phase = (num_phase / 2) * delay;
2666 laser.redraw = TRUE;
2668 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2670 int center_element = Tile[x][y];
2672 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2674 // put moving element to center field (and let it explode there)
2675 center_element = MovingOrBlocked2Element_MM(x, y);
2676 RemoveMovingField_MM(x, y);
2678 Tile[x][y] = center_element;
2681 Store[x][y] = center_element;
2682 Store2[x][y] = mode;
2684 Tile[x][y] = EL_EXPLODING_OPAQUE;
2686 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2687 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2690 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2692 ExplodePhase[x][y] = 1;
2698 GfxFrame[x][y] = 0; // restart explosion animation
2700 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2702 if (phase == half_phase)
2704 Tile[x][y] = EL_EXPLODING_TRANSP;
2706 if (x == ELX && y == ELY)
2710 if (phase == last_phase)
2712 if (Store[x][y] == EL_BOMB_ACTIVE)
2714 DrawLaser(0, DL_LASER_DISABLED);
2717 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2719 GameOver_MM(GAME_OVER_DELAYED);
2721 laser.overloaded = FALSE;
2723 else if (IS_MCDUFFIN(Store[x][y]))
2725 GameOver_MM(GAME_OVER_BOMB);
2728 Tile[x][y] = EL_EMPTY;
2730 Store[x][y] = Store2[x][y] = 0;
2731 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2733 InitField(x, y, FALSE);
2736 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2738 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2739 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2741 DrawGraphicAnimation_MM(x, y, graphic, frame);
2743 MarkTileDirty(x, y);
2747 static void Bang_MM(int x, int y)
2749 int element = Tile[x][y];
2752 DrawLaser(0, DL_LASER_ENABLED);
2755 if (IS_PACMAN(element))
2756 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2757 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2758 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2759 else if (element == EL_KEY)
2760 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2762 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2764 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2767 void TurnRound(int x, int y)
2779 { 0, 0 }, { 0, 0 }, { 0, 0 },
2784 int left, right, back;
2788 { MV_DOWN, MV_UP, MV_RIGHT },
2789 { MV_UP, MV_DOWN, MV_LEFT },
2791 { MV_LEFT, MV_RIGHT, MV_DOWN },
2792 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2793 { MV_RIGHT, MV_LEFT, MV_UP }
2796 int element = Tile[x][y];
2797 int old_move_dir = MovDir[x][y];
2798 int right_dir = turn[old_move_dir].right;
2799 int back_dir = turn[old_move_dir].back;
2800 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2801 int right_x = x + right_dx, right_y = y + right_dy;
2803 if (element == EL_PACMAN)
2805 boolean can_turn_right = FALSE;
2807 if (IN_LEV_FIELD(right_x, right_y) &&
2808 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2809 can_turn_right = TRUE;
2812 MovDir[x][y] = right_dir;
2814 MovDir[x][y] = back_dir;
2820 static void StartMoving_MM(int x, int y)
2822 int element = Tile[x][y];
2827 if (CAN_MOVE(element))
2831 if (MovDelay[x][y]) // wait some time before next movement
2839 // now make next step
2841 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2843 if (element == EL_PACMAN &&
2844 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2845 !ObjHit(newx, newy, HIT_POS_CENTER))
2847 Store[newx][newy] = Tile[newx][newy];
2848 Tile[newx][newy] = EL_EMPTY;
2850 DrawField_MM(newx, newy);
2852 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2853 ObjHit(newx, newy, HIT_POS_CENTER))
2855 // object was running against a wall
2862 InitMovingField_MM(x, y, MovDir[x][y]);
2866 ContinueMoving_MM(x, y);
2869 static void ContinueMoving_MM(int x, int y)
2871 int element = Tile[x][y];
2872 int direction = MovDir[x][y];
2873 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2874 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2875 int horiz_move = (dx!=0);
2876 int newx = x + dx, newy = y + dy;
2877 int step = (horiz_move ? dx : dy) * TILEX / 8;
2879 MovPos[x][y] += step;
2881 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2883 Tile[x][y] = EL_EMPTY;
2884 Tile[newx][newy] = element;
2886 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2887 MovDelay[newx][newy] = 0;
2889 if (!CAN_MOVE(element))
2890 MovDir[newx][newy] = 0;
2893 DrawField_MM(newx, newy);
2895 Stop[newx][newy] = TRUE;
2897 if (element == EL_PACMAN)
2899 if (Store[newx][newy] == EL_BOMB)
2900 Bang_MM(newx, newy);
2902 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2903 (LX + 2 * XS) / TILEX == newx &&
2904 (LY + 2 * YS) / TILEY == newy)
2911 else // still moving on
2916 laser.redraw = TRUE;
2919 boolean ClickElement(int x, int y, int button)
2921 static DelayCounter click_delay = { CLICK_DELAY };
2922 static boolean new_button = TRUE;
2923 boolean element_clicked = FALSE;
2928 // initialize static variables
2929 click_delay.count = 0;
2930 click_delay.value = CLICK_DELAY;
2936 // do not rotate objects hit by the laser after the game was solved
2937 if (game_mm.level_solved && Hit[x][y])
2940 if (button == MB_RELEASED)
2943 click_delay.value = CLICK_DELAY;
2945 // release eventually hold auto-rotating mirror
2946 RotateMirror(x, y, MB_RELEASED);
2951 if (!FrameReached(&click_delay) && !new_button)
2954 if (button == MB_MIDDLEBUTTON) // middle button has no function
2957 if (!IN_LEV_FIELD(x, y))
2960 if (Tile[x][y] == EL_EMPTY)
2963 element = Tile[x][y];
2965 if (IS_MIRROR(element) ||
2966 IS_BEAMER(element) ||
2967 IS_POLAR(element) ||
2968 IS_POLAR_CROSS(element) ||
2969 IS_DF_MIRROR(element) ||
2970 IS_DF_MIRROR_AUTO(element))
2972 RotateMirror(x, y, button);
2974 element_clicked = TRUE;
2976 else if (IS_MCDUFFIN(element))
2978 if (!laser.fuse_off)
2980 DrawLaser(0, DL_LASER_DISABLED);
2987 element = get_rotated_element(element, BUTTON_ROTATION(button));
2988 laser.start_angle = get_element_angle(element);
2992 Tile[x][y] = element;
2999 if (!laser.fuse_off)
3002 element_clicked = TRUE;
3004 else if (element == EL_FUSE_ON && laser.fuse_off)
3006 if (x != laser.fuse_x || y != laser.fuse_y)
3009 laser.fuse_off = FALSE;
3010 laser.fuse_x = laser.fuse_y = -1;
3012 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3015 element_clicked = TRUE;
3017 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3019 laser.fuse_off = TRUE;
3022 laser.overloaded = FALSE;
3024 DrawLaser(0, DL_LASER_DISABLED);
3025 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3027 element_clicked = TRUE;
3029 else if (element == EL_LIGHTBALL)
3032 RaiseScoreElement_MM(element);
3033 DrawLaser(0, DL_LASER_ENABLED);
3035 element_clicked = TRUE;
3037 else if (IS_ENVELOPE(element))
3039 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3041 element_clicked = TRUE;
3044 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3047 return element_clicked;
3050 void RotateMirror(int x, int y, int button)
3052 if (button == MB_RELEASED)
3054 // release eventually hold auto-rotating mirror
3061 if (IS_MIRROR(Tile[x][y]) ||
3062 IS_POLAR_CROSS(Tile[x][y]) ||
3063 IS_POLAR(Tile[x][y]) ||
3064 IS_BEAMER(Tile[x][y]) ||
3065 IS_DF_MIRROR(Tile[x][y]) ||
3066 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3067 IS_GRID_WOOD_AUTO(Tile[x][y]))
3069 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3071 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3073 if (button == MB_LEFTBUTTON)
3075 // left mouse button only for manual adjustment, no auto-rotating;
3076 // freeze mirror for until mouse button released
3080 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3082 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3086 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3088 int edge = Hit[x][y];
3094 DrawLaser(edge - 1, DL_LASER_DISABLED);
3098 else if (ObjHit(x, y, HIT_POS_CENTER))
3100 int edge = Hit[x][y];
3104 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3109 DrawLaser(edge - 1, DL_LASER_DISABLED);
3116 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3121 if ((IS_BEAMER(Tile[x][y]) ||
3122 IS_POLAR(Tile[x][y]) ||
3123 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3125 if (IS_BEAMER(Tile[x][y]))
3128 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3129 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3142 DrawLaser(0, DL_LASER_ENABLED);
3146 static void AutoRotateMirrors(void)
3150 if (!FrameReached(&rotate_delay))
3153 for (x = 0; x < lev_fieldx; x++)
3155 for (y = 0; y < lev_fieldy; y++)
3157 int element = Tile[x][y];
3159 // do not rotate objects hit by the laser after the game was solved
3160 if (game_mm.level_solved && Hit[x][y])
3163 if (IS_DF_MIRROR_AUTO(element) ||
3164 IS_GRID_WOOD_AUTO(element) ||
3165 IS_GRID_STEEL_AUTO(element) ||
3166 element == EL_REFRACTOR)
3167 RotateMirror(x, y, MB_RIGHTBUTTON);
3172 boolean ObjHit(int obx, int oby, int bits)
3179 if (bits & HIT_POS_CENTER)
3181 if (CheckLaserPixel(cSX + obx + 15,
3186 if (bits & HIT_POS_EDGE)
3188 for (i = 0; i < 4; i++)
3189 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3190 cSY + oby + 31 * (i / 2)))
3194 if (bits & HIT_POS_BETWEEN)
3196 for (i = 0; i < 4; i++)
3197 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3198 cSY + 4 + oby + 22 * (i / 2)))
3205 void DeletePacMan(int px, int py)
3211 if (game_mm.num_pacman <= 1)
3213 game_mm.num_pacman = 0;
3217 for (i = 0; i < game_mm.num_pacman; i++)
3218 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3221 game_mm.num_pacman--;
3223 for (j = i; j < game_mm.num_pacman; j++)
3225 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3226 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3227 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3231 void ColorCycling(void)
3233 static int CC, Cc = 0;
3235 static int color, old = 0xF00, new = 0x010, mult = 1;
3236 static unsigned short red, green, blue;
3238 if (color_status == STATIC_COLORS)
3243 if (CC < Cc || CC > Cc + 2)
3247 color = old + new * mult;
3253 if (ABS(mult) == 16)
3263 red = 0x0e00 * ((color & 0xF00) >> 8);
3264 green = 0x0e00 * ((color & 0x0F0) >> 4);
3265 blue = 0x0e00 * ((color & 0x00F));
3266 SetRGB(pen_magicolor[0], red, green, blue);
3268 red = 0x1111 * ((color & 0xF00) >> 8);
3269 green = 0x1111 * ((color & 0x0F0) >> 4);
3270 blue = 0x1111 * ((color & 0x00F));
3271 SetRGB(pen_magicolor[1], red, green, blue);
3275 static void GameActions_MM_Ext(void)
3282 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3285 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3287 element = Tile[x][y];
3289 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3290 StartMoving_MM(x, y);
3291 else if (IS_MOVING(x, y))
3292 ContinueMoving_MM(x, y);
3293 else if (IS_EXPLODING(element))
3294 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3295 else if (element == EL_EXIT_OPENING)
3297 else if (element == EL_GRAY_BALL_OPENING)
3298 OpenSurpriseBall(x, y);
3299 else if (IS_ENVELOPE_OPENING(element))
3301 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3303 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3305 else if (IS_MIRROR(element) ||
3306 IS_MIRROR_FIXED(element) ||
3307 element == EL_PRISM)
3308 DrawFieldTwinkle(x, y);
3309 else if (element == EL_GRAY_BALL_OPENING ||
3310 element == EL_BOMB_ACTIVE ||
3311 element == EL_MINE_ACTIVE)
3312 DrawFieldAnimated_MM(x, y);
3313 else if (!IS_BLOCKED(x, y))
3314 DrawFieldAnimatedIfNeeded_MM(x, y);
3317 AutoRotateMirrors();
3320 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3322 // redraw after Explode_MM() ...
3324 DrawLaser(0, DL_LASER_ENABLED);
3325 laser.redraw = FALSE;
3330 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3334 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3336 DrawLaser(0, DL_LASER_DISABLED);
3341 // skip all following game actions if game is over
3342 if (game_mm.game_over)
3345 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3349 GameOver_MM(GAME_OVER_NO_ENERGY);
3354 if (FrameReached(&energy_delay))
3356 if (game_mm.energy_left > 0)
3357 game_mm.energy_left--;
3359 // when out of energy, wait another frame to play "out of time" sound
3362 element = laser.dest_element;
3365 if (element != Tile[ELX][ELY])
3367 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3368 element, Tile[ELX][ELY]);
3372 if (!laser.overloaded && laser.overload_value == 0 &&
3373 element != EL_BOMB &&
3374 element != EL_BOMB_ACTIVE &&
3375 element != EL_MINE &&
3376 element != EL_MINE_ACTIVE &&
3377 element != EL_BALL_GRAY &&
3378 element != EL_BLOCK_STONE &&
3379 element != EL_BLOCK_WOOD &&
3380 element != EL_FUSE_ON &&
3381 element != EL_FUEL_FULL &&
3382 !IS_WALL_ICE(element) &&
3383 !IS_WALL_AMOEBA(element))
3386 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3388 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3389 (!laser.overloaded && laser.overload_value > 0)) &&
3390 FrameReached(&overload_delay))
3392 if (laser.overloaded)
3393 laser.overload_value++;
3395 laser.overload_value--;
3397 if (game_mm.cheat_no_overload)
3399 laser.overloaded = FALSE;
3400 laser.overload_value = 0;
3403 game_mm.laser_overload_value = laser.overload_value;
3405 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3407 SetLaserColor(0xFF);
3409 DrawLaser(0, DL_LASER_ENABLED);
3412 if (!laser.overloaded)
3413 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3414 else if (setup.sound_loops)
3415 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3417 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3419 if (laser.overloaded)
3422 BlitBitmap(pix[PIX_DOOR], drawto,
3423 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3424 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3425 - laser.overload_value,
3426 OVERLOAD_XSIZE, laser.overload_value,
3427 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3428 - laser.overload_value);
3430 redraw_mask |= REDRAW_DOOR_1;
3435 BlitBitmap(pix[PIX_DOOR], drawto,
3436 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3437 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3438 DX_OVERLOAD, DY_OVERLOAD);
3440 redraw_mask |= REDRAW_DOOR_1;
3443 if (laser.overload_value == MAX_LASER_OVERLOAD)
3445 UpdateAndDisplayGameControlValues();
3449 GameOver_MM(GAME_OVER_OVERLOADED);
3460 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3462 if (game_mm.cheat_no_explosion)
3467 laser.dest_element = EL_EXPLODING_OPAQUE;
3472 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3474 laser.fuse_off = TRUE;
3478 DrawLaser(0, DL_LASER_DISABLED);
3479 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3482 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3484 if (!Store2[ELX][ELY]) // check if content element not yet determined
3486 int last_anim_random_frame = gfx.anim_random_frame;
3489 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3490 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3492 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3493 native_mm_level.ball_choice_mode, 0,
3494 game_mm.ball_choice_pos);
3496 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3497 gfx.anim_random_frame = last_anim_random_frame;
3499 game_mm.ball_choice_pos++;
3501 int new_element = native_mm_level.ball_content[element_pos];
3503 // randomly rotate newly created game element, if needed
3504 if (native_mm_level.rotate_ball_content)
3505 new_element = get_rotated_element(new_element, RND(16));
3507 Store[ELX][ELY] = new_element;
3508 Store2[ELX][ELY] = TRUE;
3511 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3513 // !!! CHECK AGAIN: Laser on Polarizer !!!
3516 laser.dest_element_last = Tile[ELX][ELY];
3517 laser.dest_element_last_x = ELX;
3518 laser.dest_element_last_y = ELY;
3528 element = EL_MIRROR_START + RND(16);
3534 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3541 element = (rnd == 0 ? EL_FUSE_ON :
3542 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3543 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3544 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3545 EL_MIRROR_FIXED_START + rnd - 25);
3550 graphic = el2gfx(element);
3552 for (i = 0; i < 50; i++)
3558 BlitBitmap(pix[PIX_BACK], drawto,
3559 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3560 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3561 SX + ELX * TILEX + x,
3562 SY + ELY * TILEY + y);
3564 MarkTileDirty(ELX, ELY);
3567 DrawLaser(0, DL_LASER_ENABLED);
3569 Delay_WithScreenUpdates(50);
3572 Tile[ELX][ELY] = element;
3573 DrawField_MM(ELX, ELY);
3576 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3579 // above stuff: GRAY BALL -> PRISM !!!
3581 LX = ELX * TILEX + 14;
3582 LY = ELY * TILEY + 14;
3583 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3590 laser.num_edges -= 2;
3591 laser.num_damages--;
3595 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3596 if (laser.damage[i].is_mirror)
3600 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3602 DrawLaser(0, DL_LASER_DISABLED);
3604 DrawLaser(0, DL_LASER_DISABLED);
3613 if (IS_WALL_ICE(element) && CT > 50)
3615 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3618 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3619 Store[ELX][ELY] = EL_WALL_ICE;
3620 Store2[ELX][ELY] = laser.wall_mask;
3622 laser.dest_element = Tile[ELX][ELY];
3627 for (i = 0; i < 5; i++)
3633 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3637 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3639 Delay_WithScreenUpdates(100);
3642 if (Tile[ELX][ELY] == EL_WALL_ICE)
3643 Tile[ELX][ELY] = EL_EMPTY;
3647 LX = laser.edge[laser.num_edges].x - cSX2;
3648 LY = laser.edge[laser.num_edges].y - cSY2;
3651 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3652 if (laser.damage[i].is_mirror)
3656 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3658 DrawLaser(0, DL_LASER_DISABLED);
3665 if (IS_WALL_AMOEBA(element) && CT > 60)
3667 int k1, k2, k3, dx, dy, de, dm;
3668 int element2 = Tile[ELX][ELY];
3670 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3673 for (i = laser.num_damages - 1; i >= 0; i--)
3674 if (laser.damage[i].is_mirror)
3677 r = laser.num_edges;
3678 d = laser.num_damages;
3685 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3688 DrawLaser(0, DL_LASER_ENABLED);
3691 x = laser.damage[k1].x;
3692 y = laser.damage[k1].y;
3697 for (i = 0; i < 4; i++)
3699 if (laser.wall_mask & (1 << i))
3701 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3702 cSY + ELY * TILEY + 31 * (i / 2)))
3705 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3706 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3713 for (i = 0; i < 4; i++)
3715 if (laser.wall_mask & (1 << i))
3717 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3718 cSY + ELY * TILEY + 31 * (i / 2)))
3725 if (laser.num_beamers > 0 ||
3726 k1 < 1 || k2 < 4 || k3 < 4 ||
3727 CheckLaserPixel(cSX + ELX * TILEX + 14,
3728 cSY + ELY * TILEY + 14))
3730 laser.num_edges = r;
3731 laser.num_damages = d;
3733 DrawLaser(0, DL_LASER_DISABLED);
3736 Tile[ELX][ELY] = element | laser.wall_mask;
3740 de = Tile[ELX][ELY];
3741 dm = laser.wall_mask;
3745 int x = ELX, y = ELY;
3746 int wall_mask = laser.wall_mask;
3749 DrawLaser(0, DL_LASER_ENABLED);
3751 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3753 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3754 Store[x][y] = EL_WALL_AMOEBA;
3755 Store2[x][y] = wall_mask;
3761 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3763 DrawLaser(0, DL_LASER_ENABLED);
3765 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3767 for (i = 4; i >= 0; i--)
3769 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3772 Delay_WithScreenUpdates(20);
3775 DrawLaser(0, DL_LASER_ENABLED);
3780 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3781 laser.stops_inside_element && CT > native_mm_level.time_block)
3786 if (ABS(XS) > ABS(YS))
3793 for (i = 0; i < 4; i++)
3800 x = ELX + Step[k * 4].x;
3801 y = ELY + Step[k * 4].y;
3803 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3806 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3814 laser.overloaded = (element == EL_BLOCK_STONE);
3819 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3822 Tile[x][y] = element;
3824 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3827 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3829 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3830 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3838 if (element == EL_FUEL_FULL && CT > 10)
3840 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3841 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3843 for (i = start; i <= num_init_game_frames; i++)
3845 if (i == num_init_game_frames)
3846 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3847 else if (setup.sound_loops)
3848 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3850 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3852 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3854 UpdateAndDisplayGameControlValues();
3859 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3861 DrawField_MM(ELX, ELY);
3863 DrawLaser(0, DL_LASER_ENABLED);
3871 void GameActions_MM(struct MouseActionInfo action)
3873 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3874 boolean button_released = (action.button == MB_RELEASED);
3876 GameActions_MM_Ext();
3878 CheckSingleStepMode_MM(element_clicked, button_released);
3881 void MovePacMen(void)
3883 int mx, my, ox, oy, nx, ny;
3887 if (++pacman_nr >= game_mm.num_pacman)
3890 game_mm.pacman[pacman_nr].dir--;
3892 for (l = 1; l < 5; l++)
3894 game_mm.pacman[pacman_nr].dir++;
3896 if (game_mm.pacman[pacman_nr].dir > 4)
3897 game_mm.pacman[pacman_nr].dir = 1;
3899 if (game_mm.pacman[pacman_nr].dir % 2)
3902 my = game_mm.pacman[pacman_nr].dir - 2;
3907 mx = 3 - game_mm.pacman[pacman_nr].dir;
3910 ox = game_mm.pacman[pacman_nr].x;
3911 oy = game_mm.pacman[pacman_nr].y;
3914 element = Tile[nx][ny];
3916 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3919 if (!IS_EATABLE4PACMAN(element))
3922 if (ObjHit(nx, ny, HIT_POS_CENTER))
3925 Tile[ox][oy] = EL_EMPTY;
3927 EL_PACMAN_RIGHT - 1 +
3928 (game_mm.pacman[pacman_nr].dir - 1 +
3929 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3931 game_mm.pacman[pacman_nr].x = nx;
3932 game_mm.pacman[pacman_nr].y = ny;
3934 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3936 if (element != EL_EMPTY)
3938 int graphic = el2gfx(Tile[nx][ny]);
3943 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3946 ox = cSX + ox * TILEX;
3947 oy = cSY + oy * TILEY;
3949 for (i = 1; i < 33; i += 2)
3950 BlitBitmap(bitmap, window,
3951 src_x, src_y, TILEX, TILEY,
3952 ox + i * mx, oy + i * my);
3953 Ct = Ct + FrameCounter - CT;
3956 DrawField_MM(nx, ny);
3959 if (!laser.fuse_off)
3961 DrawLaser(0, DL_LASER_ENABLED);
3963 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3965 AddDamagedField(nx, ny);
3967 laser.damage[laser.num_damages - 1].edge = 0;
3971 if (element == EL_BOMB)
3972 DeletePacMan(nx, ny);
3974 if (IS_WALL_AMOEBA(element) &&
3975 (LX + 2 * XS) / TILEX == nx &&
3976 (LY + 2 * YS) / TILEY == ny)
3986 static void InitMovingField_MM(int x, int y, int direction)
3988 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3989 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3991 MovDir[x][y] = direction;
3992 MovDir[newx][newy] = direction;
3994 if (Tile[newx][newy] == EL_EMPTY)
3995 Tile[newx][newy] = EL_BLOCKED;
3998 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4000 int direction = MovDir[x][y];
4001 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4002 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4008 static void Blocked2Moving_MM(int x, int y,
4009 int *comes_from_x, int *comes_from_y)
4011 int oldx = x, oldy = y;
4012 int direction = MovDir[x][y];
4014 if (direction == MV_LEFT)
4016 else if (direction == MV_RIGHT)
4018 else if (direction == MV_UP)
4020 else if (direction == MV_DOWN)
4023 *comes_from_x = oldx;
4024 *comes_from_y = oldy;
4027 static int MovingOrBlocked2Element_MM(int x, int y)
4029 int element = Tile[x][y];
4031 if (element == EL_BLOCKED)
4035 Blocked2Moving_MM(x, y, &oldx, &oldy);
4037 return Tile[oldx][oldy];
4044 static void RemoveField(int x, int y)
4046 Tile[x][y] = EL_EMPTY;
4053 static void RemoveMovingField_MM(int x, int y)
4055 int oldx = x, oldy = y, newx = x, newy = y;
4057 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4060 if (IS_MOVING(x, y))
4062 Moving2Blocked_MM(x, y, &newx, &newy);
4063 if (Tile[newx][newy] != EL_BLOCKED)
4066 else if (Tile[x][y] == EL_BLOCKED)
4068 Blocked2Moving_MM(x, y, &oldx, &oldy);
4069 if (!IS_MOVING(oldx, oldy))
4073 Tile[oldx][oldy] = EL_EMPTY;
4074 Tile[newx][newy] = EL_EMPTY;
4075 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4076 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4078 DrawLevelField_MM(oldx, oldy);
4079 DrawLevelField_MM(newx, newy);
4082 void PlaySoundLevel(int x, int y, int sound_nr)
4084 int sx = SCREENX(x), sy = SCREENY(y);
4086 int silence_distance = 8;
4088 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4089 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4092 if (!IN_LEV_FIELD(x, y) ||
4093 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4094 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4097 volume = SOUND_MAX_VOLUME;
4100 stereo = (sx - SCR_FIELDX/2) * 12;
4102 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4103 if (stereo > SOUND_MAX_RIGHT)
4104 stereo = SOUND_MAX_RIGHT;
4105 if (stereo < SOUND_MAX_LEFT)
4106 stereo = SOUND_MAX_LEFT;
4109 if (!IN_SCR_FIELD(sx, sy))
4111 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4112 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4114 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4117 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4120 static void RaiseScore_MM(int value)
4122 game_mm.score += value;
4125 void RaiseScoreElement_MM(int element)
4130 case EL_PACMAN_RIGHT:
4132 case EL_PACMAN_LEFT:
4133 case EL_PACMAN_DOWN:
4134 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4138 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4143 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4147 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4156 // ----------------------------------------------------------------------------
4157 // Mirror Magic game engine snapshot handling functions
4158 // ----------------------------------------------------------------------------
4160 void SaveEngineSnapshotValues_MM(void)
4164 engine_snapshot_mm.game_mm = game_mm;
4165 engine_snapshot_mm.laser = laser;
4167 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4169 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4171 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4172 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4173 engine_snapshot_mm.Box[x][y] = Box[x][y];
4174 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4178 engine_snapshot_mm.LX = LX;
4179 engine_snapshot_mm.LY = LY;
4180 engine_snapshot_mm.XS = XS;
4181 engine_snapshot_mm.YS = YS;
4182 engine_snapshot_mm.ELX = ELX;
4183 engine_snapshot_mm.ELY = ELY;
4184 engine_snapshot_mm.CT = CT;
4185 engine_snapshot_mm.Ct = Ct;
4187 engine_snapshot_mm.last_LX = last_LX;
4188 engine_snapshot_mm.last_LY = last_LY;
4189 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4190 engine_snapshot_mm.hold_x = hold_x;
4191 engine_snapshot_mm.hold_y = hold_y;
4192 engine_snapshot_mm.pacman_nr = pacman_nr;
4194 engine_snapshot_mm.rotate_delay = rotate_delay;
4195 engine_snapshot_mm.pacman_delay = pacman_delay;
4196 engine_snapshot_mm.energy_delay = energy_delay;
4197 engine_snapshot_mm.overload_delay = overload_delay;
4200 void LoadEngineSnapshotValues_MM(void)
4204 // stored engine snapshot buffers already restored at this point
4206 game_mm = engine_snapshot_mm.game_mm;
4207 laser = engine_snapshot_mm.laser;
4209 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4211 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4213 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4214 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4215 Box[x][y] = engine_snapshot_mm.Box[x][y];
4216 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4220 LX = engine_snapshot_mm.LX;
4221 LY = engine_snapshot_mm.LY;
4222 XS = engine_snapshot_mm.XS;
4223 YS = engine_snapshot_mm.YS;
4224 ELX = engine_snapshot_mm.ELX;
4225 ELY = engine_snapshot_mm.ELY;
4226 CT = engine_snapshot_mm.CT;
4227 Ct = engine_snapshot_mm.Ct;
4229 last_LX = engine_snapshot_mm.last_LX;
4230 last_LY = engine_snapshot_mm.last_LY;
4231 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4232 hold_x = engine_snapshot_mm.hold_x;
4233 hold_y = engine_snapshot_mm.hold_y;
4234 pacman_nr = engine_snapshot_mm.pacman_nr;
4236 rotate_delay = engine_snapshot_mm.rotate_delay;
4237 pacman_delay = engine_snapshot_mm.pacman_delay;
4238 energy_delay = engine_snapshot_mm.energy_delay;
4239 overload_delay = engine_snapshot_mm.overload_delay;
4241 RedrawPlayfield_MM();
4244 static int getAngleFromTouchDelta(int dx, int dy, int base)
4246 double pi = 3.141592653;
4247 double rad = atan2((double)-dy, (double)dx);
4248 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4249 double deg = rad2 * 180.0 / pi;
4251 return (int)(deg * base / 360.0 + 0.5) % base;
4254 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4256 // calculate start (source) position to be at the middle of the tile
4257 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4258 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4259 int dx = dst_mx - src_mx;
4260 int dy = dst_my - src_my;
4269 if (!IN_LEV_FIELD(x, y))
4272 element = Tile[x][y];
4274 if (!IS_MCDUFFIN(element) &&
4275 !IS_MIRROR(element) &&
4276 !IS_BEAMER(element) &&
4277 !IS_POLAR(element) &&
4278 !IS_POLAR_CROSS(element) &&
4279 !IS_DF_MIRROR(element))
4282 angle_old = get_element_angle(element);
4284 if (IS_MCDUFFIN(element))
4286 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4287 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4288 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4289 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4292 else if (IS_MIRROR(element) ||
4293 IS_DF_MIRROR(element))
4295 for (i = 0; i < laser.num_damages; i++)
4297 if (laser.damage[i].x == x &&
4298 laser.damage[i].y == y &&
4299 ObjHit(x, y, HIT_POS_CENTER))
4301 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4302 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4309 if (angle_new == -1)
4311 if (IS_MIRROR(element) ||
4312 IS_DF_MIRROR(element) ||
4316 if (IS_POLAR_CROSS(element))
4319 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4322 button = (angle_new == angle_old ? 0 :
4323 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4324 MB_LEFTBUTTON : MB_RIGHTBUTTON);