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_RECTANGLE 8
143 #define MM_MASK_CIRCLE 9
145 #define NUM_MM_MASKS 10
147 // element masks for scanning pixels of MM elements
148 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
332 static int get_element_angle(int element)
334 int element_phase = get_element_phase(element);
336 if (IS_MIRROR_FIXED(element) ||
337 IS_MCDUFFIN(element) ||
339 IS_RECEIVER(element))
340 return 4 * element_phase;
342 return element_phase;
345 static int get_opposite_angle(int angle)
347 int opposite_angle = angle + ANG_RAY_180;
349 // make sure "opposite_angle" is in valid interval [0, 15]
350 return (opposite_angle + 16) % 16;
353 static int get_mirrored_angle(int laser_angle, int mirror_angle)
355 int reflected_angle = 16 - laser_angle + mirror_angle;
357 // make sure "reflected_angle" is in valid interval [0, 15]
358 return (reflected_angle + 16) % 16;
361 static void DrawLaserLines(struct XY *points, int num_points, int mode)
363 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
364 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
366 DrawLines(drawto_mm, points, num_points, pixel_drawto);
370 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
375 static boolean CheckLaserPixel(int x, int y)
381 pixel = ReadPixel(laser_bitmap, x, y);
385 return (pixel == WHITE_PIXEL);
388 static void CheckExitMM(void)
390 int exit_element = EL_EMPTY;
394 static int xy[4][2] =
402 for (y = 0; y < lev_fieldy; y++)
404 for (x = 0; x < lev_fieldx; x++)
406 if (Tile[x][y] == EL_EXIT_CLOSED)
408 // initiate opening animation of exit door
409 Tile[x][y] = EL_EXIT_OPENING;
411 exit_element = EL_EXIT_OPEN;
415 else if (IS_RECEIVER(Tile[x][y]))
417 // remove field that blocks receiver
418 int phase = Tile[x][y] - EL_RECEIVER_START;
419 int blocking_x, blocking_y;
421 blocking_x = x + xy[phase][0];
422 blocking_y = y + xy[phase][1];
424 if (IN_LEV_FIELD(blocking_x, blocking_y))
426 Tile[blocking_x][blocking_y] = EL_EMPTY;
428 DrawField_MM(blocking_x, blocking_y);
431 exit_element = EL_RECEIVER;
438 if (exit_element != EL_EMPTY)
439 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
442 static void SetLaserColor(int brightness)
444 int color_min = 0x00;
445 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
446 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
447 int color_down = color_max - color_up;
450 GetPixelFromRGB(window,
451 (game_mm.laser_red ? color_max : color_up),
452 (game_mm.laser_green ? color_down : color_min),
453 (game_mm.laser_blue ? color_down : color_min));
456 static void InitMovDir_MM(int x, int y)
458 int element = Tile[x][y];
459 static int direction[3][4] =
461 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
462 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
463 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
468 case EL_PACMAN_RIGHT:
472 Tile[x][y] = EL_PACMAN;
473 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
481 static void InitField(int x, int y, boolean init_game)
483 int element = Tile[x][y];
488 Tile[x][y] = EL_EMPTY;
493 if (init_game && native_mm_level.auto_count_kettles)
494 game_mm.kettles_still_needed++;
497 case EL_LIGHTBULB_OFF:
498 game_mm.lights_still_needed++;
502 if (IS_MIRROR(element) ||
503 IS_BEAMER_OLD(element) ||
504 IS_BEAMER(element) ||
506 IS_POLAR_CROSS(element) ||
507 IS_DF_MIRROR(element) ||
508 IS_DF_MIRROR_AUTO(element) ||
509 IS_GRID_STEEL_AUTO(element) ||
510 IS_GRID_WOOD_AUTO(element) ||
511 IS_FIBRE_OPTIC(element))
513 if (IS_BEAMER_OLD(element))
515 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
516 element = Tile[x][y];
519 if (!IS_FIBRE_OPTIC(element))
521 static int steps_grid_auto = 0;
523 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
524 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
526 if (IS_GRID_STEEL_AUTO(element) ||
527 IS_GRID_WOOD_AUTO(element))
528 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
530 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
532 game_mm.cycle[game_mm.num_cycle].x = x;
533 game_mm.cycle[game_mm.num_cycle].y = y;
537 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
539 int beamer_nr = BEAMER_NR(element);
540 int nr = laser.beamer[beamer_nr][0].num;
542 laser.beamer[beamer_nr][nr].x = x;
543 laser.beamer[beamer_nr][nr].y = y;
544 laser.beamer[beamer_nr][nr].num = 1;
547 else if (IS_PACMAN(element))
551 else if (IS_MCDUFFIN(element) || IS_LASER(element))
555 laser.start_edge.x = x;
556 laser.start_edge.y = y;
557 laser.start_angle = get_element_angle(element);
560 if (IS_MCDUFFIN(element))
562 game_mm.laser_red = native_mm_level.mm_laser_red;
563 game_mm.laser_green = native_mm_level.mm_laser_green;
564 game_mm.laser_blue = native_mm_level.mm_laser_blue;
568 game_mm.laser_red = native_mm_level.df_laser_red;
569 game_mm.laser_green = native_mm_level.df_laser_green;
570 game_mm.laser_blue = native_mm_level.df_laser_blue;
573 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
580 static void InitCycleElements_RotateSingleStep(void)
584 if (game_mm.num_cycle == 0) // no elements to cycle
587 for (i = 0; i < game_mm.num_cycle; i++)
589 int x = game_mm.cycle[i].x;
590 int y = game_mm.cycle[i].y;
591 int step = SIGN(game_mm.cycle[i].steps);
592 int last_element = Tile[x][y];
593 int next_element = get_rotated_element(last_element, step);
595 if (!game_mm.cycle[i].steps)
598 Tile[x][y] = next_element;
600 game_mm.cycle[i].steps -= step;
604 static void InitLaser(void)
606 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
607 int step = (IS_LASER(start_element) ? 4 : 0);
609 LX = laser.start_edge.x * TILEX;
610 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
613 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
615 LY = laser.start_edge.y * TILEY;
616 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
617 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
621 XS = 2 * Step[laser.start_angle].x;
622 YS = 2 * Step[laser.start_angle].y;
624 laser.current_angle = laser.start_angle;
626 laser.num_damages = 0;
628 laser.num_beamers = 0;
629 laser.beamer_edge[0] = 0;
631 laser.dest_element = EL_EMPTY;
634 AddLaserEdge(LX, LY); // set laser starting edge
639 void InitGameEngine_MM(void)
645 // initialize laser bitmap to current playfield (screen) size
646 ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
647 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
651 // set global game control values
652 game_mm.num_cycle = 0;
653 game_mm.num_pacman = 0;
656 game_mm.energy_left = 0; // later set to "native_mm_level.time"
657 game_mm.kettles_still_needed =
658 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
659 game_mm.lights_still_needed = 0;
660 game_mm.num_keys = 0;
661 game_mm.ball_choice_pos = 0;
663 game_mm.laser_red = FALSE;
664 game_mm.laser_green = FALSE;
665 game_mm.laser_blue = TRUE;
666 game_mm.has_mcduffin = TRUE;
668 game_mm.level_solved = FALSE;
669 game_mm.game_over = FALSE;
670 game_mm.game_over_cause = 0;
671 game_mm.game_over_message = NULL;
673 game_mm.laser_overload_value = 0;
674 game_mm.laser_enabled = FALSE;
676 // set global laser control values (must be set before "InitLaser()")
677 laser.start_edge.x = 0;
678 laser.start_edge.y = 0;
679 laser.start_angle = 0;
681 for (i = 0; i < MAX_NUM_BEAMERS; i++)
682 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
684 laser.overloaded = FALSE;
685 laser.overload_value = 0;
686 laser.fuse_off = FALSE;
687 laser.fuse_x = laser.fuse_y = -1;
689 laser.dest_element = EL_EMPTY;
690 laser.dest_element_last = EL_EMPTY;
691 laser.dest_element_last_x = -1;
692 laser.dest_element_last_y = -1;
706 rotate_delay.count = 0;
707 pacman_delay.count = 0;
708 energy_delay.count = 0;
709 overload_delay.count = 0;
711 ClickElement(-1, -1, -1);
713 for (x = 0; x < lev_fieldx; x++)
715 for (y = 0; y < lev_fieldy; y++)
717 Tile[x][y] = Ur[x][y];
718 Hit[x][y] = Box[x][y] = 0;
720 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
721 Store[x][y] = Store2[x][y] = 0;
724 InitField(x, y, TRUE);
731 void InitGameActions_MM(void)
733 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
734 int cycle_steps_done = 0;
739 for (i = 0; i <= num_init_game_frames; i++)
741 if (i == num_init_game_frames)
742 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
743 else if (setup.sound_loops)
744 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
746 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
748 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
750 UpdateAndDisplayGameControlValues();
752 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
754 InitCycleElements_RotateSingleStep();
759 AdvanceFrameCounter();
767 if (setup.quick_doors)
774 if (game_mm.kettles_still_needed == 0)
777 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
778 SetTileCursorActive(TRUE);
780 // restart all delay counters after initially cycling game elements
781 ResetFrameCounter(&rotate_delay);
782 ResetFrameCounter(&pacman_delay);
783 ResetFrameCounter(&energy_delay);
784 ResetFrameCounter(&overload_delay);
787 static void FadeOutLaser(void)
791 for (i = 15; i >= 0; i--)
793 SetLaserColor(0x11 * i);
795 DrawLaser(0, DL_LASER_ENABLED);
798 Delay_WithScreenUpdates(50);
801 DrawLaser(0, DL_LASER_DISABLED);
803 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
806 static void GameOver_MM(int game_over_cause)
808 game_mm.game_over = TRUE;
809 game_mm.game_over_cause = game_over_cause;
810 game_mm.game_over_message = (game_mm.has_mcduffin ?
811 (game_over_cause == GAME_OVER_BOMB ?
812 "Bomb killed Mc Duffin!" :
813 game_over_cause == GAME_OVER_NO_ENERGY ?
814 "Out of magic energy!" :
815 game_over_cause == GAME_OVER_OVERLOADED ?
816 "Magic spell hit Mc Duffin!" :
818 (game_over_cause == GAME_OVER_BOMB ?
819 "Bomb destroyed laser cannon!" :
820 game_over_cause == GAME_OVER_NO_ENERGY ?
821 "Out of laser energy!" :
822 game_over_cause == GAME_OVER_OVERLOADED ?
823 "Laser beam hit laser cannon!" :
826 SetTileCursorActive(FALSE);
829 static void AddLaserEdge(int lx, int ly)
833 int sxsize = MAX(SXSIZE, lev_fieldx * TILEX);
834 int sysize = MAX(SYSIZE, lev_fieldy * TILEY);
836 if (clx < -2 || cly < -2 || clx >= sxsize + 2 || cly >= sysize + 2)
838 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
843 laser.edge[laser.num_edges].x = cSX2 + lx;
844 laser.edge[laser.num_edges].y = cSY2 + ly;
850 static void AddDamagedField(int ex, int ey)
852 // prevent adding the same field position again
853 if (laser.num_damages > 0 &&
854 laser.damage[laser.num_damages - 1].x == ex &&
855 laser.damage[laser.num_damages - 1].y == ey &&
856 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
859 laser.damage[laser.num_damages].is_mirror = FALSE;
860 laser.damage[laser.num_damages].angle = laser.current_angle;
861 laser.damage[laser.num_damages].edge = laser.num_edges;
862 laser.damage[laser.num_damages].x = ex;
863 laser.damage[laser.num_damages].y = ey;
867 static boolean StepBehind(void)
873 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
874 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
876 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
882 static int getMaskFromElement(int element)
884 if (IS_GRID(element))
885 return MM_MASK_GRID_1 + get_element_phase(element);
886 else if (IS_MCDUFFIN(element))
887 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
888 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
889 return MM_MASK_RECTANGLE;
891 return MM_MASK_CIRCLE;
894 static int ScanPixel(void)
899 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
900 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
903 // follow laser beam until it hits something (at least the screen border)
904 while (hit_mask == HIT_MASK_NO_HIT)
910 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
911 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
913 Debug("game:mm:ScanPixel", "touched screen border!");
919 // check if laser scan has crossed element boundaries (not just mini tiles)
920 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
921 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
923 if (cross_x && cross_y)
925 int elx1 = (LX - XS) / TILEX;
926 int ely1 = (LY + YS) / TILEY;
927 int elx2 = (LX + XS) / TILEX;
928 int ely2 = (LY - YS) / TILEY;
930 // add element corners left and right from the laser beam to damage list
932 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
933 AddDamagedField(elx1, ely1);
935 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
936 AddDamagedField(elx2, ely2);
939 for (i = 0; i < 4; i++)
941 int px = LX + (i % 2) * 2;
942 int py = LY + (i / 2) * 2;
945 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
946 int ly = (py + TILEY) / TILEY - 1; // negative values!
949 if (IN_LEV_FIELD(lx, ly))
951 int element = Tile[lx][ly];
953 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
957 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
959 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
961 pixel = ((element & (1 << pos)) ? 1 : 0);
965 int pos = getMaskFromElement(element);
967 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
972 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
973 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
976 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
977 hit_mask |= (1 << i);
980 if (hit_mask == HIT_MASK_NO_HIT)
982 // hit nothing -- go on with another step
991 static void DeactivateLaserTargetElement(void)
993 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
994 laser.dest_element_last == EL_MINE_ACTIVE ||
995 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
996 laser.dest_element_last == EL_GRAY_BALL_OPENING)
998 int x = laser.dest_element_last_x;
999 int y = laser.dest_element_last_y;
1000 int element = laser.dest_element_last;
1002 if (Tile[x][y] == element)
1003 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1004 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1006 if (Tile[x][y] == EL_GRAY_BALL)
1009 laser.dest_element_last = EL_EMPTY;
1010 laser.dest_element_last_x = -1;
1011 laser.dest_element_last_y = -1;
1015 static void ScanLaser(void)
1017 int element = EL_EMPTY;
1018 int last_element = EL_EMPTY;
1019 int end = 0, rf = laser.num_edges;
1021 // do not scan laser again after the game was lost for whatever reason
1022 if (game_mm.game_over)
1025 // do not scan laser if fuse is off
1029 DeactivateLaserTargetElement();
1031 laser.overloaded = FALSE;
1032 laser.stops_inside_element = FALSE;
1034 DrawLaser(0, DL_LASER_ENABLED);
1037 Debug("game:mm:ScanLaser",
1038 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1046 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1049 laser.overloaded = TRUE;
1054 hit_mask = ScanPixel();
1057 Debug("game:mm:ScanLaser",
1058 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1062 // hit something -- check out what it was
1063 ELX = (LX + XS + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
1064 ELY = (LY + YS + TILEY) / TILEY - 1; // negative values!
1067 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1068 hit_mask, LX, LY, ELX, ELY);
1071 if (!IN_LEV_FIELD(ELX, ELY))
1074 laser.dest_element = element;
1079 // check if laser scan has hit two diagonally adjacent element corners
1080 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1081 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1083 // check if laser scan has crossed element boundaries (not just mini tiles)
1084 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1085 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1087 // handle special case of laser hitting two diagonally adjacent elements
1088 // (with or without a third corner element behind these two elements)
1089 if ((diag_1 || diag_2) && cross_x && cross_y)
1091 // compare the two diagonally adjacent elements
1093 int yoffset = 2 * (diag_1 ? -1 : +1);
1094 int elx1 = (LX - xoffset) / TILEX;
1095 int ely1 = (LY + yoffset) / TILEY;
1096 int elx2 = (LX + xoffset) / TILEX;
1097 int ely2 = (LY - yoffset) / TILEY;
1098 int e1 = Tile[elx1][ely1];
1099 int e2 = Tile[elx2][ely2];
1100 boolean use_element_1 = FALSE;
1102 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1104 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1105 use_element_1 = (RND(2) ? TRUE : FALSE);
1106 else if (IS_WALL_ICE(e1))
1107 use_element_1 = TRUE;
1109 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1111 // if both tiles match, we can just select the first one
1112 if (IS_WALL_AMOEBA(e1))
1113 use_element_1 = TRUE;
1115 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1117 // if both tiles match, we can just select the first one
1118 if (IS_ABSORBING_BLOCK(e1))
1119 use_element_1 = TRUE;
1122 ELX = (use_element_1 ? elx1 : elx2);
1123 ELY = (use_element_1 ? ely1 : ely2);
1127 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1128 hit_mask, LX, LY, ELX, ELY);
1131 last_element = element;
1133 element = Tile[ELX][ELY];
1134 laser.dest_element = element;
1137 Debug("game:mm:ScanLaser",
1138 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1141 LX % TILEX, LY % TILEY,
1146 if (!IN_LEV_FIELD(ELX, ELY))
1147 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1151 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1152 if (element == EL_EMPTY &&
1153 IS_GRID_STEEL(last_element) &&
1154 laser.current_angle % 4) // angle is not 90°
1155 element = last_element;
1157 if (element == EL_EMPTY)
1159 if (!HitOnlyAnEdge(hit_mask))
1162 else if (element == EL_FUSE_ON)
1164 if (HitPolarizer(element, hit_mask))
1167 else if (IS_GRID(element) || IS_DF_GRID(element))
1169 if (HitPolarizer(element, hit_mask))
1172 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1173 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1175 if (HitBlock(element, hit_mask))
1182 else if (IS_MCDUFFIN(element))
1184 if (HitLaserSource(element, hit_mask))
1187 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1188 IS_RECEIVER(element))
1190 if (HitLaserDestination(element, hit_mask))
1193 else if (IS_WALL(element))
1195 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1197 if (HitReflectingWalls(element, hit_mask))
1202 if (HitAbsorbingWalls(element, hit_mask))
1208 if (HitElement(element, hit_mask))
1213 DrawLaser(rf - 1, DL_LASER_ENABLED);
1214 rf = laser.num_edges;
1216 if (!IS_DF_WALL_STEEL(element))
1218 // only used for scanning DF steel walls; reset for all other elements
1226 if (laser.dest_element != Tile[ELX][ELY])
1228 Debug("game:mm:ScanLaser",
1229 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1230 laser.dest_element, Tile[ELX][ELY]);
1234 if (!end && !laser.stops_inside_element && !StepBehind())
1237 Debug("game:mm:ScanLaser", "Go one step back");
1243 AddLaserEdge(LX, LY);
1247 DrawLaser(rf - 1, DL_LASER_ENABLED);
1249 Ct = CT = FrameCounter;
1252 if (!IN_LEV_FIELD(ELX, ELY))
1253 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1257 static void ScanLaser_FromLastMirror(void)
1259 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1262 for (i = start_pos; i >= 0; i--)
1263 if (laser.damage[i].is_mirror)
1266 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1268 DrawLaser(start_edge, DL_LASER_DISABLED);
1273 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1279 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1280 start_edge, num_edges, mode);
1285 Warn("DrawLaserExt: start_edge < 0");
1292 Warn("DrawLaserExt: num_edges < 0");
1298 if (mode == DL_LASER_DISABLED)
1300 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1304 // now draw the laser to the backbuffer and (if enabled) to the screen
1305 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1307 redraw_mask |= REDRAW_FIELD;
1309 if (mode == DL_LASER_ENABLED)
1312 // after the laser was deleted, the "damaged" graphics must be restored
1313 if (laser.num_damages)
1315 int damage_start = 0;
1318 // determine the starting edge, from which graphics need to be restored
1321 for (i = 0; i < laser.num_damages; i++)
1323 if (laser.damage[i].edge == start_edge + 1)
1332 // restore graphics from this starting edge to the end of damage list
1333 for (i = damage_start; i < laser.num_damages; i++)
1335 int lx = laser.damage[i].x;
1336 int ly = laser.damage[i].y;
1337 int element = Tile[lx][ly];
1339 if (Hit[lx][ly] == laser.damage[i].edge)
1340 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1343 if (Box[lx][ly] == laser.damage[i].edge)
1346 if (IS_DRAWABLE(element))
1347 DrawField_MM(lx, ly);
1350 elx = laser.damage[damage_start].x;
1351 ely = laser.damage[damage_start].y;
1352 element = Tile[elx][ely];
1355 if (IS_BEAMER(element))
1359 for (i = 0; i < laser.num_beamers; i++)
1360 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1362 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1363 mode, elx, ely, Hit[elx][ely], start_edge);
1364 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1365 get_element_angle(element), laser.damage[damage_start].angle);
1369 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1370 laser.num_beamers > 0 &&
1371 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1373 // element is outgoing beamer
1374 laser.num_damages = damage_start + 1;
1376 if (IS_BEAMER(element))
1377 laser.current_angle = get_element_angle(element);
1381 // element is incoming beamer or other element
1382 laser.num_damages = damage_start;
1383 laser.current_angle = laser.damage[laser.num_damages].angle;
1388 // no damages but McDuffin himself (who needs to be redrawn anyway)
1390 elx = laser.start_edge.x;
1391 ely = laser.start_edge.y;
1392 element = Tile[elx][ely];
1395 laser.num_edges = start_edge + 1;
1396 if (start_edge == 0)
1397 laser.current_angle = laser.start_angle;
1399 LX = laser.edge[start_edge].x - cSX2;
1400 LY = laser.edge[start_edge].y - cSY2;
1401 XS = 2 * Step[laser.current_angle].x;
1402 YS = 2 * Step[laser.current_angle].y;
1405 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1411 if (IS_BEAMER(element) ||
1412 IS_FIBRE_OPTIC(element) ||
1413 IS_PACMAN(element) ||
1414 IS_POLAR(element) ||
1415 IS_POLAR_CROSS(element) ||
1416 element == EL_FUSE_ON)
1421 Debug("game:mm:DrawLaserExt", "element == %d", element);
1424 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1425 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1429 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1430 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1431 (laser.num_beamers == 0 ||
1432 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1434 // element is incoming beamer or other element
1435 step_size = -step_size;
1440 if (IS_BEAMER(element))
1441 Debug("game:mm:DrawLaserExt",
1442 "start_edge == %d, laser.beamer_edge == %d",
1443 start_edge, laser.beamer_edge);
1446 LX += step_size * XS;
1447 LY += step_size * YS;
1449 else if (element != EL_EMPTY)
1458 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1463 void DrawLaser(int start_edge, int mode)
1465 // do not draw laser if fuse is off
1466 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1469 if (mode == DL_LASER_DISABLED)
1470 DeactivateLaserTargetElement();
1472 if (laser.num_edges - start_edge < 0)
1474 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1479 // check if laser is interrupted by beamer element
1480 if (laser.num_beamers > 0 &&
1481 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1483 if (mode == DL_LASER_ENABLED)
1486 int tmp_start_edge = start_edge;
1488 // draw laser segments forward from the start to the last beamer
1489 for (i = 0; i < laser.num_beamers; i++)
1491 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1493 if (tmp_num_edges <= 0)
1497 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1498 i, laser.beamer_edge[i], tmp_start_edge);
1501 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1503 tmp_start_edge = laser.beamer_edge[i];
1506 // draw last segment from last beamer to the end
1507 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1513 int last_num_edges = laser.num_edges;
1514 int num_beamers = laser.num_beamers;
1516 // delete laser segments backward from the end to the first beamer
1517 for (i = num_beamers - 1; i >= 0; i--)
1519 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1521 if (laser.beamer_edge[i] - start_edge <= 0)
1524 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1526 last_num_edges = laser.beamer_edge[i];
1527 laser.num_beamers--;
1531 if (last_num_edges - start_edge <= 0)
1532 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1533 last_num_edges, start_edge);
1536 // special case when rotating first beamer: delete laser edge on beamer
1537 // (but do not start scanning on previous edge to prevent mirror sound)
1538 if (last_num_edges - start_edge == 1 && start_edge > 0)
1539 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1541 // delete first segment from start to the first beamer
1542 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1547 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1550 game_mm.laser_enabled = mode;
1553 void DrawLaser_MM(void)
1555 DrawLaser(0, game_mm.laser_enabled);
1558 static boolean HitElement(int element, int hit_mask)
1560 if (HitOnlyAnEdge(hit_mask))
1563 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1564 element = MovingOrBlocked2Element_MM(ELX, ELY);
1567 Debug("game:mm:HitElement", "(1): element == %d", element);
1571 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1572 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1575 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1579 AddDamagedField(ELX, ELY);
1581 // this is more precise: check if laser would go through the center
1582 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1586 // prevent cutting through laser emitter with laser beam
1587 if (IS_LASER(element))
1590 // skip the whole element before continuing the scan
1598 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1600 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1602 /* skipping scan positions to the right and down skips one scan
1603 position too much, because this is only the top left scan position
1604 of totally four scan positions (plus one to the right, one to the
1605 bottom and one to the bottom right) */
1606 /* ... but only roll back scan position if more than one step done */
1616 Debug("game:mm:HitElement", "(2): element == %d", element);
1619 if (LX + 5 * XS < 0 ||
1629 Debug("game:mm:HitElement", "(3): element == %d", element);
1632 if (IS_POLAR(element) &&
1633 ((element - EL_POLAR_START) % 2 ||
1634 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1636 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1638 laser.num_damages--;
1643 if (IS_POLAR_CROSS(element) &&
1644 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1646 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1648 laser.num_damages--;
1653 if (!IS_BEAMER(element) &&
1654 !IS_FIBRE_OPTIC(element) &&
1655 !IS_GRID_WOOD(element) &&
1656 element != EL_FUEL_EMPTY)
1659 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1660 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1662 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1665 LX = ELX * TILEX + 14;
1666 LY = ELY * TILEY + 14;
1668 AddLaserEdge(LX, LY);
1671 if (IS_MIRROR(element) ||
1672 IS_MIRROR_FIXED(element) ||
1673 IS_POLAR(element) ||
1674 IS_POLAR_CROSS(element) ||
1675 IS_DF_MIRROR(element) ||
1676 IS_DF_MIRROR_AUTO(element) ||
1677 element == EL_PRISM ||
1678 element == EL_REFRACTOR)
1680 int current_angle = laser.current_angle;
1683 laser.num_damages--;
1685 AddDamagedField(ELX, ELY);
1687 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1690 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1692 if (IS_MIRROR(element) ||
1693 IS_MIRROR_FIXED(element) ||
1694 IS_DF_MIRROR(element) ||
1695 IS_DF_MIRROR_AUTO(element))
1696 laser.current_angle = get_mirrored_angle(laser.current_angle,
1697 get_element_angle(element));
1699 if (element == EL_PRISM || element == EL_REFRACTOR)
1700 laser.current_angle = RND(16);
1702 XS = 2 * Step[laser.current_angle].x;
1703 YS = 2 * Step[laser.current_angle].y;
1705 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1710 LX += step_size * XS;
1711 LY += step_size * YS;
1713 // draw sparkles on mirror
1714 if ((IS_MIRROR(element) ||
1715 IS_MIRROR_FIXED(element) ||
1716 element == EL_PRISM) &&
1717 current_angle != laser.current_angle)
1719 MovDelay[ELX][ELY] = 11; // start animation
1722 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1723 current_angle != laser.current_angle)
1724 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1727 (get_opposite_angle(laser.current_angle) ==
1728 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1730 return (laser.overloaded ? TRUE : FALSE);
1733 if (element == EL_FUEL_FULL)
1735 laser.stops_inside_element = TRUE;
1740 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1742 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1744 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1745 element == EL_MINE ? EL_MINE_ACTIVE :
1746 EL_GRAY_BALL_ACTIVE);
1748 GfxFrame[ELX][ELY] = 0; // restart animation
1750 laser.dest_element_last = Tile[ELX][ELY];
1751 laser.dest_element_last_x = ELX;
1752 laser.dest_element_last_y = ELY;
1754 if (element == EL_MINE)
1755 laser.overloaded = TRUE;
1758 if (element == EL_KETTLE ||
1759 element == EL_CELL ||
1760 element == EL_KEY ||
1761 element == EL_LIGHTBALL ||
1762 element == EL_PACMAN ||
1763 IS_PACMAN(element) ||
1764 IS_ENVELOPE(element))
1766 if (!IS_PACMAN(element) &&
1767 !IS_ENVELOPE(element))
1770 if (element == EL_PACMAN)
1773 if (element == EL_KETTLE || element == EL_CELL)
1775 if (game_mm.kettles_still_needed > 0)
1776 game_mm.kettles_still_needed--;
1778 game.snapshot.collected_item = TRUE;
1780 if (game_mm.kettles_still_needed == 0)
1784 DrawLaser(0, DL_LASER_ENABLED);
1787 else if (element == EL_KEY)
1791 else if (IS_PACMAN(element))
1793 DeletePacMan(ELX, ELY);
1795 else if (IS_ENVELOPE(element))
1797 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1800 RaiseScoreElement_MM(element);
1805 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1807 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1809 DrawLaser(0, DL_LASER_ENABLED);
1811 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1813 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1814 game_mm.lights_still_needed--;
1818 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1819 game_mm.lights_still_needed++;
1822 DrawField_MM(ELX, ELY);
1823 DrawLaser(0, DL_LASER_ENABLED);
1828 laser.stops_inside_element = TRUE;
1834 Debug("game:mm:HitElement", "(4): element == %d", element);
1837 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1838 laser.num_beamers < MAX_NUM_BEAMERS &&
1839 laser.beamer[BEAMER_NR(element)][1].num)
1841 int beamer_angle = get_element_angle(element);
1842 int beamer_nr = BEAMER_NR(element);
1846 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1849 laser.num_damages--;
1851 if (IS_FIBRE_OPTIC(element) ||
1852 laser.current_angle == get_opposite_angle(beamer_angle))
1856 LX = ELX * TILEX + 14;
1857 LY = ELY * TILEY + 14;
1859 AddLaserEdge(LX, LY);
1860 AddDamagedField(ELX, ELY);
1862 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1865 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1867 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1868 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1869 ELX = laser.beamer[beamer_nr][pos].x;
1870 ELY = laser.beamer[beamer_nr][pos].y;
1871 LX = ELX * TILEX + 14;
1872 LY = ELY * TILEY + 14;
1874 if (IS_BEAMER(element))
1876 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1877 XS = 2 * Step[laser.current_angle].x;
1878 YS = 2 * Step[laser.current_angle].y;
1881 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1883 AddLaserEdge(LX, LY);
1884 AddDamagedField(ELX, ELY);
1886 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1889 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1891 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1896 LX += step_size * XS;
1897 LY += step_size * YS;
1899 laser.num_beamers++;
1908 static boolean HitOnlyAnEdge(int hit_mask)
1910 // check if the laser hit only the edge of an element and, if so, go on
1913 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1917 if ((hit_mask == HIT_MASK_TOPLEFT ||
1918 hit_mask == HIT_MASK_TOPRIGHT ||
1919 hit_mask == HIT_MASK_BOTTOMLEFT ||
1920 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1921 laser.current_angle % 4) // angle is not 90°
1925 if (hit_mask == HIT_MASK_TOPLEFT)
1930 else if (hit_mask == HIT_MASK_TOPRIGHT)
1935 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1940 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1946 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1952 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1959 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1965 static boolean HitPolarizer(int element, int hit_mask)
1967 if (HitOnlyAnEdge(hit_mask))
1970 if (IS_DF_GRID(element))
1972 int grid_angle = get_element_angle(element);
1975 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1976 grid_angle, laser.current_angle);
1979 AddLaserEdge(LX, LY);
1980 AddDamagedField(ELX, ELY);
1983 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1985 if (laser.current_angle == grid_angle ||
1986 laser.current_angle == get_opposite_angle(grid_angle))
1988 // skip the whole element before continuing the scan
1994 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1996 if (LX/TILEX > ELX || LY/TILEY > ELY)
1998 /* skipping scan positions to the right and down skips one scan
1999 position too much, because this is only the top left scan position
2000 of totally four scan positions (plus one to the right, one to the
2001 bottom and one to the bottom right) */
2007 AddLaserEdge(LX, LY);
2013 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2015 LX / TILEX, LY / TILEY,
2016 LX % TILEX, LY % TILEY);
2021 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2023 return HitReflectingWalls(element, hit_mask);
2027 return HitAbsorbingWalls(element, hit_mask);
2030 else if (IS_GRID_STEEL(element))
2032 // may be required if graphics for steel grid redefined
2033 AddDamagedField(ELX, ELY);
2035 return HitReflectingWalls(element, hit_mask);
2037 else // IS_GRID_WOOD
2039 // may be required if graphics for wooden grid redefined
2040 AddDamagedField(ELX, ELY);
2042 return HitAbsorbingWalls(element, hit_mask);
2048 static boolean HitBlock(int element, int hit_mask)
2050 boolean check = FALSE;
2052 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2053 game_mm.num_keys == 0)
2056 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2059 int ex = ELX * TILEX + 14;
2060 int ey = ELY * TILEY + 14;
2064 for (i = 1; i < 32; i++)
2069 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2074 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2075 return HitAbsorbingWalls(element, hit_mask);
2079 AddLaserEdge(LX - XS, LY - YS);
2080 AddDamagedField(ELX, ELY);
2083 Box[ELX][ELY] = laser.num_edges;
2085 return HitReflectingWalls(element, hit_mask);
2088 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2090 int xs = XS / 2, ys = YS / 2;
2092 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2093 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2095 laser.overloaded = (element == EL_GATE_STONE);
2100 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2101 (hit_mask == HIT_MASK_TOP ||
2102 hit_mask == HIT_MASK_LEFT ||
2103 hit_mask == HIT_MASK_RIGHT ||
2104 hit_mask == HIT_MASK_BOTTOM))
2105 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2106 hit_mask == HIT_MASK_BOTTOM),
2107 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2108 hit_mask == HIT_MASK_RIGHT));
2109 AddLaserEdge(LX, LY);
2115 if (element == EL_GATE_STONE && Box[ELX][ELY])
2117 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2129 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2131 int xs = XS / 2, ys = YS / 2;
2133 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2134 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2136 laser.overloaded = (element == EL_BLOCK_STONE);
2141 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2142 (hit_mask == HIT_MASK_TOP ||
2143 hit_mask == HIT_MASK_LEFT ||
2144 hit_mask == HIT_MASK_RIGHT ||
2145 hit_mask == HIT_MASK_BOTTOM))
2146 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2147 hit_mask == HIT_MASK_BOTTOM),
2148 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2149 hit_mask == HIT_MASK_RIGHT));
2150 AddDamagedField(ELX, ELY);
2152 LX = ELX * TILEX + 14;
2153 LY = ELY * TILEY + 14;
2155 AddLaserEdge(LX, LY);
2157 laser.stops_inside_element = TRUE;
2165 static boolean HitLaserSource(int element, int hit_mask)
2167 if (HitOnlyAnEdge(hit_mask))
2170 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2172 laser.overloaded = TRUE;
2177 static boolean HitLaserDestination(int element, int hit_mask)
2179 if (HitOnlyAnEdge(hit_mask))
2182 if (element != EL_EXIT_OPEN &&
2183 !(IS_RECEIVER(element) &&
2184 game_mm.kettles_still_needed == 0 &&
2185 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2187 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2192 if (IS_RECEIVER(element) ||
2193 (IS_22_5_ANGLE(laser.current_angle) &&
2194 (ELX != (LX + 6 * XS) / TILEX ||
2195 ELY != (LY + 6 * YS) / TILEY ||
2204 LX = ELX * TILEX + 14;
2205 LY = ELY * TILEY + 14;
2207 laser.stops_inside_element = TRUE;
2210 AddLaserEdge(LX, LY);
2211 AddDamagedField(ELX, ELY);
2213 if (game_mm.lights_still_needed == 0)
2215 game_mm.level_solved = TRUE;
2217 SetTileCursorActive(FALSE);
2223 static boolean HitReflectingWalls(int element, int hit_mask)
2225 // check if laser hits side of a wall with an angle that is not 90°
2226 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2227 hit_mask == HIT_MASK_LEFT ||
2228 hit_mask == HIT_MASK_RIGHT ||
2229 hit_mask == HIT_MASK_BOTTOM))
2231 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2236 if (!IS_DF_GRID(element))
2237 AddLaserEdge(LX, LY);
2239 // check if laser hits wall with an angle of 45°
2240 if (!IS_22_5_ANGLE(laser.current_angle))
2242 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2245 laser.current_angle = get_mirrored_angle(laser.current_angle,
2248 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2251 laser.current_angle = get_mirrored_angle(laser.current_angle,
2255 AddLaserEdge(LX, LY);
2257 XS = 2 * Step[laser.current_angle].x;
2258 YS = 2 * Step[laser.current_angle].y;
2262 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2264 laser.current_angle = get_mirrored_angle(laser.current_angle,
2269 if (!IS_DF_GRID(element))
2270 AddLaserEdge(LX, LY);
2275 if (!IS_DF_GRID(element))
2276 AddLaserEdge(LX, LY + YS / 2);
2279 if (!IS_DF_GRID(element))
2280 AddLaserEdge(LX, LY);
2283 YS = 2 * Step[laser.current_angle].y;
2287 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2289 laser.current_angle = get_mirrored_angle(laser.current_angle,
2294 if (!IS_DF_GRID(element))
2295 AddLaserEdge(LX, LY);
2300 if (!IS_DF_GRID(element))
2301 AddLaserEdge(LX + XS / 2, LY);
2304 if (!IS_DF_GRID(element))
2305 AddLaserEdge(LX, LY);
2308 XS = 2 * Step[laser.current_angle].x;
2314 // reflection at the edge of reflecting DF style wall
2315 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2317 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2318 hit_mask == HIT_MASK_TOPRIGHT) ||
2319 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2320 hit_mask == HIT_MASK_TOPLEFT) ||
2321 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2322 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2323 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2324 hit_mask == HIT_MASK_BOTTOMRIGHT))
2327 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2328 ANG_MIRROR_135 : ANG_MIRROR_45);
2330 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2332 AddDamagedField(ELX, ELY);
2333 AddLaserEdge(LX, LY);
2335 laser.current_angle = get_mirrored_angle(laser.current_angle,
2343 AddLaserEdge(LX, LY);
2349 // reflection inside an edge of reflecting DF style wall
2350 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2352 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2353 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2354 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2355 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2356 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2357 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2358 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2359 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2362 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2363 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2364 ANG_MIRROR_135 : ANG_MIRROR_45);
2366 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2369 AddDamagedField(ELX, ELY);
2372 AddLaserEdge(LX - XS, LY - YS);
2373 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2374 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2376 laser.current_angle = get_mirrored_angle(laser.current_angle,
2384 AddLaserEdge(LX, LY);
2390 // check if laser hits DF style wall with an angle of 90°
2391 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2393 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2394 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2395 (IS_VERT_ANGLE(laser.current_angle) &&
2396 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2398 // laser at last step touched nothing or the same side of the wall
2399 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2401 AddDamagedField(ELX, ELY);
2408 last_hit_mask = hit_mask;
2415 if (!HitOnlyAnEdge(hit_mask))
2417 laser.overloaded = TRUE;
2425 static boolean HitAbsorbingWalls(int element, int hit_mask)
2427 if (HitOnlyAnEdge(hit_mask))
2431 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2433 AddLaserEdge(LX - XS, LY - YS);
2440 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2442 AddLaserEdge(LX - XS, LY - YS);
2448 if (IS_WALL_WOOD(element) ||
2449 IS_DF_WALL_WOOD(element) ||
2450 IS_GRID_WOOD(element) ||
2451 IS_GRID_WOOD_FIXED(element) ||
2452 IS_GRID_WOOD_AUTO(element) ||
2453 element == EL_FUSE_ON ||
2454 element == EL_BLOCK_WOOD ||
2455 element == EL_GATE_WOOD)
2457 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2462 if (IS_WALL_ICE(element))
2468 // check if laser hit adjacent edges of two diagonal tiles
2469 if (ELX != lx / TILEX)
2471 if (ELY != ly / TILEY)
2474 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2475 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2477 // check if laser hits wall with an angle of 90°
2478 if (IS_90_ANGLE(laser.current_angle))
2479 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2481 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2485 for (i = 0; i < 4; i++)
2487 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2488 mask = 15 - (8 >> i);
2489 else if (ABS(XS) == 4 &&
2491 (XS > 0) == (i % 2) &&
2492 (YS < 0) == (i / 2))
2493 mask = 3 + (i / 2) * 9;
2494 else if (ABS(YS) == 4 &&
2496 (XS < 0) == (i % 2) &&
2497 (YS > 0) == (i / 2))
2498 mask = 5 + (i % 2) * 5;
2502 laser.wall_mask = mask;
2504 else if (IS_WALL_AMOEBA(element))
2506 int elx = (LX - 2 * XS) / TILEX;
2507 int ely = (LY - 2 * YS) / TILEY;
2508 int element2 = Tile[elx][ely];
2511 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2513 laser.dest_element = EL_EMPTY;
2521 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2522 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2524 if (IS_90_ANGLE(laser.current_angle))
2525 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2527 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2529 laser.wall_mask = mask;
2535 static void OpenExit(int x, int y)
2539 if (!MovDelay[x][y]) // next animation frame
2540 MovDelay[x][y] = 4 * delay;
2542 if (MovDelay[x][y]) // wait some time before next frame
2547 phase = MovDelay[x][y] / delay;
2549 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2550 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2552 if (!MovDelay[x][y])
2554 Tile[x][y] = EL_EXIT_OPEN;
2560 static void OpenGrayBall(int x, int y)
2564 if (!MovDelay[x][y]) // next animation frame
2566 if (IS_WALL(Store[x][y]))
2568 DrawWalls_MM(x, y, Store[x][y]);
2570 // copy wall tile to spare bitmap for "melting" animation
2571 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2572 TILEX, TILEY, x * TILEX, y * TILEY);
2574 DrawElement_MM(x, y, EL_GRAY_BALL);
2577 MovDelay[x][y] = 50 * delay;
2580 if (MovDelay[x][y]) // wait some time before next frame
2584 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2588 int dx = RND(26), dy = RND(26);
2590 if (IS_WALL(Store[x][y]))
2592 // copy wall tile from spare bitmap for "melting" animation
2593 bitmap = bitmap_db_field;
2599 int graphic = el2gfx(Store[x][y]);
2601 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2604 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2605 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2607 laser.redraw = TRUE;
2609 MarkTileDirty(x, y);
2612 if (!MovDelay[x][y])
2614 Tile[x][y] = Store[x][y];
2615 Store[x][y] = Store2[x][y] = 0;
2616 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2618 InitField(x, y, FALSE);
2621 ScanLaser_FromLastMirror();
2626 static void OpenEnvelope(int x, int y)
2628 int num_frames = 8; // seven frames plus final empty space
2630 if (!MovDelay[x][y]) // next animation frame
2631 MovDelay[x][y] = num_frames;
2633 if (MovDelay[x][y]) // wait some time before next frame
2635 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2639 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2641 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2642 int frame = num_frames - MovDelay[x][y] - 1;
2644 DrawGraphicAnimation_MM(x, y, graphic, frame);
2646 laser.redraw = TRUE;
2649 if (MovDelay[x][y] == 0)
2651 Tile[x][y] = EL_EMPTY;
2662 static void MeltIce(int x, int y)
2667 if (!MovDelay[x][y]) // next animation frame
2668 MovDelay[x][y] = frames * delay;
2670 if (MovDelay[x][y]) // wait some time before next frame
2673 int wall_mask = Store2[x][y];
2674 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2677 phase = frames - MovDelay[x][y] / delay - 1;
2679 if (!MovDelay[x][y])
2681 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2682 Store[x][y] = Store2[x][y] = 0;
2684 DrawWalls_MM(x, y, Tile[x][y]);
2686 if (Tile[x][y] == EL_WALL_ICE_BASE)
2687 Tile[x][y] = EL_EMPTY;
2689 ScanLaser_FromLastMirror();
2691 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2693 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2695 laser.redraw = TRUE;
2700 static void GrowAmoeba(int x, int y)
2705 if (!MovDelay[x][y]) // next animation frame
2706 MovDelay[x][y] = frames * delay;
2708 if (MovDelay[x][y]) // wait some time before next frame
2711 int wall_mask = Store2[x][y];
2712 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2715 phase = MovDelay[x][y] / delay;
2717 if (!MovDelay[x][y])
2719 Tile[x][y] = real_element;
2720 Store[x][y] = Store2[x][y] = 0;
2722 DrawWalls_MM(x, y, Tile[x][y]);
2723 DrawLaser(0, DL_LASER_ENABLED);
2725 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2727 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2732 static void DrawFieldAnimated_MM(int x, int y)
2736 laser.redraw = TRUE;
2739 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2741 int element = Tile[x][y];
2742 int graphic = el2gfx(element);
2744 if (!getGraphicInfo_NewFrame(x, y, graphic))
2749 laser.redraw = TRUE;
2752 static void DrawFieldTwinkle(int x, int y)
2754 if (MovDelay[x][y] != 0) // wait some time before next frame
2760 if (MovDelay[x][y] != 0)
2762 int graphic = IMG_TWINKLE_WHITE;
2763 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2765 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2768 laser.redraw = TRUE;
2772 static void Explode_MM(int x, int y, int phase, int mode)
2774 int num_phase = 9, delay = 2;
2775 int last_phase = num_phase * delay;
2776 int half_phase = (num_phase / 2) * delay;
2779 laser.redraw = TRUE;
2781 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2783 center_element = Tile[x][y];
2785 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2787 // put moving element to center field (and let it explode there)
2788 center_element = MovingOrBlocked2Element_MM(x, y);
2789 RemoveMovingField_MM(x, y);
2791 Tile[x][y] = center_element;
2794 if (center_element != EL_GRAY_BALL_ACTIVE)
2795 Store[x][y] = EL_EMPTY;
2796 Store2[x][y] = center_element;
2798 Tile[x][y] = EL_EXPLODING_OPAQUE;
2800 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2801 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2802 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2805 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2807 ExplodePhase[x][y] = 1;
2813 GfxFrame[x][y] = 0; // restart explosion animation
2815 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2817 center_element = Store2[x][y];
2819 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2821 Tile[x][y] = EL_EXPLODING_TRANSP;
2823 if (x == ELX && y == ELY)
2827 if (phase == last_phase)
2829 if (center_element == EL_BOMB_ACTIVE)
2831 DrawLaser(0, DL_LASER_DISABLED);
2834 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2836 laser.overloaded = FALSE;
2838 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2840 GameOver_MM(GAME_OVER_BOMB);
2843 Tile[x][y] = Store[x][y];
2845 Store[x][y] = Store2[x][y] = 0;
2846 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2848 InitField(x, y, FALSE);
2851 if (center_element == EL_GRAY_BALL_ACTIVE)
2852 ScanLaser_FromLastMirror();
2854 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2856 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2857 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2859 DrawGraphicAnimation_MM(x, y, graphic, frame);
2861 MarkTileDirty(x, y);
2865 static void Bang_MM(int x, int y)
2867 int element = Tile[x][y];
2869 if (IS_PACMAN(element))
2870 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2871 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2872 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2873 else if (element == EL_KEY)
2874 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2876 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2878 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2881 static void TurnRound(int x, int y)
2893 { 0, 0 }, { 0, 0 }, { 0, 0 },
2898 int left, right, back;
2902 { MV_DOWN, MV_UP, MV_RIGHT },
2903 { MV_UP, MV_DOWN, MV_LEFT },
2905 { MV_LEFT, MV_RIGHT, MV_DOWN },
2909 { MV_RIGHT, MV_LEFT, MV_UP }
2912 int element = Tile[x][y];
2913 int old_move_dir = MovDir[x][y];
2914 int right_dir = turn[old_move_dir].right;
2915 int back_dir = turn[old_move_dir].back;
2916 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2917 int right_x = x + right_dx, right_y = y + right_dy;
2919 if (element == EL_PACMAN)
2921 boolean can_turn_right = FALSE;
2923 if (IN_LEV_FIELD(right_x, right_y) &&
2924 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2925 can_turn_right = TRUE;
2928 MovDir[x][y] = right_dir;
2930 MovDir[x][y] = back_dir;
2936 static void StartMoving_MM(int x, int y)
2938 int element = Tile[x][y];
2943 if (CAN_MOVE(element))
2947 if (MovDelay[x][y]) // wait some time before next movement
2955 // now make next step
2957 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2959 if (element == EL_PACMAN &&
2960 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2961 !ObjHit(newx, newy, HIT_POS_CENTER))
2963 Store[newx][newy] = Tile[newx][newy];
2964 Tile[newx][newy] = EL_EMPTY;
2966 DrawField_MM(newx, newy);
2968 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2969 ObjHit(newx, newy, HIT_POS_CENTER))
2971 // object was running against a wall
2978 InitMovingField_MM(x, y, MovDir[x][y]);
2982 ContinueMoving_MM(x, y);
2985 static void ContinueMoving_MM(int x, int y)
2987 int element = Tile[x][y];
2988 int direction = MovDir[x][y];
2989 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2990 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2991 int horiz_move = (dx!=0);
2992 int newx = x + dx, newy = y + dy;
2993 int step = (horiz_move ? dx : dy) * TILEX / 8;
2995 MovPos[x][y] += step;
2997 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2999 Tile[x][y] = EL_EMPTY;
3000 Tile[newx][newy] = element;
3002 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3003 MovDelay[newx][newy] = 0;
3005 if (!CAN_MOVE(element))
3006 MovDir[newx][newy] = 0;
3009 DrawField_MM(newx, newy);
3011 Stop[newx][newy] = TRUE;
3013 if (element == EL_PACMAN)
3015 if (Store[newx][newy] == EL_BOMB)
3016 Bang_MM(newx, newy);
3018 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3019 (LX + 2 * XS) / TILEX == newx &&
3020 (LY + 2 * YS) / TILEY == newy)
3027 else // still moving on
3032 laser.redraw = TRUE;
3035 boolean ClickElement(int x, int y, int button)
3037 static DelayCounter click_delay = { CLICK_DELAY };
3038 static boolean new_button = TRUE;
3039 boolean element_clicked = FALSE;
3044 // initialize static variables
3045 click_delay.count = 0;
3046 click_delay.value = CLICK_DELAY;
3052 // do not rotate objects hit by the laser after the game was solved
3053 if (game_mm.level_solved && Hit[x][y])
3056 if (button == MB_RELEASED)
3059 click_delay.value = CLICK_DELAY;
3061 // release eventually hold auto-rotating mirror
3062 RotateMirror(x, y, MB_RELEASED);
3067 if (!FrameReached(&click_delay) && !new_button)
3070 if (button == MB_MIDDLEBUTTON) // middle button has no function
3073 if (!IN_LEV_FIELD(x, y))
3076 if (Tile[x][y] == EL_EMPTY)
3079 element = Tile[x][y];
3081 if (IS_MIRROR(element) ||
3082 IS_BEAMER(element) ||
3083 IS_POLAR(element) ||
3084 IS_POLAR_CROSS(element) ||
3085 IS_DF_MIRROR(element) ||
3086 IS_DF_MIRROR_AUTO(element))
3088 RotateMirror(x, y, button);
3090 element_clicked = TRUE;
3092 else if (IS_MCDUFFIN(element))
3094 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3096 if (has_laser && !laser.fuse_off)
3097 DrawLaser(0, DL_LASER_DISABLED);
3099 element = get_rotated_element(element, BUTTON_ROTATION(button));
3101 Tile[x][y] = element;
3106 laser.start_angle = get_element_angle(element);
3110 if (!laser.fuse_off)
3114 element_clicked = TRUE;
3116 else if (element == EL_FUSE_ON && laser.fuse_off)
3118 if (x != laser.fuse_x || y != laser.fuse_y)
3121 laser.fuse_off = FALSE;
3122 laser.fuse_x = laser.fuse_y = -1;
3124 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3127 element_clicked = TRUE;
3129 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3131 laser.fuse_off = TRUE;
3134 laser.overloaded = FALSE;
3136 DrawLaser(0, DL_LASER_DISABLED);
3137 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3139 element_clicked = TRUE;
3141 else if (element == EL_LIGHTBALL)
3144 RaiseScoreElement_MM(element);
3145 DrawLaser(0, DL_LASER_ENABLED);
3147 element_clicked = TRUE;
3149 else if (IS_ENVELOPE(element))
3151 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3153 element_clicked = TRUE;
3156 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3159 return element_clicked;
3162 static void RotateMirror(int x, int y, int button)
3164 if (button == MB_RELEASED)
3166 // release eventually hold auto-rotating mirror
3173 if (IS_MIRROR(Tile[x][y]) ||
3174 IS_POLAR_CROSS(Tile[x][y]) ||
3175 IS_POLAR(Tile[x][y]) ||
3176 IS_BEAMER(Tile[x][y]) ||
3177 IS_DF_MIRROR(Tile[x][y]) ||
3178 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3179 IS_GRID_WOOD_AUTO(Tile[x][y]))
3181 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3183 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3185 if (button == MB_LEFTBUTTON)
3187 // left mouse button only for manual adjustment, no auto-rotating;
3188 // freeze mirror for until mouse button released
3192 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3194 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3198 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3200 int edge = Hit[x][y];
3206 DrawLaser(edge - 1, DL_LASER_DISABLED);
3210 else if (ObjHit(x, y, HIT_POS_CENTER))
3212 int edge = Hit[x][y];
3216 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3221 DrawLaser(edge - 1, DL_LASER_DISABLED);
3228 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3233 if ((IS_BEAMER(Tile[x][y]) ||
3234 IS_POLAR(Tile[x][y]) ||
3235 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3237 if (IS_BEAMER(Tile[x][y]))
3240 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3241 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3254 DrawLaser(0, DL_LASER_ENABLED);
3258 static void AutoRotateMirrors(void)
3262 if (!FrameReached(&rotate_delay))
3265 for (x = 0; x < lev_fieldx; x++)
3267 for (y = 0; y < lev_fieldy; y++)
3269 int element = Tile[x][y];
3271 // do not rotate objects hit by the laser after the game was solved
3272 if (game_mm.level_solved && Hit[x][y])
3275 if (IS_DF_MIRROR_AUTO(element) ||
3276 IS_GRID_WOOD_AUTO(element) ||
3277 IS_GRID_STEEL_AUTO(element) ||
3278 element == EL_REFRACTOR)
3280 RotateMirror(x, y, MB_RIGHTBUTTON);
3282 laser.redraw = TRUE;
3288 static boolean ObjHit(int obx, int oby, int bits)
3295 if (bits & HIT_POS_CENTER)
3297 if (CheckLaserPixel(cSX + obx + 15,
3302 if (bits & HIT_POS_EDGE)
3304 for (i = 0; i < 4; i++)
3305 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3306 cSY + oby + 31 * (i / 2)))
3310 if (bits & HIT_POS_BETWEEN)
3312 for (i = 0; i < 4; i++)
3313 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3314 cSY + 4 + oby + 22 * (i / 2)))
3321 static void DeletePacMan(int px, int py)
3327 if (game_mm.num_pacman <= 1)
3329 game_mm.num_pacman = 0;
3333 for (i = 0; i < game_mm.num_pacman; i++)
3334 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3337 game_mm.num_pacman--;
3339 for (j = i; j < game_mm.num_pacman; j++)
3341 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3342 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3343 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3347 static void GameActions_MM_Ext(void)
3354 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3357 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3359 element = Tile[x][y];
3361 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3362 StartMoving_MM(x, y);
3363 else if (IS_MOVING(x, y))
3364 ContinueMoving_MM(x, y);
3365 else if (IS_EXPLODING(element))
3366 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3367 else if (element == EL_EXIT_OPENING)
3369 else if (element == EL_GRAY_BALL_OPENING)
3371 else if (IS_ENVELOPE_OPENING(element))
3373 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3375 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3377 else if (IS_MIRROR(element) ||
3378 IS_MIRROR_FIXED(element) ||
3379 element == EL_PRISM)
3380 DrawFieldTwinkle(x, y);
3381 else if (element == EL_GRAY_BALL_ACTIVE ||
3382 element == EL_BOMB_ACTIVE ||
3383 element == EL_MINE_ACTIVE)
3384 DrawFieldAnimated_MM(x, y);
3385 else if (!IS_BLOCKED(x, y))
3386 DrawFieldAnimatedIfNeeded_MM(x, y);
3389 AutoRotateMirrors();
3392 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3394 // redraw after Explode_MM() ...
3396 DrawLaser(0, DL_LASER_ENABLED);
3397 laser.redraw = FALSE;
3402 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3406 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3408 DrawLaser(0, DL_LASER_DISABLED);
3413 // skip all following game actions if game is over
3414 if (game_mm.game_over)
3417 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3421 GameOver_MM(GAME_OVER_NO_ENERGY);
3426 if (FrameReached(&energy_delay))
3428 if (game_mm.energy_left > 0)
3429 game_mm.energy_left--;
3431 // when out of energy, wait another frame to play "out of time" sound
3434 element = laser.dest_element;
3437 if (element != Tile[ELX][ELY])
3439 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3440 element, Tile[ELX][ELY]);
3444 if (!laser.overloaded && laser.overload_value == 0 &&
3445 element != EL_BOMB &&
3446 element != EL_BOMB_ACTIVE &&
3447 element != EL_MINE &&
3448 element != EL_MINE_ACTIVE &&
3449 element != EL_GRAY_BALL &&
3450 element != EL_GRAY_BALL_ACTIVE &&
3451 element != EL_BLOCK_STONE &&
3452 element != EL_BLOCK_WOOD &&
3453 element != EL_FUSE_ON &&
3454 element != EL_FUEL_FULL &&
3455 !IS_WALL_ICE(element) &&
3456 !IS_WALL_AMOEBA(element))
3459 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3461 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3462 (!laser.overloaded && laser.overload_value > 0)) &&
3463 FrameReached(&overload_delay))
3465 if (laser.overloaded)
3466 laser.overload_value++;
3468 laser.overload_value--;
3470 if (game_mm.cheat_no_overload)
3472 laser.overloaded = FALSE;
3473 laser.overload_value = 0;
3476 game_mm.laser_overload_value = laser.overload_value;
3478 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3480 SetLaserColor(0xFF);
3482 DrawLaser(0, DL_LASER_ENABLED);
3485 if (!laser.overloaded)
3486 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3487 else if (setup.sound_loops)
3488 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3490 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3492 if (laser.overload_value == MAX_LASER_OVERLOAD)
3494 UpdateAndDisplayGameControlValues();
3498 GameOver_MM(GAME_OVER_OVERLOADED);
3509 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3511 if (game_mm.cheat_no_explosion)
3516 laser.dest_element = EL_EXPLODING_OPAQUE;
3521 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3523 laser.fuse_off = TRUE;
3527 DrawLaser(0, DL_LASER_DISABLED);
3528 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3531 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3533 if (!Store2[ELX][ELY]) // check if content element not yet determined
3535 int last_anim_random_frame = gfx.anim_random_frame;
3538 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3539 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3541 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3542 native_mm_level.ball_choice_mode, 0,
3543 game_mm.ball_choice_pos);
3545 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3546 gfx.anim_random_frame = last_anim_random_frame;
3548 game_mm.ball_choice_pos++;
3550 int new_element = native_mm_level.ball_content[element_pos];
3551 int new_element_base = map_wall_to_base_element(new_element);
3553 if (IS_WALL(new_element_base))
3555 // always use completely filled wall element
3556 new_element = new_element_base | 0x000f;
3558 else if (native_mm_level.rotate_ball_content &&
3559 get_num_elements(new_element) > 1)
3561 // randomly rotate newly created game element
3562 new_element = get_rotated_element(new_element, RND(16));
3565 Store[ELX][ELY] = new_element;
3566 Store2[ELX][ELY] = TRUE;
3569 if (native_mm_level.explode_ball)
3572 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3574 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3579 if (IS_WALL_ICE(element) && CT > 50)
3581 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3583 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3584 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3585 Store2[ELX][ELY] = laser.wall_mask;
3587 laser.dest_element = Tile[ELX][ELY];
3592 if (IS_WALL_AMOEBA(element) && CT > 60)
3595 int element2 = Tile[ELX][ELY];
3597 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3600 for (i = laser.num_damages - 1; i >= 0; i--)
3601 if (laser.damage[i].is_mirror)
3604 r = laser.num_edges;
3605 d = laser.num_damages;
3612 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3615 DrawLaser(0, DL_LASER_ENABLED);
3618 x = laser.damage[k1].x;
3619 y = laser.damage[k1].y;
3624 for (i = 0; i < 4; i++)
3626 if (laser.wall_mask & (1 << i))
3628 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3629 cSY + ELY * TILEY + 31 * (i / 2)))
3632 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3633 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3640 for (i = 0; i < 4; i++)
3642 if (laser.wall_mask & (1 << i))
3644 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3645 cSY + ELY * TILEY + 31 * (i / 2)))
3652 if (laser.num_beamers > 0 ||
3653 k1 < 1 || k2 < 4 || k3 < 4 ||
3654 CheckLaserPixel(cSX + ELX * TILEX + 14,
3655 cSY + ELY * TILEY + 14))
3657 laser.num_edges = r;
3658 laser.num_damages = d;
3660 DrawLaser(0, DL_LASER_DISABLED);
3663 Tile[ELX][ELY] = element | laser.wall_mask;
3665 int x = ELX, y = ELY;
3666 int wall_mask = laser.wall_mask;
3669 DrawLaser(0, DL_LASER_ENABLED);
3671 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3673 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3674 Store[x][y] = EL_WALL_AMOEBA_BASE;
3675 Store2[x][y] = wall_mask;
3680 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3681 laser.stops_inside_element && CT > native_mm_level.time_block)
3686 if (ABS(XS) > ABS(YS))
3693 for (i = 0; i < 4; i++)
3700 x = ELX + Step[k * 4].x;
3701 y = ELY + Step[k * 4].y;
3703 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3706 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3714 laser.overloaded = (element == EL_BLOCK_STONE);
3719 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3722 Tile[x][y] = element;
3724 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3727 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3729 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3730 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3738 if (element == EL_FUEL_FULL && CT > 10)
3740 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3741 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3743 for (i = start; i <= num_init_game_frames; i++)
3745 if (i == num_init_game_frames)
3746 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3747 else if (setup.sound_loops)
3748 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3750 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3752 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3754 UpdateAndDisplayGameControlValues();
3759 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3761 DrawField_MM(ELX, ELY);
3763 DrawLaser(0, DL_LASER_ENABLED);
3769 void GameActions_MM(struct MouseActionInfo action)
3771 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3772 boolean button_released = (action.button == MB_RELEASED);
3774 GameActions_MM_Ext();
3776 CheckSingleStepMode_MM(element_clicked, button_released);
3779 static void MovePacMen(void)
3781 int mx, my, ox, oy, nx, ny;
3785 if (++pacman_nr >= game_mm.num_pacman)
3788 game_mm.pacman[pacman_nr].dir--;
3790 for (l = 1; l < 5; l++)
3792 game_mm.pacman[pacman_nr].dir++;
3794 if (game_mm.pacman[pacman_nr].dir > 4)
3795 game_mm.pacman[pacman_nr].dir = 1;
3797 if (game_mm.pacman[pacman_nr].dir % 2)
3800 my = game_mm.pacman[pacman_nr].dir - 2;
3805 mx = 3 - game_mm.pacman[pacman_nr].dir;
3808 ox = game_mm.pacman[pacman_nr].x;
3809 oy = game_mm.pacman[pacman_nr].y;
3812 element = Tile[nx][ny];
3814 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3817 if (!IS_EATABLE4PACMAN(element))
3820 if (ObjHit(nx, ny, HIT_POS_CENTER))
3823 Tile[ox][oy] = EL_EMPTY;
3825 EL_PACMAN_RIGHT - 1 +
3826 (game_mm.pacman[pacman_nr].dir - 1 +
3827 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3829 game_mm.pacman[pacman_nr].x = nx;
3830 game_mm.pacman[pacman_nr].y = ny;
3832 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3834 if (element != EL_EMPTY)
3836 int graphic = el2gfx(Tile[nx][ny]);
3841 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3844 ox = cSX + ox * TILEX;
3845 oy = cSY + oy * TILEY;
3847 for (i = 1; i < 33; i += 2)
3848 BlitBitmap(bitmap, window,
3849 src_x, src_y, TILEX, TILEY,
3850 ox + i * mx, oy + i * my);
3851 Ct = Ct + FrameCounter - CT;
3854 DrawField_MM(nx, ny);
3857 if (!laser.fuse_off)
3859 DrawLaser(0, DL_LASER_ENABLED);
3861 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3863 AddDamagedField(nx, ny);
3865 laser.damage[laser.num_damages - 1].edge = 0;
3869 if (element == EL_BOMB)
3870 DeletePacMan(nx, ny);
3872 if (IS_WALL_AMOEBA(element) &&
3873 (LX + 2 * XS) / TILEX == nx &&
3874 (LY + 2 * YS) / TILEY == ny)
3884 static void InitMovingField_MM(int x, int y, int direction)
3886 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3887 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3889 MovDir[x][y] = direction;
3890 MovDir[newx][newy] = direction;
3892 if (Tile[newx][newy] == EL_EMPTY)
3893 Tile[newx][newy] = EL_BLOCKED;
3896 static int MovingOrBlocked2Element_MM(int x, int y)
3898 int element = Tile[x][y];
3900 if (element == EL_BLOCKED)
3904 Blocked2Moving(x, y, &oldx, &oldy);
3906 return Tile[oldx][oldy];
3912 static void RemoveMovingField_MM(int x, int y)
3914 int oldx = x, oldy = y, newx = x, newy = y;
3916 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3919 if (IS_MOVING(x, y))
3921 Moving2Blocked(x, y, &newx, &newy);
3922 if (Tile[newx][newy] != EL_BLOCKED)
3925 else if (Tile[x][y] == EL_BLOCKED)
3927 Blocked2Moving(x, y, &oldx, &oldy);
3928 if (!IS_MOVING(oldx, oldy))
3932 Tile[oldx][oldy] = EL_EMPTY;
3933 Tile[newx][newy] = EL_EMPTY;
3934 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3935 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3937 DrawLevelField_MM(oldx, oldy);
3938 DrawLevelField_MM(newx, newy);
3941 static void RaiseScore_MM(int value)
3943 game_mm.score += value;
3946 void RaiseScoreElement_MM(int element)
3951 case EL_PACMAN_RIGHT:
3953 case EL_PACMAN_LEFT:
3954 case EL_PACMAN_DOWN:
3955 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3959 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3964 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3968 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3977 // ----------------------------------------------------------------------------
3978 // Mirror Magic game engine snapshot handling functions
3979 // ----------------------------------------------------------------------------
3981 void SaveEngineSnapshotValues_MM(void)
3985 engine_snapshot_mm.game_mm = game_mm;
3986 engine_snapshot_mm.laser = laser;
3988 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3990 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3992 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3993 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3994 engine_snapshot_mm.Box[x][y] = Box[x][y];
3995 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3999 engine_snapshot_mm.LX = LX;
4000 engine_snapshot_mm.LY = LY;
4001 engine_snapshot_mm.XS = XS;
4002 engine_snapshot_mm.YS = YS;
4003 engine_snapshot_mm.ELX = ELX;
4004 engine_snapshot_mm.ELY = ELY;
4005 engine_snapshot_mm.CT = CT;
4006 engine_snapshot_mm.Ct = Ct;
4008 engine_snapshot_mm.last_LX = last_LX;
4009 engine_snapshot_mm.last_LY = last_LY;
4010 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4011 engine_snapshot_mm.hold_x = hold_x;
4012 engine_snapshot_mm.hold_y = hold_y;
4013 engine_snapshot_mm.pacman_nr = pacman_nr;
4015 engine_snapshot_mm.rotate_delay = rotate_delay;
4016 engine_snapshot_mm.pacman_delay = pacman_delay;
4017 engine_snapshot_mm.energy_delay = energy_delay;
4018 engine_snapshot_mm.overload_delay = overload_delay;
4021 void LoadEngineSnapshotValues_MM(void)
4025 // stored engine snapshot buffers already restored at this point
4027 game_mm = engine_snapshot_mm.game_mm;
4028 laser = engine_snapshot_mm.laser;
4030 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4032 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4034 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4035 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4036 Box[x][y] = engine_snapshot_mm.Box[x][y];
4037 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4041 LX = engine_snapshot_mm.LX;
4042 LY = engine_snapshot_mm.LY;
4043 XS = engine_snapshot_mm.XS;
4044 YS = engine_snapshot_mm.YS;
4045 ELX = engine_snapshot_mm.ELX;
4046 ELY = engine_snapshot_mm.ELY;
4047 CT = engine_snapshot_mm.CT;
4048 Ct = engine_snapshot_mm.Ct;
4050 last_LX = engine_snapshot_mm.last_LX;
4051 last_LY = engine_snapshot_mm.last_LY;
4052 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4053 hold_x = engine_snapshot_mm.hold_x;
4054 hold_y = engine_snapshot_mm.hold_y;
4055 pacman_nr = engine_snapshot_mm.pacman_nr;
4057 rotate_delay = engine_snapshot_mm.rotate_delay;
4058 pacman_delay = engine_snapshot_mm.pacman_delay;
4059 energy_delay = engine_snapshot_mm.energy_delay;
4060 overload_delay = engine_snapshot_mm.overload_delay;
4062 RedrawPlayfield_MM();
4065 static int getAngleFromTouchDelta(int dx, int dy, int base)
4067 double pi = 3.141592653;
4068 double rad = atan2((double)-dy, (double)dx);
4069 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4070 double deg = rad2 * 180.0 / pi;
4072 return (int)(deg * base / 360.0 + 0.5) % base;
4075 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4077 // calculate start (source) position to be at the middle of the tile
4078 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4079 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4080 int dx = dst_mx - src_mx;
4081 int dy = dst_my - src_my;
4090 if (!IN_LEV_FIELD(x, y))
4093 element = Tile[x][y];
4095 if (!IS_MCDUFFIN(element) &&
4096 !IS_MIRROR(element) &&
4097 !IS_BEAMER(element) &&
4098 !IS_POLAR(element) &&
4099 !IS_POLAR_CROSS(element) &&
4100 !IS_DF_MIRROR(element))
4103 angle_old = get_element_angle(element);
4105 if (IS_MCDUFFIN(element))
4107 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4108 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4109 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4110 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4113 else if (IS_MIRROR(element) ||
4114 IS_DF_MIRROR(element))
4116 for (i = 0; i < laser.num_damages; i++)
4118 if (laser.damage[i].x == x &&
4119 laser.damage[i].y == y &&
4120 ObjHit(x, y, HIT_POS_CENTER))
4122 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4123 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4130 if (angle_new == -1)
4132 if (IS_MIRROR(element) ||
4133 IS_DF_MIRROR(element) ||
4137 if (IS_POLAR_CROSS(element))
4140 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4143 button = (angle_new == angle_old ? 0 :
4144 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4145 MB_LEFTBUTTON : MB_RIGHTBUTTON);