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
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
100 static void Moving2Blocked_MM(int, int, int *, int *);
102 // bitmap for laser beam detection
103 static Bitmap *laser_bitmap = NULL;
105 // variables for laser control
106 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
107 static int hold_x = -1, hold_y = -1;
109 // variables for pacman control
110 static int pacman_nr = -1;
112 // various game engine delay counters
113 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
114 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
115 static DelayCounter energy_delay = { ENERGY_DELAY };
116 static DelayCounter overload_delay = { 0 };
118 // element mask positions for scanning pixels of MM elements
119 #define MM_MASK_MCDUFFIN_RIGHT 0
120 #define MM_MASK_MCDUFFIN_UP 1
121 #define MM_MASK_MCDUFFIN_LEFT 2
122 #define MM_MASK_MCDUFFIN_DOWN 3
123 #define MM_MASK_GRID_1 4
124 #define MM_MASK_GRID_2 5
125 #define MM_MASK_GRID_3 6
126 #define MM_MASK_GRID_4 7
127 #define MM_MASK_RECTANGLE 8
128 #define MM_MASK_CIRCLE 9
130 #define NUM_MM_MASKS 10
132 // element masks for scanning pixels of MM elements
133 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
317 static int get_element_angle(int element)
319 int element_phase = get_element_phase(element);
321 if (IS_MIRROR_FIXED(element) ||
322 IS_MCDUFFIN(element) ||
324 IS_RECEIVER(element))
325 return 4 * element_phase;
327 return element_phase;
330 static int get_opposite_angle(int angle)
332 int opposite_angle = angle + ANG_RAY_180;
334 // make sure "opposite_angle" is in valid interval [0, 15]
335 return (opposite_angle + 16) % 16;
338 static int get_mirrored_angle(int laser_angle, int mirror_angle)
340 int reflected_angle = 16 - laser_angle + mirror_angle;
342 // make sure "reflected_angle" is in valid interval [0, 15]
343 return (reflected_angle + 16) % 16;
346 static void DrawLaserLines(struct XY *points, int num_points, int mode)
348 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
349 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
351 DrawLines(drawto, points, num_points, pixel_drawto);
355 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
360 static boolean CheckLaserPixel(int x, int y)
366 pixel = ReadPixel(laser_bitmap, x, y);
370 return (pixel == WHITE_PIXEL);
373 static void CheckExitMM(void)
375 int exit_element = EL_EMPTY;
379 static int xy[4][2] =
387 for (y = 0; y < lev_fieldy; y++)
389 for (x = 0; x < lev_fieldx; x++)
391 if (Tile[x][y] == EL_EXIT_CLOSED)
393 // initiate opening animation of exit door
394 Tile[x][y] = EL_EXIT_OPENING;
396 exit_element = EL_EXIT_OPEN;
400 else if (IS_RECEIVER(Tile[x][y]))
402 // remove field that blocks receiver
403 int phase = Tile[x][y] - EL_RECEIVER_START;
404 int blocking_x, blocking_y;
406 blocking_x = x + xy[phase][0];
407 blocking_y = y + xy[phase][1];
409 if (IN_LEV_FIELD(blocking_x, blocking_y))
411 Tile[blocking_x][blocking_y] = EL_EMPTY;
413 DrawField_MM(blocking_x, blocking_y);
416 exit_element = EL_RECEIVER;
423 if (exit_element != EL_EMPTY)
424 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
427 static void SetLaserColor(int brightness)
429 int color_min = 0x00;
430 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
431 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
432 int color_down = color_max - color_up;
435 GetPixelFromRGB(window,
436 (native_mm_level.laser_red ? color_max : color_up),
437 (native_mm_level.laser_green ? color_down : color_min),
438 (native_mm_level.laser_blue ? color_down : color_min));
441 static void InitMovDir_MM(int x, int y)
443 int element = Tile[x][y];
444 static int direction[3][4] =
446 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
447 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
448 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
453 case EL_PACMAN_RIGHT:
457 Tile[x][y] = EL_PACMAN;
458 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
466 static void InitField(int x, int y)
468 int element = Tile[x][y];
473 Tile[x][y] = EL_EMPTY;
478 if (native_mm_level.auto_count_kettles)
479 game_mm.kettles_still_needed++;
482 case EL_LIGHTBULB_OFF:
483 game_mm.lights_still_needed++;
487 if (IS_MIRROR(element) ||
488 IS_BEAMER_OLD(element) ||
489 IS_BEAMER(element) ||
491 IS_POLAR_CROSS(element) ||
492 IS_DF_MIRROR(element) ||
493 IS_DF_MIRROR_AUTO(element) ||
494 IS_GRID_STEEL_AUTO(element) ||
495 IS_GRID_WOOD_AUTO(element) ||
496 IS_FIBRE_OPTIC(element))
498 if (IS_BEAMER_OLD(element))
500 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
501 element = Tile[x][y];
504 if (!IS_FIBRE_OPTIC(element))
506 static int steps_grid_auto = 0;
508 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
509 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
511 if (IS_GRID_STEEL_AUTO(element) ||
512 IS_GRID_WOOD_AUTO(element))
513 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
515 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
517 game_mm.cycle[game_mm.num_cycle].x = x;
518 game_mm.cycle[game_mm.num_cycle].y = y;
522 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
524 int beamer_nr = BEAMER_NR(element);
525 int nr = laser.beamer[beamer_nr][0].num;
527 laser.beamer[beamer_nr][nr].x = x;
528 laser.beamer[beamer_nr][nr].y = y;
529 laser.beamer[beamer_nr][nr].num = 1;
532 else if (IS_PACMAN(element))
536 else if (IS_MCDUFFIN(element) || IS_LASER(element))
538 laser.start_edge.x = x;
539 laser.start_edge.y = y;
540 laser.start_angle = get_element_angle(element);
547 static void InitCycleElements_RotateSingleStep(void)
551 if (game_mm.num_cycle == 0) // no elements to cycle
554 for (i = 0; i < game_mm.num_cycle; i++)
556 int x = game_mm.cycle[i].x;
557 int y = game_mm.cycle[i].y;
558 int step = SIGN(game_mm.cycle[i].steps);
559 int last_element = Tile[x][y];
560 int next_element = get_rotated_element(last_element, step);
562 if (!game_mm.cycle[i].steps)
565 Tile[x][y] = next_element;
567 game_mm.cycle[i].steps -= step;
571 static void InitLaser(void)
573 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
574 int step = (IS_LASER(start_element) ? 4 : 0);
576 LX = laser.start_edge.x * TILEX;
577 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
580 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
582 LY = laser.start_edge.y * TILEY;
583 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
584 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
588 XS = 2 * Step[laser.start_angle].x;
589 YS = 2 * Step[laser.start_angle].y;
591 laser.current_angle = laser.start_angle;
593 laser.num_damages = 0;
595 laser.num_beamers = 0;
596 laser.beamer_edge[0] = 0;
598 laser.dest_element = EL_EMPTY;
601 AddLaserEdge(LX, LY); // set laser starting edge
606 void InitGameEngine_MM(void)
612 // initialize laser bitmap to current playfield (screen) size
613 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
614 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
618 // set global game control values
619 game_mm.num_cycle = 0;
620 game_mm.num_pacman = 0;
623 game_mm.energy_left = 0; // later set to "native_mm_level.time"
624 game_mm.kettles_still_needed =
625 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
626 game_mm.lights_still_needed = 0;
627 game_mm.num_keys = 0;
629 game_mm.level_solved = FALSE;
630 game_mm.game_over = FALSE;
631 game_mm.game_over_cause = 0;
633 game_mm.laser_overload_value = 0;
634 game_mm.laser_enabled = FALSE;
636 // set global laser control values (must be set before "InitLaser()")
637 laser.start_edge.x = 0;
638 laser.start_edge.y = 0;
639 laser.start_angle = 0;
641 for (i = 0; i < MAX_NUM_BEAMERS; i++)
642 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
644 laser.overloaded = FALSE;
645 laser.overload_value = 0;
646 laser.fuse_off = FALSE;
647 laser.fuse_x = laser.fuse_y = -1;
649 laser.dest_element = EL_EMPTY;
663 rotate_delay.count = 0;
664 pacman_delay.count = 0;
665 energy_delay.count = 0;
666 overload_delay.count = 0;
668 ClickElement(-1, -1, -1);
670 for (x = 0; x < lev_fieldx; x++)
672 for (y = 0; y < lev_fieldy; y++)
674 Tile[x][y] = Ur[x][y];
675 Hit[x][y] = Box[x][y] = 0;
677 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
678 Store[x][y] = Store2[x][y] = 0;
688 void InitGameActions_MM(void)
690 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
691 int cycle_steps_done = 0;
696 for (i = 0; i <= num_init_game_frames; i++)
698 if (i == num_init_game_frames)
699 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
700 else if (setup.sound_loops)
701 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
703 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
705 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
707 UpdateAndDisplayGameControlValues();
709 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
711 InitCycleElements_RotateSingleStep();
716 AdvanceFrameCounter();
726 if (setup.quick_doors)
733 if (game_mm.kettles_still_needed == 0)
736 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
737 SetTileCursorActive(TRUE);
739 ResetFrameCounter(&energy_delay);
742 static void FadeOutLaser(void)
746 for (i = 15; i >= 0; i--)
748 SetLaserColor(0x11 * i);
750 DrawLaser(0, DL_LASER_ENABLED);
753 Delay_WithScreenUpdates(50);
756 DrawLaser(0, DL_LASER_DISABLED);
758 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
761 static void GameOver_MM(int game_over_cause)
763 // do not handle game over if request dialog is already active
764 if (game.request_active)
767 game_mm.game_over = TRUE;
768 game_mm.game_over_cause = game_over_cause;
770 if (setup.ask_on_game_over)
771 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
772 "Bomb killed Mc Duffin! Play it again?" :
773 game_over_cause == GAME_OVER_NO_ENERGY ?
774 "Out of magic energy! Play it again?" :
775 game_over_cause == GAME_OVER_OVERLOADED ?
776 "Magic spell hit Mc Duffin! Play it again?" :
779 SetTileCursorActive(FALSE);
782 void AddLaserEdge(int lx, int ly)
787 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
789 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
794 laser.edge[laser.num_edges].x = cSX2 + lx;
795 laser.edge[laser.num_edges].y = cSY2 + ly;
801 void AddDamagedField(int ex, int ey)
803 laser.damage[laser.num_damages].is_mirror = FALSE;
804 laser.damage[laser.num_damages].angle = laser.current_angle;
805 laser.damage[laser.num_damages].edge = laser.num_edges;
806 laser.damage[laser.num_damages].x = ex;
807 laser.damage[laser.num_damages].y = ey;
811 static boolean StepBehind(void)
817 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
818 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
820 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
826 static int getMaskFromElement(int element)
828 if (IS_GRID(element))
829 return MM_MASK_GRID_1 + get_element_phase(element);
830 else if (IS_MCDUFFIN(element))
831 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
832 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
833 return MM_MASK_RECTANGLE;
835 return MM_MASK_CIRCLE;
838 static int ScanPixel(void)
843 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
844 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
847 // follow laser beam until it hits something (at least the screen border)
848 while (hit_mask == HIT_MASK_NO_HIT)
854 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
855 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
857 Debug("game:mm:ScanPixel", "touched screen border!");
863 for (i = 0; i < 4; i++)
865 int px = LX + (i % 2) * 2;
866 int py = LY + (i / 2) * 2;
869 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
870 int ly = (py + TILEY) / TILEY - 1; // negative values!
873 if (IN_LEV_FIELD(lx, ly))
875 int element = Tile[lx][ly];
877 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
881 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
883 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
885 pixel = ((element & (1 << pos)) ? 1 : 0);
889 int pos = getMaskFromElement(element);
891 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
896 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
897 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
900 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
901 hit_mask |= (1 << i);
904 if (hit_mask == HIT_MASK_NO_HIT)
906 // hit nothing -- go on with another step
918 int end = 0, rf = laser.num_edges;
920 // do not scan laser again after the game was lost for whatever reason
921 if (game_mm.game_over)
924 laser.overloaded = FALSE;
925 laser.stops_inside_element = FALSE;
927 DrawLaser(0, DL_LASER_ENABLED);
930 Debug("game:mm:ScanLaser",
931 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
939 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
942 laser.overloaded = TRUE;
947 hit_mask = ScanPixel();
950 Debug("game:mm:ScanLaser",
951 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
955 // hit something -- check out what it was
956 ELX = (LX + XS) / TILEX;
957 ELY = (LY + YS) / TILEY;
960 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
961 hit_mask, LX, LY, ELX, ELY);
964 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
967 laser.dest_element = element;
972 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
974 /* we have hit the top-right and bottom-left element --
975 choose the bottom-left one */
976 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
977 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
978 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
979 ELX = (LX - 2) / TILEX;
980 ELY = (LY + 2) / TILEY;
983 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
985 /* we have hit the top-left and bottom-right element --
986 choose the top-left one */
988 ELX = (LX - 2) / TILEX;
989 ELY = (LY - 2) / TILEY;
993 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
994 hit_mask, LX, LY, ELX, ELY);
997 element = Tile[ELX][ELY];
998 laser.dest_element = element;
1001 Debug("game:mm:ScanLaser",
1002 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1005 LX % TILEX, LY % TILEY,
1010 if (!IN_LEV_FIELD(ELX, ELY))
1011 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1015 if (element == EL_EMPTY)
1017 if (!HitOnlyAnEdge(hit_mask))
1020 else if (element == EL_FUSE_ON)
1022 if (HitPolarizer(element, hit_mask))
1025 else if (IS_GRID(element) || IS_DF_GRID(element))
1027 if (HitPolarizer(element, hit_mask))
1030 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1031 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1033 if (HitBlock(element, hit_mask))
1040 else if (IS_MCDUFFIN(element))
1042 if (HitLaserSource(element, hit_mask))
1045 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1046 IS_RECEIVER(element))
1048 if (HitLaserDestination(element, hit_mask))
1051 else if (IS_WALL(element))
1053 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1055 if (HitReflectingWalls(element, hit_mask))
1060 if (HitAbsorbingWalls(element, hit_mask))
1066 if (HitElement(element, hit_mask))
1071 DrawLaser(rf - 1, DL_LASER_ENABLED);
1072 rf = laser.num_edges;
1076 if (laser.dest_element != Tile[ELX][ELY])
1078 Debug("game:mm:ScanLaser",
1079 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1080 laser.dest_element, Tile[ELX][ELY]);
1084 if (!end && !laser.stops_inside_element && !StepBehind())
1087 Debug("game:mm:ScanLaser", "Go one step back");
1093 AddLaserEdge(LX, LY);
1097 DrawLaser(rf - 1, DL_LASER_ENABLED);
1099 Ct = CT = FrameCounter;
1102 if (!IN_LEV_FIELD(ELX, ELY))
1103 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1107 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1113 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1114 start_edge, num_edges, mode);
1119 Warn("DrawLaserExt: start_edge < 0");
1126 Warn("DrawLaserExt: num_edges < 0");
1132 if (mode == DL_LASER_DISABLED)
1134 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1138 // now draw the laser to the backbuffer and (if enabled) to the screen
1139 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1141 redraw_mask |= REDRAW_FIELD;
1143 if (mode == DL_LASER_ENABLED)
1146 // after the laser was deleted, the "damaged" graphics must be restored
1147 if (laser.num_damages)
1149 int damage_start = 0;
1152 // determine the starting edge, from which graphics need to be restored
1155 for (i = 0; i < laser.num_damages; i++)
1157 if (laser.damage[i].edge == start_edge + 1)
1166 // restore graphics from this starting edge to the end of damage list
1167 for (i = damage_start; i < laser.num_damages; i++)
1169 int lx = laser.damage[i].x;
1170 int ly = laser.damage[i].y;
1171 int element = Tile[lx][ly];
1173 if (Hit[lx][ly] == laser.damage[i].edge)
1174 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1177 if (Box[lx][ly] == laser.damage[i].edge)
1180 if (IS_DRAWABLE(element))
1181 DrawField_MM(lx, ly);
1184 elx = laser.damage[damage_start].x;
1185 ely = laser.damage[damage_start].y;
1186 element = Tile[elx][ely];
1189 if (IS_BEAMER(element))
1193 for (i = 0; i < laser.num_beamers; i++)
1194 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1196 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1197 mode, elx, ely, Hit[elx][ely], start_edge);
1198 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1199 get_element_angle(element), laser.damage[damage_start].angle);
1203 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1204 laser.num_beamers > 0 &&
1205 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1207 // element is outgoing beamer
1208 laser.num_damages = damage_start + 1;
1210 if (IS_BEAMER(element))
1211 laser.current_angle = get_element_angle(element);
1215 // element is incoming beamer or other element
1216 laser.num_damages = damage_start;
1217 laser.current_angle = laser.damage[laser.num_damages].angle;
1222 // no damages but McDuffin himself (who needs to be redrawn anyway)
1224 elx = laser.start_edge.x;
1225 ely = laser.start_edge.y;
1226 element = Tile[elx][ely];
1229 laser.num_edges = start_edge + 1;
1230 if (start_edge == 0)
1231 laser.current_angle = laser.start_angle;
1233 LX = laser.edge[start_edge].x - cSX2;
1234 LY = laser.edge[start_edge].y - cSY2;
1235 XS = 2 * Step[laser.current_angle].x;
1236 YS = 2 * Step[laser.current_angle].y;
1239 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1245 if (IS_BEAMER(element) ||
1246 IS_FIBRE_OPTIC(element) ||
1247 IS_PACMAN(element) ||
1248 IS_POLAR(element) ||
1249 IS_POLAR_CROSS(element) ||
1250 element == EL_FUSE_ON)
1255 Debug("game:mm:DrawLaserExt", "element == %d", element);
1258 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1259 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1263 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1264 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1265 (laser.num_beamers == 0 ||
1266 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1268 // element is incoming beamer or other element
1269 step_size = -step_size;
1274 if (IS_BEAMER(element))
1275 Debug("game:mm:DrawLaserExt",
1276 "start_edge == %d, laser.beamer_edge == %d",
1277 start_edge, laser.beamer_edge);
1280 LX += step_size * XS;
1281 LY += step_size * YS;
1283 else if (element != EL_EMPTY)
1292 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1297 void DrawLaser(int start_edge, int mode)
1299 if (laser.num_edges - start_edge < 0)
1301 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1306 // check if laser is interrupted by beamer element
1307 if (laser.num_beamers > 0 &&
1308 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1310 if (mode == DL_LASER_ENABLED)
1313 int tmp_start_edge = start_edge;
1315 // draw laser segments forward from the start to the last beamer
1316 for (i = 0; i < laser.num_beamers; i++)
1318 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1320 if (tmp_num_edges <= 0)
1324 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1325 i, laser.beamer_edge[i], tmp_start_edge);
1328 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1330 tmp_start_edge = laser.beamer_edge[i];
1333 // draw last segment from last beamer to the end
1334 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1340 int last_num_edges = laser.num_edges;
1341 int num_beamers = laser.num_beamers;
1343 // delete laser segments backward from the end to the first beamer
1344 for (i = num_beamers - 1; i >= 0; i--)
1346 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1348 if (laser.beamer_edge[i] - start_edge <= 0)
1351 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1353 last_num_edges = laser.beamer_edge[i];
1354 laser.num_beamers--;
1358 if (last_num_edges - start_edge <= 0)
1359 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1360 last_num_edges, start_edge);
1363 // special case when rotating first beamer: delete laser edge on beamer
1364 // (but do not start scanning on previous edge to prevent mirror sound)
1365 if (last_num_edges - start_edge == 1 && start_edge > 0)
1366 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1368 // delete first segment from start to the first beamer
1369 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1374 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1377 game_mm.laser_enabled = mode;
1380 void DrawLaser_MM(void)
1382 DrawLaser(0, game_mm.laser_enabled);
1385 boolean HitElement(int element, int hit_mask)
1387 if (HitOnlyAnEdge(hit_mask))
1390 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1391 element = MovingOrBlocked2Element_MM(ELX, ELY);
1394 Debug("game:mm:HitElement", "(1): element == %d", element);
1398 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1399 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1402 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1406 AddDamagedField(ELX, ELY);
1408 // this is more precise: check if laser would go through the center
1409 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1411 // skip the whole element before continuing the scan
1417 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1419 if (LX/TILEX > ELX || LY/TILEY > ELY)
1421 /* skipping scan positions to the right and down skips one scan
1422 position too much, because this is only the top left scan position
1423 of totally four scan positions (plus one to the right, one to the
1424 bottom and one to the bottom right) */
1434 Debug("game:mm:HitElement", "(2): element == %d", element);
1437 if (LX + 5 * XS < 0 ||
1447 Debug("game:mm:HitElement", "(3): element == %d", element);
1450 if (IS_POLAR(element) &&
1451 ((element - EL_POLAR_START) % 2 ||
1452 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1454 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1456 laser.num_damages--;
1461 if (IS_POLAR_CROSS(element) &&
1462 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1464 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1466 laser.num_damages--;
1471 if (!IS_BEAMER(element) &&
1472 !IS_FIBRE_OPTIC(element) &&
1473 !IS_GRID_WOOD(element) &&
1474 element != EL_FUEL_EMPTY)
1477 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1478 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1480 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1483 LX = ELX * TILEX + 14;
1484 LY = ELY * TILEY + 14;
1486 AddLaserEdge(LX, LY);
1489 if (IS_MIRROR(element) ||
1490 IS_MIRROR_FIXED(element) ||
1491 IS_POLAR(element) ||
1492 IS_POLAR_CROSS(element) ||
1493 IS_DF_MIRROR(element) ||
1494 IS_DF_MIRROR_AUTO(element) ||
1495 element == EL_PRISM ||
1496 element == EL_REFRACTOR)
1498 int current_angle = laser.current_angle;
1501 laser.num_damages--;
1503 AddDamagedField(ELX, ELY);
1505 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1508 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1510 if (IS_MIRROR(element) ||
1511 IS_MIRROR_FIXED(element) ||
1512 IS_DF_MIRROR(element) ||
1513 IS_DF_MIRROR_AUTO(element))
1514 laser.current_angle = get_mirrored_angle(laser.current_angle,
1515 get_element_angle(element));
1517 if (element == EL_PRISM || element == EL_REFRACTOR)
1518 laser.current_angle = RND(16);
1520 XS = 2 * Step[laser.current_angle].x;
1521 YS = 2 * Step[laser.current_angle].y;
1523 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1528 LX += step_size * XS;
1529 LY += step_size * YS;
1531 // draw sparkles on mirror
1532 if ((IS_MIRROR(element) ||
1533 IS_MIRROR_FIXED(element) ||
1534 element == EL_PRISM) &&
1535 current_angle != laser.current_angle)
1537 MovDelay[ELX][ELY] = 11; // start animation
1540 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1541 current_angle != laser.current_angle)
1542 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1545 (get_opposite_angle(laser.current_angle) ==
1546 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1548 return (laser.overloaded ? TRUE : FALSE);
1551 if (element == EL_FUEL_FULL)
1553 laser.stops_inside_element = TRUE;
1558 if (element == EL_BOMB || element == EL_MINE)
1560 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1562 if (element == EL_MINE)
1563 laser.overloaded = TRUE;
1566 if (element == EL_KETTLE ||
1567 element == EL_CELL ||
1568 element == EL_KEY ||
1569 element == EL_LIGHTBALL ||
1570 element == EL_PACMAN ||
1573 if (!IS_PACMAN(element))
1576 if (element == EL_PACMAN)
1579 if (element == EL_KETTLE || element == EL_CELL)
1581 if (game_mm.kettles_still_needed > 0)
1582 game_mm.kettles_still_needed--;
1584 game.snapshot.collected_item = TRUE;
1586 if (game_mm.kettles_still_needed == 0)
1590 DrawLaser(0, DL_LASER_ENABLED);
1593 else if (element == EL_KEY)
1597 else if (IS_PACMAN(element))
1599 DeletePacMan(ELX, ELY);
1602 RaiseScoreElement_MM(element);
1607 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1609 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1611 DrawLaser(0, DL_LASER_ENABLED);
1613 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1615 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1616 game_mm.lights_still_needed--;
1620 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1621 game_mm.lights_still_needed++;
1624 DrawField_MM(ELX, ELY);
1625 DrawLaser(0, DL_LASER_ENABLED);
1630 laser.stops_inside_element = TRUE;
1636 Debug("game:mm:HitElement", "(4): element == %d", element);
1639 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1640 laser.num_beamers < MAX_NUM_BEAMERS &&
1641 laser.beamer[BEAMER_NR(element)][1].num)
1643 int beamer_angle = get_element_angle(element);
1644 int beamer_nr = BEAMER_NR(element);
1648 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1651 laser.num_damages--;
1653 if (IS_FIBRE_OPTIC(element) ||
1654 laser.current_angle == get_opposite_angle(beamer_angle))
1658 LX = ELX * TILEX + 14;
1659 LY = ELY * TILEY + 14;
1661 AddLaserEdge(LX, LY);
1662 AddDamagedField(ELX, ELY);
1664 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1667 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1669 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1670 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1671 ELX = laser.beamer[beamer_nr][pos].x;
1672 ELY = laser.beamer[beamer_nr][pos].y;
1673 LX = ELX * TILEX + 14;
1674 LY = ELY * TILEY + 14;
1676 if (IS_BEAMER(element))
1678 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1679 XS = 2 * Step[laser.current_angle].x;
1680 YS = 2 * Step[laser.current_angle].y;
1683 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1685 AddLaserEdge(LX, LY);
1686 AddDamagedField(ELX, ELY);
1688 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1691 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1693 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1698 LX += step_size * XS;
1699 LY += step_size * YS;
1701 laser.num_beamers++;
1710 boolean HitOnlyAnEdge(int hit_mask)
1712 // check if the laser hit only the edge of an element and, if so, go on
1715 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1719 if ((hit_mask == HIT_MASK_TOPLEFT ||
1720 hit_mask == HIT_MASK_TOPRIGHT ||
1721 hit_mask == HIT_MASK_BOTTOMLEFT ||
1722 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1723 laser.current_angle % 4) // angle is not 90°
1727 if (hit_mask == HIT_MASK_TOPLEFT)
1732 else if (hit_mask == HIT_MASK_TOPRIGHT)
1737 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1742 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1748 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1754 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1761 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1767 boolean HitPolarizer(int element, int hit_mask)
1769 if (HitOnlyAnEdge(hit_mask))
1772 if (IS_DF_GRID(element))
1774 int grid_angle = get_element_angle(element);
1777 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1778 grid_angle, laser.current_angle);
1781 AddLaserEdge(LX, LY);
1782 AddDamagedField(ELX, ELY);
1785 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1787 if (laser.current_angle == grid_angle ||
1788 laser.current_angle == get_opposite_angle(grid_angle))
1790 // skip the whole element before continuing the scan
1796 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1798 if (LX/TILEX > ELX || LY/TILEY > ELY)
1800 /* skipping scan positions to the right and down skips one scan
1801 position too much, because this is only the top left scan position
1802 of totally four scan positions (plus one to the right, one to the
1803 bottom and one to the bottom right) */
1809 AddLaserEdge(LX, LY);
1815 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1817 LX / TILEX, LY / TILEY,
1818 LX % TILEX, LY % TILEY);
1823 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1825 return HitReflectingWalls(element, hit_mask);
1829 return HitAbsorbingWalls(element, hit_mask);
1832 else if (IS_GRID_STEEL(element))
1834 return HitReflectingWalls(element, hit_mask);
1836 else // IS_GRID_WOOD
1838 return HitAbsorbingWalls(element, hit_mask);
1844 boolean HitBlock(int element, int hit_mask)
1846 boolean check = FALSE;
1848 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1849 game_mm.num_keys == 0)
1852 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1855 int ex = ELX * TILEX + 14;
1856 int ey = ELY * TILEY + 14;
1860 for (i = 1; i < 32; i++)
1865 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1870 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1871 return HitAbsorbingWalls(element, hit_mask);
1875 AddLaserEdge(LX - XS, LY - YS);
1876 AddDamagedField(ELX, ELY);
1879 Box[ELX][ELY] = laser.num_edges;
1881 return HitReflectingWalls(element, hit_mask);
1884 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1886 int xs = XS / 2, ys = YS / 2;
1887 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1888 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1890 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1891 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1893 laser.overloaded = (element == EL_GATE_STONE);
1898 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1899 (hit_mask == HIT_MASK_TOP ||
1900 hit_mask == HIT_MASK_LEFT ||
1901 hit_mask == HIT_MASK_RIGHT ||
1902 hit_mask == HIT_MASK_BOTTOM))
1903 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1904 hit_mask == HIT_MASK_BOTTOM),
1905 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1906 hit_mask == HIT_MASK_RIGHT));
1907 AddLaserEdge(LX, LY);
1913 if (element == EL_GATE_STONE && Box[ELX][ELY])
1915 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1927 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1929 int xs = XS / 2, ys = YS / 2;
1930 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1931 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1933 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1934 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1936 laser.overloaded = (element == EL_BLOCK_STONE);
1941 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1942 (hit_mask == HIT_MASK_TOP ||
1943 hit_mask == HIT_MASK_LEFT ||
1944 hit_mask == HIT_MASK_RIGHT ||
1945 hit_mask == HIT_MASK_BOTTOM))
1946 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1947 hit_mask == HIT_MASK_BOTTOM),
1948 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1949 hit_mask == HIT_MASK_RIGHT));
1950 AddDamagedField(ELX, ELY);
1952 LX = ELX * TILEX + 14;
1953 LY = ELY * TILEY + 14;
1955 AddLaserEdge(LX, LY);
1957 laser.stops_inside_element = TRUE;
1965 boolean HitLaserSource(int element, int hit_mask)
1967 if (HitOnlyAnEdge(hit_mask))
1970 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1972 laser.overloaded = TRUE;
1977 boolean HitLaserDestination(int element, int hit_mask)
1979 if (HitOnlyAnEdge(hit_mask))
1982 if (element != EL_EXIT_OPEN &&
1983 !(IS_RECEIVER(element) &&
1984 game_mm.kettles_still_needed == 0 &&
1985 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1987 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1992 if (IS_RECEIVER(element) ||
1993 (IS_22_5_ANGLE(laser.current_angle) &&
1994 (ELX != (LX + 6 * XS) / TILEX ||
1995 ELY != (LY + 6 * YS) / TILEY ||
2004 LX = ELX * TILEX + 14;
2005 LY = ELY * TILEY + 14;
2007 laser.stops_inside_element = TRUE;
2010 AddLaserEdge(LX, LY);
2011 AddDamagedField(ELX, ELY);
2013 if (game_mm.lights_still_needed == 0)
2015 game_mm.level_solved = TRUE;
2017 SetTileCursorActive(FALSE);
2023 boolean HitReflectingWalls(int element, int hit_mask)
2025 // check if laser hits side of a wall with an angle that is not 90°
2026 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2027 hit_mask == HIT_MASK_LEFT ||
2028 hit_mask == HIT_MASK_RIGHT ||
2029 hit_mask == HIT_MASK_BOTTOM))
2031 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2036 if (!IS_DF_GRID(element))
2037 AddLaserEdge(LX, LY);
2039 // check if laser hits wall with an angle of 45°
2040 if (!IS_22_5_ANGLE(laser.current_angle))
2042 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2045 laser.current_angle = get_mirrored_angle(laser.current_angle,
2048 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2051 laser.current_angle = get_mirrored_angle(laser.current_angle,
2055 AddLaserEdge(LX, LY);
2057 XS = 2 * Step[laser.current_angle].x;
2058 YS = 2 * Step[laser.current_angle].y;
2062 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2064 laser.current_angle = get_mirrored_angle(laser.current_angle,
2069 if (!IS_DF_GRID(element))
2070 AddLaserEdge(LX, LY);
2075 if (!IS_DF_GRID(element))
2076 AddLaserEdge(LX, LY + YS / 2);
2079 if (!IS_DF_GRID(element))
2080 AddLaserEdge(LX, LY);
2083 YS = 2 * Step[laser.current_angle].y;
2087 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2089 laser.current_angle = get_mirrored_angle(laser.current_angle,
2094 if (!IS_DF_GRID(element))
2095 AddLaserEdge(LX, LY);
2100 if (!IS_DF_GRID(element))
2101 AddLaserEdge(LX + XS / 2, LY);
2104 if (!IS_DF_GRID(element))
2105 AddLaserEdge(LX, LY);
2108 XS = 2 * Step[laser.current_angle].x;
2114 // reflection at the edge of reflecting DF style wall
2115 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2117 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2118 hit_mask == HIT_MASK_TOPRIGHT) ||
2119 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2120 hit_mask == HIT_MASK_TOPLEFT) ||
2121 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2122 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2123 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2124 hit_mask == HIT_MASK_BOTTOMRIGHT))
2127 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2128 ANG_MIRROR_135 : ANG_MIRROR_45);
2130 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2132 AddDamagedField(ELX, ELY);
2133 AddLaserEdge(LX, LY);
2135 laser.current_angle = get_mirrored_angle(laser.current_angle,
2143 AddLaserEdge(LX, LY);
2149 // reflection inside an edge of reflecting DF style wall
2150 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2152 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2153 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2154 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2155 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2156 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2157 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2158 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2159 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2162 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2163 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2164 ANG_MIRROR_135 : ANG_MIRROR_45);
2166 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2169 AddDamagedField(ELX, ELY);
2172 AddLaserEdge(LX - XS, LY - YS);
2173 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2174 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2176 laser.current_angle = get_mirrored_angle(laser.current_angle,
2184 AddLaserEdge(LX, LY);
2190 // check if laser hits DF style wall with an angle of 90°
2191 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2193 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2194 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2195 (IS_VERT_ANGLE(laser.current_angle) &&
2196 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2198 // laser at last step touched nothing or the same side of the wall
2199 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2201 AddDamagedField(ELX, ELY);
2208 last_hit_mask = hit_mask;
2215 if (!HitOnlyAnEdge(hit_mask))
2217 laser.overloaded = TRUE;
2225 boolean HitAbsorbingWalls(int element, int hit_mask)
2227 if (HitOnlyAnEdge(hit_mask))
2231 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2233 AddLaserEdge(LX - XS, LY - YS);
2240 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2242 AddLaserEdge(LX - XS, LY - YS);
2248 if (IS_WALL_WOOD(element) ||
2249 IS_DF_WALL_WOOD(element) ||
2250 IS_GRID_WOOD(element) ||
2251 IS_GRID_WOOD_FIXED(element) ||
2252 IS_GRID_WOOD_AUTO(element) ||
2253 element == EL_FUSE_ON ||
2254 element == EL_BLOCK_WOOD ||
2255 element == EL_GATE_WOOD)
2257 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2262 if (IS_WALL_ICE(element))
2266 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2267 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2269 // check if laser hits wall with an angle of 90°
2270 if (IS_90_ANGLE(laser.current_angle))
2271 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2273 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2277 for (i = 0; i < 4; i++)
2279 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2280 mask = 15 - (8 >> i);
2281 else if (ABS(XS) == 4 &&
2283 (XS > 0) == (i % 2) &&
2284 (YS < 0) == (i / 2))
2285 mask = 3 + (i / 2) * 9;
2286 else if (ABS(YS) == 4 &&
2288 (XS < 0) == (i % 2) &&
2289 (YS > 0) == (i / 2))
2290 mask = 5 + (i % 2) * 5;
2294 laser.wall_mask = mask;
2296 else if (IS_WALL_AMOEBA(element))
2298 int elx = (LX - 2 * XS) / TILEX;
2299 int ely = (LY - 2 * YS) / TILEY;
2300 int element2 = Tile[elx][ely];
2303 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2305 laser.dest_element = EL_EMPTY;
2313 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2314 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2316 if (IS_90_ANGLE(laser.current_angle))
2317 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2319 laser.dest_element = element2 | EL_WALL_AMOEBA;
2321 laser.wall_mask = mask;
2327 static void OpenExit(int x, int y)
2331 if (!MovDelay[x][y]) // next animation frame
2332 MovDelay[x][y] = 4 * delay;
2334 if (MovDelay[x][y]) // wait some time before next frame
2339 phase = MovDelay[x][y] / delay;
2341 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2342 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2344 if (!MovDelay[x][y])
2346 Tile[x][y] = EL_EXIT_OPEN;
2352 static void OpenSurpriseBall(int x, int y)
2356 if (!MovDelay[x][y]) // next animation frame
2357 MovDelay[x][y] = 50 * delay;
2359 if (MovDelay[x][y]) // wait some time before next frame
2363 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2366 int graphic = el2gfx(Store[x][y]);
2368 int dx = RND(26), dy = RND(26);
2370 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2372 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2373 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2375 MarkTileDirty(x, y);
2378 if (!MovDelay[x][y])
2380 Tile[x][y] = Store[x][y];
2389 static void MeltIce(int x, int y)
2394 if (!MovDelay[x][y]) // next animation frame
2395 MovDelay[x][y] = frames * delay;
2397 if (MovDelay[x][y]) // wait some time before next frame
2400 int wall_mask = Store2[x][y];
2401 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2404 phase = frames - MovDelay[x][y] / delay - 1;
2406 if (!MovDelay[x][y])
2410 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2411 Store[x][y] = Store2[x][y] = 0;
2413 DrawWalls_MM(x, y, Tile[x][y]);
2415 if (Tile[x][y] == EL_WALL_ICE)
2416 Tile[x][y] = EL_EMPTY;
2418 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2419 if (laser.damage[i].is_mirror)
2423 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2425 DrawLaser(0, DL_LASER_DISABLED);
2429 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2431 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2433 laser.redraw = TRUE;
2438 static void GrowAmoeba(int x, int y)
2443 if (!MovDelay[x][y]) // next animation frame
2444 MovDelay[x][y] = frames * delay;
2446 if (MovDelay[x][y]) // wait some time before next frame
2449 int wall_mask = Store2[x][y];
2450 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2453 phase = MovDelay[x][y] / delay;
2455 if (!MovDelay[x][y])
2457 Tile[x][y] = real_element;
2458 Store[x][y] = Store2[x][y] = 0;
2460 DrawWalls_MM(x, y, Tile[x][y]);
2461 DrawLaser(0, DL_LASER_ENABLED);
2463 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2465 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2470 static void DrawFieldAnimated_MM(int x, int y)
2472 int element = Tile[x][y];
2474 if (IS_BLOCKED(x, y))
2479 if (IS_MIRROR(element) ||
2480 IS_MIRROR_FIXED(element) ||
2481 element == EL_PRISM)
2483 if (MovDelay[x][y] != 0) // wait some time before next frame
2487 if (MovDelay[x][y] != 0)
2489 int graphic = IMG_TWINKLE_WHITE;
2490 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2492 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2497 laser.redraw = TRUE;
2500 static void Explode_MM(int x, int y, int phase, int mode)
2502 int num_phase = 9, delay = 2;
2503 int last_phase = num_phase * delay;
2504 int half_phase = (num_phase / 2) * delay;
2506 laser.redraw = TRUE;
2508 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2510 int center_element = Tile[x][y];
2512 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2514 // put moving element to center field (and let it explode there)
2515 center_element = MovingOrBlocked2Element_MM(x, y);
2516 RemoveMovingField_MM(x, y);
2518 Tile[x][y] = center_element;
2521 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2522 Store[x][y] = center_element;
2524 Store[x][y] = EL_EMPTY;
2526 Store2[x][y] = mode;
2528 Tile[x][y] = EL_EXPLODING_OPAQUE;
2529 GfxElement[x][y] = center_element;
2531 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2533 ExplodePhase[x][y] = 1;
2539 GfxFrame[x][y] = 0; // restart explosion animation
2541 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2543 if (phase == half_phase)
2545 Tile[x][y] = EL_EXPLODING_TRANSP;
2547 if (x == ELX && y == ELY)
2551 if (phase == last_phase)
2553 if (Store[x][y] == EL_BOMB)
2555 DrawLaser(0, DL_LASER_DISABLED);
2558 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2559 Store[x][y] = EL_EMPTY;
2561 GameOver_MM(GAME_OVER_DELAYED);
2563 laser.overloaded = FALSE;
2565 else if (IS_MCDUFFIN(Store[x][y]))
2567 Store[x][y] = EL_EMPTY;
2569 GameOver_MM(GAME_OVER_BOMB);
2572 Tile[x][y] = Store[x][y];
2573 Store[x][y] = Store2[x][y] = 0;
2574 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2579 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2581 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2582 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2584 DrawGraphicAnimation_MM(x, y, graphic, frame);
2586 MarkTileDirty(x, y);
2590 static void Bang_MM(int x, int y)
2592 int element = Tile[x][y];
2595 DrawLaser(0, DL_LASER_ENABLED);
2598 if (IS_PACMAN(element))
2599 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2600 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2601 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2602 else if (element == EL_KEY)
2603 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2605 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2607 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2610 void TurnRound(int x, int y)
2622 { 0, 0 }, { 0, 0 }, { 0, 0 },
2627 int left, right, back;
2631 { MV_DOWN, MV_UP, MV_RIGHT },
2632 { MV_UP, MV_DOWN, MV_LEFT },
2634 { MV_LEFT, MV_RIGHT, MV_DOWN },
2635 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2636 { MV_RIGHT, MV_LEFT, MV_UP }
2639 int element = Tile[x][y];
2640 int old_move_dir = MovDir[x][y];
2641 int right_dir = turn[old_move_dir].right;
2642 int back_dir = turn[old_move_dir].back;
2643 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2644 int right_x = x + right_dx, right_y = y + right_dy;
2646 if (element == EL_PACMAN)
2648 boolean can_turn_right = FALSE;
2650 if (IN_LEV_FIELD(right_x, right_y) &&
2651 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2652 can_turn_right = TRUE;
2655 MovDir[x][y] = right_dir;
2657 MovDir[x][y] = back_dir;
2663 static void StartMoving_MM(int x, int y)
2665 int element = Tile[x][y];
2670 if (CAN_MOVE(element))
2674 if (MovDelay[x][y]) // wait some time before next movement
2682 // now make next step
2684 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2686 if (element == EL_PACMAN &&
2687 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2688 !ObjHit(newx, newy, HIT_POS_CENTER))
2690 Store[newx][newy] = Tile[newx][newy];
2691 Tile[newx][newy] = EL_EMPTY;
2693 DrawField_MM(newx, newy);
2695 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2696 ObjHit(newx, newy, HIT_POS_CENTER))
2698 // object was running against a wall
2705 InitMovingField_MM(x, y, MovDir[x][y]);
2709 ContinueMoving_MM(x, y);
2712 static void ContinueMoving_MM(int x, int y)
2714 int element = Tile[x][y];
2715 int direction = MovDir[x][y];
2716 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2717 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2718 int horiz_move = (dx!=0);
2719 int newx = x + dx, newy = y + dy;
2720 int step = (horiz_move ? dx : dy) * TILEX / 8;
2722 MovPos[x][y] += step;
2724 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2726 Tile[x][y] = EL_EMPTY;
2727 Tile[newx][newy] = element;
2729 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2730 MovDelay[newx][newy] = 0;
2732 if (!CAN_MOVE(element))
2733 MovDir[newx][newy] = 0;
2736 DrawField_MM(newx, newy);
2738 Stop[newx][newy] = TRUE;
2740 if (element == EL_PACMAN)
2742 if (Store[newx][newy] == EL_BOMB)
2743 Bang_MM(newx, newy);
2745 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2746 (LX + 2 * XS) / TILEX == newx &&
2747 (LY + 2 * YS) / TILEY == newy)
2754 else // still moving on
2759 laser.redraw = TRUE;
2762 boolean ClickElement(int x, int y, int button)
2764 static DelayCounter click_delay = { CLICK_DELAY };
2765 static boolean new_button = TRUE;
2766 boolean element_clicked = FALSE;
2771 // initialize static variables
2772 click_delay.count = 0;
2773 click_delay.value = CLICK_DELAY;
2779 // do not rotate objects hit by the laser after the game was solved
2780 if (game_mm.level_solved && Hit[x][y])
2783 if (button == MB_RELEASED)
2786 click_delay.value = CLICK_DELAY;
2788 // release eventually hold auto-rotating mirror
2789 RotateMirror(x, y, MB_RELEASED);
2794 if (!FrameReached(&click_delay) && !new_button)
2797 if (button == MB_MIDDLEBUTTON) // middle button has no function
2800 if (!IN_LEV_FIELD(x, y))
2803 if (Tile[x][y] == EL_EMPTY)
2806 element = Tile[x][y];
2808 if (IS_MIRROR(element) ||
2809 IS_BEAMER(element) ||
2810 IS_POLAR(element) ||
2811 IS_POLAR_CROSS(element) ||
2812 IS_DF_MIRROR(element) ||
2813 IS_DF_MIRROR_AUTO(element))
2815 RotateMirror(x, y, button);
2817 element_clicked = TRUE;
2819 else if (IS_MCDUFFIN(element))
2821 if (!laser.fuse_off)
2823 DrawLaser(0, DL_LASER_DISABLED);
2830 element = get_rotated_element(element, BUTTON_ROTATION(button));
2831 laser.start_angle = get_element_angle(element);
2835 Tile[x][y] = element;
2842 if (!laser.fuse_off)
2845 element_clicked = TRUE;
2847 else if (element == EL_FUSE_ON && laser.fuse_off)
2849 if (x != laser.fuse_x || y != laser.fuse_y)
2852 laser.fuse_off = FALSE;
2853 laser.fuse_x = laser.fuse_y = -1;
2855 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2858 element_clicked = TRUE;
2860 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2862 laser.fuse_off = TRUE;
2865 laser.overloaded = FALSE;
2867 DrawLaser(0, DL_LASER_DISABLED);
2868 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2870 element_clicked = TRUE;
2872 else if (element == EL_LIGHTBALL)
2875 RaiseScoreElement_MM(element);
2876 DrawLaser(0, DL_LASER_ENABLED);
2878 element_clicked = TRUE;
2881 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2884 return element_clicked;
2887 void RotateMirror(int x, int y, int button)
2889 if (button == MB_RELEASED)
2891 // release eventually hold auto-rotating mirror
2898 if (IS_MIRROR(Tile[x][y]) ||
2899 IS_POLAR_CROSS(Tile[x][y]) ||
2900 IS_POLAR(Tile[x][y]) ||
2901 IS_BEAMER(Tile[x][y]) ||
2902 IS_DF_MIRROR(Tile[x][y]) ||
2903 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2904 IS_GRID_WOOD_AUTO(Tile[x][y]))
2906 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2908 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2910 if (button == MB_LEFTBUTTON)
2912 // left mouse button only for manual adjustment, no auto-rotating;
2913 // freeze mirror for until mouse button released
2917 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2919 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2923 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2925 int edge = Hit[x][y];
2931 DrawLaser(edge - 1, DL_LASER_DISABLED);
2935 else if (ObjHit(x, y, HIT_POS_CENTER))
2937 int edge = Hit[x][y];
2941 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2946 DrawLaser(edge - 1, DL_LASER_DISABLED);
2953 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2958 if ((IS_BEAMER(Tile[x][y]) ||
2959 IS_POLAR(Tile[x][y]) ||
2960 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2964 if (IS_BEAMER(Tile[x][y]))
2967 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2968 LX, LY, laser.beamer_edge, laser.beamer[1].num);
2980 DrawLaser(0, DL_LASER_ENABLED);
2984 static void AutoRotateMirrors(void)
2988 if (!FrameReached(&rotate_delay))
2991 for (x = 0; x < lev_fieldx; x++)
2993 for (y = 0; y < lev_fieldy; y++)
2995 int element = Tile[x][y];
2997 // do not rotate objects hit by the laser after the game was solved
2998 if (game_mm.level_solved && Hit[x][y])
3001 if (IS_DF_MIRROR_AUTO(element) ||
3002 IS_GRID_WOOD_AUTO(element) ||
3003 IS_GRID_STEEL_AUTO(element) ||
3004 element == EL_REFRACTOR)
3005 RotateMirror(x, y, MB_RIGHTBUTTON);
3010 boolean ObjHit(int obx, int oby, int bits)
3017 if (bits & HIT_POS_CENTER)
3019 if (CheckLaserPixel(cSX + obx + 15,
3024 if (bits & HIT_POS_EDGE)
3026 for (i = 0; i < 4; i++)
3027 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3028 cSY + oby + 31 * (i / 2)))
3032 if (bits & HIT_POS_BETWEEN)
3034 for (i = 0; i < 4; i++)
3035 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3036 cSY + 4 + oby + 22 * (i / 2)))
3043 void DeletePacMan(int px, int py)
3049 if (game_mm.num_pacman <= 1)
3051 game_mm.num_pacman = 0;
3055 for (i = 0; i < game_mm.num_pacman; i++)
3056 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3059 game_mm.num_pacman--;
3061 for (j = i; j < game_mm.num_pacman; j++)
3063 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3064 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3065 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3069 void ColorCycling(void)
3071 static int CC, Cc = 0;
3073 static int color, old = 0xF00, new = 0x010, mult = 1;
3074 static unsigned short red, green, blue;
3076 if (color_status == STATIC_COLORS)
3081 if (CC < Cc || CC > Cc + 2)
3085 color = old + new * mult;
3091 if (ABS(mult) == 16)
3101 red = 0x0e00 * ((color & 0xF00) >> 8);
3102 green = 0x0e00 * ((color & 0x0F0) >> 4);
3103 blue = 0x0e00 * ((color & 0x00F));
3104 SetRGB(pen_magicolor[0], red, green, blue);
3106 red = 0x1111 * ((color & 0xF00) >> 8);
3107 green = 0x1111 * ((color & 0x0F0) >> 4);
3108 blue = 0x1111 * ((color & 0x00F));
3109 SetRGB(pen_magicolor[1], red, green, blue);
3113 static void GameActions_MM_Ext(void)
3120 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3123 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3125 element = Tile[x][y];
3127 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3128 StartMoving_MM(x, y);
3129 else if (IS_MOVING(x, y))
3130 ContinueMoving_MM(x, y);
3131 else if (IS_EXPLODING(element))
3132 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3133 else if (element == EL_EXIT_OPENING)
3135 else if (element == EL_GRAY_BALL_OPENING)
3136 OpenSurpriseBall(x, y);
3137 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3139 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3142 DrawFieldAnimated_MM(x, y);
3145 AutoRotateMirrors();
3148 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3150 // redraw after Explode_MM() ...
3152 DrawLaser(0, DL_LASER_ENABLED);
3153 laser.redraw = FALSE;
3158 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3162 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3164 DrawLaser(0, DL_LASER_DISABLED);
3169 // skip all following game actions if game is over
3170 if (game_mm.game_over)
3173 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3177 GameOver_MM(GAME_OVER_NO_ENERGY);
3182 if (FrameReached(&energy_delay))
3184 if (game_mm.energy_left > 0)
3185 game_mm.energy_left--;
3187 // when out of energy, wait another frame to play "out of time" sound
3190 element = laser.dest_element;
3193 if (element != Tile[ELX][ELY])
3195 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3196 element, Tile[ELX][ELY]);
3200 if (!laser.overloaded && laser.overload_value == 0 &&
3201 element != EL_BOMB &&
3202 element != EL_MINE &&
3203 element != EL_BALL_GRAY &&
3204 element != EL_BLOCK_STONE &&
3205 element != EL_BLOCK_WOOD &&
3206 element != EL_FUSE_ON &&
3207 element != EL_FUEL_FULL &&
3208 !IS_WALL_ICE(element) &&
3209 !IS_WALL_AMOEBA(element))
3212 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3214 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3215 (!laser.overloaded && laser.overload_value > 0)) &&
3216 FrameReached(&overload_delay))
3218 if (laser.overloaded)
3219 laser.overload_value++;
3221 laser.overload_value--;
3223 if (game_mm.cheat_no_overload)
3225 laser.overloaded = FALSE;
3226 laser.overload_value = 0;
3229 game_mm.laser_overload_value = laser.overload_value;
3231 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3233 SetLaserColor(0xFF);
3235 DrawLaser(0, DL_LASER_ENABLED);
3238 if (!laser.overloaded)
3239 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3240 else if (setup.sound_loops)
3241 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3243 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3245 if (laser.overloaded)
3248 BlitBitmap(pix[PIX_DOOR], drawto,
3249 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3250 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3251 - laser.overload_value,
3252 OVERLOAD_XSIZE, laser.overload_value,
3253 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3254 - laser.overload_value);
3256 redraw_mask |= REDRAW_DOOR_1;
3261 BlitBitmap(pix[PIX_DOOR], drawto,
3262 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3263 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3264 DX_OVERLOAD, DY_OVERLOAD);
3266 redraw_mask |= REDRAW_DOOR_1;
3269 if (laser.overload_value == MAX_LASER_OVERLOAD)
3271 UpdateAndDisplayGameControlValues();
3275 GameOver_MM(GAME_OVER_OVERLOADED);
3286 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3288 if (game_mm.cheat_no_explosion)
3293 laser.dest_element = EL_EXPLODING_OPAQUE;
3298 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3300 laser.fuse_off = TRUE;
3304 DrawLaser(0, DL_LASER_DISABLED);
3305 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3308 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3310 static int new_elements[] =
3313 EL_MIRROR_FIXED_START,
3315 EL_POLAR_CROSS_START,
3321 int num_new_elements = sizeof(new_elements) / sizeof(int);
3322 int new_element = new_elements[RND(num_new_elements)];
3324 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3325 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3327 // !!! CHECK AGAIN: Laser on Polarizer !!!
3338 element = EL_MIRROR_START + RND(16);
3344 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3351 element = (rnd == 0 ? EL_FUSE_ON :
3352 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3353 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3354 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3355 EL_MIRROR_FIXED_START + rnd - 25);
3360 graphic = el2gfx(element);
3362 for (i = 0; i < 50; i++)
3368 BlitBitmap(pix[PIX_BACK], drawto,
3369 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3370 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3371 SX + ELX * TILEX + x,
3372 SY + ELY * TILEY + y);
3374 MarkTileDirty(ELX, ELY);
3377 DrawLaser(0, DL_LASER_ENABLED);
3379 Delay_WithScreenUpdates(50);
3382 Tile[ELX][ELY] = element;
3383 DrawField_MM(ELX, ELY);
3386 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3389 // above stuff: GRAY BALL -> PRISM !!!
3391 LX = ELX * TILEX + 14;
3392 LY = ELY * TILEY + 14;
3393 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3400 laser.num_edges -= 2;
3401 laser.num_damages--;
3405 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3406 if (laser.damage[i].is_mirror)
3410 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3412 DrawLaser(0, DL_LASER_DISABLED);
3414 DrawLaser(0, DL_LASER_DISABLED);
3423 if (IS_WALL_ICE(element) && CT > 50)
3425 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3428 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3429 Store[ELX][ELY] = EL_WALL_ICE;
3430 Store2[ELX][ELY] = laser.wall_mask;
3432 laser.dest_element = Tile[ELX][ELY];
3437 for (i = 0; i < 5; i++)
3443 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3447 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3449 Delay_WithScreenUpdates(100);
3452 if (Tile[ELX][ELY] == EL_WALL_ICE)
3453 Tile[ELX][ELY] = EL_EMPTY;
3457 LX = laser.edge[laser.num_edges].x - cSX2;
3458 LY = laser.edge[laser.num_edges].y - cSY2;
3461 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3462 if (laser.damage[i].is_mirror)
3466 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3468 DrawLaser(0, DL_LASER_DISABLED);
3475 if (IS_WALL_AMOEBA(element) && CT > 60)
3477 int k1, k2, k3, dx, dy, de, dm;
3478 int element2 = Tile[ELX][ELY];
3480 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3483 for (i = laser.num_damages - 1; i >= 0; i--)
3484 if (laser.damage[i].is_mirror)
3487 r = laser.num_edges;
3488 d = laser.num_damages;
3495 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3498 DrawLaser(0, DL_LASER_ENABLED);
3501 x = laser.damage[k1].x;
3502 y = laser.damage[k1].y;
3507 for (i = 0; i < 4; i++)
3509 if (laser.wall_mask & (1 << i))
3511 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3512 cSY + ELY * TILEY + 31 * (i / 2)))
3515 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3516 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3523 for (i = 0; i < 4; i++)
3525 if (laser.wall_mask & (1 << i))
3527 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3528 cSY + ELY * TILEY + 31 * (i / 2)))
3535 if (laser.num_beamers > 0 ||
3536 k1 < 1 || k2 < 4 || k3 < 4 ||
3537 CheckLaserPixel(cSX + ELX * TILEX + 14,
3538 cSY + ELY * TILEY + 14))
3540 laser.num_edges = r;
3541 laser.num_damages = d;
3543 DrawLaser(0, DL_LASER_DISABLED);
3546 Tile[ELX][ELY] = element | laser.wall_mask;
3550 de = Tile[ELX][ELY];
3551 dm = laser.wall_mask;
3555 int x = ELX, y = ELY;
3556 int wall_mask = laser.wall_mask;
3559 DrawLaser(0, DL_LASER_ENABLED);
3561 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3563 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3564 Store[x][y] = EL_WALL_AMOEBA;
3565 Store2[x][y] = wall_mask;
3571 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3573 DrawLaser(0, DL_LASER_ENABLED);
3575 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3577 for (i = 4; i >= 0; i--)
3579 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3582 Delay_WithScreenUpdates(20);
3585 DrawLaser(0, DL_LASER_ENABLED);
3590 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3591 laser.stops_inside_element && CT > native_mm_level.time_block)
3596 if (ABS(XS) > ABS(YS))
3603 for (i = 0; i < 4; i++)
3610 x = ELX + Step[k * 4].x;
3611 y = ELY + Step[k * 4].y;
3613 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3616 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3624 laser.overloaded = (element == EL_BLOCK_STONE);
3629 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3632 Tile[x][y] = element;
3634 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3637 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3639 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3640 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3648 if (element == EL_FUEL_FULL && CT > 10)
3650 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3651 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3653 for (i = start; i <= num_init_game_frames; i++)
3655 if (i == num_init_game_frames)
3656 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3657 else if (setup.sound_loops)
3658 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3660 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3662 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3664 UpdateAndDisplayGameControlValues();
3669 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3671 DrawField_MM(ELX, ELY);
3673 DrawLaser(0, DL_LASER_ENABLED);
3681 void GameActions_MM(struct MouseActionInfo action)
3683 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3684 boolean button_released = (action.button == MB_RELEASED);
3686 GameActions_MM_Ext();
3688 CheckSingleStepMode_MM(element_clicked, button_released);
3691 void MovePacMen(void)
3693 int mx, my, ox, oy, nx, ny;
3697 if (++pacman_nr >= game_mm.num_pacman)
3700 game_mm.pacman[pacman_nr].dir--;
3702 for (l = 1; l < 5; l++)
3704 game_mm.pacman[pacman_nr].dir++;
3706 if (game_mm.pacman[pacman_nr].dir > 4)
3707 game_mm.pacman[pacman_nr].dir = 1;
3709 if (game_mm.pacman[pacman_nr].dir % 2)
3712 my = game_mm.pacman[pacman_nr].dir - 2;
3717 mx = 3 - game_mm.pacman[pacman_nr].dir;
3720 ox = game_mm.pacman[pacman_nr].x;
3721 oy = game_mm.pacman[pacman_nr].y;
3724 element = Tile[nx][ny];
3726 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3729 if (!IS_EATABLE4PACMAN(element))
3732 if (ObjHit(nx, ny, HIT_POS_CENTER))
3735 Tile[ox][oy] = EL_EMPTY;
3737 EL_PACMAN_RIGHT - 1 +
3738 (game_mm.pacman[pacman_nr].dir - 1 +
3739 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3741 game_mm.pacman[pacman_nr].x = nx;
3742 game_mm.pacman[pacman_nr].y = ny;
3744 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3746 if (element != EL_EMPTY)
3748 int graphic = el2gfx(Tile[nx][ny]);
3753 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3756 ox = cSX + ox * TILEX;
3757 oy = cSY + oy * TILEY;
3759 for (i = 1; i < 33; i += 2)
3760 BlitBitmap(bitmap, window,
3761 src_x, src_y, TILEX, TILEY,
3762 ox + i * mx, oy + i * my);
3763 Ct = Ct + FrameCounter - CT;
3766 DrawField_MM(nx, ny);
3769 if (!laser.fuse_off)
3771 DrawLaser(0, DL_LASER_ENABLED);
3773 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3775 AddDamagedField(nx, ny);
3777 laser.damage[laser.num_damages - 1].edge = 0;
3781 if (element == EL_BOMB)
3782 DeletePacMan(nx, ny);
3784 if (IS_WALL_AMOEBA(element) &&
3785 (LX + 2 * XS) / TILEX == nx &&
3786 (LY + 2 * YS) / TILEY == ny)
3796 static void InitMovingField_MM(int x, int y, int direction)
3798 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3799 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3801 MovDir[x][y] = direction;
3802 MovDir[newx][newy] = direction;
3804 if (Tile[newx][newy] == EL_EMPTY)
3805 Tile[newx][newy] = EL_BLOCKED;
3808 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3810 int direction = MovDir[x][y];
3811 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3812 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3818 static void Blocked2Moving_MM(int x, int y,
3819 int *comes_from_x, int *comes_from_y)
3821 int oldx = x, oldy = y;
3822 int direction = MovDir[x][y];
3824 if (direction == MV_LEFT)
3826 else if (direction == MV_RIGHT)
3828 else if (direction == MV_UP)
3830 else if (direction == MV_DOWN)
3833 *comes_from_x = oldx;
3834 *comes_from_y = oldy;
3837 static int MovingOrBlocked2Element_MM(int x, int y)
3839 int element = Tile[x][y];
3841 if (element == EL_BLOCKED)
3845 Blocked2Moving_MM(x, y, &oldx, &oldy);
3847 return Tile[oldx][oldy];
3854 static void RemoveField(int x, int y)
3856 Tile[x][y] = EL_EMPTY;
3863 static void RemoveMovingField_MM(int x, int y)
3865 int oldx = x, oldy = y, newx = x, newy = y;
3867 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3870 if (IS_MOVING(x, y))
3872 Moving2Blocked_MM(x, y, &newx, &newy);
3873 if (Tile[newx][newy] != EL_BLOCKED)
3876 else if (Tile[x][y] == EL_BLOCKED)
3878 Blocked2Moving_MM(x, y, &oldx, &oldy);
3879 if (!IS_MOVING(oldx, oldy))
3883 Tile[oldx][oldy] = EL_EMPTY;
3884 Tile[newx][newy] = EL_EMPTY;
3885 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3886 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3888 DrawLevelField_MM(oldx, oldy);
3889 DrawLevelField_MM(newx, newy);
3892 void PlaySoundLevel(int x, int y, int sound_nr)
3894 int sx = SCREENX(x), sy = SCREENY(y);
3896 int silence_distance = 8;
3898 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3899 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3902 if (!IN_LEV_FIELD(x, y) ||
3903 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3904 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3907 volume = SOUND_MAX_VOLUME;
3910 stereo = (sx - SCR_FIELDX/2) * 12;
3912 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3913 if (stereo > SOUND_MAX_RIGHT)
3914 stereo = SOUND_MAX_RIGHT;
3915 if (stereo < SOUND_MAX_LEFT)
3916 stereo = SOUND_MAX_LEFT;
3919 if (!IN_SCR_FIELD(sx, sy))
3921 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3922 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3924 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3927 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3930 static void RaiseScore_MM(int value)
3932 game_mm.score += value;
3935 void RaiseScoreElement_MM(int element)
3940 case EL_PACMAN_RIGHT:
3942 case EL_PACMAN_LEFT:
3943 case EL_PACMAN_DOWN:
3944 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3948 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3953 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3957 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3966 // ----------------------------------------------------------------------------
3967 // Mirror Magic game engine snapshot handling functions
3968 // ----------------------------------------------------------------------------
3970 void SaveEngineSnapshotValues_MM(void)
3974 engine_snapshot_mm.game_mm = game_mm;
3975 engine_snapshot_mm.laser = laser;
3977 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3979 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3981 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3982 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3983 engine_snapshot_mm.Box[x][y] = Box[x][y];
3984 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3988 engine_snapshot_mm.LX = LX;
3989 engine_snapshot_mm.LY = LY;
3990 engine_snapshot_mm.XS = XS;
3991 engine_snapshot_mm.YS = YS;
3992 engine_snapshot_mm.ELX = ELX;
3993 engine_snapshot_mm.ELY = ELY;
3994 engine_snapshot_mm.CT = CT;
3995 engine_snapshot_mm.Ct = Ct;
3997 engine_snapshot_mm.last_LX = last_LX;
3998 engine_snapshot_mm.last_LY = last_LY;
3999 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4000 engine_snapshot_mm.hold_x = hold_x;
4001 engine_snapshot_mm.hold_y = hold_y;
4002 engine_snapshot_mm.pacman_nr = pacman_nr;
4004 engine_snapshot_mm.rotate_delay = rotate_delay;
4005 engine_snapshot_mm.pacman_delay = pacman_delay;
4006 engine_snapshot_mm.energy_delay = energy_delay;
4007 engine_snapshot_mm.overload_delay = overload_delay;
4010 void LoadEngineSnapshotValues_MM(void)
4014 // stored engine snapshot buffers already restored at this point
4016 game_mm = engine_snapshot_mm.game_mm;
4017 laser = engine_snapshot_mm.laser;
4019 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4021 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4023 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4024 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4025 Box[x][y] = engine_snapshot_mm.Box[x][y];
4026 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4030 LX = engine_snapshot_mm.LX;
4031 LY = engine_snapshot_mm.LY;
4032 XS = engine_snapshot_mm.XS;
4033 YS = engine_snapshot_mm.YS;
4034 ELX = engine_snapshot_mm.ELX;
4035 ELY = engine_snapshot_mm.ELY;
4036 CT = engine_snapshot_mm.CT;
4037 Ct = engine_snapshot_mm.Ct;
4039 last_LX = engine_snapshot_mm.last_LX;
4040 last_LY = engine_snapshot_mm.last_LY;
4041 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4042 hold_x = engine_snapshot_mm.hold_x;
4043 hold_y = engine_snapshot_mm.hold_y;
4044 pacman_nr = engine_snapshot_mm.pacman_nr;
4046 rotate_delay = engine_snapshot_mm.rotate_delay;
4047 pacman_delay = engine_snapshot_mm.pacman_delay;
4048 energy_delay = engine_snapshot_mm.energy_delay;
4049 overload_delay = engine_snapshot_mm.overload_delay;
4051 RedrawPlayfield_MM();
4054 static int getAngleFromTouchDelta(int dx, int dy, int base)
4056 double pi = 3.141592653;
4057 double rad = atan2((double)-dy, (double)dx);
4058 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4059 double deg = rad2 * 180.0 / pi;
4061 return (int)(deg * base / 360.0 + 0.5) % base;
4064 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4066 // calculate start (source) position to be at the middle of the tile
4067 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4068 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4069 int dx = dst_mx - src_mx;
4070 int dy = dst_my - src_my;
4079 if (!IN_LEV_FIELD(x, y))
4082 element = Tile[x][y];
4084 if (!IS_MCDUFFIN(element) &&
4085 !IS_MIRROR(element) &&
4086 !IS_BEAMER(element) &&
4087 !IS_POLAR(element) &&
4088 !IS_POLAR_CROSS(element) &&
4089 !IS_DF_MIRROR(element))
4092 angle_old = get_element_angle(element);
4094 if (IS_MCDUFFIN(element))
4096 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4097 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4098 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4099 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4102 else if (IS_MIRROR(element) ||
4103 IS_DF_MIRROR(element))
4105 for (i = 0; i < laser.num_damages; i++)
4107 if (laser.damage[i].x == x &&
4108 laser.damage[i].y == y &&
4109 ObjHit(x, y, HIT_POS_CENTER))
4111 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4112 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4119 if (angle_new == -1)
4121 if (IS_MIRROR(element) ||
4122 IS_DF_MIRROR(element) ||
4126 if (IS_POLAR_CROSS(element))
4129 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4132 button = (angle_new == angle_old ? 0 :
4133 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4134 MB_LEFTBUTTON : MB_RIGHTBUTTON);