1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
100 static void Moving2Blocked_MM(int, int, int *, int *);
102 // bitmap for laser beam detection
103 static Bitmap *laser_bitmap = NULL;
105 // variables for laser control
106 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
107 static int hold_x = -1, hold_y = -1;
109 // variables for pacman control
110 static int pacman_nr = -1;
112 // various game engine delay counters
113 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
114 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
115 static DelayCounter energy_delay = { ENERGY_DELAY };
116 static DelayCounter overload_delay = { 0 };
118 // element mask positions for scanning pixels of MM elements
119 #define MM_MASK_MCDUFFIN_RIGHT 0
120 #define MM_MASK_MCDUFFIN_UP 1
121 #define MM_MASK_MCDUFFIN_LEFT 2
122 #define MM_MASK_MCDUFFIN_DOWN 3
123 #define MM_MASK_GRID_1 4
124 #define MM_MASK_GRID_2 5
125 #define MM_MASK_GRID_3 6
126 #define MM_MASK_GRID_4 7
127 #define MM_MASK_RECTANGLE 8
128 #define MM_MASK_CIRCLE 9
130 #define NUM_MM_MASKS 10
132 // element masks for scanning pixels of MM elements
133 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
317 static int get_element_angle(int element)
319 int element_phase = get_element_phase(element);
321 if (IS_MIRROR_FIXED(element) ||
322 IS_MCDUFFIN(element) ||
324 IS_RECEIVER(element))
325 return 4 * element_phase;
327 return element_phase;
330 static int get_opposite_angle(int angle)
332 int opposite_angle = angle + ANG_RAY_180;
334 // make sure "opposite_angle" is in valid interval [0, 15]
335 return (opposite_angle + 16) % 16;
338 static int get_mirrored_angle(int laser_angle, int mirror_angle)
340 int reflected_angle = 16 - laser_angle + mirror_angle;
342 // make sure "reflected_angle" is in valid interval [0, 15]
343 return (reflected_angle + 16) % 16;
346 static void DrawLaserLines(struct XY *points, int num_points, int mode)
348 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
349 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
351 DrawLines(drawto, points, num_points, pixel_drawto);
355 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
360 static boolean CheckLaserPixel(int x, int y)
366 pixel = ReadPixel(laser_bitmap, x, y);
370 return (pixel == WHITE_PIXEL);
373 static void CheckExitMM(void)
375 int exit_element = EL_EMPTY;
379 static int xy[4][2] =
387 for (y = 0; y < lev_fieldy; y++)
389 for (x = 0; x < lev_fieldx; x++)
391 if (Tile[x][y] == EL_EXIT_CLOSED)
393 // initiate opening animation of exit door
394 Tile[x][y] = EL_EXIT_OPENING;
396 exit_element = EL_EXIT_OPEN;
400 else if (IS_RECEIVER(Tile[x][y]))
402 // remove field that blocks receiver
403 int phase = Tile[x][y] - EL_RECEIVER_START;
404 int blocking_x, blocking_y;
406 blocking_x = x + xy[phase][0];
407 blocking_y = y + xy[phase][1];
409 if (IN_LEV_FIELD(blocking_x, blocking_y))
411 Tile[blocking_x][blocking_y] = EL_EMPTY;
413 DrawField_MM(blocking_x, blocking_y);
416 exit_element = EL_RECEIVER;
423 if (exit_element != EL_EMPTY)
424 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
427 static void SetLaserColor(int brightness)
429 int color_min = 0x00;
430 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
431 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
432 int color_down = color_max - color_up;
435 GetPixelFromRGB(window,
436 (game_mm.laser_red ? color_max : color_up),
437 (game_mm.laser_green ? color_down : color_min),
438 (game_mm.laser_blue ? color_down : color_min));
441 static void InitMovDir_MM(int x, int y)
443 int element = Tile[x][y];
444 static int direction[3][4] =
446 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
447 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
448 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
453 case EL_PACMAN_RIGHT:
457 Tile[x][y] = EL_PACMAN;
458 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
466 static void InitField(int x, int y, boolean init_game)
468 int element = Tile[x][y];
473 Tile[x][y] = EL_EMPTY;
478 if (init_game && native_mm_level.auto_count_kettles)
479 game_mm.kettles_still_needed++;
482 case EL_LIGHTBULB_OFF:
483 game_mm.lights_still_needed++;
487 if (IS_MIRROR(element) ||
488 IS_BEAMER_OLD(element) ||
489 IS_BEAMER(element) ||
491 IS_POLAR_CROSS(element) ||
492 IS_DF_MIRROR(element) ||
493 IS_DF_MIRROR_AUTO(element) ||
494 IS_GRID_STEEL_AUTO(element) ||
495 IS_GRID_WOOD_AUTO(element) ||
496 IS_FIBRE_OPTIC(element))
498 if (IS_BEAMER_OLD(element))
500 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
501 element = Tile[x][y];
504 if (!IS_FIBRE_OPTIC(element))
506 static int steps_grid_auto = 0;
508 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
509 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
511 if (IS_GRID_STEEL_AUTO(element) ||
512 IS_GRID_WOOD_AUTO(element))
513 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
515 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
517 game_mm.cycle[game_mm.num_cycle].x = x;
518 game_mm.cycle[game_mm.num_cycle].y = y;
522 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
524 int beamer_nr = BEAMER_NR(element);
525 int nr = laser.beamer[beamer_nr][0].num;
527 laser.beamer[beamer_nr][nr].x = x;
528 laser.beamer[beamer_nr][nr].y = y;
529 laser.beamer[beamer_nr][nr].num = 1;
532 else if (IS_PACMAN(element))
536 else if (IS_MCDUFFIN(element) || IS_LASER(element))
540 laser.start_edge.x = x;
541 laser.start_edge.y = y;
542 laser.start_angle = get_element_angle(element);
545 if (IS_MCDUFFIN(element))
547 game_mm.laser_red = native_mm_level.mm_laser_red;
548 game_mm.laser_green = native_mm_level.mm_laser_green;
549 game_mm.laser_blue = native_mm_level.mm_laser_blue;
553 game_mm.laser_red = native_mm_level.df_laser_red;
554 game_mm.laser_green = native_mm_level.df_laser_green;
555 game_mm.laser_blue = native_mm_level.df_laser_blue;
563 static void InitCycleElements_RotateSingleStep(void)
567 if (game_mm.num_cycle == 0) // no elements to cycle
570 for (i = 0; i < game_mm.num_cycle; i++)
572 int x = game_mm.cycle[i].x;
573 int y = game_mm.cycle[i].y;
574 int step = SIGN(game_mm.cycle[i].steps);
575 int last_element = Tile[x][y];
576 int next_element = get_rotated_element(last_element, step);
578 if (!game_mm.cycle[i].steps)
581 Tile[x][y] = next_element;
583 game_mm.cycle[i].steps -= step;
587 static void InitLaser(void)
589 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
590 int step = (IS_LASER(start_element) ? 4 : 0);
592 LX = laser.start_edge.x * TILEX;
593 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
596 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
598 LY = laser.start_edge.y * TILEY;
599 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
600 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
604 XS = 2 * Step[laser.start_angle].x;
605 YS = 2 * Step[laser.start_angle].y;
607 laser.current_angle = laser.start_angle;
609 laser.num_damages = 0;
611 laser.num_beamers = 0;
612 laser.beamer_edge[0] = 0;
614 laser.dest_element = EL_EMPTY;
617 AddLaserEdge(LX, LY); // set laser starting edge
622 void InitGameEngine_MM(void)
628 // initialize laser bitmap to current playfield (screen) size
629 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
630 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
634 // set global game control values
635 game_mm.num_cycle = 0;
636 game_mm.num_pacman = 0;
639 game_mm.energy_left = 0; // later set to "native_mm_level.time"
640 game_mm.kettles_still_needed =
641 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
642 game_mm.lights_still_needed = 0;
643 game_mm.num_keys = 0;
644 game_mm.ball_choice_pos = 0;
646 game_mm.laser_red = FALSE;
647 game_mm.laser_green = FALSE;
648 game_mm.laser_blue = TRUE;
650 game_mm.level_solved = FALSE;
651 game_mm.game_over = FALSE;
652 game_mm.game_over_cause = 0;
654 game_mm.laser_overload_value = 0;
655 game_mm.laser_enabled = FALSE;
657 // set global laser control values (must be set before "InitLaser()")
658 laser.start_edge.x = 0;
659 laser.start_edge.y = 0;
660 laser.start_angle = 0;
662 for (i = 0; i < MAX_NUM_BEAMERS; i++)
663 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
665 laser.overloaded = FALSE;
666 laser.overload_value = 0;
667 laser.fuse_off = FALSE;
668 laser.fuse_x = laser.fuse_y = -1;
670 laser.dest_element = EL_EMPTY;
671 laser.dest_element_last = EL_EMPTY;
672 laser.dest_element_last_x = -1;
673 laser.dest_element_last_y = -1;
687 rotate_delay.count = 0;
688 pacman_delay.count = 0;
689 energy_delay.count = 0;
690 overload_delay.count = 0;
692 ClickElement(-1, -1, -1);
694 for (x = 0; x < lev_fieldx; x++)
696 for (y = 0; y < lev_fieldy; y++)
698 Tile[x][y] = Ur[x][y];
699 Hit[x][y] = Box[x][y] = 0;
701 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
702 Store[x][y] = Store2[x][y] = 0;
705 InitField(x, y, TRUE);
712 void InitGameActions_MM(void)
714 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
715 int cycle_steps_done = 0;
720 for (i = 0; i <= num_init_game_frames; i++)
722 if (i == num_init_game_frames)
723 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
724 else if (setup.sound_loops)
725 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
727 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
729 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
731 UpdateAndDisplayGameControlValues();
733 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
735 InitCycleElements_RotateSingleStep();
740 AdvanceFrameCounter();
748 if (setup.quick_doors)
755 if (game_mm.kettles_still_needed == 0)
758 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
759 SetTileCursorActive(TRUE);
761 // restart all delay counters after initially cycling game elements
762 ResetFrameCounter(&rotate_delay);
763 ResetFrameCounter(&pacman_delay);
764 ResetFrameCounter(&energy_delay);
765 ResetFrameCounter(&overload_delay);
768 static void FadeOutLaser(void)
772 for (i = 15; i >= 0; i--)
774 SetLaserColor(0x11 * i);
776 DrawLaser(0, DL_LASER_ENABLED);
779 Delay_WithScreenUpdates(50);
782 DrawLaser(0, DL_LASER_DISABLED);
784 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
787 static void GameOver_MM(int game_over_cause)
789 // do not handle game over if request dialog is already active
790 if (game.request_active)
793 game_mm.game_over = TRUE;
794 game_mm.game_over_cause = game_over_cause;
796 if (setup.ask_on_game_over)
797 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
798 "Bomb killed Mc Duffin! Play it again?" :
799 game_over_cause == GAME_OVER_NO_ENERGY ?
800 "Out of magic energy! Play it again?" :
801 game_over_cause == GAME_OVER_OVERLOADED ?
802 "Magic spell hit Mc Duffin! Play it again?" :
805 SetTileCursorActive(FALSE);
808 void AddLaserEdge(int lx, int ly)
813 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
815 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
820 laser.edge[laser.num_edges].x = cSX2 + lx;
821 laser.edge[laser.num_edges].y = cSY2 + ly;
827 void AddDamagedField(int ex, int ey)
829 // prevent adding the same field position again
830 if (laser.num_damages > 0 &&
831 laser.damage[laser.num_damages - 1].x == ex &&
832 laser.damage[laser.num_damages - 1].y == ey &&
833 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
836 laser.damage[laser.num_damages].is_mirror = FALSE;
837 laser.damage[laser.num_damages].angle = laser.current_angle;
838 laser.damage[laser.num_damages].edge = laser.num_edges;
839 laser.damage[laser.num_damages].x = ex;
840 laser.damage[laser.num_damages].y = ey;
844 static boolean StepBehind(void)
850 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
851 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
853 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
859 static int getMaskFromElement(int element)
861 if (IS_GRID(element))
862 return MM_MASK_GRID_1 + get_element_phase(element);
863 else if (IS_MCDUFFIN(element))
864 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
865 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
866 return MM_MASK_RECTANGLE;
868 return MM_MASK_CIRCLE;
871 static int ScanPixel(void)
876 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
877 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
880 // follow laser beam until it hits something (at least the screen border)
881 while (hit_mask == HIT_MASK_NO_HIT)
887 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
888 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
890 Debug("game:mm:ScanPixel", "touched screen border!");
896 for (i = 0; i < 4; i++)
898 int px = LX + (i % 2) * 2;
899 int py = LY + (i / 2) * 2;
902 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
903 int ly = (py + TILEY) / TILEY - 1; // negative values!
906 if (IN_LEV_FIELD(lx, ly))
908 int element = Tile[lx][ly];
910 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
914 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
916 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
918 pixel = ((element & (1 << pos)) ? 1 : 0);
922 int pos = getMaskFromElement(element);
924 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
929 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
930 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
933 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
934 hit_mask |= (1 << i);
937 if (hit_mask == HIT_MASK_NO_HIT)
939 // hit nothing -- go on with another step
948 static void DeactivateLaserTargetElement(void)
950 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
951 laser.dest_element_last == EL_MINE_ACTIVE ||
952 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
953 laser.dest_element_last == EL_GRAY_BALL_OPENING)
955 int x = laser.dest_element_last_x;
956 int y = laser.dest_element_last_y;
957 int element = laser.dest_element_last;
959 if (Tile[x][y] == element)
960 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
961 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
963 if (Tile[x][y] == EL_GRAY_BALL)
966 laser.dest_element_last = EL_EMPTY;
967 laser.dest_element_last_x = -1;
968 laser.dest_element_last_y = -1;
974 int element = EL_EMPTY;
975 int last_element = EL_EMPTY;
976 int end = 0, rf = laser.num_edges;
978 // do not scan laser again after the game was lost for whatever reason
979 if (game_mm.game_over)
982 // do not scan laser if fuse is off
986 DeactivateLaserTargetElement();
988 laser.overloaded = FALSE;
989 laser.stops_inside_element = FALSE;
991 DrawLaser(0, DL_LASER_ENABLED);
994 Debug("game:mm:ScanLaser",
995 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1003 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1006 laser.overloaded = TRUE;
1011 hit_mask = ScanPixel();
1014 Debug("game:mm:ScanLaser",
1015 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1019 // hit something -- check out what it was
1020 ELX = (LX + XS) / TILEX;
1021 ELY = (LY + YS) / TILEY;
1024 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1025 hit_mask, LX, LY, ELX, ELY);
1028 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1031 laser.dest_element = element;
1036 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1038 /* we have hit the top-right and bottom-left element --
1039 choose the bottom-left one */
1040 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1041 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1042 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1043 ELX = (LX - 2) / TILEX;
1044 ELY = (LY + 2) / TILEY;
1047 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1049 /* we have hit the top-left and bottom-right element --
1050 choose the top-left one */
1051 // !!! SEE ABOVE !!!
1052 ELX = (LX - 2) / TILEX;
1053 ELY = (LY - 2) / TILEY;
1057 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1058 hit_mask, LX, LY, ELX, ELY);
1061 last_element = element;
1063 element = Tile[ELX][ELY];
1064 laser.dest_element = element;
1067 Debug("game:mm:ScanLaser",
1068 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1071 LX % TILEX, LY % TILEY,
1076 if (!IN_LEV_FIELD(ELX, ELY))
1077 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1081 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1082 if (element == EL_EMPTY &&
1083 IS_GRID_STEEL(last_element) &&
1084 laser.current_angle % 4) // angle is not 90°
1085 element = last_element;
1087 if (element == EL_EMPTY)
1089 if (!HitOnlyAnEdge(hit_mask))
1092 else if (element == EL_FUSE_ON)
1094 if (HitPolarizer(element, hit_mask))
1097 else if (IS_GRID(element) || IS_DF_GRID(element))
1099 if (HitPolarizer(element, hit_mask))
1102 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1103 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1105 if (HitBlock(element, hit_mask))
1112 else if (IS_MCDUFFIN(element))
1114 if (HitLaserSource(element, hit_mask))
1117 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1118 IS_RECEIVER(element))
1120 if (HitLaserDestination(element, hit_mask))
1123 else if (IS_WALL(element))
1125 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1127 if (HitReflectingWalls(element, hit_mask))
1132 if (HitAbsorbingWalls(element, hit_mask))
1138 if (HitElement(element, hit_mask))
1143 DrawLaser(rf - 1, DL_LASER_ENABLED);
1144 rf = laser.num_edges;
1146 if (!IS_DF_WALL_STEEL(element))
1148 // only used for scanning DF steel walls; reset for all other elements
1156 if (laser.dest_element != Tile[ELX][ELY])
1158 Debug("game:mm:ScanLaser",
1159 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1160 laser.dest_element, Tile[ELX][ELY]);
1164 if (!end && !laser.stops_inside_element && !StepBehind())
1167 Debug("game:mm:ScanLaser", "Go one step back");
1173 AddLaserEdge(LX, LY);
1177 DrawLaser(rf - 1, DL_LASER_ENABLED);
1179 Ct = CT = FrameCounter;
1182 if (!IN_LEV_FIELD(ELX, ELY))
1183 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1187 static void ScanLaser_FromLastMirror(void)
1189 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1192 for (i = start_pos; i >= 0; i--)
1193 if (laser.damage[i].is_mirror)
1196 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1198 DrawLaser(start_edge, DL_LASER_DISABLED);
1203 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1209 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1210 start_edge, num_edges, mode);
1215 Warn("DrawLaserExt: start_edge < 0");
1222 Warn("DrawLaserExt: num_edges < 0");
1228 if (mode == DL_LASER_DISABLED)
1230 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1234 // now draw the laser to the backbuffer and (if enabled) to the screen
1235 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1237 redraw_mask |= REDRAW_FIELD;
1239 if (mode == DL_LASER_ENABLED)
1242 // after the laser was deleted, the "damaged" graphics must be restored
1243 if (laser.num_damages)
1245 int damage_start = 0;
1248 // determine the starting edge, from which graphics need to be restored
1251 for (i = 0; i < laser.num_damages; i++)
1253 if (laser.damage[i].edge == start_edge + 1)
1262 // restore graphics from this starting edge to the end of damage list
1263 for (i = damage_start; i < laser.num_damages; i++)
1265 int lx = laser.damage[i].x;
1266 int ly = laser.damage[i].y;
1267 int element = Tile[lx][ly];
1269 if (Hit[lx][ly] == laser.damage[i].edge)
1270 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1273 if (Box[lx][ly] == laser.damage[i].edge)
1276 if (IS_DRAWABLE(element))
1277 DrawField_MM(lx, ly);
1280 elx = laser.damage[damage_start].x;
1281 ely = laser.damage[damage_start].y;
1282 element = Tile[elx][ely];
1285 if (IS_BEAMER(element))
1289 for (i = 0; i < laser.num_beamers; i++)
1290 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1292 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1293 mode, elx, ely, Hit[elx][ely], start_edge);
1294 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1295 get_element_angle(element), laser.damage[damage_start].angle);
1299 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1300 laser.num_beamers > 0 &&
1301 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1303 // element is outgoing beamer
1304 laser.num_damages = damage_start + 1;
1306 if (IS_BEAMER(element))
1307 laser.current_angle = get_element_angle(element);
1311 // element is incoming beamer or other element
1312 laser.num_damages = damage_start;
1313 laser.current_angle = laser.damage[laser.num_damages].angle;
1318 // no damages but McDuffin himself (who needs to be redrawn anyway)
1320 elx = laser.start_edge.x;
1321 ely = laser.start_edge.y;
1322 element = Tile[elx][ely];
1325 laser.num_edges = start_edge + 1;
1326 if (start_edge == 0)
1327 laser.current_angle = laser.start_angle;
1329 LX = laser.edge[start_edge].x - cSX2;
1330 LY = laser.edge[start_edge].y - cSY2;
1331 XS = 2 * Step[laser.current_angle].x;
1332 YS = 2 * Step[laser.current_angle].y;
1335 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1341 if (IS_BEAMER(element) ||
1342 IS_FIBRE_OPTIC(element) ||
1343 IS_PACMAN(element) ||
1344 IS_POLAR(element) ||
1345 IS_POLAR_CROSS(element) ||
1346 element == EL_FUSE_ON)
1351 Debug("game:mm:DrawLaserExt", "element == %d", element);
1354 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1355 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1359 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1360 ((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 incoming beamer or other element
1365 step_size = -step_size;
1370 if (IS_BEAMER(element))
1371 Debug("game:mm:DrawLaserExt",
1372 "start_edge == %d, laser.beamer_edge == %d",
1373 start_edge, laser.beamer_edge);
1376 LX += step_size * XS;
1377 LY += step_size * YS;
1379 else if (element != EL_EMPTY)
1388 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1393 void DrawLaser(int start_edge, int mode)
1395 // do not draw laser if fuse is off
1396 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1399 if (mode == DL_LASER_DISABLED)
1400 DeactivateLaserTargetElement();
1402 if (laser.num_edges - start_edge < 0)
1404 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1409 // check if laser is interrupted by beamer element
1410 if (laser.num_beamers > 0 &&
1411 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1413 if (mode == DL_LASER_ENABLED)
1416 int tmp_start_edge = start_edge;
1418 // draw laser segments forward from the start to the last beamer
1419 for (i = 0; i < laser.num_beamers; i++)
1421 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1423 if (tmp_num_edges <= 0)
1427 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1428 i, laser.beamer_edge[i], tmp_start_edge);
1431 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1433 tmp_start_edge = laser.beamer_edge[i];
1436 // draw last segment from last beamer to the end
1437 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1443 int last_num_edges = laser.num_edges;
1444 int num_beamers = laser.num_beamers;
1446 // delete laser segments backward from the end to the first beamer
1447 for (i = num_beamers - 1; i >= 0; i--)
1449 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1451 if (laser.beamer_edge[i] - start_edge <= 0)
1454 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1456 last_num_edges = laser.beamer_edge[i];
1457 laser.num_beamers--;
1461 if (last_num_edges - start_edge <= 0)
1462 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1463 last_num_edges, start_edge);
1466 // special case when rotating first beamer: delete laser edge on beamer
1467 // (but do not start scanning on previous edge to prevent mirror sound)
1468 if (last_num_edges - start_edge == 1 && start_edge > 0)
1469 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1471 // delete first segment from start to the first beamer
1472 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1477 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1480 game_mm.laser_enabled = mode;
1483 void DrawLaser_MM(void)
1485 DrawLaser(0, game_mm.laser_enabled);
1488 boolean HitElement(int element, int hit_mask)
1490 if (HitOnlyAnEdge(hit_mask))
1493 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1494 element = MovingOrBlocked2Element_MM(ELX, ELY);
1497 Debug("game:mm:HitElement", "(1): element == %d", element);
1501 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1502 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1505 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1509 AddDamagedField(ELX, ELY);
1511 // this is more precise: check if laser would go through the center
1512 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1516 // prevent cutting through laser emitter with laser beam
1517 if (IS_LASER(element))
1520 // skip the whole element before continuing the scan
1528 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1530 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1532 /* skipping scan positions to the right and down skips one scan
1533 position too much, because this is only the top left scan position
1534 of totally four scan positions (plus one to the right, one to the
1535 bottom and one to the bottom right) */
1536 /* ... but only roll back scan position if more than one step done */
1546 Debug("game:mm:HitElement", "(2): element == %d", element);
1549 if (LX + 5 * XS < 0 ||
1559 Debug("game:mm:HitElement", "(3): element == %d", element);
1562 if (IS_POLAR(element) &&
1563 ((element - EL_POLAR_START) % 2 ||
1564 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1566 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1568 laser.num_damages--;
1573 if (IS_POLAR_CROSS(element) &&
1574 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1576 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1578 laser.num_damages--;
1583 if (!IS_BEAMER(element) &&
1584 !IS_FIBRE_OPTIC(element) &&
1585 !IS_GRID_WOOD(element) &&
1586 element != EL_FUEL_EMPTY)
1589 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1590 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1592 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1595 LX = ELX * TILEX + 14;
1596 LY = ELY * TILEY + 14;
1598 AddLaserEdge(LX, LY);
1601 if (IS_MIRROR(element) ||
1602 IS_MIRROR_FIXED(element) ||
1603 IS_POLAR(element) ||
1604 IS_POLAR_CROSS(element) ||
1605 IS_DF_MIRROR(element) ||
1606 IS_DF_MIRROR_AUTO(element) ||
1607 element == EL_PRISM ||
1608 element == EL_REFRACTOR)
1610 int current_angle = laser.current_angle;
1613 laser.num_damages--;
1615 AddDamagedField(ELX, ELY);
1617 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1620 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1622 if (IS_MIRROR(element) ||
1623 IS_MIRROR_FIXED(element) ||
1624 IS_DF_MIRROR(element) ||
1625 IS_DF_MIRROR_AUTO(element))
1626 laser.current_angle = get_mirrored_angle(laser.current_angle,
1627 get_element_angle(element));
1629 if (element == EL_PRISM || element == EL_REFRACTOR)
1630 laser.current_angle = RND(16);
1632 XS = 2 * Step[laser.current_angle].x;
1633 YS = 2 * Step[laser.current_angle].y;
1635 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1640 LX += step_size * XS;
1641 LY += step_size * YS;
1643 // draw sparkles on mirror
1644 if ((IS_MIRROR(element) ||
1645 IS_MIRROR_FIXED(element) ||
1646 element == EL_PRISM) &&
1647 current_angle != laser.current_angle)
1649 MovDelay[ELX][ELY] = 11; // start animation
1652 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1653 current_angle != laser.current_angle)
1654 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1657 (get_opposite_angle(laser.current_angle) ==
1658 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1660 return (laser.overloaded ? TRUE : FALSE);
1663 if (element == EL_FUEL_FULL)
1665 laser.stops_inside_element = TRUE;
1670 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1672 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1674 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1675 element == EL_MINE ? EL_MINE_ACTIVE :
1676 EL_GRAY_BALL_ACTIVE);
1678 laser.dest_element_last = Tile[ELX][ELY];
1679 laser.dest_element_last_x = ELX;
1680 laser.dest_element_last_y = ELY;
1682 if (element == EL_MINE)
1683 laser.overloaded = TRUE;
1686 if (element == EL_KETTLE ||
1687 element == EL_CELL ||
1688 element == EL_KEY ||
1689 element == EL_LIGHTBALL ||
1690 element == EL_PACMAN ||
1691 IS_PACMAN(element) ||
1692 IS_ENVELOPE(element))
1694 if (!IS_PACMAN(element) &&
1695 !IS_ENVELOPE(element))
1698 if (element == EL_PACMAN)
1701 if (element == EL_KETTLE || element == EL_CELL)
1703 if (game_mm.kettles_still_needed > 0)
1704 game_mm.kettles_still_needed--;
1706 game.snapshot.collected_item = TRUE;
1708 if (game_mm.kettles_still_needed == 0)
1712 DrawLaser(0, DL_LASER_ENABLED);
1715 else if (element == EL_KEY)
1719 else if (IS_PACMAN(element))
1721 DeletePacMan(ELX, ELY);
1723 else if (IS_ENVELOPE(element))
1725 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1728 RaiseScoreElement_MM(element);
1733 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1735 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1737 DrawLaser(0, DL_LASER_ENABLED);
1739 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1741 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1742 game_mm.lights_still_needed--;
1746 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1747 game_mm.lights_still_needed++;
1750 DrawField_MM(ELX, ELY);
1751 DrawLaser(0, DL_LASER_ENABLED);
1756 laser.stops_inside_element = TRUE;
1762 Debug("game:mm:HitElement", "(4): element == %d", element);
1765 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1766 laser.num_beamers < MAX_NUM_BEAMERS &&
1767 laser.beamer[BEAMER_NR(element)][1].num)
1769 int beamer_angle = get_element_angle(element);
1770 int beamer_nr = BEAMER_NR(element);
1774 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1777 laser.num_damages--;
1779 if (IS_FIBRE_OPTIC(element) ||
1780 laser.current_angle == get_opposite_angle(beamer_angle))
1784 LX = ELX * TILEX + 14;
1785 LY = ELY * TILEY + 14;
1787 AddLaserEdge(LX, LY);
1788 AddDamagedField(ELX, ELY);
1790 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1793 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1795 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1796 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1797 ELX = laser.beamer[beamer_nr][pos].x;
1798 ELY = laser.beamer[beamer_nr][pos].y;
1799 LX = ELX * TILEX + 14;
1800 LY = ELY * TILEY + 14;
1802 if (IS_BEAMER(element))
1804 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1805 XS = 2 * Step[laser.current_angle].x;
1806 YS = 2 * Step[laser.current_angle].y;
1809 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1811 AddLaserEdge(LX, LY);
1812 AddDamagedField(ELX, ELY);
1814 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1817 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1819 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1824 LX += step_size * XS;
1825 LY += step_size * YS;
1827 laser.num_beamers++;
1836 boolean HitOnlyAnEdge(int hit_mask)
1838 // check if the laser hit only the edge of an element and, if so, go on
1841 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1845 if ((hit_mask == HIT_MASK_TOPLEFT ||
1846 hit_mask == HIT_MASK_TOPRIGHT ||
1847 hit_mask == HIT_MASK_BOTTOMLEFT ||
1848 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1849 laser.current_angle % 4) // angle is not 90°
1853 if (hit_mask == HIT_MASK_TOPLEFT)
1858 else if (hit_mask == HIT_MASK_TOPRIGHT)
1863 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1868 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1874 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1880 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1887 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1893 boolean HitPolarizer(int element, int hit_mask)
1895 if (HitOnlyAnEdge(hit_mask))
1898 if (IS_DF_GRID(element))
1900 int grid_angle = get_element_angle(element);
1903 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1904 grid_angle, laser.current_angle);
1907 AddLaserEdge(LX, LY);
1908 AddDamagedField(ELX, ELY);
1911 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1913 if (laser.current_angle == grid_angle ||
1914 laser.current_angle == get_opposite_angle(grid_angle))
1916 // skip the whole element before continuing the scan
1922 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1924 if (LX/TILEX > ELX || LY/TILEY > ELY)
1926 /* skipping scan positions to the right and down skips one scan
1927 position too much, because this is only the top left scan position
1928 of totally four scan positions (plus one to the right, one to the
1929 bottom and one to the bottom right) */
1935 AddLaserEdge(LX, LY);
1941 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1943 LX / TILEX, LY / TILEY,
1944 LX % TILEX, LY % TILEY);
1949 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1951 return HitReflectingWalls(element, hit_mask);
1955 return HitAbsorbingWalls(element, hit_mask);
1958 else if (IS_GRID_STEEL(element))
1960 return HitReflectingWalls(element, hit_mask);
1962 else // IS_GRID_WOOD
1964 return HitAbsorbingWalls(element, hit_mask);
1970 boolean HitBlock(int element, int hit_mask)
1972 boolean check = FALSE;
1974 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1975 game_mm.num_keys == 0)
1978 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1981 int ex = ELX * TILEX + 14;
1982 int ey = ELY * TILEY + 14;
1986 for (i = 1; i < 32; i++)
1991 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1996 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1997 return HitAbsorbingWalls(element, hit_mask);
2001 AddLaserEdge(LX - XS, LY - YS);
2002 AddDamagedField(ELX, ELY);
2005 Box[ELX][ELY] = laser.num_edges;
2007 return HitReflectingWalls(element, hit_mask);
2010 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2012 int xs = XS / 2, ys = YS / 2;
2013 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2014 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2016 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2017 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2019 laser.overloaded = (element == EL_GATE_STONE);
2024 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2025 (hit_mask == HIT_MASK_TOP ||
2026 hit_mask == HIT_MASK_LEFT ||
2027 hit_mask == HIT_MASK_RIGHT ||
2028 hit_mask == HIT_MASK_BOTTOM))
2029 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2030 hit_mask == HIT_MASK_BOTTOM),
2031 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2032 hit_mask == HIT_MASK_RIGHT));
2033 AddLaserEdge(LX, LY);
2039 if (element == EL_GATE_STONE && Box[ELX][ELY])
2041 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2053 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2055 int xs = XS / 2, ys = YS / 2;
2056 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2057 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2059 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2060 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2062 laser.overloaded = (element == EL_BLOCK_STONE);
2067 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2068 (hit_mask == HIT_MASK_TOP ||
2069 hit_mask == HIT_MASK_LEFT ||
2070 hit_mask == HIT_MASK_RIGHT ||
2071 hit_mask == HIT_MASK_BOTTOM))
2072 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2073 hit_mask == HIT_MASK_BOTTOM),
2074 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2075 hit_mask == HIT_MASK_RIGHT));
2076 AddDamagedField(ELX, ELY);
2078 LX = ELX * TILEX + 14;
2079 LY = ELY * TILEY + 14;
2081 AddLaserEdge(LX, LY);
2083 laser.stops_inside_element = TRUE;
2091 boolean HitLaserSource(int element, int hit_mask)
2093 if (HitOnlyAnEdge(hit_mask))
2096 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2098 laser.overloaded = TRUE;
2103 boolean HitLaserDestination(int element, int hit_mask)
2105 if (HitOnlyAnEdge(hit_mask))
2108 if (element != EL_EXIT_OPEN &&
2109 !(IS_RECEIVER(element) &&
2110 game_mm.kettles_still_needed == 0 &&
2111 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2113 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2118 if (IS_RECEIVER(element) ||
2119 (IS_22_5_ANGLE(laser.current_angle) &&
2120 (ELX != (LX + 6 * XS) / TILEX ||
2121 ELY != (LY + 6 * YS) / TILEY ||
2130 LX = ELX * TILEX + 14;
2131 LY = ELY * TILEY + 14;
2133 laser.stops_inside_element = TRUE;
2136 AddLaserEdge(LX, LY);
2137 AddDamagedField(ELX, ELY);
2139 if (game_mm.lights_still_needed == 0)
2141 game_mm.level_solved = TRUE;
2143 SetTileCursorActive(FALSE);
2149 boolean HitReflectingWalls(int element, int hit_mask)
2151 // check if laser hits side of a wall with an angle that is not 90°
2152 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2153 hit_mask == HIT_MASK_LEFT ||
2154 hit_mask == HIT_MASK_RIGHT ||
2155 hit_mask == HIT_MASK_BOTTOM))
2157 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2162 if (!IS_DF_GRID(element))
2163 AddLaserEdge(LX, LY);
2165 // check if laser hits wall with an angle of 45°
2166 if (!IS_22_5_ANGLE(laser.current_angle))
2168 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2171 laser.current_angle = get_mirrored_angle(laser.current_angle,
2174 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2177 laser.current_angle = get_mirrored_angle(laser.current_angle,
2181 AddLaserEdge(LX, LY);
2183 XS = 2 * Step[laser.current_angle].x;
2184 YS = 2 * Step[laser.current_angle].y;
2188 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2190 laser.current_angle = get_mirrored_angle(laser.current_angle,
2195 if (!IS_DF_GRID(element))
2196 AddLaserEdge(LX, LY);
2201 if (!IS_DF_GRID(element))
2202 AddLaserEdge(LX, LY + YS / 2);
2205 if (!IS_DF_GRID(element))
2206 AddLaserEdge(LX, LY);
2209 YS = 2 * Step[laser.current_angle].y;
2213 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2215 laser.current_angle = get_mirrored_angle(laser.current_angle,
2220 if (!IS_DF_GRID(element))
2221 AddLaserEdge(LX, LY);
2226 if (!IS_DF_GRID(element))
2227 AddLaserEdge(LX + XS / 2, LY);
2230 if (!IS_DF_GRID(element))
2231 AddLaserEdge(LX, LY);
2234 XS = 2 * Step[laser.current_angle].x;
2240 // reflection at the edge of reflecting DF style wall
2241 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2243 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2244 hit_mask == HIT_MASK_TOPRIGHT) ||
2245 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2246 hit_mask == HIT_MASK_TOPLEFT) ||
2247 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2248 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2249 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2250 hit_mask == HIT_MASK_BOTTOMRIGHT))
2253 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2254 ANG_MIRROR_135 : ANG_MIRROR_45);
2256 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2258 AddDamagedField(ELX, ELY);
2259 AddLaserEdge(LX, LY);
2261 laser.current_angle = get_mirrored_angle(laser.current_angle,
2269 AddLaserEdge(LX, LY);
2275 // reflection inside an edge of reflecting DF style wall
2276 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2278 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2279 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2280 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2281 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2282 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2283 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2284 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2285 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2288 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2289 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2290 ANG_MIRROR_135 : ANG_MIRROR_45);
2292 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2295 AddDamagedField(ELX, ELY);
2298 AddLaserEdge(LX - XS, LY - YS);
2299 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2300 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2302 laser.current_angle = get_mirrored_angle(laser.current_angle,
2310 AddLaserEdge(LX, LY);
2316 // check if laser hits DF style wall with an angle of 90°
2317 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2319 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2320 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2321 (IS_VERT_ANGLE(laser.current_angle) &&
2322 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2324 // laser at last step touched nothing or the same side of the wall
2325 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2327 AddDamagedField(ELX, ELY);
2334 last_hit_mask = hit_mask;
2341 if (!HitOnlyAnEdge(hit_mask))
2343 laser.overloaded = TRUE;
2351 boolean HitAbsorbingWalls(int element, int hit_mask)
2353 if (HitOnlyAnEdge(hit_mask))
2357 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2359 AddLaserEdge(LX - XS, LY - YS);
2366 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2368 AddLaserEdge(LX - XS, LY - YS);
2374 if (IS_WALL_WOOD(element) ||
2375 IS_DF_WALL_WOOD(element) ||
2376 IS_GRID_WOOD(element) ||
2377 IS_GRID_WOOD_FIXED(element) ||
2378 IS_GRID_WOOD_AUTO(element) ||
2379 element == EL_FUSE_ON ||
2380 element == EL_BLOCK_WOOD ||
2381 element == EL_GATE_WOOD)
2383 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2388 if (IS_WALL_ICE(element))
2392 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2393 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2395 // check if laser hits wall with an angle of 90°
2396 if (IS_90_ANGLE(laser.current_angle))
2397 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2399 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2403 for (i = 0; i < 4; i++)
2405 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2406 mask = 15 - (8 >> i);
2407 else if (ABS(XS) == 4 &&
2409 (XS > 0) == (i % 2) &&
2410 (YS < 0) == (i / 2))
2411 mask = 3 + (i / 2) * 9;
2412 else if (ABS(YS) == 4 &&
2414 (XS < 0) == (i % 2) &&
2415 (YS > 0) == (i / 2))
2416 mask = 5 + (i % 2) * 5;
2420 laser.wall_mask = mask;
2422 else if (IS_WALL_AMOEBA(element))
2424 int elx = (LX - 2 * XS) / TILEX;
2425 int ely = (LY - 2 * YS) / TILEY;
2426 int element2 = Tile[elx][ely];
2429 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2431 laser.dest_element = EL_EMPTY;
2439 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2440 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2442 if (IS_90_ANGLE(laser.current_angle))
2443 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2445 laser.dest_element = element2 | EL_WALL_AMOEBA;
2447 laser.wall_mask = mask;
2453 static void OpenExit(int x, int y)
2457 if (!MovDelay[x][y]) // next animation frame
2458 MovDelay[x][y] = 4 * delay;
2460 if (MovDelay[x][y]) // wait some time before next frame
2465 phase = MovDelay[x][y] / delay;
2467 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2468 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2470 if (!MovDelay[x][y])
2472 Tile[x][y] = EL_EXIT_OPEN;
2478 static void OpenGrayBall(int x, int y)
2482 if (!MovDelay[x][y]) // next animation frame
2484 if (IS_WALL(Store[x][y]))
2486 DrawWalls_MM(x, y, Store[x][y]);
2488 // copy wall tile to spare bitmap for "melting" animation
2489 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2490 TILEX, TILEY, x * TILEX, y * TILEY);
2492 DrawElement_MM(x, y, EL_GRAY_BALL);
2495 MovDelay[x][y] = 50 * delay;
2498 if (MovDelay[x][y]) // wait some time before next frame
2502 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2506 int dx = RND(26), dy = RND(26);
2508 if (IS_WALL(Store[x][y]))
2510 // copy wall tile from spare bitmap for "melting" animation
2511 bitmap = bitmap_db_field;
2517 int graphic = el2gfx(Store[x][y]);
2519 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2522 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2523 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2525 laser.redraw = TRUE;
2527 MarkTileDirty(x, y);
2530 if (!MovDelay[x][y])
2532 Tile[x][y] = Store[x][y];
2533 Store[x][y] = Store2[x][y] = 0;
2534 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2536 InitField(x, y, FALSE);
2539 ScanLaser_FromLastMirror();
2544 static void OpenEnvelope(int x, int y)
2546 int num_frames = 8; // seven frames plus final empty space
2548 if (!MovDelay[x][y]) // next animation frame
2549 MovDelay[x][y] = num_frames;
2551 if (MovDelay[x][y]) // wait some time before next frame
2553 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2557 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2559 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2560 int frame = num_frames - MovDelay[x][y] - 1;
2562 DrawGraphicAnimation_MM(x, y, graphic, frame);
2564 laser.redraw = TRUE;
2567 if (MovDelay[x][y] == 0)
2569 Tile[x][y] = EL_EMPTY;
2575 ShowEnvelope_MM(nr);
2580 static void MeltIce(int x, int y)
2585 if (!MovDelay[x][y]) // next animation frame
2586 MovDelay[x][y] = frames * delay;
2588 if (MovDelay[x][y]) // wait some time before next frame
2591 int wall_mask = Store2[x][y];
2592 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2595 phase = frames - MovDelay[x][y] / delay - 1;
2597 if (!MovDelay[x][y])
2599 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2600 Store[x][y] = Store2[x][y] = 0;
2602 DrawWalls_MM(x, y, Tile[x][y]);
2604 if (Tile[x][y] == EL_WALL_ICE)
2605 Tile[x][y] = EL_EMPTY;
2607 ScanLaser_FromLastMirror();
2609 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2611 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2613 laser.redraw = TRUE;
2618 static void GrowAmoeba(int x, int y)
2623 if (!MovDelay[x][y]) // next animation frame
2624 MovDelay[x][y] = frames * delay;
2626 if (MovDelay[x][y]) // wait some time before next frame
2629 int wall_mask = Store2[x][y];
2630 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2633 phase = MovDelay[x][y] / delay;
2635 if (!MovDelay[x][y])
2637 Tile[x][y] = real_element;
2638 Store[x][y] = Store2[x][y] = 0;
2640 DrawWalls_MM(x, y, Tile[x][y]);
2641 DrawLaser(0, DL_LASER_ENABLED);
2643 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2645 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2650 static void DrawFieldAnimated_MM(int x, int y)
2654 laser.redraw = TRUE;
2657 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2659 int element = Tile[x][y];
2660 int graphic = el2gfx(element);
2662 if (!getGraphicInfo_NewFrame(x, y, graphic))
2667 laser.redraw = TRUE;
2670 static void DrawFieldTwinkle(int x, int y)
2672 if (MovDelay[x][y] != 0) // wait some time before next frame
2678 if (MovDelay[x][y] != 0)
2680 int graphic = IMG_TWINKLE_WHITE;
2681 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2683 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2686 laser.redraw = TRUE;
2690 static void Explode_MM(int x, int y, int phase, int mode)
2692 int num_phase = 9, delay = 2;
2693 int last_phase = num_phase * delay;
2694 int half_phase = (num_phase / 2) * delay;
2696 laser.redraw = TRUE;
2698 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2700 int center_element = Tile[x][y];
2702 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2704 // put moving element to center field (and let it explode there)
2705 center_element = MovingOrBlocked2Element_MM(x, y);
2706 RemoveMovingField_MM(x, y);
2708 Tile[x][y] = center_element;
2711 Store[x][y] = center_element;
2712 Store2[x][y] = mode;
2714 Tile[x][y] = EL_EXPLODING_OPAQUE;
2716 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2717 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2720 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2722 ExplodePhase[x][y] = 1;
2728 GfxFrame[x][y] = 0; // restart explosion animation
2730 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2732 if (phase == half_phase)
2734 Tile[x][y] = EL_EXPLODING_TRANSP;
2736 if (x == ELX && y == ELY)
2740 if (phase == last_phase)
2742 if (Store[x][y] == EL_BOMB_ACTIVE)
2744 DrawLaser(0, DL_LASER_DISABLED);
2747 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2749 GameOver_MM(GAME_OVER_DELAYED);
2751 laser.overloaded = FALSE;
2753 else if (IS_MCDUFFIN(Store[x][y]))
2755 GameOver_MM(GAME_OVER_BOMB);
2758 Tile[x][y] = EL_EMPTY;
2760 Store[x][y] = Store2[x][y] = 0;
2761 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2763 InitField(x, y, FALSE);
2766 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2768 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2769 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2771 DrawGraphicAnimation_MM(x, y, graphic, frame);
2773 MarkTileDirty(x, y);
2777 static void Bang_MM(int x, int y)
2779 int element = Tile[x][y];
2781 if (IS_PACMAN(element))
2782 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2783 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2784 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2785 else if (element == EL_KEY)
2786 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2788 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2790 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2793 void TurnRound(int x, int y)
2805 { 0, 0 }, { 0, 0 }, { 0, 0 },
2810 int left, right, back;
2814 { MV_DOWN, MV_UP, MV_RIGHT },
2815 { MV_UP, MV_DOWN, MV_LEFT },
2817 { MV_LEFT, MV_RIGHT, MV_DOWN },
2818 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2819 { MV_RIGHT, MV_LEFT, MV_UP }
2822 int element = Tile[x][y];
2823 int old_move_dir = MovDir[x][y];
2824 int right_dir = turn[old_move_dir].right;
2825 int back_dir = turn[old_move_dir].back;
2826 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2827 int right_x = x + right_dx, right_y = y + right_dy;
2829 if (element == EL_PACMAN)
2831 boolean can_turn_right = FALSE;
2833 if (IN_LEV_FIELD(right_x, right_y) &&
2834 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2835 can_turn_right = TRUE;
2838 MovDir[x][y] = right_dir;
2840 MovDir[x][y] = back_dir;
2846 static void StartMoving_MM(int x, int y)
2848 int element = Tile[x][y];
2853 if (CAN_MOVE(element))
2857 if (MovDelay[x][y]) // wait some time before next movement
2865 // now make next step
2867 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2869 if (element == EL_PACMAN &&
2870 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2871 !ObjHit(newx, newy, HIT_POS_CENTER))
2873 Store[newx][newy] = Tile[newx][newy];
2874 Tile[newx][newy] = EL_EMPTY;
2876 DrawField_MM(newx, newy);
2878 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2879 ObjHit(newx, newy, HIT_POS_CENTER))
2881 // object was running against a wall
2888 InitMovingField_MM(x, y, MovDir[x][y]);
2892 ContinueMoving_MM(x, y);
2895 static void ContinueMoving_MM(int x, int y)
2897 int element = Tile[x][y];
2898 int direction = MovDir[x][y];
2899 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2900 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2901 int horiz_move = (dx!=0);
2902 int newx = x + dx, newy = y + dy;
2903 int step = (horiz_move ? dx : dy) * TILEX / 8;
2905 MovPos[x][y] += step;
2907 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2909 Tile[x][y] = EL_EMPTY;
2910 Tile[newx][newy] = element;
2912 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2913 MovDelay[newx][newy] = 0;
2915 if (!CAN_MOVE(element))
2916 MovDir[newx][newy] = 0;
2919 DrawField_MM(newx, newy);
2921 Stop[newx][newy] = TRUE;
2923 if (element == EL_PACMAN)
2925 if (Store[newx][newy] == EL_BOMB)
2926 Bang_MM(newx, newy);
2928 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2929 (LX + 2 * XS) / TILEX == newx &&
2930 (LY + 2 * YS) / TILEY == newy)
2937 else // still moving on
2942 laser.redraw = TRUE;
2945 boolean ClickElement(int x, int y, int button)
2947 static DelayCounter click_delay = { CLICK_DELAY };
2948 static boolean new_button = TRUE;
2949 boolean element_clicked = FALSE;
2954 // initialize static variables
2955 click_delay.count = 0;
2956 click_delay.value = CLICK_DELAY;
2962 // do not rotate objects hit by the laser after the game was solved
2963 if (game_mm.level_solved && Hit[x][y])
2966 if (button == MB_RELEASED)
2969 click_delay.value = CLICK_DELAY;
2971 // release eventually hold auto-rotating mirror
2972 RotateMirror(x, y, MB_RELEASED);
2977 if (!FrameReached(&click_delay) && !new_button)
2980 if (button == MB_MIDDLEBUTTON) // middle button has no function
2983 if (!IN_LEV_FIELD(x, y))
2986 if (Tile[x][y] == EL_EMPTY)
2989 element = Tile[x][y];
2991 if (IS_MIRROR(element) ||
2992 IS_BEAMER(element) ||
2993 IS_POLAR(element) ||
2994 IS_POLAR_CROSS(element) ||
2995 IS_DF_MIRROR(element) ||
2996 IS_DF_MIRROR_AUTO(element))
2998 RotateMirror(x, y, button);
3000 element_clicked = TRUE;
3002 else if (IS_MCDUFFIN(element))
3004 if (!laser.fuse_off)
3006 DrawLaser(0, DL_LASER_DISABLED);
3013 element = get_rotated_element(element, BUTTON_ROTATION(button));
3014 laser.start_angle = get_element_angle(element);
3018 Tile[x][y] = element;
3025 if (!laser.fuse_off)
3028 element_clicked = TRUE;
3030 else if (element == EL_FUSE_ON && laser.fuse_off)
3032 if (x != laser.fuse_x || y != laser.fuse_y)
3035 laser.fuse_off = FALSE;
3036 laser.fuse_x = laser.fuse_y = -1;
3038 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3041 element_clicked = TRUE;
3043 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3045 laser.fuse_off = TRUE;
3048 laser.overloaded = FALSE;
3050 DrawLaser(0, DL_LASER_DISABLED);
3051 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3053 element_clicked = TRUE;
3055 else if (element == EL_LIGHTBALL)
3058 RaiseScoreElement_MM(element);
3059 DrawLaser(0, DL_LASER_ENABLED);
3061 element_clicked = TRUE;
3063 else if (IS_ENVELOPE(element))
3065 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3067 element_clicked = TRUE;
3070 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3073 return element_clicked;
3076 void RotateMirror(int x, int y, int button)
3078 if (button == MB_RELEASED)
3080 // release eventually hold auto-rotating mirror
3087 if (IS_MIRROR(Tile[x][y]) ||
3088 IS_POLAR_CROSS(Tile[x][y]) ||
3089 IS_POLAR(Tile[x][y]) ||
3090 IS_BEAMER(Tile[x][y]) ||
3091 IS_DF_MIRROR(Tile[x][y]) ||
3092 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3093 IS_GRID_WOOD_AUTO(Tile[x][y]))
3095 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3097 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3099 if (button == MB_LEFTBUTTON)
3101 // left mouse button only for manual adjustment, no auto-rotating;
3102 // freeze mirror for until mouse button released
3106 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3108 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3112 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3114 int edge = Hit[x][y];
3120 DrawLaser(edge - 1, DL_LASER_DISABLED);
3124 else if (ObjHit(x, y, HIT_POS_CENTER))
3126 int edge = Hit[x][y];
3130 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3135 DrawLaser(edge - 1, DL_LASER_DISABLED);
3142 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3147 if ((IS_BEAMER(Tile[x][y]) ||
3148 IS_POLAR(Tile[x][y]) ||
3149 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3151 if (IS_BEAMER(Tile[x][y]))
3154 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3155 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3168 DrawLaser(0, DL_LASER_ENABLED);
3172 static void AutoRotateMirrors(void)
3176 if (!FrameReached(&rotate_delay))
3179 for (x = 0; x < lev_fieldx; x++)
3181 for (y = 0; y < lev_fieldy; y++)
3183 int element = Tile[x][y];
3185 // do not rotate objects hit by the laser after the game was solved
3186 if (game_mm.level_solved && Hit[x][y])
3189 if (IS_DF_MIRROR_AUTO(element) ||
3190 IS_GRID_WOOD_AUTO(element) ||
3191 IS_GRID_STEEL_AUTO(element) ||
3192 element == EL_REFRACTOR)
3193 RotateMirror(x, y, MB_RIGHTBUTTON);
3198 boolean ObjHit(int obx, int oby, int bits)
3205 if (bits & HIT_POS_CENTER)
3207 if (CheckLaserPixel(cSX + obx + 15,
3212 if (bits & HIT_POS_EDGE)
3214 for (i = 0; i < 4; i++)
3215 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3216 cSY + oby + 31 * (i / 2)))
3220 if (bits & HIT_POS_BETWEEN)
3222 for (i = 0; i < 4; i++)
3223 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3224 cSY + 4 + oby + 22 * (i / 2)))
3231 void DeletePacMan(int px, int py)
3237 if (game_mm.num_pacman <= 1)
3239 game_mm.num_pacman = 0;
3243 for (i = 0; i < game_mm.num_pacman; i++)
3244 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3247 game_mm.num_pacman--;
3249 for (j = i; j < game_mm.num_pacman; j++)
3251 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3252 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3253 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3257 static void GameActions_MM_Ext(void)
3264 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3267 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3269 element = Tile[x][y];
3271 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3272 StartMoving_MM(x, y);
3273 else if (IS_MOVING(x, y))
3274 ContinueMoving_MM(x, y);
3275 else if (IS_EXPLODING(element))
3276 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3277 else if (element == EL_EXIT_OPENING)
3279 else if (element == EL_GRAY_BALL_OPENING)
3281 else if (IS_ENVELOPE_OPENING(element))
3283 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3285 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3287 else if (IS_MIRROR(element) ||
3288 IS_MIRROR_FIXED(element) ||
3289 element == EL_PRISM)
3290 DrawFieldTwinkle(x, y);
3291 else if (element == EL_GRAY_BALL_ACTIVE ||
3292 element == EL_BOMB_ACTIVE ||
3293 element == EL_MINE_ACTIVE)
3294 DrawFieldAnimated_MM(x, y);
3295 else if (!IS_BLOCKED(x, y))
3296 DrawFieldAnimatedIfNeeded_MM(x, y);
3299 AutoRotateMirrors();
3302 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3304 // redraw after Explode_MM() ...
3306 DrawLaser(0, DL_LASER_ENABLED);
3307 laser.redraw = FALSE;
3312 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3316 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3318 DrawLaser(0, DL_LASER_DISABLED);
3323 // skip all following game actions if game is over
3324 if (game_mm.game_over)
3327 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3331 GameOver_MM(GAME_OVER_NO_ENERGY);
3336 if (FrameReached(&energy_delay))
3338 if (game_mm.energy_left > 0)
3339 game_mm.energy_left--;
3341 // when out of energy, wait another frame to play "out of time" sound
3344 element = laser.dest_element;
3347 if (element != Tile[ELX][ELY])
3349 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3350 element, Tile[ELX][ELY]);
3354 if (!laser.overloaded && laser.overload_value == 0 &&
3355 element != EL_BOMB &&
3356 element != EL_BOMB_ACTIVE &&
3357 element != EL_MINE &&
3358 element != EL_MINE_ACTIVE &&
3359 element != EL_GRAY_BALL &&
3360 element != EL_GRAY_BALL_ACTIVE &&
3361 element != EL_BLOCK_STONE &&
3362 element != EL_BLOCK_WOOD &&
3363 element != EL_FUSE_ON &&
3364 element != EL_FUEL_FULL &&
3365 !IS_WALL_ICE(element) &&
3366 !IS_WALL_AMOEBA(element))
3369 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3371 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3372 (!laser.overloaded && laser.overload_value > 0)) &&
3373 FrameReached(&overload_delay))
3375 if (laser.overloaded)
3376 laser.overload_value++;
3378 laser.overload_value--;
3380 if (game_mm.cheat_no_overload)
3382 laser.overloaded = FALSE;
3383 laser.overload_value = 0;
3386 game_mm.laser_overload_value = laser.overload_value;
3388 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3390 SetLaserColor(0xFF);
3392 DrawLaser(0, DL_LASER_ENABLED);
3395 if (!laser.overloaded)
3396 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3397 else if (setup.sound_loops)
3398 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3400 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3402 if (laser.overload_value == MAX_LASER_OVERLOAD)
3404 UpdateAndDisplayGameControlValues();
3408 GameOver_MM(GAME_OVER_OVERLOADED);
3419 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3421 if (game_mm.cheat_no_explosion)
3426 laser.dest_element = EL_EXPLODING_OPAQUE;
3431 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3433 laser.fuse_off = TRUE;
3437 DrawLaser(0, DL_LASER_DISABLED);
3438 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3441 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3443 if (!Store2[ELX][ELY]) // check if content element not yet determined
3445 int last_anim_random_frame = gfx.anim_random_frame;
3448 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3449 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3451 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3452 native_mm_level.ball_choice_mode, 0,
3453 game_mm.ball_choice_pos);
3455 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3456 gfx.anim_random_frame = last_anim_random_frame;
3458 game_mm.ball_choice_pos++;
3460 int new_element = native_mm_level.ball_content[element_pos];
3461 int new_element_unmapped = unmap_element(new_element);
3463 if (IS_WALL(new_element_unmapped))
3465 // always use completely filled wall element
3466 new_element = new_element_unmapped | 0x000f;
3468 else if (native_mm_level.rotate_ball_content &&
3469 get_num_elements(new_element) > 1)
3471 // randomly rotate newly created game element
3472 new_element = get_rotated_element(new_element, RND(16));
3475 Store[ELX][ELY] = new_element;
3476 Store2[ELX][ELY] = TRUE;
3479 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3481 laser.dest_element_last = Tile[ELX][ELY];
3486 if (IS_WALL_ICE(element) && CT > 50)
3488 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3490 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3491 Store[ELX][ELY] = EL_WALL_ICE;
3492 Store2[ELX][ELY] = laser.wall_mask;
3494 laser.dest_element = Tile[ELX][ELY];
3499 if (IS_WALL_AMOEBA(element) && CT > 60)
3502 int element2 = Tile[ELX][ELY];
3504 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3507 for (i = laser.num_damages - 1; i >= 0; i--)
3508 if (laser.damage[i].is_mirror)
3511 r = laser.num_edges;
3512 d = laser.num_damages;
3519 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3522 DrawLaser(0, DL_LASER_ENABLED);
3525 x = laser.damage[k1].x;
3526 y = laser.damage[k1].y;
3531 for (i = 0; i < 4; i++)
3533 if (laser.wall_mask & (1 << i))
3535 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3536 cSY + ELY * TILEY + 31 * (i / 2)))
3539 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3540 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3547 for (i = 0; i < 4; i++)
3549 if (laser.wall_mask & (1 << i))
3551 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3552 cSY + ELY * TILEY + 31 * (i / 2)))
3559 if (laser.num_beamers > 0 ||
3560 k1 < 1 || k2 < 4 || k3 < 4 ||
3561 CheckLaserPixel(cSX + ELX * TILEX + 14,
3562 cSY + ELY * TILEY + 14))
3564 laser.num_edges = r;
3565 laser.num_damages = d;
3567 DrawLaser(0, DL_LASER_DISABLED);
3570 Tile[ELX][ELY] = element | laser.wall_mask;
3572 int x = ELX, y = ELY;
3573 int wall_mask = laser.wall_mask;
3576 DrawLaser(0, DL_LASER_ENABLED);
3578 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3580 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3581 Store[x][y] = EL_WALL_AMOEBA;
3582 Store2[x][y] = wall_mask;
3587 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3588 laser.stops_inside_element && CT > native_mm_level.time_block)
3593 if (ABS(XS) > ABS(YS))
3600 for (i = 0; i < 4; i++)
3607 x = ELX + Step[k * 4].x;
3608 y = ELY + Step[k * 4].y;
3610 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3613 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3621 laser.overloaded = (element == EL_BLOCK_STONE);
3626 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3629 Tile[x][y] = element;
3631 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3634 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3636 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3637 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3645 if (element == EL_FUEL_FULL && CT > 10)
3647 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3648 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3650 for (i = start; i <= num_init_game_frames; i++)
3652 if (i == num_init_game_frames)
3653 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3654 else if (setup.sound_loops)
3655 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3657 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3659 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3661 UpdateAndDisplayGameControlValues();
3666 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3668 DrawField_MM(ELX, ELY);
3670 DrawLaser(0, DL_LASER_ENABLED);
3676 void GameActions_MM(struct MouseActionInfo action)
3678 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3679 boolean button_released = (action.button == MB_RELEASED);
3681 GameActions_MM_Ext();
3683 CheckSingleStepMode_MM(element_clicked, button_released);
3686 void MovePacMen(void)
3688 int mx, my, ox, oy, nx, ny;
3692 if (++pacman_nr >= game_mm.num_pacman)
3695 game_mm.pacman[pacman_nr].dir--;
3697 for (l = 1; l < 5; l++)
3699 game_mm.pacman[pacman_nr].dir++;
3701 if (game_mm.pacman[pacman_nr].dir > 4)
3702 game_mm.pacman[pacman_nr].dir = 1;
3704 if (game_mm.pacman[pacman_nr].dir % 2)
3707 my = game_mm.pacman[pacman_nr].dir - 2;
3712 mx = 3 - game_mm.pacman[pacman_nr].dir;
3715 ox = game_mm.pacman[pacman_nr].x;
3716 oy = game_mm.pacman[pacman_nr].y;
3719 element = Tile[nx][ny];
3721 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3724 if (!IS_EATABLE4PACMAN(element))
3727 if (ObjHit(nx, ny, HIT_POS_CENTER))
3730 Tile[ox][oy] = EL_EMPTY;
3732 EL_PACMAN_RIGHT - 1 +
3733 (game_mm.pacman[pacman_nr].dir - 1 +
3734 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3736 game_mm.pacman[pacman_nr].x = nx;
3737 game_mm.pacman[pacman_nr].y = ny;
3739 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3741 if (element != EL_EMPTY)
3743 int graphic = el2gfx(Tile[nx][ny]);
3748 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3751 ox = cSX + ox * TILEX;
3752 oy = cSY + oy * TILEY;
3754 for (i = 1; i < 33; i += 2)
3755 BlitBitmap(bitmap, window,
3756 src_x, src_y, TILEX, TILEY,
3757 ox + i * mx, oy + i * my);
3758 Ct = Ct + FrameCounter - CT;
3761 DrawField_MM(nx, ny);
3764 if (!laser.fuse_off)
3766 DrawLaser(0, DL_LASER_ENABLED);
3768 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3770 AddDamagedField(nx, ny);
3772 laser.damage[laser.num_damages - 1].edge = 0;
3776 if (element == EL_BOMB)
3777 DeletePacMan(nx, ny);
3779 if (IS_WALL_AMOEBA(element) &&
3780 (LX + 2 * XS) / TILEX == nx &&
3781 (LY + 2 * YS) / TILEY == ny)
3791 static void InitMovingField_MM(int x, int y, int direction)
3793 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3794 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3796 MovDir[x][y] = direction;
3797 MovDir[newx][newy] = direction;
3799 if (Tile[newx][newy] == EL_EMPTY)
3800 Tile[newx][newy] = EL_BLOCKED;
3803 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3805 int direction = MovDir[x][y];
3806 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3807 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3813 static void Blocked2Moving_MM(int x, int y,
3814 int *comes_from_x, int *comes_from_y)
3816 int oldx = x, oldy = y;
3817 int direction = MovDir[x][y];
3819 if (direction == MV_LEFT)
3821 else if (direction == MV_RIGHT)
3823 else if (direction == MV_UP)
3825 else if (direction == MV_DOWN)
3828 *comes_from_x = oldx;
3829 *comes_from_y = oldy;
3832 static int MovingOrBlocked2Element_MM(int x, int y)
3834 int element = Tile[x][y];
3836 if (element == EL_BLOCKED)
3840 Blocked2Moving_MM(x, y, &oldx, &oldy);
3842 return Tile[oldx][oldy];
3848 static void RemoveMovingField_MM(int x, int y)
3850 int oldx = x, oldy = y, newx = x, newy = y;
3852 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3855 if (IS_MOVING(x, y))
3857 Moving2Blocked_MM(x, y, &newx, &newy);
3858 if (Tile[newx][newy] != EL_BLOCKED)
3861 else if (Tile[x][y] == EL_BLOCKED)
3863 Blocked2Moving_MM(x, y, &oldx, &oldy);
3864 if (!IS_MOVING(oldx, oldy))
3868 Tile[oldx][oldy] = EL_EMPTY;
3869 Tile[newx][newy] = EL_EMPTY;
3870 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3871 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3873 DrawLevelField_MM(oldx, oldy);
3874 DrawLevelField_MM(newx, newy);
3877 static void RaiseScore_MM(int value)
3879 game_mm.score += value;
3882 void RaiseScoreElement_MM(int element)
3887 case EL_PACMAN_RIGHT:
3889 case EL_PACMAN_LEFT:
3890 case EL_PACMAN_DOWN:
3891 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3895 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3900 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3904 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3913 // ----------------------------------------------------------------------------
3914 // Mirror Magic game engine snapshot handling functions
3915 // ----------------------------------------------------------------------------
3917 void SaveEngineSnapshotValues_MM(void)
3921 engine_snapshot_mm.game_mm = game_mm;
3922 engine_snapshot_mm.laser = laser;
3924 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3926 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3928 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3929 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3930 engine_snapshot_mm.Box[x][y] = Box[x][y];
3931 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3935 engine_snapshot_mm.LX = LX;
3936 engine_snapshot_mm.LY = LY;
3937 engine_snapshot_mm.XS = XS;
3938 engine_snapshot_mm.YS = YS;
3939 engine_snapshot_mm.ELX = ELX;
3940 engine_snapshot_mm.ELY = ELY;
3941 engine_snapshot_mm.CT = CT;
3942 engine_snapshot_mm.Ct = Ct;
3944 engine_snapshot_mm.last_LX = last_LX;
3945 engine_snapshot_mm.last_LY = last_LY;
3946 engine_snapshot_mm.last_hit_mask = last_hit_mask;
3947 engine_snapshot_mm.hold_x = hold_x;
3948 engine_snapshot_mm.hold_y = hold_y;
3949 engine_snapshot_mm.pacman_nr = pacman_nr;
3951 engine_snapshot_mm.rotate_delay = rotate_delay;
3952 engine_snapshot_mm.pacman_delay = pacman_delay;
3953 engine_snapshot_mm.energy_delay = energy_delay;
3954 engine_snapshot_mm.overload_delay = overload_delay;
3957 void LoadEngineSnapshotValues_MM(void)
3961 // stored engine snapshot buffers already restored at this point
3963 game_mm = engine_snapshot_mm.game_mm;
3964 laser = engine_snapshot_mm.laser;
3966 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3968 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3970 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
3971 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
3972 Box[x][y] = engine_snapshot_mm.Box[x][y];
3973 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
3977 LX = engine_snapshot_mm.LX;
3978 LY = engine_snapshot_mm.LY;
3979 XS = engine_snapshot_mm.XS;
3980 YS = engine_snapshot_mm.YS;
3981 ELX = engine_snapshot_mm.ELX;
3982 ELY = engine_snapshot_mm.ELY;
3983 CT = engine_snapshot_mm.CT;
3984 Ct = engine_snapshot_mm.Ct;
3986 last_LX = engine_snapshot_mm.last_LX;
3987 last_LY = engine_snapshot_mm.last_LY;
3988 last_hit_mask = engine_snapshot_mm.last_hit_mask;
3989 hold_x = engine_snapshot_mm.hold_x;
3990 hold_y = engine_snapshot_mm.hold_y;
3991 pacman_nr = engine_snapshot_mm.pacman_nr;
3993 rotate_delay = engine_snapshot_mm.rotate_delay;
3994 pacman_delay = engine_snapshot_mm.pacman_delay;
3995 energy_delay = engine_snapshot_mm.energy_delay;
3996 overload_delay = engine_snapshot_mm.overload_delay;
3998 RedrawPlayfield_MM();
4001 static int getAngleFromTouchDelta(int dx, int dy, int base)
4003 double pi = 3.141592653;
4004 double rad = atan2((double)-dy, (double)dx);
4005 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4006 double deg = rad2 * 180.0 / pi;
4008 return (int)(deg * base / 360.0 + 0.5) % base;
4011 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4013 // calculate start (source) position to be at the middle of the tile
4014 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4015 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4016 int dx = dst_mx - src_mx;
4017 int dy = dst_my - src_my;
4026 if (!IN_LEV_FIELD(x, y))
4029 element = Tile[x][y];
4031 if (!IS_MCDUFFIN(element) &&
4032 !IS_MIRROR(element) &&
4033 !IS_BEAMER(element) &&
4034 !IS_POLAR(element) &&
4035 !IS_POLAR_CROSS(element) &&
4036 !IS_DF_MIRROR(element))
4039 angle_old = get_element_angle(element);
4041 if (IS_MCDUFFIN(element))
4043 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4044 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4045 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4046 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4049 else if (IS_MIRROR(element) ||
4050 IS_DF_MIRROR(element))
4052 for (i = 0; i < laser.num_damages; i++)
4054 if (laser.damage[i].x == x &&
4055 laser.damage[i].y == y &&
4056 ObjHit(x, y, HIT_POS_CENTER))
4058 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4059 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4066 if (angle_new == -1)
4068 if (IS_MIRROR(element) ||
4069 IS_DF_MIRROR(element) ||
4073 if (IS_POLAR_CROSS(element))
4076 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4079 button = (angle_new == angle_old ? 0 :
4080 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4081 MB_LEFTBUTTON : MB_RIGHTBUTTON);