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);
101 static void AddLaserEdge(int, int);
102 static void ScanLaser(void);
103 static void DrawLaser(int, int);
104 static boolean HitElement(int, int);
105 static boolean HitOnlyAnEdge(int);
106 static boolean HitPolarizer(int, int);
107 static boolean HitBlock(int, int);
108 static boolean HitLaserSource(int, int);
109 static boolean HitLaserDestination(int, int);
110 static boolean HitReflectingWalls(int, int);
111 static boolean HitAbsorbingWalls(int, int);
112 static void RotateMirror(int, int, int);
113 static boolean ObjHit(int, int, int);
114 static void DeletePacMan(int, int);
115 static void MovePacMen(void);
117 // bitmap for laser beam detection
118 static Bitmap *laser_bitmap = NULL;
120 // variables for laser control
121 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
122 static int hold_x = -1, hold_y = -1;
124 // variables for pacman control
125 static int pacman_nr = -1;
127 // various game engine delay counters
128 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
129 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
130 static DelayCounter energy_delay = { ENERGY_DELAY };
131 static DelayCounter overload_delay = { 0 };
133 // element mask positions for scanning pixels of MM elements
134 #define MM_MASK_MCDUFFIN_RIGHT 0
135 #define MM_MASK_MCDUFFIN_UP 1
136 #define MM_MASK_MCDUFFIN_LEFT 2
137 #define MM_MASK_MCDUFFIN_DOWN 3
138 #define MM_MASK_GRID_1 4
139 #define MM_MASK_GRID_2 5
140 #define MM_MASK_GRID_3 6
141 #define MM_MASK_GRID_4 7
142 #define MM_MASK_GRID_CLOSED 8
143 #define MM_MASK_RECTANGLE 9
144 #define MM_MASK_CIRCLE 10
146 #define NUM_MM_MASKS 11
148 // element masks for scanning pixels of MM elements
149 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
351 static int get_element_angle(int element)
353 int element_phase = get_element_phase(element);
355 if (IS_MIRROR_FIXED(element) ||
356 IS_MCDUFFIN(element) ||
358 IS_RECEIVER(element))
359 return 4 * element_phase;
361 return element_phase;
364 static int get_opposite_angle(int angle)
366 int opposite_angle = angle + ANG_RAY_180;
368 // make sure "opposite_angle" is in valid interval [0, 15]
369 return (opposite_angle + 16) % 16;
372 static int get_mirrored_angle(int laser_angle, int mirror_angle)
374 int reflected_angle = 16 - laser_angle + mirror_angle;
376 // make sure "reflected_angle" is in valid interval [0, 15]
377 return (reflected_angle + 16) % 16;
380 static void DrawLaserLines(struct XY *points, int num_points, int mode)
382 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
383 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
385 DrawLines(drawto_mm, points, num_points, pixel_drawto);
389 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
394 static boolean CheckLaserPixel(int x, int y)
400 pixel = ReadPixel(laser_bitmap, x, y);
404 return (pixel == WHITE_PIXEL);
407 static void CheckExitMM(void)
409 int exit_element = EL_EMPTY;
413 static int xy[4][2] =
421 for (y = 0; y < lev_fieldy; y++)
423 for (x = 0; x < lev_fieldx; x++)
425 if (Tile[x][y] == EL_EXIT_CLOSED)
427 // initiate opening animation of exit door
428 Tile[x][y] = EL_EXIT_OPENING;
430 exit_element = EL_EXIT_OPEN;
434 else if (IS_RECEIVER(Tile[x][y]))
436 // remove field that blocks receiver
437 int phase = Tile[x][y] - EL_RECEIVER_START;
438 int blocking_x, blocking_y;
440 blocking_x = x + xy[phase][0];
441 blocking_y = y + xy[phase][1];
443 if (IN_LEV_FIELD(blocking_x, blocking_y))
445 Tile[blocking_x][blocking_y] = EL_EMPTY;
447 DrawField_MM(blocking_x, blocking_y);
450 exit_element = EL_RECEIVER;
457 if (exit_element != EL_EMPTY)
458 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
461 static void SetLaserColor(int brightness)
463 int color_min = 0x00;
464 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
465 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
466 int color_down = color_max - color_up;
469 GetPixelFromRGB(window,
470 (game_mm.laser_red ? color_max : color_up),
471 (game_mm.laser_green ? color_down : color_min),
472 (game_mm.laser_blue ? color_down : color_min));
475 static void InitMovDir_MM(int x, int y)
477 int element = Tile[x][y];
478 static int direction[3][4] =
480 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
481 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
482 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
487 case EL_PACMAN_RIGHT:
491 Tile[x][y] = EL_PACMAN;
492 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
500 static void InitField(int x, int y, boolean init_game)
502 int element = Tile[x][y];
507 Tile[x][y] = EL_EMPTY;
512 if (init_game && native_mm_level.auto_count_kettles)
513 game_mm.kettles_still_needed++;
516 case EL_LIGHTBULB_OFF:
517 game_mm.lights_still_needed++;
521 if (IS_MIRROR(element) ||
522 IS_BEAMER_OLD(element) ||
523 IS_BEAMER(element) ||
525 IS_POLAR_CROSS(element) ||
526 IS_DF_MIRROR(element) ||
527 IS_DF_MIRROR_AUTO(element) ||
528 IS_GRID_STEEL_AUTO(element) ||
529 IS_GRID_WOOD_AUTO(element) ||
530 IS_FIBRE_OPTIC(element))
532 if (IS_BEAMER_OLD(element))
534 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
535 element = Tile[x][y];
538 if (!IS_FIBRE_OPTIC(element))
540 static int steps_grid_auto = 0;
542 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
543 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
545 if (IS_GRID_STEEL_AUTO(element) ||
546 IS_GRID_WOOD_AUTO(element))
547 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
549 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
551 game_mm.cycle[game_mm.num_cycle].x = x;
552 game_mm.cycle[game_mm.num_cycle].y = y;
556 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
558 int beamer_nr = BEAMER_NR(element);
559 int nr = laser.beamer[beamer_nr][0].num;
561 laser.beamer[beamer_nr][nr].x = x;
562 laser.beamer[beamer_nr][nr].y = y;
563 laser.beamer[beamer_nr][nr].num = 1;
566 else if (IS_PACMAN(element))
570 else if (IS_MCDUFFIN(element) || IS_LASER(element))
574 laser.start_edge.x = x;
575 laser.start_edge.y = y;
576 laser.start_angle = get_element_angle(element);
579 if (IS_MCDUFFIN(element))
581 game_mm.laser_red = native_mm_level.mm_laser_red;
582 game_mm.laser_green = native_mm_level.mm_laser_green;
583 game_mm.laser_blue = native_mm_level.mm_laser_blue;
587 game_mm.laser_red = native_mm_level.df_laser_red;
588 game_mm.laser_green = native_mm_level.df_laser_green;
589 game_mm.laser_blue = native_mm_level.df_laser_blue;
592 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
599 static void InitCycleElements_RotateSingleStep(void)
603 if (game_mm.num_cycle == 0) // no elements to cycle
606 for (i = 0; i < game_mm.num_cycle; i++)
608 int x = game_mm.cycle[i].x;
609 int y = game_mm.cycle[i].y;
610 int step = SIGN(game_mm.cycle[i].steps);
611 int last_element = Tile[x][y];
612 int next_element = get_rotated_element(last_element, step);
614 if (!game_mm.cycle[i].steps)
617 Tile[x][y] = next_element;
619 game_mm.cycle[i].steps -= step;
623 static void InitLaser(void)
625 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
626 int step = (IS_LASER(start_element) ? 4 : 0);
628 LX = laser.start_edge.x * TILEX;
629 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
632 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
634 LY = laser.start_edge.y * TILEY;
635 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
636 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
640 XS = 2 * Step[laser.start_angle].x;
641 YS = 2 * Step[laser.start_angle].y;
643 laser.current_angle = laser.start_angle;
645 laser.num_damages = 0;
647 laser.num_beamers = 0;
648 laser.beamer_edge[0] = 0;
650 laser.dest_element = EL_EMPTY;
653 AddLaserEdge(LX, LY); // set laser starting edge
658 void InitGameEngine_MM(void)
664 // initialize laser bitmap to current playfield (screen) size
665 ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
666 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
670 // set global game control values
671 game_mm.num_cycle = 0;
672 game_mm.num_pacman = 0;
675 game_mm.energy_left = 0; // later set to "native_mm_level.time"
676 game_mm.kettles_still_needed =
677 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
678 game_mm.lights_still_needed = 0;
679 game_mm.num_keys = 0;
680 game_mm.ball_choice_pos = 0;
682 game_mm.laser_red = FALSE;
683 game_mm.laser_green = FALSE;
684 game_mm.laser_blue = TRUE;
685 game_mm.has_mcduffin = TRUE;
687 game_mm.level_solved = FALSE;
688 game_mm.game_over = FALSE;
689 game_mm.game_over_cause = 0;
690 game_mm.game_over_message = NULL;
692 game_mm.laser_overload_value = 0;
693 game_mm.laser_enabled = FALSE;
695 // set global laser control values (must be set before "InitLaser()")
696 laser.start_edge.x = 0;
697 laser.start_edge.y = 0;
698 laser.start_angle = 0;
700 for (i = 0; i < MAX_NUM_BEAMERS; i++)
701 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
703 laser.overloaded = FALSE;
704 laser.overload_value = 0;
705 laser.fuse_off = FALSE;
706 laser.fuse_x = laser.fuse_y = -1;
708 laser.dest_element = EL_EMPTY;
709 laser.dest_element_last = EL_EMPTY;
710 laser.dest_element_last_x = -1;
711 laser.dest_element_last_y = -1;
725 rotate_delay.count = 0;
726 pacman_delay.count = 0;
727 energy_delay.count = 0;
728 overload_delay.count = 0;
730 ClickElement(-1, -1, -1);
732 for (x = 0; x < lev_fieldx; x++)
734 for (y = 0; y < lev_fieldy; y++)
736 Tile[x][y] = Ur[x][y];
737 Hit[x][y] = Box[x][y] = 0;
739 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
740 Store[x][y] = Store2[x][y] = 0;
743 InitField(x, y, TRUE);
750 void InitGameActions_MM(void)
752 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
753 int cycle_steps_done = 0;
758 for (i = 0; i <= num_init_game_frames; i++)
760 if (i == num_init_game_frames)
761 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
762 else if (setup.sound_loops)
763 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
765 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
767 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
769 UpdateAndDisplayGameControlValues();
771 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
773 InitCycleElements_RotateSingleStep();
778 AdvanceFrameCounter();
786 if (setup.quick_doors)
793 if (game_mm.kettles_still_needed == 0)
796 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
797 SetTileCursorActive(TRUE);
799 // restart all delay counters after initially cycling game elements
800 ResetFrameCounter(&rotate_delay);
801 ResetFrameCounter(&pacman_delay);
802 ResetFrameCounter(&energy_delay);
803 ResetFrameCounter(&overload_delay);
806 static void FadeOutLaser(void)
810 for (i = 15; i >= 0; i--)
812 SetLaserColor(0x11 * i);
814 DrawLaser(0, DL_LASER_ENABLED);
817 Delay_WithScreenUpdates(50);
820 DrawLaser(0, DL_LASER_DISABLED);
822 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
825 static void GameOver_MM(int game_over_cause)
827 game_mm.game_over = TRUE;
828 game_mm.game_over_cause = game_over_cause;
829 game_mm.game_over_message = (game_mm.has_mcduffin ?
830 (game_over_cause == GAME_OVER_BOMB ?
831 "Bomb killed Mc Duffin!" :
832 game_over_cause == GAME_OVER_NO_ENERGY ?
833 "Out of magic energy!" :
834 game_over_cause == GAME_OVER_OVERLOADED ?
835 "Magic spell hit Mc Duffin!" :
837 (game_over_cause == GAME_OVER_BOMB ?
838 "Bomb destroyed laser cannon!" :
839 game_over_cause == GAME_OVER_NO_ENERGY ?
840 "Out of laser energy!" :
841 game_over_cause == GAME_OVER_OVERLOADED ?
842 "Laser beam hit laser cannon!" :
845 SetTileCursorActive(FALSE);
848 static void AddLaserEdge(int lx, int ly)
852 int sxsize = MAX(SXSIZE, lev_fieldx * TILEX);
853 int sysize = MAX(SYSIZE, lev_fieldy * TILEY);
855 if (clx < -2 || cly < -2 || clx >= sxsize + 2 || cly >= sysize + 2)
857 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
862 laser.edge[laser.num_edges].x = cSX2 + lx;
863 laser.edge[laser.num_edges].y = cSY2 + ly;
869 static void AddDamagedField(int ex, int ey)
871 // prevent adding the same field position again
872 if (laser.num_damages > 0 &&
873 laser.damage[laser.num_damages - 1].x == ex &&
874 laser.damage[laser.num_damages - 1].y == ey &&
875 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
878 laser.damage[laser.num_damages].is_mirror = FALSE;
879 laser.damage[laser.num_damages].angle = laser.current_angle;
880 laser.damage[laser.num_damages].edge = laser.num_edges;
881 laser.damage[laser.num_damages].x = ex;
882 laser.damage[laser.num_damages].y = ey;
886 static boolean StepBehind(void)
892 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
893 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
895 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
901 static int getMaskFromElement(int element)
903 if (IS_MCDUFFIN(element))
904 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
905 else if (IS_GRID(element))
906 return MM_MASK_GRID_1 + get_element_phase(element);
907 else if (IS_DF_GRID(element))
908 return MM_MASK_GRID_CLOSED;
909 else if (IS_RECTANGLE(element))
910 return MM_MASK_RECTANGLE;
912 return MM_MASK_CIRCLE;
915 static int ScanPixel(void)
920 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
921 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
924 // follow laser beam until it hits something (at least the screen border)
925 while (hit_mask == HIT_MASK_NO_HIT)
931 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
932 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
934 Debug("game:mm:ScanPixel", "touched screen border!");
940 // check if laser scan has crossed element boundaries (not just mini tiles)
941 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
942 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
944 if (cross_x && cross_y)
946 int elx1 = (LX - XS) / TILEX;
947 int ely1 = (LY + YS) / TILEY;
948 int elx2 = (LX + XS) / TILEX;
949 int ely2 = (LY - YS) / TILEY;
951 // add element corners left and right from the laser beam to damage list
953 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
954 AddDamagedField(elx1, ely1);
956 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
957 AddDamagedField(elx2, ely2);
960 for (i = 0; i < 4; i++)
962 int px = LX + (i % 2) * 2;
963 int py = LY + (i / 2) * 2;
966 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
967 int ly = (py + TILEY) / TILEY - 1; // negative values!
970 if (IN_LEV_FIELD(lx, ly))
972 int element = Tile[lx][ly];
974 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
978 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
980 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
982 pixel = ((element & (1 << pos)) ? 1 : 0);
986 int pos = getMaskFromElement(element);
988 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
993 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
994 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
997 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
998 hit_mask |= (1 << i);
1001 if (hit_mask == HIT_MASK_NO_HIT)
1003 // hit nothing -- go on with another step
1012 static void DeactivateLaserTargetElement(void)
1014 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1015 laser.dest_element_last == EL_MINE_ACTIVE ||
1016 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1017 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1019 int x = laser.dest_element_last_x;
1020 int y = laser.dest_element_last_y;
1021 int element = laser.dest_element_last;
1023 if (Tile[x][y] == element)
1024 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1025 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1027 if (Tile[x][y] == EL_GRAY_BALL)
1030 laser.dest_element_last = EL_EMPTY;
1031 laser.dest_element_last_x = -1;
1032 laser.dest_element_last_y = -1;
1036 static void ScanLaser(void)
1038 int element = EL_EMPTY;
1039 int last_element = EL_EMPTY;
1040 int end = 0, rf = laser.num_edges;
1042 // do not scan laser again after the game was lost for whatever reason
1043 if (game_mm.game_over)
1046 // do not scan laser if fuse is off
1050 DeactivateLaserTargetElement();
1052 laser.overloaded = FALSE;
1053 laser.stops_inside_element = FALSE;
1055 DrawLaser(0, DL_LASER_ENABLED);
1058 Debug("game:mm:ScanLaser",
1059 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1067 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1070 laser.overloaded = TRUE;
1075 hit_mask = ScanPixel();
1078 Debug("game:mm:ScanLaser",
1079 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1083 // hit something -- check out what it was
1084 ELX = (LX + XS + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
1085 ELY = (LY + YS + TILEY) / TILEY - 1; // negative values!
1088 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1089 hit_mask, LX, LY, ELX, ELY);
1092 if (!IN_LEV_FIELD(ELX, ELY))
1095 laser.dest_element = element;
1100 // check if laser scan has hit two diagonally adjacent element corners
1101 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1102 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1104 // check if laser scan has crossed element boundaries (not just mini tiles)
1105 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1106 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1108 // handle special case of laser hitting two diagonally adjacent elements
1109 // (with or without a third corner element behind these two elements)
1110 if ((diag_1 || diag_2) && cross_x && cross_y)
1112 // compare the two diagonally adjacent elements
1114 int yoffset = 2 * (diag_1 ? -1 : +1);
1115 int elx1 = (LX - xoffset) / TILEX;
1116 int ely1 = (LY + yoffset) / TILEY;
1117 int elx2 = (LX + xoffset) / TILEX;
1118 int ely2 = (LY - yoffset) / TILEY;
1119 int e1 = Tile[elx1][ely1];
1120 int e2 = Tile[elx2][ely2];
1121 boolean use_element_1 = FALSE;
1123 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1125 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1126 use_element_1 = (RND(2) ? TRUE : FALSE);
1127 else if (IS_WALL_ICE(e1))
1128 use_element_1 = TRUE;
1130 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1132 // if both tiles match, we can just select the first one
1133 if (IS_WALL_AMOEBA(e1))
1134 use_element_1 = TRUE;
1136 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1138 // if both tiles match, we can just select the first one
1139 if (IS_ABSORBING_BLOCK(e1))
1140 use_element_1 = TRUE;
1143 ELX = (use_element_1 ? elx1 : elx2);
1144 ELY = (use_element_1 ? ely1 : ely2);
1148 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1149 hit_mask, LX, LY, ELX, ELY);
1152 last_element = element;
1154 element = Tile[ELX][ELY];
1155 laser.dest_element = element;
1158 Debug("game:mm:ScanLaser",
1159 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1162 LX % TILEX, LY % TILEY,
1167 if (!IN_LEV_FIELD(ELX, ELY))
1168 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1172 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1173 if (element == EL_EMPTY &&
1174 IS_GRID_STEEL(last_element) &&
1175 laser.current_angle % 4) // angle is not 90°
1176 element = last_element;
1178 if (element == EL_EMPTY)
1180 if (!HitOnlyAnEdge(hit_mask))
1183 else if (element == EL_FUSE_ON)
1185 if (HitPolarizer(element, hit_mask))
1188 else if (IS_GRID(element) || IS_DF_GRID(element))
1190 if (HitPolarizer(element, hit_mask))
1193 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1194 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1196 if (HitBlock(element, hit_mask))
1203 else if (IS_MCDUFFIN(element))
1205 if (HitLaserSource(element, hit_mask))
1208 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1209 IS_RECEIVER(element))
1211 if (HitLaserDestination(element, hit_mask))
1214 else if (IS_WALL(element))
1216 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1218 if (HitReflectingWalls(element, hit_mask))
1223 if (HitAbsorbingWalls(element, hit_mask))
1229 if (HitElement(element, hit_mask))
1234 DrawLaser(rf - 1, DL_LASER_ENABLED);
1235 rf = laser.num_edges;
1237 if (!IS_DF_WALL_STEEL(element))
1239 // only used for scanning DF steel walls; reset for all other elements
1247 if (laser.dest_element != Tile[ELX][ELY])
1249 Debug("game:mm:ScanLaser",
1250 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1251 laser.dest_element, Tile[ELX][ELY]);
1255 if (!end && !laser.stops_inside_element && !StepBehind())
1258 Debug("game:mm:ScanLaser", "Go one step back");
1264 AddLaserEdge(LX, LY);
1268 DrawLaser(rf - 1, DL_LASER_ENABLED);
1270 Ct = CT = FrameCounter;
1273 if (!IN_LEV_FIELD(ELX, ELY))
1274 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1278 static void ScanLaser_FromLastMirror(void)
1280 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1283 for (i = start_pos; i >= 0; i--)
1284 if (laser.damage[i].is_mirror)
1287 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1289 DrawLaser(start_edge, DL_LASER_DISABLED);
1294 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1300 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1301 start_edge, num_edges, mode);
1306 Warn("DrawLaserExt: start_edge < 0");
1313 Warn("DrawLaserExt: num_edges < 0");
1319 if (mode == DL_LASER_DISABLED)
1321 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1325 // now draw the laser to the backbuffer and (if enabled) to the screen
1326 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1328 redraw_mask |= REDRAW_FIELD;
1330 if (mode == DL_LASER_ENABLED)
1333 // after the laser was deleted, the "damaged" graphics must be restored
1334 if (laser.num_damages)
1336 int damage_start = 0;
1339 // determine the starting edge, from which graphics need to be restored
1342 for (i = 0; i < laser.num_damages; i++)
1344 if (laser.damage[i].edge == start_edge + 1)
1353 // restore graphics from this starting edge to the end of damage list
1354 for (i = damage_start; i < laser.num_damages; i++)
1356 int lx = laser.damage[i].x;
1357 int ly = laser.damage[i].y;
1358 int element = Tile[lx][ly];
1360 if (Hit[lx][ly] == laser.damage[i].edge)
1361 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1364 if (Box[lx][ly] == laser.damage[i].edge)
1367 if (IS_DRAWABLE(element))
1368 DrawField_MM(lx, ly);
1371 elx = laser.damage[damage_start].x;
1372 ely = laser.damage[damage_start].y;
1373 element = Tile[elx][ely];
1376 if (IS_BEAMER(element))
1380 for (i = 0; i < laser.num_beamers; i++)
1381 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1383 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1384 mode, elx, ely, Hit[elx][ely], start_edge);
1385 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1386 get_element_angle(element), laser.damage[damage_start].angle);
1390 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1391 laser.num_beamers > 0 &&
1392 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1394 // element is outgoing beamer
1395 laser.num_damages = damage_start + 1;
1397 if (IS_BEAMER(element))
1398 laser.current_angle = get_element_angle(element);
1402 // element is incoming beamer or other element
1403 laser.num_damages = damage_start;
1404 laser.current_angle = laser.damage[laser.num_damages].angle;
1409 // no damages but McDuffin himself (who needs to be redrawn anyway)
1411 elx = laser.start_edge.x;
1412 ely = laser.start_edge.y;
1413 element = Tile[elx][ely];
1416 laser.num_edges = start_edge + 1;
1417 if (start_edge == 0)
1418 laser.current_angle = laser.start_angle;
1420 LX = laser.edge[start_edge].x - cSX2;
1421 LY = laser.edge[start_edge].y - cSY2;
1422 XS = 2 * Step[laser.current_angle].x;
1423 YS = 2 * Step[laser.current_angle].y;
1426 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1432 if (IS_BEAMER(element) ||
1433 IS_FIBRE_OPTIC(element) ||
1434 IS_PACMAN(element) ||
1435 IS_POLAR(element) ||
1436 IS_POLAR_CROSS(element) ||
1437 element == EL_FUSE_ON)
1442 Debug("game:mm:DrawLaserExt", "element == %d", element);
1445 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1446 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1450 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1451 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1452 (laser.num_beamers == 0 ||
1453 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1455 // element is incoming beamer or other element
1456 step_size = -step_size;
1461 if (IS_BEAMER(element))
1462 Debug("game:mm:DrawLaserExt",
1463 "start_edge == %d, laser.beamer_edge == %d",
1464 start_edge, laser.beamer_edge);
1467 LX += step_size * XS;
1468 LY += step_size * YS;
1470 else if (element != EL_EMPTY)
1479 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1484 void DrawLaser(int start_edge, int mode)
1486 // do not draw laser if fuse is off
1487 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1490 if (mode == DL_LASER_DISABLED)
1491 DeactivateLaserTargetElement();
1493 if (laser.num_edges - start_edge < 0)
1495 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1500 // check if laser is interrupted by beamer element
1501 if (laser.num_beamers > 0 &&
1502 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1504 if (mode == DL_LASER_ENABLED)
1507 int tmp_start_edge = start_edge;
1509 // draw laser segments forward from the start to the last beamer
1510 for (i = 0; i < laser.num_beamers; i++)
1512 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1514 if (tmp_num_edges <= 0)
1518 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1519 i, laser.beamer_edge[i], tmp_start_edge);
1522 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1524 tmp_start_edge = laser.beamer_edge[i];
1527 // draw last segment from last beamer to the end
1528 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1534 int last_num_edges = laser.num_edges;
1535 int num_beamers = laser.num_beamers;
1537 // delete laser segments backward from the end to the first beamer
1538 for (i = num_beamers - 1; i >= 0; i--)
1540 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1542 if (laser.beamer_edge[i] - start_edge <= 0)
1545 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1547 last_num_edges = laser.beamer_edge[i];
1548 laser.num_beamers--;
1552 if (last_num_edges - start_edge <= 0)
1553 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1554 last_num_edges, start_edge);
1557 // special case when rotating first beamer: delete laser edge on beamer
1558 // (but do not start scanning on previous edge to prevent mirror sound)
1559 if (last_num_edges - start_edge == 1 && start_edge > 0)
1560 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1562 // delete first segment from start to the first beamer
1563 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1568 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1571 game_mm.laser_enabled = mode;
1574 void DrawLaser_MM(void)
1576 DrawLaser(0, game_mm.laser_enabled);
1579 static boolean HitElement(int element, int hit_mask)
1581 if (HitOnlyAnEdge(hit_mask))
1584 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1585 element = MovingOrBlocked2Element_MM(ELX, ELY);
1588 Debug("game:mm:HitElement", "(1): element == %d", element);
1592 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1593 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1596 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1600 AddDamagedField(ELX, ELY);
1602 // this is more precise: check if laser would go through the center
1603 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1607 // prevent cutting through laser emitter with laser beam
1608 if (IS_LASER(element))
1611 // skip the whole element before continuing the scan
1619 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1621 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1623 /* skipping scan positions to the right and down skips one scan
1624 position too much, because this is only the top left scan position
1625 of totally four scan positions (plus one to the right, one to the
1626 bottom and one to the bottom right) */
1627 /* ... but only roll back scan position if more than one step done */
1637 Debug("game:mm:HitElement", "(2): element == %d", element);
1640 if (LX + 5 * XS < 0 ||
1650 Debug("game:mm:HitElement", "(3): element == %d", element);
1653 if (IS_POLAR(element) &&
1654 ((element - EL_POLAR_START) % 2 ||
1655 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1657 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1659 laser.num_damages--;
1664 if (IS_POLAR_CROSS(element) &&
1665 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1667 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1669 laser.num_damages--;
1674 if (!IS_BEAMER(element) &&
1675 !IS_FIBRE_OPTIC(element) &&
1676 !IS_GRID_WOOD(element) &&
1677 element != EL_FUEL_EMPTY)
1680 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1681 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1683 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1686 LX = ELX * TILEX + 14;
1687 LY = ELY * TILEY + 14;
1689 AddLaserEdge(LX, LY);
1692 if (IS_MIRROR(element) ||
1693 IS_MIRROR_FIXED(element) ||
1694 IS_POLAR(element) ||
1695 IS_POLAR_CROSS(element) ||
1696 IS_DF_MIRROR(element) ||
1697 IS_DF_MIRROR_AUTO(element) ||
1698 element == EL_PRISM ||
1699 element == EL_REFRACTOR)
1701 int current_angle = laser.current_angle;
1704 laser.num_damages--;
1706 AddDamagedField(ELX, ELY);
1708 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1711 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1713 if (IS_MIRROR(element) ||
1714 IS_MIRROR_FIXED(element) ||
1715 IS_DF_MIRROR(element) ||
1716 IS_DF_MIRROR_AUTO(element))
1717 laser.current_angle = get_mirrored_angle(laser.current_angle,
1718 get_element_angle(element));
1720 if (element == EL_PRISM || element == EL_REFRACTOR)
1721 laser.current_angle = RND(16);
1723 XS = 2 * Step[laser.current_angle].x;
1724 YS = 2 * Step[laser.current_angle].y;
1726 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1731 LX += step_size * XS;
1732 LY += step_size * YS;
1734 // draw sparkles on mirror
1735 if ((IS_MIRROR(element) ||
1736 IS_MIRROR_FIXED(element) ||
1737 element == EL_PRISM) &&
1738 current_angle != laser.current_angle)
1740 MovDelay[ELX][ELY] = 11; // start animation
1743 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1744 current_angle != laser.current_angle)
1745 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1748 (get_opposite_angle(laser.current_angle) ==
1749 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1751 return (laser.overloaded ? TRUE : FALSE);
1754 if (element == EL_FUEL_FULL)
1756 laser.stops_inside_element = TRUE;
1761 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1763 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1765 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1766 element == EL_MINE ? EL_MINE_ACTIVE :
1767 EL_GRAY_BALL_ACTIVE);
1769 GfxFrame[ELX][ELY] = 0; // restart animation
1771 laser.dest_element_last = Tile[ELX][ELY];
1772 laser.dest_element_last_x = ELX;
1773 laser.dest_element_last_y = ELY;
1775 if (element == EL_MINE)
1776 laser.overloaded = TRUE;
1779 if (element == EL_KETTLE ||
1780 element == EL_CELL ||
1781 element == EL_KEY ||
1782 element == EL_LIGHTBALL ||
1783 element == EL_PACMAN ||
1784 IS_PACMAN(element) ||
1785 IS_ENVELOPE(element))
1787 if (!IS_PACMAN(element) &&
1788 !IS_ENVELOPE(element))
1791 if (element == EL_PACMAN)
1794 if (element == EL_KETTLE || element == EL_CELL)
1796 if (game_mm.kettles_still_needed > 0)
1797 game_mm.kettles_still_needed--;
1799 game.snapshot.collected_item = TRUE;
1801 if (game_mm.kettles_still_needed == 0)
1805 DrawLaser(0, DL_LASER_ENABLED);
1808 else if (element == EL_KEY)
1812 else if (IS_PACMAN(element))
1814 DeletePacMan(ELX, ELY);
1816 else if (IS_ENVELOPE(element))
1818 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1821 RaiseScoreElement_MM(element);
1826 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1828 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1830 DrawLaser(0, DL_LASER_ENABLED);
1832 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1834 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1835 game_mm.lights_still_needed--;
1839 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1840 game_mm.lights_still_needed++;
1843 DrawField_MM(ELX, ELY);
1844 DrawLaser(0, DL_LASER_ENABLED);
1849 laser.stops_inside_element = TRUE;
1855 Debug("game:mm:HitElement", "(4): element == %d", element);
1858 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1859 laser.num_beamers < MAX_NUM_BEAMERS &&
1860 laser.beamer[BEAMER_NR(element)][1].num)
1862 int beamer_angle = get_element_angle(element);
1863 int beamer_nr = BEAMER_NR(element);
1867 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1870 laser.num_damages--;
1872 if (IS_FIBRE_OPTIC(element) ||
1873 laser.current_angle == get_opposite_angle(beamer_angle))
1877 LX = ELX * TILEX + 14;
1878 LY = ELY * TILEY + 14;
1880 AddLaserEdge(LX, LY);
1881 AddDamagedField(ELX, ELY);
1883 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1886 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1888 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1889 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1890 ELX = laser.beamer[beamer_nr][pos].x;
1891 ELY = laser.beamer[beamer_nr][pos].y;
1892 LX = ELX * TILEX + 14;
1893 LY = ELY * TILEY + 14;
1895 if (IS_BEAMER(element))
1897 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1898 XS = 2 * Step[laser.current_angle].x;
1899 YS = 2 * Step[laser.current_angle].y;
1902 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1904 AddLaserEdge(LX, LY);
1905 AddDamagedField(ELX, ELY);
1907 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1910 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1912 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1917 LX += step_size * XS;
1918 LY += step_size * YS;
1920 laser.num_beamers++;
1929 static boolean HitOnlyAnEdge(int hit_mask)
1931 // check if the laser hit only the edge of an element and, if so, go on
1934 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1938 if ((hit_mask == HIT_MASK_TOPLEFT ||
1939 hit_mask == HIT_MASK_TOPRIGHT ||
1940 hit_mask == HIT_MASK_BOTTOMLEFT ||
1941 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1942 laser.current_angle % 4) // angle is not 90°
1946 if (hit_mask == HIT_MASK_TOPLEFT)
1951 else if (hit_mask == HIT_MASK_TOPRIGHT)
1956 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1961 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1967 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1973 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1980 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1986 static boolean HitPolarizer(int element, int hit_mask)
1988 if (HitOnlyAnEdge(hit_mask))
1991 if (IS_DF_GRID(element))
1993 int grid_angle = get_element_angle(element);
1996 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1997 grid_angle, laser.current_angle);
2000 AddLaserEdge(LX, LY);
2001 AddDamagedField(ELX, ELY);
2004 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2006 if (laser.current_angle == grid_angle ||
2007 laser.current_angle == get_opposite_angle(grid_angle))
2009 // skip the whole element before continuing the scan
2015 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2017 if (LX/TILEX > ELX || LY/TILEY > ELY)
2019 /* skipping scan positions to the right and down skips one scan
2020 position too much, because this is only the top left scan position
2021 of totally four scan positions (plus one to the right, one to the
2022 bottom and one to the bottom right) */
2028 AddLaserEdge(LX, LY);
2034 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2036 LX / TILEX, LY / TILEY,
2037 LX % TILEX, LY % TILEY);
2042 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2044 return HitReflectingWalls(element, hit_mask);
2048 return HitAbsorbingWalls(element, hit_mask);
2051 else if (IS_GRID_STEEL(element))
2053 // may be required if graphics for steel grid redefined
2054 AddDamagedField(ELX, ELY);
2056 return HitReflectingWalls(element, hit_mask);
2058 else // IS_GRID_WOOD
2060 // may be required if graphics for wooden grid redefined
2061 AddDamagedField(ELX, ELY);
2063 return HitAbsorbingWalls(element, hit_mask);
2069 static boolean HitBlock(int element, int hit_mask)
2071 boolean check = FALSE;
2073 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2074 game_mm.num_keys == 0)
2077 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2080 int ex = ELX * TILEX + 14;
2081 int ey = ELY * TILEY + 14;
2085 for (i = 1; i < 32; i++)
2090 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2095 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2096 return HitAbsorbingWalls(element, hit_mask);
2100 AddLaserEdge(LX - XS, LY - YS);
2101 AddDamagedField(ELX, ELY);
2104 Box[ELX][ELY] = laser.num_edges;
2106 return HitReflectingWalls(element, hit_mask);
2109 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2111 int xs = XS / 2, ys = YS / 2;
2113 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2114 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2116 laser.overloaded = (element == EL_GATE_STONE);
2121 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2122 (hit_mask == HIT_MASK_TOP ||
2123 hit_mask == HIT_MASK_LEFT ||
2124 hit_mask == HIT_MASK_RIGHT ||
2125 hit_mask == HIT_MASK_BOTTOM))
2126 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2127 hit_mask == HIT_MASK_BOTTOM),
2128 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2129 hit_mask == HIT_MASK_RIGHT));
2130 AddLaserEdge(LX, LY);
2136 if (element == EL_GATE_STONE && Box[ELX][ELY])
2138 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2150 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2152 int xs = XS / 2, ys = YS / 2;
2154 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2155 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2157 laser.overloaded = (element == EL_BLOCK_STONE);
2162 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2163 (hit_mask == HIT_MASK_TOP ||
2164 hit_mask == HIT_MASK_LEFT ||
2165 hit_mask == HIT_MASK_RIGHT ||
2166 hit_mask == HIT_MASK_BOTTOM))
2167 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2168 hit_mask == HIT_MASK_BOTTOM),
2169 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2170 hit_mask == HIT_MASK_RIGHT));
2171 AddDamagedField(ELX, ELY);
2173 LX = ELX * TILEX + 14;
2174 LY = ELY * TILEY + 14;
2176 AddLaserEdge(LX, LY);
2178 laser.stops_inside_element = TRUE;
2186 static boolean HitLaserSource(int element, int hit_mask)
2188 if (HitOnlyAnEdge(hit_mask))
2191 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2193 laser.overloaded = TRUE;
2198 static boolean HitLaserDestination(int element, int hit_mask)
2200 if (HitOnlyAnEdge(hit_mask))
2203 if (element != EL_EXIT_OPEN &&
2204 !(IS_RECEIVER(element) &&
2205 game_mm.kettles_still_needed == 0 &&
2206 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2208 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2213 if (IS_RECEIVER(element) ||
2214 (IS_22_5_ANGLE(laser.current_angle) &&
2215 (ELX != (LX + 6 * XS) / TILEX ||
2216 ELY != (LY + 6 * YS) / TILEY ||
2225 LX = ELX * TILEX + 14;
2226 LY = ELY * TILEY + 14;
2228 laser.stops_inside_element = TRUE;
2231 AddLaserEdge(LX, LY);
2232 AddDamagedField(ELX, ELY);
2234 if (game_mm.lights_still_needed == 0)
2236 game_mm.level_solved = TRUE;
2238 SetTileCursorActive(FALSE);
2244 static boolean HitReflectingWalls(int element, int hit_mask)
2246 // check if laser hits side of a wall with an angle that is not 90°
2247 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2248 hit_mask == HIT_MASK_LEFT ||
2249 hit_mask == HIT_MASK_RIGHT ||
2250 hit_mask == HIT_MASK_BOTTOM))
2252 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2257 if (!IS_DF_GRID(element))
2258 AddLaserEdge(LX, LY);
2260 // check if laser hits wall with an angle of 45°
2261 if (!IS_22_5_ANGLE(laser.current_angle))
2263 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2266 laser.current_angle = get_mirrored_angle(laser.current_angle,
2269 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2272 laser.current_angle = get_mirrored_angle(laser.current_angle,
2276 AddLaserEdge(LX, LY);
2278 XS = 2 * Step[laser.current_angle].x;
2279 YS = 2 * Step[laser.current_angle].y;
2283 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2285 laser.current_angle = get_mirrored_angle(laser.current_angle,
2290 if (!IS_DF_GRID(element))
2291 AddLaserEdge(LX, LY);
2296 if (!IS_DF_GRID(element))
2297 AddLaserEdge(LX, LY + YS / 2);
2300 if (!IS_DF_GRID(element))
2301 AddLaserEdge(LX, LY);
2304 YS = 2 * Step[laser.current_angle].y;
2308 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2310 laser.current_angle = get_mirrored_angle(laser.current_angle,
2315 if (!IS_DF_GRID(element))
2316 AddLaserEdge(LX, LY);
2321 if (!IS_DF_GRID(element))
2322 AddLaserEdge(LX + XS / 2, LY);
2325 if (!IS_DF_GRID(element))
2326 AddLaserEdge(LX, LY);
2329 XS = 2 * Step[laser.current_angle].x;
2335 // reflection at the edge of reflecting DF style wall
2336 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2338 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2339 hit_mask == HIT_MASK_TOPRIGHT) ||
2340 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2341 hit_mask == HIT_MASK_TOPLEFT) ||
2342 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2343 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2344 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2345 hit_mask == HIT_MASK_BOTTOMRIGHT))
2348 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2349 ANG_MIRROR_135 : ANG_MIRROR_45);
2351 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2353 AddDamagedField(ELX, ELY);
2354 AddLaserEdge(LX, LY);
2356 laser.current_angle = get_mirrored_angle(laser.current_angle,
2364 AddLaserEdge(LX, LY);
2370 // reflection inside an edge of reflecting DF style wall
2371 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2373 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2374 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2375 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2376 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2377 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2378 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2379 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2380 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2383 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2384 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2385 ANG_MIRROR_135 : ANG_MIRROR_45);
2387 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2390 AddDamagedField(ELX, ELY);
2393 AddLaserEdge(LX - XS, LY - YS);
2394 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2395 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2397 laser.current_angle = get_mirrored_angle(laser.current_angle,
2405 AddLaserEdge(LX, LY);
2411 // check if laser hits DF style wall with an angle of 90°
2412 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2414 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2415 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2416 (IS_VERT_ANGLE(laser.current_angle) &&
2417 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2419 // laser at last step touched nothing or the same side of the wall
2420 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2422 AddDamagedField(ELX, ELY);
2429 last_hit_mask = hit_mask;
2436 if (!HitOnlyAnEdge(hit_mask))
2438 laser.overloaded = TRUE;
2446 static boolean HitAbsorbingWalls(int element, int hit_mask)
2448 if (HitOnlyAnEdge(hit_mask))
2452 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2454 AddLaserEdge(LX - XS, LY - YS);
2461 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2463 AddLaserEdge(LX - XS, LY - YS);
2469 if (IS_WALL_WOOD(element) ||
2470 IS_DF_WALL_WOOD(element) ||
2471 IS_GRID_WOOD(element) ||
2472 IS_GRID_WOOD_FIXED(element) ||
2473 IS_GRID_WOOD_AUTO(element) ||
2474 element == EL_FUSE_ON ||
2475 element == EL_BLOCK_WOOD ||
2476 element == EL_GATE_WOOD)
2478 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2483 if (IS_WALL_ICE(element))
2489 // check if laser hit adjacent edges of two diagonal tiles
2490 if (ELX != lx / TILEX)
2492 if (ELY != ly / TILEY)
2495 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2496 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2498 // check if laser hits wall with an angle of 90°
2499 if (IS_90_ANGLE(laser.current_angle))
2500 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2502 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2506 for (i = 0; i < 4; i++)
2508 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2509 mask = 15 - (8 >> i);
2510 else if (ABS(XS) == 4 &&
2512 (XS > 0) == (i % 2) &&
2513 (YS < 0) == (i / 2))
2514 mask = 3 + (i / 2) * 9;
2515 else if (ABS(YS) == 4 &&
2517 (XS < 0) == (i % 2) &&
2518 (YS > 0) == (i / 2))
2519 mask = 5 + (i % 2) * 5;
2523 laser.wall_mask = mask;
2525 else if (IS_WALL_AMOEBA(element))
2527 int elx = (LX - 2 * XS) / TILEX;
2528 int ely = (LY - 2 * YS) / TILEY;
2529 int element2 = Tile[elx][ely];
2532 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2534 laser.dest_element = EL_EMPTY;
2542 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2543 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2545 if (IS_90_ANGLE(laser.current_angle))
2546 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2548 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2550 laser.wall_mask = mask;
2556 static void OpenExit(int x, int y)
2560 if (!MovDelay[x][y]) // next animation frame
2561 MovDelay[x][y] = 4 * delay;
2563 if (MovDelay[x][y]) // wait some time before next frame
2568 phase = MovDelay[x][y] / delay;
2570 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2571 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2573 if (!MovDelay[x][y])
2575 Tile[x][y] = EL_EXIT_OPEN;
2581 static void OpenGrayBall(int x, int y)
2585 if (!MovDelay[x][y]) // next animation frame
2587 if (IS_WALL(Store[x][y]))
2589 DrawWalls_MM(x, y, Store[x][y]);
2591 // copy wall tile to spare bitmap for "melting" animation
2592 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2593 TILEX, TILEY, x * TILEX, y * TILEY);
2595 DrawElement_MM(x, y, EL_GRAY_BALL);
2598 MovDelay[x][y] = 50 * delay;
2601 if (MovDelay[x][y]) // wait some time before next frame
2605 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2609 int dx = RND(26), dy = RND(26);
2611 if (IS_WALL(Store[x][y]))
2613 // copy wall tile from spare bitmap for "melting" animation
2614 bitmap = bitmap_db_field;
2620 int graphic = el2gfx(Store[x][y]);
2622 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2625 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2626 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2628 laser.redraw = TRUE;
2630 MarkTileDirty(x, y);
2633 if (!MovDelay[x][y])
2635 Tile[x][y] = Store[x][y];
2636 Store[x][y] = Store2[x][y] = 0;
2637 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2639 InitField(x, y, FALSE);
2642 ScanLaser_FromLastMirror();
2647 static void OpenEnvelope(int x, int y)
2649 int num_frames = 8; // seven frames plus final empty space
2651 if (!MovDelay[x][y]) // next animation frame
2652 MovDelay[x][y] = num_frames;
2654 if (MovDelay[x][y]) // wait some time before next frame
2656 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2660 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2662 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2663 int frame = num_frames - MovDelay[x][y] - 1;
2665 DrawGraphicAnimation_MM(x, y, graphic, frame);
2667 laser.redraw = TRUE;
2670 if (MovDelay[x][y] == 0)
2672 Tile[x][y] = EL_EMPTY;
2683 static void MeltIce(int x, int y)
2688 if (!MovDelay[x][y]) // next animation frame
2689 MovDelay[x][y] = frames * delay;
2691 if (MovDelay[x][y]) // wait some time before next frame
2694 int wall_mask = Store2[x][y];
2695 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2698 phase = frames - MovDelay[x][y] / delay - 1;
2700 if (!MovDelay[x][y])
2702 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2703 Store[x][y] = Store2[x][y] = 0;
2705 DrawWalls_MM(x, y, Tile[x][y]);
2707 if (Tile[x][y] == EL_WALL_ICE_BASE)
2708 Tile[x][y] = EL_EMPTY;
2710 ScanLaser_FromLastMirror();
2712 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2714 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2716 laser.redraw = TRUE;
2721 static void GrowAmoeba(int x, int y)
2726 if (!MovDelay[x][y]) // next animation frame
2727 MovDelay[x][y] = frames * delay;
2729 if (MovDelay[x][y]) // wait some time before next frame
2732 int wall_mask = Store2[x][y];
2733 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2736 phase = MovDelay[x][y] / delay;
2738 if (!MovDelay[x][y])
2740 Tile[x][y] = real_element;
2741 Store[x][y] = Store2[x][y] = 0;
2743 DrawWalls_MM(x, y, Tile[x][y]);
2744 DrawLaser(0, DL_LASER_ENABLED);
2746 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2748 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2753 static void DrawFieldAnimated_MM(int x, int y)
2757 laser.redraw = TRUE;
2760 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2762 int element = Tile[x][y];
2763 int graphic = el2gfx(element);
2765 if (!getGraphicInfo_NewFrame(x, y, graphic))
2770 laser.redraw = TRUE;
2773 static void DrawFieldTwinkle(int x, int y)
2775 if (MovDelay[x][y] != 0) // wait some time before next frame
2781 if (MovDelay[x][y] != 0)
2783 int graphic = IMG_TWINKLE_WHITE;
2784 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2786 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2789 laser.redraw = TRUE;
2793 static void Explode_MM(int x, int y, int phase, int mode)
2795 int num_phase = 9, delay = 2;
2796 int last_phase = num_phase * delay;
2797 int half_phase = (num_phase / 2) * delay;
2800 laser.redraw = TRUE;
2802 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2804 center_element = Tile[x][y];
2806 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2808 // put moving element to center field (and let it explode there)
2809 center_element = MovingOrBlocked2Element_MM(x, y);
2810 RemoveMovingField_MM(x, y);
2812 Tile[x][y] = center_element;
2815 if (center_element != EL_GRAY_BALL_ACTIVE)
2816 Store[x][y] = EL_EMPTY;
2817 Store2[x][y] = center_element;
2819 Tile[x][y] = EL_EXPLODING_OPAQUE;
2821 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2822 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2823 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2826 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2828 ExplodePhase[x][y] = 1;
2834 GfxFrame[x][y] = 0; // restart explosion animation
2836 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2838 center_element = Store2[x][y];
2840 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2842 Tile[x][y] = EL_EXPLODING_TRANSP;
2844 if (x == ELX && y == ELY)
2848 if (phase == last_phase)
2850 if (center_element == EL_BOMB_ACTIVE)
2852 DrawLaser(0, DL_LASER_DISABLED);
2855 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2857 laser.overloaded = FALSE;
2859 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2861 GameOver_MM(GAME_OVER_BOMB);
2864 Tile[x][y] = Store[x][y];
2866 Store[x][y] = Store2[x][y] = 0;
2867 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2869 InitField(x, y, FALSE);
2872 if (center_element == EL_GRAY_BALL_ACTIVE)
2873 ScanLaser_FromLastMirror();
2875 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2877 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2878 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2880 DrawGraphicAnimation_MM(x, y, graphic, frame);
2882 MarkTileDirty(x, y);
2886 static void Bang_MM(int x, int y)
2888 int element = Tile[x][y];
2890 if (IS_PACMAN(element))
2891 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2892 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2893 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2894 else if (element == EL_KEY)
2895 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2897 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2899 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2902 static void TurnRound(int x, int y)
2914 { 0, 0 }, { 0, 0 }, { 0, 0 },
2919 int left, right, back;
2923 { MV_DOWN, MV_UP, MV_RIGHT },
2924 { MV_UP, MV_DOWN, MV_LEFT },
2926 { MV_LEFT, MV_RIGHT, MV_DOWN },
2930 { MV_RIGHT, MV_LEFT, MV_UP }
2933 int element = Tile[x][y];
2934 int old_move_dir = MovDir[x][y];
2935 int right_dir = turn[old_move_dir].right;
2936 int back_dir = turn[old_move_dir].back;
2937 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2938 int right_x = x + right_dx, right_y = y + right_dy;
2940 if (element == EL_PACMAN)
2942 boolean can_turn_right = FALSE;
2944 if (IN_LEV_FIELD(right_x, right_y) &&
2945 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2946 can_turn_right = TRUE;
2949 MovDir[x][y] = right_dir;
2951 MovDir[x][y] = back_dir;
2957 static void StartMoving_MM(int x, int y)
2959 int element = Tile[x][y];
2964 if (CAN_MOVE(element))
2968 if (MovDelay[x][y]) // wait some time before next movement
2976 // now make next step
2978 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2980 if (element == EL_PACMAN &&
2981 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2982 !ObjHit(newx, newy, HIT_POS_CENTER))
2984 Store[newx][newy] = Tile[newx][newy];
2985 Tile[newx][newy] = EL_EMPTY;
2987 DrawField_MM(newx, newy);
2989 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2990 ObjHit(newx, newy, HIT_POS_CENTER))
2992 // object was running against a wall
2999 InitMovingField_MM(x, y, MovDir[x][y]);
3003 ContinueMoving_MM(x, y);
3006 static void ContinueMoving_MM(int x, int y)
3008 int element = Tile[x][y];
3009 int direction = MovDir[x][y];
3010 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3011 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3012 int horiz_move = (dx!=0);
3013 int newx = x + dx, newy = y + dy;
3014 int step = (horiz_move ? dx : dy) * TILEX / 8;
3016 MovPos[x][y] += step;
3018 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3020 Tile[x][y] = EL_EMPTY;
3021 Tile[newx][newy] = element;
3023 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3024 MovDelay[newx][newy] = 0;
3026 if (!CAN_MOVE(element))
3027 MovDir[newx][newy] = 0;
3030 DrawField_MM(newx, newy);
3032 Stop[newx][newy] = TRUE;
3034 if (element == EL_PACMAN)
3036 if (Store[newx][newy] == EL_BOMB)
3037 Bang_MM(newx, newy);
3039 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3040 (LX + 2 * XS) / TILEX == newx &&
3041 (LY + 2 * YS) / TILEY == newy)
3048 else // still moving on
3053 laser.redraw = TRUE;
3056 boolean ClickElement(int x, int y, int button)
3058 static DelayCounter click_delay = { CLICK_DELAY };
3059 static boolean new_button = TRUE;
3060 boolean element_clicked = FALSE;
3065 // initialize static variables
3066 click_delay.count = 0;
3067 click_delay.value = CLICK_DELAY;
3073 // do not rotate objects hit by the laser after the game was solved
3074 if (game_mm.level_solved && Hit[x][y])
3077 if (button == MB_RELEASED)
3080 click_delay.value = CLICK_DELAY;
3082 // release eventually hold auto-rotating mirror
3083 RotateMirror(x, y, MB_RELEASED);
3088 if (!FrameReached(&click_delay) && !new_button)
3091 if (button == MB_MIDDLEBUTTON) // middle button has no function
3094 if (!IN_LEV_FIELD(x, y))
3097 if (Tile[x][y] == EL_EMPTY)
3100 element = Tile[x][y];
3102 if (IS_MIRROR(element) ||
3103 IS_BEAMER(element) ||
3104 IS_POLAR(element) ||
3105 IS_POLAR_CROSS(element) ||
3106 IS_DF_MIRROR(element) ||
3107 IS_DF_MIRROR_AUTO(element))
3109 RotateMirror(x, y, button);
3111 element_clicked = TRUE;
3113 else if (IS_MCDUFFIN(element))
3115 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3117 if (has_laser && !laser.fuse_off)
3118 DrawLaser(0, DL_LASER_DISABLED);
3120 element = get_rotated_element(element, BUTTON_ROTATION(button));
3122 Tile[x][y] = element;
3127 laser.start_angle = get_element_angle(element);
3131 if (!laser.fuse_off)
3135 element_clicked = TRUE;
3137 else if (element == EL_FUSE_ON && laser.fuse_off)
3139 if (x != laser.fuse_x || y != laser.fuse_y)
3142 laser.fuse_off = FALSE;
3143 laser.fuse_x = laser.fuse_y = -1;
3145 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3148 element_clicked = TRUE;
3150 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3152 laser.fuse_off = TRUE;
3155 laser.overloaded = FALSE;
3157 DrawLaser(0, DL_LASER_DISABLED);
3158 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3160 element_clicked = TRUE;
3162 else if (element == EL_LIGHTBALL)
3165 RaiseScoreElement_MM(element);
3166 DrawLaser(0, DL_LASER_ENABLED);
3168 element_clicked = TRUE;
3170 else if (IS_ENVELOPE(element))
3172 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3174 element_clicked = TRUE;
3177 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3180 return element_clicked;
3183 static void RotateMirror(int x, int y, int button)
3185 if (button == MB_RELEASED)
3187 // release eventually hold auto-rotating mirror
3194 if (IS_MIRROR(Tile[x][y]) ||
3195 IS_POLAR_CROSS(Tile[x][y]) ||
3196 IS_POLAR(Tile[x][y]) ||
3197 IS_BEAMER(Tile[x][y]) ||
3198 IS_DF_MIRROR(Tile[x][y]) ||
3199 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3200 IS_GRID_WOOD_AUTO(Tile[x][y]))
3202 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3204 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3206 if (button == MB_LEFTBUTTON)
3208 // left mouse button only for manual adjustment, no auto-rotating;
3209 // freeze mirror for until mouse button released
3213 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3215 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3219 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3221 int edge = Hit[x][y];
3227 DrawLaser(edge - 1, DL_LASER_DISABLED);
3231 else if (ObjHit(x, y, HIT_POS_CENTER))
3233 int edge = Hit[x][y];
3237 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3242 DrawLaser(edge - 1, DL_LASER_DISABLED);
3249 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3254 if ((IS_BEAMER(Tile[x][y]) ||
3255 IS_POLAR(Tile[x][y]) ||
3256 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3258 if (IS_BEAMER(Tile[x][y]))
3261 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3262 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3275 DrawLaser(0, DL_LASER_ENABLED);
3279 static void AutoRotateMirrors(void)
3283 if (!FrameReached(&rotate_delay))
3286 for (x = 0; x < lev_fieldx; x++)
3288 for (y = 0; y < lev_fieldy; y++)
3290 int element = Tile[x][y];
3292 // do not rotate objects hit by the laser after the game was solved
3293 if (game_mm.level_solved && Hit[x][y])
3296 if (IS_DF_MIRROR_AUTO(element) ||
3297 IS_GRID_WOOD_AUTO(element) ||
3298 IS_GRID_STEEL_AUTO(element) ||
3299 element == EL_REFRACTOR)
3301 RotateMirror(x, y, MB_RIGHTBUTTON);
3303 laser.redraw = TRUE;
3309 static boolean ObjHit(int obx, int oby, int bits)
3316 if (bits & HIT_POS_CENTER)
3318 if (CheckLaserPixel(cSX + obx + 15,
3323 if (bits & HIT_POS_EDGE)
3325 for (i = 0; i < 4; i++)
3326 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3327 cSY + oby + 31 * (i / 2)))
3331 if (bits & HIT_POS_BETWEEN)
3333 for (i = 0; i < 4; i++)
3334 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3335 cSY + 4 + oby + 22 * (i / 2)))
3342 static void DeletePacMan(int px, int py)
3348 if (game_mm.num_pacman <= 1)
3350 game_mm.num_pacman = 0;
3354 for (i = 0; i < game_mm.num_pacman; i++)
3355 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3358 game_mm.num_pacman--;
3360 for (j = i; j < game_mm.num_pacman; j++)
3362 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3363 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3364 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3368 static void GameActions_MM_Ext(void)
3375 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3378 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3380 element = Tile[x][y];
3382 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3383 StartMoving_MM(x, y);
3384 else if (IS_MOVING(x, y))
3385 ContinueMoving_MM(x, y);
3386 else if (IS_EXPLODING(element))
3387 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3388 else if (element == EL_EXIT_OPENING)
3390 else if (element == EL_GRAY_BALL_OPENING)
3392 else if (IS_ENVELOPE_OPENING(element))
3394 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3396 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3398 else if (IS_MIRROR(element) ||
3399 IS_MIRROR_FIXED(element) ||
3400 element == EL_PRISM)
3401 DrawFieldTwinkle(x, y);
3402 else if (element == EL_GRAY_BALL_ACTIVE ||
3403 element == EL_BOMB_ACTIVE ||
3404 element == EL_MINE_ACTIVE)
3405 DrawFieldAnimated_MM(x, y);
3406 else if (!IS_BLOCKED(x, y))
3407 DrawFieldAnimatedIfNeeded_MM(x, y);
3410 AutoRotateMirrors();
3413 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3415 // redraw after Explode_MM() ...
3417 DrawLaser(0, DL_LASER_ENABLED);
3418 laser.redraw = FALSE;
3423 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3427 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3429 DrawLaser(0, DL_LASER_DISABLED);
3434 // skip all following game actions if game is over
3435 if (game_mm.game_over)
3438 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3442 GameOver_MM(GAME_OVER_NO_ENERGY);
3447 if (FrameReached(&energy_delay))
3449 if (game_mm.energy_left > 0)
3450 game_mm.energy_left--;
3452 // when out of energy, wait another frame to play "out of time" sound
3455 element = laser.dest_element;
3458 if (element != Tile[ELX][ELY])
3460 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3461 element, Tile[ELX][ELY]);
3465 if (!laser.overloaded && laser.overload_value == 0 &&
3466 element != EL_BOMB &&
3467 element != EL_BOMB_ACTIVE &&
3468 element != EL_MINE &&
3469 element != EL_MINE_ACTIVE &&
3470 element != EL_GRAY_BALL &&
3471 element != EL_GRAY_BALL_ACTIVE &&
3472 element != EL_BLOCK_STONE &&
3473 element != EL_BLOCK_WOOD &&
3474 element != EL_FUSE_ON &&
3475 element != EL_FUEL_FULL &&
3476 !IS_WALL_ICE(element) &&
3477 !IS_WALL_AMOEBA(element))
3480 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3482 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3483 (!laser.overloaded && laser.overload_value > 0)) &&
3484 FrameReached(&overload_delay))
3486 if (laser.overloaded)
3487 laser.overload_value++;
3489 laser.overload_value--;
3491 if (game_mm.cheat_no_overload)
3493 laser.overloaded = FALSE;
3494 laser.overload_value = 0;
3497 game_mm.laser_overload_value = laser.overload_value;
3499 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3501 SetLaserColor(0xFF);
3503 DrawLaser(0, DL_LASER_ENABLED);
3506 if (!laser.overloaded)
3507 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3508 else if (setup.sound_loops)
3509 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3511 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3513 if (laser.overload_value == MAX_LASER_OVERLOAD)
3515 UpdateAndDisplayGameControlValues();
3519 GameOver_MM(GAME_OVER_OVERLOADED);
3530 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3532 if (game_mm.cheat_no_explosion)
3537 laser.dest_element = EL_EXPLODING_OPAQUE;
3542 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3544 laser.fuse_off = TRUE;
3548 DrawLaser(0, DL_LASER_DISABLED);
3549 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3552 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3554 if (!Store2[ELX][ELY]) // check if content element not yet determined
3556 int last_anim_random_frame = gfx.anim_random_frame;
3559 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3560 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3562 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3563 native_mm_level.ball_choice_mode, 0,
3564 game_mm.ball_choice_pos);
3566 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3567 gfx.anim_random_frame = last_anim_random_frame;
3569 game_mm.ball_choice_pos++;
3571 int new_element = native_mm_level.ball_content[element_pos];
3572 int new_element_base = map_wall_to_base_element(new_element);
3574 if (IS_WALL(new_element_base))
3576 // always use completely filled wall element
3577 new_element = new_element_base | 0x000f;
3579 else if (native_mm_level.rotate_ball_content &&
3580 get_num_elements(new_element) > 1)
3582 // randomly rotate newly created game element
3583 new_element = get_rotated_element(new_element, RND(16));
3586 Store[ELX][ELY] = new_element;
3587 Store2[ELX][ELY] = TRUE;
3590 if (native_mm_level.explode_ball)
3593 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3595 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3600 if (IS_WALL_ICE(element) && CT > 50)
3602 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3604 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3605 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3606 Store2[ELX][ELY] = laser.wall_mask;
3608 laser.dest_element = Tile[ELX][ELY];
3613 if (IS_WALL_AMOEBA(element) && CT > 60)
3616 int element2 = Tile[ELX][ELY];
3618 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3621 for (i = laser.num_damages - 1; i >= 0; i--)
3622 if (laser.damage[i].is_mirror)
3625 r = laser.num_edges;
3626 d = laser.num_damages;
3633 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3636 DrawLaser(0, DL_LASER_ENABLED);
3639 x = laser.damage[k1].x;
3640 y = laser.damage[k1].y;
3645 for (i = 0; i < 4; i++)
3647 if (laser.wall_mask & (1 << i))
3649 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3650 cSY + ELY * TILEY + 31 * (i / 2)))
3653 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3654 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3661 for (i = 0; i < 4; i++)
3663 if (laser.wall_mask & (1 << i))
3665 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3666 cSY + ELY * TILEY + 31 * (i / 2)))
3673 if (laser.num_beamers > 0 ||
3674 k1 < 1 || k2 < 4 || k3 < 4 ||
3675 CheckLaserPixel(cSX + ELX * TILEX + 14,
3676 cSY + ELY * TILEY + 14))
3678 laser.num_edges = r;
3679 laser.num_damages = d;
3681 DrawLaser(0, DL_LASER_DISABLED);
3684 Tile[ELX][ELY] = element | laser.wall_mask;
3686 int x = ELX, y = ELY;
3687 int wall_mask = laser.wall_mask;
3690 DrawLaser(0, DL_LASER_ENABLED);
3692 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3694 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3695 Store[x][y] = EL_WALL_AMOEBA_BASE;
3696 Store2[x][y] = wall_mask;
3701 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3702 laser.stops_inside_element && CT > native_mm_level.time_block)
3707 if (ABS(XS) > ABS(YS))
3714 for (i = 0; i < 4; i++)
3721 x = ELX + Step[k * 4].x;
3722 y = ELY + Step[k * 4].y;
3724 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3727 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3735 laser.overloaded = (element == EL_BLOCK_STONE);
3740 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3743 Tile[x][y] = element;
3745 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3748 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3750 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3751 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3759 if (element == EL_FUEL_FULL && CT > 10)
3761 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3762 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3764 for (i = start; i <= num_init_game_frames; i++)
3766 if (i == num_init_game_frames)
3767 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3768 else if (setup.sound_loops)
3769 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3771 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3773 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3775 UpdateAndDisplayGameControlValues();
3780 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3782 DrawField_MM(ELX, ELY);
3784 DrawLaser(0, DL_LASER_ENABLED);
3790 void GameActions_MM(struct MouseActionInfo action)
3792 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3793 boolean button_released = (action.button == MB_RELEASED);
3795 GameActions_MM_Ext();
3797 CheckSingleStepMode_MM(element_clicked, button_released);
3800 static void MovePacMen(void)
3802 int mx, my, ox, oy, nx, ny;
3806 if (++pacman_nr >= game_mm.num_pacman)
3809 game_mm.pacman[pacman_nr].dir--;
3811 for (l = 1; l < 5; l++)
3813 game_mm.pacman[pacman_nr].dir++;
3815 if (game_mm.pacman[pacman_nr].dir > 4)
3816 game_mm.pacman[pacman_nr].dir = 1;
3818 if (game_mm.pacman[pacman_nr].dir % 2)
3821 my = game_mm.pacman[pacman_nr].dir - 2;
3826 mx = 3 - game_mm.pacman[pacman_nr].dir;
3829 ox = game_mm.pacman[pacman_nr].x;
3830 oy = game_mm.pacman[pacman_nr].y;
3833 element = Tile[nx][ny];
3835 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3838 if (!IS_EATABLE4PACMAN(element))
3841 if (ObjHit(nx, ny, HIT_POS_CENTER))
3844 Tile[ox][oy] = EL_EMPTY;
3846 EL_PACMAN_RIGHT - 1 +
3847 (game_mm.pacman[pacman_nr].dir - 1 +
3848 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3850 game_mm.pacman[pacman_nr].x = nx;
3851 game_mm.pacman[pacman_nr].y = ny;
3853 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3855 if (element != EL_EMPTY)
3857 int graphic = el2gfx(Tile[nx][ny]);
3862 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3865 ox = cSX + ox * TILEX;
3866 oy = cSY + oy * TILEY;
3868 for (i = 1; i < 33; i += 2)
3869 BlitBitmap(bitmap, window,
3870 src_x, src_y, TILEX, TILEY,
3871 ox + i * mx, oy + i * my);
3872 Ct = Ct + FrameCounter - CT;
3875 DrawField_MM(nx, ny);
3878 if (!laser.fuse_off)
3880 DrawLaser(0, DL_LASER_ENABLED);
3882 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3884 AddDamagedField(nx, ny);
3886 laser.damage[laser.num_damages - 1].edge = 0;
3890 if (element == EL_BOMB)
3891 DeletePacMan(nx, ny);
3893 if (IS_WALL_AMOEBA(element) &&
3894 (LX + 2 * XS) / TILEX == nx &&
3895 (LY + 2 * YS) / TILEY == ny)
3905 static void InitMovingField_MM(int x, int y, int direction)
3907 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3908 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3910 MovDir[x][y] = direction;
3911 MovDir[newx][newy] = direction;
3913 if (Tile[newx][newy] == EL_EMPTY)
3914 Tile[newx][newy] = EL_BLOCKED;
3917 static int MovingOrBlocked2Element_MM(int x, int y)
3919 int element = Tile[x][y];
3921 if (element == EL_BLOCKED)
3925 Blocked2Moving(x, y, &oldx, &oldy);
3927 return Tile[oldx][oldy];
3933 static void RemoveMovingField_MM(int x, int y)
3935 int oldx = x, oldy = y, newx = x, newy = y;
3937 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3940 if (IS_MOVING(x, y))
3942 Moving2Blocked(x, y, &newx, &newy);
3943 if (Tile[newx][newy] != EL_BLOCKED)
3946 else if (Tile[x][y] == EL_BLOCKED)
3948 Blocked2Moving(x, y, &oldx, &oldy);
3949 if (!IS_MOVING(oldx, oldy))
3953 Tile[oldx][oldy] = EL_EMPTY;
3954 Tile[newx][newy] = EL_EMPTY;
3955 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3956 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3958 DrawLevelField_MM(oldx, oldy);
3959 DrawLevelField_MM(newx, newy);
3962 static void RaiseScore_MM(int value)
3964 game_mm.score += value;
3967 void RaiseScoreElement_MM(int element)
3972 case EL_PACMAN_RIGHT:
3974 case EL_PACMAN_LEFT:
3975 case EL_PACMAN_DOWN:
3976 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3980 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3985 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3989 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3998 // ----------------------------------------------------------------------------
3999 // Mirror Magic game engine snapshot handling functions
4000 // ----------------------------------------------------------------------------
4002 void SaveEngineSnapshotValues_MM(void)
4006 engine_snapshot_mm.game_mm = game_mm;
4007 engine_snapshot_mm.laser = laser;
4009 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4011 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4013 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4014 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4015 engine_snapshot_mm.Box[x][y] = Box[x][y];
4016 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4020 engine_snapshot_mm.LX = LX;
4021 engine_snapshot_mm.LY = LY;
4022 engine_snapshot_mm.XS = XS;
4023 engine_snapshot_mm.YS = YS;
4024 engine_snapshot_mm.ELX = ELX;
4025 engine_snapshot_mm.ELY = ELY;
4026 engine_snapshot_mm.CT = CT;
4027 engine_snapshot_mm.Ct = Ct;
4029 engine_snapshot_mm.last_LX = last_LX;
4030 engine_snapshot_mm.last_LY = last_LY;
4031 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4032 engine_snapshot_mm.hold_x = hold_x;
4033 engine_snapshot_mm.hold_y = hold_y;
4034 engine_snapshot_mm.pacman_nr = pacman_nr;
4036 engine_snapshot_mm.rotate_delay = rotate_delay;
4037 engine_snapshot_mm.pacman_delay = pacman_delay;
4038 engine_snapshot_mm.energy_delay = energy_delay;
4039 engine_snapshot_mm.overload_delay = overload_delay;
4042 void LoadEngineSnapshotValues_MM(void)
4046 // stored engine snapshot buffers already restored at this point
4048 game_mm = engine_snapshot_mm.game_mm;
4049 laser = engine_snapshot_mm.laser;
4051 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4053 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4055 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4056 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4057 Box[x][y] = engine_snapshot_mm.Box[x][y];
4058 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4062 LX = engine_snapshot_mm.LX;
4063 LY = engine_snapshot_mm.LY;
4064 XS = engine_snapshot_mm.XS;
4065 YS = engine_snapshot_mm.YS;
4066 ELX = engine_snapshot_mm.ELX;
4067 ELY = engine_snapshot_mm.ELY;
4068 CT = engine_snapshot_mm.CT;
4069 Ct = engine_snapshot_mm.Ct;
4071 last_LX = engine_snapshot_mm.last_LX;
4072 last_LY = engine_snapshot_mm.last_LY;
4073 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4074 hold_x = engine_snapshot_mm.hold_x;
4075 hold_y = engine_snapshot_mm.hold_y;
4076 pacman_nr = engine_snapshot_mm.pacman_nr;
4078 rotate_delay = engine_snapshot_mm.rotate_delay;
4079 pacman_delay = engine_snapshot_mm.pacman_delay;
4080 energy_delay = engine_snapshot_mm.energy_delay;
4081 overload_delay = engine_snapshot_mm.overload_delay;
4083 RedrawPlayfield_MM();
4086 static int getAngleFromTouchDelta(int dx, int dy, int base)
4088 double pi = 3.141592653;
4089 double rad = atan2((double)-dy, (double)dx);
4090 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4091 double deg = rad2 * 180.0 / pi;
4093 return (int)(deg * base / 360.0 + 0.5) % base;
4096 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4098 // calculate start (source) position to be at the middle of the tile
4099 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4100 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4101 int dx = dst_mx - src_mx;
4102 int dy = dst_my - src_my;
4111 if (!IN_LEV_FIELD(x, y))
4114 element = Tile[x][y];
4116 if (!IS_MCDUFFIN(element) &&
4117 !IS_MIRROR(element) &&
4118 !IS_BEAMER(element) &&
4119 !IS_POLAR(element) &&
4120 !IS_POLAR_CROSS(element) &&
4121 !IS_DF_MIRROR(element))
4124 angle_old = get_element_angle(element);
4126 if (IS_MCDUFFIN(element))
4128 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4129 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4130 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4131 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4134 else if (IS_MIRROR(element) ||
4135 IS_DF_MIRROR(element))
4137 for (i = 0; i < laser.num_damages; i++)
4139 if (laser.damage[i].x == x &&
4140 laser.damage[i].y == y &&
4141 ObjHit(x, y, HIT_POS_CENTER))
4143 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4144 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4151 if (angle_new == -1)
4153 if (IS_MIRROR(element) ||
4154 IS_DF_MIRROR(element) ||
4158 if (IS_POLAR_CROSS(element))
4161 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4164 button = (angle_new == angle_old ? 0 :
4165 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4166 MB_LEFTBUTTON : MB_RIGHTBUTTON);