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.level_solved = FALSE;
648 game_mm.game_over = FALSE;
649 game_mm.game_over_cause = 0;
651 game_mm.laser_overload_value = 0;
652 game_mm.laser_enabled = FALSE;
654 // set global laser control values (must be set before "InitLaser()")
655 laser.start_edge.x = 0;
656 laser.start_edge.y = 0;
657 laser.start_angle = 0;
659 for (i = 0; i < MAX_NUM_BEAMERS; i++)
660 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
662 laser.overloaded = FALSE;
663 laser.overload_value = 0;
664 laser.fuse_off = FALSE;
665 laser.fuse_x = laser.fuse_y = -1;
667 laser.dest_element = EL_EMPTY;
668 laser.dest_element_last = EL_EMPTY;
669 laser.dest_element_last_x = -1;
670 laser.dest_element_last_y = -1;
684 rotate_delay.count = 0;
685 pacman_delay.count = 0;
686 energy_delay.count = 0;
687 overload_delay.count = 0;
689 ClickElement(-1, -1, -1);
691 for (x = 0; x < lev_fieldx; x++)
693 for (y = 0; y < lev_fieldy; y++)
695 Tile[x][y] = Ur[x][y];
696 Hit[x][y] = Box[x][y] = 0;
698 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
699 Store[x][y] = Store2[x][y] = 0;
709 void InitGameActions_MM(void)
711 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
712 int cycle_steps_done = 0;
717 for (i = 0; i <= num_init_game_frames; i++)
719 if (i == num_init_game_frames)
720 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
721 else if (setup.sound_loops)
722 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
724 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
726 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
728 UpdateAndDisplayGameControlValues();
730 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
732 InitCycleElements_RotateSingleStep();
737 AdvanceFrameCounter();
747 if (setup.quick_doors)
754 if (game_mm.kettles_still_needed == 0)
757 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
758 SetTileCursorActive(TRUE);
760 ResetFrameCounter(&energy_delay);
763 static void FadeOutLaser(void)
767 for (i = 15; i >= 0; i--)
769 SetLaserColor(0x11 * i);
771 DrawLaser(0, DL_LASER_ENABLED);
774 Delay_WithScreenUpdates(50);
777 DrawLaser(0, DL_LASER_DISABLED);
779 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
782 static void GameOver_MM(int game_over_cause)
784 // do not handle game over if request dialog is already active
785 if (game.request_active)
788 game_mm.game_over = TRUE;
789 game_mm.game_over_cause = game_over_cause;
791 if (setup.ask_on_game_over)
792 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
793 "Bomb killed Mc Duffin! Play it again?" :
794 game_over_cause == GAME_OVER_NO_ENERGY ?
795 "Out of magic energy! Play it again?" :
796 game_over_cause == GAME_OVER_OVERLOADED ?
797 "Magic spell hit Mc Duffin! Play it again?" :
800 SetTileCursorActive(FALSE);
803 void AddLaserEdge(int lx, int ly)
808 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
810 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
815 laser.edge[laser.num_edges].x = cSX2 + lx;
816 laser.edge[laser.num_edges].y = cSY2 + ly;
822 void AddDamagedField(int ex, int ey)
824 laser.damage[laser.num_damages].is_mirror = FALSE;
825 laser.damage[laser.num_damages].angle = laser.current_angle;
826 laser.damage[laser.num_damages].edge = laser.num_edges;
827 laser.damage[laser.num_damages].x = ex;
828 laser.damage[laser.num_damages].y = ey;
832 static boolean StepBehind(void)
838 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
839 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
841 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
847 static int getMaskFromElement(int element)
849 if (IS_GRID(element))
850 return MM_MASK_GRID_1 + get_element_phase(element);
851 else if (IS_MCDUFFIN(element))
852 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
853 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
854 return MM_MASK_RECTANGLE;
856 return MM_MASK_CIRCLE;
859 static int ScanPixel(void)
864 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
865 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
868 // follow laser beam until it hits something (at least the screen border)
869 while (hit_mask == HIT_MASK_NO_HIT)
875 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
876 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
878 Debug("game:mm:ScanPixel", "touched screen border!");
884 for (i = 0; i < 4; i++)
886 int px = LX + (i % 2) * 2;
887 int py = LY + (i / 2) * 2;
890 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
891 int ly = (py + TILEY) / TILEY - 1; // negative values!
894 if (IN_LEV_FIELD(lx, ly))
896 int element = Tile[lx][ly];
898 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
902 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
904 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
906 pixel = ((element & (1 << pos)) ? 1 : 0);
910 int pos = getMaskFromElement(element);
912 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
917 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
918 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
921 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
922 hit_mask |= (1 << i);
925 if (hit_mask == HIT_MASK_NO_HIT)
927 // hit nothing -- go on with another step
936 static void DeactivateLaserTargetElement(void)
938 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
939 laser.dest_element_last == EL_MINE_ACTIVE ||
940 laser.dest_element_last == EL_GRAY_BALL_OPENING)
942 int x = laser.dest_element_last_x;
943 int y = laser.dest_element_last_y;
944 int element = laser.dest_element_last;
946 if (Tile[x][y] == element)
947 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
948 element == EL_MINE_ACTIVE ? EL_MINE : EL_BALL_GRAY);
950 if (Tile[x][y] == EL_BALL_GRAY)
953 laser.dest_element_last = EL_EMPTY;
954 laser.dest_element_last_x = -1;
955 laser.dest_element_last_y = -1;
962 int end = 0, rf = laser.num_edges;
964 // do not scan laser again after the game was lost for whatever reason
965 if (game_mm.game_over)
968 // do not scan laser if fuse is off
972 DeactivateLaserTargetElement();
974 laser.overloaded = FALSE;
975 laser.stops_inside_element = FALSE;
977 DrawLaser(0, DL_LASER_ENABLED);
980 Debug("game:mm:ScanLaser",
981 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
989 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
992 laser.overloaded = TRUE;
997 hit_mask = ScanPixel();
1000 Debug("game:mm:ScanLaser",
1001 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1005 // hit something -- check out what it was
1006 ELX = (LX + XS) / TILEX;
1007 ELY = (LY + YS) / TILEY;
1010 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1011 hit_mask, LX, LY, ELX, ELY);
1014 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1017 laser.dest_element = element;
1022 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1024 /* we have hit the top-right and bottom-left element --
1025 choose the bottom-left one */
1026 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1027 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1028 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1029 ELX = (LX - 2) / TILEX;
1030 ELY = (LY + 2) / TILEY;
1033 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1035 /* we have hit the top-left and bottom-right element --
1036 choose the top-left one */
1037 // !!! SEE ABOVE !!!
1038 ELX = (LX - 2) / TILEX;
1039 ELY = (LY - 2) / TILEY;
1043 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1044 hit_mask, LX, LY, ELX, ELY);
1047 element = Tile[ELX][ELY];
1048 laser.dest_element = element;
1051 Debug("game:mm:ScanLaser",
1052 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1055 LX % TILEX, LY % TILEY,
1060 if (!IN_LEV_FIELD(ELX, ELY))
1061 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1065 if (element == EL_EMPTY)
1067 if (!HitOnlyAnEdge(hit_mask))
1070 else if (element == EL_FUSE_ON)
1072 if (HitPolarizer(element, hit_mask))
1075 else if (IS_GRID(element) || IS_DF_GRID(element))
1077 if (HitPolarizer(element, hit_mask))
1080 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1081 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1083 if (HitBlock(element, hit_mask))
1090 else if (IS_MCDUFFIN(element))
1092 if (HitLaserSource(element, hit_mask))
1095 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1096 IS_RECEIVER(element))
1098 if (HitLaserDestination(element, hit_mask))
1101 else if (IS_WALL(element))
1103 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1105 if (HitReflectingWalls(element, hit_mask))
1110 if (HitAbsorbingWalls(element, hit_mask))
1116 if (HitElement(element, hit_mask))
1121 DrawLaser(rf - 1, DL_LASER_ENABLED);
1122 rf = laser.num_edges;
1126 if (laser.dest_element != Tile[ELX][ELY])
1128 Debug("game:mm:ScanLaser",
1129 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1130 laser.dest_element, Tile[ELX][ELY]);
1134 if (!end && !laser.stops_inside_element && !StepBehind())
1137 Debug("game:mm:ScanLaser", "Go one step back");
1143 AddLaserEdge(LX, LY);
1147 DrawLaser(rf - 1, DL_LASER_ENABLED);
1149 Ct = CT = FrameCounter;
1152 if (!IN_LEV_FIELD(ELX, ELY))
1153 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1157 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1163 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1164 start_edge, num_edges, mode);
1169 Warn("DrawLaserExt: start_edge < 0");
1176 Warn("DrawLaserExt: num_edges < 0");
1182 if (mode == DL_LASER_DISABLED)
1184 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1188 // now draw the laser to the backbuffer and (if enabled) to the screen
1189 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1191 redraw_mask |= REDRAW_FIELD;
1193 if (mode == DL_LASER_ENABLED)
1196 // after the laser was deleted, the "damaged" graphics must be restored
1197 if (laser.num_damages)
1199 int damage_start = 0;
1202 // determine the starting edge, from which graphics need to be restored
1205 for (i = 0; i < laser.num_damages; i++)
1207 if (laser.damage[i].edge == start_edge + 1)
1216 // restore graphics from this starting edge to the end of damage list
1217 for (i = damage_start; i < laser.num_damages; i++)
1219 int lx = laser.damage[i].x;
1220 int ly = laser.damage[i].y;
1221 int element = Tile[lx][ly];
1223 if (Hit[lx][ly] == laser.damage[i].edge)
1224 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1227 if (Box[lx][ly] == laser.damage[i].edge)
1230 if (IS_DRAWABLE(element))
1231 DrawField_MM(lx, ly);
1234 elx = laser.damage[damage_start].x;
1235 ely = laser.damage[damage_start].y;
1236 element = Tile[elx][ely];
1239 if (IS_BEAMER(element))
1243 for (i = 0; i < laser.num_beamers; i++)
1244 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1246 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1247 mode, elx, ely, Hit[elx][ely], start_edge);
1248 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1249 get_element_angle(element), laser.damage[damage_start].angle);
1253 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1254 laser.num_beamers > 0 &&
1255 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1257 // element is outgoing beamer
1258 laser.num_damages = damage_start + 1;
1260 if (IS_BEAMER(element))
1261 laser.current_angle = get_element_angle(element);
1265 // element is incoming beamer or other element
1266 laser.num_damages = damage_start;
1267 laser.current_angle = laser.damage[laser.num_damages].angle;
1272 // no damages but McDuffin himself (who needs to be redrawn anyway)
1274 elx = laser.start_edge.x;
1275 ely = laser.start_edge.y;
1276 element = Tile[elx][ely];
1279 laser.num_edges = start_edge + 1;
1280 if (start_edge == 0)
1281 laser.current_angle = laser.start_angle;
1283 LX = laser.edge[start_edge].x - cSX2;
1284 LY = laser.edge[start_edge].y - cSY2;
1285 XS = 2 * Step[laser.current_angle].x;
1286 YS = 2 * Step[laser.current_angle].y;
1289 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1295 if (IS_BEAMER(element) ||
1296 IS_FIBRE_OPTIC(element) ||
1297 IS_PACMAN(element) ||
1298 IS_POLAR(element) ||
1299 IS_POLAR_CROSS(element) ||
1300 element == EL_FUSE_ON)
1305 Debug("game:mm:DrawLaserExt", "element == %d", element);
1308 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1309 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1313 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1314 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1315 (laser.num_beamers == 0 ||
1316 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1318 // element is incoming beamer or other element
1319 step_size = -step_size;
1324 if (IS_BEAMER(element))
1325 Debug("game:mm:DrawLaserExt",
1326 "start_edge == %d, laser.beamer_edge == %d",
1327 start_edge, laser.beamer_edge);
1330 LX += step_size * XS;
1331 LY += step_size * YS;
1333 else if (element != EL_EMPTY)
1342 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1347 void DrawLaser(int start_edge, int mode)
1349 // do not draw laser if fuse is off
1350 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1353 if (mode == DL_LASER_DISABLED)
1354 DeactivateLaserTargetElement();
1356 if (laser.num_edges - start_edge < 0)
1358 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1363 // check if laser is interrupted by beamer element
1364 if (laser.num_beamers > 0 &&
1365 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1367 if (mode == DL_LASER_ENABLED)
1370 int tmp_start_edge = start_edge;
1372 // draw laser segments forward from the start to the last beamer
1373 for (i = 0; i < laser.num_beamers; i++)
1375 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1377 if (tmp_num_edges <= 0)
1381 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1382 i, laser.beamer_edge[i], tmp_start_edge);
1385 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1387 tmp_start_edge = laser.beamer_edge[i];
1390 // draw last segment from last beamer to the end
1391 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1397 int last_num_edges = laser.num_edges;
1398 int num_beamers = laser.num_beamers;
1400 // delete laser segments backward from the end to the first beamer
1401 for (i = num_beamers - 1; i >= 0; i--)
1403 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1405 if (laser.beamer_edge[i] - start_edge <= 0)
1408 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1410 last_num_edges = laser.beamer_edge[i];
1411 laser.num_beamers--;
1415 if (last_num_edges - start_edge <= 0)
1416 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1417 last_num_edges, start_edge);
1420 // special case when rotating first beamer: delete laser edge on beamer
1421 // (but do not start scanning on previous edge to prevent mirror sound)
1422 if (last_num_edges - start_edge == 1 && start_edge > 0)
1423 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1425 // delete first segment from start to the first beamer
1426 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1431 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1434 game_mm.laser_enabled = mode;
1437 void DrawLaser_MM(void)
1439 DrawLaser(0, game_mm.laser_enabled);
1442 boolean HitElement(int element, int hit_mask)
1444 if (HitOnlyAnEdge(hit_mask))
1447 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1448 element = MovingOrBlocked2Element_MM(ELX, ELY);
1451 Debug("game:mm:HitElement", "(1): element == %d", element);
1455 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1456 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1459 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1463 AddDamagedField(ELX, ELY);
1465 // this is more precise: check if laser would go through the center
1466 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1468 // skip the whole element before continuing the scan
1474 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1476 if (LX/TILEX > ELX || LY/TILEY > ELY)
1478 /* skipping scan positions to the right and down skips one scan
1479 position too much, because this is only the top left scan position
1480 of totally four scan positions (plus one to the right, one to the
1481 bottom and one to the bottom right) */
1491 Debug("game:mm:HitElement", "(2): element == %d", element);
1494 if (LX + 5 * XS < 0 ||
1504 Debug("game:mm:HitElement", "(3): element == %d", element);
1507 if (IS_POLAR(element) &&
1508 ((element - EL_POLAR_START) % 2 ||
1509 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1511 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1513 laser.num_damages--;
1518 if (IS_POLAR_CROSS(element) &&
1519 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1521 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1523 laser.num_damages--;
1528 if (!IS_BEAMER(element) &&
1529 !IS_FIBRE_OPTIC(element) &&
1530 !IS_GRID_WOOD(element) &&
1531 element != EL_FUEL_EMPTY)
1534 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1535 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1537 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1540 LX = ELX * TILEX + 14;
1541 LY = ELY * TILEY + 14;
1543 AddLaserEdge(LX, LY);
1546 if (IS_MIRROR(element) ||
1547 IS_MIRROR_FIXED(element) ||
1548 IS_POLAR(element) ||
1549 IS_POLAR_CROSS(element) ||
1550 IS_DF_MIRROR(element) ||
1551 IS_DF_MIRROR_AUTO(element) ||
1552 element == EL_PRISM ||
1553 element == EL_REFRACTOR)
1555 int current_angle = laser.current_angle;
1558 laser.num_damages--;
1560 AddDamagedField(ELX, ELY);
1562 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1565 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1567 if (IS_MIRROR(element) ||
1568 IS_MIRROR_FIXED(element) ||
1569 IS_DF_MIRROR(element) ||
1570 IS_DF_MIRROR_AUTO(element))
1571 laser.current_angle = get_mirrored_angle(laser.current_angle,
1572 get_element_angle(element));
1574 if (element == EL_PRISM || element == EL_REFRACTOR)
1575 laser.current_angle = RND(16);
1577 XS = 2 * Step[laser.current_angle].x;
1578 YS = 2 * Step[laser.current_angle].y;
1580 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1585 LX += step_size * XS;
1586 LY += step_size * YS;
1588 // draw sparkles on mirror
1589 if ((IS_MIRROR(element) ||
1590 IS_MIRROR_FIXED(element) ||
1591 element == EL_PRISM) &&
1592 current_angle != laser.current_angle)
1594 MovDelay[ELX][ELY] = 11; // start animation
1597 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1598 current_angle != laser.current_angle)
1599 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1602 (get_opposite_angle(laser.current_angle) ==
1603 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1605 return (laser.overloaded ? TRUE : FALSE);
1608 if (element == EL_FUEL_FULL)
1610 laser.stops_inside_element = TRUE;
1615 if (element == EL_BOMB || element == EL_MINE)
1617 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1619 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1621 laser.dest_element_last = Tile[ELX][ELY];
1622 laser.dest_element_last_x = ELX;
1623 laser.dest_element_last_y = ELY;
1625 if (element == EL_MINE)
1626 laser.overloaded = TRUE;
1629 if (element == EL_KETTLE ||
1630 element == EL_CELL ||
1631 element == EL_KEY ||
1632 element == EL_LIGHTBALL ||
1633 element == EL_PACMAN ||
1636 if (!IS_PACMAN(element))
1639 if (element == EL_PACMAN)
1642 if (element == EL_KETTLE || element == EL_CELL)
1644 if (game_mm.kettles_still_needed > 0)
1645 game_mm.kettles_still_needed--;
1647 game.snapshot.collected_item = TRUE;
1649 if (game_mm.kettles_still_needed == 0)
1653 DrawLaser(0, DL_LASER_ENABLED);
1656 else if (element == EL_KEY)
1660 else if (IS_PACMAN(element))
1662 DeletePacMan(ELX, ELY);
1665 RaiseScoreElement_MM(element);
1670 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1672 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1674 DrawLaser(0, DL_LASER_ENABLED);
1676 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1678 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1679 game_mm.lights_still_needed--;
1683 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1684 game_mm.lights_still_needed++;
1687 DrawField_MM(ELX, ELY);
1688 DrawLaser(0, DL_LASER_ENABLED);
1693 laser.stops_inside_element = TRUE;
1699 Debug("game:mm:HitElement", "(4): element == %d", element);
1702 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1703 laser.num_beamers < MAX_NUM_BEAMERS &&
1704 laser.beamer[BEAMER_NR(element)][1].num)
1706 int beamer_angle = get_element_angle(element);
1707 int beamer_nr = BEAMER_NR(element);
1711 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1714 laser.num_damages--;
1716 if (IS_FIBRE_OPTIC(element) ||
1717 laser.current_angle == get_opposite_angle(beamer_angle))
1721 LX = ELX * TILEX + 14;
1722 LY = ELY * TILEY + 14;
1724 AddLaserEdge(LX, LY);
1725 AddDamagedField(ELX, ELY);
1727 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1730 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1732 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1733 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1734 ELX = laser.beamer[beamer_nr][pos].x;
1735 ELY = laser.beamer[beamer_nr][pos].y;
1736 LX = ELX * TILEX + 14;
1737 LY = ELY * TILEY + 14;
1739 if (IS_BEAMER(element))
1741 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1742 XS = 2 * Step[laser.current_angle].x;
1743 YS = 2 * Step[laser.current_angle].y;
1746 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1748 AddLaserEdge(LX, LY);
1749 AddDamagedField(ELX, ELY);
1751 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1754 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1756 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1761 LX += step_size * XS;
1762 LY += step_size * YS;
1764 laser.num_beamers++;
1773 boolean HitOnlyAnEdge(int hit_mask)
1775 // check if the laser hit only the edge of an element and, if so, go on
1778 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1782 if ((hit_mask == HIT_MASK_TOPLEFT ||
1783 hit_mask == HIT_MASK_TOPRIGHT ||
1784 hit_mask == HIT_MASK_BOTTOMLEFT ||
1785 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1786 laser.current_angle % 4) // angle is not 90°
1790 if (hit_mask == HIT_MASK_TOPLEFT)
1795 else if (hit_mask == HIT_MASK_TOPRIGHT)
1800 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1805 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1811 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1817 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1824 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1830 boolean HitPolarizer(int element, int hit_mask)
1832 if (HitOnlyAnEdge(hit_mask))
1835 if (IS_DF_GRID(element))
1837 int grid_angle = get_element_angle(element);
1840 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1841 grid_angle, laser.current_angle);
1844 AddLaserEdge(LX, LY);
1845 AddDamagedField(ELX, ELY);
1848 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1850 if (laser.current_angle == grid_angle ||
1851 laser.current_angle == get_opposite_angle(grid_angle))
1853 // skip the whole element before continuing the scan
1859 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1861 if (LX/TILEX > ELX || LY/TILEY > ELY)
1863 /* skipping scan positions to the right and down skips one scan
1864 position too much, because this is only the top left scan position
1865 of totally four scan positions (plus one to the right, one to the
1866 bottom and one to the bottom right) */
1872 AddLaserEdge(LX, LY);
1878 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1880 LX / TILEX, LY / TILEY,
1881 LX % TILEX, LY % TILEY);
1886 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1888 return HitReflectingWalls(element, hit_mask);
1892 return HitAbsorbingWalls(element, hit_mask);
1895 else if (IS_GRID_STEEL(element))
1897 return HitReflectingWalls(element, hit_mask);
1899 else // IS_GRID_WOOD
1901 return HitAbsorbingWalls(element, hit_mask);
1907 boolean HitBlock(int element, int hit_mask)
1909 boolean check = FALSE;
1911 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1912 game_mm.num_keys == 0)
1915 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1918 int ex = ELX * TILEX + 14;
1919 int ey = ELY * TILEY + 14;
1923 for (i = 1; i < 32; i++)
1928 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1933 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1934 return HitAbsorbingWalls(element, hit_mask);
1938 AddLaserEdge(LX - XS, LY - YS);
1939 AddDamagedField(ELX, ELY);
1942 Box[ELX][ELY] = laser.num_edges;
1944 return HitReflectingWalls(element, hit_mask);
1947 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1949 int xs = XS / 2, ys = YS / 2;
1950 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1951 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1953 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1954 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1956 laser.overloaded = (element == EL_GATE_STONE);
1961 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1962 (hit_mask == HIT_MASK_TOP ||
1963 hit_mask == HIT_MASK_LEFT ||
1964 hit_mask == HIT_MASK_RIGHT ||
1965 hit_mask == HIT_MASK_BOTTOM))
1966 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1967 hit_mask == HIT_MASK_BOTTOM),
1968 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1969 hit_mask == HIT_MASK_RIGHT));
1970 AddLaserEdge(LX, LY);
1976 if (element == EL_GATE_STONE && Box[ELX][ELY])
1978 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1990 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1992 int xs = XS / 2, ys = YS / 2;
1993 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1994 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1996 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1997 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1999 laser.overloaded = (element == EL_BLOCK_STONE);
2004 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2005 (hit_mask == HIT_MASK_TOP ||
2006 hit_mask == HIT_MASK_LEFT ||
2007 hit_mask == HIT_MASK_RIGHT ||
2008 hit_mask == HIT_MASK_BOTTOM))
2009 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2010 hit_mask == HIT_MASK_BOTTOM),
2011 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2012 hit_mask == HIT_MASK_RIGHT));
2013 AddDamagedField(ELX, ELY);
2015 LX = ELX * TILEX + 14;
2016 LY = ELY * TILEY + 14;
2018 AddLaserEdge(LX, LY);
2020 laser.stops_inside_element = TRUE;
2028 boolean HitLaserSource(int element, int hit_mask)
2030 if (HitOnlyAnEdge(hit_mask))
2033 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2035 laser.overloaded = TRUE;
2040 boolean HitLaserDestination(int element, int hit_mask)
2042 if (HitOnlyAnEdge(hit_mask))
2045 if (element != EL_EXIT_OPEN &&
2046 !(IS_RECEIVER(element) &&
2047 game_mm.kettles_still_needed == 0 &&
2048 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2050 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2055 if (IS_RECEIVER(element) ||
2056 (IS_22_5_ANGLE(laser.current_angle) &&
2057 (ELX != (LX + 6 * XS) / TILEX ||
2058 ELY != (LY + 6 * YS) / TILEY ||
2067 LX = ELX * TILEX + 14;
2068 LY = ELY * TILEY + 14;
2070 laser.stops_inside_element = TRUE;
2073 AddLaserEdge(LX, LY);
2074 AddDamagedField(ELX, ELY);
2076 if (game_mm.lights_still_needed == 0)
2078 game_mm.level_solved = TRUE;
2080 SetTileCursorActive(FALSE);
2086 boolean HitReflectingWalls(int element, int hit_mask)
2088 // check if laser hits side of a wall with an angle that is not 90°
2089 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2090 hit_mask == HIT_MASK_LEFT ||
2091 hit_mask == HIT_MASK_RIGHT ||
2092 hit_mask == HIT_MASK_BOTTOM))
2094 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2099 if (!IS_DF_GRID(element))
2100 AddLaserEdge(LX, LY);
2102 // check if laser hits wall with an angle of 45°
2103 if (!IS_22_5_ANGLE(laser.current_angle))
2105 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2108 laser.current_angle = get_mirrored_angle(laser.current_angle,
2111 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2114 laser.current_angle = get_mirrored_angle(laser.current_angle,
2118 AddLaserEdge(LX, LY);
2120 XS = 2 * Step[laser.current_angle].x;
2121 YS = 2 * Step[laser.current_angle].y;
2125 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2127 laser.current_angle = get_mirrored_angle(laser.current_angle,
2132 if (!IS_DF_GRID(element))
2133 AddLaserEdge(LX, LY);
2138 if (!IS_DF_GRID(element))
2139 AddLaserEdge(LX, LY + YS / 2);
2142 if (!IS_DF_GRID(element))
2143 AddLaserEdge(LX, LY);
2146 YS = 2 * Step[laser.current_angle].y;
2150 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2152 laser.current_angle = get_mirrored_angle(laser.current_angle,
2157 if (!IS_DF_GRID(element))
2158 AddLaserEdge(LX, LY);
2163 if (!IS_DF_GRID(element))
2164 AddLaserEdge(LX + XS / 2, LY);
2167 if (!IS_DF_GRID(element))
2168 AddLaserEdge(LX, LY);
2171 XS = 2 * Step[laser.current_angle].x;
2177 // reflection at the edge of reflecting DF style wall
2178 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2180 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2181 hit_mask == HIT_MASK_TOPRIGHT) ||
2182 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2183 hit_mask == HIT_MASK_TOPLEFT) ||
2184 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2185 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2186 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2187 hit_mask == HIT_MASK_BOTTOMRIGHT))
2190 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2191 ANG_MIRROR_135 : ANG_MIRROR_45);
2193 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2195 AddDamagedField(ELX, ELY);
2196 AddLaserEdge(LX, LY);
2198 laser.current_angle = get_mirrored_angle(laser.current_angle,
2206 AddLaserEdge(LX, LY);
2212 // reflection inside an edge of reflecting DF style wall
2213 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2215 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2216 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2217 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2218 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2219 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2220 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2221 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2222 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2225 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2226 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2227 ANG_MIRROR_135 : ANG_MIRROR_45);
2229 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2232 AddDamagedField(ELX, ELY);
2235 AddLaserEdge(LX - XS, LY - YS);
2236 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2237 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2239 laser.current_angle = get_mirrored_angle(laser.current_angle,
2247 AddLaserEdge(LX, LY);
2253 // check if laser hits DF style wall with an angle of 90°
2254 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2256 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2257 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2258 (IS_VERT_ANGLE(laser.current_angle) &&
2259 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2261 // laser at last step touched nothing or the same side of the wall
2262 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2264 AddDamagedField(ELX, ELY);
2271 last_hit_mask = hit_mask;
2278 if (!HitOnlyAnEdge(hit_mask))
2280 laser.overloaded = TRUE;
2288 boolean HitAbsorbingWalls(int element, int hit_mask)
2290 if (HitOnlyAnEdge(hit_mask))
2294 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2296 AddLaserEdge(LX - XS, LY - YS);
2303 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2305 AddLaserEdge(LX - XS, LY - YS);
2311 if (IS_WALL_WOOD(element) ||
2312 IS_DF_WALL_WOOD(element) ||
2313 IS_GRID_WOOD(element) ||
2314 IS_GRID_WOOD_FIXED(element) ||
2315 IS_GRID_WOOD_AUTO(element) ||
2316 element == EL_FUSE_ON ||
2317 element == EL_BLOCK_WOOD ||
2318 element == EL_GATE_WOOD)
2320 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2325 if (IS_WALL_ICE(element))
2329 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2330 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2332 // check if laser hits wall with an angle of 90°
2333 if (IS_90_ANGLE(laser.current_angle))
2334 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2336 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2340 for (i = 0; i < 4; i++)
2342 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2343 mask = 15 - (8 >> i);
2344 else if (ABS(XS) == 4 &&
2346 (XS > 0) == (i % 2) &&
2347 (YS < 0) == (i / 2))
2348 mask = 3 + (i / 2) * 9;
2349 else if (ABS(YS) == 4 &&
2351 (XS < 0) == (i % 2) &&
2352 (YS > 0) == (i / 2))
2353 mask = 5 + (i % 2) * 5;
2357 laser.wall_mask = mask;
2359 else if (IS_WALL_AMOEBA(element))
2361 int elx = (LX - 2 * XS) / TILEX;
2362 int ely = (LY - 2 * YS) / TILEY;
2363 int element2 = Tile[elx][ely];
2366 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2368 laser.dest_element = EL_EMPTY;
2376 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2377 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2379 if (IS_90_ANGLE(laser.current_angle))
2380 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2382 laser.dest_element = element2 | EL_WALL_AMOEBA;
2384 laser.wall_mask = mask;
2390 static void OpenExit(int x, int y)
2394 if (!MovDelay[x][y]) // next animation frame
2395 MovDelay[x][y] = 4 * delay;
2397 if (MovDelay[x][y]) // wait some time before next frame
2402 phase = MovDelay[x][y] / delay;
2404 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2405 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2407 if (!MovDelay[x][y])
2409 Tile[x][y] = EL_EXIT_OPEN;
2415 static void OpenSurpriseBall(int x, int y)
2419 if (!MovDelay[x][y]) // next animation frame
2420 MovDelay[x][y] = 50 * delay;
2422 if (MovDelay[x][y]) // wait some time before next frame
2426 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2429 int graphic = el2gfx(Store[x][y]);
2431 int dx = RND(26), dy = RND(26);
2433 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2435 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2436 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2438 MarkTileDirty(x, y);
2441 if (!MovDelay[x][y])
2443 Tile[x][y] = Store[x][y];
2444 Store[x][y] = Store2[x][y] = 0;
2453 static void MeltIce(int x, int y)
2458 if (!MovDelay[x][y]) // next animation frame
2459 MovDelay[x][y] = frames * delay;
2461 if (MovDelay[x][y]) // wait some time before next frame
2464 int wall_mask = Store2[x][y];
2465 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2468 phase = frames - MovDelay[x][y] / delay - 1;
2470 if (!MovDelay[x][y])
2474 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2475 Store[x][y] = Store2[x][y] = 0;
2477 DrawWalls_MM(x, y, Tile[x][y]);
2479 if (Tile[x][y] == EL_WALL_ICE)
2480 Tile[x][y] = EL_EMPTY;
2482 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2483 if (laser.damage[i].is_mirror)
2487 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2489 DrawLaser(0, DL_LASER_DISABLED);
2493 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2495 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2497 laser.redraw = TRUE;
2502 static void GrowAmoeba(int x, int y)
2507 if (!MovDelay[x][y]) // next animation frame
2508 MovDelay[x][y] = frames * delay;
2510 if (MovDelay[x][y]) // wait some time before next frame
2513 int wall_mask = Store2[x][y];
2514 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2517 phase = MovDelay[x][y] / delay;
2519 if (!MovDelay[x][y])
2521 Tile[x][y] = real_element;
2522 Store[x][y] = Store2[x][y] = 0;
2524 DrawWalls_MM(x, y, Tile[x][y]);
2525 DrawLaser(0, DL_LASER_ENABLED);
2527 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2529 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2534 static void DrawFieldAnimated_MM(int x, int y)
2536 int element = Tile[x][y];
2538 if (IS_BLOCKED(x, y))
2543 if (IS_MIRROR(element) ||
2544 IS_MIRROR_FIXED(element) ||
2545 element == EL_PRISM)
2547 if (MovDelay[x][y] != 0) // wait some time before next frame
2551 if (MovDelay[x][y] != 0)
2553 int graphic = IMG_TWINKLE_WHITE;
2554 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2556 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2561 laser.redraw = TRUE;
2564 static void Explode_MM(int x, int y, int phase, int mode)
2566 int num_phase = 9, delay = 2;
2567 int last_phase = num_phase * delay;
2568 int half_phase = (num_phase / 2) * delay;
2570 laser.redraw = TRUE;
2572 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2574 int center_element = Tile[x][y];
2576 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2578 // put moving element to center field (and let it explode there)
2579 center_element = MovingOrBlocked2Element_MM(x, y);
2580 RemoveMovingField_MM(x, y);
2582 Tile[x][y] = center_element;
2585 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2586 Store[x][y] = center_element;
2588 Store[x][y] = EL_EMPTY;
2590 Store2[x][y] = mode;
2592 Tile[x][y] = EL_EXPLODING_OPAQUE;
2593 GfxElement[x][y] = center_element;
2595 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2597 ExplodePhase[x][y] = 1;
2603 GfxFrame[x][y] = 0; // restart explosion animation
2605 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2607 if (phase == half_phase)
2609 Tile[x][y] = EL_EXPLODING_TRANSP;
2611 if (x == ELX && y == ELY)
2615 if (phase == last_phase)
2617 if (Store[x][y] == EL_BOMB_ACTIVE)
2619 DrawLaser(0, DL_LASER_DISABLED);
2622 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2623 Store[x][y] = EL_EMPTY;
2625 GameOver_MM(GAME_OVER_DELAYED);
2627 laser.overloaded = FALSE;
2629 else if (IS_MCDUFFIN(Store[x][y]))
2631 Store[x][y] = EL_EMPTY;
2633 GameOver_MM(GAME_OVER_BOMB);
2636 Tile[x][y] = Store[x][y];
2637 Store[x][y] = Store2[x][y] = 0;
2638 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2643 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2645 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2646 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2648 DrawGraphicAnimation_MM(x, y, graphic, frame);
2650 MarkTileDirty(x, y);
2654 static void Bang_MM(int x, int y)
2656 int element = Tile[x][y];
2659 DrawLaser(0, DL_LASER_ENABLED);
2662 if (IS_PACMAN(element))
2663 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2664 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2665 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2666 else if (element == EL_KEY)
2667 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2669 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2671 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2674 void TurnRound(int x, int y)
2686 { 0, 0 }, { 0, 0 }, { 0, 0 },
2691 int left, right, back;
2695 { MV_DOWN, MV_UP, MV_RIGHT },
2696 { MV_UP, MV_DOWN, MV_LEFT },
2698 { MV_LEFT, MV_RIGHT, MV_DOWN },
2699 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2700 { MV_RIGHT, MV_LEFT, MV_UP }
2703 int element = Tile[x][y];
2704 int old_move_dir = MovDir[x][y];
2705 int right_dir = turn[old_move_dir].right;
2706 int back_dir = turn[old_move_dir].back;
2707 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2708 int right_x = x + right_dx, right_y = y + right_dy;
2710 if (element == EL_PACMAN)
2712 boolean can_turn_right = FALSE;
2714 if (IN_LEV_FIELD(right_x, right_y) &&
2715 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2716 can_turn_right = TRUE;
2719 MovDir[x][y] = right_dir;
2721 MovDir[x][y] = back_dir;
2727 static void StartMoving_MM(int x, int y)
2729 int element = Tile[x][y];
2734 if (CAN_MOVE(element))
2738 if (MovDelay[x][y]) // wait some time before next movement
2746 // now make next step
2748 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2750 if (element == EL_PACMAN &&
2751 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2752 !ObjHit(newx, newy, HIT_POS_CENTER))
2754 Store[newx][newy] = Tile[newx][newy];
2755 Tile[newx][newy] = EL_EMPTY;
2757 DrawField_MM(newx, newy);
2759 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2760 ObjHit(newx, newy, HIT_POS_CENTER))
2762 // object was running against a wall
2769 InitMovingField_MM(x, y, MovDir[x][y]);
2773 ContinueMoving_MM(x, y);
2776 static void ContinueMoving_MM(int x, int y)
2778 int element = Tile[x][y];
2779 int direction = MovDir[x][y];
2780 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2781 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2782 int horiz_move = (dx!=0);
2783 int newx = x + dx, newy = y + dy;
2784 int step = (horiz_move ? dx : dy) * TILEX / 8;
2786 MovPos[x][y] += step;
2788 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2790 Tile[x][y] = EL_EMPTY;
2791 Tile[newx][newy] = element;
2793 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2794 MovDelay[newx][newy] = 0;
2796 if (!CAN_MOVE(element))
2797 MovDir[newx][newy] = 0;
2800 DrawField_MM(newx, newy);
2802 Stop[newx][newy] = TRUE;
2804 if (element == EL_PACMAN)
2806 if (Store[newx][newy] == EL_BOMB)
2807 Bang_MM(newx, newy);
2809 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2810 (LX + 2 * XS) / TILEX == newx &&
2811 (LY + 2 * YS) / TILEY == newy)
2818 else // still moving on
2823 laser.redraw = TRUE;
2826 boolean ClickElement(int x, int y, int button)
2828 static DelayCounter click_delay = { CLICK_DELAY };
2829 static boolean new_button = TRUE;
2830 boolean element_clicked = FALSE;
2835 // initialize static variables
2836 click_delay.count = 0;
2837 click_delay.value = CLICK_DELAY;
2843 // do not rotate objects hit by the laser after the game was solved
2844 if (game_mm.level_solved && Hit[x][y])
2847 if (button == MB_RELEASED)
2850 click_delay.value = CLICK_DELAY;
2852 // release eventually hold auto-rotating mirror
2853 RotateMirror(x, y, MB_RELEASED);
2858 if (!FrameReached(&click_delay) && !new_button)
2861 if (button == MB_MIDDLEBUTTON) // middle button has no function
2864 if (!IN_LEV_FIELD(x, y))
2867 if (Tile[x][y] == EL_EMPTY)
2870 element = Tile[x][y];
2872 if (IS_MIRROR(element) ||
2873 IS_BEAMER(element) ||
2874 IS_POLAR(element) ||
2875 IS_POLAR_CROSS(element) ||
2876 IS_DF_MIRROR(element) ||
2877 IS_DF_MIRROR_AUTO(element))
2879 RotateMirror(x, y, button);
2881 element_clicked = TRUE;
2883 else if (IS_MCDUFFIN(element))
2885 if (!laser.fuse_off)
2887 DrawLaser(0, DL_LASER_DISABLED);
2894 element = get_rotated_element(element, BUTTON_ROTATION(button));
2895 laser.start_angle = get_element_angle(element);
2899 Tile[x][y] = element;
2906 if (!laser.fuse_off)
2909 element_clicked = TRUE;
2911 else if (element == EL_FUSE_ON && laser.fuse_off)
2913 if (x != laser.fuse_x || y != laser.fuse_y)
2916 laser.fuse_off = FALSE;
2917 laser.fuse_x = laser.fuse_y = -1;
2919 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2922 element_clicked = TRUE;
2924 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2926 laser.fuse_off = TRUE;
2929 laser.overloaded = FALSE;
2931 DrawLaser(0, DL_LASER_DISABLED);
2932 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2934 element_clicked = TRUE;
2936 else if (element == EL_LIGHTBALL)
2939 RaiseScoreElement_MM(element);
2940 DrawLaser(0, DL_LASER_ENABLED);
2942 element_clicked = TRUE;
2945 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2948 return element_clicked;
2951 void RotateMirror(int x, int y, int button)
2953 if (button == MB_RELEASED)
2955 // release eventually hold auto-rotating mirror
2962 if (IS_MIRROR(Tile[x][y]) ||
2963 IS_POLAR_CROSS(Tile[x][y]) ||
2964 IS_POLAR(Tile[x][y]) ||
2965 IS_BEAMER(Tile[x][y]) ||
2966 IS_DF_MIRROR(Tile[x][y]) ||
2967 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2968 IS_GRID_WOOD_AUTO(Tile[x][y]))
2970 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2972 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2974 if (button == MB_LEFTBUTTON)
2976 // left mouse button only for manual adjustment, no auto-rotating;
2977 // freeze mirror for until mouse button released
2981 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2983 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2987 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2989 int edge = Hit[x][y];
2995 DrawLaser(edge - 1, DL_LASER_DISABLED);
2999 else if (ObjHit(x, y, HIT_POS_CENTER))
3001 int edge = Hit[x][y];
3005 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3010 DrawLaser(edge - 1, DL_LASER_DISABLED);
3017 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3022 if ((IS_BEAMER(Tile[x][y]) ||
3023 IS_POLAR(Tile[x][y]) ||
3024 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3028 if (IS_BEAMER(Tile[x][y]))
3031 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3032 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3044 DrawLaser(0, DL_LASER_ENABLED);
3048 static void AutoRotateMirrors(void)
3052 if (!FrameReached(&rotate_delay))
3055 for (x = 0; x < lev_fieldx; x++)
3057 for (y = 0; y < lev_fieldy; y++)
3059 int element = Tile[x][y];
3061 // do not rotate objects hit by the laser after the game was solved
3062 if (game_mm.level_solved && Hit[x][y])
3065 if (IS_DF_MIRROR_AUTO(element) ||
3066 IS_GRID_WOOD_AUTO(element) ||
3067 IS_GRID_STEEL_AUTO(element) ||
3068 element == EL_REFRACTOR)
3069 RotateMirror(x, y, MB_RIGHTBUTTON);
3074 boolean ObjHit(int obx, int oby, int bits)
3081 if (bits & HIT_POS_CENTER)
3083 if (CheckLaserPixel(cSX + obx + 15,
3088 if (bits & HIT_POS_EDGE)
3090 for (i = 0; i < 4; i++)
3091 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3092 cSY + oby + 31 * (i / 2)))
3096 if (bits & HIT_POS_BETWEEN)
3098 for (i = 0; i < 4; i++)
3099 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3100 cSY + 4 + oby + 22 * (i / 2)))
3107 void DeletePacMan(int px, int py)
3113 if (game_mm.num_pacman <= 1)
3115 game_mm.num_pacman = 0;
3119 for (i = 0; i < game_mm.num_pacman; i++)
3120 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3123 game_mm.num_pacman--;
3125 for (j = i; j < game_mm.num_pacman; j++)
3127 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3128 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3129 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3133 void ColorCycling(void)
3135 static int CC, Cc = 0;
3137 static int color, old = 0xF00, new = 0x010, mult = 1;
3138 static unsigned short red, green, blue;
3140 if (color_status == STATIC_COLORS)
3145 if (CC < Cc || CC > Cc + 2)
3149 color = old + new * mult;
3155 if (ABS(mult) == 16)
3165 red = 0x0e00 * ((color & 0xF00) >> 8);
3166 green = 0x0e00 * ((color & 0x0F0) >> 4);
3167 blue = 0x0e00 * ((color & 0x00F));
3168 SetRGB(pen_magicolor[0], red, green, blue);
3170 red = 0x1111 * ((color & 0xF00) >> 8);
3171 green = 0x1111 * ((color & 0x0F0) >> 4);
3172 blue = 0x1111 * ((color & 0x00F));
3173 SetRGB(pen_magicolor[1], red, green, blue);
3177 static void GameActions_MM_Ext(void)
3184 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3187 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3189 element = Tile[x][y];
3191 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3192 StartMoving_MM(x, y);
3193 else if (IS_MOVING(x, y))
3194 ContinueMoving_MM(x, y);
3195 else if (IS_EXPLODING(element))
3196 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3197 else if (element == EL_EXIT_OPENING)
3199 else if (element == EL_GRAY_BALL_OPENING)
3200 OpenSurpriseBall(x, y);
3201 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3203 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3206 DrawFieldAnimated_MM(x, y);
3209 AutoRotateMirrors();
3212 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3214 // redraw after Explode_MM() ...
3216 DrawLaser(0, DL_LASER_ENABLED);
3217 laser.redraw = FALSE;
3222 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3226 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3228 DrawLaser(0, DL_LASER_DISABLED);
3233 // skip all following game actions if game is over
3234 if (game_mm.game_over)
3237 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3241 GameOver_MM(GAME_OVER_NO_ENERGY);
3246 if (FrameReached(&energy_delay))
3248 if (game_mm.energy_left > 0)
3249 game_mm.energy_left--;
3251 // when out of energy, wait another frame to play "out of time" sound
3254 element = laser.dest_element;
3257 if (element != Tile[ELX][ELY])
3259 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3260 element, Tile[ELX][ELY]);
3264 if (!laser.overloaded && laser.overload_value == 0 &&
3265 element != EL_BOMB &&
3266 element != EL_BOMB_ACTIVE &&
3267 element != EL_MINE &&
3268 element != EL_MINE_ACTIVE &&
3269 element != EL_BALL_GRAY &&
3270 element != EL_BLOCK_STONE &&
3271 element != EL_BLOCK_WOOD &&
3272 element != EL_FUSE_ON &&
3273 element != EL_FUEL_FULL &&
3274 !IS_WALL_ICE(element) &&
3275 !IS_WALL_AMOEBA(element))
3278 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3280 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3281 (!laser.overloaded && laser.overload_value > 0)) &&
3282 FrameReached(&overload_delay))
3284 if (laser.overloaded)
3285 laser.overload_value++;
3287 laser.overload_value--;
3289 if (game_mm.cheat_no_overload)
3291 laser.overloaded = FALSE;
3292 laser.overload_value = 0;
3295 game_mm.laser_overload_value = laser.overload_value;
3297 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3299 SetLaserColor(0xFF);
3301 DrawLaser(0, DL_LASER_ENABLED);
3304 if (!laser.overloaded)
3305 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3306 else if (setup.sound_loops)
3307 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3309 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3311 if (laser.overloaded)
3314 BlitBitmap(pix[PIX_DOOR], drawto,
3315 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3316 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3317 - laser.overload_value,
3318 OVERLOAD_XSIZE, laser.overload_value,
3319 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3320 - laser.overload_value);
3322 redraw_mask |= REDRAW_DOOR_1;
3327 BlitBitmap(pix[PIX_DOOR], drawto,
3328 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3329 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3330 DX_OVERLOAD, DY_OVERLOAD);
3332 redraw_mask |= REDRAW_DOOR_1;
3335 if (laser.overload_value == MAX_LASER_OVERLOAD)
3337 UpdateAndDisplayGameControlValues();
3341 GameOver_MM(GAME_OVER_OVERLOADED);
3352 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3354 if (game_mm.cheat_no_explosion)
3359 laser.dest_element = EL_EXPLODING_OPAQUE;
3364 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3366 laser.fuse_off = TRUE;
3370 DrawLaser(0, DL_LASER_DISABLED);
3371 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3374 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3376 if (!Store2[ELX][ELY]) // check if content element not yet determined
3378 int last_anim_random_frame = gfx.anim_random_frame;
3381 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3382 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3384 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3385 native_mm_level.ball_choice_mode, 0,
3386 game_mm.ball_choice_pos);
3388 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3389 gfx.anim_random_frame = last_anim_random_frame;
3391 game_mm.ball_choice_pos++;
3393 int new_element = native_mm_level.ball_content[element_pos];
3395 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3396 Store2[ELX][ELY] = TRUE;
3399 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3401 // !!! CHECK AGAIN: Laser on Polarizer !!!
3404 laser.dest_element_last = Tile[ELX][ELY];
3405 laser.dest_element_last_x = ELX;
3406 laser.dest_element_last_y = ELY;
3416 element = EL_MIRROR_START + RND(16);
3422 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3429 element = (rnd == 0 ? EL_FUSE_ON :
3430 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3431 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3432 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3433 EL_MIRROR_FIXED_START + rnd - 25);
3438 graphic = el2gfx(element);
3440 for (i = 0; i < 50; i++)
3446 BlitBitmap(pix[PIX_BACK], drawto,
3447 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3448 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3449 SX + ELX * TILEX + x,
3450 SY + ELY * TILEY + y);
3452 MarkTileDirty(ELX, ELY);
3455 DrawLaser(0, DL_LASER_ENABLED);
3457 Delay_WithScreenUpdates(50);
3460 Tile[ELX][ELY] = element;
3461 DrawField_MM(ELX, ELY);
3464 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3467 // above stuff: GRAY BALL -> PRISM !!!
3469 LX = ELX * TILEX + 14;
3470 LY = ELY * TILEY + 14;
3471 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3478 laser.num_edges -= 2;
3479 laser.num_damages--;
3483 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3484 if (laser.damage[i].is_mirror)
3488 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3490 DrawLaser(0, DL_LASER_DISABLED);
3492 DrawLaser(0, DL_LASER_DISABLED);
3501 if (IS_WALL_ICE(element) && CT > 50)
3503 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3506 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3507 Store[ELX][ELY] = EL_WALL_ICE;
3508 Store2[ELX][ELY] = laser.wall_mask;
3510 laser.dest_element = Tile[ELX][ELY];
3515 for (i = 0; i < 5; i++)
3521 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3525 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3527 Delay_WithScreenUpdates(100);
3530 if (Tile[ELX][ELY] == EL_WALL_ICE)
3531 Tile[ELX][ELY] = EL_EMPTY;
3535 LX = laser.edge[laser.num_edges].x - cSX2;
3536 LY = laser.edge[laser.num_edges].y - cSY2;
3539 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3540 if (laser.damage[i].is_mirror)
3544 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3546 DrawLaser(0, DL_LASER_DISABLED);
3553 if (IS_WALL_AMOEBA(element) && CT > 60)
3555 int k1, k2, k3, dx, dy, de, dm;
3556 int element2 = Tile[ELX][ELY];
3558 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3561 for (i = laser.num_damages - 1; i >= 0; i--)
3562 if (laser.damage[i].is_mirror)
3565 r = laser.num_edges;
3566 d = laser.num_damages;
3573 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3576 DrawLaser(0, DL_LASER_ENABLED);
3579 x = laser.damage[k1].x;
3580 y = laser.damage[k1].y;
3585 for (i = 0; i < 4; i++)
3587 if (laser.wall_mask & (1 << i))
3589 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3590 cSY + ELY * TILEY + 31 * (i / 2)))
3593 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3594 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3601 for (i = 0; i < 4; i++)
3603 if (laser.wall_mask & (1 << i))
3605 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3606 cSY + ELY * TILEY + 31 * (i / 2)))
3613 if (laser.num_beamers > 0 ||
3614 k1 < 1 || k2 < 4 || k3 < 4 ||
3615 CheckLaserPixel(cSX + ELX * TILEX + 14,
3616 cSY + ELY * TILEY + 14))
3618 laser.num_edges = r;
3619 laser.num_damages = d;
3621 DrawLaser(0, DL_LASER_DISABLED);
3624 Tile[ELX][ELY] = element | laser.wall_mask;
3628 de = Tile[ELX][ELY];
3629 dm = laser.wall_mask;
3633 int x = ELX, y = ELY;
3634 int wall_mask = laser.wall_mask;
3637 DrawLaser(0, DL_LASER_ENABLED);
3639 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3641 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3642 Store[x][y] = EL_WALL_AMOEBA;
3643 Store2[x][y] = wall_mask;
3649 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3651 DrawLaser(0, DL_LASER_ENABLED);
3653 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3655 for (i = 4; i >= 0; i--)
3657 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3660 Delay_WithScreenUpdates(20);
3663 DrawLaser(0, DL_LASER_ENABLED);
3668 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3669 laser.stops_inside_element && CT > native_mm_level.time_block)
3674 if (ABS(XS) > ABS(YS))
3681 for (i = 0; i < 4; i++)
3688 x = ELX + Step[k * 4].x;
3689 y = ELY + Step[k * 4].y;
3691 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3694 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3702 laser.overloaded = (element == EL_BLOCK_STONE);
3707 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3710 Tile[x][y] = element;
3712 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3715 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3717 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3718 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3726 if (element == EL_FUEL_FULL && CT > 10)
3728 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3729 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3731 for (i = start; i <= num_init_game_frames; i++)
3733 if (i == num_init_game_frames)
3734 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3735 else if (setup.sound_loops)
3736 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3738 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3740 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3742 UpdateAndDisplayGameControlValues();
3747 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3749 DrawField_MM(ELX, ELY);
3751 DrawLaser(0, DL_LASER_ENABLED);
3759 void GameActions_MM(struct MouseActionInfo action)
3761 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3762 boolean button_released = (action.button == MB_RELEASED);
3764 GameActions_MM_Ext();
3766 CheckSingleStepMode_MM(element_clicked, button_released);
3769 void MovePacMen(void)
3771 int mx, my, ox, oy, nx, ny;
3775 if (++pacman_nr >= game_mm.num_pacman)
3778 game_mm.pacman[pacman_nr].dir--;
3780 for (l = 1; l < 5; l++)
3782 game_mm.pacman[pacman_nr].dir++;
3784 if (game_mm.pacman[pacman_nr].dir > 4)
3785 game_mm.pacman[pacman_nr].dir = 1;
3787 if (game_mm.pacman[pacman_nr].dir % 2)
3790 my = game_mm.pacman[pacman_nr].dir - 2;
3795 mx = 3 - game_mm.pacman[pacman_nr].dir;
3798 ox = game_mm.pacman[pacman_nr].x;
3799 oy = game_mm.pacman[pacman_nr].y;
3802 element = Tile[nx][ny];
3804 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3807 if (!IS_EATABLE4PACMAN(element))
3810 if (ObjHit(nx, ny, HIT_POS_CENTER))
3813 Tile[ox][oy] = EL_EMPTY;
3815 EL_PACMAN_RIGHT - 1 +
3816 (game_mm.pacman[pacman_nr].dir - 1 +
3817 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3819 game_mm.pacman[pacman_nr].x = nx;
3820 game_mm.pacman[pacman_nr].y = ny;
3822 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3824 if (element != EL_EMPTY)
3826 int graphic = el2gfx(Tile[nx][ny]);
3831 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3834 ox = cSX + ox * TILEX;
3835 oy = cSY + oy * TILEY;
3837 for (i = 1; i < 33; i += 2)
3838 BlitBitmap(bitmap, window,
3839 src_x, src_y, TILEX, TILEY,
3840 ox + i * mx, oy + i * my);
3841 Ct = Ct + FrameCounter - CT;
3844 DrawField_MM(nx, ny);
3847 if (!laser.fuse_off)
3849 DrawLaser(0, DL_LASER_ENABLED);
3851 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3853 AddDamagedField(nx, ny);
3855 laser.damage[laser.num_damages - 1].edge = 0;
3859 if (element == EL_BOMB)
3860 DeletePacMan(nx, ny);
3862 if (IS_WALL_AMOEBA(element) &&
3863 (LX + 2 * XS) / TILEX == nx &&
3864 (LY + 2 * YS) / TILEY == ny)
3874 static void InitMovingField_MM(int x, int y, int direction)
3876 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3877 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3879 MovDir[x][y] = direction;
3880 MovDir[newx][newy] = direction;
3882 if (Tile[newx][newy] == EL_EMPTY)
3883 Tile[newx][newy] = EL_BLOCKED;
3886 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3888 int direction = MovDir[x][y];
3889 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3890 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3896 static void Blocked2Moving_MM(int x, int y,
3897 int *comes_from_x, int *comes_from_y)
3899 int oldx = x, oldy = y;
3900 int direction = MovDir[x][y];
3902 if (direction == MV_LEFT)
3904 else if (direction == MV_RIGHT)
3906 else if (direction == MV_UP)
3908 else if (direction == MV_DOWN)
3911 *comes_from_x = oldx;
3912 *comes_from_y = oldy;
3915 static int MovingOrBlocked2Element_MM(int x, int y)
3917 int element = Tile[x][y];
3919 if (element == EL_BLOCKED)
3923 Blocked2Moving_MM(x, y, &oldx, &oldy);
3925 return Tile[oldx][oldy];
3932 static void RemoveField(int x, int y)
3934 Tile[x][y] = EL_EMPTY;
3941 static void RemoveMovingField_MM(int x, int y)
3943 int oldx = x, oldy = y, newx = x, newy = y;
3945 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3948 if (IS_MOVING(x, y))
3950 Moving2Blocked_MM(x, y, &newx, &newy);
3951 if (Tile[newx][newy] != EL_BLOCKED)
3954 else if (Tile[x][y] == EL_BLOCKED)
3956 Blocked2Moving_MM(x, y, &oldx, &oldy);
3957 if (!IS_MOVING(oldx, oldy))
3961 Tile[oldx][oldy] = EL_EMPTY;
3962 Tile[newx][newy] = EL_EMPTY;
3963 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3964 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3966 DrawLevelField_MM(oldx, oldy);
3967 DrawLevelField_MM(newx, newy);
3970 void PlaySoundLevel(int x, int y, int sound_nr)
3972 int sx = SCREENX(x), sy = SCREENY(y);
3974 int silence_distance = 8;
3976 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3977 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3980 if (!IN_LEV_FIELD(x, y) ||
3981 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3982 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3985 volume = SOUND_MAX_VOLUME;
3988 stereo = (sx - SCR_FIELDX/2) * 12;
3990 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3991 if (stereo > SOUND_MAX_RIGHT)
3992 stereo = SOUND_MAX_RIGHT;
3993 if (stereo < SOUND_MAX_LEFT)
3994 stereo = SOUND_MAX_LEFT;
3997 if (!IN_SCR_FIELD(sx, sy))
3999 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4000 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4002 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4005 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4008 static void RaiseScore_MM(int value)
4010 game_mm.score += value;
4013 void RaiseScoreElement_MM(int element)
4018 case EL_PACMAN_RIGHT:
4020 case EL_PACMAN_LEFT:
4021 case EL_PACMAN_DOWN:
4022 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4026 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4031 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4035 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4044 // ----------------------------------------------------------------------------
4045 // Mirror Magic game engine snapshot handling functions
4046 // ----------------------------------------------------------------------------
4048 void SaveEngineSnapshotValues_MM(void)
4052 engine_snapshot_mm.game_mm = game_mm;
4053 engine_snapshot_mm.laser = laser;
4055 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4057 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4059 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4060 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4061 engine_snapshot_mm.Box[x][y] = Box[x][y];
4062 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4066 engine_snapshot_mm.LX = LX;
4067 engine_snapshot_mm.LY = LY;
4068 engine_snapshot_mm.XS = XS;
4069 engine_snapshot_mm.YS = YS;
4070 engine_snapshot_mm.ELX = ELX;
4071 engine_snapshot_mm.ELY = ELY;
4072 engine_snapshot_mm.CT = CT;
4073 engine_snapshot_mm.Ct = Ct;
4075 engine_snapshot_mm.last_LX = last_LX;
4076 engine_snapshot_mm.last_LY = last_LY;
4077 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4078 engine_snapshot_mm.hold_x = hold_x;
4079 engine_snapshot_mm.hold_y = hold_y;
4080 engine_snapshot_mm.pacman_nr = pacman_nr;
4082 engine_snapshot_mm.rotate_delay = rotate_delay;
4083 engine_snapshot_mm.pacman_delay = pacman_delay;
4084 engine_snapshot_mm.energy_delay = energy_delay;
4085 engine_snapshot_mm.overload_delay = overload_delay;
4088 void LoadEngineSnapshotValues_MM(void)
4092 // stored engine snapshot buffers already restored at this point
4094 game_mm = engine_snapshot_mm.game_mm;
4095 laser = engine_snapshot_mm.laser;
4097 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4099 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4101 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4102 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4103 Box[x][y] = engine_snapshot_mm.Box[x][y];
4104 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4108 LX = engine_snapshot_mm.LX;
4109 LY = engine_snapshot_mm.LY;
4110 XS = engine_snapshot_mm.XS;
4111 YS = engine_snapshot_mm.YS;
4112 ELX = engine_snapshot_mm.ELX;
4113 ELY = engine_snapshot_mm.ELY;
4114 CT = engine_snapshot_mm.CT;
4115 Ct = engine_snapshot_mm.Ct;
4117 last_LX = engine_snapshot_mm.last_LX;
4118 last_LY = engine_snapshot_mm.last_LY;
4119 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4120 hold_x = engine_snapshot_mm.hold_x;
4121 hold_y = engine_snapshot_mm.hold_y;
4122 pacman_nr = engine_snapshot_mm.pacman_nr;
4124 rotate_delay = engine_snapshot_mm.rotate_delay;
4125 pacman_delay = engine_snapshot_mm.pacman_delay;
4126 energy_delay = engine_snapshot_mm.energy_delay;
4127 overload_delay = engine_snapshot_mm.overload_delay;
4129 RedrawPlayfield_MM();
4132 static int getAngleFromTouchDelta(int dx, int dy, int base)
4134 double pi = 3.141592653;
4135 double rad = atan2((double)-dy, (double)dx);
4136 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4137 double deg = rad2 * 180.0 / pi;
4139 return (int)(deg * base / 360.0 + 0.5) % base;
4142 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4144 // calculate start (source) position to be at the middle of the tile
4145 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4146 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4147 int dx = dst_mx - src_mx;
4148 int dy = dst_my - src_my;
4157 if (!IN_LEV_FIELD(x, y))
4160 element = Tile[x][y];
4162 if (!IS_MCDUFFIN(element) &&
4163 !IS_MIRROR(element) &&
4164 !IS_BEAMER(element) &&
4165 !IS_POLAR(element) &&
4166 !IS_POLAR_CROSS(element) &&
4167 !IS_DF_MIRROR(element))
4170 angle_old = get_element_angle(element);
4172 if (IS_MCDUFFIN(element))
4174 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4175 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4176 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4177 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4180 else if (IS_MIRROR(element) ||
4181 IS_DF_MIRROR(element))
4183 for (i = 0; i < laser.num_damages; i++)
4185 if (laser.damage[i].x == x &&
4186 laser.damage[i].y == y &&
4187 ObjHit(x, y, HIT_POS_CENTER))
4189 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4190 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4197 if (angle_new == -1)
4199 if (IS_MIRROR(element) ||
4200 IS_DF_MIRROR(element) ||
4204 if (IS_POLAR_CROSS(element))
4207 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4210 button = (angle_new == angle_old ? 0 :
4211 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4212 MB_LEFTBUTTON : MB_RIGHTBUTTON);