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)
3065 if (IS_BEAMER(Tile[x][y]))
3068 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3069 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3081 DrawLaser(0, DL_LASER_ENABLED);
3085 static void AutoRotateMirrors(void)
3089 if (!FrameReached(&rotate_delay))
3092 for (x = 0; x < lev_fieldx; x++)
3094 for (y = 0; y < lev_fieldy; y++)
3096 int element = Tile[x][y];
3098 // do not rotate objects hit by the laser after the game was solved
3099 if (game_mm.level_solved && Hit[x][y])
3102 if (IS_DF_MIRROR_AUTO(element) ||
3103 IS_GRID_WOOD_AUTO(element) ||
3104 IS_GRID_STEEL_AUTO(element) ||
3105 element == EL_REFRACTOR)
3106 RotateMirror(x, y, MB_RIGHTBUTTON);
3111 boolean ObjHit(int obx, int oby, int bits)
3118 if (bits & HIT_POS_CENTER)
3120 if (CheckLaserPixel(cSX + obx + 15,
3125 if (bits & HIT_POS_EDGE)
3127 for (i = 0; i < 4; i++)
3128 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3129 cSY + oby + 31 * (i / 2)))
3133 if (bits & HIT_POS_BETWEEN)
3135 for (i = 0; i < 4; i++)
3136 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3137 cSY + 4 + oby + 22 * (i / 2)))
3144 void DeletePacMan(int px, int py)
3150 if (game_mm.num_pacman <= 1)
3152 game_mm.num_pacman = 0;
3156 for (i = 0; i < game_mm.num_pacman; i++)
3157 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3160 game_mm.num_pacman--;
3162 for (j = i; j < game_mm.num_pacman; j++)
3164 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3165 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3166 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3170 void ColorCycling(void)
3172 static int CC, Cc = 0;
3174 static int color, old = 0xF00, new = 0x010, mult = 1;
3175 static unsigned short red, green, blue;
3177 if (color_status == STATIC_COLORS)
3182 if (CC < Cc || CC > Cc + 2)
3186 color = old + new * mult;
3192 if (ABS(mult) == 16)
3202 red = 0x0e00 * ((color & 0xF00) >> 8);
3203 green = 0x0e00 * ((color & 0x0F0) >> 4);
3204 blue = 0x0e00 * ((color & 0x00F));
3205 SetRGB(pen_magicolor[0], red, green, blue);
3207 red = 0x1111 * ((color & 0xF00) >> 8);
3208 green = 0x1111 * ((color & 0x0F0) >> 4);
3209 blue = 0x1111 * ((color & 0x00F));
3210 SetRGB(pen_magicolor[1], red, green, blue);
3214 static void GameActions_MM_Ext(void)
3221 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3224 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3226 element = Tile[x][y];
3228 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3229 StartMoving_MM(x, y);
3230 else if (IS_MOVING(x, y))
3231 ContinueMoving_MM(x, y);
3232 else if (IS_EXPLODING(element))
3233 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3234 else if (element == EL_EXIT_OPENING)
3236 else if (element == EL_GRAY_BALL_OPENING)
3237 OpenSurpriseBall(x, y);
3238 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3240 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3242 else if (IS_MIRROR(element) ||
3243 IS_MIRROR_FIXED(element) ||
3244 element == EL_PRISM)
3245 DrawFieldTwinkle(x, y);
3246 else if (element == EL_GRAY_BALL_OPENING ||
3247 element == EL_BOMB_ACTIVE ||
3248 element == EL_MINE_ACTIVE)
3249 DrawFieldAnimated_MM(x, y);
3250 else if (!IS_BLOCKED(x, y))
3251 DrawFieldAnimatedIfNeeded_MM(x, y);
3254 AutoRotateMirrors();
3257 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3259 // redraw after Explode_MM() ...
3261 DrawLaser(0, DL_LASER_ENABLED);
3262 laser.redraw = FALSE;
3267 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3271 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3273 DrawLaser(0, DL_LASER_DISABLED);
3278 // skip all following game actions if game is over
3279 if (game_mm.game_over)
3282 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3286 GameOver_MM(GAME_OVER_NO_ENERGY);
3291 if (FrameReached(&energy_delay))
3293 if (game_mm.energy_left > 0)
3294 game_mm.energy_left--;
3296 // when out of energy, wait another frame to play "out of time" sound
3299 element = laser.dest_element;
3302 if (element != Tile[ELX][ELY])
3304 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3305 element, Tile[ELX][ELY]);
3309 if (!laser.overloaded && laser.overload_value == 0 &&
3310 element != EL_BOMB &&
3311 element != EL_BOMB_ACTIVE &&
3312 element != EL_MINE &&
3313 element != EL_MINE_ACTIVE &&
3314 element != EL_BALL_GRAY &&
3315 element != EL_BLOCK_STONE &&
3316 element != EL_BLOCK_WOOD &&
3317 element != EL_FUSE_ON &&
3318 element != EL_FUEL_FULL &&
3319 !IS_WALL_ICE(element) &&
3320 !IS_WALL_AMOEBA(element))
3323 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3325 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3326 (!laser.overloaded && laser.overload_value > 0)) &&
3327 FrameReached(&overload_delay))
3329 if (laser.overloaded)
3330 laser.overload_value++;
3332 laser.overload_value--;
3334 if (game_mm.cheat_no_overload)
3336 laser.overloaded = FALSE;
3337 laser.overload_value = 0;
3340 game_mm.laser_overload_value = laser.overload_value;
3342 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3344 SetLaserColor(0xFF);
3346 DrawLaser(0, DL_LASER_ENABLED);
3349 if (!laser.overloaded)
3350 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3351 else if (setup.sound_loops)
3352 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3354 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3356 if (laser.overloaded)
3359 BlitBitmap(pix[PIX_DOOR], drawto,
3360 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3361 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3362 - laser.overload_value,
3363 OVERLOAD_XSIZE, laser.overload_value,
3364 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3365 - laser.overload_value);
3367 redraw_mask |= REDRAW_DOOR_1;
3372 BlitBitmap(pix[PIX_DOOR], drawto,
3373 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3374 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3375 DX_OVERLOAD, DY_OVERLOAD);
3377 redraw_mask |= REDRAW_DOOR_1;
3380 if (laser.overload_value == MAX_LASER_OVERLOAD)
3382 UpdateAndDisplayGameControlValues();
3386 GameOver_MM(GAME_OVER_OVERLOADED);
3397 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3399 if (game_mm.cheat_no_explosion)
3404 laser.dest_element = EL_EXPLODING_OPAQUE;
3409 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3411 laser.fuse_off = TRUE;
3415 DrawLaser(0, DL_LASER_DISABLED);
3416 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3419 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3421 if (!Store2[ELX][ELY]) // check if content element not yet determined
3423 int last_anim_random_frame = gfx.anim_random_frame;
3426 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3427 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3429 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3430 native_mm_level.ball_choice_mode, 0,
3431 game_mm.ball_choice_pos);
3433 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3434 gfx.anim_random_frame = last_anim_random_frame;
3436 game_mm.ball_choice_pos++;
3438 int new_element = native_mm_level.ball_content[element_pos];
3440 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3441 Store2[ELX][ELY] = TRUE;
3444 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3446 // !!! CHECK AGAIN: Laser on Polarizer !!!
3449 laser.dest_element_last = Tile[ELX][ELY];
3450 laser.dest_element_last_x = ELX;
3451 laser.dest_element_last_y = ELY;
3461 element = EL_MIRROR_START + RND(16);
3467 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3474 element = (rnd == 0 ? EL_FUSE_ON :
3475 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3476 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3477 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3478 EL_MIRROR_FIXED_START + rnd - 25);
3483 graphic = el2gfx(element);
3485 for (i = 0; i < 50; i++)
3491 BlitBitmap(pix[PIX_BACK], drawto,
3492 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3493 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3494 SX + ELX * TILEX + x,
3495 SY + ELY * TILEY + y);
3497 MarkTileDirty(ELX, ELY);
3500 DrawLaser(0, DL_LASER_ENABLED);
3502 Delay_WithScreenUpdates(50);
3505 Tile[ELX][ELY] = element;
3506 DrawField_MM(ELX, ELY);
3509 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3512 // above stuff: GRAY BALL -> PRISM !!!
3514 LX = ELX * TILEX + 14;
3515 LY = ELY * TILEY + 14;
3516 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3523 laser.num_edges -= 2;
3524 laser.num_damages--;
3528 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3529 if (laser.damage[i].is_mirror)
3533 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3535 DrawLaser(0, DL_LASER_DISABLED);
3537 DrawLaser(0, DL_LASER_DISABLED);
3546 if (IS_WALL_ICE(element) && CT > 50)
3548 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3551 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3552 Store[ELX][ELY] = EL_WALL_ICE;
3553 Store2[ELX][ELY] = laser.wall_mask;
3555 laser.dest_element = Tile[ELX][ELY];
3560 for (i = 0; i < 5; i++)
3566 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3570 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3572 Delay_WithScreenUpdates(100);
3575 if (Tile[ELX][ELY] == EL_WALL_ICE)
3576 Tile[ELX][ELY] = EL_EMPTY;
3580 LX = laser.edge[laser.num_edges].x - cSX2;
3581 LY = laser.edge[laser.num_edges].y - cSY2;
3584 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3585 if (laser.damage[i].is_mirror)
3589 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3591 DrawLaser(0, DL_LASER_DISABLED);
3598 if (IS_WALL_AMOEBA(element) && CT > 60)
3600 int k1, k2, k3, dx, dy, de, dm;
3601 int element2 = Tile[ELX][ELY];
3603 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3606 for (i = laser.num_damages - 1; i >= 0; i--)
3607 if (laser.damage[i].is_mirror)
3610 r = laser.num_edges;
3611 d = laser.num_damages;
3618 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3621 DrawLaser(0, DL_LASER_ENABLED);
3624 x = laser.damage[k1].x;
3625 y = laser.damage[k1].y;
3630 for (i = 0; i < 4; i++)
3632 if (laser.wall_mask & (1 << i))
3634 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3635 cSY + ELY * TILEY + 31 * (i / 2)))
3638 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3639 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3646 for (i = 0; i < 4; i++)
3648 if (laser.wall_mask & (1 << i))
3650 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3651 cSY + ELY * TILEY + 31 * (i / 2)))
3658 if (laser.num_beamers > 0 ||
3659 k1 < 1 || k2 < 4 || k3 < 4 ||
3660 CheckLaserPixel(cSX + ELX * TILEX + 14,
3661 cSY + ELY * TILEY + 14))
3663 laser.num_edges = r;
3664 laser.num_damages = d;
3666 DrawLaser(0, DL_LASER_DISABLED);
3669 Tile[ELX][ELY] = element | laser.wall_mask;
3673 de = Tile[ELX][ELY];
3674 dm = laser.wall_mask;
3678 int x = ELX, y = ELY;
3679 int wall_mask = laser.wall_mask;
3682 DrawLaser(0, DL_LASER_ENABLED);
3684 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3686 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3687 Store[x][y] = EL_WALL_AMOEBA;
3688 Store2[x][y] = wall_mask;
3694 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3696 DrawLaser(0, DL_LASER_ENABLED);
3698 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3700 for (i = 4; i >= 0; i--)
3702 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3705 Delay_WithScreenUpdates(20);
3708 DrawLaser(0, DL_LASER_ENABLED);
3713 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3714 laser.stops_inside_element && CT > native_mm_level.time_block)
3719 if (ABS(XS) > ABS(YS))
3726 for (i = 0; i < 4; i++)
3733 x = ELX + Step[k * 4].x;
3734 y = ELY + Step[k * 4].y;
3736 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3739 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3747 laser.overloaded = (element == EL_BLOCK_STONE);
3752 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3755 Tile[x][y] = element;
3757 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3760 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3762 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3763 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3771 if (element == EL_FUEL_FULL && CT > 10)
3773 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3774 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3776 for (i = start; i <= num_init_game_frames; i++)
3778 if (i == num_init_game_frames)
3779 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3780 else if (setup.sound_loops)
3781 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3783 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3785 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3787 UpdateAndDisplayGameControlValues();
3792 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3794 DrawField_MM(ELX, ELY);
3796 DrawLaser(0, DL_LASER_ENABLED);
3804 void GameActions_MM(struct MouseActionInfo action)
3806 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3807 boolean button_released = (action.button == MB_RELEASED);
3809 GameActions_MM_Ext();
3811 CheckSingleStepMode_MM(element_clicked, button_released);
3814 void MovePacMen(void)
3816 int mx, my, ox, oy, nx, ny;
3820 if (++pacman_nr >= game_mm.num_pacman)
3823 game_mm.pacman[pacman_nr].dir--;
3825 for (l = 1; l < 5; l++)
3827 game_mm.pacman[pacman_nr].dir++;
3829 if (game_mm.pacman[pacman_nr].dir > 4)
3830 game_mm.pacman[pacman_nr].dir = 1;
3832 if (game_mm.pacman[pacman_nr].dir % 2)
3835 my = game_mm.pacman[pacman_nr].dir - 2;
3840 mx = 3 - game_mm.pacman[pacman_nr].dir;
3843 ox = game_mm.pacman[pacman_nr].x;
3844 oy = game_mm.pacman[pacman_nr].y;
3847 element = Tile[nx][ny];
3849 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3852 if (!IS_EATABLE4PACMAN(element))
3855 if (ObjHit(nx, ny, HIT_POS_CENTER))
3858 Tile[ox][oy] = EL_EMPTY;
3860 EL_PACMAN_RIGHT - 1 +
3861 (game_mm.pacman[pacman_nr].dir - 1 +
3862 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3864 game_mm.pacman[pacman_nr].x = nx;
3865 game_mm.pacman[pacman_nr].y = ny;
3867 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3869 if (element != EL_EMPTY)
3871 int graphic = el2gfx(Tile[nx][ny]);
3876 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3879 ox = cSX + ox * TILEX;
3880 oy = cSY + oy * TILEY;
3882 for (i = 1; i < 33; i += 2)
3883 BlitBitmap(bitmap, window,
3884 src_x, src_y, TILEX, TILEY,
3885 ox + i * mx, oy + i * my);
3886 Ct = Ct + FrameCounter - CT;
3889 DrawField_MM(nx, ny);
3892 if (!laser.fuse_off)
3894 DrawLaser(0, DL_LASER_ENABLED);
3896 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3898 AddDamagedField(nx, ny);
3900 laser.damage[laser.num_damages - 1].edge = 0;
3904 if (element == EL_BOMB)
3905 DeletePacMan(nx, ny);
3907 if (IS_WALL_AMOEBA(element) &&
3908 (LX + 2 * XS) / TILEX == nx &&
3909 (LY + 2 * YS) / TILEY == ny)
3919 static void InitMovingField_MM(int x, int y, int direction)
3921 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3922 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3924 MovDir[x][y] = direction;
3925 MovDir[newx][newy] = direction;
3927 if (Tile[newx][newy] == EL_EMPTY)
3928 Tile[newx][newy] = EL_BLOCKED;
3931 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3933 int direction = MovDir[x][y];
3934 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3935 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3941 static void Blocked2Moving_MM(int x, int y,
3942 int *comes_from_x, int *comes_from_y)
3944 int oldx = x, oldy = y;
3945 int direction = MovDir[x][y];
3947 if (direction == MV_LEFT)
3949 else if (direction == MV_RIGHT)
3951 else if (direction == MV_UP)
3953 else if (direction == MV_DOWN)
3956 *comes_from_x = oldx;
3957 *comes_from_y = oldy;
3960 static int MovingOrBlocked2Element_MM(int x, int y)
3962 int element = Tile[x][y];
3964 if (element == EL_BLOCKED)
3968 Blocked2Moving_MM(x, y, &oldx, &oldy);
3970 return Tile[oldx][oldy];
3977 static void RemoveField(int x, int y)
3979 Tile[x][y] = EL_EMPTY;
3986 static void RemoveMovingField_MM(int x, int y)
3988 int oldx = x, oldy = y, newx = x, newy = y;
3990 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3993 if (IS_MOVING(x, y))
3995 Moving2Blocked_MM(x, y, &newx, &newy);
3996 if (Tile[newx][newy] != EL_BLOCKED)
3999 else if (Tile[x][y] == EL_BLOCKED)
4001 Blocked2Moving_MM(x, y, &oldx, &oldy);
4002 if (!IS_MOVING(oldx, oldy))
4006 Tile[oldx][oldy] = EL_EMPTY;
4007 Tile[newx][newy] = EL_EMPTY;
4008 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4009 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4011 DrawLevelField_MM(oldx, oldy);
4012 DrawLevelField_MM(newx, newy);
4015 void PlaySoundLevel(int x, int y, int sound_nr)
4017 int sx = SCREENX(x), sy = SCREENY(y);
4019 int silence_distance = 8;
4021 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4022 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4025 if (!IN_LEV_FIELD(x, y) ||
4026 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4027 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4030 volume = SOUND_MAX_VOLUME;
4033 stereo = (sx - SCR_FIELDX/2) * 12;
4035 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4036 if (stereo > SOUND_MAX_RIGHT)
4037 stereo = SOUND_MAX_RIGHT;
4038 if (stereo < SOUND_MAX_LEFT)
4039 stereo = SOUND_MAX_LEFT;
4042 if (!IN_SCR_FIELD(sx, sy))
4044 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4045 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4047 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4050 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4053 static void RaiseScore_MM(int value)
4055 game_mm.score += value;
4058 void RaiseScoreElement_MM(int element)
4063 case EL_PACMAN_RIGHT:
4065 case EL_PACMAN_LEFT:
4066 case EL_PACMAN_DOWN:
4067 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4071 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4076 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4080 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4089 // ----------------------------------------------------------------------------
4090 // Mirror Magic game engine snapshot handling functions
4091 // ----------------------------------------------------------------------------
4093 void SaveEngineSnapshotValues_MM(void)
4097 engine_snapshot_mm.game_mm = game_mm;
4098 engine_snapshot_mm.laser = laser;
4100 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4102 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4104 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4105 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4106 engine_snapshot_mm.Box[x][y] = Box[x][y];
4107 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4111 engine_snapshot_mm.LX = LX;
4112 engine_snapshot_mm.LY = LY;
4113 engine_snapshot_mm.XS = XS;
4114 engine_snapshot_mm.YS = YS;
4115 engine_snapshot_mm.ELX = ELX;
4116 engine_snapshot_mm.ELY = ELY;
4117 engine_snapshot_mm.CT = CT;
4118 engine_snapshot_mm.Ct = Ct;
4120 engine_snapshot_mm.last_LX = last_LX;
4121 engine_snapshot_mm.last_LY = last_LY;
4122 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4123 engine_snapshot_mm.hold_x = hold_x;
4124 engine_snapshot_mm.hold_y = hold_y;
4125 engine_snapshot_mm.pacman_nr = pacman_nr;
4127 engine_snapshot_mm.rotate_delay = rotate_delay;
4128 engine_snapshot_mm.pacman_delay = pacman_delay;
4129 engine_snapshot_mm.energy_delay = energy_delay;
4130 engine_snapshot_mm.overload_delay = overload_delay;
4133 void LoadEngineSnapshotValues_MM(void)
4137 // stored engine snapshot buffers already restored at this point
4139 game_mm = engine_snapshot_mm.game_mm;
4140 laser = engine_snapshot_mm.laser;
4142 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4144 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4146 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4147 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4148 Box[x][y] = engine_snapshot_mm.Box[x][y];
4149 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4153 LX = engine_snapshot_mm.LX;
4154 LY = engine_snapshot_mm.LY;
4155 XS = engine_snapshot_mm.XS;
4156 YS = engine_snapshot_mm.YS;
4157 ELX = engine_snapshot_mm.ELX;
4158 ELY = engine_snapshot_mm.ELY;
4159 CT = engine_snapshot_mm.CT;
4160 Ct = engine_snapshot_mm.Ct;
4162 last_LX = engine_snapshot_mm.last_LX;
4163 last_LY = engine_snapshot_mm.last_LY;
4164 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4165 hold_x = engine_snapshot_mm.hold_x;
4166 hold_y = engine_snapshot_mm.hold_y;
4167 pacman_nr = engine_snapshot_mm.pacman_nr;
4169 rotate_delay = engine_snapshot_mm.rotate_delay;
4170 pacman_delay = engine_snapshot_mm.pacman_delay;
4171 energy_delay = engine_snapshot_mm.energy_delay;
4172 overload_delay = engine_snapshot_mm.overload_delay;
4174 RedrawPlayfield_MM();
4177 static int getAngleFromTouchDelta(int dx, int dy, int base)
4179 double pi = 3.141592653;
4180 double rad = atan2((double)-dy, (double)dx);
4181 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4182 double deg = rad2 * 180.0 / pi;
4184 return (int)(deg * base / 360.0 + 0.5) % base;
4187 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4189 // calculate start (source) position to be at the middle of the tile
4190 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4191 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4192 int dx = dst_mx - src_mx;
4193 int dy = dst_my - src_my;
4202 if (!IN_LEV_FIELD(x, y))
4205 element = Tile[x][y];
4207 if (!IS_MCDUFFIN(element) &&
4208 !IS_MIRROR(element) &&
4209 !IS_BEAMER(element) &&
4210 !IS_POLAR(element) &&
4211 !IS_POLAR_CROSS(element) &&
4212 !IS_DF_MIRROR(element))
4215 angle_old = get_element_angle(element);
4217 if (IS_MCDUFFIN(element))
4219 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4220 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4221 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4222 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4225 else if (IS_MIRROR(element) ||
4226 IS_DF_MIRROR(element))
4228 for (i = 0; i < laser.num_damages; i++)
4230 if (laser.damage[i].x == x &&
4231 laser.damage[i].y == y &&
4232 ObjHit(x, y, HIT_POS_CENTER))
4234 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4235 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4242 if (angle_new == -1)
4244 if (IS_MIRROR(element) ||
4245 IS_DF_MIRROR(element) ||
4249 if (IS_POLAR_CROSS(element))
4252 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4255 button = (angle_new == angle_old ? 0 :
4256 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4257 MB_LEFTBUTTON : MB_RIGHTBUTTON);