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
32 // special positions in the game control window (relative to control window)
41 #define XX_OVERLOAD 60
42 #define YY_OVERLOAD YY_ENERGY
44 // special positions in the game control window (relative to main window)
45 #define DX_LEVEL (DX + XX_LEVEL)
46 #define DY_LEVEL (DY + YY_LEVEL)
47 #define DX_KETTLES (DX + XX_KETTLES)
48 #define DY_KETTLES (DY + YY_KETTLES)
49 #define DX_SCORE (DX + XX_SCORE)
50 #define DY_SCORE (DY + YY_SCORE)
51 #define DX_ENERGY (DX + XX_ENERGY)
52 #define DY_ENERGY (DY + YY_ENERGY)
53 #define DX_OVERLOAD (DX + XX_OVERLOAD)
54 #define DY_OVERLOAD (DY + YY_OVERLOAD)
56 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
57 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
59 // game button identifiers
60 #define GAME_CTRL_ID_LEFT 0
61 #define GAME_CTRL_ID_MIDDLE 1
62 #define GAME_CTRL_ID_RIGHT 2
64 #define NUM_GAME_BUTTONS 3
66 // values for DrawLaser()
67 #define DL_LASER_DISABLED 0
68 #define DL_LASER_ENABLED 1
70 // values for 'click_delay_value' in ClickElement()
71 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
72 #define CLICK_DELAY 6 // delay (frames) for pressed butten
74 #define AUTO_ROTATE_DELAY CLICK_DELAY
75 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
76 #define NUM_INIT_CYCLE_STEPS 16
77 #define PACMAN_MOVE_DELAY 12
78 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
79 #define HEALTH_DEC_DELAY 3
80 #define HEALTH_INC_DELAY 9
81 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
83 #define BEGIN_NO_HEADLESS \
85 boolean last_headless = program.headless; \
87 program.headless = FALSE; \
89 #define END_NO_HEADLESS \
90 program.headless = last_headless; \
93 // forward declaration for internal use
94 static int MovingOrBlocked2Element_MM(int, int);
95 static void Bang_MM(int, int);
96 static void RaiseScore_MM(int);
97 static void RaiseScoreElement_MM(int);
98 static void RemoveMovingField_MM(int, int);
99 static void InitMovingField_MM(int, int, int);
100 static void ContinueMoving_MM(int, int);
101 static void Moving2Blocked_MM(int, int, int *, int *);
103 // bitmap for laser beam detection
104 static Bitmap *laser_bitmap = NULL;
106 // variables for laser control
107 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
108 static int hold_x = -1, hold_y = -1;
110 // variables for pacman control
111 static int pacman_nr = -1;
113 // various game engine delay counters
114 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
115 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
116 static DelayCounter energy_delay = { ENERGY_DELAY };
117 static DelayCounter overload_delay = { 0 };
119 // element masks for scanning pixels of MM elements
120 static const char mm_masks[10][16][16 + 1] =
304 static int get_element_angle(int element)
306 int element_phase = get_element_phase(element);
308 if (IS_MIRROR_FIXED(element) ||
309 IS_MCDUFFIN(element) ||
311 IS_RECEIVER(element))
312 return 4 * element_phase;
314 return element_phase;
317 static int get_opposite_angle(int angle)
319 int opposite_angle = angle + ANG_RAY_180;
321 // make sure "opposite_angle" is in valid interval [0, 15]
322 return (opposite_angle + 16) % 16;
325 static int get_mirrored_angle(int laser_angle, int mirror_angle)
327 int reflected_angle = 16 - laser_angle + mirror_angle;
329 // make sure "reflected_angle" is in valid interval [0, 15]
330 return (reflected_angle + 16) % 16;
333 static void DrawLaserLines(struct XY *points, int num_points, int mode)
335 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
336 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
338 DrawLines(drawto, points, num_points, pixel_drawto);
342 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
347 static boolean CheckLaserPixel(int x, int y)
353 pixel = ReadPixel(laser_bitmap, x, y);
357 return (pixel == WHITE_PIXEL);
360 static void CheckExitMM(void)
362 int exit_element = EL_EMPTY;
366 static int xy[4][2] =
374 for (y = 0; y < lev_fieldy; y++)
376 for (x = 0; x < lev_fieldx; x++)
378 if (Tile[x][y] == EL_EXIT_CLOSED)
380 // initiate opening animation of exit door
381 Tile[x][y] = EL_EXIT_OPENING;
383 exit_element = EL_EXIT_OPEN;
387 else if (IS_RECEIVER(Tile[x][y]))
389 // remove field that blocks receiver
390 int phase = Tile[x][y] - EL_RECEIVER_START;
391 int blocking_x, blocking_y;
393 blocking_x = x + xy[phase][0];
394 blocking_y = y + xy[phase][1];
396 if (IN_LEV_FIELD(blocking_x, blocking_y))
398 Tile[blocking_x][blocking_y] = EL_EMPTY;
400 DrawField_MM(blocking_x, blocking_y);
403 exit_element = EL_RECEIVER;
410 if (exit_element != EL_EMPTY)
411 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
414 static void InitMovDir_MM(int x, int y)
416 int element = Tile[x][y];
417 static int direction[3][4] =
419 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
420 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
421 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
426 case EL_PACMAN_RIGHT:
430 Tile[x][y] = EL_PACMAN;
431 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
439 static void InitField(int x, int y)
441 int element = Tile[x][y];
446 Tile[x][y] = EL_EMPTY;
451 if (native_mm_level.auto_count_kettles)
452 game_mm.kettles_still_needed++;
455 case EL_LIGHTBULB_OFF:
456 game_mm.lights_still_needed++;
460 if (IS_MIRROR(element) ||
461 IS_BEAMER_OLD(element) ||
462 IS_BEAMER(element) ||
464 IS_POLAR_CROSS(element) ||
465 IS_DF_MIRROR(element) ||
466 IS_DF_MIRROR_AUTO(element) ||
467 IS_GRID_STEEL_AUTO(element) ||
468 IS_GRID_WOOD_AUTO(element) ||
469 IS_FIBRE_OPTIC(element))
471 if (IS_BEAMER_OLD(element))
473 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
474 element = Tile[x][y];
477 if (!IS_FIBRE_OPTIC(element))
479 static int steps_grid_auto = 0;
481 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
482 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
484 if (IS_GRID_STEEL_AUTO(element) ||
485 IS_GRID_WOOD_AUTO(element))
486 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
488 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
490 game_mm.cycle[game_mm.num_cycle].x = x;
491 game_mm.cycle[game_mm.num_cycle].y = y;
495 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
497 int beamer_nr = BEAMER_NR(element);
498 int nr = laser.beamer[beamer_nr][0].num;
500 laser.beamer[beamer_nr][nr].x = x;
501 laser.beamer[beamer_nr][nr].y = y;
502 laser.beamer[beamer_nr][nr].num = 1;
505 else if (IS_PACMAN(element))
509 else if (IS_MCDUFFIN(element) || IS_LASER(element))
511 laser.start_edge.x = x;
512 laser.start_edge.y = y;
513 laser.start_angle = get_element_angle(element);
520 static void InitCycleElements_RotateSingleStep(void)
524 if (game_mm.num_cycle == 0) // no elements to cycle
527 for (i = 0; i < game_mm.num_cycle; i++)
529 int x = game_mm.cycle[i].x;
530 int y = game_mm.cycle[i].y;
531 int step = SIGN(game_mm.cycle[i].steps);
532 int last_element = Tile[x][y];
533 int next_element = get_rotated_element(last_element, step);
535 if (!game_mm.cycle[i].steps)
538 Tile[x][y] = next_element;
540 game_mm.cycle[i].steps -= step;
544 static void InitLaser(void)
546 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
547 int step = (IS_LASER(start_element) ? 4 : 0);
549 LX = laser.start_edge.x * TILEX;
550 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
553 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
555 LY = laser.start_edge.y * TILEY;
556 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
557 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
561 XS = 2 * Step[laser.start_angle].x;
562 YS = 2 * Step[laser.start_angle].y;
564 laser.current_angle = laser.start_angle;
566 laser.num_damages = 0;
568 laser.num_beamers = 0;
569 laser.beamer_edge[0] = 0;
571 laser.dest_element = EL_EMPTY;
574 AddLaserEdge(LX, LY); // set laser starting edge
576 pen_ray = GetPixelFromRGB(window,
577 native_mm_level.laser_red * 0xFF,
578 native_mm_level.laser_green * 0xFF,
579 native_mm_level.laser_blue * 0xFF);
582 void InitGameEngine_MM(void)
588 // initialize laser bitmap to current playfield (screen) size
589 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
590 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
594 // set global game control values
595 game_mm.num_cycle = 0;
596 game_mm.num_pacman = 0;
599 game_mm.energy_left = 0; // later set to "native_mm_level.time"
600 game_mm.kettles_still_needed =
601 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
602 game_mm.lights_still_needed = 0;
603 game_mm.num_keys = 0;
605 game_mm.level_solved = FALSE;
606 game_mm.game_over = FALSE;
607 game_mm.game_over_cause = 0;
609 game_mm.laser_overload_value = 0;
610 game_mm.laser_enabled = FALSE;
612 // set global laser control values (must be set before "InitLaser()")
613 laser.start_edge.x = 0;
614 laser.start_edge.y = 0;
615 laser.start_angle = 0;
617 for (i = 0; i < MAX_NUM_BEAMERS; i++)
618 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
620 laser.overloaded = FALSE;
621 laser.overload_value = 0;
622 laser.fuse_off = FALSE;
623 laser.fuse_x = laser.fuse_y = -1;
625 laser.dest_element = EL_EMPTY;
639 rotate_delay.count = 0;
640 pacman_delay.count = 0;
641 energy_delay.count = 0;
642 overload_delay.count = 0;
644 ClickElement(-1, -1, -1);
646 for (x = 0; x < lev_fieldx; x++)
648 for (y = 0; y < lev_fieldy; y++)
650 Tile[x][y] = Ur[x][y];
651 Hit[x][y] = Box[x][y] = 0;
653 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
654 Store[x][y] = Store2[x][y] = 0;
665 void InitGameActions_MM(void)
667 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
668 int cycle_steps_done = 0;
673 for (i = 0; i <= num_init_game_frames; i++)
675 if (i == num_init_game_frames)
676 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
677 else if (setup.sound_loops)
678 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
680 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
682 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
684 UpdateAndDisplayGameControlValues();
686 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
688 InitCycleElements_RotateSingleStep();
693 AdvanceFrameCounter();
703 if (setup.quick_doors)
710 if (game_mm.kettles_still_needed == 0)
713 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
714 SetTileCursorActive(TRUE);
717 void AddLaserEdge(int lx, int ly)
722 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
724 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
729 laser.edge[laser.num_edges].x = cSX2 + lx;
730 laser.edge[laser.num_edges].y = cSY2 + ly;
736 void AddDamagedField(int ex, int ey)
738 laser.damage[laser.num_damages].is_mirror = FALSE;
739 laser.damage[laser.num_damages].angle = laser.current_angle;
740 laser.damage[laser.num_damages].edge = laser.num_edges;
741 laser.damage[laser.num_damages].x = ex;
742 laser.damage[laser.num_damages].y = ey;
746 static boolean StepBehind(void)
752 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
753 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
755 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
761 static int getMaskFromElement(int element)
763 if (IS_GRID(element))
764 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
765 else if (IS_MCDUFFIN(element))
766 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
767 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
768 return IMG_MM_MASK_RECTANGLE;
770 return IMG_MM_MASK_CIRCLE;
773 static int ScanPixel(void)
778 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
779 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
782 // follow laser beam until it hits something (at least the screen border)
783 while (hit_mask == HIT_MASK_NO_HIT)
789 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
790 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
792 Debug("game:mm:ScanPixel", "touched screen border!");
798 for (i = 0; i < 4; i++)
800 int px = LX + (i % 2) * 2;
801 int py = LY + (i / 2) * 2;
804 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
805 int ly = (py + TILEY) / TILEY - 1; // negative values!
808 if (IN_LEV_FIELD(lx, ly))
810 int element = Tile[lx][ly];
812 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
816 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
818 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
820 pixel = ((element & (1 << pos)) ? 1 : 0);
824 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
826 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
831 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
832 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
835 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
836 hit_mask |= (1 << i);
839 if (hit_mask == HIT_MASK_NO_HIT)
841 // hit nothing -- go on with another step
853 int end = 0, rf = laser.num_edges;
855 // do not scan laser again after the game was lost for whatever reason
856 if (game_mm.game_over)
859 laser.overloaded = FALSE;
860 laser.stops_inside_element = FALSE;
862 DrawLaser(0, DL_LASER_ENABLED);
865 Debug("game:mm:ScanLaser",
866 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
874 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
877 laser.overloaded = TRUE;
882 hit_mask = ScanPixel();
885 Debug("game:mm:ScanLaser",
886 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
890 // hit something -- check out what it was
891 ELX = (LX + XS) / TILEX;
892 ELY = (LY + YS) / TILEY;
895 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
896 hit_mask, LX, LY, ELX, ELY);
899 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
902 laser.dest_element = element;
907 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
909 /* we have hit the top-right and bottom-left element --
910 choose the bottom-left one */
911 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
912 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
913 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
914 ELX = (LX - 2) / TILEX;
915 ELY = (LY + 2) / TILEY;
918 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
920 /* we have hit the top-left and bottom-right element --
921 choose the top-left one */
923 ELX = (LX - 2) / TILEX;
924 ELY = (LY - 2) / TILEY;
928 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
929 hit_mask, LX, LY, ELX, ELY);
932 element = Tile[ELX][ELY];
933 laser.dest_element = element;
936 Debug("game:mm:ScanLaser",
937 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
940 LX % TILEX, LY % TILEY,
945 if (!IN_LEV_FIELD(ELX, ELY))
946 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
950 if (element == EL_EMPTY)
952 if (!HitOnlyAnEdge(hit_mask))
955 else if (element == EL_FUSE_ON)
957 if (HitPolarizer(element, hit_mask))
960 else if (IS_GRID(element) || IS_DF_GRID(element))
962 if (HitPolarizer(element, hit_mask))
965 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
966 element == EL_GATE_STONE || element == EL_GATE_WOOD)
968 if (HitBlock(element, hit_mask))
975 else if (IS_MCDUFFIN(element))
977 if (HitLaserSource(element, hit_mask))
980 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
981 IS_RECEIVER(element))
983 if (HitLaserDestination(element, hit_mask))
986 else if (IS_WALL(element))
988 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
990 if (HitReflectingWalls(element, hit_mask))
995 if (HitAbsorbingWalls(element, hit_mask))
1001 if (HitElement(element, hit_mask))
1006 DrawLaser(rf - 1, DL_LASER_ENABLED);
1007 rf = laser.num_edges;
1011 if (laser.dest_element != Tile[ELX][ELY])
1013 Debug("game:mm:ScanLaser",
1014 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1015 laser.dest_element, Tile[ELX][ELY]);
1019 if (!end && !laser.stops_inside_element && !StepBehind())
1022 Debug("game:mm:ScanLaser", "Go one step back");
1028 AddLaserEdge(LX, LY);
1032 DrawLaser(rf - 1, DL_LASER_ENABLED);
1034 Ct = CT = FrameCounter;
1037 if (!IN_LEV_FIELD(ELX, ELY))
1038 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1042 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1048 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1049 start_edge, num_edges, mode);
1054 Warn("DrawLaserExt: start_edge < 0");
1061 Warn("DrawLaserExt: num_edges < 0");
1067 if (mode == DL_LASER_DISABLED)
1069 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1073 // now draw the laser to the backbuffer and (if enabled) to the screen
1074 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1076 redraw_mask |= REDRAW_FIELD;
1078 if (mode == DL_LASER_ENABLED)
1081 // after the laser was deleted, the "damaged" graphics must be restored
1082 if (laser.num_damages)
1084 int damage_start = 0;
1087 // determine the starting edge, from which graphics need to be restored
1090 for (i = 0; i < laser.num_damages; i++)
1092 if (laser.damage[i].edge == start_edge + 1)
1101 // restore graphics from this starting edge to the end of damage list
1102 for (i = damage_start; i < laser.num_damages; i++)
1104 int lx = laser.damage[i].x;
1105 int ly = laser.damage[i].y;
1106 int element = Tile[lx][ly];
1108 if (Hit[lx][ly] == laser.damage[i].edge)
1109 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1112 if (Box[lx][ly] == laser.damage[i].edge)
1115 if (IS_DRAWABLE(element))
1116 DrawField_MM(lx, ly);
1119 elx = laser.damage[damage_start].x;
1120 ely = laser.damage[damage_start].y;
1121 element = Tile[elx][ely];
1124 if (IS_BEAMER(element))
1128 for (i = 0; i < laser.num_beamers; i++)
1129 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1131 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1132 mode, elx, ely, Hit[elx][ely], start_edge);
1133 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1134 get_element_angle(element), laser.damage[damage_start].angle);
1138 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1139 laser.num_beamers > 0 &&
1140 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1142 // element is outgoing beamer
1143 laser.num_damages = damage_start + 1;
1145 if (IS_BEAMER(element))
1146 laser.current_angle = get_element_angle(element);
1150 // element is incoming beamer or other element
1151 laser.num_damages = damage_start;
1152 laser.current_angle = laser.damage[laser.num_damages].angle;
1157 // no damages but McDuffin himself (who needs to be redrawn anyway)
1159 elx = laser.start_edge.x;
1160 ely = laser.start_edge.y;
1161 element = Tile[elx][ely];
1164 laser.num_edges = start_edge + 1;
1165 if (start_edge == 0)
1166 laser.current_angle = laser.start_angle;
1168 LX = laser.edge[start_edge].x - cSX2;
1169 LY = laser.edge[start_edge].y - cSY2;
1170 XS = 2 * Step[laser.current_angle].x;
1171 YS = 2 * Step[laser.current_angle].y;
1174 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1180 if (IS_BEAMER(element) ||
1181 IS_FIBRE_OPTIC(element) ||
1182 IS_PACMAN(element) ||
1183 IS_POLAR(element) ||
1184 IS_POLAR_CROSS(element) ||
1185 element == EL_FUSE_ON)
1190 Debug("game:mm:DrawLaserExt", "element == %d", element);
1193 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1194 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1198 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1199 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1200 (laser.num_beamers == 0 ||
1201 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1203 // element is incoming beamer or other element
1204 step_size = -step_size;
1209 if (IS_BEAMER(element))
1210 Debug("game:mm:DrawLaserExt",
1211 "start_edge == %d, laser.beamer_edge == %d",
1212 start_edge, laser.beamer_edge);
1215 LX += step_size * XS;
1216 LY += step_size * YS;
1218 else if (element != EL_EMPTY)
1227 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1232 void DrawLaser(int start_edge, int mode)
1234 if (laser.num_edges - start_edge < 0)
1236 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1241 // check if laser is interrupted by beamer element
1242 if (laser.num_beamers > 0 &&
1243 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1245 if (mode == DL_LASER_ENABLED)
1248 int tmp_start_edge = start_edge;
1250 // draw laser segments forward from the start to the last beamer
1251 for (i = 0; i < laser.num_beamers; i++)
1253 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1255 if (tmp_num_edges <= 0)
1259 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1260 i, laser.beamer_edge[i], tmp_start_edge);
1263 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1265 tmp_start_edge = laser.beamer_edge[i];
1268 // draw last segment from last beamer to the end
1269 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1275 int last_num_edges = laser.num_edges;
1276 int num_beamers = laser.num_beamers;
1278 // delete laser segments backward from the end to the first beamer
1279 for (i = num_beamers - 1; i >= 0; i--)
1281 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1283 if (laser.beamer_edge[i] - start_edge <= 0)
1286 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1288 last_num_edges = laser.beamer_edge[i];
1289 laser.num_beamers--;
1293 if (last_num_edges - start_edge <= 0)
1294 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1295 last_num_edges, start_edge);
1298 // special case when rotating first beamer: delete laser edge on beamer
1299 // (but do not start scanning on previous edge to prevent mirror sound)
1300 if (last_num_edges - start_edge == 1 && start_edge > 0)
1301 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1303 // delete first segment from start to the first beamer
1304 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1309 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1312 game_mm.laser_enabled = mode;
1315 void DrawLaser_MM(void)
1317 DrawLaser(0, game_mm.laser_enabled);
1320 boolean HitElement(int element, int hit_mask)
1322 if (HitOnlyAnEdge(hit_mask))
1325 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1326 element = MovingOrBlocked2Element_MM(ELX, ELY);
1329 Debug("game:mm:HitElement", "(1): element == %d", element);
1333 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1334 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1337 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1341 AddDamagedField(ELX, ELY);
1343 // this is more precise: check if laser would go through the center
1344 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1346 // skip the whole element before continuing the scan
1352 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1354 if (LX/TILEX > ELX || LY/TILEY > ELY)
1356 /* skipping scan positions to the right and down skips one scan
1357 position too much, because this is only the top left scan position
1358 of totally four scan positions (plus one to the right, one to the
1359 bottom and one to the bottom right) */
1369 Debug("game:mm:HitElement", "(2): element == %d", element);
1372 if (LX + 5 * XS < 0 ||
1382 Debug("game:mm:HitElement", "(3): element == %d", element);
1385 if (IS_POLAR(element) &&
1386 ((element - EL_POLAR_START) % 2 ||
1387 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1389 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1391 laser.num_damages--;
1396 if (IS_POLAR_CROSS(element) &&
1397 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1399 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1401 laser.num_damages--;
1406 if (!IS_BEAMER(element) &&
1407 !IS_FIBRE_OPTIC(element) &&
1408 !IS_GRID_WOOD(element) &&
1409 element != EL_FUEL_EMPTY)
1412 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1413 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1415 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1418 LX = ELX * TILEX + 14;
1419 LY = ELY * TILEY + 14;
1421 AddLaserEdge(LX, LY);
1424 if (IS_MIRROR(element) ||
1425 IS_MIRROR_FIXED(element) ||
1426 IS_POLAR(element) ||
1427 IS_POLAR_CROSS(element) ||
1428 IS_DF_MIRROR(element) ||
1429 IS_DF_MIRROR_AUTO(element) ||
1430 element == EL_PRISM ||
1431 element == EL_REFRACTOR)
1433 int current_angle = laser.current_angle;
1436 laser.num_damages--;
1438 AddDamagedField(ELX, ELY);
1440 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1443 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1445 if (IS_MIRROR(element) ||
1446 IS_MIRROR_FIXED(element) ||
1447 IS_DF_MIRROR(element) ||
1448 IS_DF_MIRROR_AUTO(element))
1449 laser.current_angle = get_mirrored_angle(laser.current_angle,
1450 get_element_angle(element));
1452 if (element == EL_PRISM || element == EL_REFRACTOR)
1453 laser.current_angle = RND(16);
1455 XS = 2 * Step[laser.current_angle].x;
1456 YS = 2 * Step[laser.current_angle].y;
1458 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1463 LX += step_size * XS;
1464 LY += step_size * YS;
1466 // draw sparkles on mirror
1467 if ((IS_MIRROR(element) ||
1468 IS_MIRROR_FIXED(element) ||
1469 element == EL_PRISM) &&
1470 current_angle != laser.current_angle)
1472 MovDelay[ELX][ELY] = 11; // start animation
1475 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1476 current_angle != laser.current_angle)
1477 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1480 (get_opposite_angle(laser.current_angle) ==
1481 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1483 return (laser.overloaded ? TRUE : FALSE);
1486 if (element == EL_FUEL_FULL)
1488 laser.stops_inside_element = TRUE;
1493 if (element == EL_BOMB || element == EL_MINE)
1495 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1497 if (element == EL_MINE)
1498 laser.overloaded = TRUE;
1501 if (element == EL_KETTLE ||
1502 element == EL_CELL ||
1503 element == EL_KEY ||
1504 element == EL_LIGHTBALL ||
1505 element == EL_PACMAN ||
1508 if (!IS_PACMAN(element))
1511 if (element == EL_PACMAN)
1514 if (element == EL_KETTLE || element == EL_CELL)
1516 if (game_mm.kettles_still_needed > 0)
1517 game_mm.kettles_still_needed--;
1519 game.snapshot.collected_item = TRUE;
1521 if (game_mm.kettles_still_needed == 0)
1525 DrawLaser(0, DL_LASER_ENABLED);
1528 else if (element == EL_KEY)
1532 else if (IS_PACMAN(element))
1534 DeletePacMan(ELX, ELY);
1537 RaiseScoreElement_MM(element);
1542 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1544 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1546 DrawLaser(0, DL_LASER_ENABLED);
1548 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1550 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1551 game_mm.lights_still_needed--;
1555 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1556 game_mm.lights_still_needed++;
1559 DrawField_MM(ELX, ELY);
1560 DrawLaser(0, DL_LASER_ENABLED);
1565 laser.stops_inside_element = TRUE;
1571 Debug("game:mm:HitElement", "(4): element == %d", element);
1574 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1575 laser.num_beamers < MAX_NUM_BEAMERS &&
1576 laser.beamer[BEAMER_NR(element)][1].num)
1578 int beamer_angle = get_element_angle(element);
1579 int beamer_nr = BEAMER_NR(element);
1583 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1586 laser.num_damages--;
1588 if (IS_FIBRE_OPTIC(element) ||
1589 laser.current_angle == get_opposite_angle(beamer_angle))
1593 LX = ELX * TILEX + 14;
1594 LY = ELY * TILEY + 14;
1596 AddLaserEdge(LX, LY);
1597 AddDamagedField(ELX, ELY);
1599 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1602 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1604 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1605 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1606 ELX = laser.beamer[beamer_nr][pos].x;
1607 ELY = laser.beamer[beamer_nr][pos].y;
1608 LX = ELX * TILEX + 14;
1609 LY = ELY * TILEY + 14;
1611 if (IS_BEAMER(element))
1613 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1614 XS = 2 * Step[laser.current_angle].x;
1615 YS = 2 * Step[laser.current_angle].y;
1618 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1620 AddLaserEdge(LX, LY);
1621 AddDamagedField(ELX, ELY);
1623 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1626 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1628 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1633 LX += step_size * XS;
1634 LY += step_size * YS;
1636 laser.num_beamers++;
1645 boolean HitOnlyAnEdge(int hit_mask)
1647 // check if the laser hit only the edge of an element and, if so, go on
1650 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1654 if ((hit_mask == HIT_MASK_TOPLEFT ||
1655 hit_mask == HIT_MASK_TOPRIGHT ||
1656 hit_mask == HIT_MASK_BOTTOMLEFT ||
1657 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1658 laser.current_angle % 4) // angle is not 90°
1662 if (hit_mask == HIT_MASK_TOPLEFT)
1667 else if (hit_mask == HIT_MASK_TOPRIGHT)
1672 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1677 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1683 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1689 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1696 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1702 boolean HitPolarizer(int element, int hit_mask)
1704 if (HitOnlyAnEdge(hit_mask))
1707 if (IS_DF_GRID(element))
1709 int grid_angle = get_element_angle(element);
1712 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1713 grid_angle, laser.current_angle);
1716 AddLaserEdge(LX, LY);
1717 AddDamagedField(ELX, ELY);
1720 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1722 if (laser.current_angle == grid_angle ||
1723 laser.current_angle == get_opposite_angle(grid_angle))
1725 // skip the whole element before continuing the scan
1731 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1733 if (LX/TILEX > ELX || LY/TILEY > ELY)
1735 /* skipping scan positions to the right and down skips one scan
1736 position too much, because this is only the top left scan position
1737 of totally four scan positions (plus one to the right, one to the
1738 bottom and one to the bottom right) */
1744 AddLaserEdge(LX, LY);
1750 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1752 LX / TILEX, LY / TILEY,
1753 LX % TILEX, LY % TILEY);
1758 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1760 return HitReflectingWalls(element, hit_mask);
1764 return HitAbsorbingWalls(element, hit_mask);
1767 else if (IS_GRID_STEEL(element))
1769 return HitReflectingWalls(element, hit_mask);
1771 else // IS_GRID_WOOD
1773 return HitAbsorbingWalls(element, hit_mask);
1779 boolean HitBlock(int element, int hit_mask)
1781 boolean check = FALSE;
1783 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1784 game_mm.num_keys == 0)
1787 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1790 int ex = ELX * TILEX + 14;
1791 int ey = ELY * TILEY + 14;
1795 for (i = 1; i < 32; i++)
1800 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1805 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1806 return HitAbsorbingWalls(element, hit_mask);
1810 AddLaserEdge(LX - XS, LY - YS);
1811 AddDamagedField(ELX, ELY);
1814 Box[ELX][ELY] = laser.num_edges;
1816 return HitReflectingWalls(element, hit_mask);
1819 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1821 int xs = XS / 2, ys = YS / 2;
1822 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1823 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1825 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1826 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1828 laser.overloaded = (element == EL_GATE_STONE);
1833 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1834 (hit_mask == HIT_MASK_TOP ||
1835 hit_mask == HIT_MASK_LEFT ||
1836 hit_mask == HIT_MASK_RIGHT ||
1837 hit_mask == HIT_MASK_BOTTOM))
1838 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1839 hit_mask == HIT_MASK_BOTTOM),
1840 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1841 hit_mask == HIT_MASK_RIGHT));
1842 AddLaserEdge(LX, LY);
1848 if (element == EL_GATE_STONE && Box[ELX][ELY])
1850 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1862 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1864 int xs = XS / 2, ys = YS / 2;
1865 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1866 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1868 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1869 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1871 laser.overloaded = (element == EL_BLOCK_STONE);
1876 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1877 (hit_mask == HIT_MASK_TOP ||
1878 hit_mask == HIT_MASK_LEFT ||
1879 hit_mask == HIT_MASK_RIGHT ||
1880 hit_mask == HIT_MASK_BOTTOM))
1881 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1882 hit_mask == HIT_MASK_BOTTOM),
1883 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1884 hit_mask == HIT_MASK_RIGHT));
1885 AddDamagedField(ELX, ELY);
1887 LX = ELX * TILEX + 14;
1888 LY = ELY * TILEY + 14;
1890 AddLaserEdge(LX, LY);
1892 laser.stops_inside_element = TRUE;
1900 boolean HitLaserSource(int element, int hit_mask)
1902 if (HitOnlyAnEdge(hit_mask))
1905 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1907 laser.overloaded = TRUE;
1912 boolean HitLaserDestination(int element, int hit_mask)
1914 if (HitOnlyAnEdge(hit_mask))
1917 if (element != EL_EXIT_OPEN &&
1918 !(IS_RECEIVER(element) &&
1919 game_mm.kettles_still_needed == 0 &&
1920 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1922 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1927 if (IS_RECEIVER(element) ||
1928 (IS_22_5_ANGLE(laser.current_angle) &&
1929 (ELX != (LX + 6 * XS) / TILEX ||
1930 ELY != (LY + 6 * YS) / TILEY ||
1939 LX = ELX * TILEX + 14;
1940 LY = ELY * TILEY + 14;
1942 laser.stops_inside_element = TRUE;
1945 AddLaserEdge(LX, LY);
1946 AddDamagedField(ELX, ELY);
1948 if (game_mm.lights_still_needed == 0)
1950 game_mm.level_solved = TRUE;
1952 SetTileCursorActive(FALSE);
1958 boolean HitReflectingWalls(int element, int hit_mask)
1960 // check if laser hits side of a wall with an angle that is not 90°
1961 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1962 hit_mask == HIT_MASK_LEFT ||
1963 hit_mask == HIT_MASK_RIGHT ||
1964 hit_mask == HIT_MASK_BOTTOM))
1966 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1971 if (!IS_DF_GRID(element))
1972 AddLaserEdge(LX, LY);
1974 // check if laser hits wall with an angle of 45°
1975 if (!IS_22_5_ANGLE(laser.current_angle))
1977 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1980 laser.current_angle = get_mirrored_angle(laser.current_angle,
1983 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
1986 laser.current_angle = get_mirrored_angle(laser.current_angle,
1990 AddLaserEdge(LX, LY);
1992 XS = 2 * Step[laser.current_angle].x;
1993 YS = 2 * Step[laser.current_angle].y;
1997 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1999 laser.current_angle = get_mirrored_angle(laser.current_angle,
2004 if (!IS_DF_GRID(element))
2005 AddLaserEdge(LX, LY);
2010 if (!IS_DF_GRID(element))
2011 AddLaserEdge(LX, LY + YS / 2);
2014 if (!IS_DF_GRID(element))
2015 AddLaserEdge(LX, LY);
2018 YS = 2 * Step[laser.current_angle].y;
2022 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2024 laser.current_angle = get_mirrored_angle(laser.current_angle,
2029 if (!IS_DF_GRID(element))
2030 AddLaserEdge(LX, LY);
2035 if (!IS_DF_GRID(element))
2036 AddLaserEdge(LX + XS / 2, LY);
2039 if (!IS_DF_GRID(element))
2040 AddLaserEdge(LX, LY);
2043 XS = 2 * Step[laser.current_angle].x;
2049 // reflection at the edge of reflecting DF style wall
2050 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2052 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2053 hit_mask == HIT_MASK_TOPRIGHT) ||
2054 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2055 hit_mask == HIT_MASK_TOPLEFT) ||
2056 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2057 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2058 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2059 hit_mask == HIT_MASK_BOTTOMRIGHT))
2062 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2063 ANG_MIRROR_135 : ANG_MIRROR_45);
2065 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2067 AddDamagedField(ELX, ELY);
2068 AddLaserEdge(LX, LY);
2070 laser.current_angle = get_mirrored_angle(laser.current_angle,
2078 AddLaserEdge(LX, LY);
2084 // reflection inside an edge of reflecting DF style wall
2085 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2087 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2088 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2089 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2090 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2091 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2092 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2093 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2094 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2097 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2098 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2099 ANG_MIRROR_135 : ANG_MIRROR_45);
2101 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2104 AddDamagedField(ELX, ELY);
2107 AddLaserEdge(LX - XS, LY - YS);
2108 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2109 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2111 laser.current_angle = get_mirrored_angle(laser.current_angle,
2119 AddLaserEdge(LX, LY);
2125 // check if laser hits DF style wall with an angle of 90°
2126 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2128 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2129 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2130 (IS_VERT_ANGLE(laser.current_angle) &&
2131 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2133 // laser at last step touched nothing or the same side of the wall
2134 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2136 AddDamagedField(ELX, ELY);
2143 last_hit_mask = hit_mask;
2150 if (!HitOnlyAnEdge(hit_mask))
2152 laser.overloaded = TRUE;
2160 boolean HitAbsorbingWalls(int element, int hit_mask)
2162 if (HitOnlyAnEdge(hit_mask))
2166 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2168 AddLaserEdge(LX - XS, LY - YS);
2175 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2177 AddLaserEdge(LX - XS, LY - YS);
2183 if (IS_WALL_WOOD(element) ||
2184 IS_DF_WALL_WOOD(element) ||
2185 IS_GRID_WOOD(element) ||
2186 IS_GRID_WOOD_FIXED(element) ||
2187 IS_GRID_WOOD_AUTO(element) ||
2188 element == EL_FUSE_ON ||
2189 element == EL_BLOCK_WOOD ||
2190 element == EL_GATE_WOOD)
2192 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2197 if (IS_WALL_ICE(element))
2201 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2202 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2204 // check if laser hits wall with an angle of 90°
2205 if (IS_90_ANGLE(laser.current_angle))
2206 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2208 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2212 for (i = 0; i < 4; i++)
2214 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2215 mask = 15 - (8 >> i);
2216 else if (ABS(XS) == 4 &&
2218 (XS > 0) == (i % 2) &&
2219 (YS < 0) == (i / 2))
2220 mask = 3 + (i / 2) * 9;
2221 else if (ABS(YS) == 4 &&
2223 (XS < 0) == (i % 2) &&
2224 (YS > 0) == (i / 2))
2225 mask = 5 + (i % 2) * 5;
2229 laser.wall_mask = mask;
2231 else if (IS_WALL_AMOEBA(element))
2233 int elx = (LX - 2 * XS) / TILEX;
2234 int ely = (LY - 2 * YS) / TILEY;
2235 int element2 = Tile[elx][ely];
2238 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2240 laser.dest_element = EL_EMPTY;
2248 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2249 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2251 if (IS_90_ANGLE(laser.current_angle))
2252 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2254 laser.dest_element = element2 | EL_WALL_AMOEBA;
2256 laser.wall_mask = mask;
2262 static void OpenExit(int x, int y)
2266 if (!MovDelay[x][y]) // next animation frame
2267 MovDelay[x][y] = 4 * delay;
2269 if (MovDelay[x][y]) // wait some time before next frame
2274 phase = MovDelay[x][y] / delay;
2276 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2277 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2279 if (!MovDelay[x][y])
2281 Tile[x][y] = EL_EXIT_OPEN;
2287 static void OpenSurpriseBall(int x, int y)
2291 if (!MovDelay[x][y]) // next animation frame
2292 MovDelay[x][y] = 50 * delay;
2294 if (MovDelay[x][y]) // wait some time before next frame
2298 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2301 int graphic = el2gfx(Store[x][y]);
2303 int dx = RND(26), dy = RND(26);
2305 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2307 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2308 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2310 MarkTileDirty(x, y);
2313 if (!MovDelay[x][y])
2315 Tile[x][y] = Store[x][y];
2324 static void MeltIce(int x, int y)
2329 if (!MovDelay[x][y]) // next animation frame
2330 MovDelay[x][y] = frames * delay;
2332 if (MovDelay[x][y]) // wait some time before next frame
2335 int wall_mask = Store2[x][y];
2336 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2339 phase = frames - MovDelay[x][y] / delay - 1;
2341 if (!MovDelay[x][y])
2345 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2346 Store[x][y] = Store2[x][y] = 0;
2348 DrawWalls_MM(x, y, Tile[x][y]);
2350 if (Tile[x][y] == EL_WALL_ICE)
2351 Tile[x][y] = EL_EMPTY;
2353 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2354 if (laser.damage[i].is_mirror)
2358 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2360 DrawLaser(0, DL_LASER_DISABLED);
2364 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2366 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2368 laser.redraw = TRUE;
2373 static void GrowAmoeba(int x, int y)
2378 if (!MovDelay[x][y]) // next animation frame
2379 MovDelay[x][y] = frames * delay;
2381 if (MovDelay[x][y]) // wait some time before next frame
2384 int wall_mask = Store2[x][y];
2385 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2388 phase = MovDelay[x][y] / delay;
2390 if (!MovDelay[x][y])
2392 Tile[x][y] = real_element;
2393 Store[x][y] = Store2[x][y] = 0;
2395 DrawWalls_MM(x, y, Tile[x][y]);
2396 DrawLaser(0, DL_LASER_ENABLED);
2398 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2400 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2405 static void DrawFieldAnimated_MM(int x, int y)
2407 int element = Tile[x][y];
2409 if (IS_BLOCKED(x, y))
2414 if (IS_MIRROR(element) ||
2415 IS_MIRROR_FIXED(element) ||
2416 element == EL_PRISM)
2418 if (MovDelay[x][y] != 0) // wait some time before next frame
2422 if (MovDelay[x][y] != 0)
2424 int graphic = IMG_TWINKLE_WHITE;
2425 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2427 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2432 laser.redraw = TRUE;
2435 static void Explode_MM(int x, int y, int phase, int mode)
2437 int num_phase = 9, delay = 2;
2438 int last_phase = num_phase * delay;
2439 int half_phase = (num_phase / 2) * delay;
2441 laser.redraw = TRUE;
2443 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2445 int center_element = Tile[x][y];
2447 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2449 // put moving element to center field (and let it explode there)
2450 center_element = MovingOrBlocked2Element_MM(x, y);
2451 RemoveMovingField_MM(x, y);
2453 Tile[x][y] = center_element;
2456 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2457 Store[x][y] = center_element;
2459 Store[x][y] = EL_EMPTY;
2461 Store2[x][y] = mode;
2462 Tile[x][y] = EL_EXPLODING_OPAQUE;
2463 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2469 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2471 if (phase == half_phase)
2473 Tile[x][y] = EL_EXPLODING_TRANSP;
2475 if (x == ELX && y == ELY)
2479 if (phase == last_phase)
2481 if (Store[x][y] == EL_BOMB)
2483 DrawLaser(0, DL_LASER_DISABLED);
2486 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2487 Store[x][y] = EL_EMPTY;
2489 game_mm.game_over = TRUE;
2490 game_mm.game_over_cause = GAME_OVER_BOMB;
2492 SetTileCursorActive(FALSE);
2494 laser.overloaded = FALSE;
2496 else if (IS_MCDUFFIN(Store[x][y]))
2498 Store[x][y] = EL_EMPTY;
2500 game.restart_game_message = "Bomb killed Mc Duffin! Play it again?";
2503 Tile[x][y] = Store[x][y];
2504 Store[x][y] = Store2[x][y] = 0;
2505 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2510 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2512 int graphic = IMG_MM_DEFAULT_EXPLODING;
2513 int graphic_phase = (phase / delay - 1);
2517 if (Store2[x][y] == EX_KETTLE)
2519 if (graphic_phase < 3)
2521 graphic = IMG_MM_KETTLE_EXPLODING;
2523 else if (graphic_phase < 5)
2529 graphic = IMG_EMPTY;
2533 else if (Store2[x][y] == EX_SHORT)
2535 if (graphic_phase < 4)
2541 graphic = IMG_EMPTY;
2546 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2548 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2549 cFX + x * TILEX, cFY + y * TILEY);
2551 MarkTileDirty(x, y);
2555 static void Bang_MM(int x, int y)
2557 int element = Tile[x][y];
2558 int mode = EX_NORMAL;
2561 DrawLaser(0, DL_LASER_ENABLED);
2580 if (IS_PACMAN(element))
2581 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2582 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2583 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2584 else if (element == EL_KEY)
2585 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2587 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2589 Explode_MM(x, y, EX_PHASE_START, mode);
2592 void TurnRound(int x, int y)
2604 { 0, 0 }, { 0, 0 }, { 0, 0 },
2609 int left, right, back;
2613 { MV_DOWN, MV_UP, MV_RIGHT },
2614 { MV_UP, MV_DOWN, MV_LEFT },
2616 { MV_LEFT, MV_RIGHT, MV_DOWN },
2617 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2618 { MV_RIGHT, MV_LEFT, MV_UP }
2621 int element = Tile[x][y];
2622 int old_move_dir = MovDir[x][y];
2623 int right_dir = turn[old_move_dir].right;
2624 int back_dir = turn[old_move_dir].back;
2625 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2626 int right_x = x + right_dx, right_y = y + right_dy;
2628 if (element == EL_PACMAN)
2630 boolean can_turn_right = FALSE;
2632 if (IN_LEV_FIELD(right_x, right_y) &&
2633 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2634 can_turn_right = TRUE;
2637 MovDir[x][y] = right_dir;
2639 MovDir[x][y] = back_dir;
2645 static void StartMoving_MM(int x, int y)
2647 int element = Tile[x][y];
2652 if (CAN_MOVE(element))
2656 if (MovDelay[x][y]) // wait some time before next movement
2664 // now make next step
2666 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2668 if (element == EL_PACMAN &&
2669 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2670 !ObjHit(newx, newy, HIT_POS_CENTER))
2672 Store[newx][newy] = Tile[newx][newy];
2673 Tile[newx][newy] = EL_EMPTY;
2675 DrawField_MM(newx, newy);
2677 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2678 ObjHit(newx, newy, HIT_POS_CENTER))
2680 // object was running against a wall
2687 InitMovingField_MM(x, y, MovDir[x][y]);
2691 ContinueMoving_MM(x, y);
2694 static void ContinueMoving_MM(int x, int y)
2696 int element = Tile[x][y];
2697 int direction = MovDir[x][y];
2698 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2699 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2700 int horiz_move = (dx!=0);
2701 int newx = x + dx, newy = y + dy;
2702 int step = (horiz_move ? dx : dy) * TILEX / 8;
2704 MovPos[x][y] += step;
2706 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2708 Tile[x][y] = EL_EMPTY;
2709 Tile[newx][newy] = element;
2711 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2712 MovDelay[newx][newy] = 0;
2714 if (!CAN_MOVE(element))
2715 MovDir[newx][newy] = 0;
2718 DrawField_MM(newx, newy);
2720 Stop[newx][newy] = TRUE;
2722 if (element == EL_PACMAN)
2724 if (Store[newx][newy] == EL_BOMB)
2725 Bang_MM(newx, newy);
2727 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2728 (LX + 2 * XS) / TILEX == newx &&
2729 (LY + 2 * YS) / TILEY == newy)
2736 else // still moving on
2741 laser.redraw = TRUE;
2744 boolean ClickElement(int x, int y, int button)
2746 static DelayCounter click_delay = { CLICK_DELAY };
2747 static boolean new_button = TRUE;
2748 boolean element_clicked = FALSE;
2753 // initialize static variables
2754 click_delay.count = 0;
2755 click_delay.value = CLICK_DELAY;
2761 // do not rotate objects hit by the laser after the game was solved
2762 if (game_mm.level_solved && Hit[x][y])
2765 if (button == MB_RELEASED)
2768 click_delay.value = CLICK_DELAY;
2770 // release eventually hold auto-rotating mirror
2771 RotateMirror(x, y, MB_RELEASED);
2776 if (!FrameReached(&click_delay) && !new_button)
2779 if (button == MB_MIDDLEBUTTON) // middle button has no function
2782 if (!IN_LEV_FIELD(x, y))
2785 if (Tile[x][y] == EL_EMPTY)
2788 element = Tile[x][y];
2790 if (IS_MIRROR(element) ||
2791 IS_BEAMER(element) ||
2792 IS_POLAR(element) ||
2793 IS_POLAR_CROSS(element) ||
2794 IS_DF_MIRROR(element) ||
2795 IS_DF_MIRROR_AUTO(element))
2797 RotateMirror(x, y, button);
2799 element_clicked = TRUE;
2801 else if (IS_MCDUFFIN(element))
2803 if (!laser.fuse_off)
2805 DrawLaser(0, DL_LASER_DISABLED);
2812 element = get_rotated_element(element, BUTTON_ROTATION(button));
2813 laser.start_angle = get_element_angle(element);
2817 Tile[x][y] = element;
2824 if (!laser.fuse_off)
2827 element_clicked = TRUE;
2829 else if (element == EL_FUSE_ON && laser.fuse_off)
2831 if (x != laser.fuse_x || y != laser.fuse_y)
2834 laser.fuse_off = FALSE;
2835 laser.fuse_x = laser.fuse_y = -1;
2837 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2840 element_clicked = TRUE;
2842 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2844 laser.fuse_off = TRUE;
2847 laser.overloaded = FALSE;
2849 DrawLaser(0, DL_LASER_DISABLED);
2850 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2852 element_clicked = TRUE;
2854 else if (element == EL_LIGHTBALL)
2857 RaiseScoreElement_MM(element);
2858 DrawLaser(0, DL_LASER_ENABLED);
2860 element_clicked = TRUE;
2863 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2866 return element_clicked;
2869 void RotateMirror(int x, int y, int button)
2871 if (button == MB_RELEASED)
2873 // release eventually hold auto-rotating mirror
2880 if (IS_MIRROR(Tile[x][y]) ||
2881 IS_POLAR_CROSS(Tile[x][y]) ||
2882 IS_POLAR(Tile[x][y]) ||
2883 IS_BEAMER(Tile[x][y]) ||
2884 IS_DF_MIRROR(Tile[x][y]) ||
2885 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2886 IS_GRID_WOOD_AUTO(Tile[x][y]))
2888 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2890 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2892 if (button == MB_LEFTBUTTON)
2894 // left mouse button only for manual adjustment, no auto-rotating;
2895 // freeze mirror for until mouse button released
2899 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2901 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2905 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2907 int edge = Hit[x][y];
2913 DrawLaser(edge - 1, DL_LASER_DISABLED);
2917 else if (ObjHit(x, y, HIT_POS_CENTER))
2919 int edge = Hit[x][y];
2923 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2928 DrawLaser(edge - 1, DL_LASER_DISABLED);
2935 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2940 if ((IS_BEAMER(Tile[x][y]) ||
2941 IS_POLAR(Tile[x][y]) ||
2942 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2946 if (IS_BEAMER(Tile[x][y]))
2949 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2950 LX, LY, laser.beamer_edge, laser.beamer[1].num);
2962 DrawLaser(0, DL_LASER_ENABLED);
2966 static void AutoRotateMirrors(void)
2970 if (!FrameReached(&rotate_delay))
2973 for (x = 0; x < lev_fieldx; x++)
2975 for (y = 0; y < lev_fieldy; y++)
2977 int element = Tile[x][y];
2979 // do not rotate objects hit by the laser after the game was solved
2980 if (game_mm.level_solved && Hit[x][y])
2983 if (IS_DF_MIRROR_AUTO(element) ||
2984 IS_GRID_WOOD_AUTO(element) ||
2985 IS_GRID_STEEL_AUTO(element) ||
2986 element == EL_REFRACTOR)
2987 RotateMirror(x, y, MB_RIGHTBUTTON);
2992 boolean ObjHit(int obx, int oby, int bits)
2999 if (bits & HIT_POS_CENTER)
3001 if (CheckLaserPixel(cSX + obx + 15,
3006 if (bits & HIT_POS_EDGE)
3008 for (i = 0; i < 4; i++)
3009 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3010 cSY + oby + 31 * (i / 2)))
3014 if (bits & HIT_POS_BETWEEN)
3016 for (i = 0; i < 4; i++)
3017 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3018 cSY + 4 + oby + 22 * (i / 2)))
3025 void DeletePacMan(int px, int py)
3031 if (game_mm.num_pacman <= 1)
3033 game_mm.num_pacman = 0;
3037 for (i = 0; i < game_mm.num_pacman; i++)
3038 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3041 game_mm.num_pacman--;
3043 for (j = i; j < game_mm.num_pacman; j++)
3045 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3046 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3047 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3051 void ColorCycling(void)
3053 static int CC, Cc = 0;
3055 static int color, old = 0xF00, new = 0x010, mult = 1;
3056 static unsigned short red, green, blue;
3058 if (color_status == STATIC_COLORS)
3063 if (CC < Cc || CC > Cc + 2)
3067 color = old + new * mult;
3073 if (ABS(mult) == 16)
3083 red = 0x0e00 * ((color & 0xF00) >> 8);
3084 green = 0x0e00 * ((color & 0x0F0) >> 4);
3085 blue = 0x0e00 * ((color & 0x00F));
3086 SetRGB(pen_magicolor[0], red, green, blue);
3088 red = 0x1111 * ((color & 0xF00) >> 8);
3089 green = 0x1111 * ((color & 0x0F0) >> 4);
3090 blue = 0x1111 * ((color & 0x00F));
3091 SetRGB(pen_magicolor[1], red, green, blue);
3095 static void GameActions_MM_Ext(void)
3102 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3105 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3107 element = Tile[x][y];
3109 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3110 StartMoving_MM(x, y);
3111 else if (IS_MOVING(x, y))
3112 ContinueMoving_MM(x, y);
3113 else if (IS_EXPLODING(element))
3114 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3115 else if (element == EL_EXIT_OPENING)
3117 else if (element == EL_GRAY_BALL_OPENING)
3118 OpenSurpriseBall(x, y);
3119 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3121 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3124 DrawFieldAnimated_MM(x, y);
3127 AutoRotateMirrors();
3130 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3132 // redraw after Explode_MM() ...
3134 DrawLaser(0, DL_LASER_ENABLED);
3135 laser.redraw = FALSE;
3140 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3144 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3146 DrawLaser(0, DL_LASER_DISABLED);
3151 if (FrameReached(&energy_delay))
3153 if (game_mm.energy_left > 0)
3155 game_mm.energy_left--;
3157 redraw_mask |= REDRAW_DOOR_1;
3159 else if (game.time_limit && !game_mm.game_over)
3163 for (i = 15; i >= 0; i--)
3166 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3168 pen_ray = GetPixelFromRGB(window,
3169 native_mm_level.laser_red * 0x11 * i,
3170 native_mm_level.laser_green * 0x11 * i,
3171 native_mm_level.laser_blue * 0x11 * i);
3173 DrawLaser(0, DL_LASER_ENABLED);
3175 Delay_WithScreenUpdates(50);
3178 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3183 DrawLaser(0, DL_LASER_DISABLED);
3184 game_mm.game_over = TRUE;
3185 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3187 SetTileCursorActive(FALSE);
3189 game.restart_game_message = "Out of magic energy! Play it again?";
3195 element = laser.dest_element;
3198 if (element != Tile[ELX][ELY])
3200 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3201 element, Tile[ELX][ELY]);
3205 if (!laser.overloaded && laser.overload_value == 0 &&
3206 element != EL_BOMB &&
3207 element != EL_MINE &&
3208 element != EL_BALL_GRAY &&
3209 element != EL_BLOCK_STONE &&
3210 element != EL_BLOCK_WOOD &&
3211 element != EL_FUSE_ON &&
3212 element != EL_FUEL_FULL &&
3213 !IS_WALL_ICE(element) &&
3214 !IS_WALL_AMOEBA(element))
3217 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3219 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3220 (!laser.overloaded && laser.overload_value > 0)) &&
3221 FrameReached(&overload_delay))
3223 if (laser.overloaded)
3224 laser.overload_value++;
3226 laser.overload_value--;
3228 if (game_mm.cheat_no_overload)
3230 laser.overloaded = FALSE;
3231 laser.overload_value = 0;
3234 game_mm.laser_overload_value = laser.overload_value;
3236 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3238 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3239 int color_down = 0xFF - color_up;
3242 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3243 (15 - (laser.overload_value / 6)) * color_scale);
3246 GetPixelFromRGB(window,
3247 (native_mm_level.laser_red ? 0xFF : color_up),
3248 (native_mm_level.laser_green ? color_down : 0x00),
3249 (native_mm_level.laser_blue ? color_down : 0x00));
3251 DrawLaser(0, DL_LASER_ENABLED);
3254 if (!laser.overloaded)
3255 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3256 else if (setup.sound_loops)
3257 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3259 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3261 if (laser.overloaded)
3264 BlitBitmap(pix[PIX_DOOR], drawto,
3265 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3266 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3267 - laser.overload_value,
3268 OVERLOAD_XSIZE, laser.overload_value,
3269 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3270 - laser.overload_value);
3272 redraw_mask |= REDRAW_DOOR_1;
3277 BlitBitmap(pix[PIX_DOOR], drawto,
3278 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3279 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3280 DX_OVERLOAD, DY_OVERLOAD);
3282 redraw_mask |= REDRAW_DOOR_1;
3285 if (laser.overload_value == MAX_LASER_OVERLOAD)
3289 UpdateAndDisplayGameControlValues();
3291 for (i = 15; i >= 0; i--)
3294 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3297 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3299 DrawLaser(0, DL_LASER_ENABLED);
3301 Delay_WithScreenUpdates(50);
3304 DrawLaser(0, DL_LASER_DISABLED);
3306 game_mm.game_over = TRUE;
3307 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3309 SetTileCursorActive(FALSE);
3311 game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?";
3322 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3324 if (game_mm.cheat_no_explosion)
3329 laser.dest_element = EL_EXPLODING_OPAQUE;
3334 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3336 laser.fuse_off = TRUE;
3340 DrawLaser(0, DL_LASER_DISABLED);
3341 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3344 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3346 static int new_elements[] =
3349 EL_MIRROR_FIXED_START,
3351 EL_POLAR_CROSS_START,
3357 int num_new_elements = sizeof(new_elements) / sizeof(int);
3358 int new_element = new_elements[RND(num_new_elements)];
3360 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3361 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3363 // !!! CHECK AGAIN: Laser on Polarizer !!!
3374 element = EL_MIRROR_START + RND(16);
3380 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3387 element = (rnd == 0 ? EL_FUSE_ON :
3388 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3389 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3390 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3391 EL_MIRROR_FIXED_START + rnd - 25);
3396 graphic = el2gfx(element);
3398 for (i = 0; i < 50; i++)
3404 BlitBitmap(pix[PIX_BACK], drawto,
3405 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3406 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3407 SX + ELX * TILEX + x,
3408 SY + ELY * TILEY + y);
3410 MarkTileDirty(ELX, ELY);
3413 DrawLaser(0, DL_LASER_ENABLED);
3415 Delay_WithScreenUpdates(50);
3418 Tile[ELX][ELY] = element;
3419 DrawField_MM(ELX, ELY);
3422 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3425 // above stuff: GRAY BALL -> PRISM !!!
3427 LX = ELX * TILEX + 14;
3428 LY = ELY * TILEY + 14;
3429 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3436 laser.num_edges -= 2;
3437 laser.num_damages--;
3441 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3442 if (laser.damage[i].is_mirror)
3446 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3448 DrawLaser(0, DL_LASER_DISABLED);
3450 DrawLaser(0, DL_LASER_DISABLED);
3459 if (IS_WALL_ICE(element) && CT > 50)
3461 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3464 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3465 Store[ELX][ELY] = EL_WALL_ICE;
3466 Store2[ELX][ELY] = laser.wall_mask;
3468 laser.dest_element = Tile[ELX][ELY];
3473 for (i = 0; i < 5; i++)
3479 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3483 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3485 Delay_WithScreenUpdates(100);
3488 if (Tile[ELX][ELY] == EL_WALL_ICE)
3489 Tile[ELX][ELY] = EL_EMPTY;
3493 LX = laser.edge[laser.num_edges].x - cSX2;
3494 LY = laser.edge[laser.num_edges].y - cSY2;
3497 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3498 if (laser.damage[i].is_mirror)
3502 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3504 DrawLaser(0, DL_LASER_DISABLED);
3511 if (IS_WALL_AMOEBA(element) && CT > 60)
3513 int k1, k2, k3, dx, dy, de, dm;
3514 int element2 = Tile[ELX][ELY];
3516 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3519 for (i = laser.num_damages - 1; i >= 0; i--)
3520 if (laser.damage[i].is_mirror)
3523 r = laser.num_edges;
3524 d = laser.num_damages;
3531 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3534 DrawLaser(0, DL_LASER_ENABLED);
3537 x = laser.damage[k1].x;
3538 y = laser.damage[k1].y;
3543 for (i = 0; i < 4; i++)
3545 if (laser.wall_mask & (1 << i))
3547 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3548 cSY + ELY * TILEY + 31 * (i / 2)))
3551 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3552 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3559 for (i = 0; i < 4; i++)
3561 if (laser.wall_mask & (1 << i))
3563 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3564 cSY + ELY * TILEY + 31 * (i / 2)))
3571 if (laser.num_beamers > 0 ||
3572 k1 < 1 || k2 < 4 || k3 < 4 ||
3573 CheckLaserPixel(cSX + ELX * TILEX + 14,
3574 cSY + ELY * TILEY + 14))
3576 laser.num_edges = r;
3577 laser.num_damages = d;
3579 DrawLaser(0, DL_LASER_DISABLED);
3582 Tile[ELX][ELY] = element | laser.wall_mask;
3586 de = Tile[ELX][ELY];
3587 dm = laser.wall_mask;
3591 int x = ELX, y = ELY;
3592 int wall_mask = laser.wall_mask;
3595 DrawLaser(0, DL_LASER_ENABLED);
3597 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3599 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3600 Store[x][y] = EL_WALL_AMOEBA;
3601 Store2[x][y] = wall_mask;
3607 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3609 DrawLaser(0, DL_LASER_ENABLED);
3611 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3613 for (i = 4; i >= 0; i--)
3615 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3618 Delay_WithScreenUpdates(20);
3621 DrawLaser(0, DL_LASER_ENABLED);
3626 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3627 laser.stops_inside_element && CT > native_mm_level.time_block)
3632 if (ABS(XS) > ABS(YS))
3639 for (i = 0; i < 4; i++)
3646 x = ELX + Step[k * 4].x;
3647 y = ELY + Step[k * 4].y;
3649 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3652 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3660 laser.overloaded = (element == EL_BLOCK_STONE);
3665 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3668 Tile[x][y] = element;
3670 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3673 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3675 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3676 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3684 if (element == EL_FUEL_FULL && CT > 10)
3686 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3689 BlitBitmap(pix[PIX_DOOR], drawto,
3690 DOOR_GFX_PAGEX4 + XX_ENERGY,
3691 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3692 ENERGY_XSIZE, i, DX_ENERGY,
3693 DY_ENERGY + ENERGY_YSIZE - i);
3696 redraw_mask |= REDRAW_DOOR_1;
3699 Delay_WithScreenUpdates(20);
3702 game_mm.energy_left = MAX_LASER_ENERGY;
3703 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3704 DrawField_MM(ELX, ELY);
3706 DrawLaser(0, DL_LASER_ENABLED);
3714 void GameActions_MM(struct MouseActionInfo action)
3716 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3717 boolean button_released = (action.button == MB_RELEASED);
3719 GameActions_MM_Ext();
3721 CheckSingleStepMode_MM(element_clicked, button_released);
3724 void MovePacMen(void)
3726 int mx, my, ox, oy, nx, ny;
3730 if (++pacman_nr >= game_mm.num_pacman)
3733 game_mm.pacman[pacman_nr].dir--;
3735 for (l = 1; l < 5; l++)
3737 game_mm.pacman[pacman_nr].dir++;
3739 if (game_mm.pacman[pacman_nr].dir > 4)
3740 game_mm.pacman[pacman_nr].dir = 1;
3742 if (game_mm.pacman[pacman_nr].dir % 2)
3745 my = game_mm.pacman[pacman_nr].dir - 2;
3750 mx = 3 - game_mm.pacman[pacman_nr].dir;
3753 ox = game_mm.pacman[pacman_nr].x;
3754 oy = game_mm.pacman[pacman_nr].y;
3757 element = Tile[nx][ny];
3759 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3762 if (!IS_EATABLE4PACMAN(element))
3765 if (ObjHit(nx, ny, HIT_POS_CENTER))
3768 Tile[ox][oy] = EL_EMPTY;
3770 EL_PACMAN_RIGHT - 1 +
3771 (game_mm.pacman[pacman_nr].dir - 1 +
3772 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3774 game_mm.pacman[pacman_nr].x = nx;
3775 game_mm.pacman[pacman_nr].y = ny;
3777 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3779 if (element != EL_EMPTY)
3781 int graphic = el2gfx(Tile[nx][ny]);
3786 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3789 ox = cSX + ox * TILEX;
3790 oy = cSY + oy * TILEY;
3792 for (i = 1; i < 33; i += 2)
3793 BlitBitmap(bitmap, window,
3794 src_x, src_y, TILEX, TILEY,
3795 ox + i * mx, oy + i * my);
3796 Ct = Ct + FrameCounter - CT;
3799 DrawField_MM(nx, ny);
3802 if (!laser.fuse_off)
3804 DrawLaser(0, DL_LASER_ENABLED);
3806 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3808 AddDamagedField(nx, ny);
3810 laser.damage[laser.num_damages - 1].edge = 0;
3814 if (element == EL_BOMB)
3815 DeletePacMan(nx, ny);
3817 if (IS_WALL_AMOEBA(element) &&
3818 (LX + 2 * XS) / TILEX == nx &&
3819 (LY + 2 * YS) / TILEY == ny)
3829 static void InitMovingField_MM(int x, int y, int direction)
3831 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3832 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3834 MovDir[x][y] = direction;
3835 MovDir[newx][newy] = direction;
3837 if (Tile[newx][newy] == EL_EMPTY)
3838 Tile[newx][newy] = EL_BLOCKED;
3841 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3843 int direction = MovDir[x][y];
3844 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3845 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3851 static void Blocked2Moving_MM(int x, int y,
3852 int *comes_from_x, int *comes_from_y)
3854 int oldx = x, oldy = y;
3855 int direction = MovDir[x][y];
3857 if (direction == MV_LEFT)
3859 else if (direction == MV_RIGHT)
3861 else if (direction == MV_UP)
3863 else if (direction == MV_DOWN)
3866 *comes_from_x = oldx;
3867 *comes_from_y = oldy;
3870 static int MovingOrBlocked2Element_MM(int x, int y)
3872 int element = Tile[x][y];
3874 if (element == EL_BLOCKED)
3878 Blocked2Moving_MM(x, y, &oldx, &oldy);
3880 return Tile[oldx][oldy];
3887 static void RemoveField(int x, int y)
3889 Tile[x][y] = EL_EMPTY;
3896 static void RemoveMovingField_MM(int x, int y)
3898 int oldx = x, oldy = y, newx = x, newy = y;
3900 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3903 if (IS_MOVING(x, y))
3905 Moving2Blocked_MM(x, y, &newx, &newy);
3906 if (Tile[newx][newy] != EL_BLOCKED)
3909 else if (Tile[x][y] == EL_BLOCKED)
3911 Blocked2Moving_MM(x, y, &oldx, &oldy);
3912 if (!IS_MOVING(oldx, oldy))
3916 Tile[oldx][oldy] = EL_EMPTY;
3917 Tile[newx][newy] = EL_EMPTY;
3918 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3919 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3921 DrawLevelField_MM(oldx, oldy);
3922 DrawLevelField_MM(newx, newy);
3925 void PlaySoundLevel(int x, int y, int sound_nr)
3927 int sx = SCREENX(x), sy = SCREENY(y);
3929 int silence_distance = 8;
3931 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3932 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3935 if (!IN_LEV_FIELD(x, y) ||
3936 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3937 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3940 volume = SOUND_MAX_VOLUME;
3943 stereo = (sx - SCR_FIELDX/2) * 12;
3945 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3946 if (stereo > SOUND_MAX_RIGHT)
3947 stereo = SOUND_MAX_RIGHT;
3948 if (stereo < SOUND_MAX_LEFT)
3949 stereo = SOUND_MAX_LEFT;
3952 if (!IN_SCR_FIELD(sx, sy))
3954 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3955 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3957 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3960 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3963 static void RaiseScore_MM(int value)
3965 game_mm.score += value;
3968 void RaiseScoreElement_MM(int element)
3973 case EL_PACMAN_RIGHT:
3975 case EL_PACMAN_LEFT:
3976 case EL_PACMAN_DOWN:
3977 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3981 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3986 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3990 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3999 // ----------------------------------------------------------------------------
4000 // Mirror Magic game engine snapshot handling functions
4001 // ----------------------------------------------------------------------------
4003 void SaveEngineSnapshotValues_MM(void)
4007 engine_snapshot_mm.game_mm = game_mm;
4008 engine_snapshot_mm.laser = laser;
4010 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4012 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4014 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4015 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4016 engine_snapshot_mm.Box[x][y] = Box[x][y];
4017 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4018 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4022 engine_snapshot_mm.LX = LX;
4023 engine_snapshot_mm.LY = LY;
4024 engine_snapshot_mm.XS = XS;
4025 engine_snapshot_mm.YS = YS;
4026 engine_snapshot_mm.ELX = ELX;
4027 engine_snapshot_mm.ELY = ELY;
4028 engine_snapshot_mm.CT = CT;
4029 engine_snapshot_mm.Ct = Ct;
4031 engine_snapshot_mm.last_LX = last_LX;
4032 engine_snapshot_mm.last_LY = last_LY;
4033 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4034 engine_snapshot_mm.hold_x = hold_x;
4035 engine_snapshot_mm.hold_y = hold_y;
4036 engine_snapshot_mm.pacman_nr = pacman_nr;
4038 engine_snapshot_mm.rotate_delay = rotate_delay;
4039 engine_snapshot_mm.pacman_delay = pacman_delay;
4040 engine_snapshot_mm.energy_delay = energy_delay;
4041 engine_snapshot_mm.overload_delay = overload_delay;
4044 void LoadEngineSnapshotValues_MM(void)
4048 // stored engine snapshot buffers already restored at this point
4050 game_mm = engine_snapshot_mm.game_mm;
4051 laser = engine_snapshot_mm.laser;
4053 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4055 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4057 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4058 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4059 Box[x][y] = engine_snapshot_mm.Box[x][y];
4060 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4061 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4065 LX = engine_snapshot_mm.LX;
4066 LY = engine_snapshot_mm.LY;
4067 XS = engine_snapshot_mm.XS;
4068 YS = engine_snapshot_mm.YS;
4069 ELX = engine_snapshot_mm.ELX;
4070 ELY = engine_snapshot_mm.ELY;
4071 CT = engine_snapshot_mm.CT;
4072 Ct = engine_snapshot_mm.Ct;
4074 last_LX = engine_snapshot_mm.last_LX;
4075 last_LY = engine_snapshot_mm.last_LY;
4076 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4077 hold_x = engine_snapshot_mm.hold_x;
4078 hold_y = engine_snapshot_mm.hold_y;
4079 pacman_nr = engine_snapshot_mm.pacman_nr;
4081 rotate_delay = engine_snapshot_mm.rotate_delay;
4082 pacman_delay = engine_snapshot_mm.pacman_delay;
4083 energy_delay = engine_snapshot_mm.energy_delay;
4084 overload_delay = engine_snapshot_mm.overload_delay;
4086 RedrawPlayfield_MM();
4089 static int getAngleFromTouchDelta(int dx, int dy, int base)
4091 double pi = 3.141592653;
4092 double rad = atan2((double)-dy, (double)dx);
4093 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4094 double deg = rad2 * 180.0 / pi;
4096 return (int)(deg * base / 360.0 + 0.5) % base;
4099 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4101 // calculate start (source) position to be at the middle of the tile
4102 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4103 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4104 int dx = dst_mx - src_mx;
4105 int dy = dst_my - src_my;
4114 if (!IN_LEV_FIELD(x, y))
4117 element = Tile[x][y];
4119 if (!IS_MCDUFFIN(element) &&
4120 !IS_MIRROR(element) &&
4121 !IS_BEAMER(element) &&
4122 !IS_POLAR(element) &&
4123 !IS_POLAR_CROSS(element) &&
4124 !IS_DF_MIRROR(element))
4127 angle_old = get_element_angle(element);
4129 if (IS_MCDUFFIN(element))
4131 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4132 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4133 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4134 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4137 else if (IS_MIRROR(element) ||
4138 IS_DF_MIRROR(element))
4140 for (i = 0; i < laser.num_damages; i++)
4142 if (laser.damage[i].x == x &&
4143 laser.damage[i].y == y &&
4144 ObjHit(x, y, HIT_POS_CENTER))
4146 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4147 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4154 if (angle_new == -1)
4156 if (IS_MIRROR(element) ||
4157 IS_DF_MIRROR(element) ||
4161 if (IS_POLAR_CROSS(element))
4164 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4167 button = (angle_new == angle_old ? 0 :
4168 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4169 MB_LEFTBUTTON : MB_RIGHTBUTTON);