1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
100 static void Moving2Blocked_MM(int, int, int *, int *);
102 static void AddLaserEdge(int, int);
103 static void ScanLaser(void);
104 static void DrawLaser(int, int);
105 static boolean HitElement(int, int);
106 static boolean HitOnlyAnEdge(int);
107 static boolean HitPolarizer(int, int);
108 static boolean HitBlock(int, int);
109 static boolean HitLaserSource(int, int);
110 static boolean HitLaserDestination(int, int);
111 static boolean HitReflectingWalls(int, int);
112 static boolean HitAbsorbingWalls(int, int);
113 static void RotateMirror(int, int, int);
114 static boolean ObjHit(int, int, int);
115 static void DeletePacMan(int, int);
116 static void MovePacMen(void);
118 // bitmap for laser beam detection
119 static Bitmap *laser_bitmap = NULL;
121 // variables for laser control
122 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
123 static int hold_x = -1, hold_y = -1;
125 // variables for pacman control
126 static int pacman_nr = -1;
128 // various game engine delay counters
129 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
130 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
131 static DelayCounter energy_delay = { ENERGY_DELAY };
132 static DelayCounter overload_delay = { 0 };
134 // element mask positions for scanning pixels of MM elements
135 #define MM_MASK_MCDUFFIN_RIGHT 0
136 #define MM_MASK_MCDUFFIN_UP 1
137 #define MM_MASK_MCDUFFIN_LEFT 2
138 #define MM_MASK_MCDUFFIN_DOWN 3
139 #define MM_MASK_GRID_1 4
140 #define MM_MASK_GRID_2 5
141 #define MM_MASK_GRID_3 6
142 #define MM_MASK_GRID_4 7
143 #define MM_MASK_RECTANGLE 8
144 #define MM_MASK_CIRCLE 9
146 #define NUM_MM_MASKS 10
148 // element masks for scanning pixels of MM elements
149 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
333 static int get_element_angle(int element)
335 int element_phase = get_element_phase(element);
337 if (IS_MIRROR_FIXED(element) ||
338 IS_MCDUFFIN(element) ||
340 IS_RECEIVER(element))
341 return 4 * element_phase;
343 return element_phase;
346 static int get_opposite_angle(int angle)
348 int opposite_angle = angle + ANG_RAY_180;
350 // make sure "opposite_angle" is in valid interval [0, 15]
351 return (opposite_angle + 16) % 16;
354 static int get_mirrored_angle(int laser_angle, int mirror_angle)
356 int reflected_angle = 16 - laser_angle + mirror_angle;
358 // make sure "reflected_angle" is in valid interval [0, 15]
359 return (reflected_angle + 16) % 16;
362 static void DrawLaserLines(struct XY *points, int num_points, int mode)
364 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
365 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
367 DrawLines(drawto, points, num_points, pixel_drawto);
371 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
376 static boolean CheckLaserPixel(int x, int y)
382 pixel = ReadPixel(laser_bitmap, x, y);
386 return (pixel == WHITE_PIXEL);
389 static void CheckExitMM(void)
391 int exit_element = EL_EMPTY;
395 static int xy[4][2] =
403 for (y = 0; y < lev_fieldy; y++)
405 for (x = 0; x < lev_fieldx; x++)
407 if (Tile[x][y] == EL_EXIT_CLOSED)
409 // initiate opening animation of exit door
410 Tile[x][y] = EL_EXIT_OPENING;
412 exit_element = EL_EXIT_OPEN;
416 else if (IS_RECEIVER(Tile[x][y]))
418 // remove field that blocks receiver
419 int phase = Tile[x][y] - EL_RECEIVER_START;
420 int blocking_x, blocking_y;
422 blocking_x = x + xy[phase][0];
423 blocking_y = y + xy[phase][1];
425 if (IN_LEV_FIELD(blocking_x, blocking_y))
427 Tile[blocking_x][blocking_y] = EL_EMPTY;
429 DrawField_MM(blocking_x, blocking_y);
432 exit_element = EL_RECEIVER;
439 if (exit_element != EL_EMPTY)
440 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
443 static void SetLaserColor(int brightness)
445 int color_min = 0x00;
446 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
447 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
448 int color_down = color_max - color_up;
451 GetPixelFromRGB(window,
452 (game_mm.laser_red ? color_max : color_up),
453 (game_mm.laser_green ? color_down : color_min),
454 (game_mm.laser_blue ? color_down : color_min));
457 static void InitMovDir_MM(int x, int y)
459 int element = Tile[x][y];
460 static int direction[3][4] =
462 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
463 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
464 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
469 case EL_PACMAN_RIGHT:
473 Tile[x][y] = EL_PACMAN;
474 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
482 static void InitField(int x, int y, boolean init_game)
484 int element = Tile[x][y];
489 Tile[x][y] = EL_EMPTY;
494 if (init_game && native_mm_level.auto_count_kettles)
495 game_mm.kettles_still_needed++;
498 case EL_LIGHTBULB_OFF:
499 game_mm.lights_still_needed++;
503 if (IS_MIRROR(element) ||
504 IS_BEAMER_OLD(element) ||
505 IS_BEAMER(element) ||
507 IS_POLAR_CROSS(element) ||
508 IS_DF_MIRROR(element) ||
509 IS_DF_MIRROR_AUTO(element) ||
510 IS_GRID_STEEL_AUTO(element) ||
511 IS_GRID_WOOD_AUTO(element) ||
512 IS_FIBRE_OPTIC(element))
514 if (IS_BEAMER_OLD(element))
516 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
517 element = Tile[x][y];
520 if (!IS_FIBRE_OPTIC(element))
522 static int steps_grid_auto = 0;
524 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
525 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
527 if (IS_GRID_STEEL_AUTO(element) ||
528 IS_GRID_WOOD_AUTO(element))
529 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
531 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
533 game_mm.cycle[game_mm.num_cycle].x = x;
534 game_mm.cycle[game_mm.num_cycle].y = y;
538 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
540 int beamer_nr = BEAMER_NR(element);
541 int nr = laser.beamer[beamer_nr][0].num;
543 laser.beamer[beamer_nr][nr].x = x;
544 laser.beamer[beamer_nr][nr].y = y;
545 laser.beamer[beamer_nr][nr].num = 1;
548 else if (IS_PACMAN(element))
552 else if (IS_MCDUFFIN(element) || IS_LASER(element))
556 laser.start_edge.x = x;
557 laser.start_edge.y = y;
558 laser.start_angle = get_element_angle(element);
561 if (IS_MCDUFFIN(element))
563 game_mm.laser_red = native_mm_level.mm_laser_red;
564 game_mm.laser_green = native_mm_level.mm_laser_green;
565 game_mm.laser_blue = native_mm_level.mm_laser_blue;
569 game_mm.laser_red = native_mm_level.df_laser_red;
570 game_mm.laser_green = native_mm_level.df_laser_green;
571 game_mm.laser_blue = native_mm_level.df_laser_blue;
574 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
581 static void InitCycleElements_RotateSingleStep(void)
585 if (game_mm.num_cycle == 0) // no elements to cycle
588 for (i = 0; i < game_mm.num_cycle; i++)
590 int x = game_mm.cycle[i].x;
591 int y = game_mm.cycle[i].y;
592 int step = SIGN(game_mm.cycle[i].steps);
593 int last_element = Tile[x][y];
594 int next_element = get_rotated_element(last_element, step);
596 if (!game_mm.cycle[i].steps)
599 Tile[x][y] = next_element;
601 game_mm.cycle[i].steps -= step;
605 static void InitLaser(void)
607 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
608 int step = (IS_LASER(start_element) ? 4 : 0);
610 LX = laser.start_edge.x * TILEX;
611 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
614 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
616 LY = laser.start_edge.y * TILEY;
617 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
618 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
622 XS = 2 * Step[laser.start_angle].x;
623 YS = 2 * Step[laser.start_angle].y;
625 laser.current_angle = laser.start_angle;
627 laser.num_damages = 0;
629 laser.num_beamers = 0;
630 laser.beamer_edge[0] = 0;
632 laser.dest_element = EL_EMPTY;
635 AddLaserEdge(LX, LY); // set laser starting edge
640 void InitGameEngine_MM(void)
646 // initialize laser bitmap to current playfield (screen) size
647 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
648 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
652 // set global game control values
653 game_mm.num_cycle = 0;
654 game_mm.num_pacman = 0;
657 game_mm.energy_left = 0; // later set to "native_mm_level.time"
658 game_mm.kettles_still_needed =
659 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
660 game_mm.lights_still_needed = 0;
661 game_mm.num_keys = 0;
662 game_mm.ball_choice_pos = 0;
664 game_mm.laser_red = FALSE;
665 game_mm.laser_green = FALSE;
666 game_mm.laser_blue = TRUE;
667 game_mm.has_mcduffin = TRUE;
669 game_mm.level_solved = FALSE;
670 game_mm.game_over = FALSE;
671 game_mm.game_over_cause = 0;
672 game_mm.game_over_message = NULL;
674 game_mm.laser_overload_value = 0;
675 game_mm.laser_enabled = FALSE;
677 // set global laser control values (must be set before "InitLaser()")
678 laser.start_edge.x = 0;
679 laser.start_edge.y = 0;
680 laser.start_angle = 0;
682 for (i = 0; i < MAX_NUM_BEAMERS; i++)
683 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
685 laser.overloaded = FALSE;
686 laser.overload_value = 0;
687 laser.fuse_off = FALSE;
688 laser.fuse_x = laser.fuse_y = -1;
690 laser.dest_element = EL_EMPTY;
691 laser.dest_element_last = EL_EMPTY;
692 laser.dest_element_last_x = -1;
693 laser.dest_element_last_y = -1;
707 rotate_delay.count = 0;
708 pacman_delay.count = 0;
709 energy_delay.count = 0;
710 overload_delay.count = 0;
712 ClickElement(-1, -1, -1);
714 for (x = 0; x < lev_fieldx; x++)
716 for (y = 0; y < lev_fieldy; y++)
718 Tile[x][y] = Ur[x][y];
719 Hit[x][y] = Box[x][y] = 0;
721 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
722 Store[x][y] = Store2[x][y] = 0;
725 InitField(x, y, TRUE);
732 void InitGameActions_MM(void)
734 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
735 int cycle_steps_done = 0;
740 for (i = 0; i <= num_init_game_frames; i++)
742 if (i == num_init_game_frames)
743 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
744 else if (setup.sound_loops)
745 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
747 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
749 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
751 UpdateAndDisplayGameControlValues();
753 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
755 InitCycleElements_RotateSingleStep();
760 AdvanceFrameCounter();
768 if (setup.quick_doors)
775 if (game_mm.kettles_still_needed == 0)
778 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
779 SetTileCursorActive(TRUE);
781 // restart all delay counters after initially cycling game elements
782 ResetFrameCounter(&rotate_delay);
783 ResetFrameCounter(&pacman_delay);
784 ResetFrameCounter(&energy_delay);
785 ResetFrameCounter(&overload_delay);
788 static void FadeOutLaser(void)
792 for (i = 15; i >= 0; i--)
794 SetLaserColor(0x11 * i);
796 DrawLaser(0, DL_LASER_ENABLED);
799 Delay_WithScreenUpdates(50);
802 DrawLaser(0, DL_LASER_DISABLED);
804 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
807 static void GameOver_MM(int game_over_cause)
809 game_mm.game_over = TRUE;
810 game_mm.game_over_cause = game_over_cause;
811 game_mm.game_over_message = (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!" :
819 SetTileCursorActive(FALSE);
822 static void AddLaserEdge(int lx, int ly)
827 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
829 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
834 laser.edge[laser.num_edges].x = cSX2 + lx;
835 laser.edge[laser.num_edges].y = cSY2 + ly;
841 static void AddDamagedField(int ex, int ey)
843 // prevent adding the same field position again
844 if (laser.num_damages > 0 &&
845 laser.damage[laser.num_damages - 1].x == ex &&
846 laser.damage[laser.num_damages - 1].y == ey &&
847 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
850 laser.damage[laser.num_damages].is_mirror = FALSE;
851 laser.damage[laser.num_damages].angle = laser.current_angle;
852 laser.damage[laser.num_damages].edge = laser.num_edges;
853 laser.damage[laser.num_damages].x = ex;
854 laser.damage[laser.num_damages].y = ey;
858 static boolean StepBehind(void)
864 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
865 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
867 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
873 static int getMaskFromElement(int element)
875 if (IS_GRID(element))
876 return MM_MASK_GRID_1 + get_element_phase(element);
877 else if (IS_MCDUFFIN(element))
878 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
879 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
880 return MM_MASK_RECTANGLE;
882 return MM_MASK_CIRCLE;
885 static int ScanPixel(void)
890 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
891 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
894 // follow laser beam until it hits something (at least the screen border)
895 while (hit_mask == HIT_MASK_NO_HIT)
901 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
902 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
904 Debug("game:mm:ScanPixel", "touched screen border!");
910 // check if laser scan has crossed element boundaries (not just mini tiles)
911 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
912 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
914 if (cross_x && cross_y)
916 int elx1 = (LX - XS) / TILEX;
917 int ely1 = (LY + YS) / TILEY;
918 int elx2 = (LX + XS) / TILEX;
919 int ely2 = (LY - YS) / TILEY;
921 // add element corners left and right from the laser beam to damage list
923 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
924 AddDamagedField(elx1, ely1);
926 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
927 AddDamagedField(elx2, ely2);
930 for (i = 0; i < 4; i++)
932 int px = LX + (i % 2) * 2;
933 int py = LY + (i / 2) * 2;
936 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
937 int ly = (py + TILEY) / TILEY - 1; // negative values!
940 if (IN_LEV_FIELD(lx, ly))
942 int element = Tile[lx][ly];
944 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
948 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
950 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
952 pixel = ((element & (1 << pos)) ? 1 : 0);
956 int pos = getMaskFromElement(element);
958 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
963 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
964 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
967 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
968 hit_mask |= (1 << i);
971 if (hit_mask == HIT_MASK_NO_HIT)
973 // hit nothing -- go on with another step
982 static void DeactivateLaserTargetElement(void)
984 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
985 laser.dest_element_last == EL_MINE_ACTIVE ||
986 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
987 laser.dest_element_last == EL_GRAY_BALL_OPENING)
989 int x = laser.dest_element_last_x;
990 int y = laser.dest_element_last_y;
991 int element = laser.dest_element_last;
993 if (Tile[x][y] == element)
994 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
995 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
997 if (Tile[x][y] == EL_GRAY_BALL)
1000 laser.dest_element_last = EL_EMPTY;
1001 laser.dest_element_last_x = -1;
1002 laser.dest_element_last_y = -1;
1006 static void ScanLaser(void)
1008 int element = EL_EMPTY;
1009 int last_element = EL_EMPTY;
1010 int end = 0, rf = laser.num_edges;
1012 // do not scan laser again after the game was lost for whatever reason
1013 if (game_mm.game_over)
1016 // do not scan laser if fuse is off
1020 DeactivateLaserTargetElement();
1022 laser.overloaded = FALSE;
1023 laser.stops_inside_element = FALSE;
1025 DrawLaser(0, DL_LASER_ENABLED);
1028 Debug("game:mm:ScanLaser",
1029 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1037 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1040 laser.overloaded = TRUE;
1045 hit_mask = ScanPixel();
1048 Debug("game:mm:ScanLaser",
1049 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1053 // hit something -- check out what it was
1054 ELX = (LX + XS) / TILEX;
1055 ELY = (LY + YS) / TILEY;
1058 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1059 hit_mask, LX, LY, ELX, ELY);
1062 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1065 laser.dest_element = element;
1070 // check if laser scan has hit two diagonally adjacent element corners
1071 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1072 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1074 // check if laser scan has crossed element boundaries (not just mini tiles)
1075 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1076 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1078 // handle special case of laser hitting two diagonally adjacent elements
1079 // (with or without a third corner element behind these two elements)
1080 if ((diag_1 || diag_2) && cross_x && cross_y)
1082 // compare the two diagonally adjacent elements
1084 int yoffset = 2 * (diag_1 ? -1 : +1);
1085 int elx1 = (LX - xoffset) / TILEX;
1086 int ely1 = (LY + yoffset) / TILEY;
1087 int elx2 = (LX + xoffset) / TILEX;
1088 int ely2 = (LY - yoffset) / TILEY;
1089 int e1 = Tile[elx1][ely1];
1090 int e2 = Tile[elx2][ely2];
1091 boolean use_element_1 = FALSE;
1093 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1095 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1096 use_element_1 = (RND(2) ? TRUE : FALSE);
1097 else if (IS_WALL_ICE(e1))
1098 use_element_1 = TRUE;
1100 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1102 // if both tiles match, we can just select the first one
1103 if (IS_WALL_AMOEBA(e1))
1104 use_element_1 = TRUE;
1106 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1108 // if both tiles match, we can just select the first one
1109 if (IS_ABSORBING_BLOCK(e1))
1110 use_element_1 = TRUE;
1113 ELX = (use_element_1 ? elx1 : elx2);
1114 ELY = (use_element_1 ? ely1 : ely2);
1118 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1119 hit_mask, LX, LY, ELX, ELY);
1122 last_element = element;
1124 element = Tile[ELX][ELY];
1125 laser.dest_element = element;
1128 Debug("game:mm:ScanLaser",
1129 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1132 LX % TILEX, LY % TILEY,
1137 if (!IN_LEV_FIELD(ELX, ELY))
1138 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1142 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1143 if (element == EL_EMPTY &&
1144 IS_GRID_STEEL(last_element) &&
1145 laser.current_angle % 4) // angle is not 90°
1146 element = last_element;
1148 if (element == EL_EMPTY)
1150 if (!HitOnlyAnEdge(hit_mask))
1153 else if (element == EL_FUSE_ON)
1155 if (HitPolarizer(element, hit_mask))
1158 else if (IS_GRID(element) || IS_DF_GRID(element))
1160 if (HitPolarizer(element, hit_mask))
1163 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1164 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1166 if (HitBlock(element, hit_mask))
1173 else if (IS_MCDUFFIN(element))
1175 if (HitLaserSource(element, hit_mask))
1178 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1179 IS_RECEIVER(element))
1181 if (HitLaserDestination(element, hit_mask))
1184 else if (IS_WALL(element))
1186 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1188 if (HitReflectingWalls(element, hit_mask))
1193 if (HitAbsorbingWalls(element, hit_mask))
1199 if (HitElement(element, hit_mask))
1204 DrawLaser(rf - 1, DL_LASER_ENABLED);
1205 rf = laser.num_edges;
1207 if (!IS_DF_WALL_STEEL(element))
1209 // only used for scanning DF steel walls; reset for all other elements
1217 if (laser.dest_element != Tile[ELX][ELY])
1219 Debug("game:mm:ScanLaser",
1220 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1221 laser.dest_element, Tile[ELX][ELY]);
1225 if (!end && !laser.stops_inside_element && !StepBehind())
1228 Debug("game:mm:ScanLaser", "Go one step back");
1234 AddLaserEdge(LX, LY);
1238 DrawLaser(rf - 1, DL_LASER_ENABLED);
1240 Ct = CT = FrameCounter;
1243 if (!IN_LEV_FIELD(ELX, ELY))
1244 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1248 static void ScanLaser_FromLastMirror(void)
1250 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1253 for (i = start_pos; i >= 0; i--)
1254 if (laser.damage[i].is_mirror)
1257 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1259 DrawLaser(start_edge, DL_LASER_DISABLED);
1264 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1270 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1271 start_edge, num_edges, mode);
1276 Warn("DrawLaserExt: start_edge < 0");
1283 Warn("DrawLaserExt: num_edges < 0");
1289 if (mode == DL_LASER_DISABLED)
1291 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1295 // now draw the laser to the backbuffer and (if enabled) to the screen
1296 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1298 redraw_mask |= REDRAW_FIELD;
1300 if (mode == DL_LASER_ENABLED)
1303 // after the laser was deleted, the "damaged" graphics must be restored
1304 if (laser.num_damages)
1306 int damage_start = 0;
1309 // determine the starting edge, from which graphics need to be restored
1312 for (i = 0; i < laser.num_damages; i++)
1314 if (laser.damage[i].edge == start_edge + 1)
1323 // restore graphics from this starting edge to the end of damage list
1324 for (i = damage_start; i < laser.num_damages; i++)
1326 int lx = laser.damage[i].x;
1327 int ly = laser.damage[i].y;
1328 int element = Tile[lx][ly];
1330 if (Hit[lx][ly] == laser.damage[i].edge)
1331 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1334 if (Box[lx][ly] == laser.damage[i].edge)
1337 if (IS_DRAWABLE(element))
1338 DrawField_MM(lx, ly);
1341 elx = laser.damage[damage_start].x;
1342 ely = laser.damage[damage_start].y;
1343 element = Tile[elx][ely];
1346 if (IS_BEAMER(element))
1350 for (i = 0; i < laser.num_beamers; i++)
1351 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1353 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1354 mode, elx, ely, Hit[elx][ely], start_edge);
1355 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1356 get_element_angle(element), laser.damage[damage_start].angle);
1360 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1361 laser.num_beamers > 0 &&
1362 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1364 // element is outgoing beamer
1365 laser.num_damages = damage_start + 1;
1367 if (IS_BEAMER(element))
1368 laser.current_angle = get_element_angle(element);
1372 // element is incoming beamer or other element
1373 laser.num_damages = damage_start;
1374 laser.current_angle = laser.damage[laser.num_damages].angle;
1379 // no damages but McDuffin himself (who needs to be redrawn anyway)
1381 elx = laser.start_edge.x;
1382 ely = laser.start_edge.y;
1383 element = Tile[elx][ely];
1386 laser.num_edges = start_edge + 1;
1387 if (start_edge == 0)
1388 laser.current_angle = laser.start_angle;
1390 LX = laser.edge[start_edge].x - cSX2;
1391 LY = laser.edge[start_edge].y - cSY2;
1392 XS = 2 * Step[laser.current_angle].x;
1393 YS = 2 * Step[laser.current_angle].y;
1396 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1402 if (IS_BEAMER(element) ||
1403 IS_FIBRE_OPTIC(element) ||
1404 IS_PACMAN(element) ||
1405 IS_POLAR(element) ||
1406 IS_POLAR_CROSS(element) ||
1407 element == EL_FUSE_ON)
1412 Debug("game:mm:DrawLaserExt", "element == %d", element);
1415 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1416 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1420 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1421 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1422 (laser.num_beamers == 0 ||
1423 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1425 // element is incoming beamer or other element
1426 step_size = -step_size;
1431 if (IS_BEAMER(element))
1432 Debug("game:mm:DrawLaserExt",
1433 "start_edge == %d, laser.beamer_edge == %d",
1434 start_edge, laser.beamer_edge);
1437 LX += step_size * XS;
1438 LY += step_size * YS;
1440 else if (element != EL_EMPTY)
1449 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1454 void DrawLaser(int start_edge, int mode)
1456 // do not draw laser if fuse is off
1457 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1460 if (mode == DL_LASER_DISABLED)
1461 DeactivateLaserTargetElement();
1463 if (laser.num_edges - start_edge < 0)
1465 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1470 // check if laser is interrupted by beamer element
1471 if (laser.num_beamers > 0 &&
1472 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1474 if (mode == DL_LASER_ENABLED)
1477 int tmp_start_edge = start_edge;
1479 // draw laser segments forward from the start to the last beamer
1480 for (i = 0; i < laser.num_beamers; i++)
1482 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1484 if (tmp_num_edges <= 0)
1488 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1489 i, laser.beamer_edge[i], tmp_start_edge);
1492 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1494 tmp_start_edge = laser.beamer_edge[i];
1497 // draw last segment from last beamer to the end
1498 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1504 int last_num_edges = laser.num_edges;
1505 int num_beamers = laser.num_beamers;
1507 // delete laser segments backward from the end to the first beamer
1508 for (i = num_beamers - 1; i >= 0; i--)
1510 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1512 if (laser.beamer_edge[i] - start_edge <= 0)
1515 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1517 last_num_edges = laser.beamer_edge[i];
1518 laser.num_beamers--;
1522 if (last_num_edges - start_edge <= 0)
1523 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1524 last_num_edges, start_edge);
1527 // special case when rotating first beamer: delete laser edge on beamer
1528 // (but do not start scanning on previous edge to prevent mirror sound)
1529 if (last_num_edges - start_edge == 1 && start_edge > 0)
1530 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1532 // delete first segment from start to the first beamer
1533 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1538 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1541 game_mm.laser_enabled = mode;
1544 void DrawLaser_MM(void)
1546 DrawLaser(0, game_mm.laser_enabled);
1549 static boolean HitElement(int element, int hit_mask)
1551 if (HitOnlyAnEdge(hit_mask))
1554 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1555 element = MovingOrBlocked2Element_MM(ELX, ELY);
1558 Debug("game:mm:HitElement", "(1): element == %d", element);
1562 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1563 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1566 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1570 AddDamagedField(ELX, ELY);
1572 // this is more precise: check if laser would go through the center
1573 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1577 // prevent cutting through laser emitter with laser beam
1578 if (IS_LASER(element))
1581 // skip the whole element before continuing the scan
1589 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1591 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1593 /* skipping scan positions to the right and down skips one scan
1594 position too much, because this is only the top left scan position
1595 of totally four scan positions (plus one to the right, one to the
1596 bottom and one to the bottom right) */
1597 /* ... but only roll back scan position if more than one step done */
1607 Debug("game:mm:HitElement", "(2): element == %d", element);
1610 if (LX + 5 * XS < 0 ||
1620 Debug("game:mm:HitElement", "(3): element == %d", element);
1623 if (IS_POLAR(element) &&
1624 ((element - EL_POLAR_START) % 2 ||
1625 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1627 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1629 laser.num_damages--;
1634 if (IS_POLAR_CROSS(element) &&
1635 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1637 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1639 laser.num_damages--;
1644 if (!IS_BEAMER(element) &&
1645 !IS_FIBRE_OPTIC(element) &&
1646 !IS_GRID_WOOD(element) &&
1647 element != EL_FUEL_EMPTY)
1650 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1651 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1653 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1656 LX = ELX * TILEX + 14;
1657 LY = ELY * TILEY + 14;
1659 AddLaserEdge(LX, LY);
1662 if (IS_MIRROR(element) ||
1663 IS_MIRROR_FIXED(element) ||
1664 IS_POLAR(element) ||
1665 IS_POLAR_CROSS(element) ||
1666 IS_DF_MIRROR(element) ||
1667 IS_DF_MIRROR_AUTO(element) ||
1668 element == EL_PRISM ||
1669 element == EL_REFRACTOR)
1671 int current_angle = laser.current_angle;
1674 laser.num_damages--;
1676 AddDamagedField(ELX, ELY);
1678 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1681 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1683 if (IS_MIRROR(element) ||
1684 IS_MIRROR_FIXED(element) ||
1685 IS_DF_MIRROR(element) ||
1686 IS_DF_MIRROR_AUTO(element))
1687 laser.current_angle = get_mirrored_angle(laser.current_angle,
1688 get_element_angle(element));
1690 if (element == EL_PRISM || element == EL_REFRACTOR)
1691 laser.current_angle = RND(16);
1693 XS = 2 * Step[laser.current_angle].x;
1694 YS = 2 * Step[laser.current_angle].y;
1696 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1701 LX += step_size * XS;
1702 LY += step_size * YS;
1704 // draw sparkles on mirror
1705 if ((IS_MIRROR(element) ||
1706 IS_MIRROR_FIXED(element) ||
1707 element == EL_PRISM) &&
1708 current_angle != laser.current_angle)
1710 MovDelay[ELX][ELY] = 11; // start animation
1713 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1714 current_angle != laser.current_angle)
1715 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1718 (get_opposite_angle(laser.current_angle) ==
1719 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1721 return (laser.overloaded ? TRUE : FALSE);
1724 if (element == EL_FUEL_FULL)
1726 laser.stops_inside_element = TRUE;
1731 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1733 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1735 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1736 element == EL_MINE ? EL_MINE_ACTIVE :
1737 EL_GRAY_BALL_ACTIVE);
1739 GfxFrame[ELX][ELY] = 0; // restart animation
1741 laser.dest_element_last = Tile[ELX][ELY];
1742 laser.dest_element_last_x = ELX;
1743 laser.dest_element_last_y = ELY;
1745 if (element == EL_MINE)
1746 laser.overloaded = TRUE;
1749 if (element == EL_KETTLE ||
1750 element == EL_CELL ||
1751 element == EL_KEY ||
1752 element == EL_LIGHTBALL ||
1753 element == EL_PACMAN ||
1754 IS_PACMAN(element) ||
1755 IS_ENVELOPE(element))
1757 if (!IS_PACMAN(element) &&
1758 !IS_ENVELOPE(element))
1761 if (element == EL_PACMAN)
1764 if (element == EL_KETTLE || element == EL_CELL)
1766 if (game_mm.kettles_still_needed > 0)
1767 game_mm.kettles_still_needed--;
1769 game.snapshot.collected_item = TRUE;
1771 if (game_mm.kettles_still_needed == 0)
1775 DrawLaser(0, DL_LASER_ENABLED);
1778 else if (element == EL_KEY)
1782 else if (IS_PACMAN(element))
1784 DeletePacMan(ELX, ELY);
1786 else if (IS_ENVELOPE(element))
1788 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1791 RaiseScoreElement_MM(element);
1796 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1798 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1800 DrawLaser(0, DL_LASER_ENABLED);
1802 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1804 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1805 game_mm.lights_still_needed--;
1809 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1810 game_mm.lights_still_needed++;
1813 DrawField_MM(ELX, ELY);
1814 DrawLaser(0, DL_LASER_ENABLED);
1819 laser.stops_inside_element = TRUE;
1825 Debug("game:mm:HitElement", "(4): element == %d", element);
1828 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1829 laser.num_beamers < MAX_NUM_BEAMERS &&
1830 laser.beamer[BEAMER_NR(element)][1].num)
1832 int beamer_angle = get_element_angle(element);
1833 int beamer_nr = BEAMER_NR(element);
1837 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1840 laser.num_damages--;
1842 if (IS_FIBRE_OPTIC(element) ||
1843 laser.current_angle == get_opposite_angle(beamer_angle))
1847 LX = ELX * TILEX + 14;
1848 LY = ELY * TILEY + 14;
1850 AddLaserEdge(LX, LY);
1851 AddDamagedField(ELX, ELY);
1853 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1856 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1858 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1859 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1860 ELX = laser.beamer[beamer_nr][pos].x;
1861 ELY = laser.beamer[beamer_nr][pos].y;
1862 LX = ELX * TILEX + 14;
1863 LY = ELY * TILEY + 14;
1865 if (IS_BEAMER(element))
1867 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1868 XS = 2 * Step[laser.current_angle].x;
1869 YS = 2 * Step[laser.current_angle].y;
1872 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1874 AddLaserEdge(LX, LY);
1875 AddDamagedField(ELX, ELY);
1877 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1880 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1882 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1887 LX += step_size * XS;
1888 LY += step_size * YS;
1890 laser.num_beamers++;
1899 static boolean HitOnlyAnEdge(int hit_mask)
1901 // check if the laser hit only the edge of an element and, if so, go on
1904 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1908 if ((hit_mask == HIT_MASK_TOPLEFT ||
1909 hit_mask == HIT_MASK_TOPRIGHT ||
1910 hit_mask == HIT_MASK_BOTTOMLEFT ||
1911 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1912 laser.current_angle % 4) // angle is not 90°
1916 if (hit_mask == HIT_MASK_TOPLEFT)
1921 else if (hit_mask == HIT_MASK_TOPRIGHT)
1926 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1931 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1937 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1943 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1950 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1956 static boolean HitPolarizer(int element, int hit_mask)
1958 if (HitOnlyAnEdge(hit_mask))
1961 if (IS_DF_GRID(element))
1963 int grid_angle = get_element_angle(element);
1966 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1967 grid_angle, laser.current_angle);
1970 AddLaserEdge(LX, LY);
1971 AddDamagedField(ELX, ELY);
1974 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1976 if (laser.current_angle == grid_angle ||
1977 laser.current_angle == get_opposite_angle(grid_angle))
1979 // skip the whole element before continuing the scan
1985 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1987 if (LX/TILEX > ELX || LY/TILEY > ELY)
1989 /* skipping scan positions to the right and down skips one scan
1990 position too much, because this is only the top left scan position
1991 of totally four scan positions (plus one to the right, one to the
1992 bottom and one to the bottom right) */
1998 AddLaserEdge(LX, LY);
2004 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2006 LX / TILEX, LY / TILEY,
2007 LX % TILEX, LY % TILEY);
2012 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2014 return HitReflectingWalls(element, hit_mask);
2018 return HitAbsorbingWalls(element, hit_mask);
2021 else if (IS_GRID_STEEL(element))
2023 // may be required if graphics for steel grid redefined
2024 AddDamagedField(ELX, ELY);
2026 return HitReflectingWalls(element, hit_mask);
2028 else // IS_GRID_WOOD
2030 // may be required if graphics for wooden grid redefined
2031 AddDamagedField(ELX, ELY);
2033 return HitAbsorbingWalls(element, hit_mask);
2039 static boolean HitBlock(int element, int hit_mask)
2041 boolean check = FALSE;
2043 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2044 game_mm.num_keys == 0)
2047 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2050 int ex = ELX * TILEX + 14;
2051 int ey = ELY * TILEY + 14;
2055 for (i = 1; i < 32; i++)
2060 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2065 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2066 return HitAbsorbingWalls(element, hit_mask);
2070 AddLaserEdge(LX - XS, LY - YS);
2071 AddDamagedField(ELX, ELY);
2074 Box[ELX][ELY] = laser.num_edges;
2076 return HitReflectingWalls(element, hit_mask);
2079 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2081 int xs = XS / 2, ys = YS / 2;
2083 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2084 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2086 laser.overloaded = (element == EL_GATE_STONE);
2091 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2092 (hit_mask == HIT_MASK_TOP ||
2093 hit_mask == HIT_MASK_LEFT ||
2094 hit_mask == HIT_MASK_RIGHT ||
2095 hit_mask == HIT_MASK_BOTTOM))
2096 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2097 hit_mask == HIT_MASK_BOTTOM),
2098 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2099 hit_mask == HIT_MASK_RIGHT));
2100 AddLaserEdge(LX, LY);
2106 if (element == EL_GATE_STONE && Box[ELX][ELY])
2108 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2120 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2122 int xs = XS / 2, ys = YS / 2;
2124 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2125 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2127 laser.overloaded = (element == EL_BLOCK_STONE);
2132 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2133 (hit_mask == HIT_MASK_TOP ||
2134 hit_mask == HIT_MASK_LEFT ||
2135 hit_mask == HIT_MASK_RIGHT ||
2136 hit_mask == HIT_MASK_BOTTOM))
2137 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2138 hit_mask == HIT_MASK_BOTTOM),
2139 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2140 hit_mask == HIT_MASK_RIGHT));
2141 AddDamagedField(ELX, ELY);
2143 LX = ELX * TILEX + 14;
2144 LY = ELY * TILEY + 14;
2146 AddLaserEdge(LX, LY);
2148 laser.stops_inside_element = TRUE;
2156 static boolean HitLaserSource(int element, int hit_mask)
2158 if (HitOnlyAnEdge(hit_mask))
2161 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2163 laser.overloaded = TRUE;
2168 static boolean HitLaserDestination(int element, int hit_mask)
2170 if (HitOnlyAnEdge(hit_mask))
2173 if (element != EL_EXIT_OPEN &&
2174 !(IS_RECEIVER(element) &&
2175 game_mm.kettles_still_needed == 0 &&
2176 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2178 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2183 if (IS_RECEIVER(element) ||
2184 (IS_22_5_ANGLE(laser.current_angle) &&
2185 (ELX != (LX + 6 * XS) / TILEX ||
2186 ELY != (LY + 6 * YS) / TILEY ||
2195 LX = ELX * TILEX + 14;
2196 LY = ELY * TILEY + 14;
2198 laser.stops_inside_element = TRUE;
2201 AddLaserEdge(LX, LY);
2202 AddDamagedField(ELX, ELY);
2204 if (game_mm.lights_still_needed == 0)
2206 game_mm.level_solved = TRUE;
2208 SetTileCursorActive(FALSE);
2214 static boolean HitReflectingWalls(int element, int hit_mask)
2216 // check if laser hits side of a wall with an angle that is not 90°
2217 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2218 hit_mask == HIT_MASK_LEFT ||
2219 hit_mask == HIT_MASK_RIGHT ||
2220 hit_mask == HIT_MASK_BOTTOM))
2222 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2227 if (!IS_DF_GRID(element))
2228 AddLaserEdge(LX, LY);
2230 // check if laser hits wall with an angle of 45°
2231 if (!IS_22_5_ANGLE(laser.current_angle))
2233 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2236 laser.current_angle = get_mirrored_angle(laser.current_angle,
2239 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2242 laser.current_angle = get_mirrored_angle(laser.current_angle,
2246 AddLaserEdge(LX, LY);
2248 XS = 2 * Step[laser.current_angle].x;
2249 YS = 2 * Step[laser.current_angle].y;
2253 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2255 laser.current_angle = get_mirrored_angle(laser.current_angle,
2260 if (!IS_DF_GRID(element))
2261 AddLaserEdge(LX, LY);
2266 if (!IS_DF_GRID(element))
2267 AddLaserEdge(LX, LY + YS / 2);
2270 if (!IS_DF_GRID(element))
2271 AddLaserEdge(LX, LY);
2274 YS = 2 * Step[laser.current_angle].y;
2278 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2280 laser.current_angle = get_mirrored_angle(laser.current_angle,
2285 if (!IS_DF_GRID(element))
2286 AddLaserEdge(LX, LY);
2291 if (!IS_DF_GRID(element))
2292 AddLaserEdge(LX + XS / 2, LY);
2295 if (!IS_DF_GRID(element))
2296 AddLaserEdge(LX, LY);
2299 XS = 2 * Step[laser.current_angle].x;
2305 // reflection at the edge of reflecting DF style wall
2306 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2308 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2309 hit_mask == HIT_MASK_TOPRIGHT) ||
2310 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2311 hit_mask == HIT_MASK_TOPLEFT) ||
2312 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2313 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2314 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2315 hit_mask == HIT_MASK_BOTTOMRIGHT))
2318 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2319 ANG_MIRROR_135 : ANG_MIRROR_45);
2321 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2323 AddDamagedField(ELX, ELY);
2324 AddLaserEdge(LX, LY);
2326 laser.current_angle = get_mirrored_angle(laser.current_angle,
2334 AddLaserEdge(LX, LY);
2340 // reflection inside an edge of reflecting DF style wall
2341 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2343 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2344 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2345 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2346 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2347 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2348 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2349 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2350 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2353 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2354 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2355 ANG_MIRROR_135 : ANG_MIRROR_45);
2357 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2360 AddDamagedField(ELX, ELY);
2363 AddLaserEdge(LX - XS, LY - YS);
2364 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2365 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2367 laser.current_angle = get_mirrored_angle(laser.current_angle,
2375 AddLaserEdge(LX, LY);
2381 // check if laser hits DF style wall with an angle of 90°
2382 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2384 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2385 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2386 (IS_VERT_ANGLE(laser.current_angle) &&
2387 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2389 // laser at last step touched nothing or the same side of the wall
2390 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2392 AddDamagedField(ELX, ELY);
2399 last_hit_mask = hit_mask;
2406 if (!HitOnlyAnEdge(hit_mask))
2408 laser.overloaded = TRUE;
2416 static boolean HitAbsorbingWalls(int element, int hit_mask)
2418 if (HitOnlyAnEdge(hit_mask))
2422 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2424 AddLaserEdge(LX - XS, LY - YS);
2431 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2433 AddLaserEdge(LX - XS, LY - YS);
2439 if (IS_WALL_WOOD(element) ||
2440 IS_DF_WALL_WOOD(element) ||
2441 IS_GRID_WOOD(element) ||
2442 IS_GRID_WOOD_FIXED(element) ||
2443 IS_GRID_WOOD_AUTO(element) ||
2444 element == EL_FUSE_ON ||
2445 element == EL_BLOCK_WOOD ||
2446 element == EL_GATE_WOOD)
2448 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2453 if (IS_WALL_ICE(element))
2459 // check if laser hit adjacent edges of two diagonal tiles
2460 if (ELX != lx / TILEX)
2462 if (ELY != ly / TILEY)
2465 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2466 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2468 // check if laser hits wall with an angle of 90°
2469 if (IS_90_ANGLE(laser.current_angle))
2470 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2472 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2476 for (i = 0; i < 4; i++)
2478 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2479 mask = 15 - (8 >> i);
2480 else if (ABS(XS) == 4 &&
2482 (XS > 0) == (i % 2) &&
2483 (YS < 0) == (i / 2))
2484 mask = 3 + (i / 2) * 9;
2485 else if (ABS(YS) == 4 &&
2487 (XS < 0) == (i % 2) &&
2488 (YS > 0) == (i / 2))
2489 mask = 5 + (i % 2) * 5;
2493 laser.wall_mask = mask;
2495 else if (IS_WALL_AMOEBA(element))
2497 int elx = (LX - 2 * XS) / TILEX;
2498 int ely = (LY - 2 * YS) / TILEY;
2499 int element2 = Tile[elx][ely];
2502 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2504 laser.dest_element = EL_EMPTY;
2512 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2513 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2515 if (IS_90_ANGLE(laser.current_angle))
2516 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2518 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2520 laser.wall_mask = mask;
2526 static void OpenExit(int x, int y)
2530 if (!MovDelay[x][y]) // next animation frame
2531 MovDelay[x][y] = 4 * delay;
2533 if (MovDelay[x][y]) // wait some time before next frame
2538 phase = MovDelay[x][y] / delay;
2540 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2541 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2543 if (!MovDelay[x][y])
2545 Tile[x][y] = EL_EXIT_OPEN;
2551 static void OpenGrayBall(int x, int y)
2555 if (!MovDelay[x][y]) // next animation frame
2557 if (IS_WALL(Store[x][y]))
2559 DrawWalls_MM(x, y, Store[x][y]);
2561 // copy wall tile to spare bitmap for "melting" animation
2562 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2563 TILEX, TILEY, x * TILEX, y * TILEY);
2565 DrawElement_MM(x, y, EL_GRAY_BALL);
2568 MovDelay[x][y] = 50 * delay;
2571 if (MovDelay[x][y]) // wait some time before next frame
2575 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2579 int dx = RND(26), dy = RND(26);
2581 if (IS_WALL(Store[x][y]))
2583 // copy wall tile from spare bitmap for "melting" animation
2584 bitmap = bitmap_db_field;
2590 int graphic = el2gfx(Store[x][y]);
2592 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2595 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2596 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2598 laser.redraw = TRUE;
2600 MarkTileDirty(x, y);
2603 if (!MovDelay[x][y])
2605 Tile[x][y] = Store[x][y];
2606 Store[x][y] = Store2[x][y] = 0;
2607 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2609 InitField(x, y, FALSE);
2612 ScanLaser_FromLastMirror();
2617 static void OpenEnvelope(int x, int y)
2619 int num_frames = 8; // seven frames plus final empty space
2621 if (!MovDelay[x][y]) // next animation frame
2622 MovDelay[x][y] = num_frames;
2624 if (MovDelay[x][y]) // wait some time before next frame
2626 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2630 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2632 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2633 int frame = num_frames - MovDelay[x][y] - 1;
2635 DrawGraphicAnimation_MM(x, y, graphic, frame);
2637 laser.redraw = TRUE;
2640 if (MovDelay[x][y] == 0)
2642 Tile[x][y] = EL_EMPTY;
2648 ShowEnvelope_MM(nr);
2653 static void MeltIce(int x, int y)
2658 if (!MovDelay[x][y]) // next animation frame
2659 MovDelay[x][y] = frames * delay;
2661 if (MovDelay[x][y]) // wait some time before next frame
2664 int wall_mask = Store2[x][y];
2665 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2668 phase = frames - MovDelay[x][y] / delay - 1;
2670 if (!MovDelay[x][y])
2672 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2673 Store[x][y] = Store2[x][y] = 0;
2675 DrawWalls_MM(x, y, Tile[x][y]);
2677 if (Tile[x][y] == EL_WALL_ICE_BASE)
2678 Tile[x][y] = EL_EMPTY;
2680 ScanLaser_FromLastMirror();
2682 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2684 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2686 laser.redraw = TRUE;
2691 static void GrowAmoeba(int x, int y)
2696 if (!MovDelay[x][y]) // next animation frame
2697 MovDelay[x][y] = frames * delay;
2699 if (MovDelay[x][y]) // wait some time before next frame
2702 int wall_mask = Store2[x][y];
2703 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2706 phase = MovDelay[x][y] / delay;
2708 if (!MovDelay[x][y])
2710 Tile[x][y] = real_element;
2711 Store[x][y] = Store2[x][y] = 0;
2713 DrawWalls_MM(x, y, Tile[x][y]);
2714 DrawLaser(0, DL_LASER_ENABLED);
2716 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2718 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2723 static void DrawFieldAnimated_MM(int x, int y)
2727 laser.redraw = TRUE;
2730 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2732 int element = Tile[x][y];
2733 int graphic = el2gfx(element);
2735 if (!getGraphicInfo_NewFrame(x, y, graphic))
2740 laser.redraw = TRUE;
2743 static void DrawFieldTwinkle(int x, int y)
2745 if (MovDelay[x][y] != 0) // wait some time before next frame
2751 if (MovDelay[x][y] != 0)
2753 int graphic = IMG_TWINKLE_WHITE;
2754 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2756 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2759 laser.redraw = TRUE;
2763 static void Explode_MM(int x, int y, int phase, int mode)
2765 int num_phase = 9, delay = 2;
2766 int last_phase = num_phase * delay;
2767 int half_phase = (num_phase / 2) * delay;
2770 laser.redraw = TRUE;
2772 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2774 center_element = Tile[x][y];
2776 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2778 // put moving element to center field (and let it explode there)
2779 center_element = MovingOrBlocked2Element_MM(x, y);
2780 RemoveMovingField_MM(x, y);
2782 Tile[x][y] = center_element;
2785 if (center_element != EL_GRAY_BALL_ACTIVE)
2786 Store[x][y] = EL_EMPTY;
2787 Store2[x][y] = center_element;
2789 Tile[x][y] = EL_EXPLODING_OPAQUE;
2791 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2792 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2793 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2796 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2798 ExplodePhase[x][y] = 1;
2804 GfxFrame[x][y] = 0; // restart explosion animation
2806 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2808 center_element = Store2[x][y];
2810 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2812 Tile[x][y] = EL_EXPLODING_TRANSP;
2814 if (x == ELX && y == ELY)
2818 if (phase == last_phase)
2820 if (center_element == EL_BOMB_ACTIVE)
2822 DrawLaser(0, DL_LASER_DISABLED);
2825 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2827 GameOver_MM(GAME_OVER_DELAYED);
2829 laser.overloaded = FALSE;
2831 else if (IS_MCDUFFIN(center_element))
2833 GameOver_MM(GAME_OVER_BOMB);
2836 Tile[x][y] = Store[x][y];
2838 Store[x][y] = Store2[x][y] = 0;
2839 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2841 InitField(x, y, FALSE);
2844 if (center_element == EL_GRAY_BALL_ACTIVE)
2845 ScanLaser_FromLastMirror();
2847 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2849 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2850 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2852 DrawGraphicAnimation_MM(x, y, graphic, frame);
2854 MarkTileDirty(x, y);
2858 static void Bang_MM(int x, int y)
2860 int element = Tile[x][y];
2862 if (IS_PACMAN(element))
2863 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2864 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2865 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2866 else if (element == EL_KEY)
2867 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2869 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2871 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2874 static void TurnRound(int x, int y)
2886 { 0, 0 }, { 0, 0 }, { 0, 0 },
2891 int left, right, back;
2895 { MV_DOWN, MV_UP, MV_RIGHT },
2896 { MV_UP, MV_DOWN, MV_LEFT },
2898 { MV_LEFT, MV_RIGHT, MV_DOWN },
2899 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2900 { MV_RIGHT, MV_LEFT, MV_UP }
2903 int element = Tile[x][y];
2904 int old_move_dir = MovDir[x][y];
2905 int right_dir = turn[old_move_dir].right;
2906 int back_dir = turn[old_move_dir].back;
2907 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2908 int right_x = x + right_dx, right_y = y + right_dy;
2910 if (element == EL_PACMAN)
2912 boolean can_turn_right = FALSE;
2914 if (IN_LEV_FIELD(right_x, right_y) &&
2915 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2916 can_turn_right = TRUE;
2919 MovDir[x][y] = right_dir;
2921 MovDir[x][y] = back_dir;
2927 static void StartMoving_MM(int x, int y)
2929 int element = Tile[x][y];
2934 if (CAN_MOVE(element))
2938 if (MovDelay[x][y]) // wait some time before next movement
2946 // now make next step
2948 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2950 if (element == EL_PACMAN &&
2951 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2952 !ObjHit(newx, newy, HIT_POS_CENTER))
2954 Store[newx][newy] = Tile[newx][newy];
2955 Tile[newx][newy] = EL_EMPTY;
2957 DrawField_MM(newx, newy);
2959 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2960 ObjHit(newx, newy, HIT_POS_CENTER))
2962 // object was running against a wall
2969 InitMovingField_MM(x, y, MovDir[x][y]);
2973 ContinueMoving_MM(x, y);
2976 static void ContinueMoving_MM(int x, int y)
2978 int element = Tile[x][y];
2979 int direction = MovDir[x][y];
2980 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2981 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2982 int horiz_move = (dx!=0);
2983 int newx = x + dx, newy = y + dy;
2984 int step = (horiz_move ? dx : dy) * TILEX / 8;
2986 MovPos[x][y] += step;
2988 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2990 Tile[x][y] = EL_EMPTY;
2991 Tile[newx][newy] = element;
2993 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2994 MovDelay[newx][newy] = 0;
2996 if (!CAN_MOVE(element))
2997 MovDir[newx][newy] = 0;
3000 DrawField_MM(newx, newy);
3002 Stop[newx][newy] = TRUE;
3004 if (element == EL_PACMAN)
3006 if (Store[newx][newy] == EL_BOMB)
3007 Bang_MM(newx, newy);
3009 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3010 (LX + 2 * XS) / TILEX == newx &&
3011 (LY + 2 * YS) / TILEY == newy)
3018 else // still moving on
3023 laser.redraw = TRUE;
3026 boolean ClickElement(int x, int y, int button)
3028 static DelayCounter click_delay = { CLICK_DELAY };
3029 static boolean new_button = TRUE;
3030 boolean element_clicked = FALSE;
3035 // initialize static variables
3036 click_delay.count = 0;
3037 click_delay.value = CLICK_DELAY;
3043 // do not rotate objects hit by the laser after the game was solved
3044 if (game_mm.level_solved && Hit[x][y])
3047 if (button == MB_RELEASED)
3050 click_delay.value = CLICK_DELAY;
3052 // release eventually hold auto-rotating mirror
3053 RotateMirror(x, y, MB_RELEASED);
3058 if (!FrameReached(&click_delay) && !new_button)
3061 if (button == MB_MIDDLEBUTTON) // middle button has no function
3064 if (!IN_LEV_FIELD(x, y))
3067 if (Tile[x][y] == EL_EMPTY)
3070 element = Tile[x][y];
3072 if (IS_MIRROR(element) ||
3073 IS_BEAMER(element) ||
3074 IS_POLAR(element) ||
3075 IS_POLAR_CROSS(element) ||
3076 IS_DF_MIRROR(element) ||
3077 IS_DF_MIRROR_AUTO(element))
3079 RotateMirror(x, y, button);
3081 element_clicked = TRUE;
3083 else if (IS_MCDUFFIN(element))
3085 if (!laser.fuse_off)
3087 DrawLaser(0, DL_LASER_DISABLED);
3094 element = get_rotated_element(element, BUTTON_ROTATION(button));
3095 laser.start_angle = get_element_angle(element);
3099 Tile[x][y] = element;
3106 if (!laser.fuse_off)
3109 element_clicked = TRUE;
3111 else if (element == EL_FUSE_ON && laser.fuse_off)
3113 if (x != laser.fuse_x || y != laser.fuse_y)
3116 laser.fuse_off = FALSE;
3117 laser.fuse_x = laser.fuse_y = -1;
3119 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3122 element_clicked = TRUE;
3124 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3126 laser.fuse_off = TRUE;
3129 laser.overloaded = FALSE;
3131 DrawLaser(0, DL_LASER_DISABLED);
3132 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3134 element_clicked = TRUE;
3136 else if (element == EL_LIGHTBALL)
3139 RaiseScoreElement_MM(element);
3140 DrawLaser(0, DL_LASER_ENABLED);
3142 element_clicked = TRUE;
3144 else if (IS_ENVELOPE(element))
3146 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3148 element_clicked = TRUE;
3151 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3154 return element_clicked;
3157 static void RotateMirror(int x, int y, int button)
3159 if (button == MB_RELEASED)
3161 // release eventually hold auto-rotating mirror
3168 if (IS_MIRROR(Tile[x][y]) ||
3169 IS_POLAR_CROSS(Tile[x][y]) ||
3170 IS_POLAR(Tile[x][y]) ||
3171 IS_BEAMER(Tile[x][y]) ||
3172 IS_DF_MIRROR(Tile[x][y]) ||
3173 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3174 IS_GRID_WOOD_AUTO(Tile[x][y]))
3176 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3178 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3180 if (button == MB_LEFTBUTTON)
3182 // left mouse button only for manual adjustment, no auto-rotating;
3183 // freeze mirror for until mouse button released
3187 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3189 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3193 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3195 int edge = Hit[x][y];
3201 DrawLaser(edge - 1, DL_LASER_DISABLED);
3205 else if (ObjHit(x, y, HIT_POS_CENTER))
3207 int edge = Hit[x][y];
3211 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3216 DrawLaser(edge - 1, DL_LASER_DISABLED);
3223 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3228 if ((IS_BEAMER(Tile[x][y]) ||
3229 IS_POLAR(Tile[x][y]) ||
3230 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3232 if (IS_BEAMER(Tile[x][y]))
3235 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3236 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3249 DrawLaser(0, DL_LASER_ENABLED);
3253 static void AutoRotateMirrors(void)
3257 if (!FrameReached(&rotate_delay))
3260 for (x = 0; x < lev_fieldx; x++)
3262 for (y = 0; y < lev_fieldy; y++)
3264 int element = Tile[x][y];
3266 // do not rotate objects hit by the laser after the game was solved
3267 if (game_mm.level_solved && Hit[x][y])
3270 if (IS_DF_MIRROR_AUTO(element) ||
3271 IS_GRID_WOOD_AUTO(element) ||
3272 IS_GRID_STEEL_AUTO(element) ||
3273 element == EL_REFRACTOR)
3274 RotateMirror(x, y, MB_RIGHTBUTTON);
3279 static boolean ObjHit(int obx, int oby, int bits)
3286 if (bits & HIT_POS_CENTER)
3288 if (CheckLaserPixel(cSX + obx + 15,
3293 if (bits & HIT_POS_EDGE)
3295 for (i = 0; i < 4; i++)
3296 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3297 cSY + oby + 31 * (i / 2)))
3301 if (bits & HIT_POS_BETWEEN)
3303 for (i = 0; i < 4; i++)
3304 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3305 cSY + 4 + oby + 22 * (i / 2)))
3312 static void DeletePacMan(int px, int py)
3318 if (game_mm.num_pacman <= 1)
3320 game_mm.num_pacman = 0;
3324 for (i = 0; i < game_mm.num_pacman; i++)
3325 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3328 game_mm.num_pacman--;
3330 for (j = i; j < game_mm.num_pacman; j++)
3332 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3333 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3334 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3338 static void GameActions_MM_Ext(void)
3345 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3348 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3350 element = Tile[x][y];
3352 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3353 StartMoving_MM(x, y);
3354 else if (IS_MOVING(x, y))
3355 ContinueMoving_MM(x, y);
3356 else if (IS_EXPLODING(element))
3357 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3358 else if (element == EL_EXIT_OPENING)
3360 else if (element == EL_GRAY_BALL_OPENING)
3362 else if (IS_ENVELOPE_OPENING(element))
3364 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3366 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3368 else if (IS_MIRROR(element) ||
3369 IS_MIRROR_FIXED(element) ||
3370 element == EL_PRISM)
3371 DrawFieldTwinkle(x, y);
3372 else if (element == EL_GRAY_BALL_ACTIVE ||
3373 element == EL_BOMB_ACTIVE ||
3374 element == EL_MINE_ACTIVE)
3375 DrawFieldAnimated_MM(x, y);
3376 else if (!IS_BLOCKED(x, y))
3377 DrawFieldAnimatedIfNeeded_MM(x, y);
3380 AutoRotateMirrors();
3383 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3385 // redraw after Explode_MM() ...
3387 DrawLaser(0, DL_LASER_ENABLED);
3388 laser.redraw = FALSE;
3393 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3397 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3399 DrawLaser(0, DL_LASER_DISABLED);
3404 // skip all following game actions if game is over
3405 if (game_mm.game_over)
3408 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3412 GameOver_MM(GAME_OVER_NO_ENERGY);
3417 if (FrameReached(&energy_delay))
3419 if (game_mm.energy_left > 0)
3420 game_mm.energy_left--;
3422 // when out of energy, wait another frame to play "out of time" sound
3425 element = laser.dest_element;
3428 if (element != Tile[ELX][ELY])
3430 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3431 element, Tile[ELX][ELY]);
3435 if (!laser.overloaded && laser.overload_value == 0 &&
3436 element != EL_BOMB &&
3437 element != EL_BOMB_ACTIVE &&
3438 element != EL_MINE &&
3439 element != EL_MINE_ACTIVE &&
3440 element != EL_GRAY_BALL &&
3441 element != EL_GRAY_BALL_ACTIVE &&
3442 element != EL_BLOCK_STONE &&
3443 element != EL_BLOCK_WOOD &&
3444 element != EL_FUSE_ON &&
3445 element != EL_FUEL_FULL &&
3446 !IS_WALL_ICE(element) &&
3447 !IS_WALL_AMOEBA(element))
3450 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3452 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3453 (!laser.overloaded && laser.overload_value > 0)) &&
3454 FrameReached(&overload_delay))
3456 if (laser.overloaded)
3457 laser.overload_value++;
3459 laser.overload_value--;
3461 if (game_mm.cheat_no_overload)
3463 laser.overloaded = FALSE;
3464 laser.overload_value = 0;
3467 game_mm.laser_overload_value = laser.overload_value;
3469 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3471 SetLaserColor(0xFF);
3473 DrawLaser(0, DL_LASER_ENABLED);
3476 if (!laser.overloaded)
3477 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3478 else if (setup.sound_loops)
3479 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3481 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3483 if (laser.overload_value == MAX_LASER_OVERLOAD)
3485 UpdateAndDisplayGameControlValues();
3489 GameOver_MM(GAME_OVER_OVERLOADED);
3500 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3502 if (game_mm.cheat_no_explosion)
3507 laser.dest_element = EL_EXPLODING_OPAQUE;
3512 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3514 laser.fuse_off = TRUE;
3518 DrawLaser(0, DL_LASER_DISABLED);
3519 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3522 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3524 if (!Store2[ELX][ELY]) // check if content element not yet determined
3526 int last_anim_random_frame = gfx.anim_random_frame;
3529 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3530 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3532 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3533 native_mm_level.ball_choice_mode, 0,
3534 game_mm.ball_choice_pos);
3536 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3537 gfx.anim_random_frame = last_anim_random_frame;
3539 game_mm.ball_choice_pos++;
3541 int new_element = native_mm_level.ball_content[element_pos];
3542 int new_element_base = map_wall_to_base_element(new_element);
3544 if (IS_WALL(new_element_base))
3546 // always use completely filled wall element
3547 new_element = new_element_base | 0x000f;
3549 else if (native_mm_level.rotate_ball_content &&
3550 get_num_elements(new_element) > 1)
3552 // randomly rotate newly created game element
3553 new_element = get_rotated_element(new_element, RND(16));
3556 Store[ELX][ELY] = new_element;
3557 Store2[ELX][ELY] = TRUE;
3560 if (native_mm_level.explode_ball)
3563 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3565 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3570 if (IS_WALL_ICE(element) && CT > 50)
3572 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3574 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3575 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3576 Store2[ELX][ELY] = laser.wall_mask;
3578 laser.dest_element = Tile[ELX][ELY];
3583 if (IS_WALL_AMOEBA(element) && CT > 60)
3586 int element2 = Tile[ELX][ELY];
3588 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3591 for (i = laser.num_damages - 1; i >= 0; i--)
3592 if (laser.damage[i].is_mirror)
3595 r = laser.num_edges;
3596 d = laser.num_damages;
3603 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3606 DrawLaser(0, DL_LASER_ENABLED);
3609 x = laser.damage[k1].x;
3610 y = laser.damage[k1].y;
3615 for (i = 0; i < 4; i++)
3617 if (laser.wall_mask & (1 << i))
3619 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3620 cSY + ELY * TILEY + 31 * (i / 2)))
3623 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3624 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3631 for (i = 0; i < 4; i++)
3633 if (laser.wall_mask & (1 << i))
3635 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3636 cSY + ELY * TILEY + 31 * (i / 2)))
3643 if (laser.num_beamers > 0 ||
3644 k1 < 1 || k2 < 4 || k3 < 4 ||
3645 CheckLaserPixel(cSX + ELX * TILEX + 14,
3646 cSY + ELY * TILEY + 14))
3648 laser.num_edges = r;
3649 laser.num_damages = d;
3651 DrawLaser(0, DL_LASER_DISABLED);
3654 Tile[ELX][ELY] = element | laser.wall_mask;
3656 int x = ELX, y = ELY;
3657 int wall_mask = laser.wall_mask;
3660 DrawLaser(0, DL_LASER_ENABLED);
3662 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3664 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3665 Store[x][y] = EL_WALL_AMOEBA_BASE;
3666 Store2[x][y] = wall_mask;
3671 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3672 laser.stops_inside_element && CT > native_mm_level.time_block)
3677 if (ABS(XS) > ABS(YS))
3684 for (i = 0; i < 4; i++)
3691 x = ELX + Step[k * 4].x;
3692 y = ELY + Step[k * 4].y;
3694 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3697 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3705 laser.overloaded = (element == EL_BLOCK_STONE);
3710 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3713 Tile[x][y] = element;
3715 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3718 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3720 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3721 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3729 if (element == EL_FUEL_FULL && CT > 10)
3731 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3732 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3734 for (i = start; i <= num_init_game_frames; i++)
3736 if (i == num_init_game_frames)
3737 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3738 else if (setup.sound_loops)
3739 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3741 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3743 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3745 UpdateAndDisplayGameControlValues();
3750 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3752 DrawField_MM(ELX, ELY);
3754 DrawLaser(0, DL_LASER_ENABLED);
3760 void GameActions_MM(struct MouseActionInfo action)
3762 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3763 boolean button_released = (action.button == MB_RELEASED);
3765 GameActions_MM_Ext();
3767 CheckSingleStepMode_MM(element_clicked, button_released);
3770 static void MovePacMen(void)
3772 int mx, my, ox, oy, nx, ny;
3776 if (++pacman_nr >= game_mm.num_pacman)
3779 game_mm.pacman[pacman_nr].dir--;
3781 for (l = 1; l < 5; l++)
3783 game_mm.pacman[pacman_nr].dir++;
3785 if (game_mm.pacman[pacman_nr].dir > 4)
3786 game_mm.pacman[pacman_nr].dir = 1;
3788 if (game_mm.pacman[pacman_nr].dir % 2)
3791 my = game_mm.pacman[pacman_nr].dir - 2;
3796 mx = 3 - game_mm.pacman[pacman_nr].dir;
3799 ox = game_mm.pacman[pacman_nr].x;
3800 oy = game_mm.pacman[pacman_nr].y;
3803 element = Tile[nx][ny];
3805 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3808 if (!IS_EATABLE4PACMAN(element))
3811 if (ObjHit(nx, ny, HIT_POS_CENTER))
3814 Tile[ox][oy] = EL_EMPTY;
3816 EL_PACMAN_RIGHT - 1 +
3817 (game_mm.pacman[pacman_nr].dir - 1 +
3818 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3820 game_mm.pacman[pacman_nr].x = nx;
3821 game_mm.pacman[pacman_nr].y = ny;
3823 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3825 if (element != EL_EMPTY)
3827 int graphic = el2gfx(Tile[nx][ny]);
3832 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3835 ox = cSX + ox * TILEX;
3836 oy = cSY + oy * TILEY;
3838 for (i = 1; i < 33; i += 2)
3839 BlitBitmap(bitmap, window,
3840 src_x, src_y, TILEX, TILEY,
3841 ox + i * mx, oy + i * my);
3842 Ct = Ct + FrameCounter - CT;
3845 DrawField_MM(nx, ny);
3848 if (!laser.fuse_off)
3850 DrawLaser(0, DL_LASER_ENABLED);
3852 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3854 AddDamagedField(nx, ny);
3856 laser.damage[laser.num_damages - 1].edge = 0;
3860 if (element == EL_BOMB)
3861 DeletePacMan(nx, ny);
3863 if (IS_WALL_AMOEBA(element) &&
3864 (LX + 2 * XS) / TILEX == nx &&
3865 (LY + 2 * YS) / TILEY == ny)
3875 static void InitMovingField_MM(int x, int y, int direction)
3877 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3878 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3880 MovDir[x][y] = direction;
3881 MovDir[newx][newy] = direction;
3883 if (Tile[newx][newy] == EL_EMPTY)
3884 Tile[newx][newy] = EL_BLOCKED;
3887 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3889 int direction = MovDir[x][y];
3890 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3891 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3897 static void Blocked2Moving_MM(int x, int y,
3898 int *comes_from_x, int *comes_from_y)
3900 int oldx = x, oldy = y;
3901 int direction = MovDir[x][y];
3903 if (direction == MV_LEFT)
3905 else if (direction == MV_RIGHT)
3907 else if (direction == MV_UP)
3909 else if (direction == MV_DOWN)
3912 *comes_from_x = oldx;
3913 *comes_from_y = oldy;
3916 static int MovingOrBlocked2Element_MM(int x, int y)
3918 int element = Tile[x][y];
3920 if (element == EL_BLOCKED)
3924 Blocked2Moving_MM(x, y, &oldx, &oldy);
3926 return Tile[oldx][oldy];
3932 static void RemoveMovingField_MM(int x, int y)
3934 int oldx = x, oldy = y, newx = x, newy = y;
3936 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3939 if (IS_MOVING(x, y))
3941 Moving2Blocked_MM(x, y, &newx, &newy);
3942 if (Tile[newx][newy] != EL_BLOCKED)
3945 else if (Tile[x][y] == EL_BLOCKED)
3947 Blocked2Moving_MM(x, y, &oldx, &oldy);
3948 if (!IS_MOVING(oldx, oldy))
3952 Tile[oldx][oldy] = EL_EMPTY;
3953 Tile[newx][newy] = EL_EMPTY;
3954 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3955 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3957 DrawLevelField_MM(oldx, oldy);
3958 DrawLevelField_MM(newx, newy);
3961 static void RaiseScore_MM(int value)
3963 game_mm.score += value;
3966 void RaiseScoreElement_MM(int element)
3971 case EL_PACMAN_RIGHT:
3973 case EL_PACMAN_LEFT:
3974 case EL_PACMAN_DOWN:
3975 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3979 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3984 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3988 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3997 // ----------------------------------------------------------------------------
3998 // Mirror Magic game engine snapshot handling functions
3999 // ----------------------------------------------------------------------------
4001 void SaveEngineSnapshotValues_MM(void)
4005 engine_snapshot_mm.game_mm = game_mm;
4006 engine_snapshot_mm.laser = laser;
4008 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4010 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4012 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4013 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4014 engine_snapshot_mm.Box[x][y] = Box[x][y];
4015 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4019 engine_snapshot_mm.LX = LX;
4020 engine_snapshot_mm.LY = LY;
4021 engine_snapshot_mm.XS = XS;
4022 engine_snapshot_mm.YS = YS;
4023 engine_snapshot_mm.ELX = ELX;
4024 engine_snapshot_mm.ELY = ELY;
4025 engine_snapshot_mm.CT = CT;
4026 engine_snapshot_mm.Ct = Ct;
4028 engine_snapshot_mm.last_LX = last_LX;
4029 engine_snapshot_mm.last_LY = last_LY;
4030 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4031 engine_snapshot_mm.hold_x = hold_x;
4032 engine_snapshot_mm.hold_y = hold_y;
4033 engine_snapshot_mm.pacman_nr = pacman_nr;
4035 engine_snapshot_mm.rotate_delay = rotate_delay;
4036 engine_snapshot_mm.pacman_delay = pacman_delay;
4037 engine_snapshot_mm.energy_delay = energy_delay;
4038 engine_snapshot_mm.overload_delay = overload_delay;
4041 void LoadEngineSnapshotValues_MM(void)
4045 // stored engine snapshot buffers already restored at this point
4047 game_mm = engine_snapshot_mm.game_mm;
4048 laser = engine_snapshot_mm.laser;
4050 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4052 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4054 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4055 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4056 Box[x][y] = engine_snapshot_mm.Box[x][y];
4057 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4061 LX = engine_snapshot_mm.LX;
4062 LY = engine_snapshot_mm.LY;
4063 XS = engine_snapshot_mm.XS;
4064 YS = engine_snapshot_mm.YS;
4065 ELX = engine_snapshot_mm.ELX;
4066 ELY = engine_snapshot_mm.ELY;
4067 CT = engine_snapshot_mm.CT;
4068 Ct = engine_snapshot_mm.Ct;
4070 last_LX = engine_snapshot_mm.last_LX;
4071 last_LY = engine_snapshot_mm.last_LY;
4072 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4073 hold_x = engine_snapshot_mm.hold_x;
4074 hold_y = engine_snapshot_mm.hold_y;
4075 pacman_nr = engine_snapshot_mm.pacman_nr;
4077 rotate_delay = engine_snapshot_mm.rotate_delay;
4078 pacman_delay = engine_snapshot_mm.pacman_delay;
4079 energy_delay = engine_snapshot_mm.energy_delay;
4080 overload_delay = engine_snapshot_mm.overload_delay;
4082 RedrawPlayfield_MM();
4085 static int getAngleFromTouchDelta(int dx, int dy, int base)
4087 double pi = 3.141592653;
4088 double rad = atan2((double)-dy, (double)dx);
4089 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4090 double deg = rad2 * 180.0 / pi;
4092 return (int)(deg * base / 360.0 + 0.5) % base;
4095 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4097 // calculate start (source) position to be at the middle of the tile
4098 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4099 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4100 int dx = dst_mx - src_mx;
4101 int dy = dst_my - src_my;
4110 if (!IN_LEV_FIELD(x, y))
4113 element = Tile[x][y];
4115 if (!IS_MCDUFFIN(element) &&
4116 !IS_MIRROR(element) &&
4117 !IS_BEAMER(element) &&
4118 !IS_POLAR(element) &&
4119 !IS_POLAR_CROSS(element) &&
4120 !IS_DF_MIRROR(element))
4123 angle_old = get_element_angle(element);
4125 if (IS_MCDUFFIN(element))
4127 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4128 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4129 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4130 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4133 else if (IS_MIRROR(element) ||
4134 IS_DF_MIRROR(element))
4136 for (i = 0; i < laser.num_damages; i++)
4138 if (laser.damage[i].x == x &&
4139 laser.damage[i].y == y &&
4140 ObjHit(x, y, HIT_POS_CENTER))
4142 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4143 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4150 if (angle_new == -1)
4152 if (IS_MIRROR(element) ||
4153 IS_DF_MIRROR(element) ||
4157 if (IS_POLAR_CROSS(element))
4160 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4163 button = (angle_new == angle_old ? 0 :
4164 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4165 MB_LEFTBUTTON : MB_RIGHTBUTTON);