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 static void FadeOutLaser(boolean overloaded)
721 for (i = 15; i >= 0; i--)
724 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
726 pen_ray = GetPixelFromRGB(window,
727 native_mm_level.laser_red * 0x11 * i,
728 native_mm_level.laser_green * 0x11 * i,
729 native_mm_level.laser_blue * 0x11 * i);
731 DrawLaser(0, DL_LASER_ENABLED);
734 Delay_WithScreenUpdates(50);
737 DrawLaser(0, DL_LASER_DISABLED);
740 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
743 static void GameOver_MM(int game_over_cause)
745 // do not handle game over if request dialog is already active
746 if (game.request_active)
749 game_mm.game_over = TRUE;
750 game_mm.game_over_cause = game_over_cause;
752 if (setup.ask_on_game_over)
753 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
754 "Bomb killed Mc Duffin! Play it again?" :
755 game_over_cause == GAME_OVER_NO_ENERGY ?
756 "Out of magic energy! Play it again?" :
757 game_over_cause == GAME_OVER_OVERLOADED ?
758 "Magic spell hit Mc Duffin! Play it again?" :
761 SetTileCursorActive(FALSE);
764 void AddLaserEdge(int lx, int ly)
769 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
771 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
776 laser.edge[laser.num_edges].x = cSX2 + lx;
777 laser.edge[laser.num_edges].y = cSY2 + ly;
783 void AddDamagedField(int ex, int ey)
785 laser.damage[laser.num_damages].is_mirror = FALSE;
786 laser.damage[laser.num_damages].angle = laser.current_angle;
787 laser.damage[laser.num_damages].edge = laser.num_edges;
788 laser.damage[laser.num_damages].x = ex;
789 laser.damage[laser.num_damages].y = ey;
793 static boolean StepBehind(void)
799 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
800 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
802 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
808 static int getMaskFromElement(int element)
810 if (IS_GRID(element))
811 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
812 else if (IS_MCDUFFIN(element))
813 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
814 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
815 return IMG_MM_MASK_RECTANGLE;
817 return IMG_MM_MASK_CIRCLE;
820 static int ScanPixel(void)
825 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
826 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
829 // follow laser beam until it hits something (at least the screen border)
830 while (hit_mask == HIT_MASK_NO_HIT)
836 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
837 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
839 Debug("game:mm:ScanPixel", "touched screen border!");
845 for (i = 0; i < 4; i++)
847 int px = LX + (i % 2) * 2;
848 int py = LY + (i / 2) * 2;
851 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
852 int ly = (py + TILEY) / TILEY - 1; // negative values!
855 if (IN_LEV_FIELD(lx, ly))
857 int element = Tile[lx][ly];
859 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
863 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
865 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
867 pixel = ((element & (1 << pos)) ? 1 : 0);
871 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
873 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
878 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
879 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
882 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
883 hit_mask |= (1 << i);
886 if (hit_mask == HIT_MASK_NO_HIT)
888 // hit nothing -- go on with another step
900 int end = 0, rf = laser.num_edges;
902 // do not scan laser again after the game was lost for whatever reason
903 if (game_mm.game_over)
906 laser.overloaded = FALSE;
907 laser.stops_inside_element = FALSE;
909 DrawLaser(0, DL_LASER_ENABLED);
912 Debug("game:mm:ScanLaser",
913 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
921 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
924 laser.overloaded = TRUE;
929 hit_mask = ScanPixel();
932 Debug("game:mm:ScanLaser",
933 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
937 // hit something -- check out what it was
938 ELX = (LX + XS) / TILEX;
939 ELY = (LY + YS) / TILEY;
942 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
943 hit_mask, LX, LY, ELX, ELY);
946 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
949 laser.dest_element = element;
954 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
956 /* we have hit the top-right and bottom-left element --
957 choose the bottom-left one */
958 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
959 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
960 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
961 ELX = (LX - 2) / TILEX;
962 ELY = (LY + 2) / TILEY;
965 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
967 /* we have hit the top-left and bottom-right element --
968 choose the top-left one */
970 ELX = (LX - 2) / TILEX;
971 ELY = (LY - 2) / TILEY;
975 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
976 hit_mask, LX, LY, ELX, ELY);
979 element = Tile[ELX][ELY];
980 laser.dest_element = element;
983 Debug("game:mm:ScanLaser",
984 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
987 LX % TILEX, LY % TILEY,
992 if (!IN_LEV_FIELD(ELX, ELY))
993 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
997 if (element == EL_EMPTY)
999 if (!HitOnlyAnEdge(hit_mask))
1002 else if (element == EL_FUSE_ON)
1004 if (HitPolarizer(element, hit_mask))
1007 else if (IS_GRID(element) || IS_DF_GRID(element))
1009 if (HitPolarizer(element, hit_mask))
1012 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1013 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1015 if (HitBlock(element, hit_mask))
1022 else if (IS_MCDUFFIN(element))
1024 if (HitLaserSource(element, hit_mask))
1027 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1028 IS_RECEIVER(element))
1030 if (HitLaserDestination(element, hit_mask))
1033 else if (IS_WALL(element))
1035 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1037 if (HitReflectingWalls(element, hit_mask))
1042 if (HitAbsorbingWalls(element, hit_mask))
1048 if (HitElement(element, hit_mask))
1053 DrawLaser(rf - 1, DL_LASER_ENABLED);
1054 rf = laser.num_edges;
1058 if (laser.dest_element != Tile[ELX][ELY])
1060 Debug("game:mm:ScanLaser",
1061 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1062 laser.dest_element, Tile[ELX][ELY]);
1066 if (!end && !laser.stops_inside_element && !StepBehind())
1069 Debug("game:mm:ScanLaser", "Go one step back");
1075 AddLaserEdge(LX, LY);
1079 DrawLaser(rf - 1, DL_LASER_ENABLED);
1081 Ct = CT = FrameCounter;
1084 if (!IN_LEV_FIELD(ELX, ELY))
1085 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1089 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1095 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1096 start_edge, num_edges, mode);
1101 Warn("DrawLaserExt: start_edge < 0");
1108 Warn("DrawLaserExt: num_edges < 0");
1114 if (mode == DL_LASER_DISABLED)
1116 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1120 // now draw the laser to the backbuffer and (if enabled) to the screen
1121 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1123 redraw_mask |= REDRAW_FIELD;
1125 if (mode == DL_LASER_ENABLED)
1128 // after the laser was deleted, the "damaged" graphics must be restored
1129 if (laser.num_damages)
1131 int damage_start = 0;
1134 // determine the starting edge, from which graphics need to be restored
1137 for (i = 0; i < laser.num_damages; i++)
1139 if (laser.damage[i].edge == start_edge + 1)
1148 // restore graphics from this starting edge to the end of damage list
1149 for (i = damage_start; i < laser.num_damages; i++)
1151 int lx = laser.damage[i].x;
1152 int ly = laser.damage[i].y;
1153 int element = Tile[lx][ly];
1155 if (Hit[lx][ly] == laser.damage[i].edge)
1156 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1159 if (Box[lx][ly] == laser.damage[i].edge)
1162 if (IS_DRAWABLE(element))
1163 DrawField_MM(lx, ly);
1166 elx = laser.damage[damage_start].x;
1167 ely = laser.damage[damage_start].y;
1168 element = Tile[elx][ely];
1171 if (IS_BEAMER(element))
1175 for (i = 0; i < laser.num_beamers; i++)
1176 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1178 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1179 mode, elx, ely, Hit[elx][ely], start_edge);
1180 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1181 get_element_angle(element), laser.damage[damage_start].angle);
1185 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1186 laser.num_beamers > 0 &&
1187 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1189 // element is outgoing beamer
1190 laser.num_damages = damage_start + 1;
1192 if (IS_BEAMER(element))
1193 laser.current_angle = get_element_angle(element);
1197 // element is incoming beamer or other element
1198 laser.num_damages = damage_start;
1199 laser.current_angle = laser.damage[laser.num_damages].angle;
1204 // no damages but McDuffin himself (who needs to be redrawn anyway)
1206 elx = laser.start_edge.x;
1207 ely = laser.start_edge.y;
1208 element = Tile[elx][ely];
1211 laser.num_edges = start_edge + 1;
1212 if (start_edge == 0)
1213 laser.current_angle = laser.start_angle;
1215 LX = laser.edge[start_edge].x - cSX2;
1216 LY = laser.edge[start_edge].y - cSY2;
1217 XS = 2 * Step[laser.current_angle].x;
1218 YS = 2 * Step[laser.current_angle].y;
1221 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1227 if (IS_BEAMER(element) ||
1228 IS_FIBRE_OPTIC(element) ||
1229 IS_PACMAN(element) ||
1230 IS_POLAR(element) ||
1231 IS_POLAR_CROSS(element) ||
1232 element == EL_FUSE_ON)
1237 Debug("game:mm:DrawLaserExt", "element == %d", element);
1240 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1241 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1245 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1246 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1247 (laser.num_beamers == 0 ||
1248 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1250 // element is incoming beamer or other element
1251 step_size = -step_size;
1256 if (IS_BEAMER(element))
1257 Debug("game:mm:DrawLaserExt",
1258 "start_edge == %d, laser.beamer_edge == %d",
1259 start_edge, laser.beamer_edge);
1262 LX += step_size * XS;
1263 LY += step_size * YS;
1265 else if (element != EL_EMPTY)
1274 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1279 void DrawLaser(int start_edge, int mode)
1281 if (laser.num_edges - start_edge < 0)
1283 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1288 // check if laser is interrupted by beamer element
1289 if (laser.num_beamers > 0 &&
1290 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1292 if (mode == DL_LASER_ENABLED)
1295 int tmp_start_edge = start_edge;
1297 // draw laser segments forward from the start to the last beamer
1298 for (i = 0; i < laser.num_beamers; i++)
1300 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1302 if (tmp_num_edges <= 0)
1306 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1307 i, laser.beamer_edge[i], tmp_start_edge);
1310 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1312 tmp_start_edge = laser.beamer_edge[i];
1315 // draw last segment from last beamer to the end
1316 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1322 int last_num_edges = laser.num_edges;
1323 int num_beamers = laser.num_beamers;
1325 // delete laser segments backward from the end to the first beamer
1326 for (i = num_beamers - 1; i >= 0; i--)
1328 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1330 if (laser.beamer_edge[i] - start_edge <= 0)
1333 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1335 last_num_edges = laser.beamer_edge[i];
1336 laser.num_beamers--;
1340 if (last_num_edges - start_edge <= 0)
1341 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1342 last_num_edges, start_edge);
1345 // special case when rotating first beamer: delete laser edge on beamer
1346 // (but do not start scanning on previous edge to prevent mirror sound)
1347 if (last_num_edges - start_edge == 1 && start_edge > 0)
1348 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1350 // delete first segment from start to the first beamer
1351 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1356 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1359 game_mm.laser_enabled = mode;
1362 void DrawLaser_MM(void)
1364 DrawLaser(0, game_mm.laser_enabled);
1367 boolean HitElement(int element, int hit_mask)
1369 if (HitOnlyAnEdge(hit_mask))
1372 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1373 element = MovingOrBlocked2Element_MM(ELX, ELY);
1376 Debug("game:mm:HitElement", "(1): element == %d", element);
1380 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1381 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1384 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1388 AddDamagedField(ELX, ELY);
1390 // this is more precise: check if laser would go through the center
1391 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1393 // skip the whole element before continuing the scan
1399 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1401 if (LX/TILEX > ELX || LY/TILEY > ELY)
1403 /* skipping scan positions to the right and down skips one scan
1404 position too much, because this is only the top left scan position
1405 of totally four scan positions (plus one to the right, one to the
1406 bottom and one to the bottom right) */
1416 Debug("game:mm:HitElement", "(2): element == %d", element);
1419 if (LX + 5 * XS < 0 ||
1429 Debug("game:mm:HitElement", "(3): element == %d", element);
1432 if (IS_POLAR(element) &&
1433 ((element - EL_POLAR_START) % 2 ||
1434 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1436 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1438 laser.num_damages--;
1443 if (IS_POLAR_CROSS(element) &&
1444 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1446 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1448 laser.num_damages--;
1453 if (!IS_BEAMER(element) &&
1454 !IS_FIBRE_OPTIC(element) &&
1455 !IS_GRID_WOOD(element) &&
1456 element != EL_FUEL_EMPTY)
1459 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1460 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1462 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1465 LX = ELX * TILEX + 14;
1466 LY = ELY * TILEY + 14;
1468 AddLaserEdge(LX, LY);
1471 if (IS_MIRROR(element) ||
1472 IS_MIRROR_FIXED(element) ||
1473 IS_POLAR(element) ||
1474 IS_POLAR_CROSS(element) ||
1475 IS_DF_MIRROR(element) ||
1476 IS_DF_MIRROR_AUTO(element) ||
1477 element == EL_PRISM ||
1478 element == EL_REFRACTOR)
1480 int current_angle = laser.current_angle;
1483 laser.num_damages--;
1485 AddDamagedField(ELX, ELY);
1487 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1490 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1492 if (IS_MIRROR(element) ||
1493 IS_MIRROR_FIXED(element) ||
1494 IS_DF_MIRROR(element) ||
1495 IS_DF_MIRROR_AUTO(element))
1496 laser.current_angle = get_mirrored_angle(laser.current_angle,
1497 get_element_angle(element));
1499 if (element == EL_PRISM || element == EL_REFRACTOR)
1500 laser.current_angle = RND(16);
1502 XS = 2 * Step[laser.current_angle].x;
1503 YS = 2 * Step[laser.current_angle].y;
1505 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1510 LX += step_size * XS;
1511 LY += step_size * YS;
1513 // draw sparkles on mirror
1514 if ((IS_MIRROR(element) ||
1515 IS_MIRROR_FIXED(element) ||
1516 element == EL_PRISM) &&
1517 current_angle != laser.current_angle)
1519 MovDelay[ELX][ELY] = 11; // start animation
1522 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1523 current_angle != laser.current_angle)
1524 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1527 (get_opposite_angle(laser.current_angle) ==
1528 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1530 return (laser.overloaded ? TRUE : FALSE);
1533 if (element == EL_FUEL_FULL)
1535 laser.stops_inside_element = TRUE;
1540 if (element == EL_BOMB || element == EL_MINE)
1542 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1544 if (element == EL_MINE)
1545 laser.overloaded = TRUE;
1548 if (element == EL_KETTLE ||
1549 element == EL_CELL ||
1550 element == EL_KEY ||
1551 element == EL_LIGHTBALL ||
1552 element == EL_PACMAN ||
1555 if (!IS_PACMAN(element))
1558 if (element == EL_PACMAN)
1561 if (element == EL_KETTLE || element == EL_CELL)
1563 if (game_mm.kettles_still_needed > 0)
1564 game_mm.kettles_still_needed--;
1566 game.snapshot.collected_item = TRUE;
1568 if (game_mm.kettles_still_needed == 0)
1572 DrawLaser(0, DL_LASER_ENABLED);
1575 else if (element == EL_KEY)
1579 else if (IS_PACMAN(element))
1581 DeletePacMan(ELX, ELY);
1584 RaiseScoreElement_MM(element);
1589 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1591 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1593 DrawLaser(0, DL_LASER_ENABLED);
1595 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1597 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1598 game_mm.lights_still_needed--;
1602 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1603 game_mm.lights_still_needed++;
1606 DrawField_MM(ELX, ELY);
1607 DrawLaser(0, DL_LASER_ENABLED);
1612 laser.stops_inside_element = TRUE;
1618 Debug("game:mm:HitElement", "(4): element == %d", element);
1621 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1622 laser.num_beamers < MAX_NUM_BEAMERS &&
1623 laser.beamer[BEAMER_NR(element)][1].num)
1625 int beamer_angle = get_element_angle(element);
1626 int beamer_nr = BEAMER_NR(element);
1630 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1633 laser.num_damages--;
1635 if (IS_FIBRE_OPTIC(element) ||
1636 laser.current_angle == get_opposite_angle(beamer_angle))
1640 LX = ELX * TILEX + 14;
1641 LY = ELY * TILEY + 14;
1643 AddLaserEdge(LX, LY);
1644 AddDamagedField(ELX, ELY);
1646 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1649 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1651 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1652 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1653 ELX = laser.beamer[beamer_nr][pos].x;
1654 ELY = laser.beamer[beamer_nr][pos].y;
1655 LX = ELX * TILEX + 14;
1656 LY = ELY * TILEY + 14;
1658 if (IS_BEAMER(element))
1660 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1661 XS = 2 * Step[laser.current_angle].x;
1662 YS = 2 * Step[laser.current_angle].y;
1665 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1667 AddLaserEdge(LX, LY);
1668 AddDamagedField(ELX, ELY);
1670 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1673 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1675 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1680 LX += step_size * XS;
1681 LY += step_size * YS;
1683 laser.num_beamers++;
1692 boolean HitOnlyAnEdge(int hit_mask)
1694 // check if the laser hit only the edge of an element and, if so, go on
1697 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1701 if ((hit_mask == HIT_MASK_TOPLEFT ||
1702 hit_mask == HIT_MASK_TOPRIGHT ||
1703 hit_mask == HIT_MASK_BOTTOMLEFT ||
1704 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1705 laser.current_angle % 4) // angle is not 90°
1709 if (hit_mask == HIT_MASK_TOPLEFT)
1714 else if (hit_mask == HIT_MASK_TOPRIGHT)
1719 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1724 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1730 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1736 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1743 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1749 boolean HitPolarizer(int element, int hit_mask)
1751 if (HitOnlyAnEdge(hit_mask))
1754 if (IS_DF_GRID(element))
1756 int grid_angle = get_element_angle(element);
1759 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1760 grid_angle, laser.current_angle);
1763 AddLaserEdge(LX, LY);
1764 AddDamagedField(ELX, ELY);
1767 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1769 if (laser.current_angle == grid_angle ||
1770 laser.current_angle == get_opposite_angle(grid_angle))
1772 // skip the whole element before continuing the scan
1778 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1780 if (LX/TILEX > ELX || LY/TILEY > ELY)
1782 /* skipping scan positions to the right and down skips one scan
1783 position too much, because this is only the top left scan position
1784 of totally four scan positions (plus one to the right, one to the
1785 bottom and one to the bottom right) */
1791 AddLaserEdge(LX, LY);
1797 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1799 LX / TILEX, LY / TILEY,
1800 LX % TILEX, LY % TILEY);
1805 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1807 return HitReflectingWalls(element, hit_mask);
1811 return HitAbsorbingWalls(element, hit_mask);
1814 else if (IS_GRID_STEEL(element))
1816 return HitReflectingWalls(element, hit_mask);
1818 else // IS_GRID_WOOD
1820 return HitAbsorbingWalls(element, hit_mask);
1826 boolean HitBlock(int element, int hit_mask)
1828 boolean check = FALSE;
1830 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1831 game_mm.num_keys == 0)
1834 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1837 int ex = ELX * TILEX + 14;
1838 int ey = ELY * TILEY + 14;
1842 for (i = 1; i < 32; i++)
1847 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1852 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1853 return HitAbsorbingWalls(element, hit_mask);
1857 AddLaserEdge(LX - XS, LY - YS);
1858 AddDamagedField(ELX, ELY);
1861 Box[ELX][ELY] = laser.num_edges;
1863 return HitReflectingWalls(element, hit_mask);
1866 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1868 int xs = XS / 2, ys = YS / 2;
1869 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1870 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1872 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1873 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1875 laser.overloaded = (element == EL_GATE_STONE);
1880 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1881 (hit_mask == HIT_MASK_TOP ||
1882 hit_mask == HIT_MASK_LEFT ||
1883 hit_mask == HIT_MASK_RIGHT ||
1884 hit_mask == HIT_MASK_BOTTOM))
1885 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1886 hit_mask == HIT_MASK_BOTTOM),
1887 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1888 hit_mask == HIT_MASK_RIGHT));
1889 AddLaserEdge(LX, LY);
1895 if (element == EL_GATE_STONE && Box[ELX][ELY])
1897 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1909 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1911 int xs = XS / 2, ys = YS / 2;
1912 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1913 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1915 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1916 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1918 laser.overloaded = (element == EL_BLOCK_STONE);
1923 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1924 (hit_mask == HIT_MASK_TOP ||
1925 hit_mask == HIT_MASK_LEFT ||
1926 hit_mask == HIT_MASK_RIGHT ||
1927 hit_mask == HIT_MASK_BOTTOM))
1928 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1929 hit_mask == HIT_MASK_BOTTOM),
1930 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1931 hit_mask == HIT_MASK_RIGHT));
1932 AddDamagedField(ELX, ELY);
1934 LX = ELX * TILEX + 14;
1935 LY = ELY * TILEY + 14;
1937 AddLaserEdge(LX, LY);
1939 laser.stops_inside_element = TRUE;
1947 boolean HitLaserSource(int element, int hit_mask)
1949 if (HitOnlyAnEdge(hit_mask))
1952 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1954 laser.overloaded = TRUE;
1959 boolean HitLaserDestination(int element, int hit_mask)
1961 if (HitOnlyAnEdge(hit_mask))
1964 if (element != EL_EXIT_OPEN &&
1965 !(IS_RECEIVER(element) &&
1966 game_mm.kettles_still_needed == 0 &&
1967 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1969 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1974 if (IS_RECEIVER(element) ||
1975 (IS_22_5_ANGLE(laser.current_angle) &&
1976 (ELX != (LX + 6 * XS) / TILEX ||
1977 ELY != (LY + 6 * YS) / TILEY ||
1986 LX = ELX * TILEX + 14;
1987 LY = ELY * TILEY + 14;
1989 laser.stops_inside_element = TRUE;
1992 AddLaserEdge(LX, LY);
1993 AddDamagedField(ELX, ELY);
1995 if (game_mm.lights_still_needed == 0)
1997 game_mm.level_solved = TRUE;
1999 SetTileCursorActive(FALSE);
2005 boolean HitReflectingWalls(int element, int hit_mask)
2007 // check if laser hits side of a wall with an angle that is not 90°
2008 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2009 hit_mask == HIT_MASK_LEFT ||
2010 hit_mask == HIT_MASK_RIGHT ||
2011 hit_mask == HIT_MASK_BOTTOM))
2013 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2018 if (!IS_DF_GRID(element))
2019 AddLaserEdge(LX, LY);
2021 // check if laser hits wall with an angle of 45°
2022 if (!IS_22_5_ANGLE(laser.current_angle))
2024 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2027 laser.current_angle = get_mirrored_angle(laser.current_angle,
2030 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2033 laser.current_angle = get_mirrored_angle(laser.current_angle,
2037 AddLaserEdge(LX, LY);
2039 XS = 2 * Step[laser.current_angle].x;
2040 YS = 2 * Step[laser.current_angle].y;
2044 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2046 laser.current_angle = get_mirrored_angle(laser.current_angle,
2051 if (!IS_DF_GRID(element))
2052 AddLaserEdge(LX, LY);
2057 if (!IS_DF_GRID(element))
2058 AddLaserEdge(LX, LY + YS / 2);
2061 if (!IS_DF_GRID(element))
2062 AddLaserEdge(LX, LY);
2065 YS = 2 * Step[laser.current_angle].y;
2069 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2071 laser.current_angle = get_mirrored_angle(laser.current_angle,
2076 if (!IS_DF_GRID(element))
2077 AddLaserEdge(LX, LY);
2082 if (!IS_DF_GRID(element))
2083 AddLaserEdge(LX + XS / 2, LY);
2086 if (!IS_DF_GRID(element))
2087 AddLaserEdge(LX, LY);
2090 XS = 2 * Step[laser.current_angle].x;
2096 // reflection at the edge of reflecting DF style wall
2097 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2099 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2100 hit_mask == HIT_MASK_TOPRIGHT) ||
2101 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2102 hit_mask == HIT_MASK_TOPLEFT) ||
2103 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2104 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2105 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2106 hit_mask == HIT_MASK_BOTTOMRIGHT))
2109 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2110 ANG_MIRROR_135 : ANG_MIRROR_45);
2112 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2114 AddDamagedField(ELX, ELY);
2115 AddLaserEdge(LX, LY);
2117 laser.current_angle = get_mirrored_angle(laser.current_angle,
2125 AddLaserEdge(LX, LY);
2131 // reflection inside an edge of reflecting DF style wall
2132 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2134 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2135 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2136 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2137 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2138 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2139 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2140 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2141 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2144 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2145 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2146 ANG_MIRROR_135 : ANG_MIRROR_45);
2148 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2151 AddDamagedField(ELX, ELY);
2154 AddLaserEdge(LX - XS, LY - YS);
2155 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2156 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2158 laser.current_angle = get_mirrored_angle(laser.current_angle,
2166 AddLaserEdge(LX, LY);
2172 // check if laser hits DF style wall with an angle of 90°
2173 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2175 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2176 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2177 (IS_VERT_ANGLE(laser.current_angle) &&
2178 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2180 // laser at last step touched nothing or the same side of the wall
2181 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2183 AddDamagedField(ELX, ELY);
2190 last_hit_mask = hit_mask;
2197 if (!HitOnlyAnEdge(hit_mask))
2199 laser.overloaded = TRUE;
2207 boolean HitAbsorbingWalls(int element, int hit_mask)
2209 if (HitOnlyAnEdge(hit_mask))
2213 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2215 AddLaserEdge(LX - XS, LY - YS);
2222 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2224 AddLaserEdge(LX - XS, LY - YS);
2230 if (IS_WALL_WOOD(element) ||
2231 IS_DF_WALL_WOOD(element) ||
2232 IS_GRID_WOOD(element) ||
2233 IS_GRID_WOOD_FIXED(element) ||
2234 IS_GRID_WOOD_AUTO(element) ||
2235 element == EL_FUSE_ON ||
2236 element == EL_BLOCK_WOOD ||
2237 element == EL_GATE_WOOD)
2239 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2244 if (IS_WALL_ICE(element))
2248 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2249 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2251 // check if laser hits wall with an angle of 90°
2252 if (IS_90_ANGLE(laser.current_angle))
2253 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2255 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2259 for (i = 0; i < 4; i++)
2261 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2262 mask = 15 - (8 >> i);
2263 else if (ABS(XS) == 4 &&
2265 (XS > 0) == (i % 2) &&
2266 (YS < 0) == (i / 2))
2267 mask = 3 + (i / 2) * 9;
2268 else if (ABS(YS) == 4 &&
2270 (XS < 0) == (i % 2) &&
2271 (YS > 0) == (i / 2))
2272 mask = 5 + (i % 2) * 5;
2276 laser.wall_mask = mask;
2278 else if (IS_WALL_AMOEBA(element))
2280 int elx = (LX - 2 * XS) / TILEX;
2281 int ely = (LY - 2 * YS) / TILEY;
2282 int element2 = Tile[elx][ely];
2285 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2287 laser.dest_element = EL_EMPTY;
2295 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2296 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2298 if (IS_90_ANGLE(laser.current_angle))
2299 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2301 laser.dest_element = element2 | EL_WALL_AMOEBA;
2303 laser.wall_mask = mask;
2309 static void OpenExit(int x, int y)
2313 if (!MovDelay[x][y]) // next animation frame
2314 MovDelay[x][y] = 4 * delay;
2316 if (MovDelay[x][y]) // wait some time before next frame
2321 phase = MovDelay[x][y] / delay;
2323 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2324 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2326 if (!MovDelay[x][y])
2328 Tile[x][y] = EL_EXIT_OPEN;
2334 static void OpenSurpriseBall(int x, int y)
2338 if (!MovDelay[x][y]) // next animation frame
2339 MovDelay[x][y] = 50 * delay;
2341 if (MovDelay[x][y]) // wait some time before next frame
2345 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2348 int graphic = el2gfx(Store[x][y]);
2350 int dx = RND(26), dy = RND(26);
2352 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2354 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2355 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2357 MarkTileDirty(x, y);
2360 if (!MovDelay[x][y])
2362 Tile[x][y] = Store[x][y];
2371 static void MeltIce(int x, int y)
2376 if (!MovDelay[x][y]) // next animation frame
2377 MovDelay[x][y] = frames * delay;
2379 if (MovDelay[x][y]) // wait some time before next frame
2382 int wall_mask = Store2[x][y];
2383 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2386 phase = frames - MovDelay[x][y] / delay - 1;
2388 if (!MovDelay[x][y])
2392 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2393 Store[x][y] = Store2[x][y] = 0;
2395 DrawWalls_MM(x, y, Tile[x][y]);
2397 if (Tile[x][y] == EL_WALL_ICE)
2398 Tile[x][y] = EL_EMPTY;
2400 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2401 if (laser.damage[i].is_mirror)
2405 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2407 DrawLaser(0, DL_LASER_DISABLED);
2411 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2413 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2415 laser.redraw = TRUE;
2420 static void GrowAmoeba(int x, int y)
2425 if (!MovDelay[x][y]) // next animation frame
2426 MovDelay[x][y] = frames * delay;
2428 if (MovDelay[x][y]) // wait some time before next frame
2431 int wall_mask = Store2[x][y];
2432 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2435 phase = MovDelay[x][y] / delay;
2437 if (!MovDelay[x][y])
2439 Tile[x][y] = real_element;
2440 Store[x][y] = Store2[x][y] = 0;
2442 DrawWalls_MM(x, y, Tile[x][y]);
2443 DrawLaser(0, DL_LASER_ENABLED);
2445 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2447 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2452 static void DrawFieldAnimated_MM(int x, int y)
2454 int element = Tile[x][y];
2456 if (IS_BLOCKED(x, y))
2461 if (IS_MIRROR(element) ||
2462 IS_MIRROR_FIXED(element) ||
2463 element == EL_PRISM)
2465 if (MovDelay[x][y] != 0) // wait some time before next frame
2469 if (MovDelay[x][y] != 0)
2471 int graphic = IMG_TWINKLE_WHITE;
2472 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2474 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2479 laser.redraw = TRUE;
2482 static void Explode_MM(int x, int y, int phase, int mode)
2484 int num_phase = 9, delay = 2;
2485 int last_phase = num_phase * delay;
2486 int half_phase = (num_phase / 2) * delay;
2488 laser.redraw = TRUE;
2490 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2492 int center_element = Tile[x][y];
2494 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2496 // put moving element to center field (and let it explode there)
2497 center_element = MovingOrBlocked2Element_MM(x, y);
2498 RemoveMovingField_MM(x, y);
2500 Tile[x][y] = center_element;
2503 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2504 Store[x][y] = center_element;
2506 Store[x][y] = EL_EMPTY;
2508 Store2[x][y] = mode;
2509 Tile[x][y] = EL_EXPLODING_OPAQUE;
2510 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2516 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2518 if (phase == half_phase)
2520 Tile[x][y] = EL_EXPLODING_TRANSP;
2522 if (x == ELX && y == ELY)
2526 if (phase == last_phase)
2528 if (Store[x][y] == EL_BOMB)
2530 DrawLaser(0, DL_LASER_DISABLED);
2533 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2534 Store[x][y] = EL_EMPTY;
2536 GameOver_MM(GAME_OVER_DELAYED);
2538 laser.overloaded = FALSE;
2540 else if (IS_MCDUFFIN(Store[x][y]))
2542 Store[x][y] = EL_EMPTY;
2544 GameOver_MM(GAME_OVER_BOMB);
2547 Tile[x][y] = Store[x][y];
2548 Store[x][y] = Store2[x][y] = 0;
2549 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2554 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2556 int graphic = IMG_MM_DEFAULT_EXPLODING;
2557 int graphic_phase = (phase / delay - 1);
2561 if (Store2[x][y] == EX_KETTLE)
2563 if (graphic_phase < 3)
2565 graphic = IMG_MM_KETTLE_EXPLODING;
2567 else if (graphic_phase < 5)
2573 graphic = IMG_EMPTY;
2577 else if (Store2[x][y] == EX_SHORT)
2579 if (graphic_phase < 4)
2585 graphic = IMG_EMPTY;
2590 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2592 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2593 cFX + x * TILEX, cFY + y * TILEY);
2595 MarkTileDirty(x, y);
2599 static void Bang_MM(int x, int y)
2601 int element = Tile[x][y];
2602 int mode = EX_NORMAL;
2605 DrawLaser(0, DL_LASER_ENABLED);
2624 if (IS_PACMAN(element))
2625 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2626 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2627 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2628 else if (element == EL_KEY)
2629 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2631 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2633 Explode_MM(x, y, EX_PHASE_START, mode);
2636 void TurnRound(int x, int y)
2648 { 0, 0 }, { 0, 0 }, { 0, 0 },
2653 int left, right, back;
2657 { MV_DOWN, MV_UP, MV_RIGHT },
2658 { MV_UP, MV_DOWN, MV_LEFT },
2660 { MV_LEFT, MV_RIGHT, MV_DOWN },
2661 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2662 { MV_RIGHT, MV_LEFT, MV_UP }
2665 int element = Tile[x][y];
2666 int old_move_dir = MovDir[x][y];
2667 int right_dir = turn[old_move_dir].right;
2668 int back_dir = turn[old_move_dir].back;
2669 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2670 int right_x = x + right_dx, right_y = y + right_dy;
2672 if (element == EL_PACMAN)
2674 boolean can_turn_right = FALSE;
2676 if (IN_LEV_FIELD(right_x, right_y) &&
2677 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2678 can_turn_right = TRUE;
2681 MovDir[x][y] = right_dir;
2683 MovDir[x][y] = back_dir;
2689 static void StartMoving_MM(int x, int y)
2691 int element = Tile[x][y];
2696 if (CAN_MOVE(element))
2700 if (MovDelay[x][y]) // wait some time before next movement
2708 // now make next step
2710 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2712 if (element == EL_PACMAN &&
2713 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2714 !ObjHit(newx, newy, HIT_POS_CENTER))
2716 Store[newx][newy] = Tile[newx][newy];
2717 Tile[newx][newy] = EL_EMPTY;
2719 DrawField_MM(newx, newy);
2721 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2722 ObjHit(newx, newy, HIT_POS_CENTER))
2724 // object was running against a wall
2731 InitMovingField_MM(x, y, MovDir[x][y]);
2735 ContinueMoving_MM(x, y);
2738 static void ContinueMoving_MM(int x, int y)
2740 int element = Tile[x][y];
2741 int direction = MovDir[x][y];
2742 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2743 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2744 int horiz_move = (dx!=0);
2745 int newx = x + dx, newy = y + dy;
2746 int step = (horiz_move ? dx : dy) * TILEX / 8;
2748 MovPos[x][y] += step;
2750 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2752 Tile[x][y] = EL_EMPTY;
2753 Tile[newx][newy] = element;
2755 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2756 MovDelay[newx][newy] = 0;
2758 if (!CAN_MOVE(element))
2759 MovDir[newx][newy] = 0;
2762 DrawField_MM(newx, newy);
2764 Stop[newx][newy] = TRUE;
2766 if (element == EL_PACMAN)
2768 if (Store[newx][newy] == EL_BOMB)
2769 Bang_MM(newx, newy);
2771 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2772 (LX + 2 * XS) / TILEX == newx &&
2773 (LY + 2 * YS) / TILEY == newy)
2780 else // still moving on
2785 laser.redraw = TRUE;
2788 boolean ClickElement(int x, int y, int button)
2790 static DelayCounter click_delay = { CLICK_DELAY };
2791 static boolean new_button = TRUE;
2792 boolean element_clicked = FALSE;
2797 // initialize static variables
2798 click_delay.count = 0;
2799 click_delay.value = CLICK_DELAY;
2805 // do not rotate objects hit by the laser after the game was solved
2806 if (game_mm.level_solved && Hit[x][y])
2809 if (button == MB_RELEASED)
2812 click_delay.value = CLICK_DELAY;
2814 // release eventually hold auto-rotating mirror
2815 RotateMirror(x, y, MB_RELEASED);
2820 if (!FrameReached(&click_delay) && !new_button)
2823 if (button == MB_MIDDLEBUTTON) // middle button has no function
2826 if (!IN_LEV_FIELD(x, y))
2829 if (Tile[x][y] == EL_EMPTY)
2832 element = Tile[x][y];
2834 if (IS_MIRROR(element) ||
2835 IS_BEAMER(element) ||
2836 IS_POLAR(element) ||
2837 IS_POLAR_CROSS(element) ||
2838 IS_DF_MIRROR(element) ||
2839 IS_DF_MIRROR_AUTO(element))
2841 RotateMirror(x, y, button);
2843 element_clicked = TRUE;
2845 else if (IS_MCDUFFIN(element))
2847 if (!laser.fuse_off)
2849 DrawLaser(0, DL_LASER_DISABLED);
2856 element = get_rotated_element(element, BUTTON_ROTATION(button));
2857 laser.start_angle = get_element_angle(element);
2861 Tile[x][y] = element;
2868 if (!laser.fuse_off)
2871 element_clicked = TRUE;
2873 else if (element == EL_FUSE_ON && laser.fuse_off)
2875 if (x != laser.fuse_x || y != laser.fuse_y)
2878 laser.fuse_off = FALSE;
2879 laser.fuse_x = laser.fuse_y = -1;
2881 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2884 element_clicked = TRUE;
2886 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2888 laser.fuse_off = TRUE;
2891 laser.overloaded = FALSE;
2893 DrawLaser(0, DL_LASER_DISABLED);
2894 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2896 element_clicked = TRUE;
2898 else if (element == EL_LIGHTBALL)
2901 RaiseScoreElement_MM(element);
2902 DrawLaser(0, DL_LASER_ENABLED);
2904 element_clicked = TRUE;
2907 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2910 return element_clicked;
2913 void RotateMirror(int x, int y, int button)
2915 if (button == MB_RELEASED)
2917 // release eventually hold auto-rotating mirror
2924 if (IS_MIRROR(Tile[x][y]) ||
2925 IS_POLAR_CROSS(Tile[x][y]) ||
2926 IS_POLAR(Tile[x][y]) ||
2927 IS_BEAMER(Tile[x][y]) ||
2928 IS_DF_MIRROR(Tile[x][y]) ||
2929 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2930 IS_GRID_WOOD_AUTO(Tile[x][y]))
2932 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2934 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2936 if (button == MB_LEFTBUTTON)
2938 // left mouse button only for manual adjustment, no auto-rotating;
2939 // freeze mirror for until mouse button released
2943 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2945 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2949 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2951 int edge = Hit[x][y];
2957 DrawLaser(edge - 1, DL_LASER_DISABLED);
2961 else if (ObjHit(x, y, HIT_POS_CENTER))
2963 int edge = Hit[x][y];
2967 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2972 DrawLaser(edge - 1, DL_LASER_DISABLED);
2979 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2984 if ((IS_BEAMER(Tile[x][y]) ||
2985 IS_POLAR(Tile[x][y]) ||
2986 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2990 if (IS_BEAMER(Tile[x][y]))
2993 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2994 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3006 DrawLaser(0, DL_LASER_ENABLED);
3010 static void AutoRotateMirrors(void)
3014 if (!FrameReached(&rotate_delay))
3017 for (x = 0; x < lev_fieldx; x++)
3019 for (y = 0; y < lev_fieldy; y++)
3021 int element = Tile[x][y];
3023 // do not rotate objects hit by the laser after the game was solved
3024 if (game_mm.level_solved && Hit[x][y])
3027 if (IS_DF_MIRROR_AUTO(element) ||
3028 IS_GRID_WOOD_AUTO(element) ||
3029 IS_GRID_STEEL_AUTO(element) ||
3030 element == EL_REFRACTOR)
3031 RotateMirror(x, y, MB_RIGHTBUTTON);
3036 boolean ObjHit(int obx, int oby, int bits)
3043 if (bits & HIT_POS_CENTER)
3045 if (CheckLaserPixel(cSX + obx + 15,
3050 if (bits & HIT_POS_EDGE)
3052 for (i = 0; i < 4; i++)
3053 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3054 cSY + oby + 31 * (i / 2)))
3058 if (bits & HIT_POS_BETWEEN)
3060 for (i = 0; i < 4; i++)
3061 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3062 cSY + 4 + oby + 22 * (i / 2)))
3069 void DeletePacMan(int px, int py)
3075 if (game_mm.num_pacman <= 1)
3077 game_mm.num_pacman = 0;
3081 for (i = 0; i < game_mm.num_pacman; i++)
3082 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3085 game_mm.num_pacman--;
3087 for (j = i; j < game_mm.num_pacman; j++)
3089 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3090 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3091 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3095 void ColorCycling(void)
3097 static int CC, Cc = 0;
3099 static int color, old = 0xF00, new = 0x010, mult = 1;
3100 static unsigned short red, green, blue;
3102 if (color_status == STATIC_COLORS)
3107 if (CC < Cc || CC > Cc + 2)
3111 color = old + new * mult;
3117 if (ABS(mult) == 16)
3127 red = 0x0e00 * ((color & 0xF00) >> 8);
3128 green = 0x0e00 * ((color & 0x0F0) >> 4);
3129 blue = 0x0e00 * ((color & 0x00F));
3130 SetRGB(pen_magicolor[0], red, green, blue);
3132 red = 0x1111 * ((color & 0xF00) >> 8);
3133 green = 0x1111 * ((color & 0x0F0) >> 4);
3134 blue = 0x1111 * ((color & 0x00F));
3135 SetRGB(pen_magicolor[1], red, green, blue);
3139 static void GameActions_MM_Ext(void)
3146 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3149 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3151 element = Tile[x][y];
3153 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3154 StartMoving_MM(x, y);
3155 else if (IS_MOVING(x, y))
3156 ContinueMoving_MM(x, y);
3157 else if (IS_EXPLODING(element))
3158 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3159 else if (element == EL_EXIT_OPENING)
3161 else if (element == EL_GRAY_BALL_OPENING)
3162 OpenSurpriseBall(x, y);
3163 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3165 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3168 DrawFieldAnimated_MM(x, y);
3171 AutoRotateMirrors();
3174 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3176 // redraw after Explode_MM() ...
3178 DrawLaser(0, DL_LASER_ENABLED);
3179 laser.redraw = FALSE;
3184 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3188 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3190 DrawLaser(0, DL_LASER_DISABLED);
3195 // skip all following game actions if game is over
3196 if (game_mm.game_over)
3199 if (FrameReached(&energy_delay))
3201 if (game_mm.energy_left > 0)
3203 game_mm.energy_left--;
3205 redraw_mask |= REDRAW_DOOR_1;
3207 else if (game.time_limit && !game_mm.game_over)
3209 FadeOutLaser(FALSE);
3211 GameOver_MM(GAME_OVER_NO_ENERGY);
3217 element = laser.dest_element;
3220 if (element != Tile[ELX][ELY])
3222 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3223 element, Tile[ELX][ELY]);
3227 if (!laser.overloaded && laser.overload_value == 0 &&
3228 element != EL_BOMB &&
3229 element != EL_MINE &&
3230 element != EL_BALL_GRAY &&
3231 element != EL_BLOCK_STONE &&
3232 element != EL_BLOCK_WOOD &&
3233 element != EL_FUSE_ON &&
3234 element != EL_FUEL_FULL &&
3235 !IS_WALL_ICE(element) &&
3236 !IS_WALL_AMOEBA(element))
3239 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3241 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3242 (!laser.overloaded && laser.overload_value > 0)) &&
3243 FrameReached(&overload_delay))
3245 if (laser.overloaded)
3246 laser.overload_value++;
3248 laser.overload_value--;
3250 if (game_mm.cheat_no_overload)
3252 laser.overloaded = FALSE;
3253 laser.overload_value = 0;
3256 game_mm.laser_overload_value = laser.overload_value;
3258 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3260 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3261 int color_down = 0xFF - color_up;
3264 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3265 (15 - (laser.overload_value / 6)) * color_scale);
3268 GetPixelFromRGB(window,
3269 (native_mm_level.laser_red ? 0xFF : color_up),
3270 (native_mm_level.laser_green ? color_down : 0x00),
3271 (native_mm_level.laser_blue ? color_down : 0x00));
3273 DrawLaser(0, DL_LASER_ENABLED);
3276 if (!laser.overloaded)
3277 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3278 else if (setup.sound_loops)
3279 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3281 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3283 if (laser.overloaded)
3286 BlitBitmap(pix[PIX_DOOR], drawto,
3287 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3288 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3289 - laser.overload_value,
3290 OVERLOAD_XSIZE, laser.overload_value,
3291 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3292 - laser.overload_value);
3294 redraw_mask |= REDRAW_DOOR_1;
3299 BlitBitmap(pix[PIX_DOOR], drawto,
3300 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3301 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3302 DX_OVERLOAD, DY_OVERLOAD);
3304 redraw_mask |= REDRAW_DOOR_1;
3307 if (laser.overload_value == MAX_LASER_OVERLOAD)
3309 UpdateAndDisplayGameControlValues();
3313 GameOver_MM(GAME_OVER_OVERLOADED);
3324 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3326 if (game_mm.cheat_no_explosion)
3331 laser.dest_element = EL_EXPLODING_OPAQUE;
3336 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3338 laser.fuse_off = TRUE;
3342 DrawLaser(0, DL_LASER_DISABLED);
3343 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3346 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3348 static int new_elements[] =
3351 EL_MIRROR_FIXED_START,
3353 EL_POLAR_CROSS_START,
3359 int num_new_elements = sizeof(new_elements) / sizeof(int);
3360 int new_element = new_elements[RND(num_new_elements)];
3362 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3363 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3365 // !!! CHECK AGAIN: Laser on Polarizer !!!
3376 element = EL_MIRROR_START + RND(16);
3382 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3389 element = (rnd == 0 ? EL_FUSE_ON :
3390 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3391 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3392 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3393 EL_MIRROR_FIXED_START + rnd - 25);
3398 graphic = el2gfx(element);
3400 for (i = 0; i < 50; i++)
3406 BlitBitmap(pix[PIX_BACK], drawto,
3407 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3408 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3409 SX + ELX * TILEX + x,
3410 SY + ELY * TILEY + y);
3412 MarkTileDirty(ELX, ELY);
3415 DrawLaser(0, DL_LASER_ENABLED);
3417 Delay_WithScreenUpdates(50);
3420 Tile[ELX][ELY] = element;
3421 DrawField_MM(ELX, ELY);
3424 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3427 // above stuff: GRAY BALL -> PRISM !!!
3429 LX = ELX * TILEX + 14;
3430 LY = ELY * TILEY + 14;
3431 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3438 laser.num_edges -= 2;
3439 laser.num_damages--;
3443 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3444 if (laser.damage[i].is_mirror)
3448 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3450 DrawLaser(0, DL_LASER_DISABLED);
3452 DrawLaser(0, DL_LASER_DISABLED);
3461 if (IS_WALL_ICE(element) && CT > 50)
3463 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3466 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3467 Store[ELX][ELY] = EL_WALL_ICE;
3468 Store2[ELX][ELY] = laser.wall_mask;
3470 laser.dest_element = Tile[ELX][ELY];
3475 for (i = 0; i < 5; i++)
3481 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3485 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3487 Delay_WithScreenUpdates(100);
3490 if (Tile[ELX][ELY] == EL_WALL_ICE)
3491 Tile[ELX][ELY] = EL_EMPTY;
3495 LX = laser.edge[laser.num_edges].x - cSX2;
3496 LY = laser.edge[laser.num_edges].y - cSY2;
3499 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3500 if (laser.damage[i].is_mirror)
3504 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3506 DrawLaser(0, DL_LASER_DISABLED);
3513 if (IS_WALL_AMOEBA(element) && CT > 60)
3515 int k1, k2, k3, dx, dy, de, dm;
3516 int element2 = Tile[ELX][ELY];
3518 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3521 for (i = laser.num_damages - 1; i >= 0; i--)
3522 if (laser.damage[i].is_mirror)
3525 r = laser.num_edges;
3526 d = laser.num_damages;
3533 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3536 DrawLaser(0, DL_LASER_ENABLED);
3539 x = laser.damage[k1].x;
3540 y = laser.damage[k1].y;
3545 for (i = 0; i < 4; i++)
3547 if (laser.wall_mask & (1 << i))
3549 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3550 cSY + ELY * TILEY + 31 * (i / 2)))
3553 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3554 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3561 for (i = 0; i < 4; i++)
3563 if (laser.wall_mask & (1 << i))
3565 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3566 cSY + ELY * TILEY + 31 * (i / 2)))
3573 if (laser.num_beamers > 0 ||
3574 k1 < 1 || k2 < 4 || k3 < 4 ||
3575 CheckLaserPixel(cSX + ELX * TILEX + 14,
3576 cSY + ELY * TILEY + 14))
3578 laser.num_edges = r;
3579 laser.num_damages = d;
3581 DrawLaser(0, DL_LASER_DISABLED);
3584 Tile[ELX][ELY] = element | laser.wall_mask;
3588 de = Tile[ELX][ELY];
3589 dm = laser.wall_mask;
3593 int x = ELX, y = ELY;
3594 int wall_mask = laser.wall_mask;
3597 DrawLaser(0, DL_LASER_ENABLED);
3599 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3601 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3602 Store[x][y] = EL_WALL_AMOEBA;
3603 Store2[x][y] = wall_mask;
3609 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3611 DrawLaser(0, DL_LASER_ENABLED);
3613 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3615 for (i = 4; i >= 0; i--)
3617 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3620 Delay_WithScreenUpdates(20);
3623 DrawLaser(0, DL_LASER_ENABLED);
3628 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3629 laser.stops_inside_element && CT > native_mm_level.time_block)
3634 if (ABS(XS) > ABS(YS))
3641 for (i = 0; i < 4; i++)
3648 x = ELX + Step[k * 4].x;
3649 y = ELY + Step[k * 4].y;
3651 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3654 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3662 laser.overloaded = (element == EL_BLOCK_STONE);
3667 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3670 Tile[x][y] = element;
3672 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3675 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3677 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3678 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3686 if (element == EL_FUEL_FULL && CT > 10)
3688 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3691 BlitBitmap(pix[PIX_DOOR], drawto,
3692 DOOR_GFX_PAGEX4 + XX_ENERGY,
3693 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3694 ENERGY_XSIZE, i, DX_ENERGY,
3695 DY_ENERGY + ENERGY_YSIZE - i);
3698 redraw_mask |= REDRAW_DOOR_1;
3701 Delay_WithScreenUpdates(20);
3704 game_mm.energy_left = MAX_LASER_ENERGY;
3705 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3706 DrawField_MM(ELX, ELY);
3708 DrawLaser(0, DL_LASER_ENABLED);
3716 void GameActions_MM(struct MouseActionInfo action)
3718 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3719 boolean button_released = (action.button == MB_RELEASED);
3721 GameActions_MM_Ext();
3723 CheckSingleStepMode_MM(element_clicked, button_released);
3726 void MovePacMen(void)
3728 int mx, my, ox, oy, nx, ny;
3732 if (++pacman_nr >= game_mm.num_pacman)
3735 game_mm.pacman[pacman_nr].dir--;
3737 for (l = 1; l < 5; l++)
3739 game_mm.pacman[pacman_nr].dir++;
3741 if (game_mm.pacman[pacman_nr].dir > 4)
3742 game_mm.pacman[pacman_nr].dir = 1;
3744 if (game_mm.pacman[pacman_nr].dir % 2)
3747 my = game_mm.pacman[pacman_nr].dir - 2;
3752 mx = 3 - game_mm.pacman[pacman_nr].dir;
3755 ox = game_mm.pacman[pacman_nr].x;
3756 oy = game_mm.pacman[pacman_nr].y;
3759 element = Tile[nx][ny];
3761 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3764 if (!IS_EATABLE4PACMAN(element))
3767 if (ObjHit(nx, ny, HIT_POS_CENTER))
3770 Tile[ox][oy] = EL_EMPTY;
3772 EL_PACMAN_RIGHT - 1 +
3773 (game_mm.pacman[pacman_nr].dir - 1 +
3774 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3776 game_mm.pacman[pacman_nr].x = nx;
3777 game_mm.pacman[pacman_nr].y = ny;
3779 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3781 if (element != EL_EMPTY)
3783 int graphic = el2gfx(Tile[nx][ny]);
3788 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3791 ox = cSX + ox * TILEX;
3792 oy = cSY + oy * TILEY;
3794 for (i = 1; i < 33; i += 2)
3795 BlitBitmap(bitmap, window,
3796 src_x, src_y, TILEX, TILEY,
3797 ox + i * mx, oy + i * my);
3798 Ct = Ct + FrameCounter - CT;
3801 DrawField_MM(nx, ny);
3804 if (!laser.fuse_off)
3806 DrawLaser(0, DL_LASER_ENABLED);
3808 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3810 AddDamagedField(nx, ny);
3812 laser.damage[laser.num_damages - 1].edge = 0;
3816 if (element == EL_BOMB)
3817 DeletePacMan(nx, ny);
3819 if (IS_WALL_AMOEBA(element) &&
3820 (LX + 2 * XS) / TILEX == nx &&
3821 (LY + 2 * YS) / TILEY == ny)
3831 static void InitMovingField_MM(int x, int y, int direction)
3833 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3834 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3836 MovDir[x][y] = direction;
3837 MovDir[newx][newy] = direction;
3839 if (Tile[newx][newy] == EL_EMPTY)
3840 Tile[newx][newy] = EL_BLOCKED;
3843 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3845 int direction = MovDir[x][y];
3846 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3847 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3853 static void Blocked2Moving_MM(int x, int y,
3854 int *comes_from_x, int *comes_from_y)
3856 int oldx = x, oldy = y;
3857 int direction = MovDir[x][y];
3859 if (direction == MV_LEFT)
3861 else if (direction == MV_RIGHT)
3863 else if (direction == MV_UP)
3865 else if (direction == MV_DOWN)
3868 *comes_from_x = oldx;
3869 *comes_from_y = oldy;
3872 static int MovingOrBlocked2Element_MM(int x, int y)
3874 int element = Tile[x][y];
3876 if (element == EL_BLOCKED)
3880 Blocked2Moving_MM(x, y, &oldx, &oldy);
3882 return Tile[oldx][oldy];
3889 static void RemoveField(int x, int y)
3891 Tile[x][y] = EL_EMPTY;
3898 static void RemoveMovingField_MM(int x, int y)
3900 int oldx = x, oldy = y, newx = x, newy = y;
3902 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3905 if (IS_MOVING(x, y))
3907 Moving2Blocked_MM(x, y, &newx, &newy);
3908 if (Tile[newx][newy] != EL_BLOCKED)
3911 else if (Tile[x][y] == EL_BLOCKED)
3913 Blocked2Moving_MM(x, y, &oldx, &oldy);
3914 if (!IS_MOVING(oldx, oldy))
3918 Tile[oldx][oldy] = EL_EMPTY;
3919 Tile[newx][newy] = EL_EMPTY;
3920 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3921 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3923 DrawLevelField_MM(oldx, oldy);
3924 DrawLevelField_MM(newx, newy);
3927 void PlaySoundLevel(int x, int y, int sound_nr)
3929 int sx = SCREENX(x), sy = SCREENY(y);
3931 int silence_distance = 8;
3933 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3934 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3937 if (!IN_LEV_FIELD(x, y) ||
3938 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3939 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3942 volume = SOUND_MAX_VOLUME;
3945 stereo = (sx - SCR_FIELDX/2) * 12;
3947 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3948 if (stereo > SOUND_MAX_RIGHT)
3949 stereo = SOUND_MAX_RIGHT;
3950 if (stereo < SOUND_MAX_LEFT)
3951 stereo = SOUND_MAX_LEFT;
3954 if (!IN_SCR_FIELD(sx, sy))
3956 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3957 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3959 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3962 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3965 static void RaiseScore_MM(int value)
3967 game_mm.score += value;
3970 void RaiseScoreElement_MM(int element)
3975 case EL_PACMAN_RIGHT:
3977 case EL_PACMAN_LEFT:
3978 case EL_PACMAN_DOWN:
3979 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3983 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3988 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3992 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4001 // ----------------------------------------------------------------------------
4002 // Mirror Magic game engine snapshot handling functions
4003 // ----------------------------------------------------------------------------
4005 void SaveEngineSnapshotValues_MM(void)
4009 engine_snapshot_mm.game_mm = game_mm;
4010 engine_snapshot_mm.laser = laser;
4012 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4014 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4016 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4017 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4018 engine_snapshot_mm.Box[x][y] = Box[x][y];
4019 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4020 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4024 engine_snapshot_mm.LX = LX;
4025 engine_snapshot_mm.LY = LY;
4026 engine_snapshot_mm.XS = XS;
4027 engine_snapshot_mm.YS = YS;
4028 engine_snapshot_mm.ELX = ELX;
4029 engine_snapshot_mm.ELY = ELY;
4030 engine_snapshot_mm.CT = CT;
4031 engine_snapshot_mm.Ct = Ct;
4033 engine_snapshot_mm.last_LX = last_LX;
4034 engine_snapshot_mm.last_LY = last_LY;
4035 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4036 engine_snapshot_mm.hold_x = hold_x;
4037 engine_snapshot_mm.hold_y = hold_y;
4038 engine_snapshot_mm.pacman_nr = pacman_nr;
4040 engine_snapshot_mm.rotate_delay = rotate_delay;
4041 engine_snapshot_mm.pacman_delay = pacman_delay;
4042 engine_snapshot_mm.energy_delay = energy_delay;
4043 engine_snapshot_mm.overload_delay = overload_delay;
4046 void LoadEngineSnapshotValues_MM(void)
4050 // stored engine snapshot buffers already restored at this point
4052 game_mm = engine_snapshot_mm.game_mm;
4053 laser = engine_snapshot_mm.laser;
4055 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4057 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4059 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4060 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4061 Box[x][y] = engine_snapshot_mm.Box[x][y];
4062 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4063 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4067 LX = engine_snapshot_mm.LX;
4068 LY = engine_snapshot_mm.LY;
4069 XS = engine_snapshot_mm.XS;
4070 YS = engine_snapshot_mm.YS;
4071 ELX = engine_snapshot_mm.ELX;
4072 ELY = engine_snapshot_mm.ELY;
4073 CT = engine_snapshot_mm.CT;
4074 Ct = engine_snapshot_mm.Ct;
4076 last_LX = engine_snapshot_mm.last_LX;
4077 last_LY = engine_snapshot_mm.last_LY;
4078 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4079 hold_x = engine_snapshot_mm.hold_x;
4080 hold_y = engine_snapshot_mm.hold_y;
4081 pacman_nr = engine_snapshot_mm.pacman_nr;
4083 rotate_delay = engine_snapshot_mm.rotate_delay;
4084 pacman_delay = engine_snapshot_mm.pacman_delay;
4085 energy_delay = engine_snapshot_mm.energy_delay;
4086 overload_delay = engine_snapshot_mm.overload_delay;
4088 RedrawPlayfield_MM();
4091 static int getAngleFromTouchDelta(int dx, int dy, int base)
4093 double pi = 3.141592653;
4094 double rad = atan2((double)-dy, (double)dx);
4095 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4096 double deg = rad2 * 180.0 / pi;
4098 return (int)(deg * base / 360.0 + 0.5) % base;
4101 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4103 // calculate start (source) position to be at the middle of the tile
4104 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4105 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4106 int dx = dst_mx - src_mx;
4107 int dy = dst_my - src_my;
4116 if (!IN_LEV_FIELD(x, y))
4119 element = Tile[x][y];
4121 if (!IS_MCDUFFIN(element) &&
4122 !IS_MIRROR(element) &&
4123 !IS_BEAMER(element) &&
4124 !IS_POLAR(element) &&
4125 !IS_POLAR_CROSS(element) &&
4126 !IS_DF_MIRROR(element))
4129 angle_old = get_element_angle(element);
4131 if (IS_MCDUFFIN(element))
4133 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4134 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4135 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4136 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4139 else if (IS_MIRROR(element) ||
4140 IS_DF_MIRROR(element))
4142 for (i = 0; i < laser.num_damages; i++)
4144 if (laser.damage[i].x == x &&
4145 laser.damage[i].y == y &&
4146 ObjHit(x, y, HIT_POS_CENTER))
4148 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4149 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4156 if (angle_new == -1)
4158 if (IS_MIRROR(element) ||
4159 IS_DF_MIRROR(element) ||
4163 if (IS_POLAR_CROSS(element))
4166 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4169 button = (angle_new == angle_old ? 0 :
4170 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4171 MB_LEFTBUTTON : MB_RIGHTBUTTON);