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)
3057 if (IS_BEAMER(Tile[x][y]))
3060 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3061 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3074 DrawLaser(0, DL_LASER_ENABLED);
3078 static void AutoRotateMirrors(void)
3082 if (!FrameReached(&rotate_delay))
3085 for (x = 0; x < lev_fieldx; x++)
3087 for (y = 0; y < lev_fieldy; y++)
3089 int element = Tile[x][y];
3091 // do not rotate objects hit by the laser after the game was solved
3092 if (game_mm.level_solved && Hit[x][y])
3095 if (IS_DF_MIRROR_AUTO(element) ||
3096 IS_GRID_WOOD_AUTO(element) ||
3097 IS_GRID_STEEL_AUTO(element) ||
3098 element == EL_REFRACTOR)
3099 RotateMirror(x, y, MB_RIGHTBUTTON);
3104 boolean ObjHit(int obx, int oby, int bits)
3111 if (bits & HIT_POS_CENTER)
3113 if (CheckLaserPixel(cSX + obx + 15,
3118 if (bits & HIT_POS_EDGE)
3120 for (i = 0; i < 4; i++)
3121 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3122 cSY + oby + 31 * (i / 2)))
3126 if (bits & HIT_POS_BETWEEN)
3128 for (i = 0; i < 4; i++)
3129 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3130 cSY + 4 + oby + 22 * (i / 2)))
3137 void DeletePacMan(int px, int py)
3143 if (game_mm.num_pacman <= 1)
3145 game_mm.num_pacman = 0;
3149 for (i = 0; i < game_mm.num_pacman; i++)
3150 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3153 game_mm.num_pacman--;
3155 for (j = i; j < game_mm.num_pacman; j++)
3157 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3158 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3159 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3163 void ColorCycling(void)
3165 static int CC, Cc = 0;
3167 static int color, old = 0xF00, new = 0x010, mult = 1;
3168 static unsigned short red, green, blue;
3170 if (color_status == STATIC_COLORS)
3175 if (CC < Cc || CC > Cc + 2)
3179 color = old + new * mult;
3185 if (ABS(mult) == 16)
3195 red = 0x0e00 * ((color & 0xF00) >> 8);
3196 green = 0x0e00 * ((color & 0x0F0) >> 4);
3197 blue = 0x0e00 * ((color & 0x00F));
3198 SetRGB(pen_magicolor[0], red, green, blue);
3200 red = 0x1111 * ((color & 0xF00) >> 8);
3201 green = 0x1111 * ((color & 0x0F0) >> 4);
3202 blue = 0x1111 * ((color & 0x00F));
3203 SetRGB(pen_magicolor[1], red, green, blue);
3207 static void GameActions_MM_Ext(void)
3214 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3217 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3219 element = Tile[x][y];
3221 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3222 StartMoving_MM(x, y);
3223 else if (IS_MOVING(x, y))
3224 ContinueMoving_MM(x, y);
3225 else if (IS_EXPLODING(element))
3226 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3227 else if (element == EL_EXIT_OPENING)
3229 else if (element == EL_GRAY_BALL_OPENING)
3230 OpenSurpriseBall(x, y);
3231 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3233 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3235 else if (IS_MIRROR(element) ||
3236 IS_MIRROR_FIXED(element) ||
3237 element == EL_PRISM)
3238 DrawFieldTwinkle(x, y);
3239 else if (element == EL_GRAY_BALL_OPENING ||
3240 element == EL_BOMB_ACTIVE ||
3241 element == EL_MINE_ACTIVE)
3242 DrawFieldAnimated_MM(x, y);
3243 else if (!IS_BLOCKED(x, y))
3244 DrawFieldAnimatedIfNeeded_MM(x, y);
3247 AutoRotateMirrors();
3250 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3252 // redraw after Explode_MM() ...
3254 DrawLaser(0, DL_LASER_ENABLED);
3255 laser.redraw = FALSE;
3260 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3264 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3266 DrawLaser(0, DL_LASER_DISABLED);
3271 // skip all following game actions if game is over
3272 if (game_mm.game_over)
3275 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3279 GameOver_MM(GAME_OVER_NO_ENERGY);
3284 if (FrameReached(&energy_delay))
3286 if (game_mm.energy_left > 0)
3287 game_mm.energy_left--;
3289 // when out of energy, wait another frame to play "out of time" sound
3292 element = laser.dest_element;
3295 if (element != Tile[ELX][ELY])
3297 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3298 element, Tile[ELX][ELY]);
3302 if (!laser.overloaded && laser.overload_value == 0 &&
3303 element != EL_BOMB &&
3304 element != EL_BOMB_ACTIVE &&
3305 element != EL_MINE &&
3306 element != EL_MINE_ACTIVE &&
3307 element != EL_BALL_GRAY &&
3308 element != EL_BLOCK_STONE &&
3309 element != EL_BLOCK_WOOD &&
3310 element != EL_FUSE_ON &&
3311 element != EL_FUEL_FULL &&
3312 !IS_WALL_ICE(element) &&
3313 !IS_WALL_AMOEBA(element))
3316 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3318 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3319 (!laser.overloaded && laser.overload_value > 0)) &&
3320 FrameReached(&overload_delay))
3322 if (laser.overloaded)
3323 laser.overload_value++;
3325 laser.overload_value--;
3327 if (game_mm.cheat_no_overload)
3329 laser.overloaded = FALSE;
3330 laser.overload_value = 0;
3333 game_mm.laser_overload_value = laser.overload_value;
3335 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3337 SetLaserColor(0xFF);
3339 DrawLaser(0, DL_LASER_ENABLED);
3342 if (!laser.overloaded)
3343 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3344 else if (setup.sound_loops)
3345 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3347 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3349 if (laser.overloaded)
3352 BlitBitmap(pix[PIX_DOOR], drawto,
3353 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3354 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3355 - laser.overload_value,
3356 OVERLOAD_XSIZE, laser.overload_value,
3357 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3358 - laser.overload_value);
3360 redraw_mask |= REDRAW_DOOR_1;
3365 BlitBitmap(pix[PIX_DOOR], drawto,
3366 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3367 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3368 DX_OVERLOAD, DY_OVERLOAD);
3370 redraw_mask |= REDRAW_DOOR_1;
3373 if (laser.overload_value == MAX_LASER_OVERLOAD)
3375 UpdateAndDisplayGameControlValues();
3379 GameOver_MM(GAME_OVER_OVERLOADED);
3390 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3392 if (game_mm.cheat_no_explosion)
3397 laser.dest_element = EL_EXPLODING_OPAQUE;
3402 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3404 laser.fuse_off = TRUE;
3408 DrawLaser(0, DL_LASER_DISABLED);
3409 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3412 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3414 if (!Store2[ELX][ELY]) // check if content element not yet determined
3416 int last_anim_random_frame = gfx.anim_random_frame;
3419 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3420 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3422 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3423 native_mm_level.ball_choice_mode, 0,
3424 game_mm.ball_choice_pos);
3426 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3427 gfx.anim_random_frame = last_anim_random_frame;
3429 game_mm.ball_choice_pos++;
3431 int new_element = native_mm_level.ball_content[element_pos];
3433 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3434 Store2[ELX][ELY] = TRUE;
3437 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3439 // !!! CHECK AGAIN: Laser on Polarizer !!!
3442 laser.dest_element_last = Tile[ELX][ELY];
3443 laser.dest_element_last_x = ELX;
3444 laser.dest_element_last_y = ELY;
3454 element = EL_MIRROR_START + RND(16);
3460 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3467 element = (rnd == 0 ? EL_FUSE_ON :
3468 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3469 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3470 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3471 EL_MIRROR_FIXED_START + rnd - 25);
3476 graphic = el2gfx(element);
3478 for (i = 0; i < 50; i++)
3484 BlitBitmap(pix[PIX_BACK], drawto,
3485 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3486 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3487 SX + ELX * TILEX + x,
3488 SY + ELY * TILEY + y);
3490 MarkTileDirty(ELX, ELY);
3493 DrawLaser(0, DL_LASER_ENABLED);
3495 Delay_WithScreenUpdates(50);
3498 Tile[ELX][ELY] = element;
3499 DrawField_MM(ELX, ELY);
3502 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3505 // above stuff: GRAY BALL -> PRISM !!!
3507 LX = ELX * TILEX + 14;
3508 LY = ELY * TILEY + 14;
3509 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3516 laser.num_edges -= 2;
3517 laser.num_damages--;
3521 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3522 if (laser.damage[i].is_mirror)
3526 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3528 DrawLaser(0, DL_LASER_DISABLED);
3530 DrawLaser(0, DL_LASER_DISABLED);
3539 if (IS_WALL_ICE(element) && CT > 50)
3541 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3544 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3545 Store[ELX][ELY] = EL_WALL_ICE;
3546 Store2[ELX][ELY] = laser.wall_mask;
3548 laser.dest_element = Tile[ELX][ELY];
3553 for (i = 0; i < 5; i++)
3559 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3563 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3565 Delay_WithScreenUpdates(100);
3568 if (Tile[ELX][ELY] == EL_WALL_ICE)
3569 Tile[ELX][ELY] = EL_EMPTY;
3573 LX = laser.edge[laser.num_edges].x - cSX2;
3574 LY = laser.edge[laser.num_edges].y - cSY2;
3577 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3578 if (laser.damage[i].is_mirror)
3582 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3584 DrawLaser(0, DL_LASER_DISABLED);
3591 if (IS_WALL_AMOEBA(element) && CT > 60)
3593 int k1, k2, k3, dx, dy, de, dm;
3594 int element2 = Tile[ELX][ELY];
3596 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3599 for (i = laser.num_damages - 1; i >= 0; i--)
3600 if (laser.damage[i].is_mirror)
3603 r = laser.num_edges;
3604 d = laser.num_damages;
3611 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3614 DrawLaser(0, DL_LASER_ENABLED);
3617 x = laser.damage[k1].x;
3618 y = laser.damage[k1].y;
3623 for (i = 0; i < 4; i++)
3625 if (laser.wall_mask & (1 << i))
3627 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3628 cSY + ELY * TILEY + 31 * (i / 2)))
3631 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3632 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3639 for (i = 0; i < 4; i++)
3641 if (laser.wall_mask & (1 << i))
3643 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3644 cSY + ELY * TILEY + 31 * (i / 2)))
3651 if (laser.num_beamers > 0 ||
3652 k1 < 1 || k2 < 4 || k3 < 4 ||
3653 CheckLaserPixel(cSX + ELX * TILEX + 14,
3654 cSY + ELY * TILEY + 14))
3656 laser.num_edges = r;
3657 laser.num_damages = d;
3659 DrawLaser(0, DL_LASER_DISABLED);
3662 Tile[ELX][ELY] = element | laser.wall_mask;
3666 de = Tile[ELX][ELY];
3667 dm = laser.wall_mask;
3671 int x = ELX, y = ELY;
3672 int wall_mask = laser.wall_mask;
3675 DrawLaser(0, DL_LASER_ENABLED);
3677 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3679 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3680 Store[x][y] = EL_WALL_AMOEBA;
3681 Store2[x][y] = wall_mask;
3687 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3689 DrawLaser(0, DL_LASER_ENABLED);
3691 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3693 for (i = 4; i >= 0; i--)
3695 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3698 Delay_WithScreenUpdates(20);
3701 DrawLaser(0, DL_LASER_ENABLED);
3706 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3707 laser.stops_inside_element && CT > native_mm_level.time_block)
3712 if (ABS(XS) > ABS(YS))
3719 for (i = 0; i < 4; i++)
3726 x = ELX + Step[k * 4].x;
3727 y = ELY + Step[k * 4].y;
3729 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3732 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3740 laser.overloaded = (element == EL_BLOCK_STONE);
3745 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3748 Tile[x][y] = element;
3750 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3753 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3755 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3756 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3764 if (element == EL_FUEL_FULL && CT > 10)
3766 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3767 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3769 for (i = start; i <= num_init_game_frames; i++)
3771 if (i == num_init_game_frames)
3772 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3773 else if (setup.sound_loops)
3774 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3776 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3778 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3780 UpdateAndDisplayGameControlValues();
3785 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3787 DrawField_MM(ELX, ELY);
3789 DrawLaser(0, DL_LASER_ENABLED);
3797 void GameActions_MM(struct MouseActionInfo action)
3799 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3800 boolean button_released = (action.button == MB_RELEASED);
3802 GameActions_MM_Ext();
3804 CheckSingleStepMode_MM(element_clicked, button_released);
3807 void MovePacMen(void)
3809 int mx, my, ox, oy, nx, ny;
3813 if (++pacman_nr >= game_mm.num_pacman)
3816 game_mm.pacman[pacman_nr].dir--;
3818 for (l = 1; l < 5; l++)
3820 game_mm.pacman[pacman_nr].dir++;
3822 if (game_mm.pacman[pacman_nr].dir > 4)
3823 game_mm.pacman[pacman_nr].dir = 1;
3825 if (game_mm.pacman[pacman_nr].dir % 2)
3828 my = game_mm.pacman[pacman_nr].dir - 2;
3833 mx = 3 - game_mm.pacman[pacman_nr].dir;
3836 ox = game_mm.pacman[pacman_nr].x;
3837 oy = game_mm.pacman[pacman_nr].y;
3840 element = Tile[nx][ny];
3842 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3845 if (!IS_EATABLE4PACMAN(element))
3848 if (ObjHit(nx, ny, HIT_POS_CENTER))
3851 Tile[ox][oy] = EL_EMPTY;
3853 EL_PACMAN_RIGHT - 1 +
3854 (game_mm.pacman[pacman_nr].dir - 1 +
3855 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3857 game_mm.pacman[pacman_nr].x = nx;
3858 game_mm.pacman[pacman_nr].y = ny;
3860 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3862 if (element != EL_EMPTY)
3864 int graphic = el2gfx(Tile[nx][ny]);
3869 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3872 ox = cSX + ox * TILEX;
3873 oy = cSY + oy * TILEY;
3875 for (i = 1; i < 33; i += 2)
3876 BlitBitmap(bitmap, window,
3877 src_x, src_y, TILEX, TILEY,
3878 ox + i * mx, oy + i * my);
3879 Ct = Ct + FrameCounter - CT;
3882 DrawField_MM(nx, ny);
3885 if (!laser.fuse_off)
3887 DrawLaser(0, DL_LASER_ENABLED);
3889 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3891 AddDamagedField(nx, ny);
3893 laser.damage[laser.num_damages - 1].edge = 0;
3897 if (element == EL_BOMB)
3898 DeletePacMan(nx, ny);
3900 if (IS_WALL_AMOEBA(element) &&
3901 (LX + 2 * XS) / TILEX == nx &&
3902 (LY + 2 * YS) / TILEY == ny)
3912 static void InitMovingField_MM(int x, int y, int direction)
3914 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3915 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3917 MovDir[x][y] = direction;
3918 MovDir[newx][newy] = direction;
3920 if (Tile[newx][newy] == EL_EMPTY)
3921 Tile[newx][newy] = EL_BLOCKED;
3924 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3926 int direction = MovDir[x][y];
3927 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3928 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3934 static void Blocked2Moving_MM(int x, int y,
3935 int *comes_from_x, int *comes_from_y)
3937 int oldx = x, oldy = y;
3938 int direction = MovDir[x][y];
3940 if (direction == MV_LEFT)
3942 else if (direction == MV_RIGHT)
3944 else if (direction == MV_UP)
3946 else if (direction == MV_DOWN)
3949 *comes_from_x = oldx;
3950 *comes_from_y = oldy;
3953 static int MovingOrBlocked2Element_MM(int x, int y)
3955 int element = Tile[x][y];
3957 if (element == EL_BLOCKED)
3961 Blocked2Moving_MM(x, y, &oldx, &oldy);
3963 return Tile[oldx][oldy];
3970 static void RemoveField(int x, int y)
3972 Tile[x][y] = EL_EMPTY;
3979 static void RemoveMovingField_MM(int x, int y)
3981 int oldx = x, oldy = y, newx = x, newy = y;
3983 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3986 if (IS_MOVING(x, y))
3988 Moving2Blocked_MM(x, y, &newx, &newy);
3989 if (Tile[newx][newy] != EL_BLOCKED)
3992 else if (Tile[x][y] == EL_BLOCKED)
3994 Blocked2Moving_MM(x, y, &oldx, &oldy);
3995 if (!IS_MOVING(oldx, oldy))
3999 Tile[oldx][oldy] = EL_EMPTY;
4000 Tile[newx][newy] = EL_EMPTY;
4001 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4002 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4004 DrawLevelField_MM(oldx, oldy);
4005 DrawLevelField_MM(newx, newy);
4008 void PlaySoundLevel(int x, int y, int sound_nr)
4010 int sx = SCREENX(x), sy = SCREENY(y);
4012 int silence_distance = 8;
4014 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4015 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4018 if (!IN_LEV_FIELD(x, y) ||
4019 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4020 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4023 volume = SOUND_MAX_VOLUME;
4026 stereo = (sx - SCR_FIELDX/2) * 12;
4028 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4029 if (stereo > SOUND_MAX_RIGHT)
4030 stereo = SOUND_MAX_RIGHT;
4031 if (stereo < SOUND_MAX_LEFT)
4032 stereo = SOUND_MAX_LEFT;
4035 if (!IN_SCR_FIELD(sx, sy))
4037 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4038 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4040 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4043 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4046 static void RaiseScore_MM(int value)
4048 game_mm.score += value;
4051 void RaiseScoreElement_MM(int element)
4056 case EL_PACMAN_RIGHT:
4058 case EL_PACMAN_LEFT:
4059 case EL_PACMAN_DOWN:
4060 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4064 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4069 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4073 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4082 // ----------------------------------------------------------------------------
4083 // Mirror Magic game engine snapshot handling functions
4084 // ----------------------------------------------------------------------------
4086 void SaveEngineSnapshotValues_MM(void)
4090 engine_snapshot_mm.game_mm = game_mm;
4091 engine_snapshot_mm.laser = laser;
4093 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4095 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4097 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4098 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4099 engine_snapshot_mm.Box[x][y] = Box[x][y];
4100 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4104 engine_snapshot_mm.LX = LX;
4105 engine_snapshot_mm.LY = LY;
4106 engine_snapshot_mm.XS = XS;
4107 engine_snapshot_mm.YS = YS;
4108 engine_snapshot_mm.ELX = ELX;
4109 engine_snapshot_mm.ELY = ELY;
4110 engine_snapshot_mm.CT = CT;
4111 engine_snapshot_mm.Ct = Ct;
4113 engine_snapshot_mm.last_LX = last_LX;
4114 engine_snapshot_mm.last_LY = last_LY;
4115 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4116 engine_snapshot_mm.hold_x = hold_x;
4117 engine_snapshot_mm.hold_y = hold_y;
4118 engine_snapshot_mm.pacman_nr = pacman_nr;
4120 engine_snapshot_mm.rotate_delay = rotate_delay;
4121 engine_snapshot_mm.pacman_delay = pacman_delay;
4122 engine_snapshot_mm.energy_delay = energy_delay;
4123 engine_snapshot_mm.overload_delay = overload_delay;
4126 void LoadEngineSnapshotValues_MM(void)
4130 // stored engine snapshot buffers already restored at this point
4132 game_mm = engine_snapshot_mm.game_mm;
4133 laser = engine_snapshot_mm.laser;
4135 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4137 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4139 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4140 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4141 Box[x][y] = engine_snapshot_mm.Box[x][y];
4142 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4146 LX = engine_snapshot_mm.LX;
4147 LY = engine_snapshot_mm.LY;
4148 XS = engine_snapshot_mm.XS;
4149 YS = engine_snapshot_mm.YS;
4150 ELX = engine_snapshot_mm.ELX;
4151 ELY = engine_snapshot_mm.ELY;
4152 CT = engine_snapshot_mm.CT;
4153 Ct = engine_snapshot_mm.Ct;
4155 last_LX = engine_snapshot_mm.last_LX;
4156 last_LY = engine_snapshot_mm.last_LY;
4157 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4158 hold_x = engine_snapshot_mm.hold_x;
4159 hold_y = engine_snapshot_mm.hold_y;
4160 pacman_nr = engine_snapshot_mm.pacman_nr;
4162 rotate_delay = engine_snapshot_mm.rotate_delay;
4163 pacman_delay = engine_snapshot_mm.pacman_delay;
4164 energy_delay = engine_snapshot_mm.energy_delay;
4165 overload_delay = engine_snapshot_mm.overload_delay;
4167 RedrawPlayfield_MM();
4170 static int getAngleFromTouchDelta(int dx, int dy, int base)
4172 double pi = 3.141592653;
4173 double rad = atan2((double)-dy, (double)dx);
4174 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4175 double deg = rad2 * 180.0 / pi;
4177 return (int)(deg * base / 360.0 + 0.5) % base;
4180 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4182 // calculate start (source) position to be at the middle of the tile
4183 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4184 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4185 int dx = dst_mx - src_mx;
4186 int dy = dst_my - src_my;
4195 if (!IN_LEV_FIELD(x, y))
4198 element = Tile[x][y];
4200 if (!IS_MCDUFFIN(element) &&
4201 !IS_MIRROR(element) &&
4202 !IS_BEAMER(element) &&
4203 !IS_POLAR(element) &&
4204 !IS_POLAR_CROSS(element) &&
4205 !IS_DF_MIRROR(element))
4208 angle_old = get_element_angle(element);
4210 if (IS_MCDUFFIN(element))
4212 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4213 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4214 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4215 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4218 else if (IS_MIRROR(element) ||
4219 IS_DF_MIRROR(element))
4221 for (i = 0; i < laser.num_damages; i++)
4223 if (laser.damage[i].x == x &&
4224 laser.damage[i].y == y &&
4225 ObjHit(x, y, HIT_POS_CENTER))
4227 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4228 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4235 if (angle_new == -1)
4237 if (IS_MIRROR(element) ||
4238 IS_DF_MIRROR(element) ||
4242 if (IS_POLAR_CROSS(element))
4245 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4248 button = (angle_new == angle_old ? 0 :
4249 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4250 MB_LEFTBUTTON : MB_RIGHTBUTTON);