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)
2559 laser.redraw = TRUE;
2562 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2564 int element = Tile[x][y];
2565 int graphic = el2gfx(element);
2567 if (!getGraphicInfo_NewFrame(x, y, graphic))
2572 laser.redraw = TRUE;
2575 static void DrawFieldTwinkle(int x, int y)
2577 if (MovDelay[x][y] != 0) // wait some time before next frame
2583 if (MovDelay[x][y] != 0)
2585 int graphic = IMG_TWINKLE_WHITE;
2586 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2588 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2591 laser.redraw = TRUE;
2595 static void Explode_MM(int x, int y, int phase, int mode)
2597 int num_phase = 9, delay = 2;
2598 int last_phase = num_phase * delay;
2599 int half_phase = (num_phase / 2) * delay;
2601 laser.redraw = TRUE;
2603 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2605 int center_element = Tile[x][y];
2607 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2609 // put moving element to center field (and let it explode there)
2610 center_element = MovingOrBlocked2Element_MM(x, y);
2611 RemoveMovingField_MM(x, y);
2613 Tile[x][y] = center_element;
2616 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2617 Store[x][y] = center_element;
2619 Store[x][y] = EL_EMPTY;
2621 Store2[x][y] = mode;
2623 Tile[x][y] = EL_EXPLODING_OPAQUE;
2624 GfxElement[x][y] = center_element;
2626 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2628 ExplodePhase[x][y] = 1;
2634 GfxFrame[x][y] = 0; // restart explosion animation
2636 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2638 if (phase == half_phase)
2640 Tile[x][y] = EL_EXPLODING_TRANSP;
2642 if (x == ELX && y == ELY)
2646 if (phase == last_phase)
2648 if (Store[x][y] == EL_BOMB_ACTIVE)
2650 DrawLaser(0, DL_LASER_DISABLED);
2653 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2654 Store[x][y] = EL_EMPTY;
2656 GameOver_MM(GAME_OVER_DELAYED);
2658 laser.overloaded = FALSE;
2660 else if (IS_MCDUFFIN(Store[x][y]))
2662 Store[x][y] = EL_EMPTY;
2664 GameOver_MM(GAME_OVER_BOMB);
2667 Tile[x][y] = Store[x][y];
2668 Store[x][y] = Store2[x][y] = 0;
2669 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2674 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2676 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2677 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2679 DrawGraphicAnimation_MM(x, y, graphic, frame);
2681 MarkTileDirty(x, y);
2685 static void Bang_MM(int x, int y)
2687 int element = Tile[x][y];
2690 DrawLaser(0, DL_LASER_ENABLED);
2693 if (IS_PACMAN(element))
2694 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2695 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2696 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2697 else if (element == EL_KEY)
2698 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2700 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2702 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2705 void TurnRound(int x, int y)
2717 { 0, 0 }, { 0, 0 }, { 0, 0 },
2722 int left, right, back;
2726 { MV_DOWN, MV_UP, MV_RIGHT },
2727 { MV_UP, MV_DOWN, MV_LEFT },
2729 { MV_LEFT, MV_RIGHT, MV_DOWN },
2730 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2731 { MV_RIGHT, MV_LEFT, MV_UP }
2734 int element = Tile[x][y];
2735 int old_move_dir = MovDir[x][y];
2736 int right_dir = turn[old_move_dir].right;
2737 int back_dir = turn[old_move_dir].back;
2738 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2739 int right_x = x + right_dx, right_y = y + right_dy;
2741 if (element == EL_PACMAN)
2743 boolean can_turn_right = FALSE;
2745 if (IN_LEV_FIELD(right_x, right_y) &&
2746 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2747 can_turn_right = TRUE;
2750 MovDir[x][y] = right_dir;
2752 MovDir[x][y] = back_dir;
2758 static void StartMoving_MM(int x, int y)
2760 int element = Tile[x][y];
2765 if (CAN_MOVE(element))
2769 if (MovDelay[x][y]) // wait some time before next movement
2777 // now make next step
2779 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2781 if (element == EL_PACMAN &&
2782 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2783 !ObjHit(newx, newy, HIT_POS_CENTER))
2785 Store[newx][newy] = Tile[newx][newy];
2786 Tile[newx][newy] = EL_EMPTY;
2788 DrawField_MM(newx, newy);
2790 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2791 ObjHit(newx, newy, HIT_POS_CENTER))
2793 // object was running against a wall
2800 InitMovingField_MM(x, y, MovDir[x][y]);
2804 ContinueMoving_MM(x, y);
2807 static void ContinueMoving_MM(int x, int y)
2809 int element = Tile[x][y];
2810 int direction = MovDir[x][y];
2811 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2812 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2813 int horiz_move = (dx!=0);
2814 int newx = x + dx, newy = y + dy;
2815 int step = (horiz_move ? dx : dy) * TILEX / 8;
2817 MovPos[x][y] += step;
2819 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2821 Tile[x][y] = EL_EMPTY;
2822 Tile[newx][newy] = element;
2824 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2825 MovDelay[newx][newy] = 0;
2827 if (!CAN_MOVE(element))
2828 MovDir[newx][newy] = 0;
2831 DrawField_MM(newx, newy);
2833 Stop[newx][newy] = TRUE;
2835 if (element == EL_PACMAN)
2837 if (Store[newx][newy] == EL_BOMB)
2838 Bang_MM(newx, newy);
2840 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2841 (LX + 2 * XS) / TILEX == newx &&
2842 (LY + 2 * YS) / TILEY == newy)
2849 else // still moving on
2854 laser.redraw = TRUE;
2857 boolean ClickElement(int x, int y, int button)
2859 static DelayCounter click_delay = { CLICK_DELAY };
2860 static boolean new_button = TRUE;
2861 boolean element_clicked = FALSE;
2866 // initialize static variables
2867 click_delay.count = 0;
2868 click_delay.value = CLICK_DELAY;
2874 // do not rotate objects hit by the laser after the game was solved
2875 if (game_mm.level_solved && Hit[x][y])
2878 if (button == MB_RELEASED)
2881 click_delay.value = CLICK_DELAY;
2883 // release eventually hold auto-rotating mirror
2884 RotateMirror(x, y, MB_RELEASED);
2889 if (!FrameReached(&click_delay) && !new_button)
2892 if (button == MB_MIDDLEBUTTON) // middle button has no function
2895 if (!IN_LEV_FIELD(x, y))
2898 if (Tile[x][y] == EL_EMPTY)
2901 element = Tile[x][y];
2903 if (IS_MIRROR(element) ||
2904 IS_BEAMER(element) ||
2905 IS_POLAR(element) ||
2906 IS_POLAR_CROSS(element) ||
2907 IS_DF_MIRROR(element) ||
2908 IS_DF_MIRROR_AUTO(element))
2910 RotateMirror(x, y, button);
2912 element_clicked = TRUE;
2914 else if (IS_MCDUFFIN(element))
2916 if (!laser.fuse_off)
2918 DrawLaser(0, DL_LASER_DISABLED);
2925 element = get_rotated_element(element, BUTTON_ROTATION(button));
2926 laser.start_angle = get_element_angle(element);
2930 Tile[x][y] = element;
2937 if (!laser.fuse_off)
2940 element_clicked = TRUE;
2942 else if (element == EL_FUSE_ON && laser.fuse_off)
2944 if (x != laser.fuse_x || y != laser.fuse_y)
2947 laser.fuse_off = FALSE;
2948 laser.fuse_x = laser.fuse_y = -1;
2950 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2953 element_clicked = TRUE;
2955 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2957 laser.fuse_off = TRUE;
2960 laser.overloaded = FALSE;
2962 DrawLaser(0, DL_LASER_DISABLED);
2963 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2965 element_clicked = TRUE;
2967 else if (element == EL_LIGHTBALL)
2970 RaiseScoreElement_MM(element);
2971 DrawLaser(0, DL_LASER_ENABLED);
2973 element_clicked = TRUE;
2976 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2979 return element_clicked;
2982 void RotateMirror(int x, int y, int button)
2984 if (button == MB_RELEASED)
2986 // release eventually hold auto-rotating mirror
2993 if (IS_MIRROR(Tile[x][y]) ||
2994 IS_POLAR_CROSS(Tile[x][y]) ||
2995 IS_POLAR(Tile[x][y]) ||
2996 IS_BEAMER(Tile[x][y]) ||
2997 IS_DF_MIRROR(Tile[x][y]) ||
2998 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2999 IS_GRID_WOOD_AUTO(Tile[x][y]))
3001 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3003 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3005 if (button == MB_LEFTBUTTON)
3007 // left mouse button only for manual adjustment, no auto-rotating;
3008 // freeze mirror for until mouse button released
3012 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3014 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3018 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3020 int edge = Hit[x][y];
3026 DrawLaser(edge - 1, DL_LASER_DISABLED);
3030 else if (ObjHit(x, y, HIT_POS_CENTER))
3032 int edge = Hit[x][y];
3036 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3041 DrawLaser(edge - 1, DL_LASER_DISABLED);
3048 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3053 if ((IS_BEAMER(Tile[x][y]) ||
3054 IS_POLAR(Tile[x][y]) ||
3055 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3059 if (IS_BEAMER(Tile[x][y]))
3062 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3063 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3075 DrawLaser(0, DL_LASER_ENABLED);
3079 static void AutoRotateMirrors(void)
3083 if (!FrameReached(&rotate_delay))
3086 for (x = 0; x < lev_fieldx; x++)
3088 for (y = 0; y < lev_fieldy; y++)
3090 int element = Tile[x][y];
3092 // do not rotate objects hit by the laser after the game was solved
3093 if (game_mm.level_solved && Hit[x][y])
3096 if (IS_DF_MIRROR_AUTO(element) ||
3097 IS_GRID_WOOD_AUTO(element) ||
3098 IS_GRID_STEEL_AUTO(element) ||
3099 element == EL_REFRACTOR)
3100 RotateMirror(x, y, MB_RIGHTBUTTON);
3105 boolean ObjHit(int obx, int oby, int bits)
3112 if (bits & HIT_POS_CENTER)
3114 if (CheckLaserPixel(cSX + obx + 15,
3119 if (bits & HIT_POS_EDGE)
3121 for (i = 0; i < 4; i++)
3122 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3123 cSY + oby + 31 * (i / 2)))
3127 if (bits & HIT_POS_BETWEEN)
3129 for (i = 0; i < 4; i++)
3130 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3131 cSY + 4 + oby + 22 * (i / 2)))
3138 void DeletePacMan(int px, int py)
3144 if (game_mm.num_pacman <= 1)
3146 game_mm.num_pacman = 0;
3150 for (i = 0; i < game_mm.num_pacman; i++)
3151 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3154 game_mm.num_pacman--;
3156 for (j = i; j < game_mm.num_pacman; j++)
3158 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3159 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3160 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3164 void ColorCycling(void)
3166 static int CC, Cc = 0;
3168 static int color, old = 0xF00, new = 0x010, mult = 1;
3169 static unsigned short red, green, blue;
3171 if (color_status == STATIC_COLORS)
3176 if (CC < Cc || CC > Cc + 2)
3180 color = old + new * mult;
3186 if (ABS(mult) == 16)
3196 red = 0x0e00 * ((color & 0xF00) >> 8);
3197 green = 0x0e00 * ((color & 0x0F0) >> 4);
3198 blue = 0x0e00 * ((color & 0x00F));
3199 SetRGB(pen_magicolor[0], red, green, blue);
3201 red = 0x1111 * ((color & 0xF00) >> 8);
3202 green = 0x1111 * ((color & 0x0F0) >> 4);
3203 blue = 0x1111 * ((color & 0x00F));
3204 SetRGB(pen_magicolor[1], red, green, blue);
3208 static void GameActions_MM_Ext(void)
3215 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3218 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3220 element = Tile[x][y];
3222 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3223 StartMoving_MM(x, y);
3224 else if (IS_MOVING(x, y))
3225 ContinueMoving_MM(x, y);
3226 else if (IS_EXPLODING(element))
3227 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3228 else if (element == EL_EXIT_OPENING)
3230 else if (element == EL_GRAY_BALL_OPENING)
3231 OpenSurpriseBall(x, y);
3232 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3234 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3236 else if (IS_MIRROR(element) ||
3237 IS_MIRROR_FIXED(element) ||
3238 element == EL_PRISM)
3239 DrawFieldTwinkle(x, y);
3240 else if (element == EL_GRAY_BALL_OPENING ||
3241 element == EL_BOMB_ACTIVE ||
3242 element == EL_MINE_ACTIVE)
3243 DrawFieldAnimated_MM(x, y);
3244 else if (!IS_BLOCKED(x, y))
3245 DrawFieldAnimatedIfNeeded_MM(x, y);
3248 AutoRotateMirrors();
3251 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3253 // redraw after Explode_MM() ...
3255 DrawLaser(0, DL_LASER_ENABLED);
3256 laser.redraw = FALSE;
3261 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3265 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3267 DrawLaser(0, DL_LASER_DISABLED);
3272 // skip all following game actions if game is over
3273 if (game_mm.game_over)
3276 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3280 GameOver_MM(GAME_OVER_NO_ENERGY);
3285 if (FrameReached(&energy_delay))
3287 if (game_mm.energy_left > 0)
3288 game_mm.energy_left--;
3290 // when out of energy, wait another frame to play "out of time" sound
3293 element = laser.dest_element;
3296 if (element != Tile[ELX][ELY])
3298 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3299 element, Tile[ELX][ELY]);
3303 if (!laser.overloaded && laser.overload_value == 0 &&
3304 element != EL_BOMB &&
3305 element != EL_BOMB_ACTIVE &&
3306 element != EL_MINE &&
3307 element != EL_MINE_ACTIVE &&
3308 element != EL_BALL_GRAY &&
3309 element != EL_BLOCK_STONE &&
3310 element != EL_BLOCK_WOOD &&
3311 element != EL_FUSE_ON &&
3312 element != EL_FUEL_FULL &&
3313 !IS_WALL_ICE(element) &&
3314 !IS_WALL_AMOEBA(element))
3317 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3319 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3320 (!laser.overloaded && laser.overload_value > 0)) &&
3321 FrameReached(&overload_delay))
3323 if (laser.overloaded)
3324 laser.overload_value++;
3326 laser.overload_value--;
3328 if (game_mm.cheat_no_overload)
3330 laser.overloaded = FALSE;
3331 laser.overload_value = 0;
3334 game_mm.laser_overload_value = laser.overload_value;
3336 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3338 SetLaserColor(0xFF);
3340 DrawLaser(0, DL_LASER_ENABLED);
3343 if (!laser.overloaded)
3344 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3345 else if (setup.sound_loops)
3346 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3348 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3350 if (laser.overloaded)
3353 BlitBitmap(pix[PIX_DOOR], drawto,
3354 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3355 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3356 - laser.overload_value,
3357 OVERLOAD_XSIZE, laser.overload_value,
3358 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3359 - laser.overload_value);
3361 redraw_mask |= REDRAW_DOOR_1;
3366 BlitBitmap(pix[PIX_DOOR], drawto,
3367 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3368 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3369 DX_OVERLOAD, DY_OVERLOAD);
3371 redraw_mask |= REDRAW_DOOR_1;
3374 if (laser.overload_value == MAX_LASER_OVERLOAD)
3376 UpdateAndDisplayGameControlValues();
3380 GameOver_MM(GAME_OVER_OVERLOADED);
3391 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3393 if (game_mm.cheat_no_explosion)
3398 laser.dest_element = EL_EXPLODING_OPAQUE;
3403 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3405 laser.fuse_off = TRUE;
3409 DrawLaser(0, DL_LASER_DISABLED);
3410 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3413 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3415 if (!Store2[ELX][ELY]) // check if content element not yet determined
3417 int last_anim_random_frame = gfx.anim_random_frame;
3420 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3421 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3423 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3424 native_mm_level.ball_choice_mode, 0,
3425 game_mm.ball_choice_pos);
3427 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3428 gfx.anim_random_frame = last_anim_random_frame;
3430 game_mm.ball_choice_pos++;
3432 int new_element = native_mm_level.ball_content[element_pos];
3434 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3435 Store2[ELX][ELY] = TRUE;
3438 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3440 // !!! CHECK AGAIN: Laser on Polarizer !!!
3443 laser.dest_element_last = Tile[ELX][ELY];
3444 laser.dest_element_last_x = ELX;
3445 laser.dest_element_last_y = ELY;
3455 element = EL_MIRROR_START + RND(16);
3461 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3468 element = (rnd == 0 ? EL_FUSE_ON :
3469 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3470 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3471 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3472 EL_MIRROR_FIXED_START + rnd - 25);
3477 graphic = el2gfx(element);
3479 for (i = 0; i < 50; i++)
3485 BlitBitmap(pix[PIX_BACK], drawto,
3486 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3487 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3488 SX + ELX * TILEX + x,
3489 SY + ELY * TILEY + y);
3491 MarkTileDirty(ELX, ELY);
3494 DrawLaser(0, DL_LASER_ENABLED);
3496 Delay_WithScreenUpdates(50);
3499 Tile[ELX][ELY] = element;
3500 DrawField_MM(ELX, ELY);
3503 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3506 // above stuff: GRAY BALL -> PRISM !!!
3508 LX = ELX * TILEX + 14;
3509 LY = ELY * TILEY + 14;
3510 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3517 laser.num_edges -= 2;
3518 laser.num_damages--;
3522 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3523 if (laser.damage[i].is_mirror)
3527 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3529 DrawLaser(0, DL_LASER_DISABLED);
3531 DrawLaser(0, DL_LASER_DISABLED);
3540 if (IS_WALL_ICE(element) && CT > 50)
3542 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3545 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3546 Store[ELX][ELY] = EL_WALL_ICE;
3547 Store2[ELX][ELY] = laser.wall_mask;
3549 laser.dest_element = Tile[ELX][ELY];
3554 for (i = 0; i < 5; i++)
3560 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3564 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3566 Delay_WithScreenUpdates(100);
3569 if (Tile[ELX][ELY] == EL_WALL_ICE)
3570 Tile[ELX][ELY] = EL_EMPTY;
3574 LX = laser.edge[laser.num_edges].x - cSX2;
3575 LY = laser.edge[laser.num_edges].y - cSY2;
3578 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3579 if (laser.damage[i].is_mirror)
3583 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3585 DrawLaser(0, DL_LASER_DISABLED);
3592 if (IS_WALL_AMOEBA(element) && CT > 60)
3594 int k1, k2, k3, dx, dy, de, dm;
3595 int element2 = Tile[ELX][ELY];
3597 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3600 for (i = laser.num_damages - 1; i >= 0; i--)
3601 if (laser.damage[i].is_mirror)
3604 r = laser.num_edges;
3605 d = laser.num_damages;
3612 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3615 DrawLaser(0, DL_LASER_ENABLED);
3618 x = laser.damage[k1].x;
3619 y = laser.damage[k1].y;
3624 for (i = 0; i < 4; i++)
3626 if (laser.wall_mask & (1 << i))
3628 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3629 cSY + ELY * TILEY + 31 * (i / 2)))
3632 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3633 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3640 for (i = 0; i < 4; i++)
3642 if (laser.wall_mask & (1 << i))
3644 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3645 cSY + ELY * TILEY + 31 * (i / 2)))
3652 if (laser.num_beamers > 0 ||
3653 k1 < 1 || k2 < 4 || k3 < 4 ||
3654 CheckLaserPixel(cSX + ELX * TILEX + 14,
3655 cSY + ELY * TILEY + 14))
3657 laser.num_edges = r;
3658 laser.num_damages = d;
3660 DrawLaser(0, DL_LASER_DISABLED);
3663 Tile[ELX][ELY] = element | laser.wall_mask;
3667 de = Tile[ELX][ELY];
3668 dm = laser.wall_mask;
3672 int x = ELX, y = ELY;
3673 int wall_mask = laser.wall_mask;
3676 DrawLaser(0, DL_LASER_ENABLED);
3678 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3680 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3681 Store[x][y] = EL_WALL_AMOEBA;
3682 Store2[x][y] = wall_mask;
3688 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3690 DrawLaser(0, DL_LASER_ENABLED);
3692 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3694 for (i = 4; i >= 0; i--)
3696 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3699 Delay_WithScreenUpdates(20);
3702 DrawLaser(0, DL_LASER_ENABLED);
3707 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3708 laser.stops_inside_element && CT > native_mm_level.time_block)
3713 if (ABS(XS) > ABS(YS))
3720 for (i = 0; i < 4; i++)
3727 x = ELX + Step[k * 4].x;
3728 y = ELY + Step[k * 4].y;
3730 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3733 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3741 laser.overloaded = (element == EL_BLOCK_STONE);
3746 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3749 Tile[x][y] = element;
3751 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3754 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3756 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3757 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3765 if (element == EL_FUEL_FULL && CT > 10)
3767 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3768 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3770 for (i = start; i <= num_init_game_frames; i++)
3772 if (i == num_init_game_frames)
3773 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3774 else if (setup.sound_loops)
3775 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3777 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3779 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3781 UpdateAndDisplayGameControlValues();
3786 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3788 DrawField_MM(ELX, ELY);
3790 DrawLaser(0, DL_LASER_ENABLED);
3798 void GameActions_MM(struct MouseActionInfo action)
3800 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3801 boolean button_released = (action.button == MB_RELEASED);
3803 GameActions_MM_Ext();
3805 CheckSingleStepMode_MM(element_clicked, button_released);
3808 void MovePacMen(void)
3810 int mx, my, ox, oy, nx, ny;
3814 if (++pacman_nr >= game_mm.num_pacman)
3817 game_mm.pacman[pacman_nr].dir--;
3819 for (l = 1; l < 5; l++)
3821 game_mm.pacman[pacman_nr].dir++;
3823 if (game_mm.pacman[pacman_nr].dir > 4)
3824 game_mm.pacman[pacman_nr].dir = 1;
3826 if (game_mm.pacman[pacman_nr].dir % 2)
3829 my = game_mm.pacman[pacman_nr].dir - 2;
3834 mx = 3 - game_mm.pacman[pacman_nr].dir;
3837 ox = game_mm.pacman[pacman_nr].x;
3838 oy = game_mm.pacman[pacman_nr].y;
3841 element = Tile[nx][ny];
3843 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3846 if (!IS_EATABLE4PACMAN(element))
3849 if (ObjHit(nx, ny, HIT_POS_CENTER))
3852 Tile[ox][oy] = EL_EMPTY;
3854 EL_PACMAN_RIGHT - 1 +
3855 (game_mm.pacman[pacman_nr].dir - 1 +
3856 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3858 game_mm.pacman[pacman_nr].x = nx;
3859 game_mm.pacman[pacman_nr].y = ny;
3861 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3863 if (element != EL_EMPTY)
3865 int graphic = el2gfx(Tile[nx][ny]);
3870 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3873 ox = cSX + ox * TILEX;
3874 oy = cSY + oy * TILEY;
3876 for (i = 1; i < 33; i += 2)
3877 BlitBitmap(bitmap, window,
3878 src_x, src_y, TILEX, TILEY,
3879 ox + i * mx, oy + i * my);
3880 Ct = Ct + FrameCounter - CT;
3883 DrawField_MM(nx, ny);
3886 if (!laser.fuse_off)
3888 DrawLaser(0, DL_LASER_ENABLED);
3890 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3892 AddDamagedField(nx, ny);
3894 laser.damage[laser.num_damages - 1].edge = 0;
3898 if (element == EL_BOMB)
3899 DeletePacMan(nx, ny);
3901 if (IS_WALL_AMOEBA(element) &&
3902 (LX + 2 * XS) / TILEX == nx &&
3903 (LY + 2 * YS) / TILEY == ny)
3913 static void InitMovingField_MM(int x, int y, int direction)
3915 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3916 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3918 MovDir[x][y] = direction;
3919 MovDir[newx][newy] = direction;
3921 if (Tile[newx][newy] == EL_EMPTY)
3922 Tile[newx][newy] = EL_BLOCKED;
3925 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3927 int direction = MovDir[x][y];
3928 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3929 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3935 static void Blocked2Moving_MM(int x, int y,
3936 int *comes_from_x, int *comes_from_y)
3938 int oldx = x, oldy = y;
3939 int direction = MovDir[x][y];
3941 if (direction == MV_LEFT)
3943 else if (direction == MV_RIGHT)
3945 else if (direction == MV_UP)
3947 else if (direction == MV_DOWN)
3950 *comes_from_x = oldx;
3951 *comes_from_y = oldy;
3954 static int MovingOrBlocked2Element_MM(int x, int y)
3956 int element = Tile[x][y];
3958 if (element == EL_BLOCKED)
3962 Blocked2Moving_MM(x, y, &oldx, &oldy);
3964 return Tile[oldx][oldy];
3971 static void RemoveField(int x, int y)
3973 Tile[x][y] = EL_EMPTY;
3980 static void RemoveMovingField_MM(int x, int y)
3982 int oldx = x, oldy = y, newx = x, newy = y;
3984 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3987 if (IS_MOVING(x, y))
3989 Moving2Blocked_MM(x, y, &newx, &newy);
3990 if (Tile[newx][newy] != EL_BLOCKED)
3993 else if (Tile[x][y] == EL_BLOCKED)
3995 Blocked2Moving_MM(x, y, &oldx, &oldy);
3996 if (!IS_MOVING(oldx, oldy))
4000 Tile[oldx][oldy] = EL_EMPTY;
4001 Tile[newx][newy] = EL_EMPTY;
4002 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4003 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4005 DrawLevelField_MM(oldx, oldy);
4006 DrawLevelField_MM(newx, newy);
4009 void PlaySoundLevel(int x, int y, int sound_nr)
4011 int sx = SCREENX(x), sy = SCREENY(y);
4013 int silence_distance = 8;
4015 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4016 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4019 if (!IN_LEV_FIELD(x, y) ||
4020 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4021 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4024 volume = SOUND_MAX_VOLUME;
4027 stereo = (sx - SCR_FIELDX/2) * 12;
4029 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4030 if (stereo > SOUND_MAX_RIGHT)
4031 stereo = SOUND_MAX_RIGHT;
4032 if (stereo < SOUND_MAX_LEFT)
4033 stereo = SOUND_MAX_LEFT;
4036 if (!IN_SCR_FIELD(sx, sy))
4038 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4039 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4041 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4044 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4047 static void RaiseScore_MM(int value)
4049 game_mm.score += value;
4052 void RaiseScoreElement_MM(int element)
4057 case EL_PACMAN_RIGHT:
4059 case EL_PACMAN_LEFT:
4060 case EL_PACMAN_DOWN:
4061 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4065 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4070 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4074 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4083 // ----------------------------------------------------------------------------
4084 // Mirror Magic game engine snapshot handling functions
4085 // ----------------------------------------------------------------------------
4087 void SaveEngineSnapshotValues_MM(void)
4091 engine_snapshot_mm.game_mm = game_mm;
4092 engine_snapshot_mm.laser = laser;
4094 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4096 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4098 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4099 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4100 engine_snapshot_mm.Box[x][y] = Box[x][y];
4101 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4105 engine_snapshot_mm.LX = LX;
4106 engine_snapshot_mm.LY = LY;
4107 engine_snapshot_mm.XS = XS;
4108 engine_snapshot_mm.YS = YS;
4109 engine_snapshot_mm.ELX = ELX;
4110 engine_snapshot_mm.ELY = ELY;
4111 engine_snapshot_mm.CT = CT;
4112 engine_snapshot_mm.Ct = Ct;
4114 engine_snapshot_mm.last_LX = last_LX;
4115 engine_snapshot_mm.last_LY = last_LY;
4116 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4117 engine_snapshot_mm.hold_x = hold_x;
4118 engine_snapshot_mm.hold_y = hold_y;
4119 engine_snapshot_mm.pacman_nr = pacman_nr;
4121 engine_snapshot_mm.rotate_delay = rotate_delay;
4122 engine_snapshot_mm.pacman_delay = pacman_delay;
4123 engine_snapshot_mm.energy_delay = energy_delay;
4124 engine_snapshot_mm.overload_delay = overload_delay;
4127 void LoadEngineSnapshotValues_MM(void)
4131 // stored engine snapshot buffers already restored at this point
4133 game_mm = engine_snapshot_mm.game_mm;
4134 laser = engine_snapshot_mm.laser;
4136 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4138 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4140 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4141 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4142 Box[x][y] = engine_snapshot_mm.Box[x][y];
4143 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4147 LX = engine_snapshot_mm.LX;
4148 LY = engine_snapshot_mm.LY;
4149 XS = engine_snapshot_mm.XS;
4150 YS = engine_snapshot_mm.YS;
4151 ELX = engine_snapshot_mm.ELX;
4152 ELY = engine_snapshot_mm.ELY;
4153 CT = engine_snapshot_mm.CT;
4154 Ct = engine_snapshot_mm.Ct;
4156 last_LX = engine_snapshot_mm.last_LX;
4157 last_LY = engine_snapshot_mm.last_LY;
4158 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4159 hold_x = engine_snapshot_mm.hold_x;
4160 hold_y = engine_snapshot_mm.hold_y;
4161 pacman_nr = engine_snapshot_mm.pacman_nr;
4163 rotate_delay = engine_snapshot_mm.rotate_delay;
4164 pacman_delay = engine_snapshot_mm.pacman_delay;
4165 energy_delay = engine_snapshot_mm.energy_delay;
4166 overload_delay = engine_snapshot_mm.overload_delay;
4168 RedrawPlayfield_MM();
4171 static int getAngleFromTouchDelta(int dx, int dy, int base)
4173 double pi = 3.141592653;
4174 double rad = atan2((double)-dy, (double)dx);
4175 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4176 double deg = rad2 * 180.0 / pi;
4178 return (int)(deg * base / 360.0 + 0.5) % base;
4181 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4183 // calculate start (source) position to be at the middle of the tile
4184 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4185 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4186 int dx = dst_mx - src_mx;
4187 int dy = dst_my - src_my;
4196 if (!IN_LEV_FIELD(x, y))
4199 element = Tile[x][y];
4201 if (!IS_MCDUFFIN(element) &&
4202 !IS_MIRROR(element) &&
4203 !IS_BEAMER(element) &&
4204 !IS_POLAR(element) &&
4205 !IS_POLAR_CROSS(element) &&
4206 !IS_DF_MIRROR(element))
4209 angle_old = get_element_angle(element);
4211 if (IS_MCDUFFIN(element))
4213 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4214 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4215 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4216 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4219 else if (IS_MIRROR(element) ||
4220 IS_DF_MIRROR(element))
4222 for (i = 0; i < laser.num_damages; i++)
4224 if (laser.damage[i].x == x &&
4225 laser.damage[i].y == y &&
4226 ObjHit(x, y, HIT_POS_CENTER))
4228 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4229 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4236 if (angle_new == -1)
4238 if (IS_MIRROR(element) ||
4239 IS_DF_MIRROR(element) ||
4243 if (IS_POLAR_CROSS(element))
4246 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4249 button = (angle_new == angle_old ? 0 :
4250 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4251 MB_LEFTBUTTON : MB_RIGHTBUTTON);