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)
3279 RotateMirror(x, y, MB_RIGHTBUTTON);
3284 static boolean ObjHit(int obx, int oby, int bits)
3291 if (bits & HIT_POS_CENTER)
3293 if (CheckLaserPixel(cSX + obx + 15,
3298 if (bits & HIT_POS_EDGE)
3300 for (i = 0; i < 4; i++)
3301 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3302 cSY + oby + 31 * (i / 2)))
3306 if (bits & HIT_POS_BETWEEN)
3308 for (i = 0; i < 4; i++)
3309 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3310 cSY + 4 + oby + 22 * (i / 2)))
3317 static void DeletePacMan(int px, int py)
3323 if (game_mm.num_pacman <= 1)
3325 game_mm.num_pacman = 0;
3329 for (i = 0; i < game_mm.num_pacman; i++)
3330 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3333 game_mm.num_pacman--;
3335 for (j = i; j < game_mm.num_pacman; j++)
3337 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3338 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3339 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3343 static void GameActions_MM_Ext(void)
3350 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3353 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3355 element = Tile[x][y];
3357 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3358 StartMoving_MM(x, y);
3359 else if (IS_MOVING(x, y))
3360 ContinueMoving_MM(x, y);
3361 else if (IS_EXPLODING(element))
3362 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3363 else if (element == EL_EXIT_OPENING)
3365 else if (element == EL_GRAY_BALL_OPENING)
3367 else if (IS_ENVELOPE_OPENING(element))
3369 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3371 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3373 else if (IS_MIRROR(element) ||
3374 IS_MIRROR_FIXED(element) ||
3375 element == EL_PRISM)
3376 DrawFieldTwinkle(x, y);
3377 else if (element == EL_GRAY_BALL_ACTIVE ||
3378 element == EL_BOMB_ACTIVE ||
3379 element == EL_MINE_ACTIVE)
3380 DrawFieldAnimated_MM(x, y);
3381 else if (!IS_BLOCKED(x, y))
3382 DrawFieldAnimatedIfNeeded_MM(x, y);
3385 AutoRotateMirrors();
3388 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3390 // redraw after Explode_MM() ...
3392 DrawLaser(0, DL_LASER_ENABLED);
3393 laser.redraw = FALSE;
3398 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3402 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3404 DrawLaser(0, DL_LASER_DISABLED);
3409 // skip all following game actions if game is over
3410 if (game_mm.game_over)
3413 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3417 GameOver_MM(GAME_OVER_NO_ENERGY);
3422 if (FrameReached(&energy_delay))
3424 if (game_mm.energy_left > 0)
3425 game_mm.energy_left--;
3427 // when out of energy, wait another frame to play "out of time" sound
3430 element = laser.dest_element;
3433 if (element != Tile[ELX][ELY])
3435 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3436 element, Tile[ELX][ELY]);
3440 if (!laser.overloaded && laser.overload_value == 0 &&
3441 element != EL_BOMB &&
3442 element != EL_BOMB_ACTIVE &&
3443 element != EL_MINE &&
3444 element != EL_MINE_ACTIVE &&
3445 element != EL_GRAY_BALL &&
3446 element != EL_GRAY_BALL_ACTIVE &&
3447 element != EL_BLOCK_STONE &&
3448 element != EL_BLOCK_WOOD &&
3449 element != EL_FUSE_ON &&
3450 element != EL_FUEL_FULL &&
3451 !IS_WALL_ICE(element) &&
3452 !IS_WALL_AMOEBA(element))
3455 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3457 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3458 (!laser.overloaded && laser.overload_value > 0)) &&
3459 FrameReached(&overload_delay))
3461 if (laser.overloaded)
3462 laser.overload_value++;
3464 laser.overload_value--;
3466 if (game_mm.cheat_no_overload)
3468 laser.overloaded = FALSE;
3469 laser.overload_value = 0;
3472 game_mm.laser_overload_value = laser.overload_value;
3474 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3476 SetLaserColor(0xFF);
3478 DrawLaser(0, DL_LASER_ENABLED);
3481 if (!laser.overloaded)
3482 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3483 else if (setup.sound_loops)
3484 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3486 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3488 if (laser.overload_value == MAX_LASER_OVERLOAD)
3490 UpdateAndDisplayGameControlValues();
3494 GameOver_MM(GAME_OVER_OVERLOADED);
3505 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3507 if (game_mm.cheat_no_explosion)
3512 laser.dest_element = EL_EXPLODING_OPAQUE;
3517 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3519 laser.fuse_off = TRUE;
3523 DrawLaser(0, DL_LASER_DISABLED);
3524 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3527 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3529 if (!Store2[ELX][ELY]) // check if content element not yet determined
3531 int last_anim_random_frame = gfx.anim_random_frame;
3534 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3535 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3537 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3538 native_mm_level.ball_choice_mode, 0,
3539 game_mm.ball_choice_pos);
3541 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3542 gfx.anim_random_frame = last_anim_random_frame;
3544 game_mm.ball_choice_pos++;
3546 int new_element = native_mm_level.ball_content[element_pos];
3547 int new_element_base = map_wall_to_base_element(new_element);
3549 if (IS_WALL(new_element_base))
3551 // always use completely filled wall element
3552 new_element = new_element_base | 0x000f;
3554 else if (native_mm_level.rotate_ball_content &&
3555 get_num_elements(new_element) > 1)
3557 // randomly rotate newly created game element
3558 new_element = get_rotated_element(new_element, RND(16));
3561 Store[ELX][ELY] = new_element;
3562 Store2[ELX][ELY] = TRUE;
3565 if (native_mm_level.explode_ball)
3568 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3570 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3575 if (IS_WALL_ICE(element) && CT > 50)
3577 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3579 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3580 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3581 Store2[ELX][ELY] = laser.wall_mask;
3583 laser.dest_element = Tile[ELX][ELY];
3588 if (IS_WALL_AMOEBA(element) && CT > 60)
3591 int element2 = Tile[ELX][ELY];
3593 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3596 for (i = laser.num_damages - 1; i >= 0; i--)
3597 if (laser.damage[i].is_mirror)
3600 r = laser.num_edges;
3601 d = laser.num_damages;
3608 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3611 DrawLaser(0, DL_LASER_ENABLED);
3614 x = laser.damage[k1].x;
3615 y = laser.damage[k1].y;
3620 for (i = 0; i < 4; i++)
3622 if (laser.wall_mask & (1 << i))
3624 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3625 cSY + ELY * TILEY + 31 * (i / 2)))
3628 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3629 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3636 for (i = 0; i < 4; i++)
3638 if (laser.wall_mask & (1 << i))
3640 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3641 cSY + ELY * TILEY + 31 * (i / 2)))
3648 if (laser.num_beamers > 0 ||
3649 k1 < 1 || k2 < 4 || k3 < 4 ||
3650 CheckLaserPixel(cSX + ELX * TILEX + 14,
3651 cSY + ELY * TILEY + 14))
3653 laser.num_edges = r;
3654 laser.num_damages = d;
3656 DrawLaser(0, DL_LASER_DISABLED);
3659 Tile[ELX][ELY] = element | laser.wall_mask;
3661 int x = ELX, y = ELY;
3662 int wall_mask = laser.wall_mask;
3665 DrawLaser(0, DL_LASER_ENABLED);
3667 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3669 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3670 Store[x][y] = EL_WALL_AMOEBA_BASE;
3671 Store2[x][y] = wall_mask;
3676 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3677 laser.stops_inside_element && CT > native_mm_level.time_block)
3682 if (ABS(XS) > ABS(YS))
3689 for (i = 0; i < 4; i++)
3696 x = ELX + Step[k * 4].x;
3697 y = ELY + Step[k * 4].y;
3699 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3702 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3710 laser.overloaded = (element == EL_BLOCK_STONE);
3715 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3718 Tile[x][y] = element;
3720 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3723 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3725 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3726 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3734 if (element == EL_FUEL_FULL && CT > 10)
3736 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3737 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3739 for (i = start; i <= num_init_game_frames; i++)
3741 if (i == num_init_game_frames)
3742 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3743 else if (setup.sound_loops)
3744 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3746 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3748 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3750 UpdateAndDisplayGameControlValues();
3755 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3757 DrawField_MM(ELX, ELY);
3759 DrawLaser(0, DL_LASER_ENABLED);
3765 void GameActions_MM(struct MouseActionInfo action)
3767 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3768 boolean button_released = (action.button == MB_RELEASED);
3770 GameActions_MM_Ext();
3772 CheckSingleStepMode_MM(element_clicked, button_released);
3775 static void MovePacMen(void)
3777 int mx, my, ox, oy, nx, ny;
3781 if (++pacman_nr >= game_mm.num_pacman)
3784 game_mm.pacman[pacman_nr].dir--;
3786 for (l = 1; l < 5; l++)
3788 game_mm.pacman[pacman_nr].dir++;
3790 if (game_mm.pacman[pacman_nr].dir > 4)
3791 game_mm.pacman[pacman_nr].dir = 1;
3793 if (game_mm.pacman[pacman_nr].dir % 2)
3796 my = game_mm.pacman[pacman_nr].dir - 2;
3801 mx = 3 - game_mm.pacman[pacman_nr].dir;
3804 ox = game_mm.pacman[pacman_nr].x;
3805 oy = game_mm.pacman[pacman_nr].y;
3808 element = Tile[nx][ny];
3810 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3813 if (!IS_EATABLE4PACMAN(element))
3816 if (ObjHit(nx, ny, HIT_POS_CENTER))
3819 Tile[ox][oy] = EL_EMPTY;
3821 EL_PACMAN_RIGHT - 1 +
3822 (game_mm.pacman[pacman_nr].dir - 1 +
3823 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3825 game_mm.pacman[pacman_nr].x = nx;
3826 game_mm.pacman[pacman_nr].y = ny;
3828 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3830 if (element != EL_EMPTY)
3832 int graphic = el2gfx(Tile[nx][ny]);
3837 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3840 ox = cSX + ox * TILEX;
3841 oy = cSY + oy * TILEY;
3843 for (i = 1; i < 33; i += 2)
3844 BlitBitmap(bitmap, window,
3845 src_x, src_y, TILEX, TILEY,
3846 ox + i * mx, oy + i * my);
3847 Ct = Ct + FrameCounter - CT;
3850 DrawField_MM(nx, ny);
3853 if (!laser.fuse_off)
3855 DrawLaser(0, DL_LASER_ENABLED);
3857 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3859 AddDamagedField(nx, ny);
3861 laser.damage[laser.num_damages - 1].edge = 0;
3865 if (element == EL_BOMB)
3866 DeletePacMan(nx, ny);
3868 if (IS_WALL_AMOEBA(element) &&
3869 (LX + 2 * XS) / TILEX == nx &&
3870 (LY + 2 * YS) / TILEY == ny)
3880 static void InitMovingField_MM(int x, int y, int direction)
3882 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3883 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3885 MovDir[x][y] = direction;
3886 MovDir[newx][newy] = direction;
3888 if (Tile[newx][newy] == EL_EMPTY)
3889 Tile[newx][newy] = EL_BLOCKED;
3892 static int MovingOrBlocked2Element_MM(int x, int y)
3894 int element = Tile[x][y];
3896 if (element == EL_BLOCKED)
3900 Blocked2Moving(x, y, &oldx, &oldy);
3902 return Tile[oldx][oldy];
3908 static void RemoveMovingField_MM(int x, int y)
3910 int oldx = x, oldy = y, newx = x, newy = y;
3912 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3915 if (IS_MOVING(x, y))
3917 Moving2Blocked(x, y, &newx, &newy);
3918 if (Tile[newx][newy] != EL_BLOCKED)
3921 else if (Tile[x][y] == EL_BLOCKED)
3923 Blocked2Moving(x, y, &oldx, &oldy);
3924 if (!IS_MOVING(oldx, oldy))
3928 Tile[oldx][oldy] = EL_EMPTY;
3929 Tile[newx][newy] = EL_EMPTY;
3930 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3931 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3933 DrawLevelField_MM(oldx, oldy);
3934 DrawLevelField_MM(newx, newy);
3937 static void RaiseScore_MM(int value)
3939 game_mm.score += value;
3942 void RaiseScoreElement_MM(int element)
3947 case EL_PACMAN_RIGHT:
3949 case EL_PACMAN_LEFT:
3950 case EL_PACMAN_DOWN:
3951 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3955 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3960 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3964 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3973 // ----------------------------------------------------------------------------
3974 // Mirror Magic game engine snapshot handling functions
3975 // ----------------------------------------------------------------------------
3977 void SaveEngineSnapshotValues_MM(void)
3981 engine_snapshot_mm.game_mm = game_mm;
3982 engine_snapshot_mm.laser = laser;
3984 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3986 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3988 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3989 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3990 engine_snapshot_mm.Box[x][y] = Box[x][y];
3991 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3995 engine_snapshot_mm.LX = LX;
3996 engine_snapshot_mm.LY = LY;
3997 engine_snapshot_mm.XS = XS;
3998 engine_snapshot_mm.YS = YS;
3999 engine_snapshot_mm.ELX = ELX;
4000 engine_snapshot_mm.ELY = ELY;
4001 engine_snapshot_mm.CT = CT;
4002 engine_snapshot_mm.Ct = Ct;
4004 engine_snapshot_mm.last_LX = last_LX;
4005 engine_snapshot_mm.last_LY = last_LY;
4006 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4007 engine_snapshot_mm.hold_x = hold_x;
4008 engine_snapshot_mm.hold_y = hold_y;
4009 engine_snapshot_mm.pacman_nr = pacman_nr;
4011 engine_snapshot_mm.rotate_delay = rotate_delay;
4012 engine_snapshot_mm.pacman_delay = pacman_delay;
4013 engine_snapshot_mm.energy_delay = energy_delay;
4014 engine_snapshot_mm.overload_delay = overload_delay;
4017 void LoadEngineSnapshotValues_MM(void)
4021 // stored engine snapshot buffers already restored at this point
4023 game_mm = engine_snapshot_mm.game_mm;
4024 laser = engine_snapshot_mm.laser;
4026 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4028 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4030 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4031 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4032 Box[x][y] = engine_snapshot_mm.Box[x][y];
4033 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4037 LX = engine_snapshot_mm.LX;
4038 LY = engine_snapshot_mm.LY;
4039 XS = engine_snapshot_mm.XS;
4040 YS = engine_snapshot_mm.YS;
4041 ELX = engine_snapshot_mm.ELX;
4042 ELY = engine_snapshot_mm.ELY;
4043 CT = engine_snapshot_mm.CT;
4044 Ct = engine_snapshot_mm.Ct;
4046 last_LX = engine_snapshot_mm.last_LX;
4047 last_LY = engine_snapshot_mm.last_LY;
4048 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4049 hold_x = engine_snapshot_mm.hold_x;
4050 hold_y = engine_snapshot_mm.hold_y;
4051 pacman_nr = engine_snapshot_mm.pacman_nr;
4053 rotate_delay = engine_snapshot_mm.rotate_delay;
4054 pacman_delay = engine_snapshot_mm.pacman_delay;
4055 energy_delay = engine_snapshot_mm.energy_delay;
4056 overload_delay = engine_snapshot_mm.overload_delay;
4058 RedrawPlayfield_MM();
4061 static int getAngleFromTouchDelta(int dx, int dy, int base)
4063 double pi = 3.141592653;
4064 double rad = atan2((double)-dy, (double)dx);
4065 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4066 double deg = rad2 * 180.0 / pi;
4068 return (int)(deg * base / 360.0 + 0.5) % base;
4071 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4073 // calculate start (source) position to be at the middle of the tile
4074 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4075 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4076 int dx = dst_mx - src_mx;
4077 int dy = dst_my - src_my;
4086 if (!IN_LEV_FIELD(x, y))
4089 element = Tile[x][y];
4091 if (!IS_MCDUFFIN(element) &&
4092 !IS_MIRROR(element) &&
4093 !IS_BEAMER(element) &&
4094 !IS_POLAR(element) &&
4095 !IS_POLAR_CROSS(element) &&
4096 !IS_DF_MIRROR(element))
4099 angle_old = get_element_angle(element);
4101 if (IS_MCDUFFIN(element))
4103 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4104 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4105 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4106 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4109 else if (IS_MIRROR(element) ||
4110 IS_DF_MIRROR(element))
4112 for (i = 0; i < laser.num_damages; i++)
4114 if (laser.damage[i].x == x &&
4115 laser.damage[i].y == y &&
4116 ObjHit(x, y, HIT_POS_CENTER))
4118 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4119 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4126 if (angle_new == -1)
4128 if (IS_MIRROR(element) ||
4129 IS_DF_MIRROR(element) ||
4133 if (IS_POLAR_CROSS(element))
4136 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4139 button = (angle_new == angle_old ? 0 :
4140 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4141 MB_LEFTBUTTON : MB_RIGHTBUTTON);