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 (game_mm.laser_red ? color_max : color_up),
437 (game_mm.laser_green ? color_down : color_min),
438 (game_mm.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);
542 if (IS_MCDUFFIN(element))
544 game_mm.laser_red = native_mm_level.mm_laser_red;
545 game_mm.laser_green = native_mm_level.mm_laser_green;
546 game_mm.laser_blue = native_mm_level.mm_laser_blue;
550 game_mm.laser_red = native_mm_level.df_laser_red;
551 game_mm.laser_green = native_mm_level.df_laser_green;
552 game_mm.laser_blue = native_mm_level.df_laser_blue;
560 static void InitCycleElements_RotateSingleStep(void)
564 if (game_mm.num_cycle == 0) // no elements to cycle
567 for (i = 0; i < game_mm.num_cycle; i++)
569 int x = game_mm.cycle[i].x;
570 int y = game_mm.cycle[i].y;
571 int step = SIGN(game_mm.cycle[i].steps);
572 int last_element = Tile[x][y];
573 int next_element = get_rotated_element(last_element, step);
575 if (!game_mm.cycle[i].steps)
578 Tile[x][y] = next_element;
580 game_mm.cycle[i].steps -= step;
584 static void InitLaser(void)
586 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
587 int step = (IS_LASER(start_element) ? 4 : 0);
589 LX = laser.start_edge.x * TILEX;
590 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
593 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
595 LY = laser.start_edge.y * TILEY;
596 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
597 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
601 XS = 2 * Step[laser.start_angle].x;
602 YS = 2 * Step[laser.start_angle].y;
604 laser.current_angle = laser.start_angle;
606 laser.num_damages = 0;
608 laser.num_beamers = 0;
609 laser.beamer_edge[0] = 0;
611 laser.dest_element = EL_EMPTY;
614 AddLaserEdge(LX, LY); // set laser starting edge
619 void InitGameEngine_MM(void)
625 // initialize laser bitmap to current playfield (screen) size
626 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
627 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
631 // set global game control values
632 game_mm.num_cycle = 0;
633 game_mm.num_pacman = 0;
636 game_mm.energy_left = 0; // later set to "native_mm_level.time"
637 game_mm.kettles_still_needed =
638 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
639 game_mm.lights_still_needed = 0;
640 game_mm.num_keys = 0;
641 game_mm.ball_choice_pos = 0;
643 game_mm.laser_red = FALSE;
644 game_mm.laser_green = FALSE;
645 game_mm.laser_blue = TRUE;
647 game_mm.lightball_rnd = TRUE;
649 game_mm.level_solved = FALSE;
650 game_mm.game_over = FALSE;
651 game_mm.game_over_cause = 0;
653 game_mm.laser_overload_value = 0;
654 game_mm.laser_enabled = FALSE;
656 // set global laser control values (must be set before "InitLaser()")
657 laser.start_edge.x = 0;
658 laser.start_edge.y = 0;
659 laser.start_angle = 0;
661 for (i = 0; i < MAX_NUM_BEAMERS; i++)
662 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
664 laser.overloaded = FALSE;
665 laser.overload_value = 0;
666 laser.fuse_off = FALSE;
667 laser.fuse_x = laser.fuse_y = -1;
669 laser.dest_element = EL_EMPTY;
670 laser.dest_element_last = EL_EMPTY;
671 laser.dest_element_last_x = -1;
672 laser.dest_element_last_y = -1;
686 rotate_delay.count = 0;
687 pacman_delay.count = 0;
688 energy_delay.count = 0;
689 overload_delay.count = 0;
691 ClickElement(-1, -1, -1);
693 for (x = 0; x < lev_fieldx; x++)
695 for (y = 0; y < lev_fieldy; y++)
697 Tile[x][y] = Ur[x][y];
698 Hit[x][y] = Box[x][y] = 0;
700 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
701 Store[x][y] = Store2[x][y] = 0;
711 void InitGameActions_MM(void)
713 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
714 int cycle_steps_done = 0;
719 game_mm.lightball_rnd = FALSE;
721 for (i = 0; i <= num_init_game_frames; i++)
723 if (i == num_init_game_frames)
724 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
725 else if (setup.sound_loops)
726 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
728 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
730 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
732 UpdateAndDisplayGameControlValues();
734 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
736 InitCycleElements_RotateSingleStep();
741 AdvanceFrameCounter();
751 if (setup.quick_doors)
756 game_mm.lightball_rnd = TRUE;
760 if (game_mm.kettles_still_needed == 0)
763 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
764 SetTileCursorActive(TRUE);
766 ResetFrameCounter(&energy_delay);
769 static void FadeOutLaser(void)
773 for (i = 15; i >= 0; i--)
775 SetLaserColor(0x11 * i);
777 DrawLaser(0, DL_LASER_ENABLED);
780 Delay_WithScreenUpdates(50);
783 DrawLaser(0, DL_LASER_DISABLED);
785 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
788 static void GameOver_MM(int game_over_cause)
790 // do not handle game over if request dialog is already active
791 if (game.request_active)
794 game_mm.game_over = TRUE;
795 game_mm.game_over_cause = game_over_cause;
797 if (setup.ask_on_game_over)
798 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
799 "Bomb killed Mc Duffin! Play it again?" :
800 game_over_cause == GAME_OVER_NO_ENERGY ?
801 "Out of magic energy! Play it again?" :
802 game_over_cause == GAME_OVER_OVERLOADED ?
803 "Magic spell hit Mc Duffin! Play it again?" :
806 SetTileCursorActive(FALSE);
809 void AddLaserEdge(int lx, int ly)
814 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
816 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
821 laser.edge[laser.num_edges].x = cSX2 + lx;
822 laser.edge[laser.num_edges].y = cSY2 + ly;
828 void AddDamagedField(int ex, int ey)
830 laser.damage[laser.num_damages].is_mirror = FALSE;
831 laser.damage[laser.num_damages].angle = laser.current_angle;
832 laser.damage[laser.num_damages].edge = laser.num_edges;
833 laser.damage[laser.num_damages].x = ex;
834 laser.damage[laser.num_damages].y = ey;
838 static boolean StepBehind(void)
844 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
845 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
847 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
853 static int getMaskFromElement(int element)
855 if (IS_GRID(element))
856 return MM_MASK_GRID_1 + get_element_phase(element);
857 else if (IS_MCDUFFIN(element))
858 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
859 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
860 return MM_MASK_RECTANGLE;
862 return MM_MASK_CIRCLE;
865 static int ScanPixel(void)
870 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
871 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
874 // follow laser beam until it hits something (at least the screen border)
875 while (hit_mask == HIT_MASK_NO_HIT)
881 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
882 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
884 Debug("game:mm:ScanPixel", "touched screen border!");
890 for (i = 0; i < 4; i++)
892 int px = LX + (i % 2) * 2;
893 int py = LY + (i / 2) * 2;
896 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
897 int ly = (py + TILEY) / TILEY - 1; // negative values!
900 if (IN_LEV_FIELD(lx, ly))
902 int element = Tile[lx][ly];
904 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
908 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
910 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
912 pixel = ((element & (1 << pos)) ? 1 : 0);
916 int pos = getMaskFromElement(element);
918 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
923 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
924 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
927 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
928 hit_mask |= (1 << i);
931 if (hit_mask == HIT_MASK_NO_HIT)
933 // hit nothing -- go on with another step
942 static void DeactivateLaserTargetElement(void)
944 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
945 laser.dest_element_last == EL_MINE_ACTIVE ||
946 laser.dest_element_last == EL_GRAY_BALL_OPENING)
948 int x = laser.dest_element_last_x;
949 int y = laser.dest_element_last_y;
950 int element = laser.dest_element_last;
952 if (Tile[x][y] == element)
953 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
954 element == EL_MINE_ACTIVE ? EL_MINE : EL_BALL_GRAY);
956 if (Tile[x][y] == EL_BALL_GRAY)
959 laser.dest_element_last = EL_EMPTY;
960 laser.dest_element_last_x = -1;
961 laser.dest_element_last_y = -1;
967 int element = EL_EMPTY;
968 int last_element = EL_EMPTY;
969 int end = 0, rf = laser.num_edges;
971 // do not scan laser again after the game was lost for whatever reason
972 if (game_mm.game_over)
975 // do not scan laser if fuse is off
979 DeactivateLaserTargetElement();
981 laser.overloaded = FALSE;
982 laser.stops_inside_element = FALSE;
984 DrawLaser(0, DL_LASER_ENABLED);
987 Debug("game:mm:ScanLaser",
988 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
996 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
999 laser.overloaded = TRUE;
1004 hit_mask = ScanPixel();
1007 Debug("game:mm:ScanLaser",
1008 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1012 // hit something -- check out what it was
1013 ELX = (LX + XS) / TILEX;
1014 ELY = (LY + YS) / TILEY;
1017 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1018 hit_mask, LX, LY, ELX, ELY);
1021 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1024 laser.dest_element = element;
1029 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1031 /* we have hit the top-right and bottom-left element --
1032 choose the bottom-left one */
1033 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1034 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1035 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1036 ELX = (LX - 2) / TILEX;
1037 ELY = (LY + 2) / TILEY;
1040 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1042 /* we have hit the top-left and bottom-right element --
1043 choose the top-left one */
1044 // !!! SEE ABOVE !!!
1045 ELX = (LX - 2) / TILEX;
1046 ELY = (LY - 2) / TILEY;
1050 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1051 hit_mask, LX, LY, ELX, ELY);
1054 last_element = element;
1056 element = Tile[ELX][ELY];
1057 laser.dest_element = element;
1060 Debug("game:mm:ScanLaser",
1061 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1064 LX % TILEX, LY % TILEY,
1069 if (!IN_LEV_FIELD(ELX, ELY))
1070 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1074 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1075 if (element == EL_EMPTY &&
1076 IS_GRID_STEEL(last_element) &&
1077 laser.current_angle % 4) // angle is not 90°
1078 element = last_element;
1080 if (element == EL_EMPTY)
1082 if (!HitOnlyAnEdge(hit_mask))
1085 else if (element == EL_FUSE_ON)
1087 if (HitPolarizer(element, hit_mask))
1090 else if (IS_GRID(element) || IS_DF_GRID(element))
1092 if (HitPolarizer(element, hit_mask))
1095 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1096 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1098 if (HitBlock(element, hit_mask))
1105 else if (IS_MCDUFFIN(element))
1107 if (HitLaserSource(element, hit_mask))
1110 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1111 IS_RECEIVER(element))
1113 if (HitLaserDestination(element, hit_mask))
1116 else if (IS_WALL(element))
1118 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1120 if (HitReflectingWalls(element, hit_mask))
1125 if (HitAbsorbingWalls(element, hit_mask))
1131 if (HitElement(element, hit_mask))
1136 DrawLaser(rf - 1, DL_LASER_ENABLED);
1137 rf = laser.num_edges;
1139 if (!IS_DF_WALL_STEEL(element))
1141 // only used for scanning DF steel walls; reset for all other elements
1149 if (laser.dest_element != Tile[ELX][ELY])
1151 Debug("game:mm:ScanLaser",
1152 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1153 laser.dest_element, Tile[ELX][ELY]);
1157 if (!end && !laser.stops_inside_element && !StepBehind())
1160 Debug("game:mm:ScanLaser", "Go one step back");
1166 AddLaserEdge(LX, LY);
1170 DrawLaser(rf - 1, DL_LASER_ENABLED);
1172 Ct = CT = FrameCounter;
1175 if (!IN_LEV_FIELD(ELX, ELY))
1176 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1180 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1186 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1187 start_edge, num_edges, mode);
1192 Warn("DrawLaserExt: start_edge < 0");
1199 Warn("DrawLaserExt: num_edges < 0");
1205 if (mode == DL_LASER_DISABLED)
1207 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1211 // now draw the laser to the backbuffer and (if enabled) to the screen
1212 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1214 redraw_mask |= REDRAW_FIELD;
1216 if (mode == DL_LASER_ENABLED)
1219 // after the laser was deleted, the "damaged" graphics must be restored
1220 if (laser.num_damages)
1222 int damage_start = 0;
1225 // determine the starting edge, from which graphics need to be restored
1228 for (i = 0; i < laser.num_damages; i++)
1230 if (laser.damage[i].edge == start_edge + 1)
1239 // restore graphics from this starting edge to the end of damage list
1240 for (i = damage_start; i < laser.num_damages; i++)
1242 int lx = laser.damage[i].x;
1243 int ly = laser.damage[i].y;
1244 int element = Tile[lx][ly];
1246 if (Hit[lx][ly] == laser.damage[i].edge)
1247 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1250 if (Box[lx][ly] == laser.damage[i].edge)
1253 if (IS_DRAWABLE(element))
1254 DrawField_MM(lx, ly);
1257 elx = laser.damage[damage_start].x;
1258 ely = laser.damage[damage_start].y;
1259 element = Tile[elx][ely];
1262 if (IS_BEAMER(element))
1266 for (i = 0; i < laser.num_beamers; i++)
1267 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1269 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1270 mode, elx, ely, Hit[elx][ely], start_edge);
1271 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1272 get_element_angle(element), laser.damage[damage_start].angle);
1276 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1277 laser.num_beamers > 0 &&
1278 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1280 // element is outgoing beamer
1281 laser.num_damages = damage_start + 1;
1283 if (IS_BEAMER(element))
1284 laser.current_angle = get_element_angle(element);
1288 // element is incoming beamer or other element
1289 laser.num_damages = damage_start;
1290 laser.current_angle = laser.damage[laser.num_damages].angle;
1295 // no damages but McDuffin himself (who needs to be redrawn anyway)
1297 elx = laser.start_edge.x;
1298 ely = laser.start_edge.y;
1299 element = Tile[elx][ely];
1302 laser.num_edges = start_edge + 1;
1303 if (start_edge == 0)
1304 laser.current_angle = laser.start_angle;
1306 LX = laser.edge[start_edge].x - cSX2;
1307 LY = laser.edge[start_edge].y - cSY2;
1308 XS = 2 * Step[laser.current_angle].x;
1309 YS = 2 * Step[laser.current_angle].y;
1312 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1318 if (IS_BEAMER(element) ||
1319 IS_FIBRE_OPTIC(element) ||
1320 IS_PACMAN(element) ||
1321 IS_POLAR(element) ||
1322 IS_POLAR_CROSS(element) ||
1323 element == EL_FUSE_ON)
1328 Debug("game:mm:DrawLaserExt", "element == %d", element);
1331 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1332 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1336 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1337 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1338 (laser.num_beamers == 0 ||
1339 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1341 // element is incoming beamer or other element
1342 step_size = -step_size;
1347 if (IS_BEAMER(element))
1348 Debug("game:mm:DrawLaserExt",
1349 "start_edge == %d, laser.beamer_edge == %d",
1350 start_edge, laser.beamer_edge);
1353 LX += step_size * XS;
1354 LY += step_size * YS;
1356 else if (element != EL_EMPTY)
1365 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1370 void DrawLaser(int start_edge, int mode)
1372 // do not draw laser if fuse is off
1373 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1376 if (mode == DL_LASER_DISABLED)
1377 DeactivateLaserTargetElement();
1379 if (laser.num_edges - start_edge < 0)
1381 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1386 // check if laser is interrupted by beamer element
1387 if (laser.num_beamers > 0 &&
1388 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1390 if (mode == DL_LASER_ENABLED)
1393 int tmp_start_edge = start_edge;
1395 // draw laser segments forward from the start to the last beamer
1396 for (i = 0; i < laser.num_beamers; i++)
1398 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1400 if (tmp_num_edges <= 0)
1404 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1405 i, laser.beamer_edge[i], tmp_start_edge);
1408 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1410 tmp_start_edge = laser.beamer_edge[i];
1413 // draw last segment from last beamer to the end
1414 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1420 int last_num_edges = laser.num_edges;
1421 int num_beamers = laser.num_beamers;
1423 // delete laser segments backward from the end to the first beamer
1424 for (i = num_beamers - 1; i >= 0; i--)
1426 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1428 if (laser.beamer_edge[i] - start_edge <= 0)
1431 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1433 last_num_edges = laser.beamer_edge[i];
1434 laser.num_beamers--;
1438 if (last_num_edges - start_edge <= 0)
1439 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1440 last_num_edges, start_edge);
1443 // special case when rotating first beamer: delete laser edge on beamer
1444 // (but do not start scanning on previous edge to prevent mirror sound)
1445 if (last_num_edges - start_edge == 1 && start_edge > 0)
1446 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1448 // delete first segment from start to the first beamer
1449 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1454 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1457 game_mm.laser_enabled = mode;
1460 void DrawLaser_MM(void)
1462 DrawLaser(0, game_mm.laser_enabled);
1465 boolean HitElement(int element, int hit_mask)
1467 if (HitOnlyAnEdge(hit_mask))
1470 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1471 element = MovingOrBlocked2Element_MM(ELX, ELY);
1474 Debug("game:mm:HitElement", "(1): element == %d", element);
1478 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1479 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1482 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1486 AddDamagedField(ELX, ELY);
1488 // this is more precise: check if laser would go through the center
1489 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1491 // prevent cutting through laser emitter with laser beam
1492 if (IS_LASER(element))
1495 // skip the whole element before continuing the scan
1501 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1503 if (LX/TILEX > ELX || LY/TILEY > ELY)
1505 /* skipping scan positions to the right and down skips one scan
1506 position too much, because this is only the top left scan position
1507 of totally four scan positions (plus one to the right, one to the
1508 bottom and one to the bottom right) */
1518 Debug("game:mm:HitElement", "(2): element == %d", element);
1521 if (LX + 5 * XS < 0 ||
1531 Debug("game:mm:HitElement", "(3): element == %d", element);
1534 if (IS_POLAR(element) &&
1535 ((element - EL_POLAR_START) % 2 ||
1536 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1538 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1540 laser.num_damages--;
1545 if (IS_POLAR_CROSS(element) &&
1546 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1548 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1550 laser.num_damages--;
1555 if (!IS_BEAMER(element) &&
1556 !IS_FIBRE_OPTIC(element) &&
1557 !IS_GRID_WOOD(element) &&
1558 element != EL_FUEL_EMPTY)
1561 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1562 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1564 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1567 LX = ELX * TILEX + 14;
1568 LY = ELY * TILEY + 14;
1570 AddLaserEdge(LX, LY);
1573 if (IS_MIRROR(element) ||
1574 IS_MIRROR_FIXED(element) ||
1575 IS_POLAR(element) ||
1576 IS_POLAR_CROSS(element) ||
1577 IS_DF_MIRROR(element) ||
1578 IS_DF_MIRROR_AUTO(element) ||
1579 element == EL_PRISM ||
1580 element == EL_REFRACTOR)
1582 int current_angle = laser.current_angle;
1585 laser.num_damages--;
1587 AddDamagedField(ELX, ELY);
1589 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1592 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1594 if (IS_MIRROR(element) ||
1595 IS_MIRROR_FIXED(element) ||
1596 IS_DF_MIRROR(element) ||
1597 IS_DF_MIRROR_AUTO(element))
1598 laser.current_angle = get_mirrored_angle(laser.current_angle,
1599 get_element_angle(element));
1601 if (element == EL_PRISM || element == EL_REFRACTOR)
1602 laser.current_angle = RND(16);
1604 XS = 2 * Step[laser.current_angle].x;
1605 YS = 2 * Step[laser.current_angle].y;
1607 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1612 LX += step_size * XS;
1613 LY += step_size * YS;
1615 // draw sparkles on mirror
1616 if ((IS_MIRROR(element) ||
1617 IS_MIRROR_FIXED(element) ||
1618 element == EL_PRISM) &&
1619 current_angle != laser.current_angle)
1621 MovDelay[ELX][ELY] = 11; // start animation
1624 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1625 current_angle != laser.current_angle)
1626 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1629 (get_opposite_angle(laser.current_angle) ==
1630 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1632 return (laser.overloaded ? TRUE : FALSE);
1635 if (element == EL_FUEL_FULL)
1637 laser.stops_inside_element = TRUE;
1642 if (element == EL_BOMB || element == EL_MINE)
1644 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1646 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1648 laser.dest_element_last = Tile[ELX][ELY];
1649 laser.dest_element_last_x = ELX;
1650 laser.dest_element_last_y = ELY;
1652 if (element == EL_MINE)
1653 laser.overloaded = TRUE;
1656 if (element == EL_KETTLE ||
1657 element == EL_CELL ||
1658 element == EL_KEY ||
1659 element == EL_LIGHTBALL ||
1660 element == EL_PACMAN ||
1663 if (!IS_PACMAN(element))
1666 if (element == EL_PACMAN)
1669 if (element == EL_KETTLE || element == EL_CELL)
1671 if (game_mm.kettles_still_needed > 0)
1672 game_mm.kettles_still_needed--;
1674 game.snapshot.collected_item = TRUE;
1676 if (game_mm.kettles_still_needed == 0)
1680 DrawLaser(0, DL_LASER_ENABLED);
1683 else if (element == EL_KEY)
1687 else if (IS_PACMAN(element))
1689 DeletePacMan(ELX, ELY);
1692 RaiseScoreElement_MM(element);
1697 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1699 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1701 DrawLaser(0, DL_LASER_ENABLED);
1703 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1705 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1706 game_mm.lights_still_needed--;
1710 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1711 game_mm.lights_still_needed++;
1714 DrawField_MM(ELX, ELY);
1715 DrawLaser(0, DL_LASER_ENABLED);
1720 laser.stops_inside_element = TRUE;
1726 Debug("game:mm:HitElement", "(4): element == %d", element);
1729 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1730 laser.num_beamers < MAX_NUM_BEAMERS &&
1731 laser.beamer[BEAMER_NR(element)][1].num)
1733 int beamer_angle = get_element_angle(element);
1734 int beamer_nr = BEAMER_NR(element);
1738 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1741 laser.num_damages--;
1743 if (IS_FIBRE_OPTIC(element) ||
1744 laser.current_angle == get_opposite_angle(beamer_angle))
1748 LX = ELX * TILEX + 14;
1749 LY = ELY * TILEY + 14;
1751 AddLaserEdge(LX, LY);
1752 AddDamagedField(ELX, ELY);
1754 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1757 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1759 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1760 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1761 ELX = laser.beamer[beamer_nr][pos].x;
1762 ELY = laser.beamer[beamer_nr][pos].y;
1763 LX = ELX * TILEX + 14;
1764 LY = ELY * TILEY + 14;
1766 if (IS_BEAMER(element))
1768 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1769 XS = 2 * Step[laser.current_angle].x;
1770 YS = 2 * Step[laser.current_angle].y;
1773 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1775 AddLaserEdge(LX, LY);
1776 AddDamagedField(ELX, ELY);
1778 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1781 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1783 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1788 LX += step_size * XS;
1789 LY += step_size * YS;
1791 laser.num_beamers++;
1800 boolean HitOnlyAnEdge(int hit_mask)
1802 // check if the laser hit only the edge of an element and, if so, go on
1805 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1809 if ((hit_mask == HIT_MASK_TOPLEFT ||
1810 hit_mask == HIT_MASK_TOPRIGHT ||
1811 hit_mask == HIT_MASK_BOTTOMLEFT ||
1812 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1813 laser.current_angle % 4) // angle is not 90°
1817 if (hit_mask == HIT_MASK_TOPLEFT)
1822 else if (hit_mask == HIT_MASK_TOPRIGHT)
1827 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1832 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1838 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1844 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1851 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1857 boolean HitPolarizer(int element, int hit_mask)
1859 if (HitOnlyAnEdge(hit_mask))
1862 if (IS_DF_GRID(element))
1864 int grid_angle = get_element_angle(element);
1867 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1868 grid_angle, laser.current_angle);
1871 AddLaserEdge(LX, LY);
1872 AddDamagedField(ELX, ELY);
1875 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1877 if (laser.current_angle == grid_angle ||
1878 laser.current_angle == get_opposite_angle(grid_angle))
1880 // skip the whole element before continuing the scan
1886 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1888 if (LX/TILEX > ELX || LY/TILEY > ELY)
1890 /* skipping scan positions to the right and down skips one scan
1891 position too much, because this is only the top left scan position
1892 of totally four scan positions (plus one to the right, one to the
1893 bottom and one to the bottom right) */
1899 AddLaserEdge(LX, LY);
1905 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1907 LX / TILEX, LY / TILEY,
1908 LX % TILEX, LY % TILEY);
1913 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1915 return HitReflectingWalls(element, hit_mask);
1919 return HitAbsorbingWalls(element, hit_mask);
1922 else if (IS_GRID_STEEL(element))
1924 return HitReflectingWalls(element, hit_mask);
1926 else // IS_GRID_WOOD
1928 return HitAbsorbingWalls(element, hit_mask);
1934 boolean HitBlock(int element, int hit_mask)
1936 boolean check = FALSE;
1938 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1939 game_mm.num_keys == 0)
1942 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1945 int ex = ELX * TILEX + 14;
1946 int ey = ELY * TILEY + 14;
1950 for (i = 1; i < 32; i++)
1955 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1960 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1961 return HitAbsorbingWalls(element, hit_mask);
1965 AddLaserEdge(LX - XS, LY - YS);
1966 AddDamagedField(ELX, ELY);
1969 Box[ELX][ELY] = laser.num_edges;
1971 return HitReflectingWalls(element, hit_mask);
1974 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1976 int xs = XS / 2, ys = YS / 2;
1977 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1978 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1980 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1981 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1983 laser.overloaded = (element == EL_GATE_STONE);
1988 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1989 (hit_mask == HIT_MASK_TOP ||
1990 hit_mask == HIT_MASK_LEFT ||
1991 hit_mask == HIT_MASK_RIGHT ||
1992 hit_mask == HIT_MASK_BOTTOM))
1993 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1994 hit_mask == HIT_MASK_BOTTOM),
1995 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1996 hit_mask == HIT_MASK_RIGHT));
1997 AddLaserEdge(LX, LY);
2003 if (element == EL_GATE_STONE && Box[ELX][ELY])
2005 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2017 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2019 int xs = XS / 2, ys = YS / 2;
2020 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2021 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2023 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2024 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2026 laser.overloaded = (element == EL_BLOCK_STONE);
2031 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2032 (hit_mask == HIT_MASK_TOP ||
2033 hit_mask == HIT_MASK_LEFT ||
2034 hit_mask == HIT_MASK_RIGHT ||
2035 hit_mask == HIT_MASK_BOTTOM))
2036 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2037 hit_mask == HIT_MASK_BOTTOM),
2038 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2039 hit_mask == HIT_MASK_RIGHT));
2040 AddDamagedField(ELX, ELY);
2042 LX = ELX * TILEX + 14;
2043 LY = ELY * TILEY + 14;
2045 AddLaserEdge(LX, LY);
2047 laser.stops_inside_element = TRUE;
2055 boolean HitLaserSource(int element, int hit_mask)
2057 if (HitOnlyAnEdge(hit_mask))
2060 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2062 laser.overloaded = TRUE;
2067 boolean HitLaserDestination(int element, int hit_mask)
2069 if (HitOnlyAnEdge(hit_mask))
2072 if (element != EL_EXIT_OPEN &&
2073 !(IS_RECEIVER(element) &&
2074 game_mm.kettles_still_needed == 0 &&
2075 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2077 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2082 if (IS_RECEIVER(element) ||
2083 (IS_22_5_ANGLE(laser.current_angle) &&
2084 (ELX != (LX + 6 * XS) / TILEX ||
2085 ELY != (LY + 6 * YS) / TILEY ||
2094 LX = ELX * TILEX + 14;
2095 LY = ELY * TILEY + 14;
2097 laser.stops_inside_element = TRUE;
2100 AddLaserEdge(LX, LY);
2101 AddDamagedField(ELX, ELY);
2103 if (game_mm.lights_still_needed == 0)
2105 game_mm.level_solved = TRUE;
2107 SetTileCursorActive(FALSE);
2113 boolean HitReflectingWalls(int element, int hit_mask)
2115 // check if laser hits side of a wall with an angle that is not 90°
2116 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2117 hit_mask == HIT_MASK_LEFT ||
2118 hit_mask == HIT_MASK_RIGHT ||
2119 hit_mask == HIT_MASK_BOTTOM))
2121 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2126 if (!IS_DF_GRID(element))
2127 AddLaserEdge(LX, LY);
2129 // check if laser hits wall with an angle of 45°
2130 if (!IS_22_5_ANGLE(laser.current_angle))
2132 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2135 laser.current_angle = get_mirrored_angle(laser.current_angle,
2138 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2141 laser.current_angle = get_mirrored_angle(laser.current_angle,
2145 AddLaserEdge(LX, LY);
2147 XS = 2 * Step[laser.current_angle].x;
2148 YS = 2 * Step[laser.current_angle].y;
2152 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2154 laser.current_angle = get_mirrored_angle(laser.current_angle,
2159 if (!IS_DF_GRID(element))
2160 AddLaserEdge(LX, LY);
2165 if (!IS_DF_GRID(element))
2166 AddLaserEdge(LX, LY + YS / 2);
2169 if (!IS_DF_GRID(element))
2170 AddLaserEdge(LX, LY);
2173 YS = 2 * Step[laser.current_angle].y;
2177 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2179 laser.current_angle = get_mirrored_angle(laser.current_angle,
2184 if (!IS_DF_GRID(element))
2185 AddLaserEdge(LX, LY);
2190 if (!IS_DF_GRID(element))
2191 AddLaserEdge(LX + XS / 2, LY);
2194 if (!IS_DF_GRID(element))
2195 AddLaserEdge(LX, LY);
2198 XS = 2 * Step[laser.current_angle].x;
2204 // reflection at the edge of reflecting DF style wall
2205 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2207 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2208 hit_mask == HIT_MASK_TOPRIGHT) ||
2209 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2210 hit_mask == HIT_MASK_TOPLEFT) ||
2211 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2212 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2213 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2214 hit_mask == HIT_MASK_BOTTOMRIGHT))
2217 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2218 ANG_MIRROR_135 : ANG_MIRROR_45);
2220 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2222 AddDamagedField(ELX, ELY);
2223 AddLaserEdge(LX, LY);
2225 laser.current_angle = get_mirrored_angle(laser.current_angle,
2233 AddLaserEdge(LX, LY);
2239 // reflection inside an edge of reflecting DF style wall
2240 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2242 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2243 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2244 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2245 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2246 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2247 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2248 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2249 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2252 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2253 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2254 ANG_MIRROR_135 : ANG_MIRROR_45);
2256 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2259 AddDamagedField(ELX, ELY);
2262 AddLaserEdge(LX - XS, LY - YS);
2263 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2264 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2266 laser.current_angle = get_mirrored_angle(laser.current_angle,
2274 AddLaserEdge(LX, LY);
2280 // check if laser hits DF style wall with an angle of 90°
2281 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2283 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2284 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2285 (IS_VERT_ANGLE(laser.current_angle) &&
2286 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2288 // laser at last step touched nothing or the same side of the wall
2289 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2291 AddDamagedField(ELX, ELY);
2298 last_hit_mask = hit_mask;
2305 if (!HitOnlyAnEdge(hit_mask))
2307 laser.overloaded = TRUE;
2315 boolean HitAbsorbingWalls(int element, int hit_mask)
2317 if (HitOnlyAnEdge(hit_mask))
2321 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2323 AddLaserEdge(LX - XS, LY - YS);
2330 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2332 AddLaserEdge(LX - XS, LY - YS);
2338 if (IS_WALL_WOOD(element) ||
2339 IS_DF_WALL_WOOD(element) ||
2340 IS_GRID_WOOD(element) ||
2341 IS_GRID_WOOD_FIXED(element) ||
2342 IS_GRID_WOOD_AUTO(element) ||
2343 element == EL_FUSE_ON ||
2344 element == EL_BLOCK_WOOD ||
2345 element == EL_GATE_WOOD)
2347 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2352 if (IS_WALL_ICE(element))
2356 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2357 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2359 // check if laser hits wall with an angle of 90°
2360 if (IS_90_ANGLE(laser.current_angle))
2361 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2363 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2367 for (i = 0; i < 4; i++)
2369 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2370 mask = 15 - (8 >> i);
2371 else if (ABS(XS) == 4 &&
2373 (XS > 0) == (i % 2) &&
2374 (YS < 0) == (i / 2))
2375 mask = 3 + (i / 2) * 9;
2376 else if (ABS(YS) == 4 &&
2378 (XS < 0) == (i % 2) &&
2379 (YS > 0) == (i / 2))
2380 mask = 5 + (i % 2) * 5;
2384 laser.wall_mask = mask;
2386 else if (IS_WALL_AMOEBA(element))
2388 int elx = (LX - 2 * XS) / TILEX;
2389 int ely = (LY - 2 * YS) / TILEY;
2390 int element2 = Tile[elx][ely];
2393 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2395 laser.dest_element = EL_EMPTY;
2403 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2404 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2406 if (IS_90_ANGLE(laser.current_angle))
2407 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2409 laser.dest_element = element2 | EL_WALL_AMOEBA;
2411 laser.wall_mask = mask;
2417 static void OpenExit(int x, int y)
2421 if (!MovDelay[x][y]) // next animation frame
2422 MovDelay[x][y] = 4 * delay;
2424 if (MovDelay[x][y]) // wait some time before next frame
2429 phase = MovDelay[x][y] / delay;
2431 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2432 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2434 if (!MovDelay[x][y])
2436 Tile[x][y] = EL_EXIT_OPEN;
2442 static void OpenSurpriseBall(int x, int y)
2446 if (!MovDelay[x][y]) // next animation frame
2447 MovDelay[x][y] = 50 * delay;
2449 if (MovDelay[x][y]) // wait some time before next frame
2453 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2456 int graphic = el2gfx(Store[x][y]);
2458 int dx = RND(26), dy = RND(26);
2460 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2462 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2463 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2465 MarkTileDirty(x, y);
2468 if (!MovDelay[x][y])
2470 Tile[x][y] = Store[x][y];
2471 Store[x][y] = Store2[x][y] = 0;
2480 static void MeltIce(int x, int y)
2485 if (!MovDelay[x][y]) // next animation frame
2486 MovDelay[x][y] = frames * delay;
2488 if (MovDelay[x][y]) // wait some time before next frame
2491 int wall_mask = Store2[x][y];
2492 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2495 phase = frames - MovDelay[x][y] / delay - 1;
2497 if (!MovDelay[x][y])
2501 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2502 Store[x][y] = Store2[x][y] = 0;
2504 DrawWalls_MM(x, y, Tile[x][y]);
2506 if (Tile[x][y] == EL_WALL_ICE)
2507 Tile[x][y] = EL_EMPTY;
2509 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2510 if (laser.damage[i].is_mirror)
2514 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2516 DrawLaser(0, DL_LASER_DISABLED);
2520 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2522 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2524 laser.redraw = TRUE;
2529 static void GrowAmoeba(int x, int y)
2534 if (!MovDelay[x][y]) // next animation frame
2535 MovDelay[x][y] = frames * delay;
2537 if (MovDelay[x][y]) // wait some time before next frame
2540 int wall_mask = Store2[x][y];
2541 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2544 phase = MovDelay[x][y] / delay;
2546 if (!MovDelay[x][y])
2548 Tile[x][y] = real_element;
2549 Store[x][y] = Store2[x][y] = 0;
2551 DrawWalls_MM(x, y, Tile[x][y]);
2552 DrawLaser(0, DL_LASER_ENABLED);
2554 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2556 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2561 static void DrawFieldAnimated_MM(int x, int y)
2565 laser.redraw = TRUE;
2568 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2570 int element = Tile[x][y];
2571 int graphic = el2gfx(element);
2573 if (!getGraphicInfo_NewFrame(x, y, graphic))
2578 laser.redraw = TRUE;
2581 static void DrawFieldTwinkle(int x, int y)
2583 if (MovDelay[x][y] != 0) // wait some time before next frame
2589 if (MovDelay[x][y] != 0)
2591 int graphic = IMG_TWINKLE_WHITE;
2592 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2594 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2597 laser.redraw = TRUE;
2601 static void Explode_MM(int x, int y, int phase, int mode)
2603 int num_phase = 9, delay = 2;
2604 int last_phase = num_phase * delay;
2605 int half_phase = (num_phase / 2) * delay;
2607 laser.redraw = TRUE;
2609 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2611 int center_element = Tile[x][y];
2613 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2615 // put moving element to center field (and let it explode there)
2616 center_element = MovingOrBlocked2Element_MM(x, y);
2617 RemoveMovingField_MM(x, y);
2619 Tile[x][y] = center_element;
2622 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2623 Store[x][y] = center_element;
2625 Store[x][y] = EL_EMPTY;
2627 Store2[x][y] = mode;
2629 Tile[x][y] = EL_EXPLODING_OPAQUE;
2630 GfxElement[x][y] = center_element;
2632 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2634 ExplodePhase[x][y] = 1;
2640 GfxFrame[x][y] = 0; // restart explosion animation
2642 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2644 if (phase == half_phase)
2646 Tile[x][y] = EL_EXPLODING_TRANSP;
2648 if (x == ELX && y == ELY)
2652 if (phase == last_phase)
2654 if (Store[x][y] == EL_BOMB_ACTIVE)
2656 DrawLaser(0, DL_LASER_DISABLED);
2659 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2660 Store[x][y] = EL_EMPTY;
2662 GameOver_MM(GAME_OVER_DELAYED);
2664 laser.overloaded = FALSE;
2666 else if (IS_MCDUFFIN(Store[x][y]))
2668 Store[x][y] = EL_EMPTY;
2670 GameOver_MM(GAME_OVER_BOMB);
2673 Tile[x][y] = Store[x][y];
2674 Store[x][y] = Store2[x][y] = 0;
2675 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2680 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2682 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2683 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2685 DrawGraphicAnimation_MM(x, y, graphic, frame);
2687 MarkTileDirty(x, y);
2691 static void Bang_MM(int x, int y)
2693 int element = Tile[x][y];
2696 DrawLaser(0, DL_LASER_ENABLED);
2699 if (IS_PACMAN(element))
2700 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2701 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2702 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2703 else if (element == EL_KEY)
2704 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2706 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2708 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2711 void TurnRound(int x, int y)
2723 { 0, 0 }, { 0, 0 }, { 0, 0 },
2728 int left, right, back;
2732 { MV_DOWN, MV_UP, MV_RIGHT },
2733 { MV_UP, MV_DOWN, MV_LEFT },
2735 { MV_LEFT, MV_RIGHT, MV_DOWN },
2736 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2737 { MV_RIGHT, MV_LEFT, MV_UP }
2740 int element = Tile[x][y];
2741 int old_move_dir = MovDir[x][y];
2742 int right_dir = turn[old_move_dir].right;
2743 int back_dir = turn[old_move_dir].back;
2744 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2745 int right_x = x + right_dx, right_y = y + right_dy;
2747 if (element == EL_PACMAN)
2749 boolean can_turn_right = FALSE;
2751 if (IN_LEV_FIELD(right_x, right_y) &&
2752 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2753 can_turn_right = TRUE;
2756 MovDir[x][y] = right_dir;
2758 MovDir[x][y] = back_dir;
2764 static void StartMoving_MM(int x, int y)
2766 int element = Tile[x][y];
2771 if (CAN_MOVE(element))
2775 if (MovDelay[x][y]) // wait some time before next movement
2783 // now make next step
2785 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2787 if (element == EL_PACMAN &&
2788 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2789 !ObjHit(newx, newy, HIT_POS_CENTER))
2791 Store[newx][newy] = Tile[newx][newy];
2792 Tile[newx][newy] = EL_EMPTY;
2794 DrawField_MM(newx, newy);
2796 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2797 ObjHit(newx, newy, HIT_POS_CENTER))
2799 // object was running against a wall
2806 InitMovingField_MM(x, y, MovDir[x][y]);
2810 ContinueMoving_MM(x, y);
2813 static void ContinueMoving_MM(int x, int y)
2815 int element = Tile[x][y];
2816 int direction = MovDir[x][y];
2817 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2818 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2819 int horiz_move = (dx!=0);
2820 int newx = x + dx, newy = y + dy;
2821 int step = (horiz_move ? dx : dy) * TILEX / 8;
2823 MovPos[x][y] += step;
2825 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2827 Tile[x][y] = EL_EMPTY;
2828 Tile[newx][newy] = element;
2830 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2831 MovDelay[newx][newy] = 0;
2833 if (!CAN_MOVE(element))
2834 MovDir[newx][newy] = 0;
2837 DrawField_MM(newx, newy);
2839 Stop[newx][newy] = TRUE;
2841 if (element == EL_PACMAN)
2843 if (Store[newx][newy] == EL_BOMB)
2844 Bang_MM(newx, newy);
2846 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2847 (LX + 2 * XS) / TILEX == newx &&
2848 (LY + 2 * YS) / TILEY == newy)
2855 else // still moving on
2860 laser.redraw = TRUE;
2863 boolean ClickElement(int x, int y, int button)
2865 static DelayCounter click_delay = { CLICK_DELAY };
2866 static boolean new_button = TRUE;
2867 boolean element_clicked = FALSE;
2872 // initialize static variables
2873 click_delay.count = 0;
2874 click_delay.value = CLICK_DELAY;
2880 // do not rotate objects hit by the laser after the game was solved
2881 if (game_mm.level_solved && Hit[x][y])
2884 if (button == MB_RELEASED)
2887 click_delay.value = CLICK_DELAY;
2889 // release eventually hold auto-rotating mirror
2890 RotateMirror(x, y, MB_RELEASED);
2895 if (!FrameReached(&click_delay) && !new_button)
2898 if (button == MB_MIDDLEBUTTON) // middle button has no function
2901 if (!IN_LEV_FIELD(x, y))
2904 if (Tile[x][y] == EL_EMPTY)
2907 element = Tile[x][y];
2909 if (IS_MIRROR(element) ||
2910 IS_BEAMER(element) ||
2911 IS_POLAR(element) ||
2912 IS_POLAR_CROSS(element) ||
2913 IS_DF_MIRROR(element) ||
2914 IS_DF_MIRROR_AUTO(element))
2916 RotateMirror(x, y, button);
2918 element_clicked = TRUE;
2920 else if (IS_MCDUFFIN(element))
2922 if (!laser.fuse_off)
2924 DrawLaser(0, DL_LASER_DISABLED);
2931 element = get_rotated_element(element, BUTTON_ROTATION(button));
2932 laser.start_angle = get_element_angle(element);
2936 Tile[x][y] = element;
2943 if (!laser.fuse_off)
2946 element_clicked = TRUE;
2948 else if (element == EL_FUSE_ON && laser.fuse_off)
2950 if (x != laser.fuse_x || y != laser.fuse_y)
2953 laser.fuse_off = FALSE;
2954 laser.fuse_x = laser.fuse_y = -1;
2956 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2959 element_clicked = TRUE;
2961 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2963 laser.fuse_off = TRUE;
2966 laser.overloaded = FALSE;
2968 DrawLaser(0, DL_LASER_DISABLED);
2969 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2971 element_clicked = TRUE;
2973 else if (element == EL_LIGHTBALL)
2976 RaiseScoreElement_MM(element);
2977 DrawLaser(0, DL_LASER_ENABLED);
2979 element_clicked = TRUE;
2982 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2985 return element_clicked;
2988 void RotateMirror(int x, int y, int button)
2990 if (button == MB_RELEASED)
2992 // release eventually hold auto-rotating mirror
2999 if (IS_MIRROR(Tile[x][y]) ||
3000 IS_POLAR_CROSS(Tile[x][y]) ||
3001 IS_POLAR(Tile[x][y]) ||
3002 IS_BEAMER(Tile[x][y]) ||
3003 IS_DF_MIRROR(Tile[x][y]) ||
3004 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3005 IS_GRID_WOOD_AUTO(Tile[x][y]))
3007 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3009 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3011 if (button == MB_LEFTBUTTON)
3013 // left mouse button only for manual adjustment, no auto-rotating;
3014 // freeze mirror for until mouse button released
3018 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3020 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3024 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3026 int edge = Hit[x][y];
3032 DrawLaser(edge - 1, DL_LASER_DISABLED);
3036 else if (ObjHit(x, y, HIT_POS_CENTER))
3038 int edge = Hit[x][y];
3042 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3047 DrawLaser(edge - 1, DL_LASER_DISABLED);
3054 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3059 if ((IS_BEAMER(Tile[x][y]) ||
3060 IS_POLAR(Tile[x][y]) ||
3061 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3063 if (IS_BEAMER(Tile[x][y]))
3066 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3067 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3080 DrawLaser(0, DL_LASER_ENABLED);
3084 static void AutoRotateMirrors(void)
3088 if (!FrameReached(&rotate_delay))
3091 for (x = 0; x < lev_fieldx; x++)
3093 for (y = 0; y < lev_fieldy; y++)
3095 int element = Tile[x][y];
3097 // do not rotate objects hit by the laser after the game was solved
3098 if (game_mm.level_solved && Hit[x][y])
3101 if (IS_DF_MIRROR_AUTO(element) ||
3102 IS_GRID_WOOD_AUTO(element) ||
3103 IS_GRID_STEEL_AUTO(element) ||
3104 element == EL_REFRACTOR)
3105 RotateMirror(x, y, MB_RIGHTBUTTON);
3110 boolean ObjHit(int obx, int oby, int bits)
3117 if (bits & HIT_POS_CENTER)
3119 if (CheckLaserPixel(cSX + obx + 15,
3124 if (bits & HIT_POS_EDGE)
3126 for (i = 0; i < 4; i++)
3127 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3128 cSY + oby + 31 * (i / 2)))
3132 if (bits & HIT_POS_BETWEEN)
3134 for (i = 0; i < 4; i++)
3135 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3136 cSY + 4 + oby + 22 * (i / 2)))
3143 void DeletePacMan(int px, int py)
3149 if (game_mm.num_pacman <= 1)
3151 game_mm.num_pacman = 0;
3155 for (i = 0; i < game_mm.num_pacman; i++)
3156 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3159 game_mm.num_pacman--;
3161 for (j = i; j < game_mm.num_pacman; j++)
3163 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3164 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3165 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3169 void ColorCycling(void)
3171 static int CC, Cc = 0;
3173 static int color, old = 0xF00, new = 0x010, mult = 1;
3174 static unsigned short red, green, blue;
3176 if (color_status == STATIC_COLORS)
3181 if (CC < Cc || CC > Cc + 2)
3185 color = old + new * mult;
3191 if (ABS(mult) == 16)
3201 red = 0x0e00 * ((color & 0xF00) >> 8);
3202 green = 0x0e00 * ((color & 0x0F0) >> 4);
3203 blue = 0x0e00 * ((color & 0x00F));
3204 SetRGB(pen_magicolor[0], red, green, blue);
3206 red = 0x1111 * ((color & 0xF00) >> 8);
3207 green = 0x1111 * ((color & 0x0F0) >> 4);
3208 blue = 0x1111 * ((color & 0x00F));
3209 SetRGB(pen_magicolor[1], red, green, blue);
3213 static void GameActions_MM_Ext(void)
3220 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3223 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3225 element = Tile[x][y];
3227 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3228 StartMoving_MM(x, y);
3229 else if (IS_MOVING(x, y))
3230 ContinueMoving_MM(x, y);
3231 else if (IS_EXPLODING(element))
3232 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3233 else if (element == EL_EXIT_OPENING)
3235 else if (element == EL_GRAY_BALL_OPENING)
3236 OpenSurpriseBall(x, y);
3237 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3239 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3241 else if (IS_MIRROR(element) ||
3242 IS_MIRROR_FIXED(element) ||
3243 element == EL_PRISM)
3244 DrawFieldTwinkle(x, y);
3245 else if (element == EL_GRAY_BALL_OPENING ||
3246 element == EL_BOMB_ACTIVE ||
3247 element == EL_MINE_ACTIVE)
3248 DrawFieldAnimated_MM(x, y);
3249 else if (!IS_BLOCKED(x, y))
3250 DrawFieldAnimatedIfNeeded_MM(x, y);
3253 AutoRotateMirrors();
3256 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3258 // redraw after Explode_MM() ...
3260 DrawLaser(0, DL_LASER_ENABLED);
3261 laser.redraw = FALSE;
3266 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3270 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3272 DrawLaser(0, DL_LASER_DISABLED);
3277 // skip all following game actions if game is over
3278 if (game_mm.game_over)
3281 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3285 GameOver_MM(GAME_OVER_NO_ENERGY);
3290 if (FrameReached(&energy_delay))
3292 if (game_mm.energy_left > 0)
3293 game_mm.energy_left--;
3295 // when out of energy, wait another frame to play "out of time" sound
3298 element = laser.dest_element;
3301 if (element != Tile[ELX][ELY])
3303 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3304 element, Tile[ELX][ELY]);
3308 if (!laser.overloaded && laser.overload_value == 0 &&
3309 element != EL_BOMB &&
3310 element != EL_BOMB_ACTIVE &&
3311 element != EL_MINE &&
3312 element != EL_MINE_ACTIVE &&
3313 element != EL_BALL_GRAY &&
3314 element != EL_BLOCK_STONE &&
3315 element != EL_BLOCK_WOOD &&
3316 element != EL_FUSE_ON &&
3317 element != EL_FUEL_FULL &&
3318 !IS_WALL_ICE(element) &&
3319 !IS_WALL_AMOEBA(element))
3322 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3324 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3325 (!laser.overloaded && laser.overload_value > 0)) &&
3326 FrameReached(&overload_delay))
3328 if (laser.overloaded)
3329 laser.overload_value++;
3331 laser.overload_value--;
3333 if (game_mm.cheat_no_overload)
3335 laser.overloaded = FALSE;
3336 laser.overload_value = 0;
3339 game_mm.laser_overload_value = laser.overload_value;
3341 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3343 SetLaserColor(0xFF);
3345 DrawLaser(0, DL_LASER_ENABLED);
3348 if (!laser.overloaded)
3349 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3350 else if (setup.sound_loops)
3351 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3353 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3355 if (laser.overloaded)
3358 BlitBitmap(pix[PIX_DOOR], drawto,
3359 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3360 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3361 - laser.overload_value,
3362 OVERLOAD_XSIZE, laser.overload_value,
3363 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3364 - laser.overload_value);
3366 redraw_mask |= REDRAW_DOOR_1;
3371 BlitBitmap(pix[PIX_DOOR], drawto,
3372 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3373 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3374 DX_OVERLOAD, DY_OVERLOAD);
3376 redraw_mask |= REDRAW_DOOR_1;
3379 if (laser.overload_value == MAX_LASER_OVERLOAD)
3381 UpdateAndDisplayGameControlValues();
3385 GameOver_MM(GAME_OVER_OVERLOADED);
3396 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3398 if (game_mm.cheat_no_explosion)
3403 laser.dest_element = EL_EXPLODING_OPAQUE;
3408 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3410 laser.fuse_off = TRUE;
3414 DrawLaser(0, DL_LASER_DISABLED);
3415 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3418 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3420 if (!Store2[ELX][ELY]) // check if content element not yet determined
3422 int last_anim_random_frame = gfx.anim_random_frame;
3425 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3426 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3428 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3429 native_mm_level.ball_choice_mode, 0,
3430 game_mm.ball_choice_pos);
3432 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3433 gfx.anim_random_frame = last_anim_random_frame;
3435 game_mm.ball_choice_pos++;
3437 int new_element = native_mm_level.ball_content[element_pos];
3439 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3440 Store2[ELX][ELY] = TRUE;
3443 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3445 // !!! CHECK AGAIN: Laser on Polarizer !!!
3448 laser.dest_element_last = Tile[ELX][ELY];
3449 laser.dest_element_last_x = ELX;
3450 laser.dest_element_last_y = ELY;
3460 element = EL_MIRROR_START + RND(16);
3466 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3473 element = (rnd == 0 ? EL_FUSE_ON :
3474 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3475 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3476 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3477 EL_MIRROR_FIXED_START + rnd - 25);
3482 graphic = el2gfx(element);
3484 for (i = 0; i < 50; i++)
3490 BlitBitmap(pix[PIX_BACK], drawto,
3491 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3492 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3493 SX + ELX * TILEX + x,
3494 SY + ELY * TILEY + y);
3496 MarkTileDirty(ELX, ELY);
3499 DrawLaser(0, DL_LASER_ENABLED);
3501 Delay_WithScreenUpdates(50);
3504 Tile[ELX][ELY] = element;
3505 DrawField_MM(ELX, ELY);
3508 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3511 // above stuff: GRAY BALL -> PRISM !!!
3513 LX = ELX * TILEX + 14;
3514 LY = ELY * TILEY + 14;
3515 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3522 laser.num_edges -= 2;
3523 laser.num_damages--;
3527 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3528 if (laser.damage[i].is_mirror)
3532 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3534 DrawLaser(0, DL_LASER_DISABLED);
3536 DrawLaser(0, DL_LASER_DISABLED);
3545 if (IS_WALL_ICE(element) && CT > 50)
3547 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3550 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3551 Store[ELX][ELY] = EL_WALL_ICE;
3552 Store2[ELX][ELY] = laser.wall_mask;
3554 laser.dest_element = Tile[ELX][ELY];
3559 for (i = 0; i < 5; i++)
3565 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3569 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3571 Delay_WithScreenUpdates(100);
3574 if (Tile[ELX][ELY] == EL_WALL_ICE)
3575 Tile[ELX][ELY] = EL_EMPTY;
3579 LX = laser.edge[laser.num_edges].x - cSX2;
3580 LY = laser.edge[laser.num_edges].y - cSY2;
3583 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3584 if (laser.damage[i].is_mirror)
3588 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3590 DrawLaser(0, DL_LASER_DISABLED);
3597 if (IS_WALL_AMOEBA(element) && CT > 60)
3599 int k1, k2, k3, dx, dy, de, dm;
3600 int element2 = Tile[ELX][ELY];
3602 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3605 for (i = laser.num_damages - 1; i >= 0; i--)
3606 if (laser.damage[i].is_mirror)
3609 r = laser.num_edges;
3610 d = laser.num_damages;
3617 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3620 DrawLaser(0, DL_LASER_ENABLED);
3623 x = laser.damage[k1].x;
3624 y = laser.damage[k1].y;
3629 for (i = 0; i < 4; i++)
3631 if (laser.wall_mask & (1 << i))
3633 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3634 cSY + ELY * TILEY + 31 * (i / 2)))
3637 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3638 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3645 for (i = 0; i < 4; i++)
3647 if (laser.wall_mask & (1 << i))
3649 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3650 cSY + ELY * TILEY + 31 * (i / 2)))
3657 if (laser.num_beamers > 0 ||
3658 k1 < 1 || k2 < 4 || k3 < 4 ||
3659 CheckLaserPixel(cSX + ELX * TILEX + 14,
3660 cSY + ELY * TILEY + 14))
3662 laser.num_edges = r;
3663 laser.num_damages = d;
3665 DrawLaser(0, DL_LASER_DISABLED);
3668 Tile[ELX][ELY] = element | laser.wall_mask;
3672 de = Tile[ELX][ELY];
3673 dm = laser.wall_mask;
3677 int x = ELX, y = ELY;
3678 int wall_mask = laser.wall_mask;
3681 DrawLaser(0, DL_LASER_ENABLED);
3683 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3685 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3686 Store[x][y] = EL_WALL_AMOEBA;
3687 Store2[x][y] = wall_mask;
3693 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3695 DrawLaser(0, DL_LASER_ENABLED);
3697 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3699 for (i = 4; i >= 0; i--)
3701 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3704 Delay_WithScreenUpdates(20);
3707 DrawLaser(0, DL_LASER_ENABLED);
3712 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3713 laser.stops_inside_element && CT > native_mm_level.time_block)
3718 if (ABS(XS) > ABS(YS))
3725 for (i = 0; i < 4; i++)
3732 x = ELX + Step[k * 4].x;
3733 y = ELY + Step[k * 4].y;
3735 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3738 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3746 laser.overloaded = (element == EL_BLOCK_STONE);
3751 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3754 Tile[x][y] = element;
3756 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3759 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3761 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3762 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3770 if (element == EL_FUEL_FULL && CT > 10)
3772 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3773 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3775 for (i = start; i <= num_init_game_frames; i++)
3777 if (i == num_init_game_frames)
3778 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3779 else if (setup.sound_loops)
3780 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3782 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3784 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3786 UpdateAndDisplayGameControlValues();
3791 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3793 DrawField_MM(ELX, ELY);
3795 DrawLaser(0, DL_LASER_ENABLED);
3803 void GameActions_MM(struct MouseActionInfo action)
3805 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3806 boolean button_released = (action.button == MB_RELEASED);
3808 GameActions_MM_Ext();
3810 CheckSingleStepMode_MM(element_clicked, button_released);
3813 void MovePacMen(void)
3815 int mx, my, ox, oy, nx, ny;
3819 if (++pacman_nr >= game_mm.num_pacman)
3822 game_mm.pacman[pacman_nr].dir--;
3824 for (l = 1; l < 5; l++)
3826 game_mm.pacman[pacman_nr].dir++;
3828 if (game_mm.pacman[pacman_nr].dir > 4)
3829 game_mm.pacman[pacman_nr].dir = 1;
3831 if (game_mm.pacman[pacman_nr].dir % 2)
3834 my = game_mm.pacman[pacman_nr].dir - 2;
3839 mx = 3 - game_mm.pacman[pacman_nr].dir;
3842 ox = game_mm.pacman[pacman_nr].x;
3843 oy = game_mm.pacman[pacman_nr].y;
3846 element = Tile[nx][ny];
3848 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3851 if (!IS_EATABLE4PACMAN(element))
3854 if (ObjHit(nx, ny, HIT_POS_CENTER))
3857 Tile[ox][oy] = EL_EMPTY;
3859 EL_PACMAN_RIGHT - 1 +
3860 (game_mm.pacman[pacman_nr].dir - 1 +
3861 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3863 game_mm.pacman[pacman_nr].x = nx;
3864 game_mm.pacman[pacman_nr].y = ny;
3866 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3868 if (element != EL_EMPTY)
3870 int graphic = el2gfx(Tile[nx][ny]);
3875 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3878 ox = cSX + ox * TILEX;
3879 oy = cSY + oy * TILEY;
3881 for (i = 1; i < 33; i += 2)
3882 BlitBitmap(bitmap, window,
3883 src_x, src_y, TILEX, TILEY,
3884 ox + i * mx, oy + i * my);
3885 Ct = Ct + FrameCounter - CT;
3888 DrawField_MM(nx, ny);
3891 if (!laser.fuse_off)
3893 DrawLaser(0, DL_LASER_ENABLED);
3895 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3897 AddDamagedField(nx, ny);
3899 laser.damage[laser.num_damages - 1].edge = 0;
3903 if (element == EL_BOMB)
3904 DeletePacMan(nx, ny);
3906 if (IS_WALL_AMOEBA(element) &&
3907 (LX + 2 * XS) / TILEX == nx &&
3908 (LY + 2 * YS) / TILEY == ny)
3918 static void InitMovingField_MM(int x, int y, int direction)
3920 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3921 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3923 MovDir[x][y] = direction;
3924 MovDir[newx][newy] = direction;
3926 if (Tile[newx][newy] == EL_EMPTY)
3927 Tile[newx][newy] = EL_BLOCKED;
3930 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3932 int direction = MovDir[x][y];
3933 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3934 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3940 static void Blocked2Moving_MM(int x, int y,
3941 int *comes_from_x, int *comes_from_y)
3943 int oldx = x, oldy = y;
3944 int direction = MovDir[x][y];
3946 if (direction == MV_LEFT)
3948 else if (direction == MV_RIGHT)
3950 else if (direction == MV_UP)
3952 else if (direction == MV_DOWN)
3955 *comes_from_x = oldx;
3956 *comes_from_y = oldy;
3959 static int MovingOrBlocked2Element_MM(int x, int y)
3961 int element = Tile[x][y];
3963 if (element == EL_BLOCKED)
3967 Blocked2Moving_MM(x, y, &oldx, &oldy);
3969 return Tile[oldx][oldy];
3976 static void RemoveField(int x, int y)
3978 Tile[x][y] = EL_EMPTY;
3985 static void RemoveMovingField_MM(int x, int y)
3987 int oldx = x, oldy = y, newx = x, newy = y;
3989 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3992 if (IS_MOVING(x, y))
3994 Moving2Blocked_MM(x, y, &newx, &newy);
3995 if (Tile[newx][newy] != EL_BLOCKED)
3998 else if (Tile[x][y] == EL_BLOCKED)
4000 Blocked2Moving_MM(x, y, &oldx, &oldy);
4001 if (!IS_MOVING(oldx, oldy))
4005 Tile[oldx][oldy] = EL_EMPTY;
4006 Tile[newx][newy] = EL_EMPTY;
4007 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4008 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4010 DrawLevelField_MM(oldx, oldy);
4011 DrawLevelField_MM(newx, newy);
4014 void PlaySoundLevel(int x, int y, int sound_nr)
4016 int sx = SCREENX(x), sy = SCREENY(y);
4018 int silence_distance = 8;
4020 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4021 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4024 if (!IN_LEV_FIELD(x, y) ||
4025 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4026 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4029 volume = SOUND_MAX_VOLUME;
4032 stereo = (sx - SCR_FIELDX/2) * 12;
4034 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4035 if (stereo > SOUND_MAX_RIGHT)
4036 stereo = SOUND_MAX_RIGHT;
4037 if (stereo < SOUND_MAX_LEFT)
4038 stereo = SOUND_MAX_LEFT;
4041 if (!IN_SCR_FIELD(sx, sy))
4043 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4044 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4046 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4049 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4052 static void RaiseScore_MM(int value)
4054 game_mm.score += value;
4057 void RaiseScoreElement_MM(int element)
4062 case EL_PACMAN_RIGHT:
4064 case EL_PACMAN_LEFT:
4065 case EL_PACMAN_DOWN:
4066 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4070 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4075 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4079 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4088 // ----------------------------------------------------------------------------
4089 // Mirror Magic game engine snapshot handling functions
4090 // ----------------------------------------------------------------------------
4092 void SaveEngineSnapshotValues_MM(void)
4096 engine_snapshot_mm.game_mm = game_mm;
4097 engine_snapshot_mm.laser = laser;
4099 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4101 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4103 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4104 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4105 engine_snapshot_mm.Box[x][y] = Box[x][y];
4106 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4110 engine_snapshot_mm.LX = LX;
4111 engine_snapshot_mm.LY = LY;
4112 engine_snapshot_mm.XS = XS;
4113 engine_snapshot_mm.YS = YS;
4114 engine_snapshot_mm.ELX = ELX;
4115 engine_snapshot_mm.ELY = ELY;
4116 engine_snapshot_mm.CT = CT;
4117 engine_snapshot_mm.Ct = Ct;
4119 engine_snapshot_mm.last_LX = last_LX;
4120 engine_snapshot_mm.last_LY = last_LY;
4121 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4122 engine_snapshot_mm.hold_x = hold_x;
4123 engine_snapshot_mm.hold_y = hold_y;
4124 engine_snapshot_mm.pacman_nr = pacman_nr;
4126 engine_snapshot_mm.rotate_delay = rotate_delay;
4127 engine_snapshot_mm.pacman_delay = pacman_delay;
4128 engine_snapshot_mm.energy_delay = energy_delay;
4129 engine_snapshot_mm.overload_delay = overload_delay;
4132 void LoadEngineSnapshotValues_MM(void)
4136 // stored engine snapshot buffers already restored at this point
4138 game_mm = engine_snapshot_mm.game_mm;
4139 laser = engine_snapshot_mm.laser;
4141 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4143 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4145 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4146 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4147 Box[x][y] = engine_snapshot_mm.Box[x][y];
4148 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4152 LX = engine_snapshot_mm.LX;
4153 LY = engine_snapshot_mm.LY;
4154 XS = engine_snapshot_mm.XS;
4155 YS = engine_snapshot_mm.YS;
4156 ELX = engine_snapshot_mm.ELX;
4157 ELY = engine_snapshot_mm.ELY;
4158 CT = engine_snapshot_mm.CT;
4159 Ct = engine_snapshot_mm.Ct;
4161 last_LX = engine_snapshot_mm.last_LX;
4162 last_LY = engine_snapshot_mm.last_LY;
4163 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4164 hold_x = engine_snapshot_mm.hold_x;
4165 hold_y = engine_snapshot_mm.hold_y;
4166 pacman_nr = engine_snapshot_mm.pacman_nr;
4168 rotate_delay = engine_snapshot_mm.rotate_delay;
4169 pacman_delay = engine_snapshot_mm.pacman_delay;
4170 energy_delay = engine_snapshot_mm.energy_delay;
4171 overload_delay = engine_snapshot_mm.overload_delay;
4173 RedrawPlayfield_MM();
4176 static int getAngleFromTouchDelta(int dx, int dy, int base)
4178 double pi = 3.141592653;
4179 double rad = atan2((double)-dy, (double)dx);
4180 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4181 double deg = rad2 * 180.0 / pi;
4183 return (int)(deg * base / 360.0 + 0.5) % base;
4186 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4188 // calculate start (source) position to be at the middle of the tile
4189 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4190 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4191 int dx = dst_mx - src_mx;
4192 int dy = dst_my - src_my;
4201 if (!IN_LEV_FIELD(x, y))
4204 element = Tile[x][y];
4206 if (!IS_MCDUFFIN(element) &&
4207 !IS_MIRROR(element) &&
4208 !IS_BEAMER(element) &&
4209 !IS_POLAR(element) &&
4210 !IS_POLAR_CROSS(element) &&
4211 !IS_DF_MIRROR(element))
4214 angle_old = get_element_angle(element);
4216 if (IS_MCDUFFIN(element))
4218 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4219 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4220 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4221 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4224 else if (IS_MIRROR(element) ||
4225 IS_DF_MIRROR(element))
4227 for (i = 0; i < laser.num_damages; i++)
4229 if (laser.damage[i].x == x &&
4230 laser.damage[i].y == y &&
4231 ObjHit(x, y, HIT_POS_CENTER))
4233 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4234 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4241 if (angle_new == -1)
4243 if (IS_MIRROR(element) ||
4244 IS_DF_MIRROR(element) ||
4248 if (IS_POLAR_CROSS(element))
4251 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4254 button = (angle_new == angle_old ? 0 :
4255 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4256 MB_LEFTBUTTON : MB_RIGHTBUTTON);