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);
2960 DrawLaser(0, DL_LASER_ENABLED);
2964 static void AutoRotateMirrors(void)
2968 if (!FrameReached(&rotate_delay))
2971 for (x = 0; x < lev_fieldx; x++)
2973 for (y = 0; y < lev_fieldy; y++)
2975 int element = Tile[x][y];
2977 // do not rotate objects hit by the laser after the game was solved
2978 if (game_mm.level_solved && Hit[x][y])
2981 if (IS_DF_MIRROR_AUTO(element) ||
2982 IS_GRID_WOOD_AUTO(element) ||
2983 IS_GRID_STEEL_AUTO(element) ||
2984 element == EL_REFRACTOR)
2985 RotateMirror(x, y, MB_RIGHTBUTTON);
2990 boolean ObjHit(int obx, int oby, int bits)
2997 if (bits & HIT_POS_CENTER)
2999 if (CheckLaserPixel(cSX + obx + 15,
3004 if (bits & HIT_POS_EDGE)
3006 for (i = 0; i < 4; i++)
3007 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3008 cSY + oby + 31 * (i / 2)))
3012 if (bits & HIT_POS_BETWEEN)
3014 for (i = 0; i < 4; i++)
3015 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3016 cSY + 4 + oby + 22 * (i / 2)))
3023 void DeletePacMan(int px, int py)
3029 if (game_mm.num_pacman <= 1)
3031 game_mm.num_pacman = 0;
3035 for (i = 0; i < game_mm.num_pacman; i++)
3036 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3039 game_mm.num_pacman--;
3041 for (j = i; j < game_mm.num_pacman; j++)
3043 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3044 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3045 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3049 void ColorCycling(void)
3051 static int CC, Cc = 0;
3053 static int color, old = 0xF00, new = 0x010, mult = 1;
3054 static unsigned short red, green, blue;
3056 if (color_status == STATIC_COLORS)
3061 if (CC < Cc || CC > Cc + 2)
3065 color = old + new * mult;
3071 if (ABS(mult) == 16)
3081 red = 0x0e00 * ((color & 0xF00) >> 8);
3082 green = 0x0e00 * ((color & 0x0F0) >> 4);
3083 blue = 0x0e00 * ((color & 0x00F));
3084 SetRGB(pen_magicolor[0], red, green, blue);
3086 red = 0x1111 * ((color & 0xF00) >> 8);
3087 green = 0x1111 * ((color & 0x0F0) >> 4);
3088 blue = 0x1111 * ((color & 0x00F));
3089 SetRGB(pen_magicolor[1], red, green, blue);
3093 static void GameActions_MM_Ext(void)
3100 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3103 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3105 element = Tile[x][y];
3107 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3108 StartMoving_MM(x, y);
3109 else if (IS_MOVING(x, y))
3110 ContinueMoving_MM(x, y);
3111 else if (IS_EXPLODING(element))
3112 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3113 else if (element == EL_EXIT_OPENING)
3115 else if (element == EL_GRAY_BALL_OPENING)
3116 OpenSurpriseBall(x, y);
3117 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3119 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3122 DrawFieldAnimated_MM(x, y);
3125 AutoRotateMirrors();
3128 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3130 // redraw after Explode_MM() ...
3132 DrawLaser(0, DL_LASER_ENABLED);
3133 laser.redraw = FALSE;
3138 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3142 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3144 DrawLaser(0, DL_LASER_DISABLED);
3149 if (FrameReached(&energy_delay))
3151 if (game_mm.energy_left > 0)
3153 game_mm.energy_left--;
3155 redraw_mask |= REDRAW_DOOR_1;
3157 else if (game.time_limit && !game_mm.game_over)
3161 for (i = 15; i >= 0; i--)
3164 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3166 pen_ray = GetPixelFromRGB(window,
3167 native_mm_level.laser_red * 0x11 * i,
3168 native_mm_level.laser_green * 0x11 * i,
3169 native_mm_level.laser_blue * 0x11 * i);
3171 DrawLaser(0, DL_LASER_ENABLED);
3173 Delay_WithScreenUpdates(50);
3176 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3181 DrawLaser(0, DL_LASER_DISABLED);
3182 game_mm.game_over = TRUE;
3183 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3185 SetTileCursorActive(FALSE);
3187 game.restart_game_message = "Out of magic energy! Play it again?";
3193 element = laser.dest_element;
3196 if (element != Tile[ELX][ELY])
3198 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3199 element, Tile[ELX][ELY]);
3203 if (!laser.overloaded && laser.overload_value == 0 &&
3204 element != EL_BOMB &&
3205 element != EL_MINE &&
3206 element != EL_BALL_GRAY &&
3207 element != EL_BLOCK_STONE &&
3208 element != EL_BLOCK_WOOD &&
3209 element != EL_FUSE_ON &&
3210 element != EL_FUEL_FULL &&
3211 !IS_WALL_ICE(element) &&
3212 !IS_WALL_AMOEBA(element))
3215 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3217 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3218 (!laser.overloaded && laser.overload_value > 0)) &&
3219 FrameReached(&overload_delay))
3221 if (laser.overloaded)
3222 laser.overload_value++;
3224 laser.overload_value--;
3226 if (game_mm.cheat_no_overload)
3228 laser.overloaded = FALSE;
3229 laser.overload_value = 0;
3232 game_mm.laser_overload_value = laser.overload_value;
3234 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3236 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3237 int color_down = 0xFF - color_up;
3240 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3241 (15 - (laser.overload_value / 6)) * color_scale);
3244 GetPixelFromRGB(window,
3245 (native_mm_level.laser_red ? 0xFF : color_up),
3246 (native_mm_level.laser_green ? color_down : 0x00),
3247 (native_mm_level.laser_blue ? color_down : 0x00));
3249 DrawLaser(0, DL_LASER_ENABLED);
3252 if (!laser.overloaded)
3253 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3254 else if (setup.sound_loops)
3255 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3257 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3259 if (laser.overloaded)
3262 BlitBitmap(pix[PIX_DOOR], drawto,
3263 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3264 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3265 - laser.overload_value,
3266 OVERLOAD_XSIZE, laser.overload_value,
3267 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3268 - laser.overload_value);
3270 redraw_mask |= REDRAW_DOOR_1;
3275 BlitBitmap(pix[PIX_DOOR], drawto,
3276 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3277 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3278 DX_OVERLOAD, DY_OVERLOAD);
3280 redraw_mask |= REDRAW_DOOR_1;
3283 if (laser.overload_value == MAX_LASER_OVERLOAD)
3287 UpdateAndDisplayGameControlValues();
3289 for (i = 15; i >= 0; i--)
3292 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3295 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3297 DrawLaser(0, DL_LASER_ENABLED);
3299 Delay_WithScreenUpdates(50);
3302 DrawLaser(0, DL_LASER_DISABLED);
3304 game_mm.game_over = TRUE;
3305 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3307 SetTileCursorActive(FALSE);
3309 game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?";
3320 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3322 if (game_mm.cheat_no_explosion)
3327 laser.dest_element = EL_EXPLODING_OPAQUE;
3332 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3334 laser.fuse_off = TRUE;
3338 DrawLaser(0, DL_LASER_DISABLED);
3339 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3342 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3344 static int new_elements[] =
3347 EL_MIRROR_FIXED_START,
3349 EL_POLAR_CROSS_START,
3355 int num_new_elements = sizeof(new_elements) / sizeof(int);
3356 int new_element = new_elements[RND(num_new_elements)];
3358 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3359 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3361 // !!! CHECK AGAIN: Laser on Polarizer !!!
3372 element = EL_MIRROR_START + RND(16);
3378 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3385 element = (rnd == 0 ? EL_FUSE_ON :
3386 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3387 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3388 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3389 EL_MIRROR_FIXED_START + rnd - 25);
3394 graphic = el2gfx(element);
3396 for (i = 0; i < 50; i++)
3402 BlitBitmap(pix[PIX_BACK], drawto,
3403 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3404 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3405 SX + ELX * TILEX + x,
3406 SY + ELY * TILEY + y);
3408 MarkTileDirty(ELX, ELY);
3411 DrawLaser(0, DL_LASER_ENABLED);
3413 Delay_WithScreenUpdates(50);
3416 Tile[ELX][ELY] = element;
3417 DrawField_MM(ELX, ELY);
3420 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3423 // above stuff: GRAY BALL -> PRISM !!!
3425 LX = ELX * TILEX + 14;
3426 LY = ELY * TILEY + 14;
3427 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3434 laser.num_edges -= 2;
3435 laser.num_damages--;
3439 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3440 if (laser.damage[i].is_mirror)
3444 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3446 DrawLaser(0, DL_LASER_DISABLED);
3448 DrawLaser(0, DL_LASER_DISABLED);
3457 if (IS_WALL_ICE(element) && CT > 50)
3459 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3462 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3463 Store[ELX][ELY] = EL_WALL_ICE;
3464 Store2[ELX][ELY] = laser.wall_mask;
3466 laser.dest_element = Tile[ELX][ELY];
3471 for (i = 0; i < 5; i++)
3477 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3481 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3483 Delay_WithScreenUpdates(100);
3486 if (Tile[ELX][ELY] == EL_WALL_ICE)
3487 Tile[ELX][ELY] = EL_EMPTY;
3491 LX = laser.edge[laser.num_edges].x - cSX2;
3492 LY = laser.edge[laser.num_edges].y - cSY2;
3495 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3496 if (laser.damage[i].is_mirror)
3500 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3502 DrawLaser(0, DL_LASER_DISABLED);
3509 if (IS_WALL_AMOEBA(element) && CT > 60)
3511 int k1, k2, k3, dx, dy, de, dm;
3512 int element2 = Tile[ELX][ELY];
3514 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3517 for (i = laser.num_damages - 1; i >= 0; i--)
3518 if (laser.damage[i].is_mirror)
3521 r = laser.num_edges;
3522 d = laser.num_damages;
3529 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3532 DrawLaser(0, DL_LASER_ENABLED);
3535 x = laser.damage[k1].x;
3536 y = laser.damage[k1].y;
3541 for (i = 0; i < 4; i++)
3543 if (laser.wall_mask & (1 << i))
3545 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3546 cSY + ELY * TILEY + 31 * (i / 2)))
3549 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3550 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3557 for (i = 0; i < 4; i++)
3559 if (laser.wall_mask & (1 << i))
3561 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3562 cSY + ELY * TILEY + 31 * (i / 2)))
3569 if (laser.num_beamers > 0 ||
3570 k1 < 1 || k2 < 4 || k3 < 4 ||
3571 CheckLaserPixel(cSX + ELX * TILEX + 14,
3572 cSY + ELY * TILEY + 14))
3574 laser.num_edges = r;
3575 laser.num_damages = d;
3577 DrawLaser(0, DL_LASER_DISABLED);
3580 Tile[ELX][ELY] = element | laser.wall_mask;
3584 de = Tile[ELX][ELY];
3585 dm = laser.wall_mask;
3589 int x = ELX, y = ELY;
3590 int wall_mask = laser.wall_mask;
3593 DrawLaser(0, DL_LASER_ENABLED);
3595 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3597 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3598 Store[x][y] = EL_WALL_AMOEBA;
3599 Store2[x][y] = wall_mask;
3605 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3607 DrawLaser(0, DL_LASER_ENABLED);
3609 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3611 for (i = 4; i >= 0; i--)
3613 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3616 Delay_WithScreenUpdates(20);
3619 DrawLaser(0, DL_LASER_ENABLED);
3624 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3625 laser.stops_inside_element && CT > native_mm_level.time_block)
3630 if (ABS(XS) > ABS(YS))
3637 for (i = 0; i < 4; i++)
3644 x = ELX + Step[k * 4].x;
3645 y = ELY + Step[k * 4].y;
3647 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3650 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3658 laser.overloaded = (element == EL_BLOCK_STONE);
3663 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3666 Tile[x][y] = element;
3668 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3671 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3673 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3674 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3682 if (element == EL_FUEL_FULL && CT > 10)
3684 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3687 BlitBitmap(pix[PIX_DOOR], drawto,
3688 DOOR_GFX_PAGEX4 + XX_ENERGY,
3689 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3690 ENERGY_XSIZE, i, DX_ENERGY,
3691 DY_ENERGY + ENERGY_YSIZE - i);
3694 redraw_mask |= REDRAW_DOOR_1;
3697 Delay_WithScreenUpdates(20);
3700 game_mm.energy_left = MAX_LASER_ENERGY;
3701 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3702 DrawField_MM(ELX, ELY);
3704 DrawLaser(0, DL_LASER_ENABLED);
3712 void GameActions_MM(struct MouseActionInfo action)
3714 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3715 boolean button_released = (action.button == MB_RELEASED);
3717 GameActions_MM_Ext();
3719 CheckSingleStepMode_MM(element_clicked, button_released);
3722 void MovePacMen(void)
3724 int mx, my, ox, oy, nx, ny;
3728 if (++pacman_nr >= game_mm.num_pacman)
3731 game_mm.pacman[pacman_nr].dir--;
3733 for (l = 1; l < 5; l++)
3735 game_mm.pacman[pacman_nr].dir++;
3737 if (game_mm.pacman[pacman_nr].dir > 4)
3738 game_mm.pacman[pacman_nr].dir = 1;
3740 if (game_mm.pacman[pacman_nr].dir % 2)
3743 my = game_mm.pacman[pacman_nr].dir - 2;
3748 mx = 3 - game_mm.pacman[pacman_nr].dir;
3751 ox = game_mm.pacman[pacman_nr].x;
3752 oy = game_mm.pacman[pacman_nr].y;
3755 element = Tile[nx][ny];
3757 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3760 if (!IS_EATABLE4PACMAN(element))
3763 if (ObjHit(nx, ny, HIT_POS_CENTER))
3766 Tile[ox][oy] = EL_EMPTY;
3768 EL_PACMAN_RIGHT - 1 +
3769 (game_mm.pacman[pacman_nr].dir - 1 +
3770 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3772 game_mm.pacman[pacman_nr].x = nx;
3773 game_mm.pacman[pacman_nr].y = ny;
3775 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3777 if (element != EL_EMPTY)
3779 int graphic = el2gfx(Tile[nx][ny]);
3784 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3787 ox = cSX + ox * TILEX;
3788 oy = cSY + oy * TILEY;
3790 for (i = 1; i < 33; i += 2)
3791 BlitBitmap(bitmap, window,
3792 src_x, src_y, TILEX, TILEY,
3793 ox + i * mx, oy + i * my);
3794 Ct = Ct + FrameCounter - CT;
3797 DrawField_MM(nx, ny);
3800 if (!laser.fuse_off)
3802 DrawLaser(0, DL_LASER_ENABLED);
3804 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3806 AddDamagedField(nx, ny);
3808 laser.damage[laser.num_damages - 1].edge = 0;
3812 if (element == EL_BOMB)
3813 DeletePacMan(nx, ny);
3815 if (IS_WALL_AMOEBA(element) &&
3816 (LX + 2 * XS) / TILEX == nx &&
3817 (LY + 2 * YS) / TILEY == ny)
3827 static void InitMovingField_MM(int x, int y, int direction)
3829 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3830 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3832 MovDir[x][y] = direction;
3833 MovDir[newx][newy] = direction;
3835 if (Tile[newx][newy] == EL_EMPTY)
3836 Tile[newx][newy] = EL_BLOCKED;
3839 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3841 int direction = MovDir[x][y];
3842 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3843 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3849 static void Blocked2Moving_MM(int x, int y,
3850 int *comes_from_x, int *comes_from_y)
3852 int oldx = x, oldy = y;
3853 int direction = MovDir[x][y];
3855 if (direction == MV_LEFT)
3857 else if (direction == MV_RIGHT)
3859 else if (direction == MV_UP)
3861 else if (direction == MV_DOWN)
3864 *comes_from_x = oldx;
3865 *comes_from_y = oldy;
3868 static int MovingOrBlocked2Element_MM(int x, int y)
3870 int element = Tile[x][y];
3872 if (element == EL_BLOCKED)
3876 Blocked2Moving_MM(x, y, &oldx, &oldy);
3878 return Tile[oldx][oldy];
3885 static void RemoveField(int x, int y)
3887 Tile[x][y] = EL_EMPTY;
3894 static void RemoveMovingField_MM(int x, int y)
3896 int oldx = x, oldy = y, newx = x, newy = y;
3898 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3901 if (IS_MOVING(x, y))
3903 Moving2Blocked_MM(x, y, &newx, &newy);
3904 if (Tile[newx][newy] != EL_BLOCKED)
3907 else if (Tile[x][y] == EL_BLOCKED)
3909 Blocked2Moving_MM(x, y, &oldx, &oldy);
3910 if (!IS_MOVING(oldx, oldy))
3914 Tile[oldx][oldy] = EL_EMPTY;
3915 Tile[newx][newy] = EL_EMPTY;
3916 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3917 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3919 DrawLevelField_MM(oldx, oldy);
3920 DrawLevelField_MM(newx, newy);
3923 void PlaySoundLevel(int x, int y, int sound_nr)
3925 int sx = SCREENX(x), sy = SCREENY(y);
3927 int silence_distance = 8;
3929 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3930 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3933 if (!IN_LEV_FIELD(x, y) ||
3934 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3935 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3938 volume = SOUND_MAX_VOLUME;
3941 stereo = (sx - SCR_FIELDX/2) * 12;
3943 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3944 if (stereo > SOUND_MAX_RIGHT)
3945 stereo = SOUND_MAX_RIGHT;
3946 if (stereo < SOUND_MAX_LEFT)
3947 stereo = SOUND_MAX_LEFT;
3950 if (!IN_SCR_FIELD(sx, sy))
3952 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3953 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3955 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3958 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3961 static void RaiseScore_MM(int value)
3963 game_mm.score += value;
3966 void RaiseScoreElement_MM(int element)
3971 case EL_PACMAN_RIGHT:
3973 case EL_PACMAN_LEFT:
3974 case EL_PACMAN_DOWN:
3975 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3979 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3984 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3988 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3997 // ----------------------------------------------------------------------------
3998 // Mirror Magic game engine snapshot handling functions
3999 // ----------------------------------------------------------------------------
4001 void SaveEngineSnapshotValues_MM(void)
4005 engine_snapshot_mm.game_mm = game_mm;
4006 engine_snapshot_mm.laser = laser;
4008 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4010 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4012 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4013 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4014 engine_snapshot_mm.Box[x][y] = Box[x][y];
4015 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4016 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4020 engine_snapshot_mm.LX = LX;
4021 engine_snapshot_mm.LY = LY;
4022 engine_snapshot_mm.XS = XS;
4023 engine_snapshot_mm.YS = YS;
4024 engine_snapshot_mm.ELX = ELX;
4025 engine_snapshot_mm.ELY = ELY;
4026 engine_snapshot_mm.CT = CT;
4027 engine_snapshot_mm.Ct = Ct;
4029 engine_snapshot_mm.last_LX = last_LX;
4030 engine_snapshot_mm.last_LY = last_LY;
4031 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4032 engine_snapshot_mm.hold_x = hold_x;
4033 engine_snapshot_mm.hold_y = hold_y;
4034 engine_snapshot_mm.pacman_nr = pacman_nr;
4036 engine_snapshot_mm.rotate_delay = rotate_delay;
4037 engine_snapshot_mm.pacman_delay = pacman_delay;
4038 engine_snapshot_mm.energy_delay = energy_delay;
4039 engine_snapshot_mm.overload_delay = overload_delay;
4042 void LoadEngineSnapshotValues_MM(void)
4046 // stored engine snapshot buffers already restored at this point
4048 game_mm = engine_snapshot_mm.game_mm;
4049 laser = engine_snapshot_mm.laser;
4051 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4053 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4055 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4056 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4057 Box[x][y] = engine_snapshot_mm.Box[x][y];
4058 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4059 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4063 LX = engine_snapshot_mm.LX;
4064 LY = engine_snapshot_mm.LY;
4065 XS = engine_snapshot_mm.XS;
4066 YS = engine_snapshot_mm.YS;
4067 ELX = engine_snapshot_mm.ELX;
4068 ELY = engine_snapshot_mm.ELY;
4069 CT = engine_snapshot_mm.CT;
4070 Ct = engine_snapshot_mm.Ct;
4072 last_LX = engine_snapshot_mm.last_LX;
4073 last_LY = engine_snapshot_mm.last_LY;
4074 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4075 hold_x = engine_snapshot_mm.hold_x;
4076 hold_y = engine_snapshot_mm.hold_y;
4077 pacman_nr = engine_snapshot_mm.pacman_nr;
4079 rotate_delay = engine_snapshot_mm.rotate_delay;
4080 pacman_delay = engine_snapshot_mm.pacman_delay;
4081 energy_delay = engine_snapshot_mm.energy_delay;
4082 overload_delay = engine_snapshot_mm.overload_delay;
4084 RedrawPlayfield_MM();
4087 static int getAngleFromTouchDelta(int dx, int dy, int base)
4089 double pi = 3.141592653;
4090 double rad = atan2((double)-dy, (double)dx);
4091 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4092 double deg = rad2 * 180.0 / pi;
4094 return (int)(deg * base / 360.0 + 0.5) % base;
4097 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4099 // calculate start (source) position to be at the middle of the tile
4100 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4101 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4102 int dx = dst_mx - src_mx;
4103 int dy = dst_my - src_my;
4112 if (!IN_LEV_FIELD(x, y))
4115 element = Tile[x][y];
4117 if (!IS_MCDUFFIN(element) &&
4118 !IS_MIRROR(element) &&
4119 !IS_BEAMER(element) &&
4120 !IS_POLAR(element) &&
4121 !IS_POLAR_CROSS(element) &&
4122 !IS_DF_MIRROR(element))
4125 angle_old = get_element_angle(element);
4127 if (IS_MCDUFFIN(element))
4129 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4130 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4131 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4132 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4135 else if (IS_MIRROR(element) ||
4136 IS_DF_MIRROR(element))
4138 for (i = 0; i < laser.num_damages; i++)
4140 if (laser.damage[i].x == x &&
4141 laser.damage[i].y == y &&
4142 ObjHit(x, y, HIT_POS_CENTER))
4144 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4145 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4152 if (angle_new == -1)
4154 if (IS_MIRROR(element) ||
4155 IS_DF_MIRROR(element) ||
4159 if (IS_POLAR_CROSS(element))
4162 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4165 button = (angle_new == angle_old ? 0 :
4166 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4167 MB_LEFTBUTTON : MB_RIGHTBUTTON);