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 void AddLaserEdge(int lx, int ly)
748 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
750 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
755 laser.edge[laser.num_edges].x = cSX2 + lx;
756 laser.edge[laser.num_edges].y = cSY2 + ly;
762 void AddDamagedField(int ex, int ey)
764 laser.damage[laser.num_damages].is_mirror = FALSE;
765 laser.damage[laser.num_damages].angle = laser.current_angle;
766 laser.damage[laser.num_damages].edge = laser.num_edges;
767 laser.damage[laser.num_damages].x = ex;
768 laser.damage[laser.num_damages].y = ey;
772 static boolean StepBehind(void)
778 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
779 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
781 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
787 static int getMaskFromElement(int element)
789 if (IS_GRID(element))
790 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
791 else if (IS_MCDUFFIN(element))
792 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
793 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
794 return IMG_MM_MASK_RECTANGLE;
796 return IMG_MM_MASK_CIRCLE;
799 static int ScanPixel(void)
804 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
805 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
808 // follow laser beam until it hits something (at least the screen border)
809 while (hit_mask == HIT_MASK_NO_HIT)
815 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
816 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
818 Debug("game:mm:ScanPixel", "touched screen border!");
824 for (i = 0; i < 4; i++)
826 int px = LX + (i % 2) * 2;
827 int py = LY + (i / 2) * 2;
830 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
831 int ly = (py + TILEY) / TILEY - 1; // negative values!
834 if (IN_LEV_FIELD(lx, ly))
836 int element = Tile[lx][ly];
838 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
842 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
844 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
846 pixel = ((element & (1 << pos)) ? 1 : 0);
850 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
852 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
857 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
858 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
861 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
862 hit_mask |= (1 << i);
865 if (hit_mask == HIT_MASK_NO_HIT)
867 // hit nothing -- go on with another step
879 int end = 0, rf = laser.num_edges;
881 // do not scan laser again after the game was lost for whatever reason
882 if (game_mm.game_over)
885 laser.overloaded = FALSE;
886 laser.stops_inside_element = FALSE;
888 DrawLaser(0, DL_LASER_ENABLED);
891 Debug("game:mm:ScanLaser",
892 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
900 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
903 laser.overloaded = TRUE;
908 hit_mask = ScanPixel();
911 Debug("game:mm:ScanLaser",
912 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
916 // hit something -- check out what it was
917 ELX = (LX + XS) / TILEX;
918 ELY = (LY + YS) / TILEY;
921 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
922 hit_mask, LX, LY, ELX, ELY);
925 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
928 laser.dest_element = element;
933 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
935 /* we have hit the top-right and bottom-left element --
936 choose the bottom-left one */
937 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
938 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
939 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
940 ELX = (LX - 2) / TILEX;
941 ELY = (LY + 2) / TILEY;
944 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
946 /* we have hit the top-left and bottom-right element --
947 choose the top-left one */
949 ELX = (LX - 2) / TILEX;
950 ELY = (LY - 2) / TILEY;
954 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
955 hit_mask, LX, LY, ELX, ELY);
958 element = Tile[ELX][ELY];
959 laser.dest_element = element;
962 Debug("game:mm:ScanLaser",
963 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
966 LX % TILEX, LY % TILEY,
971 if (!IN_LEV_FIELD(ELX, ELY))
972 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
976 if (element == EL_EMPTY)
978 if (!HitOnlyAnEdge(hit_mask))
981 else if (element == EL_FUSE_ON)
983 if (HitPolarizer(element, hit_mask))
986 else if (IS_GRID(element) || IS_DF_GRID(element))
988 if (HitPolarizer(element, hit_mask))
991 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
992 element == EL_GATE_STONE || element == EL_GATE_WOOD)
994 if (HitBlock(element, hit_mask))
1001 else if (IS_MCDUFFIN(element))
1003 if (HitLaserSource(element, hit_mask))
1006 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1007 IS_RECEIVER(element))
1009 if (HitLaserDestination(element, hit_mask))
1012 else if (IS_WALL(element))
1014 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1016 if (HitReflectingWalls(element, hit_mask))
1021 if (HitAbsorbingWalls(element, hit_mask))
1027 if (HitElement(element, hit_mask))
1032 DrawLaser(rf - 1, DL_LASER_ENABLED);
1033 rf = laser.num_edges;
1037 if (laser.dest_element != Tile[ELX][ELY])
1039 Debug("game:mm:ScanLaser",
1040 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1041 laser.dest_element, Tile[ELX][ELY]);
1045 if (!end && !laser.stops_inside_element && !StepBehind())
1048 Debug("game:mm:ScanLaser", "Go one step back");
1054 AddLaserEdge(LX, LY);
1058 DrawLaser(rf - 1, DL_LASER_ENABLED);
1060 Ct = CT = FrameCounter;
1063 if (!IN_LEV_FIELD(ELX, ELY))
1064 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1068 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1074 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1075 start_edge, num_edges, mode);
1080 Warn("DrawLaserExt: start_edge < 0");
1087 Warn("DrawLaserExt: num_edges < 0");
1093 if (mode == DL_LASER_DISABLED)
1095 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1099 // now draw the laser to the backbuffer and (if enabled) to the screen
1100 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1102 redraw_mask |= REDRAW_FIELD;
1104 if (mode == DL_LASER_ENABLED)
1107 // after the laser was deleted, the "damaged" graphics must be restored
1108 if (laser.num_damages)
1110 int damage_start = 0;
1113 // determine the starting edge, from which graphics need to be restored
1116 for (i = 0; i < laser.num_damages; i++)
1118 if (laser.damage[i].edge == start_edge + 1)
1127 // restore graphics from this starting edge to the end of damage list
1128 for (i = damage_start; i < laser.num_damages; i++)
1130 int lx = laser.damage[i].x;
1131 int ly = laser.damage[i].y;
1132 int element = Tile[lx][ly];
1134 if (Hit[lx][ly] == laser.damage[i].edge)
1135 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1138 if (Box[lx][ly] == laser.damage[i].edge)
1141 if (IS_DRAWABLE(element))
1142 DrawField_MM(lx, ly);
1145 elx = laser.damage[damage_start].x;
1146 ely = laser.damage[damage_start].y;
1147 element = Tile[elx][ely];
1150 if (IS_BEAMER(element))
1154 for (i = 0; i < laser.num_beamers; i++)
1155 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1157 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1158 mode, elx, ely, Hit[elx][ely], start_edge);
1159 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1160 get_element_angle(element), laser.damage[damage_start].angle);
1164 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1165 laser.num_beamers > 0 &&
1166 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1168 // element is outgoing beamer
1169 laser.num_damages = damage_start + 1;
1171 if (IS_BEAMER(element))
1172 laser.current_angle = get_element_angle(element);
1176 // element is incoming beamer or other element
1177 laser.num_damages = damage_start;
1178 laser.current_angle = laser.damage[laser.num_damages].angle;
1183 // no damages but McDuffin himself (who needs to be redrawn anyway)
1185 elx = laser.start_edge.x;
1186 ely = laser.start_edge.y;
1187 element = Tile[elx][ely];
1190 laser.num_edges = start_edge + 1;
1191 if (start_edge == 0)
1192 laser.current_angle = laser.start_angle;
1194 LX = laser.edge[start_edge].x - cSX2;
1195 LY = laser.edge[start_edge].y - cSY2;
1196 XS = 2 * Step[laser.current_angle].x;
1197 YS = 2 * Step[laser.current_angle].y;
1200 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1206 if (IS_BEAMER(element) ||
1207 IS_FIBRE_OPTIC(element) ||
1208 IS_PACMAN(element) ||
1209 IS_POLAR(element) ||
1210 IS_POLAR_CROSS(element) ||
1211 element == EL_FUSE_ON)
1216 Debug("game:mm:DrawLaserExt", "element == %d", element);
1219 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1220 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1224 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1225 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1226 (laser.num_beamers == 0 ||
1227 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1229 // element is incoming beamer or other element
1230 step_size = -step_size;
1235 if (IS_BEAMER(element))
1236 Debug("game:mm:DrawLaserExt",
1237 "start_edge == %d, laser.beamer_edge == %d",
1238 start_edge, laser.beamer_edge);
1241 LX += step_size * XS;
1242 LY += step_size * YS;
1244 else if (element != EL_EMPTY)
1253 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1258 void DrawLaser(int start_edge, int mode)
1260 if (laser.num_edges - start_edge < 0)
1262 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1267 // check if laser is interrupted by beamer element
1268 if (laser.num_beamers > 0 &&
1269 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1271 if (mode == DL_LASER_ENABLED)
1274 int tmp_start_edge = start_edge;
1276 // draw laser segments forward from the start to the last beamer
1277 for (i = 0; i < laser.num_beamers; i++)
1279 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1281 if (tmp_num_edges <= 0)
1285 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1286 i, laser.beamer_edge[i], tmp_start_edge);
1289 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1291 tmp_start_edge = laser.beamer_edge[i];
1294 // draw last segment from last beamer to the end
1295 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1301 int last_num_edges = laser.num_edges;
1302 int num_beamers = laser.num_beamers;
1304 // delete laser segments backward from the end to the first beamer
1305 for (i = num_beamers - 1; i >= 0; i--)
1307 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1309 if (laser.beamer_edge[i] - start_edge <= 0)
1312 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1314 last_num_edges = laser.beamer_edge[i];
1315 laser.num_beamers--;
1319 if (last_num_edges - start_edge <= 0)
1320 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1321 last_num_edges, start_edge);
1324 // special case when rotating first beamer: delete laser edge on beamer
1325 // (but do not start scanning on previous edge to prevent mirror sound)
1326 if (last_num_edges - start_edge == 1 && start_edge > 0)
1327 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1329 // delete first segment from start to the first beamer
1330 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1335 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1338 game_mm.laser_enabled = mode;
1341 void DrawLaser_MM(void)
1343 DrawLaser(0, game_mm.laser_enabled);
1346 boolean HitElement(int element, int hit_mask)
1348 if (HitOnlyAnEdge(hit_mask))
1351 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1352 element = MovingOrBlocked2Element_MM(ELX, ELY);
1355 Debug("game:mm:HitElement", "(1): element == %d", element);
1359 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1360 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1363 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1367 AddDamagedField(ELX, ELY);
1369 // this is more precise: check if laser would go through the center
1370 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1372 // skip the whole element before continuing the scan
1378 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1380 if (LX/TILEX > ELX || LY/TILEY > ELY)
1382 /* skipping scan positions to the right and down skips one scan
1383 position too much, because this is only the top left scan position
1384 of totally four scan positions (plus one to the right, one to the
1385 bottom and one to the bottom right) */
1395 Debug("game:mm:HitElement", "(2): element == %d", element);
1398 if (LX + 5 * XS < 0 ||
1408 Debug("game:mm:HitElement", "(3): element == %d", element);
1411 if (IS_POLAR(element) &&
1412 ((element - EL_POLAR_START) % 2 ||
1413 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1415 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1417 laser.num_damages--;
1422 if (IS_POLAR_CROSS(element) &&
1423 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1425 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1427 laser.num_damages--;
1432 if (!IS_BEAMER(element) &&
1433 !IS_FIBRE_OPTIC(element) &&
1434 !IS_GRID_WOOD(element) &&
1435 element != EL_FUEL_EMPTY)
1438 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1439 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1441 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1444 LX = ELX * TILEX + 14;
1445 LY = ELY * TILEY + 14;
1447 AddLaserEdge(LX, LY);
1450 if (IS_MIRROR(element) ||
1451 IS_MIRROR_FIXED(element) ||
1452 IS_POLAR(element) ||
1453 IS_POLAR_CROSS(element) ||
1454 IS_DF_MIRROR(element) ||
1455 IS_DF_MIRROR_AUTO(element) ||
1456 element == EL_PRISM ||
1457 element == EL_REFRACTOR)
1459 int current_angle = laser.current_angle;
1462 laser.num_damages--;
1464 AddDamagedField(ELX, ELY);
1466 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1469 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1471 if (IS_MIRROR(element) ||
1472 IS_MIRROR_FIXED(element) ||
1473 IS_DF_MIRROR(element) ||
1474 IS_DF_MIRROR_AUTO(element))
1475 laser.current_angle = get_mirrored_angle(laser.current_angle,
1476 get_element_angle(element));
1478 if (element == EL_PRISM || element == EL_REFRACTOR)
1479 laser.current_angle = RND(16);
1481 XS = 2 * Step[laser.current_angle].x;
1482 YS = 2 * Step[laser.current_angle].y;
1484 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1489 LX += step_size * XS;
1490 LY += step_size * YS;
1492 // draw sparkles on mirror
1493 if ((IS_MIRROR(element) ||
1494 IS_MIRROR_FIXED(element) ||
1495 element == EL_PRISM) &&
1496 current_angle != laser.current_angle)
1498 MovDelay[ELX][ELY] = 11; // start animation
1501 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1502 current_angle != laser.current_angle)
1503 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1506 (get_opposite_angle(laser.current_angle) ==
1507 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1509 return (laser.overloaded ? TRUE : FALSE);
1512 if (element == EL_FUEL_FULL)
1514 laser.stops_inside_element = TRUE;
1519 if (element == EL_BOMB || element == EL_MINE)
1521 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1523 if (element == EL_MINE)
1524 laser.overloaded = TRUE;
1527 if (element == EL_KETTLE ||
1528 element == EL_CELL ||
1529 element == EL_KEY ||
1530 element == EL_LIGHTBALL ||
1531 element == EL_PACMAN ||
1534 if (!IS_PACMAN(element))
1537 if (element == EL_PACMAN)
1540 if (element == EL_KETTLE || element == EL_CELL)
1542 if (game_mm.kettles_still_needed > 0)
1543 game_mm.kettles_still_needed--;
1545 game.snapshot.collected_item = TRUE;
1547 if (game_mm.kettles_still_needed == 0)
1551 DrawLaser(0, DL_LASER_ENABLED);
1554 else if (element == EL_KEY)
1558 else if (IS_PACMAN(element))
1560 DeletePacMan(ELX, ELY);
1563 RaiseScoreElement_MM(element);
1568 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1570 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1572 DrawLaser(0, DL_LASER_ENABLED);
1574 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1576 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1577 game_mm.lights_still_needed--;
1581 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1582 game_mm.lights_still_needed++;
1585 DrawField_MM(ELX, ELY);
1586 DrawLaser(0, DL_LASER_ENABLED);
1591 laser.stops_inside_element = TRUE;
1597 Debug("game:mm:HitElement", "(4): element == %d", element);
1600 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1601 laser.num_beamers < MAX_NUM_BEAMERS &&
1602 laser.beamer[BEAMER_NR(element)][1].num)
1604 int beamer_angle = get_element_angle(element);
1605 int beamer_nr = BEAMER_NR(element);
1609 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1612 laser.num_damages--;
1614 if (IS_FIBRE_OPTIC(element) ||
1615 laser.current_angle == get_opposite_angle(beamer_angle))
1619 LX = ELX * TILEX + 14;
1620 LY = ELY * TILEY + 14;
1622 AddLaserEdge(LX, LY);
1623 AddDamagedField(ELX, ELY);
1625 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1628 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1630 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1631 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1632 ELX = laser.beamer[beamer_nr][pos].x;
1633 ELY = laser.beamer[beamer_nr][pos].y;
1634 LX = ELX * TILEX + 14;
1635 LY = ELY * TILEY + 14;
1637 if (IS_BEAMER(element))
1639 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1640 XS = 2 * Step[laser.current_angle].x;
1641 YS = 2 * Step[laser.current_angle].y;
1644 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1646 AddLaserEdge(LX, LY);
1647 AddDamagedField(ELX, ELY);
1649 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1652 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1654 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1659 LX += step_size * XS;
1660 LY += step_size * YS;
1662 laser.num_beamers++;
1671 boolean HitOnlyAnEdge(int hit_mask)
1673 // check if the laser hit only the edge of an element and, if so, go on
1676 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1680 if ((hit_mask == HIT_MASK_TOPLEFT ||
1681 hit_mask == HIT_MASK_TOPRIGHT ||
1682 hit_mask == HIT_MASK_BOTTOMLEFT ||
1683 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1684 laser.current_angle % 4) // angle is not 90°
1688 if (hit_mask == HIT_MASK_TOPLEFT)
1693 else if (hit_mask == HIT_MASK_TOPRIGHT)
1698 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1703 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1709 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1715 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1722 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1728 boolean HitPolarizer(int element, int hit_mask)
1730 if (HitOnlyAnEdge(hit_mask))
1733 if (IS_DF_GRID(element))
1735 int grid_angle = get_element_angle(element);
1738 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1739 grid_angle, laser.current_angle);
1742 AddLaserEdge(LX, LY);
1743 AddDamagedField(ELX, ELY);
1746 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1748 if (laser.current_angle == grid_angle ||
1749 laser.current_angle == get_opposite_angle(grid_angle))
1751 // skip the whole element before continuing the scan
1757 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1759 if (LX/TILEX > ELX || LY/TILEY > ELY)
1761 /* skipping scan positions to the right and down skips one scan
1762 position too much, because this is only the top left scan position
1763 of totally four scan positions (plus one to the right, one to the
1764 bottom and one to the bottom right) */
1770 AddLaserEdge(LX, LY);
1776 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1778 LX / TILEX, LY / TILEY,
1779 LX % TILEX, LY % TILEY);
1784 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1786 return HitReflectingWalls(element, hit_mask);
1790 return HitAbsorbingWalls(element, hit_mask);
1793 else if (IS_GRID_STEEL(element))
1795 return HitReflectingWalls(element, hit_mask);
1797 else // IS_GRID_WOOD
1799 return HitAbsorbingWalls(element, hit_mask);
1805 boolean HitBlock(int element, int hit_mask)
1807 boolean check = FALSE;
1809 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1810 game_mm.num_keys == 0)
1813 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1816 int ex = ELX * TILEX + 14;
1817 int ey = ELY * TILEY + 14;
1821 for (i = 1; i < 32; i++)
1826 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1831 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1832 return HitAbsorbingWalls(element, hit_mask);
1836 AddLaserEdge(LX - XS, LY - YS);
1837 AddDamagedField(ELX, ELY);
1840 Box[ELX][ELY] = laser.num_edges;
1842 return HitReflectingWalls(element, hit_mask);
1845 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1847 int xs = XS / 2, ys = YS / 2;
1848 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1849 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1851 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1852 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1854 laser.overloaded = (element == EL_GATE_STONE);
1859 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1860 (hit_mask == HIT_MASK_TOP ||
1861 hit_mask == HIT_MASK_LEFT ||
1862 hit_mask == HIT_MASK_RIGHT ||
1863 hit_mask == HIT_MASK_BOTTOM))
1864 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1865 hit_mask == HIT_MASK_BOTTOM),
1866 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1867 hit_mask == HIT_MASK_RIGHT));
1868 AddLaserEdge(LX, LY);
1874 if (element == EL_GATE_STONE && Box[ELX][ELY])
1876 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1888 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1890 int xs = XS / 2, ys = YS / 2;
1891 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1892 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1894 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1895 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1897 laser.overloaded = (element == EL_BLOCK_STONE);
1902 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1903 (hit_mask == HIT_MASK_TOP ||
1904 hit_mask == HIT_MASK_LEFT ||
1905 hit_mask == HIT_MASK_RIGHT ||
1906 hit_mask == HIT_MASK_BOTTOM))
1907 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1908 hit_mask == HIT_MASK_BOTTOM),
1909 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1910 hit_mask == HIT_MASK_RIGHT));
1911 AddDamagedField(ELX, ELY);
1913 LX = ELX * TILEX + 14;
1914 LY = ELY * TILEY + 14;
1916 AddLaserEdge(LX, LY);
1918 laser.stops_inside_element = TRUE;
1926 boolean HitLaserSource(int element, int hit_mask)
1928 if (HitOnlyAnEdge(hit_mask))
1931 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1933 laser.overloaded = TRUE;
1938 boolean HitLaserDestination(int element, int hit_mask)
1940 if (HitOnlyAnEdge(hit_mask))
1943 if (element != EL_EXIT_OPEN &&
1944 !(IS_RECEIVER(element) &&
1945 game_mm.kettles_still_needed == 0 &&
1946 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1948 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1953 if (IS_RECEIVER(element) ||
1954 (IS_22_5_ANGLE(laser.current_angle) &&
1955 (ELX != (LX + 6 * XS) / TILEX ||
1956 ELY != (LY + 6 * YS) / TILEY ||
1965 LX = ELX * TILEX + 14;
1966 LY = ELY * TILEY + 14;
1968 laser.stops_inside_element = TRUE;
1971 AddLaserEdge(LX, LY);
1972 AddDamagedField(ELX, ELY);
1974 if (game_mm.lights_still_needed == 0)
1976 game_mm.level_solved = TRUE;
1978 SetTileCursorActive(FALSE);
1984 boolean HitReflectingWalls(int element, int hit_mask)
1986 // check if laser hits side of a wall with an angle that is not 90°
1987 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1988 hit_mask == HIT_MASK_LEFT ||
1989 hit_mask == HIT_MASK_RIGHT ||
1990 hit_mask == HIT_MASK_BOTTOM))
1992 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1997 if (!IS_DF_GRID(element))
1998 AddLaserEdge(LX, LY);
2000 // check if laser hits wall with an angle of 45°
2001 if (!IS_22_5_ANGLE(laser.current_angle))
2003 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2006 laser.current_angle = get_mirrored_angle(laser.current_angle,
2009 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2012 laser.current_angle = get_mirrored_angle(laser.current_angle,
2016 AddLaserEdge(LX, LY);
2018 XS = 2 * Step[laser.current_angle].x;
2019 YS = 2 * Step[laser.current_angle].y;
2023 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2025 laser.current_angle = get_mirrored_angle(laser.current_angle,
2030 if (!IS_DF_GRID(element))
2031 AddLaserEdge(LX, LY);
2036 if (!IS_DF_GRID(element))
2037 AddLaserEdge(LX, LY + YS / 2);
2040 if (!IS_DF_GRID(element))
2041 AddLaserEdge(LX, LY);
2044 YS = 2 * Step[laser.current_angle].y;
2048 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2050 laser.current_angle = get_mirrored_angle(laser.current_angle,
2055 if (!IS_DF_GRID(element))
2056 AddLaserEdge(LX, LY);
2061 if (!IS_DF_GRID(element))
2062 AddLaserEdge(LX + XS / 2, LY);
2065 if (!IS_DF_GRID(element))
2066 AddLaserEdge(LX, LY);
2069 XS = 2 * Step[laser.current_angle].x;
2075 // reflection at the edge of reflecting DF style wall
2076 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2078 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2079 hit_mask == HIT_MASK_TOPRIGHT) ||
2080 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2081 hit_mask == HIT_MASK_TOPLEFT) ||
2082 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2083 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2084 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2085 hit_mask == HIT_MASK_BOTTOMRIGHT))
2088 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2089 ANG_MIRROR_135 : ANG_MIRROR_45);
2091 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2093 AddDamagedField(ELX, ELY);
2094 AddLaserEdge(LX, LY);
2096 laser.current_angle = get_mirrored_angle(laser.current_angle,
2104 AddLaserEdge(LX, LY);
2110 // reflection inside an edge of reflecting DF style wall
2111 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2113 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2114 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2115 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2116 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2117 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2118 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2119 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2120 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2123 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2124 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2125 ANG_MIRROR_135 : ANG_MIRROR_45);
2127 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2130 AddDamagedField(ELX, ELY);
2133 AddLaserEdge(LX - XS, LY - YS);
2134 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2135 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2137 laser.current_angle = get_mirrored_angle(laser.current_angle,
2145 AddLaserEdge(LX, LY);
2151 // check if laser hits DF style wall with an angle of 90°
2152 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2154 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2155 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2156 (IS_VERT_ANGLE(laser.current_angle) &&
2157 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2159 // laser at last step touched nothing or the same side of the wall
2160 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2162 AddDamagedField(ELX, ELY);
2169 last_hit_mask = hit_mask;
2176 if (!HitOnlyAnEdge(hit_mask))
2178 laser.overloaded = TRUE;
2186 boolean HitAbsorbingWalls(int element, int hit_mask)
2188 if (HitOnlyAnEdge(hit_mask))
2192 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2194 AddLaserEdge(LX - XS, LY - YS);
2201 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2203 AddLaserEdge(LX - XS, LY - YS);
2209 if (IS_WALL_WOOD(element) ||
2210 IS_DF_WALL_WOOD(element) ||
2211 IS_GRID_WOOD(element) ||
2212 IS_GRID_WOOD_FIXED(element) ||
2213 IS_GRID_WOOD_AUTO(element) ||
2214 element == EL_FUSE_ON ||
2215 element == EL_BLOCK_WOOD ||
2216 element == EL_GATE_WOOD)
2218 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2223 if (IS_WALL_ICE(element))
2227 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2228 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2230 // check if laser hits wall with an angle of 90°
2231 if (IS_90_ANGLE(laser.current_angle))
2232 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2234 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2238 for (i = 0; i < 4; i++)
2240 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2241 mask = 15 - (8 >> i);
2242 else if (ABS(XS) == 4 &&
2244 (XS > 0) == (i % 2) &&
2245 (YS < 0) == (i / 2))
2246 mask = 3 + (i / 2) * 9;
2247 else if (ABS(YS) == 4 &&
2249 (XS < 0) == (i % 2) &&
2250 (YS > 0) == (i / 2))
2251 mask = 5 + (i % 2) * 5;
2255 laser.wall_mask = mask;
2257 else if (IS_WALL_AMOEBA(element))
2259 int elx = (LX - 2 * XS) / TILEX;
2260 int ely = (LY - 2 * YS) / TILEY;
2261 int element2 = Tile[elx][ely];
2264 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2266 laser.dest_element = EL_EMPTY;
2274 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2275 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2277 if (IS_90_ANGLE(laser.current_angle))
2278 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2280 laser.dest_element = element2 | EL_WALL_AMOEBA;
2282 laser.wall_mask = mask;
2288 static void OpenExit(int x, int y)
2292 if (!MovDelay[x][y]) // next animation frame
2293 MovDelay[x][y] = 4 * delay;
2295 if (MovDelay[x][y]) // wait some time before next frame
2300 phase = MovDelay[x][y] / delay;
2302 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2303 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2305 if (!MovDelay[x][y])
2307 Tile[x][y] = EL_EXIT_OPEN;
2313 static void OpenSurpriseBall(int x, int y)
2317 if (!MovDelay[x][y]) // next animation frame
2318 MovDelay[x][y] = 50 * delay;
2320 if (MovDelay[x][y]) // wait some time before next frame
2324 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2327 int graphic = el2gfx(Store[x][y]);
2329 int dx = RND(26), dy = RND(26);
2331 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2333 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2334 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2336 MarkTileDirty(x, y);
2339 if (!MovDelay[x][y])
2341 Tile[x][y] = Store[x][y];
2350 static void MeltIce(int x, int y)
2355 if (!MovDelay[x][y]) // next animation frame
2356 MovDelay[x][y] = frames * delay;
2358 if (MovDelay[x][y]) // wait some time before next frame
2361 int wall_mask = Store2[x][y];
2362 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2365 phase = frames - MovDelay[x][y] / delay - 1;
2367 if (!MovDelay[x][y])
2371 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2372 Store[x][y] = Store2[x][y] = 0;
2374 DrawWalls_MM(x, y, Tile[x][y]);
2376 if (Tile[x][y] == EL_WALL_ICE)
2377 Tile[x][y] = EL_EMPTY;
2379 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2380 if (laser.damage[i].is_mirror)
2384 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2386 DrawLaser(0, DL_LASER_DISABLED);
2390 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2392 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2394 laser.redraw = TRUE;
2399 static void GrowAmoeba(int x, int y)
2404 if (!MovDelay[x][y]) // next animation frame
2405 MovDelay[x][y] = frames * delay;
2407 if (MovDelay[x][y]) // wait some time before next frame
2410 int wall_mask = Store2[x][y];
2411 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2414 phase = MovDelay[x][y] / delay;
2416 if (!MovDelay[x][y])
2418 Tile[x][y] = real_element;
2419 Store[x][y] = Store2[x][y] = 0;
2421 DrawWalls_MM(x, y, Tile[x][y]);
2422 DrawLaser(0, DL_LASER_ENABLED);
2424 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2426 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2431 static void DrawFieldAnimated_MM(int x, int y)
2433 int element = Tile[x][y];
2435 if (IS_BLOCKED(x, y))
2440 if (IS_MIRROR(element) ||
2441 IS_MIRROR_FIXED(element) ||
2442 element == EL_PRISM)
2444 if (MovDelay[x][y] != 0) // wait some time before next frame
2448 if (MovDelay[x][y] != 0)
2450 int graphic = IMG_TWINKLE_WHITE;
2451 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2453 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2458 laser.redraw = TRUE;
2461 static void Explode_MM(int x, int y, int phase, int mode)
2463 int num_phase = 9, delay = 2;
2464 int last_phase = num_phase * delay;
2465 int half_phase = (num_phase / 2) * delay;
2467 laser.redraw = TRUE;
2469 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2471 int center_element = Tile[x][y];
2473 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2475 // put moving element to center field (and let it explode there)
2476 center_element = MovingOrBlocked2Element_MM(x, y);
2477 RemoveMovingField_MM(x, y);
2479 Tile[x][y] = center_element;
2482 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2483 Store[x][y] = center_element;
2485 Store[x][y] = EL_EMPTY;
2487 Store2[x][y] = mode;
2488 Tile[x][y] = EL_EXPLODING_OPAQUE;
2489 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2495 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2497 if (phase == half_phase)
2499 Tile[x][y] = EL_EXPLODING_TRANSP;
2501 if (x == ELX && y == ELY)
2505 if (phase == last_phase)
2507 if (Store[x][y] == EL_BOMB)
2509 DrawLaser(0, DL_LASER_DISABLED);
2512 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2513 Store[x][y] = EL_EMPTY;
2515 game_mm.game_over = TRUE;
2516 game_mm.game_over_cause = GAME_OVER_BOMB;
2518 SetTileCursorActive(FALSE);
2520 laser.overloaded = FALSE;
2522 else if (IS_MCDUFFIN(Store[x][y]))
2524 Store[x][y] = EL_EMPTY;
2526 game.restart_game_message = "Bomb killed Mc Duffin! Play it again?";
2529 Tile[x][y] = Store[x][y];
2530 Store[x][y] = Store2[x][y] = 0;
2531 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2536 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2538 int graphic = IMG_MM_DEFAULT_EXPLODING;
2539 int graphic_phase = (phase / delay - 1);
2543 if (Store2[x][y] == EX_KETTLE)
2545 if (graphic_phase < 3)
2547 graphic = IMG_MM_KETTLE_EXPLODING;
2549 else if (graphic_phase < 5)
2555 graphic = IMG_EMPTY;
2559 else if (Store2[x][y] == EX_SHORT)
2561 if (graphic_phase < 4)
2567 graphic = IMG_EMPTY;
2572 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2574 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2575 cFX + x * TILEX, cFY + y * TILEY);
2577 MarkTileDirty(x, y);
2581 static void Bang_MM(int x, int y)
2583 int element = Tile[x][y];
2584 int mode = EX_NORMAL;
2587 DrawLaser(0, DL_LASER_ENABLED);
2606 if (IS_PACMAN(element))
2607 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2608 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2609 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2610 else if (element == EL_KEY)
2611 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2613 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2615 Explode_MM(x, y, EX_PHASE_START, mode);
2618 void TurnRound(int x, int y)
2630 { 0, 0 }, { 0, 0 }, { 0, 0 },
2635 int left, right, back;
2639 { MV_DOWN, MV_UP, MV_RIGHT },
2640 { MV_UP, MV_DOWN, MV_LEFT },
2642 { MV_LEFT, MV_RIGHT, MV_DOWN },
2643 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2644 { MV_RIGHT, MV_LEFT, MV_UP }
2647 int element = Tile[x][y];
2648 int old_move_dir = MovDir[x][y];
2649 int right_dir = turn[old_move_dir].right;
2650 int back_dir = turn[old_move_dir].back;
2651 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2652 int right_x = x + right_dx, right_y = y + right_dy;
2654 if (element == EL_PACMAN)
2656 boolean can_turn_right = FALSE;
2658 if (IN_LEV_FIELD(right_x, right_y) &&
2659 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2660 can_turn_right = TRUE;
2663 MovDir[x][y] = right_dir;
2665 MovDir[x][y] = back_dir;
2671 static void StartMoving_MM(int x, int y)
2673 int element = Tile[x][y];
2678 if (CAN_MOVE(element))
2682 if (MovDelay[x][y]) // wait some time before next movement
2690 // now make next step
2692 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2694 if (element == EL_PACMAN &&
2695 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2696 !ObjHit(newx, newy, HIT_POS_CENTER))
2698 Store[newx][newy] = Tile[newx][newy];
2699 Tile[newx][newy] = EL_EMPTY;
2701 DrawField_MM(newx, newy);
2703 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2704 ObjHit(newx, newy, HIT_POS_CENTER))
2706 // object was running against a wall
2713 InitMovingField_MM(x, y, MovDir[x][y]);
2717 ContinueMoving_MM(x, y);
2720 static void ContinueMoving_MM(int x, int y)
2722 int element = Tile[x][y];
2723 int direction = MovDir[x][y];
2724 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2725 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2726 int horiz_move = (dx!=0);
2727 int newx = x + dx, newy = y + dy;
2728 int step = (horiz_move ? dx : dy) * TILEX / 8;
2730 MovPos[x][y] += step;
2732 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2734 Tile[x][y] = EL_EMPTY;
2735 Tile[newx][newy] = element;
2737 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2738 MovDelay[newx][newy] = 0;
2740 if (!CAN_MOVE(element))
2741 MovDir[newx][newy] = 0;
2744 DrawField_MM(newx, newy);
2746 Stop[newx][newy] = TRUE;
2748 if (element == EL_PACMAN)
2750 if (Store[newx][newy] == EL_BOMB)
2751 Bang_MM(newx, newy);
2753 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2754 (LX + 2 * XS) / TILEX == newx &&
2755 (LY + 2 * YS) / TILEY == newy)
2762 else // still moving on
2767 laser.redraw = TRUE;
2770 boolean ClickElement(int x, int y, int button)
2772 static DelayCounter click_delay = { CLICK_DELAY };
2773 static boolean new_button = TRUE;
2774 boolean element_clicked = FALSE;
2779 // initialize static variables
2780 click_delay.count = 0;
2781 click_delay.value = CLICK_DELAY;
2787 // do not rotate objects hit by the laser after the game was solved
2788 if (game_mm.level_solved && Hit[x][y])
2791 if (button == MB_RELEASED)
2794 click_delay.value = CLICK_DELAY;
2796 // release eventually hold auto-rotating mirror
2797 RotateMirror(x, y, MB_RELEASED);
2802 if (!FrameReached(&click_delay) && !new_button)
2805 if (button == MB_MIDDLEBUTTON) // middle button has no function
2808 if (!IN_LEV_FIELD(x, y))
2811 if (Tile[x][y] == EL_EMPTY)
2814 element = Tile[x][y];
2816 if (IS_MIRROR(element) ||
2817 IS_BEAMER(element) ||
2818 IS_POLAR(element) ||
2819 IS_POLAR_CROSS(element) ||
2820 IS_DF_MIRROR(element) ||
2821 IS_DF_MIRROR_AUTO(element))
2823 RotateMirror(x, y, button);
2825 element_clicked = TRUE;
2827 else if (IS_MCDUFFIN(element))
2829 if (!laser.fuse_off)
2831 DrawLaser(0, DL_LASER_DISABLED);
2838 element = get_rotated_element(element, BUTTON_ROTATION(button));
2839 laser.start_angle = get_element_angle(element);
2843 Tile[x][y] = element;
2850 if (!laser.fuse_off)
2853 element_clicked = TRUE;
2855 else if (element == EL_FUSE_ON && laser.fuse_off)
2857 if (x != laser.fuse_x || y != laser.fuse_y)
2860 laser.fuse_off = FALSE;
2861 laser.fuse_x = laser.fuse_y = -1;
2863 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2866 element_clicked = TRUE;
2868 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2870 laser.fuse_off = TRUE;
2873 laser.overloaded = FALSE;
2875 DrawLaser(0, DL_LASER_DISABLED);
2876 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2878 element_clicked = TRUE;
2880 else if (element == EL_LIGHTBALL)
2883 RaiseScoreElement_MM(element);
2884 DrawLaser(0, DL_LASER_ENABLED);
2886 element_clicked = TRUE;
2889 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2892 return element_clicked;
2895 void RotateMirror(int x, int y, int button)
2897 if (button == MB_RELEASED)
2899 // release eventually hold auto-rotating mirror
2906 if (IS_MIRROR(Tile[x][y]) ||
2907 IS_POLAR_CROSS(Tile[x][y]) ||
2908 IS_POLAR(Tile[x][y]) ||
2909 IS_BEAMER(Tile[x][y]) ||
2910 IS_DF_MIRROR(Tile[x][y]) ||
2911 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2912 IS_GRID_WOOD_AUTO(Tile[x][y]))
2914 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2916 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2918 if (button == MB_LEFTBUTTON)
2920 // left mouse button only for manual adjustment, no auto-rotating;
2921 // freeze mirror for until mouse button released
2925 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2927 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2931 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2933 int edge = Hit[x][y];
2939 DrawLaser(edge - 1, DL_LASER_DISABLED);
2943 else if (ObjHit(x, y, HIT_POS_CENTER))
2945 int edge = Hit[x][y];
2949 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2954 DrawLaser(edge - 1, DL_LASER_DISABLED);
2961 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2966 if ((IS_BEAMER(Tile[x][y]) ||
2967 IS_POLAR(Tile[x][y]) ||
2968 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2972 if (IS_BEAMER(Tile[x][y]))
2975 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2976 LX, LY, laser.beamer_edge, laser.beamer[1].num);
2988 DrawLaser(0, DL_LASER_ENABLED);
2992 static void AutoRotateMirrors(void)
2996 if (!FrameReached(&rotate_delay))
2999 for (x = 0; x < lev_fieldx; x++)
3001 for (y = 0; y < lev_fieldy; y++)
3003 int element = Tile[x][y];
3005 // do not rotate objects hit by the laser after the game was solved
3006 if (game_mm.level_solved && Hit[x][y])
3009 if (IS_DF_MIRROR_AUTO(element) ||
3010 IS_GRID_WOOD_AUTO(element) ||
3011 IS_GRID_STEEL_AUTO(element) ||
3012 element == EL_REFRACTOR)
3013 RotateMirror(x, y, MB_RIGHTBUTTON);
3018 boolean ObjHit(int obx, int oby, int bits)
3025 if (bits & HIT_POS_CENTER)
3027 if (CheckLaserPixel(cSX + obx + 15,
3032 if (bits & HIT_POS_EDGE)
3034 for (i = 0; i < 4; i++)
3035 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3036 cSY + oby + 31 * (i / 2)))
3040 if (bits & HIT_POS_BETWEEN)
3042 for (i = 0; i < 4; i++)
3043 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3044 cSY + 4 + oby + 22 * (i / 2)))
3051 void DeletePacMan(int px, int py)
3057 if (game_mm.num_pacman <= 1)
3059 game_mm.num_pacman = 0;
3063 for (i = 0; i < game_mm.num_pacman; i++)
3064 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3067 game_mm.num_pacman--;
3069 for (j = i; j < game_mm.num_pacman; j++)
3071 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3072 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3073 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3077 void ColorCycling(void)
3079 static int CC, Cc = 0;
3081 static int color, old = 0xF00, new = 0x010, mult = 1;
3082 static unsigned short red, green, blue;
3084 if (color_status == STATIC_COLORS)
3089 if (CC < Cc || CC > Cc + 2)
3093 color = old + new * mult;
3099 if (ABS(mult) == 16)
3109 red = 0x0e00 * ((color & 0xF00) >> 8);
3110 green = 0x0e00 * ((color & 0x0F0) >> 4);
3111 blue = 0x0e00 * ((color & 0x00F));
3112 SetRGB(pen_magicolor[0], red, green, blue);
3114 red = 0x1111 * ((color & 0xF00) >> 8);
3115 green = 0x1111 * ((color & 0x0F0) >> 4);
3116 blue = 0x1111 * ((color & 0x00F));
3117 SetRGB(pen_magicolor[1], red, green, blue);
3121 static void GameActions_MM_Ext(void)
3128 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3131 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3133 element = Tile[x][y];
3135 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3136 StartMoving_MM(x, y);
3137 else if (IS_MOVING(x, y))
3138 ContinueMoving_MM(x, y);
3139 else if (IS_EXPLODING(element))
3140 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3141 else if (element == EL_EXIT_OPENING)
3143 else if (element == EL_GRAY_BALL_OPENING)
3144 OpenSurpriseBall(x, y);
3145 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3147 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3150 DrawFieldAnimated_MM(x, y);
3153 AutoRotateMirrors();
3156 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3158 // redraw after Explode_MM() ...
3160 DrawLaser(0, DL_LASER_ENABLED);
3161 laser.redraw = FALSE;
3166 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3170 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3172 DrawLaser(0, DL_LASER_DISABLED);
3177 if (FrameReached(&energy_delay))
3179 if (game_mm.energy_left > 0)
3181 game_mm.energy_left--;
3183 redraw_mask |= REDRAW_DOOR_1;
3185 else if (game.time_limit && !game_mm.game_over)
3187 FadeOutLaser(FALSE);
3189 game_mm.game_over = TRUE;
3190 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3192 SetTileCursorActive(FALSE);
3194 game.restart_game_message = "Out of magic energy! Play it again?";
3200 element = laser.dest_element;
3203 if (element != Tile[ELX][ELY])
3205 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3206 element, Tile[ELX][ELY]);
3210 if (!laser.overloaded && laser.overload_value == 0 &&
3211 element != EL_BOMB &&
3212 element != EL_MINE &&
3213 element != EL_BALL_GRAY &&
3214 element != EL_BLOCK_STONE &&
3215 element != EL_BLOCK_WOOD &&
3216 element != EL_FUSE_ON &&
3217 element != EL_FUEL_FULL &&
3218 !IS_WALL_ICE(element) &&
3219 !IS_WALL_AMOEBA(element))
3222 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3224 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3225 (!laser.overloaded && laser.overload_value > 0)) &&
3226 FrameReached(&overload_delay))
3228 if (laser.overloaded)
3229 laser.overload_value++;
3231 laser.overload_value--;
3233 if (game_mm.cheat_no_overload)
3235 laser.overloaded = FALSE;
3236 laser.overload_value = 0;
3239 game_mm.laser_overload_value = laser.overload_value;
3241 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3243 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3244 int color_down = 0xFF - color_up;
3247 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3248 (15 - (laser.overload_value / 6)) * color_scale);
3251 GetPixelFromRGB(window,
3252 (native_mm_level.laser_red ? 0xFF : color_up),
3253 (native_mm_level.laser_green ? color_down : 0x00),
3254 (native_mm_level.laser_blue ? color_down : 0x00));
3256 DrawLaser(0, DL_LASER_ENABLED);
3259 if (!laser.overloaded)
3260 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3261 else if (setup.sound_loops)
3262 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3264 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3266 if (laser.overloaded)
3269 BlitBitmap(pix[PIX_DOOR], drawto,
3270 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3271 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3272 - laser.overload_value,
3273 OVERLOAD_XSIZE, laser.overload_value,
3274 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3275 - laser.overload_value);
3277 redraw_mask |= REDRAW_DOOR_1;
3282 BlitBitmap(pix[PIX_DOOR], drawto,
3283 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3284 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3285 DX_OVERLOAD, DY_OVERLOAD);
3287 redraw_mask |= REDRAW_DOOR_1;
3290 if (laser.overload_value == MAX_LASER_OVERLOAD)
3292 UpdateAndDisplayGameControlValues();
3296 game_mm.game_over = TRUE;
3297 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3299 SetTileCursorActive(FALSE);
3301 game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?";
3312 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3314 if (game_mm.cheat_no_explosion)
3319 laser.dest_element = EL_EXPLODING_OPAQUE;
3324 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3326 laser.fuse_off = TRUE;
3330 DrawLaser(0, DL_LASER_DISABLED);
3331 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3334 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3336 static int new_elements[] =
3339 EL_MIRROR_FIXED_START,
3341 EL_POLAR_CROSS_START,
3347 int num_new_elements = sizeof(new_elements) / sizeof(int);
3348 int new_element = new_elements[RND(num_new_elements)];
3350 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3351 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3353 // !!! CHECK AGAIN: Laser on Polarizer !!!
3364 element = EL_MIRROR_START + RND(16);
3370 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3377 element = (rnd == 0 ? EL_FUSE_ON :
3378 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3379 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3380 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3381 EL_MIRROR_FIXED_START + rnd - 25);
3386 graphic = el2gfx(element);
3388 for (i = 0; i < 50; i++)
3394 BlitBitmap(pix[PIX_BACK], drawto,
3395 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3396 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3397 SX + ELX * TILEX + x,
3398 SY + ELY * TILEY + y);
3400 MarkTileDirty(ELX, ELY);
3403 DrawLaser(0, DL_LASER_ENABLED);
3405 Delay_WithScreenUpdates(50);
3408 Tile[ELX][ELY] = element;
3409 DrawField_MM(ELX, ELY);
3412 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3415 // above stuff: GRAY BALL -> PRISM !!!
3417 LX = ELX * TILEX + 14;
3418 LY = ELY * TILEY + 14;
3419 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3426 laser.num_edges -= 2;
3427 laser.num_damages--;
3431 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3432 if (laser.damage[i].is_mirror)
3436 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3438 DrawLaser(0, DL_LASER_DISABLED);
3440 DrawLaser(0, DL_LASER_DISABLED);
3449 if (IS_WALL_ICE(element) && CT > 50)
3451 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3454 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3455 Store[ELX][ELY] = EL_WALL_ICE;
3456 Store2[ELX][ELY] = laser.wall_mask;
3458 laser.dest_element = Tile[ELX][ELY];
3463 for (i = 0; i < 5; i++)
3469 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3473 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3475 Delay_WithScreenUpdates(100);
3478 if (Tile[ELX][ELY] == EL_WALL_ICE)
3479 Tile[ELX][ELY] = EL_EMPTY;
3483 LX = laser.edge[laser.num_edges].x - cSX2;
3484 LY = laser.edge[laser.num_edges].y - cSY2;
3487 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3488 if (laser.damage[i].is_mirror)
3492 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3494 DrawLaser(0, DL_LASER_DISABLED);
3501 if (IS_WALL_AMOEBA(element) && CT > 60)
3503 int k1, k2, k3, dx, dy, de, dm;
3504 int element2 = Tile[ELX][ELY];
3506 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3509 for (i = laser.num_damages - 1; i >= 0; i--)
3510 if (laser.damage[i].is_mirror)
3513 r = laser.num_edges;
3514 d = laser.num_damages;
3521 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3524 DrawLaser(0, DL_LASER_ENABLED);
3527 x = laser.damage[k1].x;
3528 y = laser.damage[k1].y;
3533 for (i = 0; i < 4; i++)
3535 if (laser.wall_mask & (1 << i))
3537 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3538 cSY + ELY * TILEY + 31 * (i / 2)))
3541 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3542 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3549 for (i = 0; i < 4; i++)
3551 if (laser.wall_mask & (1 << i))
3553 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3554 cSY + ELY * TILEY + 31 * (i / 2)))
3561 if (laser.num_beamers > 0 ||
3562 k1 < 1 || k2 < 4 || k3 < 4 ||
3563 CheckLaserPixel(cSX + ELX * TILEX + 14,
3564 cSY + ELY * TILEY + 14))
3566 laser.num_edges = r;
3567 laser.num_damages = d;
3569 DrawLaser(0, DL_LASER_DISABLED);
3572 Tile[ELX][ELY] = element | laser.wall_mask;
3576 de = Tile[ELX][ELY];
3577 dm = laser.wall_mask;
3581 int x = ELX, y = ELY;
3582 int wall_mask = laser.wall_mask;
3585 DrawLaser(0, DL_LASER_ENABLED);
3587 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3589 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3590 Store[x][y] = EL_WALL_AMOEBA;
3591 Store2[x][y] = wall_mask;
3597 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3599 DrawLaser(0, DL_LASER_ENABLED);
3601 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3603 for (i = 4; i >= 0; i--)
3605 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3608 Delay_WithScreenUpdates(20);
3611 DrawLaser(0, DL_LASER_ENABLED);
3616 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3617 laser.stops_inside_element && CT > native_mm_level.time_block)
3622 if (ABS(XS) > ABS(YS))
3629 for (i = 0; i < 4; i++)
3636 x = ELX + Step[k * 4].x;
3637 y = ELY + Step[k * 4].y;
3639 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3642 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3650 laser.overloaded = (element == EL_BLOCK_STONE);
3655 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3658 Tile[x][y] = element;
3660 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3663 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3665 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3666 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3674 if (element == EL_FUEL_FULL && CT > 10)
3676 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3679 BlitBitmap(pix[PIX_DOOR], drawto,
3680 DOOR_GFX_PAGEX4 + XX_ENERGY,
3681 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3682 ENERGY_XSIZE, i, DX_ENERGY,
3683 DY_ENERGY + ENERGY_YSIZE - i);
3686 redraw_mask |= REDRAW_DOOR_1;
3689 Delay_WithScreenUpdates(20);
3692 game_mm.energy_left = MAX_LASER_ENERGY;
3693 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3694 DrawField_MM(ELX, ELY);
3696 DrawLaser(0, DL_LASER_ENABLED);
3704 void GameActions_MM(struct MouseActionInfo action)
3706 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3707 boolean button_released = (action.button == MB_RELEASED);
3709 GameActions_MM_Ext();
3711 CheckSingleStepMode_MM(element_clicked, button_released);
3714 void MovePacMen(void)
3716 int mx, my, ox, oy, nx, ny;
3720 if (++pacman_nr >= game_mm.num_pacman)
3723 game_mm.pacman[pacman_nr].dir--;
3725 for (l = 1; l < 5; l++)
3727 game_mm.pacman[pacman_nr].dir++;
3729 if (game_mm.pacman[pacman_nr].dir > 4)
3730 game_mm.pacman[pacman_nr].dir = 1;
3732 if (game_mm.pacman[pacman_nr].dir % 2)
3735 my = game_mm.pacman[pacman_nr].dir - 2;
3740 mx = 3 - game_mm.pacman[pacman_nr].dir;
3743 ox = game_mm.pacman[pacman_nr].x;
3744 oy = game_mm.pacman[pacman_nr].y;
3747 element = Tile[nx][ny];
3749 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3752 if (!IS_EATABLE4PACMAN(element))
3755 if (ObjHit(nx, ny, HIT_POS_CENTER))
3758 Tile[ox][oy] = EL_EMPTY;
3760 EL_PACMAN_RIGHT - 1 +
3761 (game_mm.pacman[pacman_nr].dir - 1 +
3762 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3764 game_mm.pacman[pacman_nr].x = nx;
3765 game_mm.pacman[pacman_nr].y = ny;
3767 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3769 if (element != EL_EMPTY)
3771 int graphic = el2gfx(Tile[nx][ny]);
3776 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3779 ox = cSX + ox * TILEX;
3780 oy = cSY + oy * TILEY;
3782 for (i = 1; i < 33; i += 2)
3783 BlitBitmap(bitmap, window,
3784 src_x, src_y, TILEX, TILEY,
3785 ox + i * mx, oy + i * my);
3786 Ct = Ct + FrameCounter - CT;
3789 DrawField_MM(nx, ny);
3792 if (!laser.fuse_off)
3794 DrawLaser(0, DL_LASER_ENABLED);
3796 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3798 AddDamagedField(nx, ny);
3800 laser.damage[laser.num_damages - 1].edge = 0;
3804 if (element == EL_BOMB)
3805 DeletePacMan(nx, ny);
3807 if (IS_WALL_AMOEBA(element) &&
3808 (LX + 2 * XS) / TILEX == nx &&
3809 (LY + 2 * YS) / TILEY == ny)
3819 static void InitMovingField_MM(int x, int y, int direction)
3821 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3822 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3824 MovDir[x][y] = direction;
3825 MovDir[newx][newy] = direction;
3827 if (Tile[newx][newy] == EL_EMPTY)
3828 Tile[newx][newy] = EL_BLOCKED;
3831 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3833 int direction = MovDir[x][y];
3834 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3835 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3841 static void Blocked2Moving_MM(int x, int y,
3842 int *comes_from_x, int *comes_from_y)
3844 int oldx = x, oldy = y;
3845 int direction = MovDir[x][y];
3847 if (direction == MV_LEFT)
3849 else if (direction == MV_RIGHT)
3851 else if (direction == MV_UP)
3853 else if (direction == MV_DOWN)
3856 *comes_from_x = oldx;
3857 *comes_from_y = oldy;
3860 static int MovingOrBlocked2Element_MM(int x, int y)
3862 int element = Tile[x][y];
3864 if (element == EL_BLOCKED)
3868 Blocked2Moving_MM(x, y, &oldx, &oldy);
3870 return Tile[oldx][oldy];
3877 static void RemoveField(int x, int y)
3879 Tile[x][y] = EL_EMPTY;
3886 static void RemoveMovingField_MM(int x, int y)
3888 int oldx = x, oldy = y, newx = x, newy = y;
3890 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3893 if (IS_MOVING(x, y))
3895 Moving2Blocked_MM(x, y, &newx, &newy);
3896 if (Tile[newx][newy] != EL_BLOCKED)
3899 else if (Tile[x][y] == EL_BLOCKED)
3901 Blocked2Moving_MM(x, y, &oldx, &oldy);
3902 if (!IS_MOVING(oldx, oldy))
3906 Tile[oldx][oldy] = EL_EMPTY;
3907 Tile[newx][newy] = EL_EMPTY;
3908 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3909 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3911 DrawLevelField_MM(oldx, oldy);
3912 DrawLevelField_MM(newx, newy);
3915 void PlaySoundLevel(int x, int y, int sound_nr)
3917 int sx = SCREENX(x), sy = SCREENY(y);
3919 int silence_distance = 8;
3921 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3922 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3925 if (!IN_LEV_FIELD(x, y) ||
3926 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3927 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3930 volume = SOUND_MAX_VOLUME;
3933 stereo = (sx - SCR_FIELDX/2) * 12;
3935 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3936 if (stereo > SOUND_MAX_RIGHT)
3937 stereo = SOUND_MAX_RIGHT;
3938 if (stereo < SOUND_MAX_LEFT)
3939 stereo = SOUND_MAX_LEFT;
3942 if (!IN_SCR_FIELD(sx, sy))
3944 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3945 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3947 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3950 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3953 static void RaiseScore_MM(int value)
3955 game_mm.score += value;
3958 void RaiseScoreElement_MM(int element)
3963 case EL_PACMAN_RIGHT:
3965 case EL_PACMAN_LEFT:
3966 case EL_PACMAN_DOWN:
3967 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3971 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3976 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3980 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3989 // ----------------------------------------------------------------------------
3990 // Mirror Magic game engine snapshot handling functions
3991 // ----------------------------------------------------------------------------
3993 void SaveEngineSnapshotValues_MM(void)
3997 engine_snapshot_mm.game_mm = game_mm;
3998 engine_snapshot_mm.laser = laser;
4000 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4002 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4004 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4005 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4006 engine_snapshot_mm.Box[x][y] = Box[x][y];
4007 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4008 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4012 engine_snapshot_mm.LX = LX;
4013 engine_snapshot_mm.LY = LY;
4014 engine_snapshot_mm.XS = XS;
4015 engine_snapshot_mm.YS = YS;
4016 engine_snapshot_mm.ELX = ELX;
4017 engine_snapshot_mm.ELY = ELY;
4018 engine_snapshot_mm.CT = CT;
4019 engine_snapshot_mm.Ct = Ct;
4021 engine_snapshot_mm.last_LX = last_LX;
4022 engine_snapshot_mm.last_LY = last_LY;
4023 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4024 engine_snapshot_mm.hold_x = hold_x;
4025 engine_snapshot_mm.hold_y = hold_y;
4026 engine_snapshot_mm.pacman_nr = pacman_nr;
4028 engine_snapshot_mm.rotate_delay = rotate_delay;
4029 engine_snapshot_mm.pacman_delay = pacman_delay;
4030 engine_snapshot_mm.energy_delay = energy_delay;
4031 engine_snapshot_mm.overload_delay = overload_delay;
4034 void LoadEngineSnapshotValues_MM(void)
4038 // stored engine snapshot buffers already restored at this point
4040 game_mm = engine_snapshot_mm.game_mm;
4041 laser = engine_snapshot_mm.laser;
4043 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4045 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4047 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4048 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4049 Box[x][y] = engine_snapshot_mm.Box[x][y];
4050 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4051 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4055 LX = engine_snapshot_mm.LX;
4056 LY = engine_snapshot_mm.LY;
4057 XS = engine_snapshot_mm.XS;
4058 YS = engine_snapshot_mm.YS;
4059 ELX = engine_snapshot_mm.ELX;
4060 ELY = engine_snapshot_mm.ELY;
4061 CT = engine_snapshot_mm.CT;
4062 Ct = engine_snapshot_mm.Ct;
4064 last_LX = engine_snapshot_mm.last_LX;
4065 last_LY = engine_snapshot_mm.last_LY;
4066 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4067 hold_x = engine_snapshot_mm.hold_x;
4068 hold_y = engine_snapshot_mm.hold_y;
4069 pacman_nr = engine_snapshot_mm.pacman_nr;
4071 rotate_delay = engine_snapshot_mm.rotate_delay;
4072 pacman_delay = engine_snapshot_mm.pacman_delay;
4073 energy_delay = engine_snapshot_mm.energy_delay;
4074 overload_delay = engine_snapshot_mm.overload_delay;
4076 RedrawPlayfield_MM();
4079 static int getAngleFromTouchDelta(int dx, int dy, int base)
4081 double pi = 3.141592653;
4082 double rad = atan2((double)-dy, (double)dx);
4083 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4084 double deg = rad2 * 180.0 / pi;
4086 return (int)(deg * base / 360.0 + 0.5) % base;
4089 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4091 // calculate start (source) position to be at the middle of the tile
4092 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4093 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4094 int dx = dst_mx - src_mx;
4095 int dy = dst_my - src_my;
4104 if (!IN_LEV_FIELD(x, y))
4107 element = Tile[x][y];
4109 if (!IS_MCDUFFIN(element) &&
4110 !IS_MIRROR(element) &&
4111 !IS_BEAMER(element) &&
4112 !IS_POLAR(element) &&
4113 !IS_POLAR_CROSS(element) &&
4114 !IS_DF_MIRROR(element))
4117 angle_old = get_element_angle(element);
4119 if (IS_MCDUFFIN(element))
4121 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4122 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4123 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4124 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4127 else if (IS_MIRROR(element) ||
4128 IS_DF_MIRROR(element))
4130 for (i = 0; i < laser.num_damages; i++)
4132 if (laser.damage[i].x == x &&
4133 laser.damage[i].y == y &&
4134 ObjHit(x, y, HIT_POS_CENTER))
4136 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4137 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4144 if (angle_new == -1)
4146 if (IS_MIRROR(element) ||
4147 IS_DF_MIRROR(element) ||
4151 if (IS_POLAR_CROSS(element))
4154 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4157 button = (angle_new == angle_old ? 0 :
4158 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4159 MB_LEFTBUTTON : MB_RIGHTBUTTON);