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)
468 int element = Tile[x][y];
473 Tile[x][y] = EL_EMPTY;
478 if (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))
538 laser.start_edge.x = x;
539 laser.start_edge.y = y;
540 laser.start_angle = get_element_angle(element);
542 if (IS_MCDUFFIN(element))
544 game_mm.laser_red = native_mm_level.mm_laser_red;
545 game_mm.laser_green = native_mm_level.mm_laser_green;
546 game_mm.laser_blue = native_mm_level.mm_laser_blue;
550 game_mm.laser_red = native_mm_level.df_laser_red;
551 game_mm.laser_green = native_mm_level.df_laser_green;
552 game_mm.laser_blue = native_mm_level.df_laser_blue;
560 static void InitCycleElements_RotateSingleStep(void)
564 if (game_mm.num_cycle == 0) // no elements to cycle
567 for (i = 0; i < game_mm.num_cycle; i++)
569 int x = game_mm.cycle[i].x;
570 int y = game_mm.cycle[i].y;
571 int step = SIGN(game_mm.cycle[i].steps);
572 int last_element = Tile[x][y];
573 int next_element = get_rotated_element(last_element, step);
575 if (!game_mm.cycle[i].steps)
578 Tile[x][y] = next_element;
580 game_mm.cycle[i].steps -= step;
584 static void InitLaser(void)
586 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
587 int step = (IS_LASER(start_element) ? 4 : 0);
589 LX = laser.start_edge.x * TILEX;
590 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
593 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
595 LY = laser.start_edge.y * TILEY;
596 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
597 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
601 XS = 2 * Step[laser.start_angle].x;
602 YS = 2 * Step[laser.start_angle].y;
604 laser.current_angle = laser.start_angle;
606 laser.num_damages = 0;
608 laser.num_beamers = 0;
609 laser.beamer_edge[0] = 0;
611 laser.dest_element = EL_EMPTY;
614 AddLaserEdge(LX, LY); // set laser starting edge
619 void InitGameEngine_MM(void)
625 // initialize laser bitmap to current playfield (screen) size
626 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
627 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
631 // set global game control values
632 game_mm.num_cycle = 0;
633 game_mm.num_pacman = 0;
636 game_mm.energy_left = 0; // later set to "native_mm_level.time"
637 game_mm.kettles_still_needed =
638 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
639 game_mm.lights_still_needed = 0;
640 game_mm.num_keys = 0;
641 game_mm.ball_choice_pos = 0;
643 game_mm.laser_red = FALSE;
644 game_mm.laser_green = FALSE;
645 game_mm.laser_blue = TRUE;
647 game_mm.level_solved = FALSE;
648 game_mm.game_over = FALSE;
649 game_mm.game_over_cause = 0;
651 game_mm.laser_overload_value = 0;
652 game_mm.laser_enabled = FALSE;
654 // set global laser control values (must be set before "InitLaser()")
655 laser.start_edge.x = 0;
656 laser.start_edge.y = 0;
657 laser.start_angle = 0;
659 for (i = 0; i < MAX_NUM_BEAMERS; i++)
660 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
662 laser.overloaded = FALSE;
663 laser.overload_value = 0;
664 laser.fuse_off = FALSE;
665 laser.fuse_x = laser.fuse_y = -1;
667 laser.dest_element = EL_EMPTY;
668 laser.dest_element_last = EL_EMPTY;
669 laser.dest_element_last_x = -1;
670 laser.dest_element_last_y = -1;
684 rotate_delay.count = 0;
685 pacman_delay.count = 0;
686 energy_delay.count = 0;
687 overload_delay.count = 0;
689 ClickElement(-1, -1, -1);
691 for (x = 0; x < lev_fieldx; x++)
693 for (y = 0; y < lev_fieldy; y++)
695 Tile[x][y] = Ur[x][y];
696 Hit[x][y] = Box[x][y] = 0;
698 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
699 Store[x][y] = Store2[x][y] = 0;
709 void InitGameActions_MM(void)
711 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
712 int cycle_steps_done = 0;
717 for (i = 0; i <= num_init_game_frames; i++)
719 if (i == num_init_game_frames)
720 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
721 else if (setup.sound_loops)
722 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
724 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
726 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
728 UpdateAndDisplayGameControlValues();
730 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
732 InitCycleElements_RotateSingleStep();
737 AdvanceFrameCounter();
747 if (setup.quick_doors)
754 if (game_mm.kettles_still_needed == 0)
757 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
758 SetTileCursorActive(TRUE);
760 // restart all delay counters after initially cycling game elements
761 ResetFrameCounter(&rotate_delay);
762 ResetFrameCounter(&pacman_delay);
763 ResetFrameCounter(&energy_delay);
764 ResetFrameCounter(&overload_delay);
767 static void FadeOutLaser(void)
771 for (i = 15; i >= 0; i--)
773 SetLaserColor(0x11 * i);
775 DrawLaser(0, DL_LASER_ENABLED);
778 Delay_WithScreenUpdates(50);
781 DrawLaser(0, DL_LASER_DISABLED);
783 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
786 static void GameOver_MM(int game_over_cause)
788 // do not handle game over if request dialog is already active
789 if (game.request_active)
792 game_mm.game_over = TRUE;
793 game_mm.game_over_cause = game_over_cause;
795 if (setup.ask_on_game_over)
796 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
797 "Bomb killed Mc Duffin! Play it again?" :
798 game_over_cause == GAME_OVER_NO_ENERGY ?
799 "Out of magic energy! Play it again?" :
800 game_over_cause == GAME_OVER_OVERLOADED ?
801 "Magic spell hit Mc Duffin! Play it again?" :
804 SetTileCursorActive(FALSE);
807 void AddLaserEdge(int lx, int ly)
812 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
814 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
819 laser.edge[laser.num_edges].x = cSX2 + lx;
820 laser.edge[laser.num_edges].y = cSY2 + ly;
826 void AddDamagedField(int ex, int ey)
828 laser.damage[laser.num_damages].is_mirror = FALSE;
829 laser.damage[laser.num_damages].angle = laser.current_angle;
830 laser.damage[laser.num_damages].edge = laser.num_edges;
831 laser.damage[laser.num_damages].x = ex;
832 laser.damage[laser.num_damages].y = ey;
836 static boolean StepBehind(void)
842 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
843 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
845 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
851 static int getMaskFromElement(int element)
853 if (IS_GRID(element))
854 return MM_MASK_GRID_1 + get_element_phase(element);
855 else if (IS_MCDUFFIN(element))
856 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
857 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
858 return MM_MASK_RECTANGLE;
860 return MM_MASK_CIRCLE;
863 static int ScanPixel(void)
868 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
869 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
872 // follow laser beam until it hits something (at least the screen border)
873 while (hit_mask == HIT_MASK_NO_HIT)
879 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
880 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
882 Debug("game:mm:ScanPixel", "touched screen border!");
888 for (i = 0; i < 4; i++)
890 int px = LX + (i % 2) * 2;
891 int py = LY + (i / 2) * 2;
894 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
895 int ly = (py + TILEY) / TILEY - 1; // negative values!
898 if (IN_LEV_FIELD(lx, ly))
900 int element = Tile[lx][ly];
902 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
906 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
908 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
910 pixel = ((element & (1 << pos)) ? 1 : 0);
914 int pos = getMaskFromElement(element);
916 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
921 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
922 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
925 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
926 hit_mask |= (1 << i);
929 if (hit_mask == HIT_MASK_NO_HIT)
931 // hit nothing -- go on with another step
940 static void DeactivateLaserTargetElement(void)
942 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
943 laser.dest_element_last == EL_MINE_ACTIVE ||
944 laser.dest_element_last == EL_GRAY_BALL_OPENING)
946 int x = laser.dest_element_last_x;
947 int y = laser.dest_element_last_y;
948 int element = laser.dest_element_last;
950 if (Tile[x][y] == element)
951 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
952 element == EL_MINE_ACTIVE ? EL_MINE : EL_BALL_GRAY);
954 if (Tile[x][y] == EL_BALL_GRAY)
957 laser.dest_element_last = EL_EMPTY;
958 laser.dest_element_last_x = -1;
959 laser.dest_element_last_y = -1;
965 int element = EL_EMPTY;
966 int last_element = EL_EMPTY;
967 int end = 0, rf = laser.num_edges;
969 // do not scan laser again after the game was lost for whatever reason
970 if (game_mm.game_over)
973 // do not scan laser if fuse is off
977 DeactivateLaserTargetElement();
979 laser.overloaded = FALSE;
980 laser.stops_inside_element = FALSE;
982 DrawLaser(0, DL_LASER_ENABLED);
985 Debug("game:mm:ScanLaser",
986 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
994 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
997 laser.overloaded = TRUE;
1002 hit_mask = ScanPixel();
1005 Debug("game:mm:ScanLaser",
1006 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1010 // hit something -- check out what it was
1011 ELX = (LX + XS) / TILEX;
1012 ELY = (LY + YS) / TILEY;
1015 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1016 hit_mask, LX, LY, ELX, ELY);
1019 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1022 laser.dest_element = element;
1027 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1029 /* we have hit the top-right and bottom-left element --
1030 choose the bottom-left one */
1031 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1032 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1033 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1034 ELX = (LX - 2) / TILEX;
1035 ELY = (LY + 2) / TILEY;
1038 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1040 /* we have hit the top-left and bottom-right element --
1041 choose the top-left one */
1042 // !!! SEE ABOVE !!!
1043 ELX = (LX - 2) / TILEX;
1044 ELY = (LY - 2) / TILEY;
1048 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1049 hit_mask, LX, LY, ELX, ELY);
1052 last_element = element;
1054 element = Tile[ELX][ELY];
1055 laser.dest_element = element;
1058 Debug("game:mm:ScanLaser",
1059 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1062 LX % TILEX, LY % TILEY,
1067 if (!IN_LEV_FIELD(ELX, ELY))
1068 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1072 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1073 if (element == EL_EMPTY &&
1074 IS_GRID_STEEL(last_element) &&
1075 laser.current_angle % 4) // angle is not 90°
1076 element = last_element;
1078 if (element == EL_EMPTY)
1080 if (!HitOnlyAnEdge(hit_mask))
1083 else if (element == EL_FUSE_ON)
1085 if (HitPolarizer(element, hit_mask))
1088 else if (IS_GRID(element) || IS_DF_GRID(element))
1090 if (HitPolarizer(element, hit_mask))
1093 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1094 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1096 if (HitBlock(element, hit_mask))
1103 else if (IS_MCDUFFIN(element))
1105 if (HitLaserSource(element, hit_mask))
1108 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1109 IS_RECEIVER(element))
1111 if (HitLaserDestination(element, hit_mask))
1114 else if (IS_WALL(element))
1116 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1118 if (HitReflectingWalls(element, hit_mask))
1123 if (HitAbsorbingWalls(element, hit_mask))
1129 if (HitElement(element, hit_mask))
1134 DrawLaser(rf - 1, DL_LASER_ENABLED);
1135 rf = laser.num_edges;
1137 if (!IS_DF_WALL_STEEL(element))
1139 // only used for scanning DF steel walls; reset for all other elements
1147 if (laser.dest_element != Tile[ELX][ELY])
1149 Debug("game:mm:ScanLaser",
1150 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1151 laser.dest_element, Tile[ELX][ELY]);
1155 if (!end && !laser.stops_inside_element && !StepBehind())
1158 Debug("game:mm:ScanLaser", "Go one step back");
1164 AddLaserEdge(LX, LY);
1168 DrawLaser(rf - 1, DL_LASER_ENABLED);
1170 Ct = CT = FrameCounter;
1173 if (!IN_LEV_FIELD(ELX, ELY))
1174 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1178 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1184 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1185 start_edge, num_edges, mode);
1190 Warn("DrawLaserExt: start_edge < 0");
1197 Warn("DrawLaserExt: num_edges < 0");
1203 if (mode == DL_LASER_DISABLED)
1205 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1209 // now draw the laser to the backbuffer and (if enabled) to the screen
1210 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1212 redraw_mask |= REDRAW_FIELD;
1214 if (mode == DL_LASER_ENABLED)
1217 // after the laser was deleted, the "damaged" graphics must be restored
1218 if (laser.num_damages)
1220 int damage_start = 0;
1223 // determine the starting edge, from which graphics need to be restored
1226 for (i = 0; i < laser.num_damages; i++)
1228 if (laser.damage[i].edge == start_edge + 1)
1237 // restore graphics from this starting edge to the end of damage list
1238 for (i = damage_start; i < laser.num_damages; i++)
1240 int lx = laser.damage[i].x;
1241 int ly = laser.damage[i].y;
1242 int element = Tile[lx][ly];
1244 if (Hit[lx][ly] == laser.damage[i].edge)
1245 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1248 if (Box[lx][ly] == laser.damage[i].edge)
1251 if (IS_DRAWABLE(element))
1252 DrawField_MM(lx, ly);
1255 elx = laser.damage[damage_start].x;
1256 ely = laser.damage[damage_start].y;
1257 element = Tile[elx][ely];
1260 if (IS_BEAMER(element))
1264 for (i = 0; i < laser.num_beamers; i++)
1265 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1267 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1268 mode, elx, ely, Hit[elx][ely], start_edge);
1269 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1270 get_element_angle(element), laser.damage[damage_start].angle);
1274 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1275 laser.num_beamers > 0 &&
1276 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1278 // element is outgoing beamer
1279 laser.num_damages = damage_start + 1;
1281 if (IS_BEAMER(element))
1282 laser.current_angle = get_element_angle(element);
1286 // element is incoming beamer or other element
1287 laser.num_damages = damage_start;
1288 laser.current_angle = laser.damage[laser.num_damages].angle;
1293 // no damages but McDuffin himself (who needs to be redrawn anyway)
1295 elx = laser.start_edge.x;
1296 ely = laser.start_edge.y;
1297 element = Tile[elx][ely];
1300 laser.num_edges = start_edge + 1;
1301 if (start_edge == 0)
1302 laser.current_angle = laser.start_angle;
1304 LX = laser.edge[start_edge].x - cSX2;
1305 LY = laser.edge[start_edge].y - cSY2;
1306 XS = 2 * Step[laser.current_angle].x;
1307 YS = 2 * Step[laser.current_angle].y;
1310 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1316 if (IS_BEAMER(element) ||
1317 IS_FIBRE_OPTIC(element) ||
1318 IS_PACMAN(element) ||
1319 IS_POLAR(element) ||
1320 IS_POLAR_CROSS(element) ||
1321 element == EL_FUSE_ON)
1326 Debug("game:mm:DrawLaserExt", "element == %d", element);
1329 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1330 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1334 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1335 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1336 (laser.num_beamers == 0 ||
1337 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1339 // element is incoming beamer or other element
1340 step_size = -step_size;
1345 if (IS_BEAMER(element))
1346 Debug("game:mm:DrawLaserExt",
1347 "start_edge == %d, laser.beamer_edge == %d",
1348 start_edge, laser.beamer_edge);
1351 LX += step_size * XS;
1352 LY += step_size * YS;
1354 else if (element != EL_EMPTY)
1363 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1368 void DrawLaser(int start_edge, int mode)
1370 // do not draw laser if fuse is off
1371 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1374 if (mode == DL_LASER_DISABLED)
1375 DeactivateLaserTargetElement();
1377 if (laser.num_edges - start_edge < 0)
1379 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1384 // check if laser is interrupted by beamer element
1385 if (laser.num_beamers > 0 &&
1386 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1388 if (mode == DL_LASER_ENABLED)
1391 int tmp_start_edge = start_edge;
1393 // draw laser segments forward from the start to the last beamer
1394 for (i = 0; i < laser.num_beamers; i++)
1396 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1398 if (tmp_num_edges <= 0)
1402 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1403 i, laser.beamer_edge[i], tmp_start_edge);
1406 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1408 tmp_start_edge = laser.beamer_edge[i];
1411 // draw last segment from last beamer to the end
1412 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1418 int last_num_edges = laser.num_edges;
1419 int num_beamers = laser.num_beamers;
1421 // delete laser segments backward from the end to the first beamer
1422 for (i = num_beamers - 1; i >= 0; i--)
1424 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1426 if (laser.beamer_edge[i] - start_edge <= 0)
1429 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1431 last_num_edges = laser.beamer_edge[i];
1432 laser.num_beamers--;
1436 if (last_num_edges - start_edge <= 0)
1437 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1438 last_num_edges, start_edge);
1441 // special case when rotating first beamer: delete laser edge on beamer
1442 // (but do not start scanning on previous edge to prevent mirror sound)
1443 if (last_num_edges - start_edge == 1 && start_edge > 0)
1444 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1446 // delete first segment from start to the first beamer
1447 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1452 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1455 game_mm.laser_enabled = mode;
1458 void DrawLaser_MM(void)
1460 DrawLaser(0, game_mm.laser_enabled);
1463 boolean HitElement(int element, int hit_mask)
1465 if (HitOnlyAnEdge(hit_mask))
1468 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1469 element = MovingOrBlocked2Element_MM(ELX, ELY);
1472 Debug("game:mm:HitElement", "(1): element == %d", element);
1476 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1477 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1480 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1484 AddDamagedField(ELX, ELY);
1486 // this is more precise: check if laser would go through the center
1487 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1489 // prevent cutting through laser emitter with laser beam
1490 if (IS_LASER(element))
1493 // skip the whole element before continuing the scan
1499 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1501 if (LX/TILEX > ELX || LY/TILEY > ELY)
1503 /* skipping scan positions to the right and down skips one scan
1504 position too much, because this is only the top left scan position
1505 of totally four scan positions (plus one to the right, one to the
1506 bottom and one to the bottom right) */
1516 Debug("game:mm:HitElement", "(2): element == %d", element);
1519 if (LX + 5 * XS < 0 ||
1529 Debug("game:mm:HitElement", "(3): element == %d", element);
1532 if (IS_POLAR(element) &&
1533 ((element - EL_POLAR_START) % 2 ||
1534 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1536 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1538 laser.num_damages--;
1543 if (IS_POLAR_CROSS(element) &&
1544 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1546 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1548 laser.num_damages--;
1553 if (!IS_BEAMER(element) &&
1554 !IS_FIBRE_OPTIC(element) &&
1555 !IS_GRID_WOOD(element) &&
1556 element != EL_FUEL_EMPTY)
1559 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1560 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1562 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1565 LX = ELX * TILEX + 14;
1566 LY = ELY * TILEY + 14;
1568 AddLaserEdge(LX, LY);
1571 if (IS_MIRROR(element) ||
1572 IS_MIRROR_FIXED(element) ||
1573 IS_POLAR(element) ||
1574 IS_POLAR_CROSS(element) ||
1575 IS_DF_MIRROR(element) ||
1576 IS_DF_MIRROR_AUTO(element) ||
1577 element == EL_PRISM ||
1578 element == EL_REFRACTOR)
1580 int current_angle = laser.current_angle;
1583 laser.num_damages--;
1585 AddDamagedField(ELX, ELY);
1587 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1590 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1592 if (IS_MIRROR(element) ||
1593 IS_MIRROR_FIXED(element) ||
1594 IS_DF_MIRROR(element) ||
1595 IS_DF_MIRROR_AUTO(element))
1596 laser.current_angle = get_mirrored_angle(laser.current_angle,
1597 get_element_angle(element));
1599 if (element == EL_PRISM || element == EL_REFRACTOR)
1600 laser.current_angle = RND(16);
1602 XS = 2 * Step[laser.current_angle].x;
1603 YS = 2 * Step[laser.current_angle].y;
1605 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1610 LX += step_size * XS;
1611 LY += step_size * YS;
1613 // draw sparkles on mirror
1614 if ((IS_MIRROR(element) ||
1615 IS_MIRROR_FIXED(element) ||
1616 element == EL_PRISM) &&
1617 current_angle != laser.current_angle)
1619 MovDelay[ELX][ELY] = 11; // start animation
1622 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1623 current_angle != laser.current_angle)
1624 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1627 (get_opposite_angle(laser.current_angle) ==
1628 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1630 return (laser.overloaded ? TRUE : FALSE);
1633 if (element == EL_FUEL_FULL)
1635 laser.stops_inside_element = TRUE;
1640 if (element == EL_BOMB || element == EL_MINE)
1642 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1644 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1646 laser.dest_element_last = Tile[ELX][ELY];
1647 laser.dest_element_last_x = ELX;
1648 laser.dest_element_last_y = ELY;
1650 if (element == EL_MINE)
1651 laser.overloaded = TRUE;
1654 if (element == EL_KETTLE ||
1655 element == EL_CELL ||
1656 element == EL_KEY ||
1657 element == EL_LIGHTBALL ||
1658 element == EL_PACMAN ||
1661 if (!IS_PACMAN(element))
1664 if (element == EL_PACMAN)
1667 if (element == EL_KETTLE || element == EL_CELL)
1669 if (game_mm.kettles_still_needed > 0)
1670 game_mm.kettles_still_needed--;
1672 game.snapshot.collected_item = TRUE;
1674 if (game_mm.kettles_still_needed == 0)
1678 DrawLaser(0, DL_LASER_ENABLED);
1681 else if (element == EL_KEY)
1685 else if (IS_PACMAN(element))
1687 DeletePacMan(ELX, ELY);
1690 RaiseScoreElement_MM(element);
1695 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1697 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1699 DrawLaser(0, DL_LASER_ENABLED);
1701 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1703 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1704 game_mm.lights_still_needed--;
1708 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1709 game_mm.lights_still_needed++;
1712 DrawField_MM(ELX, ELY);
1713 DrawLaser(0, DL_LASER_ENABLED);
1718 laser.stops_inside_element = TRUE;
1724 Debug("game:mm:HitElement", "(4): element == %d", element);
1727 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1728 laser.num_beamers < MAX_NUM_BEAMERS &&
1729 laser.beamer[BEAMER_NR(element)][1].num)
1731 int beamer_angle = get_element_angle(element);
1732 int beamer_nr = BEAMER_NR(element);
1736 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1739 laser.num_damages--;
1741 if (IS_FIBRE_OPTIC(element) ||
1742 laser.current_angle == get_opposite_angle(beamer_angle))
1746 LX = ELX * TILEX + 14;
1747 LY = ELY * TILEY + 14;
1749 AddLaserEdge(LX, LY);
1750 AddDamagedField(ELX, ELY);
1752 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1755 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1757 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1758 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1759 ELX = laser.beamer[beamer_nr][pos].x;
1760 ELY = laser.beamer[beamer_nr][pos].y;
1761 LX = ELX * TILEX + 14;
1762 LY = ELY * TILEY + 14;
1764 if (IS_BEAMER(element))
1766 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1767 XS = 2 * Step[laser.current_angle].x;
1768 YS = 2 * Step[laser.current_angle].y;
1771 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1773 AddLaserEdge(LX, LY);
1774 AddDamagedField(ELX, ELY);
1776 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1779 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1781 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1786 LX += step_size * XS;
1787 LY += step_size * YS;
1789 laser.num_beamers++;
1798 boolean HitOnlyAnEdge(int hit_mask)
1800 // check if the laser hit only the edge of an element and, if so, go on
1803 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1807 if ((hit_mask == HIT_MASK_TOPLEFT ||
1808 hit_mask == HIT_MASK_TOPRIGHT ||
1809 hit_mask == HIT_MASK_BOTTOMLEFT ||
1810 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1811 laser.current_angle % 4) // angle is not 90°
1815 if (hit_mask == HIT_MASK_TOPLEFT)
1820 else if (hit_mask == HIT_MASK_TOPRIGHT)
1825 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1830 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1836 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1842 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1849 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1855 boolean HitPolarizer(int element, int hit_mask)
1857 if (HitOnlyAnEdge(hit_mask))
1860 if (IS_DF_GRID(element))
1862 int grid_angle = get_element_angle(element);
1865 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1866 grid_angle, laser.current_angle);
1869 AddLaserEdge(LX, LY);
1870 AddDamagedField(ELX, ELY);
1873 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1875 if (laser.current_angle == grid_angle ||
1876 laser.current_angle == get_opposite_angle(grid_angle))
1878 // skip the whole element before continuing the scan
1884 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1886 if (LX/TILEX > ELX || LY/TILEY > ELY)
1888 /* skipping scan positions to the right and down skips one scan
1889 position too much, because this is only the top left scan position
1890 of totally four scan positions (plus one to the right, one to the
1891 bottom and one to the bottom right) */
1897 AddLaserEdge(LX, LY);
1903 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1905 LX / TILEX, LY / TILEY,
1906 LX % TILEX, LY % TILEY);
1911 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1913 return HitReflectingWalls(element, hit_mask);
1917 return HitAbsorbingWalls(element, hit_mask);
1920 else if (IS_GRID_STEEL(element))
1922 return HitReflectingWalls(element, hit_mask);
1924 else // IS_GRID_WOOD
1926 return HitAbsorbingWalls(element, hit_mask);
1932 boolean HitBlock(int element, int hit_mask)
1934 boolean check = FALSE;
1936 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1937 game_mm.num_keys == 0)
1940 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1943 int ex = ELX * TILEX + 14;
1944 int ey = ELY * TILEY + 14;
1948 for (i = 1; i < 32; i++)
1953 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1958 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1959 return HitAbsorbingWalls(element, hit_mask);
1963 AddLaserEdge(LX - XS, LY - YS);
1964 AddDamagedField(ELX, ELY);
1967 Box[ELX][ELY] = laser.num_edges;
1969 return HitReflectingWalls(element, hit_mask);
1972 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1974 int xs = XS / 2, ys = YS / 2;
1975 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1976 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1978 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1979 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1981 laser.overloaded = (element == EL_GATE_STONE);
1986 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1987 (hit_mask == HIT_MASK_TOP ||
1988 hit_mask == HIT_MASK_LEFT ||
1989 hit_mask == HIT_MASK_RIGHT ||
1990 hit_mask == HIT_MASK_BOTTOM))
1991 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1992 hit_mask == HIT_MASK_BOTTOM),
1993 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1994 hit_mask == HIT_MASK_RIGHT));
1995 AddLaserEdge(LX, LY);
2001 if (element == EL_GATE_STONE && Box[ELX][ELY])
2003 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2015 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2017 int xs = XS / 2, ys = YS / 2;
2018 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2019 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2021 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2022 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2024 laser.overloaded = (element == EL_BLOCK_STONE);
2029 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2030 (hit_mask == HIT_MASK_TOP ||
2031 hit_mask == HIT_MASK_LEFT ||
2032 hit_mask == HIT_MASK_RIGHT ||
2033 hit_mask == HIT_MASK_BOTTOM))
2034 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2035 hit_mask == HIT_MASK_BOTTOM),
2036 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2037 hit_mask == HIT_MASK_RIGHT));
2038 AddDamagedField(ELX, ELY);
2040 LX = ELX * TILEX + 14;
2041 LY = ELY * TILEY + 14;
2043 AddLaserEdge(LX, LY);
2045 laser.stops_inside_element = TRUE;
2053 boolean HitLaserSource(int element, int hit_mask)
2055 if (HitOnlyAnEdge(hit_mask))
2058 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2060 laser.overloaded = TRUE;
2065 boolean HitLaserDestination(int element, int hit_mask)
2067 if (HitOnlyAnEdge(hit_mask))
2070 if (element != EL_EXIT_OPEN &&
2071 !(IS_RECEIVER(element) &&
2072 game_mm.kettles_still_needed == 0 &&
2073 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2075 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2080 if (IS_RECEIVER(element) ||
2081 (IS_22_5_ANGLE(laser.current_angle) &&
2082 (ELX != (LX + 6 * XS) / TILEX ||
2083 ELY != (LY + 6 * YS) / TILEY ||
2092 LX = ELX * TILEX + 14;
2093 LY = ELY * TILEY + 14;
2095 laser.stops_inside_element = TRUE;
2098 AddLaserEdge(LX, LY);
2099 AddDamagedField(ELX, ELY);
2101 if (game_mm.lights_still_needed == 0)
2103 game_mm.level_solved = TRUE;
2105 SetTileCursorActive(FALSE);
2111 boolean HitReflectingWalls(int element, int hit_mask)
2113 // check if laser hits side of a wall with an angle that is not 90°
2114 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2115 hit_mask == HIT_MASK_LEFT ||
2116 hit_mask == HIT_MASK_RIGHT ||
2117 hit_mask == HIT_MASK_BOTTOM))
2119 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2124 if (!IS_DF_GRID(element))
2125 AddLaserEdge(LX, LY);
2127 // check if laser hits wall with an angle of 45°
2128 if (!IS_22_5_ANGLE(laser.current_angle))
2130 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2133 laser.current_angle = get_mirrored_angle(laser.current_angle,
2136 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2139 laser.current_angle = get_mirrored_angle(laser.current_angle,
2143 AddLaserEdge(LX, LY);
2145 XS = 2 * Step[laser.current_angle].x;
2146 YS = 2 * Step[laser.current_angle].y;
2150 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2152 laser.current_angle = get_mirrored_angle(laser.current_angle,
2157 if (!IS_DF_GRID(element))
2158 AddLaserEdge(LX, LY);
2163 if (!IS_DF_GRID(element))
2164 AddLaserEdge(LX, LY + YS / 2);
2167 if (!IS_DF_GRID(element))
2168 AddLaserEdge(LX, LY);
2171 YS = 2 * Step[laser.current_angle].y;
2175 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2177 laser.current_angle = get_mirrored_angle(laser.current_angle,
2182 if (!IS_DF_GRID(element))
2183 AddLaserEdge(LX, LY);
2188 if (!IS_DF_GRID(element))
2189 AddLaserEdge(LX + XS / 2, LY);
2192 if (!IS_DF_GRID(element))
2193 AddLaserEdge(LX, LY);
2196 XS = 2 * Step[laser.current_angle].x;
2202 // reflection at the edge of reflecting DF style wall
2203 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2205 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2206 hit_mask == HIT_MASK_TOPRIGHT) ||
2207 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2208 hit_mask == HIT_MASK_TOPLEFT) ||
2209 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2210 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2211 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2212 hit_mask == HIT_MASK_BOTTOMRIGHT))
2215 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2216 ANG_MIRROR_135 : ANG_MIRROR_45);
2218 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2220 AddDamagedField(ELX, ELY);
2221 AddLaserEdge(LX, LY);
2223 laser.current_angle = get_mirrored_angle(laser.current_angle,
2231 AddLaserEdge(LX, LY);
2237 // reflection inside an edge of reflecting DF style wall
2238 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2240 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2241 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2242 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2243 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2244 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2245 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2246 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2247 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2250 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2251 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2252 ANG_MIRROR_135 : ANG_MIRROR_45);
2254 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2257 AddDamagedField(ELX, ELY);
2260 AddLaserEdge(LX - XS, LY - YS);
2261 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2262 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2264 laser.current_angle = get_mirrored_angle(laser.current_angle,
2272 AddLaserEdge(LX, LY);
2278 // check if laser hits DF style wall with an angle of 90°
2279 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2281 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2282 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2283 (IS_VERT_ANGLE(laser.current_angle) &&
2284 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2286 // laser at last step touched nothing or the same side of the wall
2287 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2289 AddDamagedField(ELX, ELY);
2296 last_hit_mask = hit_mask;
2303 if (!HitOnlyAnEdge(hit_mask))
2305 laser.overloaded = TRUE;
2313 boolean HitAbsorbingWalls(int element, int hit_mask)
2315 if (HitOnlyAnEdge(hit_mask))
2319 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2321 AddLaserEdge(LX - XS, LY - YS);
2328 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2330 AddLaserEdge(LX - XS, LY - YS);
2336 if (IS_WALL_WOOD(element) ||
2337 IS_DF_WALL_WOOD(element) ||
2338 IS_GRID_WOOD(element) ||
2339 IS_GRID_WOOD_FIXED(element) ||
2340 IS_GRID_WOOD_AUTO(element) ||
2341 element == EL_FUSE_ON ||
2342 element == EL_BLOCK_WOOD ||
2343 element == EL_GATE_WOOD)
2345 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2350 if (IS_WALL_ICE(element))
2354 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2355 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2357 // check if laser hits wall with an angle of 90°
2358 if (IS_90_ANGLE(laser.current_angle))
2359 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2361 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2365 for (i = 0; i < 4; i++)
2367 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2368 mask = 15 - (8 >> i);
2369 else if (ABS(XS) == 4 &&
2371 (XS > 0) == (i % 2) &&
2372 (YS < 0) == (i / 2))
2373 mask = 3 + (i / 2) * 9;
2374 else if (ABS(YS) == 4 &&
2376 (XS < 0) == (i % 2) &&
2377 (YS > 0) == (i / 2))
2378 mask = 5 + (i % 2) * 5;
2382 laser.wall_mask = mask;
2384 else if (IS_WALL_AMOEBA(element))
2386 int elx = (LX - 2 * XS) / TILEX;
2387 int ely = (LY - 2 * YS) / TILEY;
2388 int element2 = Tile[elx][ely];
2391 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2393 laser.dest_element = EL_EMPTY;
2401 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2402 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2404 if (IS_90_ANGLE(laser.current_angle))
2405 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2407 laser.dest_element = element2 | EL_WALL_AMOEBA;
2409 laser.wall_mask = mask;
2415 static void OpenExit(int x, int y)
2419 if (!MovDelay[x][y]) // next animation frame
2420 MovDelay[x][y] = 4 * delay;
2422 if (MovDelay[x][y]) // wait some time before next frame
2427 phase = MovDelay[x][y] / delay;
2429 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2430 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2432 if (!MovDelay[x][y])
2434 Tile[x][y] = EL_EXIT_OPEN;
2440 static void OpenSurpriseBall(int x, int y)
2444 if (!MovDelay[x][y]) // next animation frame
2445 MovDelay[x][y] = 50 * delay;
2447 if (MovDelay[x][y]) // wait some time before next frame
2451 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2454 int graphic = el2gfx(Store[x][y]);
2456 int dx = RND(26), dy = RND(26);
2458 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2460 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2461 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2463 MarkTileDirty(x, y);
2466 if (!MovDelay[x][y])
2468 Tile[x][y] = Store[x][y];
2469 Store[x][y] = Store2[x][y] = 0;
2478 static void MeltIce(int x, int y)
2483 if (!MovDelay[x][y]) // next animation frame
2484 MovDelay[x][y] = frames * delay;
2486 if (MovDelay[x][y]) // wait some time before next frame
2489 int wall_mask = Store2[x][y];
2490 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2493 phase = frames - MovDelay[x][y] / delay - 1;
2495 if (!MovDelay[x][y])
2499 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2500 Store[x][y] = Store2[x][y] = 0;
2502 DrawWalls_MM(x, y, Tile[x][y]);
2504 if (Tile[x][y] == EL_WALL_ICE)
2505 Tile[x][y] = EL_EMPTY;
2507 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2508 if (laser.damage[i].is_mirror)
2512 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2514 DrawLaser(0, DL_LASER_DISABLED);
2518 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2520 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2522 laser.redraw = TRUE;
2527 static void GrowAmoeba(int x, int y)
2532 if (!MovDelay[x][y]) // next animation frame
2533 MovDelay[x][y] = frames * delay;
2535 if (MovDelay[x][y]) // wait some time before next frame
2538 int wall_mask = Store2[x][y];
2539 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2542 phase = MovDelay[x][y] / delay;
2544 if (!MovDelay[x][y])
2546 Tile[x][y] = real_element;
2547 Store[x][y] = Store2[x][y] = 0;
2549 DrawWalls_MM(x, y, Tile[x][y]);
2550 DrawLaser(0, DL_LASER_ENABLED);
2552 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2554 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2559 static void DrawFieldAnimated_MM(int x, int y)
2563 laser.redraw = TRUE;
2566 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2568 int element = Tile[x][y];
2569 int graphic = el2gfx(element);
2571 if (!getGraphicInfo_NewFrame(x, y, graphic))
2576 laser.redraw = TRUE;
2579 static void DrawFieldTwinkle(int x, int y)
2581 if (MovDelay[x][y] != 0) // wait some time before next frame
2587 if (MovDelay[x][y] != 0)
2589 int graphic = IMG_TWINKLE_WHITE;
2590 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2592 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2595 laser.redraw = TRUE;
2599 static void Explode_MM(int x, int y, int phase, int mode)
2601 int num_phase = 9, delay = 2;
2602 int last_phase = num_phase * delay;
2603 int half_phase = (num_phase / 2) * delay;
2605 laser.redraw = TRUE;
2607 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2609 int center_element = Tile[x][y];
2611 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2613 // put moving element to center field (and let it explode there)
2614 center_element = MovingOrBlocked2Element_MM(x, y);
2615 RemoveMovingField_MM(x, y);
2617 Tile[x][y] = center_element;
2620 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2621 Store[x][y] = center_element;
2623 Store[x][y] = EL_EMPTY;
2625 Store2[x][y] = mode;
2627 Tile[x][y] = EL_EXPLODING_OPAQUE;
2628 GfxElement[x][y] = center_element;
2630 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2632 ExplodePhase[x][y] = 1;
2638 GfxFrame[x][y] = 0; // restart explosion animation
2640 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2642 if (phase == half_phase)
2644 Tile[x][y] = EL_EXPLODING_TRANSP;
2646 if (x == ELX && y == ELY)
2650 if (phase == last_phase)
2652 if (Store[x][y] == EL_BOMB_ACTIVE)
2654 DrawLaser(0, DL_LASER_DISABLED);
2657 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2658 Store[x][y] = EL_EMPTY;
2660 GameOver_MM(GAME_OVER_DELAYED);
2662 laser.overloaded = FALSE;
2664 else if (IS_MCDUFFIN(Store[x][y]))
2666 Store[x][y] = EL_EMPTY;
2668 GameOver_MM(GAME_OVER_BOMB);
2671 Tile[x][y] = Store[x][y];
2672 Store[x][y] = Store2[x][y] = 0;
2673 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2678 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2680 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2681 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2683 DrawGraphicAnimation_MM(x, y, graphic, frame);
2685 MarkTileDirty(x, y);
2689 static void Bang_MM(int x, int y)
2691 int element = Tile[x][y];
2694 DrawLaser(0, DL_LASER_ENABLED);
2697 if (IS_PACMAN(element))
2698 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2699 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2700 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2701 else if (element == EL_KEY)
2702 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2704 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2706 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2709 void TurnRound(int x, int y)
2721 { 0, 0 }, { 0, 0 }, { 0, 0 },
2726 int left, right, back;
2730 { MV_DOWN, MV_UP, MV_RIGHT },
2731 { MV_UP, MV_DOWN, MV_LEFT },
2733 { MV_LEFT, MV_RIGHT, MV_DOWN },
2734 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2735 { MV_RIGHT, MV_LEFT, MV_UP }
2738 int element = Tile[x][y];
2739 int old_move_dir = MovDir[x][y];
2740 int right_dir = turn[old_move_dir].right;
2741 int back_dir = turn[old_move_dir].back;
2742 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2743 int right_x = x + right_dx, right_y = y + right_dy;
2745 if (element == EL_PACMAN)
2747 boolean can_turn_right = FALSE;
2749 if (IN_LEV_FIELD(right_x, right_y) &&
2750 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2751 can_turn_right = TRUE;
2754 MovDir[x][y] = right_dir;
2756 MovDir[x][y] = back_dir;
2762 static void StartMoving_MM(int x, int y)
2764 int element = Tile[x][y];
2769 if (CAN_MOVE(element))
2773 if (MovDelay[x][y]) // wait some time before next movement
2781 // now make next step
2783 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2785 if (element == EL_PACMAN &&
2786 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2787 !ObjHit(newx, newy, HIT_POS_CENTER))
2789 Store[newx][newy] = Tile[newx][newy];
2790 Tile[newx][newy] = EL_EMPTY;
2792 DrawField_MM(newx, newy);
2794 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2795 ObjHit(newx, newy, HIT_POS_CENTER))
2797 // object was running against a wall
2804 InitMovingField_MM(x, y, MovDir[x][y]);
2808 ContinueMoving_MM(x, y);
2811 static void ContinueMoving_MM(int x, int y)
2813 int element = Tile[x][y];
2814 int direction = MovDir[x][y];
2815 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2816 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2817 int horiz_move = (dx!=0);
2818 int newx = x + dx, newy = y + dy;
2819 int step = (horiz_move ? dx : dy) * TILEX / 8;
2821 MovPos[x][y] += step;
2823 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2825 Tile[x][y] = EL_EMPTY;
2826 Tile[newx][newy] = element;
2828 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2829 MovDelay[newx][newy] = 0;
2831 if (!CAN_MOVE(element))
2832 MovDir[newx][newy] = 0;
2835 DrawField_MM(newx, newy);
2837 Stop[newx][newy] = TRUE;
2839 if (element == EL_PACMAN)
2841 if (Store[newx][newy] == EL_BOMB)
2842 Bang_MM(newx, newy);
2844 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2845 (LX + 2 * XS) / TILEX == newx &&
2846 (LY + 2 * YS) / TILEY == newy)
2853 else // still moving on
2858 laser.redraw = TRUE;
2861 boolean ClickElement(int x, int y, int button)
2863 static DelayCounter click_delay = { CLICK_DELAY };
2864 static boolean new_button = TRUE;
2865 boolean element_clicked = FALSE;
2870 // initialize static variables
2871 click_delay.count = 0;
2872 click_delay.value = CLICK_DELAY;
2878 // do not rotate objects hit by the laser after the game was solved
2879 if (game_mm.level_solved && Hit[x][y])
2882 if (button == MB_RELEASED)
2885 click_delay.value = CLICK_DELAY;
2887 // release eventually hold auto-rotating mirror
2888 RotateMirror(x, y, MB_RELEASED);
2893 if (!FrameReached(&click_delay) && !new_button)
2896 if (button == MB_MIDDLEBUTTON) // middle button has no function
2899 if (!IN_LEV_FIELD(x, y))
2902 if (Tile[x][y] == EL_EMPTY)
2905 element = Tile[x][y];
2907 if (IS_MIRROR(element) ||
2908 IS_BEAMER(element) ||
2909 IS_POLAR(element) ||
2910 IS_POLAR_CROSS(element) ||
2911 IS_DF_MIRROR(element) ||
2912 IS_DF_MIRROR_AUTO(element))
2914 RotateMirror(x, y, button);
2916 element_clicked = TRUE;
2918 else if (IS_MCDUFFIN(element))
2920 if (!laser.fuse_off)
2922 DrawLaser(0, DL_LASER_DISABLED);
2929 element = get_rotated_element(element, BUTTON_ROTATION(button));
2930 laser.start_angle = get_element_angle(element);
2934 Tile[x][y] = element;
2941 if (!laser.fuse_off)
2944 element_clicked = TRUE;
2946 else if (element == EL_FUSE_ON && laser.fuse_off)
2948 if (x != laser.fuse_x || y != laser.fuse_y)
2951 laser.fuse_off = FALSE;
2952 laser.fuse_x = laser.fuse_y = -1;
2954 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2957 element_clicked = TRUE;
2959 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2961 laser.fuse_off = TRUE;
2964 laser.overloaded = FALSE;
2966 DrawLaser(0, DL_LASER_DISABLED);
2967 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2969 element_clicked = TRUE;
2971 else if (element == EL_LIGHTBALL)
2974 RaiseScoreElement_MM(element);
2975 DrawLaser(0, DL_LASER_ENABLED);
2977 element_clicked = TRUE;
2980 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2983 return element_clicked;
2986 void RotateMirror(int x, int y, int button)
2988 if (button == MB_RELEASED)
2990 // release eventually hold auto-rotating mirror
2997 if (IS_MIRROR(Tile[x][y]) ||
2998 IS_POLAR_CROSS(Tile[x][y]) ||
2999 IS_POLAR(Tile[x][y]) ||
3000 IS_BEAMER(Tile[x][y]) ||
3001 IS_DF_MIRROR(Tile[x][y]) ||
3002 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3003 IS_GRID_WOOD_AUTO(Tile[x][y]))
3005 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3007 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3009 if (button == MB_LEFTBUTTON)
3011 // left mouse button only for manual adjustment, no auto-rotating;
3012 // freeze mirror for until mouse button released
3016 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3018 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3022 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3024 int edge = Hit[x][y];
3030 DrawLaser(edge - 1, DL_LASER_DISABLED);
3034 else if (ObjHit(x, y, HIT_POS_CENTER))
3036 int edge = Hit[x][y];
3040 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3045 DrawLaser(edge - 1, DL_LASER_DISABLED);
3052 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3057 if ((IS_BEAMER(Tile[x][y]) ||
3058 IS_POLAR(Tile[x][y]) ||
3059 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3061 if (IS_BEAMER(Tile[x][y]))
3064 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3065 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3078 DrawLaser(0, DL_LASER_ENABLED);
3082 static void AutoRotateMirrors(void)
3086 if (!FrameReached(&rotate_delay))
3089 for (x = 0; x < lev_fieldx; x++)
3091 for (y = 0; y < lev_fieldy; y++)
3093 int element = Tile[x][y];
3095 // do not rotate objects hit by the laser after the game was solved
3096 if (game_mm.level_solved && Hit[x][y])
3099 if (IS_DF_MIRROR_AUTO(element) ||
3100 IS_GRID_WOOD_AUTO(element) ||
3101 IS_GRID_STEEL_AUTO(element) ||
3102 element == EL_REFRACTOR)
3103 RotateMirror(x, y, MB_RIGHTBUTTON);
3108 boolean ObjHit(int obx, int oby, int bits)
3115 if (bits & HIT_POS_CENTER)
3117 if (CheckLaserPixel(cSX + obx + 15,
3122 if (bits & HIT_POS_EDGE)
3124 for (i = 0; i < 4; i++)
3125 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3126 cSY + oby + 31 * (i / 2)))
3130 if (bits & HIT_POS_BETWEEN)
3132 for (i = 0; i < 4; i++)
3133 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3134 cSY + 4 + oby + 22 * (i / 2)))
3141 void DeletePacMan(int px, int py)
3147 if (game_mm.num_pacman <= 1)
3149 game_mm.num_pacman = 0;
3153 for (i = 0; i < game_mm.num_pacman; i++)
3154 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3157 game_mm.num_pacman--;
3159 for (j = i; j < game_mm.num_pacman; j++)
3161 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3162 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3163 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3167 void ColorCycling(void)
3169 static int CC, Cc = 0;
3171 static int color, old = 0xF00, new = 0x010, mult = 1;
3172 static unsigned short red, green, blue;
3174 if (color_status == STATIC_COLORS)
3179 if (CC < Cc || CC > Cc + 2)
3183 color = old + new * mult;
3189 if (ABS(mult) == 16)
3199 red = 0x0e00 * ((color & 0xF00) >> 8);
3200 green = 0x0e00 * ((color & 0x0F0) >> 4);
3201 blue = 0x0e00 * ((color & 0x00F));
3202 SetRGB(pen_magicolor[0], red, green, blue);
3204 red = 0x1111 * ((color & 0xF00) >> 8);
3205 green = 0x1111 * ((color & 0x0F0) >> 4);
3206 blue = 0x1111 * ((color & 0x00F));
3207 SetRGB(pen_magicolor[1], red, green, blue);
3211 static void GameActions_MM_Ext(void)
3218 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3221 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3223 element = Tile[x][y];
3225 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3226 StartMoving_MM(x, y);
3227 else if (IS_MOVING(x, y))
3228 ContinueMoving_MM(x, y);
3229 else if (IS_EXPLODING(element))
3230 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3231 else if (element == EL_EXIT_OPENING)
3233 else if (element == EL_GRAY_BALL_OPENING)
3234 OpenSurpriseBall(x, y);
3235 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3237 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3239 else if (IS_MIRROR(element) ||
3240 IS_MIRROR_FIXED(element) ||
3241 element == EL_PRISM)
3242 DrawFieldTwinkle(x, y);
3243 else if (element == EL_GRAY_BALL_OPENING ||
3244 element == EL_BOMB_ACTIVE ||
3245 element == EL_MINE_ACTIVE)
3246 DrawFieldAnimated_MM(x, y);
3247 else if (!IS_BLOCKED(x, y))
3248 DrawFieldAnimatedIfNeeded_MM(x, y);
3251 AutoRotateMirrors();
3254 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3256 // redraw after Explode_MM() ...
3258 DrawLaser(0, DL_LASER_ENABLED);
3259 laser.redraw = FALSE;
3264 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3268 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3270 DrawLaser(0, DL_LASER_DISABLED);
3275 // skip all following game actions if game is over
3276 if (game_mm.game_over)
3279 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3283 GameOver_MM(GAME_OVER_NO_ENERGY);
3288 if (FrameReached(&energy_delay))
3290 if (game_mm.energy_left > 0)
3291 game_mm.energy_left--;
3293 // when out of energy, wait another frame to play "out of time" sound
3296 element = laser.dest_element;
3299 if (element != Tile[ELX][ELY])
3301 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3302 element, Tile[ELX][ELY]);
3306 if (!laser.overloaded && laser.overload_value == 0 &&
3307 element != EL_BOMB &&
3308 element != EL_BOMB_ACTIVE &&
3309 element != EL_MINE &&
3310 element != EL_MINE_ACTIVE &&
3311 element != EL_BALL_GRAY &&
3312 element != EL_BLOCK_STONE &&
3313 element != EL_BLOCK_WOOD &&
3314 element != EL_FUSE_ON &&
3315 element != EL_FUEL_FULL &&
3316 !IS_WALL_ICE(element) &&
3317 !IS_WALL_AMOEBA(element))
3320 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3322 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3323 (!laser.overloaded && laser.overload_value > 0)) &&
3324 FrameReached(&overload_delay))
3326 if (laser.overloaded)
3327 laser.overload_value++;
3329 laser.overload_value--;
3331 if (game_mm.cheat_no_overload)
3333 laser.overloaded = FALSE;
3334 laser.overload_value = 0;
3337 game_mm.laser_overload_value = laser.overload_value;
3339 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3341 SetLaserColor(0xFF);
3343 DrawLaser(0, DL_LASER_ENABLED);
3346 if (!laser.overloaded)
3347 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3348 else if (setup.sound_loops)
3349 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3351 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3353 if (laser.overloaded)
3356 BlitBitmap(pix[PIX_DOOR], drawto,
3357 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3358 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3359 - laser.overload_value,
3360 OVERLOAD_XSIZE, laser.overload_value,
3361 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3362 - laser.overload_value);
3364 redraw_mask |= REDRAW_DOOR_1;
3369 BlitBitmap(pix[PIX_DOOR], drawto,
3370 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3371 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3372 DX_OVERLOAD, DY_OVERLOAD);
3374 redraw_mask |= REDRAW_DOOR_1;
3377 if (laser.overload_value == MAX_LASER_OVERLOAD)
3379 UpdateAndDisplayGameControlValues();
3383 GameOver_MM(GAME_OVER_OVERLOADED);
3394 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3396 if (game_mm.cheat_no_explosion)
3401 laser.dest_element = EL_EXPLODING_OPAQUE;
3406 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3408 laser.fuse_off = TRUE;
3412 DrawLaser(0, DL_LASER_DISABLED);
3413 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3416 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3418 if (!Store2[ELX][ELY]) // check if content element not yet determined
3420 int last_anim_random_frame = gfx.anim_random_frame;
3423 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3424 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3426 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3427 native_mm_level.ball_choice_mode, 0,
3428 game_mm.ball_choice_pos);
3430 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3431 gfx.anim_random_frame = last_anim_random_frame;
3433 game_mm.ball_choice_pos++;
3435 int new_element = native_mm_level.ball_content[element_pos];
3437 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3438 Store2[ELX][ELY] = TRUE;
3441 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3443 // !!! CHECK AGAIN: Laser on Polarizer !!!
3446 laser.dest_element_last = Tile[ELX][ELY];
3447 laser.dest_element_last_x = ELX;
3448 laser.dest_element_last_y = ELY;
3458 element = EL_MIRROR_START + RND(16);
3464 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3471 element = (rnd == 0 ? EL_FUSE_ON :
3472 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3473 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3474 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3475 EL_MIRROR_FIXED_START + rnd - 25);
3480 graphic = el2gfx(element);
3482 for (i = 0; i < 50; i++)
3488 BlitBitmap(pix[PIX_BACK], drawto,
3489 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3490 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3491 SX + ELX * TILEX + x,
3492 SY + ELY * TILEY + y);
3494 MarkTileDirty(ELX, ELY);
3497 DrawLaser(0, DL_LASER_ENABLED);
3499 Delay_WithScreenUpdates(50);
3502 Tile[ELX][ELY] = element;
3503 DrawField_MM(ELX, ELY);
3506 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3509 // above stuff: GRAY BALL -> PRISM !!!
3511 LX = ELX * TILEX + 14;
3512 LY = ELY * TILEY + 14;
3513 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3520 laser.num_edges -= 2;
3521 laser.num_damages--;
3525 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3526 if (laser.damage[i].is_mirror)
3530 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3532 DrawLaser(0, DL_LASER_DISABLED);
3534 DrawLaser(0, DL_LASER_DISABLED);
3543 if (IS_WALL_ICE(element) && CT > 50)
3545 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3548 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3549 Store[ELX][ELY] = EL_WALL_ICE;
3550 Store2[ELX][ELY] = laser.wall_mask;
3552 laser.dest_element = Tile[ELX][ELY];
3557 for (i = 0; i < 5; i++)
3563 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3567 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3569 Delay_WithScreenUpdates(100);
3572 if (Tile[ELX][ELY] == EL_WALL_ICE)
3573 Tile[ELX][ELY] = EL_EMPTY;
3577 LX = laser.edge[laser.num_edges].x - cSX2;
3578 LY = laser.edge[laser.num_edges].y - cSY2;
3581 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3582 if (laser.damage[i].is_mirror)
3586 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3588 DrawLaser(0, DL_LASER_DISABLED);
3595 if (IS_WALL_AMOEBA(element) && CT > 60)
3597 int k1, k2, k3, dx, dy, de, dm;
3598 int element2 = Tile[ELX][ELY];
3600 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3603 for (i = laser.num_damages - 1; i >= 0; i--)
3604 if (laser.damage[i].is_mirror)
3607 r = laser.num_edges;
3608 d = laser.num_damages;
3615 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3618 DrawLaser(0, DL_LASER_ENABLED);
3621 x = laser.damage[k1].x;
3622 y = laser.damage[k1].y;
3627 for (i = 0; i < 4; i++)
3629 if (laser.wall_mask & (1 << i))
3631 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3632 cSY + ELY * TILEY + 31 * (i / 2)))
3635 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3636 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3643 for (i = 0; i < 4; i++)
3645 if (laser.wall_mask & (1 << i))
3647 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3648 cSY + ELY * TILEY + 31 * (i / 2)))
3655 if (laser.num_beamers > 0 ||
3656 k1 < 1 || k2 < 4 || k3 < 4 ||
3657 CheckLaserPixel(cSX + ELX * TILEX + 14,
3658 cSY + ELY * TILEY + 14))
3660 laser.num_edges = r;
3661 laser.num_damages = d;
3663 DrawLaser(0, DL_LASER_DISABLED);
3666 Tile[ELX][ELY] = element | laser.wall_mask;
3670 de = Tile[ELX][ELY];
3671 dm = laser.wall_mask;
3675 int x = ELX, y = ELY;
3676 int wall_mask = laser.wall_mask;
3679 DrawLaser(0, DL_LASER_ENABLED);
3681 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3683 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3684 Store[x][y] = EL_WALL_AMOEBA;
3685 Store2[x][y] = wall_mask;
3691 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3693 DrawLaser(0, DL_LASER_ENABLED);
3695 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3697 for (i = 4; i >= 0; i--)
3699 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3702 Delay_WithScreenUpdates(20);
3705 DrawLaser(0, DL_LASER_ENABLED);
3710 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3711 laser.stops_inside_element && CT > native_mm_level.time_block)
3716 if (ABS(XS) > ABS(YS))
3723 for (i = 0; i < 4; i++)
3730 x = ELX + Step[k * 4].x;
3731 y = ELY + Step[k * 4].y;
3733 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3736 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3744 laser.overloaded = (element == EL_BLOCK_STONE);
3749 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3752 Tile[x][y] = element;
3754 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3757 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3759 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3760 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3768 if (element == EL_FUEL_FULL && CT > 10)
3770 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3771 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3773 for (i = start; i <= num_init_game_frames; i++)
3775 if (i == num_init_game_frames)
3776 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3777 else if (setup.sound_loops)
3778 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3780 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3782 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3784 UpdateAndDisplayGameControlValues();
3789 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3791 DrawField_MM(ELX, ELY);
3793 DrawLaser(0, DL_LASER_ENABLED);
3801 void GameActions_MM(struct MouseActionInfo action)
3803 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3804 boolean button_released = (action.button == MB_RELEASED);
3806 GameActions_MM_Ext();
3808 CheckSingleStepMode_MM(element_clicked, button_released);
3811 void MovePacMen(void)
3813 int mx, my, ox, oy, nx, ny;
3817 if (++pacman_nr >= game_mm.num_pacman)
3820 game_mm.pacman[pacman_nr].dir--;
3822 for (l = 1; l < 5; l++)
3824 game_mm.pacman[pacman_nr].dir++;
3826 if (game_mm.pacman[pacman_nr].dir > 4)
3827 game_mm.pacman[pacman_nr].dir = 1;
3829 if (game_mm.pacman[pacman_nr].dir % 2)
3832 my = game_mm.pacman[pacman_nr].dir - 2;
3837 mx = 3 - game_mm.pacman[pacman_nr].dir;
3840 ox = game_mm.pacman[pacman_nr].x;
3841 oy = game_mm.pacman[pacman_nr].y;
3844 element = Tile[nx][ny];
3846 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3849 if (!IS_EATABLE4PACMAN(element))
3852 if (ObjHit(nx, ny, HIT_POS_CENTER))
3855 Tile[ox][oy] = EL_EMPTY;
3857 EL_PACMAN_RIGHT - 1 +
3858 (game_mm.pacman[pacman_nr].dir - 1 +
3859 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3861 game_mm.pacman[pacman_nr].x = nx;
3862 game_mm.pacman[pacman_nr].y = ny;
3864 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3866 if (element != EL_EMPTY)
3868 int graphic = el2gfx(Tile[nx][ny]);
3873 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3876 ox = cSX + ox * TILEX;
3877 oy = cSY + oy * TILEY;
3879 for (i = 1; i < 33; i += 2)
3880 BlitBitmap(bitmap, window,
3881 src_x, src_y, TILEX, TILEY,
3882 ox + i * mx, oy + i * my);
3883 Ct = Ct + FrameCounter - CT;
3886 DrawField_MM(nx, ny);
3889 if (!laser.fuse_off)
3891 DrawLaser(0, DL_LASER_ENABLED);
3893 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3895 AddDamagedField(nx, ny);
3897 laser.damage[laser.num_damages - 1].edge = 0;
3901 if (element == EL_BOMB)
3902 DeletePacMan(nx, ny);
3904 if (IS_WALL_AMOEBA(element) &&
3905 (LX + 2 * XS) / TILEX == nx &&
3906 (LY + 2 * YS) / TILEY == ny)
3916 static void InitMovingField_MM(int x, int y, int direction)
3918 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3919 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3921 MovDir[x][y] = direction;
3922 MovDir[newx][newy] = direction;
3924 if (Tile[newx][newy] == EL_EMPTY)
3925 Tile[newx][newy] = EL_BLOCKED;
3928 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3930 int direction = MovDir[x][y];
3931 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3932 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3938 static void Blocked2Moving_MM(int x, int y,
3939 int *comes_from_x, int *comes_from_y)
3941 int oldx = x, oldy = y;
3942 int direction = MovDir[x][y];
3944 if (direction == MV_LEFT)
3946 else if (direction == MV_RIGHT)
3948 else if (direction == MV_UP)
3950 else if (direction == MV_DOWN)
3953 *comes_from_x = oldx;
3954 *comes_from_y = oldy;
3957 static int MovingOrBlocked2Element_MM(int x, int y)
3959 int element = Tile[x][y];
3961 if (element == EL_BLOCKED)
3965 Blocked2Moving_MM(x, y, &oldx, &oldy);
3967 return Tile[oldx][oldy];
3974 static void RemoveField(int x, int y)
3976 Tile[x][y] = EL_EMPTY;
3983 static void RemoveMovingField_MM(int x, int y)
3985 int oldx = x, oldy = y, newx = x, newy = y;
3987 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3990 if (IS_MOVING(x, y))
3992 Moving2Blocked_MM(x, y, &newx, &newy);
3993 if (Tile[newx][newy] != EL_BLOCKED)
3996 else if (Tile[x][y] == EL_BLOCKED)
3998 Blocked2Moving_MM(x, y, &oldx, &oldy);
3999 if (!IS_MOVING(oldx, oldy))
4003 Tile[oldx][oldy] = EL_EMPTY;
4004 Tile[newx][newy] = EL_EMPTY;
4005 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4006 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4008 DrawLevelField_MM(oldx, oldy);
4009 DrawLevelField_MM(newx, newy);
4012 void PlaySoundLevel(int x, int y, int sound_nr)
4014 int sx = SCREENX(x), sy = SCREENY(y);
4016 int silence_distance = 8;
4018 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4019 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4022 if (!IN_LEV_FIELD(x, y) ||
4023 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4024 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4027 volume = SOUND_MAX_VOLUME;
4030 stereo = (sx - SCR_FIELDX/2) * 12;
4032 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4033 if (stereo > SOUND_MAX_RIGHT)
4034 stereo = SOUND_MAX_RIGHT;
4035 if (stereo < SOUND_MAX_LEFT)
4036 stereo = SOUND_MAX_LEFT;
4039 if (!IN_SCR_FIELD(sx, sy))
4041 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4042 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4044 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4047 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4050 static void RaiseScore_MM(int value)
4052 game_mm.score += value;
4055 void RaiseScoreElement_MM(int element)
4060 case EL_PACMAN_RIGHT:
4062 case EL_PACMAN_LEFT:
4063 case EL_PACMAN_DOWN:
4064 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4068 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4073 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4077 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4086 // ----------------------------------------------------------------------------
4087 // Mirror Magic game engine snapshot handling functions
4088 // ----------------------------------------------------------------------------
4090 void SaveEngineSnapshotValues_MM(void)
4094 engine_snapshot_mm.game_mm = game_mm;
4095 engine_snapshot_mm.laser = laser;
4097 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4099 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4101 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4102 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4103 engine_snapshot_mm.Box[x][y] = Box[x][y];
4104 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4108 engine_snapshot_mm.LX = LX;
4109 engine_snapshot_mm.LY = LY;
4110 engine_snapshot_mm.XS = XS;
4111 engine_snapshot_mm.YS = YS;
4112 engine_snapshot_mm.ELX = ELX;
4113 engine_snapshot_mm.ELY = ELY;
4114 engine_snapshot_mm.CT = CT;
4115 engine_snapshot_mm.Ct = Ct;
4117 engine_snapshot_mm.last_LX = last_LX;
4118 engine_snapshot_mm.last_LY = last_LY;
4119 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4120 engine_snapshot_mm.hold_x = hold_x;
4121 engine_snapshot_mm.hold_y = hold_y;
4122 engine_snapshot_mm.pacman_nr = pacman_nr;
4124 engine_snapshot_mm.rotate_delay = rotate_delay;
4125 engine_snapshot_mm.pacman_delay = pacman_delay;
4126 engine_snapshot_mm.energy_delay = energy_delay;
4127 engine_snapshot_mm.overload_delay = overload_delay;
4130 void LoadEngineSnapshotValues_MM(void)
4134 // stored engine snapshot buffers already restored at this point
4136 game_mm = engine_snapshot_mm.game_mm;
4137 laser = engine_snapshot_mm.laser;
4139 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4141 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4143 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4144 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4145 Box[x][y] = engine_snapshot_mm.Box[x][y];
4146 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4150 LX = engine_snapshot_mm.LX;
4151 LY = engine_snapshot_mm.LY;
4152 XS = engine_snapshot_mm.XS;
4153 YS = engine_snapshot_mm.YS;
4154 ELX = engine_snapshot_mm.ELX;
4155 ELY = engine_snapshot_mm.ELY;
4156 CT = engine_snapshot_mm.CT;
4157 Ct = engine_snapshot_mm.Ct;
4159 last_LX = engine_snapshot_mm.last_LX;
4160 last_LY = engine_snapshot_mm.last_LY;
4161 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4162 hold_x = engine_snapshot_mm.hold_x;
4163 hold_y = engine_snapshot_mm.hold_y;
4164 pacman_nr = engine_snapshot_mm.pacman_nr;
4166 rotate_delay = engine_snapshot_mm.rotate_delay;
4167 pacman_delay = engine_snapshot_mm.pacman_delay;
4168 energy_delay = engine_snapshot_mm.energy_delay;
4169 overload_delay = engine_snapshot_mm.overload_delay;
4171 RedrawPlayfield_MM();
4174 static int getAngleFromTouchDelta(int dx, int dy, int base)
4176 double pi = 3.141592653;
4177 double rad = atan2((double)-dy, (double)dx);
4178 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4179 double deg = rad2 * 180.0 / pi;
4181 return (int)(deg * base / 360.0 + 0.5) % base;
4184 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4186 // calculate start (source) position to be at the middle of the tile
4187 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4188 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4189 int dx = dst_mx - src_mx;
4190 int dy = dst_my - src_my;
4199 if (!IN_LEV_FIELD(x, y))
4202 element = Tile[x][y];
4204 if (!IS_MCDUFFIN(element) &&
4205 !IS_MIRROR(element) &&
4206 !IS_BEAMER(element) &&
4207 !IS_POLAR(element) &&
4208 !IS_POLAR_CROSS(element) &&
4209 !IS_DF_MIRROR(element))
4212 angle_old = get_element_angle(element);
4214 if (IS_MCDUFFIN(element))
4216 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4217 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4218 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4219 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4222 else if (IS_MIRROR(element) ||
4223 IS_DF_MIRROR(element))
4225 for (i = 0; i < laser.num_damages; i++)
4227 if (laser.damage[i].x == x &&
4228 laser.damage[i].y == y &&
4229 ObjHit(x, y, HIT_POS_CENTER))
4231 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4232 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4239 if (angle_new == -1)
4241 if (IS_MIRROR(element) ||
4242 IS_DF_MIRROR(element) ||
4246 if (IS_POLAR_CROSS(element))
4249 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4252 button = (angle_new == angle_old ? 0 :
4253 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4254 MB_LEFTBUTTON : MB_RIGHTBUTTON);