1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
32 // special positions in the game control window (relative to control window)
41 #define XX_OVERLOAD 60
42 #define YY_OVERLOAD YY_ENERGY
44 // special positions in the game control window (relative to main window)
45 #define DX_LEVEL (DX + XX_LEVEL)
46 #define DY_LEVEL (DY + YY_LEVEL)
47 #define DX_KETTLES (DX + XX_KETTLES)
48 #define DY_KETTLES (DY + YY_KETTLES)
49 #define DX_SCORE (DX + XX_SCORE)
50 #define DY_SCORE (DY + YY_SCORE)
51 #define DX_ENERGY (DX + XX_ENERGY)
52 #define DY_ENERGY (DY + YY_ENERGY)
53 #define DX_OVERLOAD (DX + XX_OVERLOAD)
54 #define DY_OVERLOAD (DY + YY_OVERLOAD)
56 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
57 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
59 // game button identifiers
60 #define GAME_CTRL_ID_LEFT 0
61 #define GAME_CTRL_ID_MIDDLE 1
62 #define GAME_CTRL_ID_RIGHT 2
64 #define NUM_GAME_BUTTONS 3
66 // values for DrawLaser()
67 #define DL_LASER_DISABLED 0
68 #define DL_LASER_ENABLED 1
70 // values for 'click_delay_value' in ClickElement()
71 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
72 #define CLICK_DELAY 6 // delay (frames) for pressed butten
74 #define AUTO_ROTATE_DELAY CLICK_DELAY
75 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
76 #define NUM_INIT_CYCLE_STEPS 16
77 #define PACMAN_MOVE_DELAY 12
78 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
79 #define HEALTH_DEC_DELAY 3
80 #define HEALTH_INC_DELAY 9
81 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
83 #define BEGIN_NO_HEADLESS \
85 boolean last_headless = program.headless; \
87 program.headless = FALSE; \
89 #define END_NO_HEADLESS \
90 program.headless = last_headless; \
93 // forward declaration for internal use
94 static int MovingOrBlocked2Element_MM(int, int);
95 static void Bang_MM(int, int);
96 static void RaiseScore_MM(int);
97 static void RaiseScoreElement_MM(int);
98 static void RemoveMovingField_MM(int, int);
99 static void InitMovingField_MM(int, int, int);
100 static void ContinueMoving_MM(int, int);
101 static void Moving2Blocked_MM(int, int, int *, int *);
103 // bitmap for laser beam detection
104 static Bitmap *laser_bitmap = NULL;
106 // variables for laser control
107 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
108 static int hold_x = -1, hold_y = -1;
110 // variables for pacman control
111 static int pacman_nr = -1;
113 // various game engine delay counters
114 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
115 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
116 static DelayCounter energy_delay = { ENERGY_DELAY };
117 static DelayCounter overload_delay = { 0 };
119 // element masks for scanning pixels of MM elements
120 static const char mm_masks[10][16][16 + 1] =
304 static int get_element_angle(int element)
306 int element_phase = get_element_phase(element);
308 if (IS_MIRROR_FIXED(element) ||
309 IS_MCDUFFIN(element) ||
311 IS_RECEIVER(element))
312 return 4 * element_phase;
314 return element_phase;
317 static int get_opposite_angle(int angle)
319 int opposite_angle = angle + ANG_RAY_180;
321 // make sure "opposite_angle" is in valid interval [0, 15]
322 return (opposite_angle + 16) % 16;
325 static int get_mirrored_angle(int laser_angle, int mirror_angle)
327 int reflected_angle = 16 - laser_angle + mirror_angle;
329 // make sure "reflected_angle" is in valid interval [0, 15]
330 return (reflected_angle + 16) % 16;
333 static void DrawLaserLines(struct XY *points, int num_points, int mode)
335 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
336 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
338 DrawLines(drawto, points, num_points, pixel_drawto);
342 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
347 static boolean CheckLaserPixel(int x, int y)
353 pixel = ReadPixel(laser_bitmap, x, y);
357 return (pixel == WHITE_PIXEL);
360 static void CheckExitMM(void)
362 int exit_element = EL_EMPTY;
366 static int xy[4][2] =
374 for (y = 0; y < lev_fieldy; y++)
376 for (x = 0; x < lev_fieldx; x++)
378 if (Tile[x][y] == EL_EXIT_CLOSED)
380 // initiate opening animation of exit door
381 Tile[x][y] = EL_EXIT_OPENING;
383 exit_element = EL_EXIT_OPEN;
387 else if (IS_RECEIVER(Tile[x][y]))
389 // remove field that blocks receiver
390 int phase = Tile[x][y] - EL_RECEIVER_START;
391 int blocking_x, blocking_y;
393 blocking_x = x + xy[phase][0];
394 blocking_y = y + xy[phase][1];
396 if (IN_LEV_FIELD(blocking_x, blocking_y))
398 Tile[blocking_x][blocking_y] = EL_EMPTY;
400 DrawField_MM(blocking_x, blocking_y);
403 exit_element = EL_RECEIVER;
410 if (exit_element != EL_EMPTY)
411 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
414 static void InitMovDir_MM(int x, int y)
416 int element = Tile[x][y];
417 static int direction[3][4] =
419 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
420 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
421 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
426 case EL_PACMAN_RIGHT:
430 Tile[x][y] = EL_PACMAN;
431 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
439 static void InitField(int x, int y)
441 int element = Tile[x][y];
446 Tile[x][y] = EL_EMPTY;
451 if (native_mm_level.auto_count_kettles)
452 game_mm.kettles_still_needed++;
455 case EL_LIGHTBULB_OFF:
456 game_mm.lights_still_needed++;
460 if (IS_MIRROR(element) ||
461 IS_BEAMER_OLD(element) ||
462 IS_BEAMER(element) ||
464 IS_POLAR_CROSS(element) ||
465 IS_DF_MIRROR(element) ||
466 IS_DF_MIRROR_AUTO(element) ||
467 IS_GRID_STEEL_AUTO(element) ||
468 IS_GRID_WOOD_AUTO(element) ||
469 IS_FIBRE_OPTIC(element))
471 if (IS_BEAMER_OLD(element))
473 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
474 element = Tile[x][y];
477 if (!IS_FIBRE_OPTIC(element))
479 static int steps_grid_auto = 0;
481 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
482 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
484 if (IS_GRID_STEEL_AUTO(element) ||
485 IS_GRID_WOOD_AUTO(element))
486 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
488 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
490 game_mm.cycle[game_mm.num_cycle].x = x;
491 game_mm.cycle[game_mm.num_cycle].y = y;
495 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
497 int beamer_nr = BEAMER_NR(element);
498 int nr = laser.beamer[beamer_nr][0].num;
500 laser.beamer[beamer_nr][nr].x = x;
501 laser.beamer[beamer_nr][nr].y = y;
502 laser.beamer[beamer_nr][nr].num = 1;
505 else if (IS_PACMAN(element))
509 else if (IS_MCDUFFIN(element) || IS_LASER(element))
511 laser.start_edge.x = x;
512 laser.start_edge.y = y;
513 laser.start_angle = get_element_angle(element);
520 static void InitCycleElements_RotateSingleStep(void)
524 if (game_mm.num_cycle == 0) // no elements to cycle
527 for (i = 0; i < game_mm.num_cycle; i++)
529 int x = game_mm.cycle[i].x;
530 int y = game_mm.cycle[i].y;
531 int step = SIGN(game_mm.cycle[i].steps);
532 int last_element = Tile[x][y];
533 int next_element = get_rotated_element(last_element, step);
535 if (!game_mm.cycle[i].steps)
538 Tile[x][y] = next_element;
540 game_mm.cycle[i].steps -= step;
544 static void InitLaser(void)
546 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
547 int step = (IS_LASER(start_element) ? 4 : 0);
549 LX = laser.start_edge.x * TILEX;
550 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
553 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
555 LY = laser.start_edge.y * TILEY;
556 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
557 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
561 XS = 2 * Step[laser.start_angle].x;
562 YS = 2 * Step[laser.start_angle].y;
564 laser.current_angle = laser.start_angle;
566 laser.num_damages = 0;
568 laser.num_beamers = 0;
569 laser.beamer_edge[0] = 0;
571 laser.dest_element = EL_EMPTY;
574 AddLaserEdge(LX, LY); // set laser starting edge
576 pen_ray = GetPixelFromRGB(window,
577 native_mm_level.laser_red * 0xFF,
578 native_mm_level.laser_green * 0xFF,
579 native_mm_level.laser_blue * 0xFF);
582 void InitGameEngine_MM(void)
588 // initialize laser bitmap to current playfield (screen) size
589 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
590 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
594 // set global game control values
595 game_mm.num_cycle = 0;
596 game_mm.num_pacman = 0;
599 game_mm.energy_left = 0; // later set to "native_mm_level.time"
600 game_mm.kettles_still_needed =
601 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
602 game_mm.lights_still_needed = 0;
603 game_mm.num_keys = 0;
605 game_mm.level_solved = FALSE;
606 game_mm.game_over = FALSE;
607 game_mm.game_over_cause = 0;
609 game_mm.laser_overload_value = 0;
610 game_mm.laser_enabled = FALSE;
612 // set global laser control values (must be set before "InitLaser()")
613 laser.start_edge.x = 0;
614 laser.start_edge.y = 0;
615 laser.start_angle = 0;
617 for (i = 0; i < MAX_NUM_BEAMERS; i++)
618 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
620 laser.overloaded = FALSE;
621 laser.overload_value = 0;
622 laser.fuse_off = FALSE;
623 laser.fuse_x = laser.fuse_y = -1;
625 laser.dest_element = EL_EMPTY;
639 rotate_delay.count = 0;
640 pacman_delay.count = 0;
641 energy_delay.count = 0;
642 overload_delay.count = 0;
644 ClickElement(-1, -1, -1);
646 for (x = 0; x < lev_fieldx; x++)
648 for (y = 0; y < lev_fieldy; y++)
650 Tile[x][y] = Ur[x][y];
651 Hit[x][y] = Box[x][y] = 0;
653 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
654 Store[x][y] = Store2[x][y] = 0;
665 void InitGameActions_MM(void)
667 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
668 int cycle_steps_done = 0;
673 for (i = 0; i <= num_init_game_frames; i++)
675 if (i == num_init_game_frames)
676 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
677 else if (setup.sound_loops)
678 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
680 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
682 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
684 UpdateAndDisplayGameControlValues();
686 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
688 InitCycleElements_RotateSingleStep();
693 AdvanceFrameCounter();
703 if (setup.quick_doors)
710 if (game_mm.kettles_still_needed == 0)
713 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
714 SetTileCursorActive(TRUE);
717 void AddLaserEdge(int lx, int ly)
722 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
724 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
729 laser.edge[laser.num_edges].x = cSX2 + lx;
730 laser.edge[laser.num_edges].y = cSY2 + ly;
736 void AddDamagedField(int ex, int ey)
738 laser.damage[laser.num_damages].is_mirror = FALSE;
739 laser.damage[laser.num_damages].angle = laser.current_angle;
740 laser.damage[laser.num_damages].edge = laser.num_edges;
741 laser.damage[laser.num_damages].x = ex;
742 laser.damage[laser.num_damages].y = ey;
746 static boolean StepBehind(void)
752 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
753 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
755 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
761 static int getMaskFromElement(int element)
763 if (IS_GRID(element))
764 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
765 else if (IS_MCDUFFIN(element))
766 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
767 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
768 return IMG_MM_MASK_RECTANGLE;
770 return IMG_MM_MASK_CIRCLE;
773 static int ScanPixel(void)
778 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
779 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
782 // follow laser beam until it hits something (at least the screen border)
783 while (hit_mask == HIT_MASK_NO_HIT)
789 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
790 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
792 Debug("game:mm:ScanPixel", "touched screen border!");
798 for (i = 0; i < 4; i++)
800 int px = LX + (i % 2) * 2;
801 int py = LY + (i / 2) * 2;
804 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
805 int ly = (py + TILEY) / TILEY - 1; // negative values!
808 if (IN_LEV_FIELD(lx, ly))
810 int element = Tile[lx][ly];
812 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
816 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
818 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
820 pixel = ((element & (1 << pos)) ? 1 : 0);
824 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
826 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
831 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
832 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
835 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
836 hit_mask |= (1 << i);
839 if (hit_mask == HIT_MASK_NO_HIT)
841 // hit nothing -- go on with another step
853 int end = 0, rf = laser.num_edges;
855 // do not scan laser again after the game was lost for whatever reason
856 if (game_mm.game_over)
859 laser.overloaded = FALSE;
860 laser.stops_inside_element = FALSE;
862 DrawLaser(0, DL_LASER_ENABLED);
865 Debug("game:mm:ScanLaser",
866 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
874 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
877 laser.overloaded = TRUE;
882 hit_mask = ScanPixel();
885 Debug("game:mm:ScanLaser",
886 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
890 // hit something -- check out what it was
891 ELX = (LX + XS) / TILEX;
892 ELY = (LY + YS) / TILEY;
895 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
896 hit_mask, LX, LY, ELX, ELY);
899 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
902 laser.dest_element = element;
907 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
909 /* we have hit the top-right and bottom-left element --
910 choose the bottom-left one */
911 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
912 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
913 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
914 ELX = (LX - 2) / TILEX;
915 ELY = (LY + 2) / TILEY;
918 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
920 /* we have hit the top-left and bottom-right element --
921 choose the top-left one */
923 ELX = (LX - 2) / TILEX;
924 ELY = (LY - 2) / TILEY;
928 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
929 hit_mask, LX, LY, ELX, ELY);
932 element = Tile[ELX][ELY];
933 laser.dest_element = element;
936 Debug("game:mm:ScanLaser",
937 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
940 LX % TILEX, LY % TILEY,
945 if (!IN_LEV_FIELD(ELX, ELY))
946 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
950 if (element == EL_EMPTY)
952 if (!HitOnlyAnEdge(hit_mask))
955 else if (element == EL_FUSE_ON)
957 if (HitPolarizer(element, hit_mask))
960 else if (IS_GRID(element) || IS_DF_GRID(element))
962 if (HitPolarizer(element, hit_mask))
965 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
966 element == EL_GATE_STONE || element == EL_GATE_WOOD)
968 if (HitBlock(element, hit_mask))
975 else if (IS_MCDUFFIN(element))
977 if (HitLaserSource(element, hit_mask))
980 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
981 IS_RECEIVER(element))
983 if (HitLaserDestination(element, hit_mask))
986 else if (IS_WALL(element))
988 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
990 if (HitReflectingWalls(element, hit_mask))
995 if (HitAbsorbingWalls(element, hit_mask))
1001 if (HitElement(element, hit_mask))
1006 DrawLaser(rf - 1, DL_LASER_ENABLED);
1007 rf = laser.num_edges;
1011 if (laser.dest_element != Tile[ELX][ELY])
1013 Debug("game:mm:ScanLaser",
1014 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1015 laser.dest_element, Tile[ELX][ELY]);
1019 if (!end && !laser.stops_inside_element && !StepBehind())
1022 Debug("game:mm:ScanLaser", "Go one step back");
1028 AddLaserEdge(LX, LY);
1032 DrawLaser(rf - 1, DL_LASER_ENABLED);
1034 Ct = CT = FrameCounter;
1037 if (!IN_LEV_FIELD(ELX, ELY))
1038 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1042 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1048 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1049 start_edge, num_edges, mode);
1054 Warn("DrawLaserExt: start_edge < 0");
1061 Warn("DrawLaserExt: num_edges < 0");
1067 if (mode == DL_LASER_DISABLED)
1069 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1073 // now draw the laser to the backbuffer and (if enabled) to the screen
1074 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1076 redraw_mask |= REDRAW_FIELD;
1078 if (mode == DL_LASER_ENABLED)
1081 // after the laser was deleted, the "damaged" graphics must be restored
1082 if (laser.num_damages)
1084 int damage_start = 0;
1087 // determine the starting edge, from which graphics need to be restored
1090 for (i = 0; i < laser.num_damages; i++)
1092 if (laser.damage[i].edge == start_edge + 1)
1101 // restore graphics from this starting edge to the end of damage list
1102 for (i = damage_start; i < laser.num_damages; i++)
1104 int lx = laser.damage[i].x;
1105 int ly = laser.damage[i].y;
1106 int element = Tile[lx][ly];
1108 if (Hit[lx][ly] == laser.damage[i].edge)
1109 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1112 if (Box[lx][ly] == laser.damage[i].edge)
1115 if (IS_DRAWABLE(element))
1116 DrawField_MM(lx, ly);
1119 elx = laser.damage[damage_start].x;
1120 ely = laser.damage[damage_start].y;
1121 element = Tile[elx][ely];
1124 if (IS_BEAMER(element))
1128 for (i = 0; i < laser.num_beamers; i++)
1129 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1131 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1132 mode, elx, ely, Hit[elx][ely], start_edge);
1133 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1134 get_element_angle(element), laser.damage[damage_start].angle);
1138 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1139 laser.num_beamers > 0 &&
1140 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1142 // element is outgoing beamer
1143 laser.num_damages = damage_start + 1;
1145 if (IS_BEAMER(element))
1146 laser.current_angle = get_element_angle(element);
1150 // element is incoming beamer or other element
1151 laser.num_damages = damage_start;
1152 laser.current_angle = laser.damage[laser.num_damages].angle;
1157 // no damages but McDuffin himself (who needs to be redrawn anyway)
1159 elx = laser.start_edge.x;
1160 ely = laser.start_edge.y;
1161 element = Tile[elx][ely];
1164 laser.num_edges = start_edge + 1;
1165 if (start_edge == 0)
1166 laser.current_angle = laser.start_angle;
1168 LX = laser.edge[start_edge].x - cSX2;
1169 LY = laser.edge[start_edge].y - cSY2;
1170 XS = 2 * Step[laser.current_angle].x;
1171 YS = 2 * Step[laser.current_angle].y;
1174 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1180 if (IS_BEAMER(element) ||
1181 IS_FIBRE_OPTIC(element) ||
1182 IS_PACMAN(element) ||
1183 IS_POLAR(element) ||
1184 IS_POLAR_CROSS(element) ||
1185 element == EL_FUSE_ON)
1190 Debug("game:mm:DrawLaserExt", "element == %d", element);
1193 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1194 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1198 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1199 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1200 (laser.num_beamers == 0 ||
1201 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1203 // element is incoming beamer or other element
1204 step_size = -step_size;
1209 if (IS_BEAMER(element))
1210 Debug("game:mm:DrawLaserExt",
1211 "start_edge == %d, laser.beamer_edge == %d",
1212 start_edge, laser.beamer_edge);
1215 LX += step_size * XS;
1216 LY += step_size * YS;
1218 else if (element != EL_EMPTY)
1227 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1232 void DrawLaser(int start_edge, int mode)
1234 if (laser.num_edges - start_edge < 0)
1236 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1241 // check if laser is interrupted by beamer element
1242 if (laser.num_beamers > 0 &&
1243 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1245 if (mode == DL_LASER_ENABLED)
1248 int tmp_start_edge = start_edge;
1250 // draw laser segments forward from the start to the last beamer
1251 for (i = 0; i < laser.num_beamers; i++)
1253 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1255 if (tmp_num_edges <= 0)
1259 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1260 i, laser.beamer_edge[i], tmp_start_edge);
1263 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1265 tmp_start_edge = laser.beamer_edge[i];
1268 // draw last segment from last beamer to the end
1269 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1275 int last_num_edges = laser.num_edges;
1276 int num_beamers = laser.num_beamers;
1278 // delete laser segments backward from the end to the first beamer
1279 for (i = num_beamers - 1; i >= 0; i--)
1281 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1283 if (laser.beamer_edge[i] - start_edge <= 0)
1286 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1288 last_num_edges = laser.beamer_edge[i];
1289 laser.num_beamers--;
1293 if (last_num_edges - start_edge <= 0)
1294 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1295 last_num_edges, start_edge);
1298 // special case when rotating first beamer: delete laser edge on beamer
1299 // (but do not start scanning on previous edge to prevent mirror sound)
1300 if (last_num_edges - start_edge == 1 && start_edge > 0)
1301 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1303 // delete first segment from start to the first beamer
1304 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1309 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1312 game_mm.laser_enabled = mode;
1315 void DrawLaser_MM(void)
1317 DrawLaser(0, game_mm.laser_enabled);
1320 boolean HitElement(int element, int hit_mask)
1322 if (HitOnlyAnEdge(hit_mask))
1325 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1326 element = MovingOrBlocked2Element_MM(ELX, ELY);
1329 Debug("game:mm:HitElement", "(1): element == %d", element);
1333 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1334 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1337 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1341 AddDamagedField(ELX, ELY);
1343 // this is more precise: check if laser would go through the center
1344 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1346 // skip the whole element before continuing the scan
1352 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1354 if (LX/TILEX > ELX || LY/TILEY > ELY)
1356 /* skipping scan positions to the right and down skips one scan
1357 position too much, because this is only the top left scan position
1358 of totally four scan positions (plus one to the right, one to the
1359 bottom and one to the bottom right) */
1369 Debug("game:mm:HitElement", "(2): element == %d", element);
1372 if (LX + 5 * XS < 0 ||
1382 Debug("game:mm:HitElement", "(3): element == %d", element);
1385 if (IS_POLAR(element) &&
1386 ((element - EL_POLAR_START) % 2 ||
1387 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1389 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1391 laser.num_damages--;
1396 if (IS_POLAR_CROSS(element) &&
1397 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1399 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1401 laser.num_damages--;
1406 if (!IS_BEAMER(element) &&
1407 !IS_FIBRE_OPTIC(element) &&
1408 !IS_GRID_WOOD(element) &&
1409 element != EL_FUEL_EMPTY)
1412 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1413 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1415 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1418 LX = ELX * TILEX + 14;
1419 LY = ELY * TILEY + 14;
1421 AddLaserEdge(LX, LY);
1424 if (IS_MIRROR(element) ||
1425 IS_MIRROR_FIXED(element) ||
1426 IS_POLAR(element) ||
1427 IS_POLAR_CROSS(element) ||
1428 IS_DF_MIRROR(element) ||
1429 IS_DF_MIRROR_AUTO(element) ||
1430 element == EL_PRISM ||
1431 element == EL_REFRACTOR)
1433 int current_angle = laser.current_angle;
1436 laser.num_damages--;
1438 AddDamagedField(ELX, ELY);
1440 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1443 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1445 if (IS_MIRROR(element) ||
1446 IS_MIRROR_FIXED(element) ||
1447 IS_DF_MIRROR(element) ||
1448 IS_DF_MIRROR_AUTO(element))
1449 laser.current_angle = get_mirrored_angle(laser.current_angle,
1450 get_element_angle(element));
1452 if (element == EL_PRISM || element == EL_REFRACTOR)
1453 laser.current_angle = RND(16);
1455 XS = 2 * Step[laser.current_angle].x;
1456 YS = 2 * Step[laser.current_angle].y;
1458 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1463 LX += step_size * XS;
1464 LY += step_size * YS;
1467 // draw sparkles on mirror
1468 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1469 current_angle != laser.current_angle)
1471 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1475 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1476 current_angle != laser.current_angle)
1477 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1480 (get_opposite_angle(laser.current_angle) ==
1481 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1483 return (laser.overloaded ? TRUE : FALSE);
1486 if (element == EL_FUEL_FULL)
1488 laser.stops_inside_element = TRUE;
1493 if (element == EL_BOMB || element == EL_MINE)
1495 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1497 if (element == EL_MINE)
1498 laser.overloaded = TRUE;
1501 if (element == EL_KETTLE ||
1502 element == EL_CELL ||
1503 element == EL_KEY ||
1504 element == EL_LIGHTBALL ||
1505 element == EL_PACMAN ||
1508 if (!IS_PACMAN(element))
1511 if (element == EL_PACMAN)
1514 if (element == EL_KETTLE || element == EL_CELL)
1516 if (game_mm.kettles_still_needed > 0)
1517 game_mm.kettles_still_needed--;
1519 game.snapshot.collected_item = TRUE;
1521 if (game_mm.kettles_still_needed == 0)
1525 DrawLaser(0, DL_LASER_ENABLED);
1528 else if (element == EL_KEY)
1532 else if (IS_PACMAN(element))
1534 DeletePacMan(ELX, ELY);
1537 RaiseScoreElement_MM(element);
1542 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1544 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1546 DrawLaser(0, DL_LASER_ENABLED);
1548 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1550 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1551 game_mm.lights_still_needed--;
1555 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1556 game_mm.lights_still_needed++;
1559 DrawField_MM(ELX, ELY);
1560 DrawLaser(0, DL_LASER_ENABLED);
1565 laser.stops_inside_element = TRUE;
1571 Debug("game:mm:HitElement", "(4): element == %d", element);
1574 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1575 laser.num_beamers < MAX_NUM_BEAMERS &&
1576 laser.beamer[BEAMER_NR(element)][1].num)
1578 int beamer_angle = get_element_angle(element);
1579 int beamer_nr = BEAMER_NR(element);
1583 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1586 laser.num_damages--;
1588 if (IS_FIBRE_OPTIC(element) ||
1589 laser.current_angle == get_opposite_angle(beamer_angle))
1593 LX = ELX * TILEX + 14;
1594 LY = ELY * TILEY + 14;
1596 AddLaserEdge(LX, LY);
1597 AddDamagedField(ELX, ELY);
1599 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1602 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1604 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1605 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1606 ELX = laser.beamer[beamer_nr][pos].x;
1607 ELY = laser.beamer[beamer_nr][pos].y;
1608 LX = ELX * TILEX + 14;
1609 LY = ELY * TILEY + 14;
1611 if (IS_BEAMER(element))
1613 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1614 XS = 2 * Step[laser.current_angle].x;
1615 YS = 2 * Step[laser.current_angle].y;
1618 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1620 AddLaserEdge(LX, LY);
1621 AddDamagedField(ELX, ELY);
1623 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1626 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1628 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1633 LX += step_size * XS;
1634 LY += step_size * YS;
1636 laser.num_beamers++;
1645 boolean HitOnlyAnEdge(int hit_mask)
1647 // check if the laser hit only the edge of an element and, if so, go on
1650 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1654 if ((hit_mask == HIT_MASK_TOPLEFT ||
1655 hit_mask == HIT_MASK_TOPRIGHT ||
1656 hit_mask == HIT_MASK_BOTTOMLEFT ||
1657 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1658 laser.current_angle % 4) // angle is not 90°
1662 if (hit_mask == HIT_MASK_TOPLEFT)
1667 else if (hit_mask == HIT_MASK_TOPRIGHT)
1672 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1677 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1683 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1689 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1696 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1702 boolean HitPolarizer(int element, int hit_mask)
1704 if (HitOnlyAnEdge(hit_mask))
1707 if (IS_DF_GRID(element))
1709 int grid_angle = get_element_angle(element);
1712 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1713 grid_angle, laser.current_angle);
1716 AddLaserEdge(LX, LY);
1717 AddDamagedField(ELX, ELY);
1720 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1722 if (laser.current_angle == grid_angle ||
1723 laser.current_angle == get_opposite_angle(grid_angle))
1725 // skip the whole element before continuing the scan
1731 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1733 if (LX/TILEX > ELX || LY/TILEY > ELY)
1735 /* skipping scan positions to the right and down skips one scan
1736 position too much, because this is only the top left scan position
1737 of totally four scan positions (plus one to the right, one to the
1738 bottom and one to the bottom right) */
1744 AddLaserEdge(LX, LY);
1750 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1752 LX / TILEX, LY / TILEY,
1753 LX % TILEX, LY % TILEY);
1758 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1760 return HitReflectingWalls(element, hit_mask);
1764 return HitAbsorbingWalls(element, hit_mask);
1767 else if (IS_GRID_STEEL(element))
1769 return HitReflectingWalls(element, hit_mask);
1771 else // IS_GRID_WOOD
1773 return HitAbsorbingWalls(element, hit_mask);
1779 boolean HitBlock(int element, int hit_mask)
1781 boolean check = FALSE;
1783 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1784 game_mm.num_keys == 0)
1787 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1790 int ex = ELX * TILEX + 14;
1791 int ey = ELY * TILEY + 14;
1795 for (i = 1; i < 32; i++)
1800 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1805 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1806 return HitAbsorbingWalls(element, hit_mask);
1810 AddLaserEdge(LX - XS, LY - YS);
1811 AddDamagedField(ELX, ELY);
1814 Box[ELX][ELY] = laser.num_edges;
1816 return HitReflectingWalls(element, hit_mask);
1819 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1821 int xs = XS / 2, ys = YS / 2;
1822 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1823 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1825 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1826 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1828 laser.overloaded = (element == EL_GATE_STONE);
1833 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1834 (hit_mask == HIT_MASK_TOP ||
1835 hit_mask == HIT_MASK_LEFT ||
1836 hit_mask == HIT_MASK_RIGHT ||
1837 hit_mask == HIT_MASK_BOTTOM))
1838 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1839 hit_mask == HIT_MASK_BOTTOM),
1840 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1841 hit_mask == HIT_MASK_RIGHT));
1842 AddLaserEdge(LX, LY);
1848 if (element == EL_GATE_STONE && Box[ELX][ELY])
1850 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1862 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1864 int xs = XS / 2, ys = YS / 2;
1865 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1866 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1868 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1869 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1871 laser.overloaded = (element == EL_BLOCK_STONE);
1876 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1877 (hit_mask == HIT_MASK_TOP ||
1878 hit_mask == HIT_MASK_LEFT ||
1879 hit_mask == HIT_MASK_RIGHT ||
1880 hit_mask == HIT_MASK_BOTTOM))
1881 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1882 hit_mask == HIT_MASK_BOTTOM),
1883 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1884 hit_mask == HIT_MASK_RIGHT));
1885 AddDamagedField(ELX, ELY);
1887 LX = ELX * TILEX + 14;
1888 LY = ELY * TILEY + 14;
1890 AddLaserEdge(LX, LY);
1892 laser.stops_inside_element = TRUE;
1900 boolean HitLaserSource(int element, int hit_mask)
1902 if (HitOnlyAnEdge(hit_mask))
1905 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1907 laser.overloaded = TRUE;
1912 boolean HitLaserDestination(int element, int hit_mask)
1914 if (HitOnlyAnEdge(hit_mask))
1917 if (element != EL_EXIT_OPEN &&
1918 !(IS_RECEIVER(element) &&
1919 game_mm.kettles_still_needed == 0 &&
1920 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1922 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1927 if (IS_RECEIVER(element) ||
1928 (IS_22_5_ANGLE(laser.current_angle) &&
1929 (ELX != (LX + 6 * XS) / TILEX ||
1930 ELY != (LY + 6 * YS) / TILEY ||
1939 LX = ELX * TILEX + 14;
1940 LY = ELY * TILEY + 14;
1942 laser.stops_inside_element = TRUE;
1945 AddLaserEdge(LX, LY);
1946 AddDamagedField(ELX, ELY);
1948 if (game_mm.lights_still_needed == 0)
1950 game_mm.level_solved = TRUE;
1952 SetTileCursorActive(FALSE);
1958 boolean HitReflectingWalls(int element, int hit_mask)
1960 // check if laser hits side of a wall with an angle that is not 90°
1961 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1962 hit_mask == HIT_MASK_LEFT ||
1963 hit_mask == HIT_MASK_RIGHT ||
1964 hit_mask == HIT_MASK_BOTTOM))
1966 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1971 if (!IS_DF_GRID(element))
1972 AddLaserEdge(LX, LY);
1974 // check if laser hits wall with an angle of 45°
1975 if (!IS_22_5_ANGLE(laser.current_angle))
1977 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1980 laser.current_angle = get_mirrored_angle(laser.current_angle,
1983 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
1986 laser.current_angle = get_mirrored_angle(laser.current_angle,
1990 AddLaserEdge(LX, LY);
1992 XS = 2 * Step[laser.current_angle].x;
1993 YS = 2 * Step[laser.current_angle].y;
1997 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1999 laser.current_angle = get_mirrored_angle(laser.current_angle,
2004 if (!IS_DF_GRID(element))
2005 AddLaserEdge(LX, LY);
2010 if (!IS_DF_GRID(element))
2011 AddLaserEdge(LX, LY + YS / 2);
2014 if (!IS_DF_GRID(element))
2015 AddLaserEdge(LX, LY);
2018 YS = 2 * Step[laser.current_angle].y;
2022 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2024 laser.current_angle = get_mirrored_angle(laser.current_angle,
2029 if (!IS_DF_GRID(element))
2030 AddLaserEdge(LX, LY);
2035 if (!IS_DF_GRID(element))
2036 AddLaserEdge(LX + XS / 2, LY);
2039 if (!IS_DF_GRID(element))
2040 AddLaserEdge(LX, LY);
2043 XS = 2 * Step[laser.current_angle].x;
2049 // reflection at the edge of reflecting DF style wall
2050 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2052 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2053 hit_mask == HIT_MASK_TOPRIGHT) ||
2054 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2055 hit_mask == HIT_MASK_TOPLEFT) ||
2056 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2057 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2058 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2059 hit_mask == HIT_MASK_BOTTOMRIGHT))
2062 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2063 ANG_MIRROR_135 : ANG_MIRROR_45);
2065 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2067 AddDamagedField(ELX, ELY);
2068 AddLaserEdge(LX, LY);
2070 laser.current_angle = get_mirrored_angle(laser.current_angle,
2078 AddLaserEdge(LX, LY);
2084 // reflection inside an edge of reflecting DF style wall
2085 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2087 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2088 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2089 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2090 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2091 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2092 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2093 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2094 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2097 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2098 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2099 ANG_MIRROR_135 : ANG_MIRROR_45);
2101 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2104 AddDamagedField(ELX, ELY);
2107 AddLaserEdge(LX - XS, LY - YS);
2108 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2109 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2111 laser.current_angle = get_mirrored_angle(laser.current_angle,
2119 AddLaserEdge(LX, LY);
2125 // check if laser hits DF style wall with an angle of 90°
2126 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2128 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2129 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2130 (IS_VERT_ANGLE(laser.current_angle) &&
2131 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2133 // laser at last step touched nothing or the same side of the wall
2134 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2136 AddDamagedField(ELX, ELY);
2143 last_hit_mask = hit_mask;
2150 if (!HitOnlyAnEdge(hit_mask))
2152 laser.overloaded = TRUE;
2160 boolean HitAbsorbingWalls(int element, int hit_mask)
2162 if (HitOnlyAnEdge(hit_mask))
2166 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2168 AddLaserEdge(LX - XS, LY - YS);
2175 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2177 AddLaserEdge(LX - XS, LY - YS);
2183 if (IS_WALL_WOOD(element) ||
2184 IS_DF_WALL_WOOD(element) ||
2185 IS_GRID_WOOD(element) ||
2186 IS_GRID_WOOD_FIXED(element) ||
2187 IS_GRID_WOOD_AUTO(element) ||
2188 element == EL_FUSE_ON ||
2189 element == EL_BLOCK_WOOD ||
2190 element == EL_GATE_WOOD)
2192 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2197 if (IS_WALL_ICE(element))
2201 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2202 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2204 // check if laser hits wall with an angle of 90°
2205 if (IS_90_ANGLE(laser.current_angle))
2206 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2208 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2212 for (i = 0; i < 4; i++)
2214 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2215 mask = 15 - (8 >> i);
2216 else if (ABS(XS) == 4 &&
2218 (XS > 0) == (i % 2) &&
2219 (YS < 0) == (i / 2))
2220 mask = 3 + (i / 2) * 9;
2221 else if (ABS(YS) == 4 &&
2223 (XS < 0) == (i % 2) &&
2224 (YS > 0) == (i / 2))
2225 mask = 5 + (i % 2) * 5;
2229 laser.wall_mask = mask;
2231 else if (IS_WALL_AMOEBA(element))
2233 int elx = (LX - 2 * XS) / TILEX;
2234 int ely = (LY - 2 * YS) / TILEY;
2235 int element2 = Tile[elx][ely];
2238 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2240 laser.dest_element = EL_EMPTY;
2248 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2249 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2251 if (IS_90_ANGLE(laser.current_angle))
2252 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2254 laser.dest_element = element2 | EL_WALL_AMOEBA;
2256 laser.wall_mask = mask;
2262 static void OpenExit(int x, int y)
2266 if (!MovDelay[x][y]) // next animation frame
2267 MovDelay[x][y] = 4 * delay;
2269 if (MovDelay[x][y]) // wait some time before next frame
2274 phase = MovDelay[x][y] / delay;
2276 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2277 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2279 if (!MovDelay[x][y])
2281 Tile[x][y] = EL_EXIT_OPEN;
2287 static void OpenSurpriseBall(int x, int y)
2291 if (!MovDelay[x][y]) // next animation frame
2292 MovDelay[x][y] = 50 * delay;
2294 if (MovDelay[x][y]) // wait some time before next frame
2298 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2301 int graphic = el2gfx(Store[x][y]);
2303 int dx = RND(26), dy = RND(26);
2305 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2307 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2308 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2310 MarkTileDirty(x, y);
2313 if (!MovDelay[x][y])
2315 Tile[x][y] = Store[x][y];
2324 static void MeltIce(int x, int y)
2329 if (!MovDelay[x][y]) // next animation frame
2330 MovDelay[x][y] = frames * delay;
2332 if (MovDelay[x][y]) // wait some time before next frame
2335 int wall_mask = Store2[x][y];
2336 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2339 phase = frames - MovDelay[x][y] / delay - 1;
2341 if (!MovDelay[x][y])
2345 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2346 Store[x][y] = Store2[x][y] = 0;
2348 DrawWalls_MM(x, y, Tile[x][y]);
2350 if (Tile[x][y] == EL_WALL_ICE)
2351 Tile[x][y] = EL_EMPTY;
2353 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2354 if (laser.damage[i].is_mirror)
2358 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2360 DrawLaser(0, DL_LASER_DISABLED);
2364 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2366 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2368 laser.redraw = TRUE;
2373 static void GrowAmoeba(int x, int y)
2378 if (!MovDelay[x][y]) // next animation frame
2379 MovDelay[x][y] = frames * delay;
2381 if (MovDelay[x][y]) // wait some time before next frame
2384 int wall_mask = Store2[x][y];
2385 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2388 phase = MovDelay[x][y] / delay;
2390 if (!MovDelay[x][y])
2392 Tile[x][y] = real_element;
2393 Store[x][y] = Store2[x][y] = 0;
2395 DrawWalls_MM(x, y, Tile[x][y]);
2396 DrawLaser(0, DL_LASER_ENABLED);
2398 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2400 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2405 static void DrawFieldAnimated_MM(int x, int y)
2407 if (IS_BLOCKED(x, y))
2412 laser.redraw = TRUE;
2415 static void Explode_MM(int x, int y, int phase, int mode)
2417 int num_phase = 9, delay = 2;
2418 int last_phase = num_phase * delay;
2419 int half_phase = (num_phase / 2) * delay;
2421 laser.redraw = TRUE;
2423 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2425 int center_element = Tile[x][y];
2427 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2429 // put moving element to center field (and let it explode there)
2430 center_element = MovingOrBlocked2Element_MM(x, y);
2431 RemoveMovingField_MM(x, y);
2433 Tile[x][y] = center_element;
2436 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2437 Store[x][y] = center_element;
2439 Store[x][y] = EL_EMPTY;
2441 Store2[x][y] = mode;
2442 Tile[x][y] = EL_EXPLODING_OPAQUE;
2443 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2449 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2451 if (phase == half_phase)
2453 Tile[x][y] = EL_EXPLODING_TRANSP;
2455 if (x == ELX && y == ELY)
2459 if (phase == last_phase)
2461 if (Store[x][y] == EL_BOMB)
2463 DrawLaser(0, DL_LASER_DISABLED);
2466 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2467 Store[x][y] = EL_EMPTY;
2469 game_mm.game_over = TRUE;
2470 game_mm.game_over_cause = GAME_OVER_BOMB;
2472 SetTileCursorActive(FALSE);
2474 laser.overloaded = FALSE;
2476 else if (IS_MCDUFFIN(Store[x][y]))
2478 Store[x][y] = EL_EMPTY;
2480 game.restart_game_message = "Bomb killed Mc Duffin! Play it again?";
2483 Tile[x][y] = Store[x][y];
2484 Store[x][y] = Store2[x][y] = 0;
2485 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2490 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2492 int graphic = IMG_MM_DEFAULT_EXPLODING;
2493 int graphic_phase = (phase / delay - 1);
2497 if (Store2[x][y] == EX_KETTLE)
2499 if (graphic_phase < 3)
2501 graphic = IMG_MM_KETTLE_EXPLODING;
2503 else if (graphic_phase < 5)
2509 graphic = IMG_EMPTY;
2513 else if (Store2[x][y] == EX_SHORT)
2515 if (graphic_phase < 4)
2521 graphic = IMG_EMPTY;
2526 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2528 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2529 cFX + x * TILEX, cFY + y * TILEY);
2531 MarkTileDirty(x, y);
2535 static void Bang_MM(int x, int y)
2537 int element = Tile[x][y];
2538 int mode = EX_NORMAL;
2541 DrawLaser(0, DL_LASER_ENABLED);
2560 if (IS_PACMAN(element))
2561 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2562 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2563 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2564 else if (element == EL_KEY)
2565 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2567 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2569 Explode_MM(x, y, EX_PHASE_START, mode);
2572 void TurnRound(int x, int y)
2584 { 0, 0 }, { 0, 0 }, { 0, 0 },
2589 int left, right, back;
2593 { MV_DOWN, MV_UP, MV_RIGHT },
2594 { MV_UP, MV_DOWN, MV_LEFT },
2596 { MV_LEFT, MV_RIGHT, MV_DOWN },
2597 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2598 { MV_RIGHT, MV_LEFT, MV_UP }
2601 int element = Tile[x][y];
2602 int old_move_dir = MovDir[x][y];
2603 int right_dir = turn[old_move_dir].right;
2604 int back_dir = turn[old_move_dir].back;
2605 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2606 int right_x = x + right_dx, right_y = y + right_dy;
2608 if (element == EL_PACMAN)
2610 boolean can_turn_right = FALSE;
2612 if (IN_LEV_FIELD(right_x, right_y) &&
2613 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2614 can_turn_right = TRUE;
2617 MovDir[x][y] = right_dir;
2619 MovDir[x][y] = back_dir;
2625 static void StartMoving_MM(int x, int y)
2627 int element = Tile[x][y];
2632 if (CAN_MOVE(element))
2636 if (MovDelay[x][y]) // wait some time before next movement
2644 // now make next step
2646 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2648 if (element == EL_PACMAN &&
2649 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2650 !ObjHit(newx, newy, HIT_POS_CENTER))
2652 Store[newx][newy] = Tile[newx][newy];
2653 Tile[newx][newy] = EL_EMPTY;
2655 DrawField_MM(newx, newy);
2657 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2658 ObjHit(newx, newy, HIT_POS_CENTER))
2660 // object was running against a wall
2667 InitMovingField_MM(x, y, MovDir[x][y]);
2671 ContinueMoving_MM(x, y);
2674 static void ContinueMoving_MM(int x, int y)
2676 int element = Tile[x][y];
2677 int direction = MovDir[x][y];
2678 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2679 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2680 int horiz_move = (dx!=0);
2681 int newx = x + dx, newy = y + dy;
2682 int step = (horiz_move ? dx : dy) * TILEX / 8;
2684 MovPos[x][y] += step;
2686 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2688 Tile[x][y] = EL_EMPTY;
2689 Tile[newx][newy] = element;
2691 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2692 MovDelay[newx][newy] = 0;
2694 if (!CAN_MOVE(element))
2695 MovDir[newx][newy] = 0;
2698 DrawField_MM(newx, newy);
2700 Stop[newx][newy] = TRUE;
2702 if (element == EL_PACMAN)
2704 if (Store[newx][newy] == EL_BOMB)
2705 Bang_MM(newx, newy);
2707 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2708 (LX + 2 * XS) / TILEX == newx &&
2709 (LY + 2 * YS) / TILEY == newy)
2716 else // still moving on
2721 laser.redraw = TRUE;
2724 boolean ClickElement(int x, int y, int button)
2726 static DelayCounter click_delay = { CLICK_DELAY };
2727 static boolean new_button = TRUE;
2728 boolean element_clicked = FALSE;
2733 // initialize static variables
2734 click_delay.count = 0;
2735 click_delay.value = CLICK_DELAY;
2741 // do not rotate objects hit by the laser after the game was solved
2742 if (game_mm.level_solved && Hit[x][y])
2745 if (button == MB_RELEASED)
2748 click_delay.value = CLICK_DELAY;
2750 // release eventually hold auto-rotating mirror
2751 RotateMirror(x, y, MB_RELEASED);
2756 if (!FrameReached(&click_delay) && !new_button)
2759 if (button == MB_MIDDLEBUTTON) // middle button has no function
2762 if (!IN_LEV_FIELD(x, y))
2765 if (Tile[x][y] == EL_EMPTY)
2768 element = Tile[x][y];
2770 if (IS_MIRROR(element) ||
2771 IS_BEAMER(element) ||
2772 IS_POLAR(element) ||
2773 IS_POLAR_CROSS(element) ||
2774 IS_DF_MIRROR(element) ||
2775 IS_DF_MIRROR_AUTO(element))
2777 RotateMirror(x, y, button);
2779 element_clicked = TRUE;
2781 else if (IS_MCDUFFIN(element))
2783 if (!laser.fuse_off)
2785 DrawLaser(0, DL_LASER_DISABLED);
2792 element = get_rotated_element(element, BUTTON_ROTATION(button));
2793 laser.start_angle = get_element_angle(element);
2797 Tile[x][y] = element;
2804 if (!laser.fuse_off)
2807 element_clicked = TRUE;
2809 else if (element == EL_FUSE_ON && laser.fuse_off)
2811 if (x != laser.fuse_x || y != laser.fuse_y)
2814 laser.fuse_off = FALSE;
2815 laser.fuse_x = laser.fuse_y = -1;
2817 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2820 element_clicked = TRUE;
2822 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2824 laser.fuse_off = TRUE;
2827 laser.overloaded = FALSE;
2829 DrawLaser(0, DL_LASER_DISABLED);
2830 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2832 element_clicked = TRUE;
2834 else if (element == EL_LIGHTBALL)
2837 RaiseScoreElement_MM(element);
2838 DrawLaser(0, DL_LASER_ENABLED);
2840 element_clicked = TRUE;
2843 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2846 return element_clicked;
2849 void RotateMirror(int x, int y, int button)
2851 if (button == MB_RELEASED)
2853 // release eventually hold auto-rotating mirror
2860 if (IS_MIRROR(Tile[x][y]) ||
2861 IS_POLAR_CROSS(Tile[x][y]) ||
2862 IS_POLAR(Tile[x][y]) ||
2863 IS_BEAMER(Tile[x][y]) ||
2864 IS_DF_MIRROR(Tile[x][y]) ||
2865 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2866 IS_GRID_WOOD_AUTO(Tile[x][y]))
2868 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2870 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2872 if (button == MB_LEFTBUTTON)
2874 // left mouse button only for manual adjustment, no auto-rotating;
2875 // freeze mirror for until mouse button released
2879 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2881 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2885 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2887 int edge = Hit[x][y];
2893 DrawLaser(edge - 1, DL_LASER_DISABLED);
2897 else if (ObjHit(x, y, HIT_POS_CENTER))
2899 int edge = Hit[x][y];
2903 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2908 DrawLaser(edge - 1, DL_LASER_DISABLED);
2915 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2920 if ((IS_BEAMER(Tile[x][y]) ||
2921 IS_POLAR(Tile[x][y]) ||
2922 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2926 if (IS_BEAMER(Tile[x][y]))
2929 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2930 LX, LY, laser.beamer_edge, laser.beamer[1].num);
2940 DrawLaser(0, DL_LASER_ENABLED);
2944 static void AutoRotateMirrors(void)
2948 if (!FrameReached(&rotate_delay))
2951 for (x = 0; x < lev_fieldx; x++)
2953 for (y = 0; y < lev_fieldy; y++)
2955 int element = Tile[x][y];
2957 // do not rotate objects hit by the laser after the game was solved
2958 if (game_mm.level_solved && Hit[x][y])
2961 if (IS_DF_MIRROR_AUTO(element) ||
2962 IS_GRID_WOOD_AUTO(element) ||
2963 IS_GRID_STEEL_AUTO(element) ||
2964 element == EL_REFRACTOR)
2965 RotateMirror(x, y, MB_RIGHTBUTTON);
2970 boolean ObjHit(int obx, int oby, int bits)
2977 if (bits & HIT_POS_CENTER)
2979 if (CheckLaserPixel(cSX + obx + 15,
2984 if (bits & HIT_POS_EDGE)
2986 for (i = 0; i < 4; i++)
2987 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
2988 cSY + oby + 31 * (i / 2)))
2992 if (bits & HIT_POS_BETWEEN)
2994 for (i = 0; i < 4; i++)
2995 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
2996 cSY + 4 + oby + 22 * (i / 2)))
3003 void DeletePacMan(int px, int py)
3009 if (game_mm.num_pacman <= 1)
3011 game_mm.num_pacman = 0;
3015 for (i = 0; i < game_mm.num_pacman; i++)
3016 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3019 game_mm.num_pacman--;
3021 for (j = i; j < game_mm.num_pacman; j++)
3023 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3024 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3025 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3029 void ColorCycling(void)
3031 static int CC, Cc = 0;
3033 static int color, old = 0xF00, new = 0x010, mult = 1;
3034 static unsigned short red, green, blue;
3036 if (color_status == STATIC_COLORS)
3041 if (CC < Cc || CC > Cc + 2)
3045 color = old + new * mult;
3051 if (ABS(mult) == 16)
3061 red = 0x0e00 * ((color & 0xF00) >> 8);
3062 green = 0x0e00 * ((color & 0x0F0) >> 4);
3063 blue = 0x0e00 * ((color & 0x00F));
3064 SetRGB(pen_magicolor[0], red, green, blue);
3066 red = 0x1111 * ((color & 0xF00) >> 8);
3067 green = 0x1111 * ((color & 0x0F0) >> 4);
3068 blue = 0x1111 * ((color & 0x00F));
3069 SetRGB(pen_magicolor[1], red, green, blue);
3073 static void GameActions_MM_Ext(void)
3080 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3083 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3085 element = Tile[x][y];
3087 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3088 StartMoving_MM(x, y);
3089 else if (IS_MOVING(x, y))
3090 ContinueMoving_MM(x, y);
3091 else if (IS_EXPLODING(element))
3092 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3093 else if (element == EL_EXIT_OPENING)
3095 else if (element == EL_GRAY_BALL_OPENING)
3096 OpenSurpriseBall(x, y);
3097 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3099 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3102 DrawFieldAnimated_MM(x, y);
3105 AutoRotateMirrors();
3108 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3110 // redraw after Explode_MM() ...
3112 DrawLaser(0, DL_LASER_ENABLED);
3113 laser.redraw = FALSE;
3118 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3122 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3124 DrawLaser(0, DL_LASER_DISABLED);
3129 if (FrameReached(&energy_delay))
3131 if (game_mm.energy_left > 0)
3133 game_mm.energy_left--;
3135 redraw_mask |= REDRAW_DOOR_1;
3137 else if (game.time_limit && !game_mm.game_over)
3141 for (i = 15; i >= 0; i--)
3144 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3146 pen_ray = GetPixelFromRGB(window,
3147 native_mm_level.laser_red * 0x11 * i,
3148 native_mm_level.laser_green * 0x11 * i,
3149 native_mm_level.laser_blue * 0x11 * i);
3151 DrawLaser(0, DL_LASER_ENABLED);
3153 Delay_WithScreenUpdates(50);
3156 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3161 DrawLaser(0, DL_LASER_DISABLED);
3162 game_mm.game_over = TRUE;
3163 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3165 SetTileCursorActive(FALSE);
3167 game.restart_game_message = "Out of magic energy! Play it again?";
3173 element = laser.dest_element;
3176 if (element != Tile[ELX][ELY])
3178 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3179 element, Tile[ELX][ELY]);
3183 if (!laser.overloaded && laser.overload_value == 0 &&
3184 element != EL_BOMB &&
3185 element != EL_MINE &&
3186 element != EL_BALL_GRAY &&
3187 element != EL_BLOCK_STONE &&
3188 element != EL_BLOCK_WOOD &&
3189 element != EL_FUSE_ON &&
3190 element != EL_FUEL_FULL &&
3191 !IS_WALL_ICE(element) &&
3192 !IS_WALL_AMOEBA(element))
3195 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3197 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3198 (!laser.overloaded && laser.overload_value > 0)) &&
3199 FrameReached(&overload_delay))
3201 if (laser.overloaded)
3202 laser.overload_value++;
3204 laser.overload_value--;
3206 if (game_mm.cheat_no_overload)
3208 laser.overloaded = FALSE;
3209 laser.overload_value = 0;
3212 game_mm.laser_overload_value = laser.overload_value;
3214 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3216 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3217 int color_down = 0xFF - color_up;
3220 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3221 (15 - (laser.overload_value / 6)) * color_scale);
3224 GetPixelFromRGB(window,
3225 (native_mm_level.laser_red ? 0xFF : color_up),
3226 (native_mm_level.laser_green ? color_down : 0x00),
3227 (native_mm_level.laser_blue ? color_down : 0x00));
3229 DrawLaser(0, DL_LASER_ENABLED);
3232 if (!laser.overloaded)
3233 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3234 else if (setup.sound_loops)
3235 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3237 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3239 if (laser.overloaded)
3242 BlitBitmap(pix[PIX_DOOR], drawto,
3243 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3244 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3245 - laser.overload_value,
3246 OVERLOAD_XSIZE, laser.overload_value,
3247 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3248 - laser.overload_value);
3250 redraw_mask |= REDRAW_DOOR_1;
3255 BlitBitmap(pix[PIX_DOOR], drawto,
3256 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3257 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3258 DX_OVERLOAD, DY_OVERLOAD);
3260 redraw_mask |= REDRAW_DOOR_1;
3263 if (laser.overload_value == MAX_LASER_OVERLOAD)
3267 UpdateAndDisplayGameControlValues();
3269 for (i = 15; i >= 0; i--)
3272 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3275 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3277 DrawLaser(0, DL_LASER_ENABLED);
3279 Delay_WithScreenUpdates(50);
3282 DrawLaser(0, DL_LASER_DISABLED);
3284 game_mm.game_over = TRUE;
3285 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3287 SetTileCursorActive(FALSE);
3289 game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?";
3300 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3302 if (game_mm.cheat_no_explosion)
3307 laser.dest_element = EL_EXPLODING_OPAQUE;
3312 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3314 laser.fuse_off = TRUE;
3318 DrawLaser(0, DL_LASER_DISABLED);
3319 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3322 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3324 static int new_elements[] =
3327 EL_MIRROR_FIXED_START,
3329 EL_POLAR_CROSS_START,
3335 int num_new_elements = sizeof(new_elements) / sizeof(int);
3336 int new_element = new_elements[RND(num_new_elements)];
3338 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3339 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3341 // !!! CHECK AGAIN: Laser on Polarizer !!!
3352 element = EL_MIRROR_START + RND(16);
3358 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3365 element = (rnd == 0 ? EL_FUSE_ON :
3366 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3367 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3368 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3369 EL_MIRROR_FIXED_START + rnd - 25);
3374 graphic = el2gfx(element);
3376 for (i = 0; i < 50; i++)
3382 BlitBitmap(pix[PIX_BACK], drawto,
3383 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3384 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3385 SX + ELX * TILEX + x,
3386 SY + ELY * TILEY + y);
3388 MarkTileDirty(ELX, ELY);
3391 DrawLaser(0, DL_LASER_ENABLED);
3393 Delay_WithScreenUpdates(50);
3396 Tile[ELX][ELY] = element;
3397 DrawField_MM(ELX, ELY);
3400 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3403 // above stuff: GRAY BALL -> PRISM !!!
3405 LX = ELX * TILEX + 14;
3406 LY = ELY * TILEY + 14;
3407 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3414 laser.num_edges -= 2;
3415 laser.num_damages--;
3419 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3420 if (laser.damage[i].is_mirror)
3424 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3426 DrawLaser(0, DL_LASER_DISABLED);
3428 DrawLaser(0, DL_LASER_DISABLED);
3437 if (IS_WALL_ICE(element) && CT > 50)
3439 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3442 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3443 Store[ELX][ELY] = EL_WALL_ICE;
3444 Store2[ELX][ELY] = laser.wall_mask;
3446 laser.dest_element = Tile[ELX][ELY];
3451 for (i = 0; i < 5; i++)
3457 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3461 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3463 Delay_WithScreenUpdates(100);
3466 if (Tile[ELX][ELY] == EL_WALL_ICE)
3467 Tile[ELX][ELY] = EL_EMPTY;
3471 LX = laser.edge[laser.num_edges].x - cSX2;
3472 LY = laser.edge[laser.num_edges].y - cSY2;
3475 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3476 if (laser.damage[i].is_mirror)
3480 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3482 DrawLaser(0, DL_LASER_DISABLED);
3489 if (IS_WALL_AMOEBA(element) && CT > 60)
3491 int k1, k2, k3, dx, dy, de, dm;
3492 int element2 = Tile[ELX][ELY];
3494 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3497 for (i = laser.num_damages - 1; i >= 0; i--)
3498 if (laser.damage[i].is_mirror)
3501 r = laser.num_edges;
3502 d = laser.num_damages;
3509 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3512 DrawLaser(0, DL_LASER_ENABLED);
3515 x = laser.damage[k1].x;
3516 y = laser.damage[k1].y;
3521 for (i = 0; i < 4; i++)
3523 if (laser.wall_mask & (1 << i))
3525 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3526 cSY + ELY * TILEY + 31 * (i / 2)))
3529 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3530 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3537 for (i = 0; i < 4; i++)
3539 if (laser.wall_mask & (1 << i))
3541 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3542 cSY + ELY * TILEY + 31 * (i / 2)))
3549 if (laser.num_beamers > 0 ||
3550 k1 < 1 || k2 < 4 || k3 < 4 ||
3551 CheckLaserPixel(cSX + ELX * TILEX + 14,
3552 cSY + ELY * TILEY + 14))
3554 laser.num_edges = r;
3555 laser.num_damages = d;
3557 DrawLaser(0, DL_LASER_DISABLED);
3560 Tile[ELX][ELY] = element | laser.wall_mask;
3564 de = Tile[ELX][ELY];
3565 dm = laser.wall_mask;
3569 int x = ELX, y = ELY;
3570 int wall_mask = laser.wall_mask;
3573 DrawLaser(0, DL_LASER_ENABLED);
3575 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3577 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3578 Store[x][y] = EL_WALL_AMOEBA;
3579 Store2[x][y] = wall_mask;
3585 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3587 DrawLaser(0, DL_LASER_ENABLED);
3589 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3591 for (i = 4; i >= 0; i--)
3593 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3596 Delay_WithScreenUpdates(20);
3599 DrawLaser(0, DL_LASER_ENABLED);
3604 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3605 laser.stops_inside_element && CT > native_mm_level.time_block)
3610 if (ABS(XS) > ABS(YS))
3617 for (i = 0; i < 4; i++)
3624 x = ELX + Step[k * 4].x;
3625 y = ELY + Step[k * 4].y;
3627 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3630 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3638 laser.overloaded = (element == EL_BLOCK_STONE);
3643 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3646 Tile[x][y] = element;
3648 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3651 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3653 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3654 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3662 if (element == EL_FUEL_FULL && CT > 10)
3664 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3667 BlitBitmap(pix[PIX_DOOR], drawto,
3668 DOOR_GFX_PAGEX4 + XX_ENERGY,
3669 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3670 ENERGY_XSIZE, i, DX_ENERGY,
3671 DY_ENERGY + ENERGY_YSIZE - i);
3674 redraw_mask |= REDRAW_DOOR_1;
3677 Delay_WithScreenUpdates(20);
3680 game_mm.energy_left = MAX_LASER_ENERGY;
3681 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3682 DrawField_MM(ELX, ELY);
3684 DrawLaser(0, DL_LASER_ENABLED);
3692 void GameActions_MM(struct MouseActionInfo action)
3694 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3695 boolean button_released = (action.button == MB_RELEASED);
3697 GameActions_MM_Ext();
3699 CheckSingleStepMode_MM(element_clicked, button_released);
3702 void MovePacMen(void)
3704 int mx, my, ox, oy, nx, ny;
3708 if (++pacman_nr >= game_mm.num_pacman)
3711 game_mm.pacman[pacman_nr].dir--;
3713 for (l = 1; l < 5; l++)
3715 game_mm.pacman[pacman_nr].dir++;
3717 if (game_mm.pacman[pacman_nr].dir > 4)
3718 game_mm.pacman[pacman_nr].dir = 1;
3720 if (game_mm.pacman[pacman_nr].dir % 2)
3723 my = game_mm.pacman[pacman_nr].dir - 2;
3728 mx = 3 - game_mm.pacman[pacman_nr].dir;
3731 ox = game_mm.pacman[pacman_nr].x;
3732 oy = game_mm.pacman[pacman_nr].y;
3735 element = Tile[nx][ny];
3737 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3740 if (!IS_EATABLE4PACMAN(element))
3743 if (ObjHit(nx, ny, HIT_POS_CENTER))
3746 Tile[ox][oy] = EL_EMPTY;
3748 EL_PACMAN_RIGHT - 1 +
3749 (game_mm.pacman[pacman_nr].dir - 1 +
3750 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3752 game_mm.pacman[pacman_nr].x = nx;
3753 game_mm.pacman[pacman_nr].y = ny;
3755 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3757 if (element != EL_EMPTY)
3759 int graphic = el2gfx(Tile[nx][ny]);
3764 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3767 ox = cSX + ox * TILEX;
3768 oy = cSY + oy * TILEY;
3770 for (i = 1; i < 33; i += 2)
3771 BlitBitmap(bitmap, window,
3772 src_x, src_y, TILEX, TILEY,
3773 ox + i * mx, oy + i * my);
3774 Ct = Ct + FrameCounter - CT;
3777 DrawField_MM(nx, ny);
3780 if (!laser.fuse_off)
3782 DrawLaser(0, DL_LASER_ENABLED);
3784 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3786 AddDamagedField(nx, ny);
3788 laser.damage[laser.num_damages - 1].edge = 0;
3792 if (element == EL_BOMB)
3793 DeletePacMan(nx, ny);
3795 if (IS_WALL_AMOEBA(element) &&
3796 (LX + 2 * XS) / TILEX == nx &&
3797 (LY + 2 * YS) / TILEY == ny)
3807 static void InitMovingField_MM(int x, int y, int direction)
3809 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3810 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3812 MovDir[x][y] = direction;
3813 MovDir[newx][newy] = direction;
3815 if (Tile[newx][newy] == EL_EMPTY)
3816 Tile[newx][newy] = EL_BLOCKED;
3819 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3821 int direction = MovDir[x][y];
3822 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3823 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3829 static void Blocked2Moving_MM(int x, int y,
3830 int *comes_from_x, int *comes_from_y)
3832 int oldx = x, oldy = y;
3833 int direction = MovDir[x][y];
3835 if (direction == MV_LEFT)
3837 else if (direction == MV_RIGHT)
3839 else if (direction == MV_UP)
3841 else if (direction == MV_DOWN)
3844 *comes_from_x = oldx;
3845 *comes_from_y = oldy;
3848 static int MovingOrBlocked2Element_MM(int x, int y)
3850 int element = Tile[x][y];
3852 if (element == EL_BLOCKED)
3856 Blocked2Moving_MM(x, y, &oldx, &oldy);
3858 return Tile[oldx][oldy];
3865 static void RemoveField(int x, int y)
3867 Tile[x][y] = EL_EMPTY;
3874 static void RemoveMovingField_MM(int x, int y)
3876 int oldx = x, oldy = y, newx = x, newy = y;
3878 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3881 if (IS_MOVING(x, y))
3883 Moving2Blocked_MM(x, y, &newx, &newy);
3884 if (Tile[newx][newy] != EL_BLOCKED)
3887 else if (Tile[x][y] == EL_BLOCKED)
3889 Blocked2Moving_MM(x, y, &oldx, &oldy);
3890 if (!IS_MOVING(oldx, oldy))
3894 Tile[oldx][oldy] = EL_EMPTY;
3895 Tile[newx][newy] = EL_EMPTY;
3896 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3897 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3899 DrawLevelField_MM(oldx, oldy);
3900 DrawLevelField_MM(newx, newy);
3903 void PlaySoundLevel(int x, int y, int sound_nr)
3905 int sx = SCREENX(x), sy = SCREENY(y);
3907 int silence_distance = 8;
3909 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3910 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3913 if (!IN_LEV_FIELD(x, y) ||
3914 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3915 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3918 volume = SOUND_MAX_VOLUME;
3921 stereo = (sx - SCR_FIELDX/2) * 12;
3923 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3924 if (stereo > SOUND_MAX_RIGHT)
3925 stereo = SOUND_MAX_RIGHT;
3926 if (stereo < SOUND_MAX_LEFT)
3927 stereo = SOUND_MAX_LEFT;
3930 if (!IN_SCR_FIELD(sx, sy))
3932 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3933 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3935 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3938 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3941 static void RaiseScore_MM(int value)
3943 game_mm.score += value;
3946 void RaiseScoreElement_MM(int element)
3951 case EL_PACMAN_RIGHT:
3953 case EL_PACMAN_LEFT:
3954 case EL_PACMAN_DOWN:
3955 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3959 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3964 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3968 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3977 // ----------------------------------------------------------------------------
3978 // Mirror Magic game engine snapshot handling functions
3979 // ----------------------------------------------------------------------------
3981 void SaveEngineSnapshotValues_MM(void)
3985 engine_snapshot_mm.game_mm = game_mm;
3986 engine_snapshot_mm.laser = laser;
3988 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3990 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3992 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3993 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3994 engine_snapshot_mm.Box[x][y] = Box[x][y];
3995 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3996 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4000 engine_snapshot_mm.LX = LX;
4001 engine_snapshot_mm.LY = LY;
4002 engine_snapshot_mm.XS = XS;
4003 engine_snapshot_mm.YS = YS;
4004 engine_snapshot_mm.ELX = ELX;
4005 engine_snapshot_mm.ELY = ELY;
4006 engine_snapshot_mm.CT = CT;
4007 engine_snapshot_mm.Ct = Ct;
4009 engine_snapshot_mm.last_LX = last_LX;
4010 engine_snapshot_mm.last_LY = last_LY;
4011 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4012 engine_snapshot_mm.hold_x = hold_x;
4013 engine_snapshot_mm.hold_y = hold_y;
4014 engine_snapshot_mm.pacman_nr = pacman_nr;
4016 engine_snapshot_mm.rotate_delay = rotate_delay;
4017 engine_snapshot_mm.pacman_delay = pacman_delay;
4018 engine_snapshot_mm.energy_delay = energy_delay;
4019 engine_snapshot_mm.overload_delay = overload_delay;
4022 void LoadEngineSnapshotValues_MM(void)
4026 // stored engine snapshot buffers already restored at this point
4028 game_mm = engine_snapshot_mm.game_mm;
4029 laser = engine_snapshot_mm.laser;
4031 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4033 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4035 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4036 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4037 Box[x][y] = engine_snapshot_mm.Box[x][y];
4038 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4039 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4043 LX = engine_snapshot_mm.LX;
4044 LY = engine_snapshot_mm.LY;
4045 XS = engine_snapshot_mm.XS;
4046 YS = engine_snapshot_mm.YS;
4047 ELX = engine_snapshot_mm.ELX;
4048 ELY = engine_snapshot_mm.ELY;
4049 CT = engine_snapshot_mm.CT;
4050 Ct = engine_snapshot_mm.Ct;
4052 last_LX = engine_snapshot_mm.last_LX;
4053 last_LY = engine_snapshot_mm.last_LY;
4054 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4055 hold_x = engine_snapshot_mm.hold_x;
4056 hold_y = engine_snapshot_mm.hold_y;
4057 pacman_nr = engine_snapshot_mm.pacman_nr;
4059 rotate_delay = engine_snapshot_mm.rotate_delay;
4060 pacman_delay = engine_snapshot_mm.pacman_delay;
4061 energy_delay = engine_snapshot_mm.energy_delay;
4062 overload_delay = engine_snapshot_mm.overload_delay;
4064 RedrawPlayfield_MM();
4067 static int getAngleFromTouchDelta(int dx, int dy, int base)
4069 double pi = 3.141592653;
4070 double rad = atan2((double)-dy, (double)dx);
4071 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4072 double deg = rad2 * 180.0 / pi;
4074 return (int)(deg * base / 360.0 + 0.5) % base;
4077 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4079 // calculate start (source) position to be at the middle of the tile
4080 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4081 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4082 int dx = dst_mx - src_mx;
4083 int dy = dst_my - src_my;
4092 if (!IN_LEV_FIELD(x, y))
4095 element = Tile[x][y];
4097 if (!IS_MCDUFFIN(element) &&
4098 !IS_MIRROR(element) &&
4099 !IS_BEAMER(element) &&
4100 !IS_POLAR(element) &&
4101 !IS_POLAR_CROSS(element) &&
4102 !IS_DF_MIRROR(element))
4105 angle_old = get_element_angle(element);
4107 if (IS_MCDUFFIN(element))
4109 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4110 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4111 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4112 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4115 else if (IS_MIRROR(element) ||
4116 IS_DF_MIRROR(element))
4118 for (i = 0; i < laser.num_damages; i++)
4120 if (laser.damage[i].x == x &&
4121 laser.damage[i].y == y &&
4122 ObjHit(x, y, HIT_POS_CENTER))
4124 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4125 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4132 if (angle_new == -1)
4134 if (IS_MIRROR(element) ||
4135 IS_DF_MIRROR(element) ||
4139 if (IS_POLAR_CROSS(element))
4142 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4145 button = (angle_new == angle_old ? 0 :
4146 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4147 MB_LEFTBUTTON : MB_RIGHTBUTTON);