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;
961 int element = EL_EMPTY;
962 int last_element = EL_EMPTY;
963 int end = 0, rf = laser.num_edges;
965 // do not scan laser again after the game was lost for whatever reason
966 if (game_mm.game_over)
969 // do not scan laser if fuse is off
973 DeactivateLaserTargetElement();
975 laser.overloaded = FALSE;
976 laser.stops_inside_element = FALSE;
978 DrawLaser(0, DL_LASER_ENABLED);
981 Debug("game:mm:ScanLaser",
982 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
990 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
993 laser.overloaded = TRUE;
998 hit_mask = ScanPixel();
1001 Debug("game:mm:ScanLaser",
1002 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1006 // hit something -- check out what it was
1007 ELX = (LX + XS) / TILEX;
1008 ELY = (LY + YS) / TILEY;
1011 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1012 hit_mask, LX, LY, ELX, ELY);
1015 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1018 laser.dest_element = element;
1023 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1025 /* we have hit the top-right and bottom-left element --
1026 choose the bottom-left one */
1027 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1028 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1029 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1030 ELX = (LX - 2) / TILEX;
1031 ELY = (LY + 2) / TILEY;
1034 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1036 /* we have hit the top-left and bottom-right element --
1037 choose the top-left one */
1038 // !!! SEE ABOVE !!!
1039 ELX = (LX - 2) / TILEX;
1040 ELY = (LY - 2) / TILEY;
1044 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1045 hit_mask, LX, LY, ELX, ELY);
1048 last_element = element;
1050 element = Tile[ELX][ELY];
1051 laser.dest_element = element;
1054 Debug("game:mm:ScanLaser",
1055 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1058 LX % TILEX, LY % TILEY,
1063 if (!IN_LEV_FIELD(ELX, ELY))
1064 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1068 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1069 if (element == EL_EMPTY &&
1070 IS_GRID_STEEL(last_element) &&
1071 laser.current_angle % 4) // angle is not 90°
1072 element = last_element;
1074 if (element == EL_EMPTY)
1076 if (!HitOnlyAnEdge(hit_mask))
1079 else if (element == EL_FUSE_ON)
1081 if (HitPolarizer(element, hit_mask))
1084 else if (IS_GRID(element) || IS_DF_GRID(element))
1086 if (HitPolarizer(element, hit_mask))
1089 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1090 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1092 if (HitBlock(element, hit_mask))
1099 else if (IS_MCDUFFIN(element))
1101 if (HitLaserSource(element, hit_mask))
1104 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1105 IS_RECEIVER(element))
1107 if (HitLaserDestination(element, hit_mask))
1110 else if (IS_WALL(element))
1112 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1114 if (HitReflectingWalls(element, hit_mask))
1119 if (HitAbsorbingWalls(element, hit_mask))
1125 if (HitElement(element, hit_mask))
1130 DrawLaser(rf - 1, DL_LASER_ENABLED);
1131 rf = laser.num_edges;
1133 if (!IS_DF_WALL_STEEL(element))
1135 // only used for scanning DF steel walls; reset for all other elements
1143 if (laser.dest_element != Tile[ELX][ELY])
1145 Debug("game:mm:ScanLaser",
1146 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1147 laser.dest_element, Tile[ELX][ELY]);
1151 if (!end && !laser.stops_inside_element && !StepBehind())
1154 Debug("game:mm:ScanLaser", "Go one step back");
1160 AddLaserEdge(LX, LY);
1164 DrawLaser(rf - 1, DL_LASER_ENABLED);
1166 Ct = CT = FrameCounter;
1169 if (!IN_LEV_FIELD(ELX, ELY))
1170 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1174 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1180 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1181 start_edge, num_edges, mode);
1186 Warn("DrawLaserExt: start_edge < 0");
1193 Warn("DrawLaserExt: num_edges < 0");
1199 if (mode == DL_LASER_DISABLED)
1201 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1205 // now draw the laser to the backbuffer and (if enabled) to the screen
1206 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1208 redraw_mask |= REDRAW_FIELD;
1210 if (mode == DL_LASER_ENABLED)
1213 // after the laser was deleted, the "damaged" graphics must be restored
1214 if (laser.num_damages)
1216 int damage_start = 0;
1219 // determine the starting edge, from which graphics need to be restored
1222 for (i = 0; i < laser.num_damages; i++)
1224 if (laser.damage[i].edge == start_edge + 1)
1233 // restore graphics from this starting edge to the end of damage list
1234 for (i = damage_start; i < laser.num_damages; i++)
1236 int lx = laser.damage[i].x;
1237 int ly = laser.damage[i].y;
1238 int element = Tile[lx][ly];
1240 if (Hit[lx][ly] == laser.damage[i].edge)
1241 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1244 if (Box[lx][ly] == laser.damage[i].edge)
1247 if (IS_DRAWABLE(element))
1248 DrawField_MM(lx, ly);
1251 elx = laser.damage[damage_start].x;
1252 ely = laser.damage[damage_start].y;
1253 element = Tile[elx][ely];
1256 if (IS_BEAMER(element))
1260 for (i = 0; i < laser.num_beamers; i++)
1261 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1263 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1264 mode, elx, ely, Hit[elx][ely], start_edge);
1265 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1266 get_element_angle(element), laser.damage[damage_start].angle);
1270 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1271 laser.num_beamers > 0 &&
1272 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1274 // element is outgoing beamer
1275 laser.num_damages = damage_start + 1;
1277 if (IS_BEAMER(element))
1278 laser.current_angle = get_element_angle(element);
1282 // element is incoming beamer or other element
1283 laser.num_damages = damage_start;
1284 laser.current_angle = laser.damage[laser.num_damages].angle;
1289 // no damages but McDuffin himself (who needs to be redrawn anyway)
1291 elx = laser.start_edge.x;
1292 ely = laser.start_edge.y;
1293 element = Tile[elx][ely];
1296 laser.num_edges = start_edge + 1;
1297 if (start_edge == 0)
1298 laser.current_angle = laser.start_angle;
1300 LX = laser.edge[start_edge].x - cSX2;
1301 LY = laser.edge[start_edge].y - cSY2;
1302 XS = 2 * Step[laser.current_angle].x;
1303 YS = 2 * Step[laser.current_angle].y;
1306 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1312 if (IS_BEAMER(element) ||
1313 IS_FIBRE_OPTIC(element) ||
1314 IS_PACMAN(element) ||
1315 IS_POLAR(element) ||
1316 IS_POLAR_CROSS(element) ||
1317 element == EL_FUSE_ON)
1322 Debug("game:mm:DrawLaserExt", "element == %d", element);
1325 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1326 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1330 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1331 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1332 (laser.num_beamers == 0 ||
1333 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1335 // element is incoming beamer or other element
1336 step_size = -step_size;
1341 if (IS_BEAMER(element))
1342 Debug("game:mm:DrawLaserExt",
1343 "start_edge == %d, laser.beamer_edge == %d",
1344 start_edge, laser.beamer_edge);
1347 LX += step_size * XS;
1348 LY += step_size * YS;
1350 else if (element != EL_EMPTY)
1359 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1364 void DrawLaser(int start_edge, int mode)
1366 // do not draw laser if fuse is off
1367 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1370 if (mode == DL_LASER_DISABLED)
1371 DeactivateLaserTargetElement();
1373 if (laser.num_edges - start_edge < 0)
1375 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1380 // check if laser is interrupted by beamer element
1381 if (laser.num_beamers > 0 &&
1382 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1384 if (mode == DL_LASER_ENABLED)
1387 int tmp_start_edge = start_edge;
1389 // draw laser segments forward from the start to the last beamer
1390 for (i = 0; i < laser.num_beamers; i++)
1392 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1394 if (tmp_num_edges <= 0)
1398 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1399 i, laser.beamer_edge[i], tmp_start_edge);
1402 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1404 tmp_start_edge = laser.beamer_edge[i];
1407 // draw last segment from last beamer to the end
1408 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1414 int last_num_edges = laser.num_edges;
1415 int num_beamers = laser.num_beamers;
1417 // delete laser segments backward from the end to the first beamer
1418 for (i = num_beamers - 1; i >= 0; i--)
1420 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1422 if (laser.beamer_edge[i] - start_edge <= 0)
1425 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1427 last_num_edges = laser.beamer_edge[i];
1428 laser.num_beamers--;
1432 if (last_num_edges - start_edge <= 0)
1433 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1434 last_num_edges, start_edge);
1437 // special case when rotating first beamer: delete laser edge on beamer
1438 // (but do not start scanning on previous edge to prevent mirror sound)
1439 if (last_num_edges - start_edge == 1 && start_edge > 0)
1440 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1442 // delete first segment from start to the first beamer
1443 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1448 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1451 game_mm.laser_enabled = mode;
1454 void DrawLaser_MM(void)
1456 DrawLaser(0, game_mm.laser_enabled);
1459 boolean HitElement(int element, int hit_mask)
1461 if (HitOnlyAnEdge(hit_mask))
1464 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1465 element = MovingOrBlocked2Element_MM(ELX, ELY);
1468 Debug("game:mm:HitElement", "(1): element == %d", element);
1472 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1473 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1476 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1480 AddDamagedField(ELX, ELY);
1482 // this is more precise: check if laser would go through the center
1483 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1485 // prevent cutting through laser emitter with laser beam
1486 if (IS_LASER(element))
1489 // skip the whole element before continuing the scan
1495 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1497 if (LX/TILEX > ELX || LY/TILEY > ELY)
1499 /* skipping scan positions to the right and down skips one scan
1500 position too much, because this is only the top left scan position
1501 of totally four scan positions (plus one to the right, one to the
1502 bottom and one to the bottom right) */
1512 Debug("game:mm:HitElement", "(2): element == %d", element);
1515 if (LX + 5 * XS < 0 ||
1525 Debug("game:mm:HitElement", "(3): element == %d", element);
1528 if (IS_POLAR(element) &&
1529 ((element - EL_POLAR_START) % 2 ||
1530 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1532 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1534 laser.num_damages--;
1539 if (IS_POLAR_CROSS(element) &&
1540 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1542 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1544 laser.num_damages--;
1549 if (!IS_BEAMER(element) &&
1550 !IS_FIBRE_OPTIC(element) &&
1551 !IS_GRID_WOOD(element) &&
1552 element != EL_FUEL_EMPTY)
1555 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1556 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1558 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1561 LX = ELX * TILEX + 14;
1562 LY = ELY * TILEY + 14;
1564 AddLaserEdge(LX, LY);
1567 if (IS_MIRROR(element) ||
1568 IS_MIRROR_FIXED(element) ||
1569 IS_POLAR(element) ||
1570 IS_POLAR_CROSS(element) ||
1571 IS_DF_MIRROR(element) ||
1572 IS_DF_MIRROR_AUTO(element) ||
1573 element == EL_PRISM ||
1574 element == EL_REFRACTOR)
1576 int current_angle = laser.current_angle;
1579 laser.num_damages--;
1581 AddDamagedField(ELX, ELY);
1583 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1586 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1588 if (IS_MIRROR(element) ||
1589 IS_MIRROR_FIXED(element) ||
1590 IS_DF_MIRROR(element) ||
1591 IS_DF_MIRROR_AUTO(element))
1592 laser.current_angle = get_mirrored_angle(laser.current_angle,
1593 get_element_angle(element));
1595 if (element == EL_PRISM || element == EL_REFRACTOR)
1596 laser.current_angle = RND(16);
1598 XS = 2 * Step[laser.current_angle].x;
1599 YS = 2 * Step[laser.current_angle].y;
1601 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1606 LX += step_size * XS;
1607 LY += step_size * YS;
1609 // draw sparkles on mirror
1610 if ((IS_MIRROR(element) ||
1611 IS_MIRROR_FIXED(element) ||
1612 element == EL_PRISM) &&
1613 current_angle != laser.current_angle)
1615 MovDelay[ELX][ELY] = 11; // start animation
1618 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1619 current_angle != laser.current_angle)
1620 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1623 (get_opposite_angle(laser.current_angle) ==
1624 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1626 return (laser.overloaded ? TRUE : FALSE);
1629 if (element == EL_FUEL_FULL)
1631 laser.stops_inside_element = TRUE;
1636 if (element == EL_BOMB || element == EL_MINE)
1638 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1640 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1642 laser.dest_element_last = Tile[ELX][ELY];
1643 laser.dest_element_last_x = ELX;
1644 laser.dest_element_last_y = ELY;
1646 if (element == EL_MINE)
1647 laser.overloaded = TRUE;
1650 if (element == EL_KETTLE ||
1651 element == EL_CELL ||
1652 element == EL_KEY ||
1653 element == EL_LIGHTBALL ||
1654 element == EL_PACMAN ||
1657 if (!IS_PACMAN(element))
1660 if (element == EL_PACMAN)
1663 if (element == EL_KETTLE || element == EL_CELL)
1665 if (game_mm.kettles_still_needed > 0)
1666 game_mm.kettles_still_needed--;
1668 game.snapshot.collected_item = TRUE;
1670 if (game_mm.kettles_still_needed == 0)
1674 DrawLaser(0, DL_LASER_ENABLED);
1677 else if (element == EL_KEY)
1681 else if (IS_PACMAN(element))
1683 DeletePacMan(ELX, ELY);
1686 RaiseScoreElement_MM(element);
1691 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1693 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1695 DrawLaser(0, DL_LASER_ENABLED);
1697 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1699 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1700 game_mm.lights_still_needed--;
1704 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1705 game_mm.lights_still_needed++;
1708 DrawField_MM(ELX, ELY);
1709 DrawLaser(0, DL_LASER_ENABLED);
1714 laser.stops_inside_element = TRUE;
1720 Debug("game:mm:HitElement", "(4): element == %d", element);
1723 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1724 laser.num_beamers < MAX_NUM_BEAMERS &&
1725 laser.beamer[BEAMER_NR(element)][1].num)
1727 int beamer_angle = get_element_angle(element);
1728 int beamer_nr = BEAMER_NR(element);
1732 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1735 laser.num_damages--;
1737 if (IS_FIBRE_OPTIC(element) ||
1738 laser.current_angle == get_opposite_angle(beamer_angle))
1742 LX = ELX * TILEX + 14;
1743 LY = ELY * TILEY + 14;
1745 AddLaserEdge(LX, LY);
1746 AddDamagedField(ELX, ELY);
1748 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1751 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1753 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1754 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1755 ELX = laser.beamer[beamer_nr][pos].x;
1756 ELY = laser.beamer[beamer_nr][pos].y;
1757 LX = ELX * TILEX + 14;
1758 LY = ELY * TILEY + 14;
1760 if (IS_BEAMER(element))
1762 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1763 XS = 2 * Step[laser.current_angle].x;
1764 YS = 2 * Step[laser.current_angle].y;
1767 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1769 AddLaserEdge(LX, LY);
1770 AddDamagedField(ELX, ELY);
1772 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1775 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1777 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1782 LX += step_size * XS;
1783 LY += step_size * YS;
1785 laser.num_beamers++;
1794 boolean HitOnlyAnEdge(int hit_mask)
1796 // check if the laser hit only the edge of an element and, if so, go on
1799 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1803 if ((hit_mask == HIT_MASK_TOPLEFT ||
1804 hit_mask == HIT_MASK_TOPRIGHT ||
1805 hit_mask == HIT_MASK_BOTTOMLEFT ||
1806 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1807 laser.current_angle % 4) // angle is not 90°
1811 if (hit_mask == HIT_MASK_TOPLEFT)
1816 else if (hit_mask == HIT_MASK_TOPRIGHT)
1821 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1826 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1832 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1838 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1845 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1851 boolean HitPolarizer(int element, int hit_mask)
1853 if (HitOnlyAnEdge(hit_mask))
1856 if (IS_DF_GRID(element))
1858 int grid_angle = get_element_angle(element);
1861 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1862 grid_angle, laser.current_angle);
1865 AddLaserEdge(LX, LY);
1866 AddDamagedField(ELX, ELY);
1869 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1871 if (laser.current_angle == grid_angle ||
1872 laser.current_angle == get_opposite_angle(grid_angle))
1874 // skip the whole element before continuing the scan
1880 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1882 if (LX/TILEX > ELX || LY/TILEY > ELY)
1884 /* skipping scan positions to the right and down skips one scan
1885 position too much, because this is only the top left scan position
1886 of totally four scan positions (plus one to the right, one to the
1887 bottom and one to the bottom right) */
1893 AddLaserEdge(LX, LY);
1899 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1901 LX / TILEX, LY / TILEY,
1902 LX % TILEX, LY % TILEY);
1907 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1909 return HitReflectingWalls(element, hit_mask);
1913 return HitAbsorbingWalls(element, hit_mask);
1916 else if (IS_GRID_STEEL(element))
1918 return HitReflectingWalls(element, hit_mask);
1920 else // IS_GRID_WOOD
1922 return HitAbsorbingWalls(element, hit_mask);
1928 boolean HitBlock(int element, int hit_mask)
1930 boolean check = FALSE;
1932 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1933 game_mm.num_keys == 0)
1936 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1939 int ex = ELX * TILEX + 14;
1940 int ey = ELY * TILEY + 14;
1944 for (i = 1; i < 32; i++)
1949 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1954 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1955 return HitAbsorbingWalls(element, hit_mask);
1959 AddLaserEdge(LX - XS, LY - YS);
1960 AddDamagedField(ELX, ELY);
1963 Box[ELX][ELY] = laser.num_edges;
1965 return HitReflectingWalls(element, hit_mask);
1968 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1970 int xs = XS / 2, ys = YS / 2;
1971 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1972 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1974 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1975 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1977 laser.overloaded = (element == EL_GATE_STONE);
1982 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1983 (hit_mask == HIT_MASK_TOP ||
1984 hit_mask == HIT_MASK_LEFT ||
1985 hit_mask == HIT_MASK_RIGHT ||
1986 hit_mask == HIT_MASK_BOTTOM))
1987 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1988 hit_mask == HIT_MASK_BOTTOM),
1989 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1990 hit_mask == HIT_MASK_RIGHT));
1991 AddLaserEdge(LX, LY);
1997 if (element == EL_GATE_STONE && Box[ELX][ELY])
1999 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2011 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2013 int xs = XS / 2, ys = YS / 2;
2014 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2015 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2017 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2018 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2020 laser.overloaded = (element == EL_BLOCK_STONE);
2025 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2026 (hit_mask == HIT_MASK_TOP ||
2027 hit_mask == HIT_MASK_LEFT ||
2028 hit_mask == HIT_MASK_RIGHT ||
2029 hit_mask == HIT_MASK_BOTTOM))
2030 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2031 hit_mask == HIT_MASK_BOTTOM),
2032 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2033 hit_mask == HIT_MASK_RIGHT));
2034 AddDamagedField(ELX, ELY);
2036 LX = ELX * TILEX + 14;
2037 LY = ELY * TILEY + 14;
2039 AddLaserEdge(LX, LY);
2041 laser.stops_inside_element = TRUE;
2049 boolean HitLaserSource(int element, int hit_mask)
2051 if (HitOnlyAnEdge(hit_mask))
2054 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2056 laser.overloaded = TRUE;
2061 boolean HitLaserDestination(int element, int hit_mask)
2063 if (HitOnlyAnEdge(hit_mask))
2066 if (element != EL_EXIT_OPEN &&
2067 !(IS_RECEIVER(element) &&
2068 game_mm.kettles_still_needed == 0 &&
2069 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2071 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2076 if (IS_RECEIVER(element) ||
2077 (IS_22_5_ANGLE(laser.current_angle) &&
2078 (ELX != (LX + 6 * XS) / TILEX ||
2079 ELY != (LY + 6 * YS) / TILEY ||
2088 LX = ELX * TILEX + 14;
2089 LY = ELY * TILEY + 14;
2091 laser.stops_inside_element = TRUE;
2094 AddLaserEdge(LX, LY);
2095 AddDamagedField(ELX, ELY);
2097 if (game_mm.lights_still_needed == 0)
2099 game_mm.level_solved = TRUE;
2101 SetTileCursorActive(FALSE);
2107 boolean HitReflectingWalls(int element, int hit_mask)
2109 // check if laser hits side of a wall with an angle that is not 90°
2110 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2111 hit_mask == HIT_MASK_LEFT ||
2112 hit_mask == HIT_MASK_RIGHT ||
2113 hit_mask == HIT_MASK_BOTTOM))
2115 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2120 if (!IS_DF_GRID(element))
2121 AddLaserEdge(LX, LY);
2123 // check if laser hits wall with an angle of 45°
2124 if (!IS_22_5_ANGLE(laser.current_angle))
2126 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2129 laser.current_angle = get_mirrored_angle(laser.current_angle,
2132 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2135 laser.current_angle = get_mirrored_angle(laser.current_angle,
2139 AddLaserEdge(LX, LY);
2141 XS = 2 * Step[laser.current_angle].x;
2142 YS = 2 * Step[laser.current_angle].y;
2146 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2148 laser.current_angle = get_mirrored_angle(laser.current_angle,
2153 if (!IS_DF_GRID(element))
2154 AddLaserEdge(LX, LY);
2159 if (!IS_DF_GRID(element))
2160 AddLaserEdge(LX, LY + YS / 2);
2163 if (!IS_DF_GRID(element))
2164 AddLaserEdge(LX, LY);
2167 YS = 2 * Step[laser.current_angle].y;
2171 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2173 laser.current_angle = get_mirrored_angle(laser.current_angle,
2178 if (!IS_DF_GRID(element))
2179 AddLaserEdge(LX, LY);
2184 if (!IS_DF_GRID(element))
2185 AddLaserEdge(LX + XS / 2, LY);
2188 if (!IS_DF_GRID(element))
2189 AddLaserEdge(LX, LY);
2192 XS = 2 * Step[laser.current_angle].x;
2198 // reflection at the edge of reflecting DF style wall
2199 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2201 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2202 hit_mask == HIT_MASK_TOPRIGHT) ||
2203 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2204 hit_mask == HIT_MASK_TOPLEFT) ||
2205 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2206 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2207 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2208 hit_mask == HIT_MASK_BOTTOMRIGHT))
2211 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2212 ANG_MIRROR_135 : ANG_MIRROR_45);
2214 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2216 AddDamagedField(ELX, ELY);
2217 AddLaserEdge(LX, LY);
2219 laser.current_angle = get_mirrored_angle(laser.current_angle,
2227 AddLaserEdge(LX, LY);
2233 // reflection inside an edge of reflecting DF style wall
2234 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2236 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2237 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2238 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2239 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2240 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2241 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2242 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2243 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2246 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2247 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2248 ANG_MIRROR_135 : ANG_MIRROR_45);
2250 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2253 AddDamagedField(ELX, ELY);
2256 AddLaserEdge(LX - XS, LY - YS);
2257 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2258 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2260 laser.current_angle = get_mirrored_angle(laser.current_angle,
2268 AddLaserEdge(LX, LY);
2274 // check if laser hits DF style wall with an angle of 90°
2275 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2277 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2278 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2279 (IS_VERT_ANGLE(laser.current_angle) &&
2280 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2282 // laser at last step touched nothing or the same side of the wall
2283 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2285 AddDamagedField(ELX, ELY);
2292 last_hit_mask = hit_mask;
2299 if (!HitOnlyAnEdge(hit_mask))
2301 laser.overloaded = TRUE;
2309 boolean HitAbsorbingWalls(int element, int hit_mask)
2311 if (HitOnlyAnEdge(hit_mask))
2315 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2317 AddLaserEdge(LX - XS, LY - YS);
2324 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2326 AddLaserEdge(LX - XS, LY - YS);
2332 if (IS_WALL_WOOD(element) ||
2333 IS_DF_WALL_WOOD(element) ||
2334 IS_GRID_WOOD(element) ||
2335 IS_GRID_WOOD_FIXED(element) ||
2336 IS_GRID_WOOD_AUTO(element) ||
2337 element == EL_FUSE_ON ||
2338 element == EL_BLOCK_WOOD ||
2339 element == EL_GATE_WOOD)
2341 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2346 if (IS_WALL_ICE(element))
2350 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2351 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2353 // check if laser hits wall with an angle of 90°
2354 if (IS_90_ANGLE(laser.current_angle))
2355 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2357 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2361 for (i = 0; i < 4; i++)
2363 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2364 mask = 15 - (8 >> i);
2365 else if (ABS(XS) == 4 &&
2367 (XS > 0) == (i % 2) &&
2368 (YS < 0) == (i / 2))
2369 mask = 3 + (i / 2) * 9;
2370 else if (ABS(YS) == 4 &&
2372 (XS < 0) == (i % 2) &&
2373 (YS > 0) == (i / 2))
2374 mask = 5 + (i % 2) * 5;
2378 laser.wall_mask = mask;
2380 else if (IS_WALL_AMOEBA(element))
2382 int elx = (LX - 2 * XS) / TILEX;
2383 int ely = (LY - 2 * YS) / TILEY;
2384 int element2 = Tile[elx][ely];
2387 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2389 laser.dest_element = EL_EMPTY;
2397 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2398 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2400 if (IS_90_ANGLE(laser.current_angle))
2401 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2403 laser.dest_element = element2 | EL_WALL_AMOEBA;
2405 laser.wall_mask = mask;
2411 static void OpenExit(int x, int y)
2415 if (!MovDelay[x][y]) // next animation frame
2416 MovDelay[x][y] = 4 * delay;
2418 if (MovDelay[x][y]) // wait some time before next frame
2423 phase = MovDelay[x][y] / delay;
2425 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2426 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2428 if (!MovDelay[x][y])
2430 Tile[x][y] = EL_EXIT_OPEN;
2436 static void OpenSurpriseBall(int x, int y)
2440 if (!MovDelay[x][y]) // next animation frame
2441 MovDelay[x][y] = 50 * delay;
2443 if (MovDelay[x][y]) // wait some time before next frame
2447 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2450 int graphic = el2gfx(Store[x][y]);
2452 int dx = RND(26), dy = RND(26);
2454 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2456 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2457 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2459 MarkTileDirty(x, y);
2462 if (!MovDelay[x][y])
2464 Tile[x][y] = Store[x][y];
2465 Store[x][y] = Store2[x][y] = 0;
2474 static void MeltIce(int x, int y)
2479 if (!MovDelay[x][y]) // next animation frame
2480 MovDelay[x][y] = frames * delay;
2482 if (MovDelay[x][y]) // wait some time before next frame
2485 int wall_mask = Store2[x][y];
2486 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2489 phase = frames - MovDelay[x][y] / delay - 1;
2491 if (!MovDelay[x][y])
2495 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2496 Store[x][y] = Store2[x][y] = 0;
2498 DrawWalls_MM(x, y, Tile[x][y]);
2500 if (Tile[x][y] == EL_WALL_ICE)
2501 Tile[x][y] = EL_EMPTY;
2503 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2504 if (laser.damage[i].is_mirror)
2508 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2510 DrawLaser(0, DL_LASER_DISABLED);
2514 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2516 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2518 laser.redraw = TRUE;
2523 static void GrowAmoeba(int x, int y)
2528 if (!MovDelay[x][y]) // next animation frame
2529 MovDelay[x][y] = frames * delay;
2531 if (MovDelay[x][y]) // wait some time before next frame
2534 int wall_mask = Store2[x][y];
2535 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2538 phase = MovDelay[x][y] / delay;
2540 if (!MovDelay[x][y])
2542 Tile[x][y] = real_element;
2543 Store[x][y] = Store2[x][y] = 0;
2545 DrawWalls_MM(x, y, Tile[x][y]);
2546 DrawLaser(0, DL_LASER_ENABLED);
2548 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2550 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2555 static void DrawFieldAnimated_MM(int x, int y)
2557 int element = Tile[x][y];
2559 if (IS_BLOCKED(x, y))
2564 if (IS_MIRROR(element) ||
2565 IS_MIRROR_FIXED(element) ||
2566 element == EL_PRISM)
2568 if (MovDelay[x][y] != 0) // wait some time before next frame
2572 if (MovDelay[x][y] != 0)
2574 int graphic = IMG_TWINKLE_WHITE;
2575 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2577 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2582 laser.redraw = TRUE;
2585 static void Explode_MM(int x, int y, int phase, int mode)
2587 int num_phase = 9, delay = 2;
2588 int last_phase = num_phase * delay;
2589 int half_phase = (num_phase / 2) * delay;
2591 laser.redraw = TRUE;
2593 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2595 int center_element = Tile[x][y];
2597 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2599 // put moving element to center field (and let it explode there)
2600 center_element = MovingOrBlocked2Element_MM(x, y);
2601 RemoveMovingField_MM(x, y);
2603 Tile[x][y] = center_element;
2606 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2607 Store[x][y] = center_element;
2609 Store[x][y] = EL_EMPTY;
2611 Store2[x][y] = mode;
2613 Tile[x][y] = EL_EXPLODING_OPAQUE;
2614 GfxElement[x][y] = center_element;
2616 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2618 ExplodePhase[x][y] = 1;
2624 GfxFrame[x][y] = 0; // restart explosion animation
2626 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2628 if (phase == half_phase)
2630 Tile[x][y] = EL_EXPLODING_TRANSP;
2632 if (x == ELX && y == ELY)
2636 if (phase == last_phase)
2638 if (Store[x][y] == EL_BOMB_ACTIVE)
2640 DrawLaser(0, DL_LASER_DISABLED);
2643 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2644 Store[x][y] = EL_EMPTY;
2646 GameOver_MM(GAME_OVER_DELAYED);
2648 laser.overloaded = FALSE;
2650 else if (IS_MCDUFFIN(Store[x][y]))
2652 Store[x][y] = EL_EMPTY;
2654 GameOver_MM(GAME_OVER_BOMB);
2657 Tile[x][y] = Store[x][y];
2658 Store[x][y] = Store2[x][y] = 0;
2659 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2664 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2666 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2667 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2669 DrawGraphicAnimation_MM(x, y, graphic, frame);
2671 MarkTileDirty(x, y);
2675 static void Bang_MM(int x, int y)
2677 int element = Tile[x][y];
2680 DrawLaser(0, DL_LASER_ENABLED);
2683 if (IS_PACMAN(element))
2684 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2685 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2686 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2687 else if (element == EL_KEY)
2688 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2690 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2692 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2695 void TurnRound(int x, int y)
2707 { 0, 0 }, { 0, 0 }, { 0, 0 },
2712 int left, right, back;
2716 { MV_DOWN, MV_UP, MV_RIGHT },
2717 { MV_UP, MV_DOWN, MV_LEFT },
2719 { MV_LEFT, MV_RIGHT, MV_DOWN },
2720 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2721 { MV_RIGHT, MV_LEFT, MV_UP }
2724 int element = Tile[x][y];
2725 int old_move_dir = MovDir[x][y];
2726 int right_dir = turn[old_move_dir].right;
2727 int back_dir = turn[old_move_dir].back;
2728 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2729 int right_x = x + right_dx, right_y = y + right_dy;
2731 if (element == EL_PACMAN)
2733 boolean can_turn_right = FALSE;
2735 if (IN_LEV_FIELD(right_x, right_y) &&
2736 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2737 can_turn_right = TRUE;
2740 MovDir[x][y] = right_dir;
2742 MovDir[x][y] = back_dir;
2748 static void StartMoving_MM(int x, int y)
2750 int element = Tile[x][y];
2755 if (CAN_MOVE(element))
2759 if (MovDelay[x][y]) // wait some time before next movement
2767 // now make next step
2769 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2771 if (element == EL_PACMAN &&
2772 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2773 !ObjHit(newx, newy, HIT_POS_CENTER))
2775 Store[newx][newy] = Tile[newx][newy];
2776 Tile[newx][newy] = EL_EMPTY;
2778 DrawField_MM(newx, newy);
2780 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2781 ObjHit(newx, newy, HIT_POS_CENTER))
2783 // object was running against a wall
2790 InitMovingField_MM(x, y, MovDir[x][y]);
2794 ContinueMoving_MM(x, y);
2797 static void ContinueMoving_MM(int x, int y)
2799 int element = Tile[x][y];
2800 int direction = MovDir[x][y];
2801 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2802 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2803 int horiz_move = (dx!=0);
2804 int newx = x + dx, newy = y + dy;
2805 int step = (horiz_move ? dx : dy) * TILEX / 8;
2807 MovPos[x][y] += step;
2809 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2811 Tile[x][y] = EL_EMPTY;
2812 Tile[newx][newy] = element;
2814 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2815 MovDelay[newx][newy] = 0;
2817 if (!CAN_MOVE(element))
2818 MovDir[newx][newy] = 0;
2821 DrawField_MM(newx, newy);
2823 Stop[newx][newy] = TRUE;
2825 if (element == EL_PACMAN)
2827 if (Store[newx][newy] == EL_BOMB)
2828 Bang_MM(newx, newy);
2830 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2831 (LX + 2 * XS) / TILEX == newx &&
2832 (LY + 2 * YS) / TILEY == newy)
2839 else // still moving on
2844 laser.redraw = TRUE;
2847 boolean ClickElement(int x, int y, int button)
2849 static DelayCounter click_delay = { CLICK_DELAY };
2850 static boolean new_button = TRUE;
2851 boolean element_clicked = FALSE;
2856 // initialize static variables
2857 click_delay.count = 0;
2858 click_delay.value = CLICK_DELAY;
2864 // do not rotate objects hit by the laser after the game was solved
2865 if (game_mm.level_solved && Hit[x][y])
2868 if (button == MB_RELEASED)
2871 click_delay.value = CLICK_DELAY;
2873 // release eventually hold auto-rotating mirror
2874 RotateMirror(x, y, MB_RELEASED);
2879 if (!FrameReached(&click_delay) && !new_button)
2882 if (button == MB_MIDDLEBUTTON) // middle button has no function
2885 if (!IN_LEV_FIELD(x, y))
2888 if (Tile[x][y] == EL_EMPTY)
2891 element = Tile[x][y];
2893 if (IS_MIRROR(element) ||
2894 IS_BEAMER(element) ||
2895 IS_POLAR(element) ||
2896 IS_POLAR_CROSS(element) ||
2897 IS_DF_MIRROR(element) ||
2898 IS_DF_MIRROR_AUTO(element))
2900 RotateMirror(x, y, button);
2902 element_clicked = TRUE;
2904 else if (IS_MCDUFFIN(element))
2906 if (!laser.fuse_off)
2908 DrawLaser(0, DL_LASER_DISABLED);
2915 element = get_rotated_element(element, BUTTON_ROTATION(button));
2916 laser.start_angle = get_element_angle(element);
2920 Tile[x][y] = element;
2927 if (!laser.fuse_off)
2930 element_clicked = TRUE;
2932 else if (element == EL_FUSE_ON && laser.fuse_off)
2934 if (x != laser.fuse_x || y != laser.fuse_y)
2937 laser.fuse_off = FALSE;
2938 laser.fuse_x = laser.fuse_y = -1;
2940 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2943 element_clicked = TRUE;
2945 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2947 laser.fuse_off = TRUE;
2950 laser.overloaded = FALSE;
2952 DrawLaser(0, DL_LASER_DISABLED);
2953 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2955 element_clicked = TRUE;
2957 else if (element == EL_LIGHTBALL)
2960 RaiseScoreElement_MM(element);
2961 DrawLaser(0, DL_LASER_ENABLED);
2963 element_clicked = TRUE;
2966 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2969 return element_clicked;
2972 void RotateMirror(int x, int y, int button)
2974 if (button == MB_RELEASED)
2976 // release eventually hold auto-rotating mirror
2983 if (IS_MIRROR(Tile[x][y]) ||
2984 IS_POLAR_CROSS(Tile[x][y]) ||
2985 IS_POLAR(Tile[x][y]) ||
2986 IS_BEAMER(Tile[x][y]) ||
2987 IS_DF_MIRROR(Tile[x][y]) ||
2988 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2989 IS_GRID_WOOD_AUTO(Tile[x][y]))
2991 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2993 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2995 if (button == MB_LEFTBUTTON)
2997 // left mouse button only for manual adjustment, no auto-rotating;
2998 // freeze mirror for until mouse button released
3002 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3004 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3008 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3010 int edge = Hit[x][y];
3016 DrawLaser(edge - 1, DL_LASER_DISABLED);
3020 else if (ObjHit(x, y, HIT_POS_CENTER))
3022 int edge = Hit[x][y];
3026 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3031 DrawLaser(edge - 1, DL_LASER_DISABLED);
3038 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3043 if ((IS_BEAMER(Tile[x][y]) ||
3044 IS_POLAR(Tile[x][y]) ||
3045 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3049 if (IS_BEAMER(Tile[x][y]))
3052 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3053 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3065 DrawLaser(0, DL_LASER_ENABLED);
3069 static void AutoRotateMirrors(void)
3073 if (!FrameReached(&rotate_delay))
3076 for (x = 0; x < lev_fieldx; x++)
3078 for (y = 0; y < lev_fieldy; y++)
3080 int element = Tile[x][y];
3082 // do not rotate objects hit by the laser after the game was solved
3083 if (game_mm.level_solved && Hit[x][y])
3086 if (IS_DF_MIRROR_AUTO(element) ||
3087 IS_GRID_WOOD_AUTO(element) ||
3088 IS_GRID_STEEL_AUTO(element) ||
3089 element == EL_REFRACTOR)
3090 RotateMirror(x, y, MB_RIGHTBUTTON);
3095 boolean ObjHit(int obx, int oby, int bits)
3102 if (bits & HIT_POS_CENTER)
3104 if (CheckLaserPixel(cSX + obx + 15,
3109 if (bits & HIT_POS_EDGE)
3111 for (i = 0; i < 4; i++)
3112 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3113 cSY + oby + 31 * (i / 2)))
3117 if (bits & HIT_POS_BETWEEN)
3119 for (i = 0; i < 4; i++)
3120 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3121 cSY + 4 + oby + 22 * (i / 2)))
3128 void DeletePacMan(int px, int py)
3134 if (game_mm.num_pacman <= 1)
3136 game_mm.num_pacman = 0;
3140 for (i = 0; i < game_mm.num_pacman; i++)
3141 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3144 game_mm.num_pacman--;
3146 for (j = i; j < game_mm.num_pacman; j++)
3148 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3149 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3150 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3154 void ColorCycling(void)
3156 static int CC, Cc = 0;
3158 static int color, old = 0xF00, new = 0x010, mult = 1;
3159 static unsigned short red, green, blue;
3161 if (color_status == STATIC_COLORS)
3166 if (CC < Cc || CC > Cc + 2)
3170 color = old + new * mult;
3176 if (ABS(mult) == 16)
3186 red = 0x0e00 * ((color & 0xF00) >> 8);
3187 green = 0x0e00 * ((color & 0x0F0) >> 4);
3188 blue = 0x0e00 * ((color & 0x00F));
3189 SetRGB(pen_magicolor[0], red, green, blue);
3191 red = 0x1111 * ((color & 0xF00) >> 8);
3192 green = 0x1111 * ((color & 0x0F0) >> 4);
3193 blue = 0x1111 * ((color & 0x00F));
3194 SetRGB(pen_magicolor[1], red, green, blue);
3198 static void GameActions_MM_Ext(void)
3205 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3208 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3210 element = Tile[x][y];
3212 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3213 StartMoving_MM(x, y);
3214 else if (IS_MOVING(x, y))
3215 ContinueMoving_MM(x, y);
3216 else if (IS_EXPLODING(element))
3217 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3218 else if (element == EL_EXIT_OPENING)
3220 else if (element == EL_GRAY_BALL_OPENING)
3221 OpenSurpriseBall(x, y);
3222 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3224 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3227 DrawFieldAnimated_MM(x, y);
3230 AutoRotateMirrors();
3233 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3235 // redraw after Explode_MM() ...
3237 DrawLaser(0, DL_LASER_ENABLED);
3238 laser.redraw = FALSE;
3243 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3247 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3249 DrawLaser(0, DL_LASER_DISABLED);
3254 // skip all following game actions if game is over
3255 if (game_mm.game_over)
3258 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3262 GameOver_MM(GAME_OVER_NO_ENERGY);
3267 if (FrameReached(&energy_delay))
3269 if (game_mm.energy_left > 0)
3270 game_mm.energy_left--;
3272 // when out of energy, wait another frame to play "out of time" sound
3275 element = laser.dest_element;
3278 if (element != Tile[ELX][ELY])
3280 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3281 element, Tile[ELX][ELY]);
3285 if (!laser.overloaded && laser.overload_value == 0 &&
3286 element != EL_BOMB &&
3287 element != EL_BOMB_ACTIVE &&
3288 element != EL_MINE &&
3289 element != EL_MINE_ACTIVE &&
3290 element != EL_BALL_GRAY &&
3291 element != EL_BLOCK_STONE &&
3292 element != EL_BLOCK_WOOD &&
3293 element != EL_FUSE_ON &&
3294 element != EL_FUEL_FULL &&
3295 !IS_WALL_ICE(element) &&
3296 !IS_WALL_AMOEBA(element))
3299 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3301 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3302 (!laser.overloaded && laser.overload_value > 0)) &&
3303 FrameReached(&overload_delay))
3305 if (laser.overloaded)
3306 laser.overload_value++;
3308 laser.overload_value--;
3310 if (game_mm.cheat_no_overload)
3312 laser.overloaded = FALSE;
3313 laser.overload_value = 0;
3316 game_mm.laser_overload_value = laser.overload_value;
3318 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3320 SetLaserColor(0xFF);
3322 DrawLaser(0, DL_LASER_ENABLED);
3325 if (!laser.overloaded)
3326 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3327 else if (setup.sound_loops)
3328 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3330 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3332 if (laser.overloaded)
3335 BlitBitmap(pix[PIX_DOOR], drawto,
3336 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3337 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3338 - laser.overload_value,
3339 OVERLOAD_XSIZE, laser.overload_value,
3340 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3341 - laser.overload_value);
3343 redraw_mask |= REDRAW_DOOR_1;
3348 BlitBitmap(pix[PIX_DOOR], drawto,
3349 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3350 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3351 DX_OVERLOAD, DY_OVERLOAD);
3353 redraw_mask |= REDRAW_DOOR_1;
3356 if (laser.overload_value == MAX_LASER_OVERLOAD)
3358 UpdateAndDisplayGameControlValues();
3362 GameOver_MM(GAME_OVER_OVERLOADED);
3373 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3375 if (game_mm.cheat_no_explosion)
3380 laser.dest_element = EL_EXPLODING_OPAQUE;
3385 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3387 laser.fuse_off = TRUE;
3391 DrawLaser(0, DL_LASER_DISABLED);
3392 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3395 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3397 if (!Store2[ELX][ELY]) // check if content element not yet determined
3399 int last_anim_random_frame = gfx.anim_random_frame;
3402 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3403 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3405 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3406 native_mm_level.ball_choice_mode, 0,
3407 game_mm.ball_choice_pos);
3409 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3410 gfx.anim_random_frame = last_anim_random_frame;
3412 game_mm.ball_choice_pos++;
3414 int new_element = native_mm_level.ball_content[element_pos];
3416 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3417 Store2[ELX][ELY] = TRUE;
3420 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3422 // !!! CHECK AGAIN: Laser on Polarizer !!!
3425 laser.dest_element_last = Tile[ELX][ELY];
3426 laser.dest_element_last_x = ELX;
3427 laser.dest_element_last_y = ELY;
3437 element = EL_MIRROR_START + RND(16);
3443 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3450 element = (rnd == 0 ? EL_FUSE_ON :
3451 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3452 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3453 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3454 EL_MIRROR_FIXED_START + rnd - 25);
3459 graphic = el2gfx(element);
3461 for (i = 0; i < 50; i++)
3467 BlitBitmap(pix[PIX_BACK], drawto,
3468 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3469 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3470 SX + ELX * TILEX + x,
3471 SY + ELY * TILEY + y);
3473 MarkTileDirty(ELX, ELY);
3476 DrawLaser(0, DL_LASER_ENABLED);
3478 Delay_WithScreenUpdates(50);
3481 Tile[ELX][ELY] = element;
3482 DrawField_MM(ELX, ELY);
3485 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3488 // above stuff: GRAY BALL -> PRISM !!!
3490 LX = ELX * TILEX + 14;
3491 LY = ELY * TILEY + 14;
3492 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3499 laser.num_edges -= 2;
3500 laser.num_damages--;
3504 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3505 if (laser.damage[i].is_mirror)
3509 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3511 DrawLaser(0, DL_LASER_DISABLED);
3513 DrawLaser(0, DL_LASER_DISABLED);
3522 if (IS_WALL_ICE(element) && CT > 50)
3524 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3527 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3528 Store[ELX][ELY] = EL_WALL_ICE;
3529 Store2[ELX][ELY] = laser.wall_mask;
3531 laser.dest_element = Tile[ELX][ELY];
3536 for (i = 0; i < 5; i++)
3542 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3546 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3548 Delay_WithScreenUpdates(100);
3551 if (Tile[ELX][ELY] == EL_WALL_ICE)
3552 Tile[ELX][ELY] = EL_EMPTY;
3556 LX = laser.edge[laser.num_edges].x - cSX2;
3557 LY = laser.edge[laser.num_edges].y - cSY2;
3560 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3561 if (laser.damage[i].is_mirror)
3565 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3567 DrawLaser(0, DL_LASER_DISABLED);
3574 if (IS_WALL_AMOEBA(element) && CT > 60)
3576 int k1, k2, k3, dx, dy, de, dm;
3577 int element2 = Tile[ELX][ELY];
3579 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3582 for (i = laser.num_damages - 1; i >= 0; i--)
3583 if (laser.damage[i].is_mirror)
3586 r = laser.num_edges;
3587 d = laser.num_damages;
3594 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3597 DrawLaser(0, DL_LASER_ENABLED);
3600 x = laser.damage[k1].x;
3601 y = laser.damage[k1].y;
3606 for (i = 0; i < 4; i++)
3608 if (laser.wall_mask & (1 << i))
3610 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3611 cSY + ELY * TILEY + 31 * (i / 2)))
3614 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3615 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3622 for (i = 0; i < 4; i++)
3624 if (laser.wall_mask & (1 << i))
3626 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3627 cSY + ELY * TILEY + 31 * (i / 2)))
3634 if (laser.num_beamers > 0 ||
3635 k1 < 1 || k2 < 4 || k3 < 4 ||
3636 CheckLaserPixel(cSX + ELX * TILEX + 14,
3637 cSY + ELY * TILEY + 14))
3639 laser.num_edges = r;
3640 laser.num_damages = d;
3642 DrawLaser(0, DL_LASER_DISABLED);
3645 Tile[ELX][ELY] = element | laser.wall_mask;
3649 de = Tile[ELX][ELY];
3650 dm = laser.wall_mask;
3654 int x = ELX, y = ELY;
3655 int wall_mask = laser.wall_mask;
3658 DrawLaser(0, DL_LASER_ENABLED);
3660 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3662 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3663 Store[x][y] = EL_WALL_AMOEBA;
3664 Store2[x][y] = wall_mask;
3670 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3672 DrawLaser(0, DL_LASER_ENABLED);
3674 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3676 for (i = 4; i >= 0; i--)
3678 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3681 Delay_WithScreenUpdates(20);
3684 DrawLaser(0, DL_LASER_ENABLED);
3689 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3690 laser.stops_inside_element && CT > native_mm_level.time_block)
3695 if (ABS(XS) > ABS(YS))
3702 for (i = 0; i < 4; i++)
3709 x = ELX + Step[k * 4].x;
3710 y = ELY + Step[k * 4].y;
3712 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3715 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3723 laser.overloaded = (element == EL_BLOCK_STONE);
3728 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3731 Tile[x][y] = element;
3733 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3736 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3738 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3739 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3747 if (element == EL_FUEL_FULL && CT > 10)
3749 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3750 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3752 for (i = start; i <= num_init_game_frames; i++)
3754 if (i == num_init_game_frames)
3755 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3756 else if (setup.sound_loops)
3757 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3759 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3761 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3763 UpdateAndDisplayGameControlValues();
3768 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3770 DrawField_MM(ELX, ELY);
3772 DrawLaser(0, DL_LASER_ENABLED);
3780 void GameActions_MM(struct MouseActionInfo action)
3782 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3783 boolean button_released = (action.button == MB_RELEASED);
3785 GameActions_MM_Ext();
3787 CheckSingleStepMode_MM(element_clicked, button_released);
3790 void MovePacMen(void)
3792 int mx, my, ox, oy, nx, ny;
3796 if (++pacman_nr >= game_mm.num_pacman)
3799 game_mm.pacman[pacman_nr].dir--;
3801 for (l = 1; l < 5; l++)
3803 game_mm.pacman[pacman_nr].dir++;
3805 if (game_mm.pacman[pacman_nr].dir > 4)
3806 game_mm.pacman[pacman_nr].dir = 1;
3808 if (game_mm.pacman[pacman_nr].dir % 2)
3811 my = game_mm.pacman[pacman_nr].dir - 2;
3816 mx = 3 - game_mm.pacman[pacman_nr].dir;
3819 ox = game_mm.pacman[pacman_nr].x;
3820 oy = game_mm.pacman[pacman_nr].y;
3823 element = Tile[nx][ny];
3825 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3828 if (!IS_EATABLE4PACMAN(element))
3831 if (ObjHit(nx, ny, HIT_POS_CENTER))
3834 Tile[ox][oy] = EL_EMPTY;
3836 EL_PACMAN_RIGHT - 1 +
3837 (game_mm.pacman[pacman_nr].dir - 1 +
3838 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3840 game_mm.pacman[pacman_nr].x = nx;
3841 game_mm.pacman[pacman_nr].y = ny;
3843 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3845 if (element != EL_EMPTY)
3847 int graphic = el2gfx(Tile[nx][ny]);
3852 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3855 ox = cSX + ox * TILEX;
3856 oy = cSY + oy * TILEY;
3858 for (i = 1; i < 33; i += 2)
3859 BlitBitmap(bitmap, window,
3860 src_x, src_y, TILEX, TILEY,
3861 ox + i * mx, oy + i * my);
3862 Ct = Ct + FrameCounter - CT;
3865 DrawField_MM(nx, ny);
3868 if (!laser.fuse_off)
3870 DrawLaser(0, DL_LASER_ENABLED);
3872 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3874 AddDamagedField(nx, ny);
3876 laser.damage[laser.num_damages - 1].edge = 0;
3880 if (element == EL_BOMB)
3881 DeletePacMan(nx, ny);
3883 if (IS_WALL_AMOEBA(element) &&
3884 (LX + 2 * XS) / TILEX == nx &&
3885 (LY + 2 * YS) / TILEY == ny)
3895 static void InitMovingField_MM(int x, int y, int direction)
3897 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3898 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3900 MovDir[x][y] = direction;
3901 MovDir[newx][newy] = direction;
3903 if (Tile[newx][newy] == EL_EMPTY)
3904 Tile[newx][newy] = EL_BLOCKED;
3907 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3909 int direction = MovDir[x][y];
3910 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3911 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3917 static void Blocked2Moving_MM(int x, int y,
3918 int *comes_from_x, int *comes_from_y)
3920 int oldx = x, oldy = y;
3921 int direction = MovDir[x][y];
3923 if (direction == MV_LEFT)
3925 else if (direction == MV_RIGHT)
3927 else if (direction == MV_UP)
3929 else if (direction == MV_DOWN)
3932 *comes_from_x = oldx;
3933 *comes_from_y = oldy;
3936 static int MovingOrBlocked2Element_MM(int x, int y)
3938 int element = Tile[x][y];
3940 if (element == EL_BLOCKED)
3944 Blocked2Moving_MM(x, y, &oldx, &oldy);
3946 return Tile[oldx][oldy];
3953 static void RemoveField(int x, int y)
3955 Tile[x][y] = EL_EMPTY;
3962 static void RemoveMovingField_MM(int x, int y)
3964 int oldx = x, oldy = y, newx = x, newy = y;
3966 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3969 if (IS_MOVING(x, y))
3971 Moving2Blocked_MM(x, y, &newx, &newy);
3972 if (Tile[newx][newy] != EL_BLOCKED)
3975 else if (Tile[x][y] == EL_BLOCKED)
3977 Blocked2Moving_MM(x, y, &oldx, &oldy);
3978 if (!IS_MOVING(oldx, oldy))
3982 Tile[oldx][oldy] = EL_EMPTY;
3983 Tile[newx][newy] = EL_EMPTY;
3984 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3985 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3987 DrawLevelField_MM(oldx, oldy);
3988 DrawLevelField_MM(newx, newy);
3991 void PlaySoundLevel(int x, int y, int sound_nr)
3993 int sx = SCREENX(x), sy = SCREENY(y);
3995 int silence_distance = 8;
3997 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3998 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4001 if (!IN_LEV_FIELD(x, y) ||
4002 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4003 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4006 volume = SOUND_MAX_VOLUME;
4009 stereo = (sx - SCR_FIELDX/2) * 12;
4011 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4012 if (stereo > SOUND_MAX_RIGHT)
4013 stereo = SOUND_MAX_RIGHT;
4014 if (stereo < SOUND_MAX_LEFT)
4015 stereo = SOUND_MAX_LEFT;
4018 if (!IN_SCR_FIELD(sx, sy))
4020 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4021 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4023 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4026 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4029 static void RaiseScore_MM(int value)
4031 game_mm.score += value;
4034 void RaiseScoreElement_MM(int element)
4039 case EL_PACMAN_RIGHT:
4041 case EL_PACMAN_LEFT:
4042 case EL_PACMAN_DOWN:
4043 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4047 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4052 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4056 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4065 // ----------------------------------------------------------------------------
4066 // Mirror Magic game engine snapshot handling functions
4067 // ----------------------------------------------------------------------------
4069 void SaveEngineSnapshotValues_MM(void)
4073 engine_snapshot_mm.game_mm = game_mm;
4074 engine_snapshot_mm.laser = laser;
4076 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4078 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4080 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4081 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4082 engine_snapshot_mm.Box[x][y] = Box[x][y];
4083 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4087 engine_snapshot_mm.LX = LX;
4088 engine_snapshot_mm.LY = LY;
4089 engine_snapshot_mm.XS = XS;
4090 engine_snapshot_mm.YS = YS;
4091 engine_snapshot_mm.ELX = ELX;
4092 engine_snapshot_mm.ELY = ELY;
4093 engine_snapshot_mm.CT = CT;
4094 engine_snapshot_mm.Ct = Ct;
4096 engine_snapshot_mm.last_LX = last_LX;
4097 engine_snapshot_mm.last_LY = last_LY;
4098 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4099 engine_snapshot_mm.hold_x = hold_x;
4100 engine_snapshot_mm.hold_y = hold_y;
4101 engine_snapshot_mm.pacman_nr = pacman_nr;
4103 engine_snapshot_mm.rotate_delay = rotate_delay;
4104 engine_snapshot_mm.pacman_delay = pacman_delay;
4105 engine_snapshot_mm.energy_delay = energy_delay;
4106 engine_snapshot_mm.overload_delay = overload_delay;
4109 void LoadEngineSnapshotValues_MM(void)
4113 // stored engine snapshot buffers already restored at this point
4115 game_mm = engine_snapshot_mm.game_mm;
4116 laser = engine_snapshot_mm.laser;
4118 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4120 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4122 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4123 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4124 Box[x][y] = engine_snapshot_mm.Box[x][y];
4125 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4129 LX = engine_snapshot_mm.LX;
4130 LY = engine_snapshot_mm.LY;
4131 XS = engine_snapshot_mm.XS;
4132 YS = engine_snapshot_mm.YS;
4133 ELX = engine_snapshot_mm.ELX;
4134 ELY = engine_snapshot_mm.ELY;
4135 CT = engine_snapshot_mm.CT;
4136 Ct = engine_snapshot_mm.Ct;
4138 last_LX = engine_snapshot_mm.last_LX;
4139 last_LY = engine_snapshot_mm.last_LY;
4140 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4141 hold_x = engine_snapshot_mm.hold_x;
4142 hold_y = engine_snapshot_mm.hold_y;
4143 pacman_nr = engine_snapshot_mm.pacman_nr;
4145 rotate_delay = engine_snapshot_mm.rotate_delay;
4146 pacman_delay = engine_snapshot_mm.pacman_delay;
4147 energy_delay = engine_snapshot_mm.energy_delay;
4148 overload_delay = engine_snapshot_mm.overload_delay;
4150 RedrawPlayfield_MM();
4153 static int getAngleFromTouchDelta(int dx, int dy, int base)
4155 double pi = 3.141592653;
4156 double rad = atan2((double)-dy, (double)dx);
4157 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4158 double deg = rad2 * 180.0 / pi;
4160 return (int)(deg * base / 360.0 + 0.5) % base;
4163 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4165 // calculate start (source) position to be at the middle of the tile
4166 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4167 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4168 int dx = dst_mx - src_mx;
4169 int dy = dst_my - src_my;
4178 if (!IN_LEV_FIELD(x, y))
4181 element = Tile[x][y];
4183 if (!IS_MCDUFFIN(element) &&
4184 !IS_MIRROR(element) &&
4185 !IS_BEAMER(element) &&
4186 !IS_POLAR(element) &&
4187 !IS_POLAR_CROSS(element) &&
4188 !IS_DF_MIRROR(element))
4191 angle_old = get_element_angle(element);
4193 if (IS_MCDUFFIN(element))
4195 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4196 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4197 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4198 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4201 else if (IS_MIRROR(element) ||
4202 IS_DF_MIRROR(element))
4204 for (i = 0; i < laser.num_damages; i++)
4206 if (laser.damage[i].x == x &&
4207 laser.damage[i].y == y &&
4208 ObjHit(x, y, HIT_POS_CENTER))
4210 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4211 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4218 if (angle_new == -1)
4220 if (IS_MIRROR(element) ||
4221 IS_DF_MIRROR(element) ||
4225 if (IS_POLAR_CROSS(element))
4228 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4231 button = (angle_new == angle_old ? 0 :
4232 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4233 MB_LEFTBUTTON : MB_RIGHTBUTTON);