1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
100 static void Moving2Blocked_MM(int, int, int *, int *);
102 // bitmap for laser beam detection
103 static Bitmap *laser_bitmap = NULL;
105 // variables for laser control
106 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
107 static int hold_x = -1, hold_y = -1;
109 // variables for pacman control
110 static int pacman_nr = -1;
112 // various game engine delay counters
113 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
114 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
115 static DelayCounter energy_delay = { ENERGY_DELAY };
116 static DelayCounter overload_delay = { 0 };
118 // element mask positions for scanning pixels of MM elements
119 #define MM_MASK_MCDUFFIN_RIGHT 0
120 #define MM_MASK_MCDUFFIN_UP 1
121 #define MM_MASK_MCDUFFIN_LEFT 2
122 #define MM_MASK_MCDUFFIN_DOWN 3
123 #define MM_MASK_GRID_1 4
124 #define MM_MASK_GRID_2 5
125 #define MM_MASK_GRID_3 6
126 #define MM_MASK_GRID_4 7
127 #define MM_MASK_RECTANGLE 8
128 #define MM_MASK_CIRCLE 9
130 #define NUM_MM_MASKS 10
132 // element masks for scanning pixels of MM elements
133 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
317 static int get_element_angle(int element)
319 int element_phase = get_element_phase(element);
321 if (IS_MIRROR_FIXED(element) ||
322 IS_MCDUFFIN(element) ||
324 IS_RECEIVER(element))
325 return 4 * element_phase;
327 return element_phase;
330 static int get_opposite_angle(int angle)
332 int opposite_angle = angle + ANG_RAY_180;
334 // make sure "opposite_angle" is in valid interval [0, 15]
335 return (opposite_angle + 16) % 16;
338 static int get_mirrored_angle(int laser_angle, int mirror_angle)
340 int reflected_angle = 16 - laser_angle + mirror_angle;
342 // make sure "reflected_angle" is in valid interval [0, 15]
343 return (reflected_angle + 16) % 16;
346 static void DrawLaserLines(struct XY *points, int num_points, int mode)
348 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
349 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
351 DrawLines(drawto, points, num_points, pixel_drawto);
355 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
360 static boolean CheckLaserPixel(int x, int y)
366 pixel = ReadPixel(laser_bitmap, x, y);
370 return (pixel == WHITE_PIXEL);
373 static void CheckExitMM(void)
375 int exit_element = EL_EMPTY;
379 static int xy[4][2] =
387 for (y = 0; y < lev_fieldy; y++)
389 for (x = 0; x < lev_fieldx; x++)
391 if (Tile[x][y] == EL_EXIT_CLOSED)
393 // initiate opening animation of exit door
394 Tile[x][y] = EL_EXIT_OPENING;
396 exit_element = EL_EXIT_OPEN;
400 else if (IS_RECEIVER(Tile[x][y]))
402 // remove field that blocks receiver
403 int phase = Tile[x][y] - EL_RECEIVER_START;
404 int blocking_x, blocking_y;
406 blocking_x = x + xy[phase][0];
407 blocking_y = y + xy[phase][1];
409 if (IN_LEV_FIELD(blocking_x, blocking_y))
411 Tile[blocking_x][blocking_y] = EL_EMPTY;
413 DrawField_MM(blocking_x, blocking_y);
416 exit_element = EL_RECEIVER;
423 if (exit_element != EL_EMPTY)
424 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
427 static void SetLaserColor(int brightness)
429 int color_min = 0x00;
430 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
431 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
432 int color_down = color_max - color_up;
435 GetPixelFromRGB(window,
436 (game_mm.laser_red ? color_max : color_up),
437 (game_mm.laser_green ? color_down : color_min),
438 (game_mm.laser_blue ? color_down : color_min));
441 static void InitMovDir_MM(int x, int y)
443 int element = Tile[x][y];
444 static int direction[3][4] =
446 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
447 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
448 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
453 case EL_PACMAN_RIGHT:
457 Tile[x][y] = EL_PACMAN;
458 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
466 static void InitField(int x, int y)
468 int element = Tile[x][y];
473 Tile[x][y] = EL_EMPTY;
478 if (native_mm_level.auto_count_kettles)
479 game_mm.kettles_still_needed++;
482 case EL_LIGHTBULB_OFF:
483 game_mm.lights_still_needed++;
487 if (IS_MIRROR(element) ||
488 IS_BEAMER_OLD(element) ||
489 IS_BEAMER(element) ||
491 IS_POLAR_CROSS(element) ||
492 IS_DF_MIRROR(element) ||
493 IS_DF_MIRROR_AUTO(element) ||
494 IS_GRID_STEEL_AUTO(element) ||
495 IS_GRID_WOOD_AUTO(element) ||
496 IS_FIBRE_OPTIC(element))
498 if (IS_BEAMER_OLD(element))
500 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
501 element = Tile[x][y];
504 if (!IS_FIBRE_OPTIC(element))
506 static int steps_grid_auto = 0;
508 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
509 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
511 if (IS_GRID_STEEL_AUTO(element) ||
512 IS_GRID_WOOD_AUTO(element))
513 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
515 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
517 game_mm.cycle[game_mm.num_cycle].x = x;
518 game_mm.cycle[game_mm.num_cycle].y = y;
522 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
524 int beamer_nr = BEAMER_NR(element);
525 int nr = laser.beamer[beamer_nr][0].num;
527 laser.beamer[beamer_nr][nr].x = x;
528 laser.beamer[beamer_nr][nr].y = y;
529 laser.beamer[beamer_nr][nr].num = 1;
532 else if (IS_PACMAN(element))
536 else if (IS_MCDUFFIN(element) || IS_LASER(element))
538 laser.start_edge.x = x;
539 laser.start_edge.y = y;
540 laser.start_angle = get_element_angle(element);
542 if (IS_MCDUFFIN(element))
544 game_mm.laser_red = native_mm_level.mm_laser_red;
545 game_mm.laser_green = native_mm_level.mm_laser_green;
546 game_mm.laser_blue = native_mm_level.mm_laser_blue;
550 game_mm.laser_red = native_mm_level.df_laser_red;
551 game_mm.laser_green = native_mm_level.df_laser_green;
552 game_mm.laser_blue = native_mm_level.df_laser_blue;
560 static void InitCycleElements_RotateSingleStep(void)
564 if (game_mm.num_cycle == 0) // no elements to cycle
567 for (i = 0; i < game_mm.num_cycle; i++)
569 int x = game_mm.cycle[i].x;
570 int y = game_mm.cycle[i].y;
571 int step = SIGN(game_mm.cycle[i].steps);
572 int last_element = Tile[x][y];
573 int next_element = get_rotated_element(last_element, step);
575 if (!game_mm.cycle[i].steps)
578 Tile[x][y] = next_element;
580 game_mm.cycle[i].steps -= step;
584 static void InitLaser(void)
586 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
587 int step = (IS_LASER(start_element) ? 4 : 0);
589 LX = laser.start_edge.x * TILEX;
590 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
593 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
595 LY = laser.start_edge.y * TILEY;
596 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
597 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
601 XS = 2 * Step[laser.start_angle].x;
602 YS = 2 * Step[laser.start_angle].y;
604 laser.current_angle = laser.start_angle;
606 laser.num_damages = 0;
608 laser.num_beamers = 0;
609 laser.beamer_edge[0] = 0;
611 laser.dest_element = EL_EMPTY;
614 AddLaserEdge(LX, LY); // set laser starting edge
619 void InitGameEngine_MM(void)
625 // initialize laser bitmap to current playfield (screen) size
626 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
627 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
631 // set global game control values
632 game_mm.num_cycle = 0;
633 game_mm.num_pacman = 0;
636 game_mm.energy_left = 0; // later set to "native_mm_level.time"
637 game_mm.kettles_still_needed =
638 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
639 game_mm.lights_still_needed = 0;
640 game_mm.num_keys = 0;
641 game_mm.ball_choice_pos = 0;
643 game_mm.laser_red = FALSE;
644 game_mm.laser_green = FALSE;
645 game_mm.laser_blue = TRUE;
647 game_mm.level_solved = FALSE;
648 game_mm.game_over = FALSE;
649 game_mm.game_over_cause = 0;
651 game_mm.laser_overload_value = 0;
652 game_mm.laser_enabled = FALSE;
654 // set global laser control values (must be set before "InitLaser()")
655 laser.start_edge.x = 0;
656 laser.start_edge.y = 0;
657 laser.start_angle = 0;
659 for (i = 0; i < MAX_NUM_BEAMERS; i++)
660 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
662 laser.overloaded = FALSE;
663 laser.overload_value = 0;
664 laser.fuse_off = FALSE;
665 laser.fuse_x = laser.fuse_y = -1;
667 laser.dest_element = EL_EMPTY;
668 laser.dest_element_last = EL_EMPTY;
669 laser.dest_element_last_x = -1;
670 laser.dest_element_last_y = -1;
684 rotate_delay.count = 0;
685 pacman_delay.count = 0;
686 energy_delay.count = 0;
687 overload_delay.count = 0;
689 ClickElement(-1, -1, -1);
691 for (x = 0; x < lev_fieldx; x++)
693 for (y = 0; y < lev_fieldy; y++)
695 Tile[x][y] = Ur[x][y];
696 Hit[x][y] = Box[x][y] = 0;
698 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
699 Store[x][y] = Store2[x][y] = 0;
709 void InitGameActions_MM(void)
711 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
712 int cycle_steps_done = 0;
717 for (i = 0; i <= num_init_game_frames; i++)
719 if (i == num_init_game_frames)
720 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
721 else if (setup.sound_loops)
722 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
724 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
726 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
728 UpdateAndDisplayGameControlValues();
730 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
732 InitCycleElements_RotateSingleStep();
737 AdvanceFrameCounter();
747 if (setup.quick_doors)
754 if (game_mm.kettles_still_needed == 0)
757 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
758 SetTileCursorActive(TRUE);
760 ResetFrameCounter(&energy_delay);
763 static void FadeOutLaser(void)
767 for (i = 15; i >= 0; i--)
769 SetLaserColor(0x11 * i);
771 DrawLaser(0, DL_LASER_ENABLED);
774 Delay_WithScreenUpdates(50);
777 DrawLaser(0, DL_LASER_DISABLED);
779 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
782 static void GameOver_MM(int game_over_cause)
784 // do not handle game over if request dialog is already active
785 if (game.request_active)
788 game_mm.game_over = TRUE;
789 game_mm.game_over_cause = game_over_cause;
791 if (setup.ask_on_game_over)
792 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
793 "Bomb killed Mc Duffin! Play it again?" :
794 game_over_cause == GAME_OVER_NO_ENERGY ?
795 "Out of magic energy! Play it again?" :
796 game_over_cause == GAME_OVER_OVERLOADED ?
797 "Magic spell hit Mc Duffin! Play it again?" :
800 SetTileCursorActive(FALSE);
803 void AddLaserEdge(int lx, int ly)
808 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
810 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
815 laser.edge[laser.num_edges].x = cSX2 + lx;
816 laser.edge[laser.num_edges].y = cSY2 + ly;
822 void AddDamagedField(int ex, int ey)
824 laser.damage[laser.num_damages].is_mirror = FALSE;
825 laser.damage[laser.num_damages].angle = laser.current_angle;
826 laser.damage[laser.num_damages].edge = laser.num_edges;
827 laser.damage[laser.num_damages].x = ex;
828 laser.damage[laser.num_damages].y = ey;
832 static boolean StepBehind(void)
838 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
839 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
841 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
847 static int getMaskFromElement(int element)
849 if (IS_GRID(element))
850 return MM_MASK_GRID_1 + get_element_phase(element);
851 else if (IS_MCDUFFIN(element))
852 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
853 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
854 return MM_MASK_RECTANGLE;
856 return MM_MASK_CIRCLE;
859 static int ScanPixel(void)
864 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
865 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
868 // follow laser beam until it hits something (at least the screen border)
869 while (hit_mask == HIT_MASK_NO_HIT)
875 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
876 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
878 Debug("game:mm:ScanPixel", "touched screen border!");
884 for (i = 0; i < 4; i++)
886 int px = LX + (i % 2) * 2;
887 int py = LY + (i / 2) * 2;
890 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
891 int ly = (py + TILEY) / TILEY - 1; // negative values!
894 if (IN_LEV_FIELD(lx, ly))
896 int element = Tile[lx][ly];
898 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
902 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
904 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
906 pixel = ((element & (1 << pos)) ? 1 : 0);
910 int pos = getMaskFromElement(element);
912 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
917 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
918 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
921 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
922 hit_mask |= (1 << i);
925 if (hit_mask == HIT_MASK_NO_HIT)
927 // hit nothing -- go on with another step
936 static void DeactivateLaserTargetElement(void)
938 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
939 laser.dest_element_last == EL_MINE_ACTIVE ||
940 laser.dest_element_last == EL_GRAY_BALL_OPENING)
942 int x = laser.dest_element_last_x;
943 int y = laser.dest_element_last_y;
944 int element = laser.dest_element_last;
946 if (Tile[x][y] == element)
947 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
948 element == EL_MINE_ACTIVE ? EL_MINE : EL_BALL_GRAY);
950 if (Tile[x][y] == EL_BALL_GRAY)
953 laser.dest_element_last = EL_EMPTY;
954 laser.dest_element_last_x = -1;
955 laser.dest_element_last_y = -1;
962 int end = 0, rf = laser.num_edges;
964 // do not scan laser again after the game was lost for whatever reason
965 if (game_mm.game_over)
968 // do not scan laser if fuse is off
972 DeactivateLaserTargetElement();
974 laser.overloaded = FALSE;
975 laser.stops_inside_element = FALSE;
977 DrawLaser(0, DL_LASER_ENABLED);
980 Debug("game:mm:ScanLaser",
981 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
989 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
992 laser.overloaded = TRUE;
997 hit_mask = ScanPixel();
1000 Debug("game:mm:ScanLaser",
1001 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1005 // hit something -- check out what it was
1006 ELX = (LX + XS) / TILEX;
1007 ELY = (LY + YS) / TILEY;
1010 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1011 hit_mask, LX, LY, ELX, ELY);
1014 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1017 laser.dest_element = element;
1022 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1024 /* we have hit the top-right and bottom-left element --
1025 choose the bottom-left one */
1026 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1027 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1028 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1029 ELX = (LX - 2) / TILEX;
1030 ELY = (LY + 2) / TILEY;
1033 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1035 /* we have hit the top-left and bottom-right element --
1036 choose the top-left one */
1037 // !!! SEE ABOVE !!!
1038 ELX = (LX - 2) / TILEX;
1039 ELY = (LY - 2) / TILEY;
1043 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1044 hit_mask, LX, LY, ELX, ELY);
1047 element = Tile[ELX][ELY];
1048 laser.dest_element = element;
1051 Debug("game:mm:ScanLaser",
1052 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1055 LX % TILEX, LY % TILEY,
1060 if (!IN_LEV_FIELD(ELX, ELY))
1061 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1065 if (element == EL_EMPTY)
1067 if (!HitOnlyAnEdge(hit_mask))
1070 else if (element == EL_FUSE_ON)
1072 if (HitPolarizer(element, hit_mask))
1075 else if (IS_GRID(element) || IS_DF_GRID(element))
1077 if (HitPolarizer(element, hit_mask))
1080 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1081 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1083 if (HitBlock(element, hit_mask))
1090 else if (IS_MCDUFFIN(element))
1092 if (HitLaserSource(element, hit_mask))
1095 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1096 IS_RECEIVER(element))
1098 if (HitLaserDestination(element, hit_mask))
1101 else if (IS_WALL(element))
1103 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1105 if (HitReflectingWalls(element, hit_mask))
1110 if (HitAbsorbingWalls(element, hit_mask))
1116 if (HitElement(element, hit_mask))
1121 DrawLaser(rf - 1, DL_LASER_ENABLED);
1122 rf = laser.num_edges;
1124 if (!IS_DF_WALL_STEEL(element))
1126 // only used for scanning DF steel walls; reset for all other elements
1134 if (laser.dest_element != Tile[ELX][ELY])
1136 Debug("game:mm:ScanLaser",
1137 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1138 laser.dest_element, Tile[ELX][ELY]);
1142 if (!end && !laser.stops_inside_element && !StepBehind())
1145 Debug("game:mm:ScanLaser", "Go one step back");
1151 AddLaserEdge(LX, LY);
1155 DrawLaser(rf - 1, DL_LASER_ENABLED);
1157 Ct = CT = FrameCounter;
1160 if (!IN_LEV_FIELD(ELX, ELY))
1161 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1165 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1171 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1172 start_edge, num_edges, mode);
1177 Warn("DrawLaserExt: start_edge < 0");
1184 Warn("DrawLaserExt: num_edges < 0");
1190 if (mode == DL_LASER_DISABLED)
1192 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1196 // now draw the laser to the backbuffer and (if enabled) to the screen
1197 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1199 redraw_mask |= REDRAW_FIELD;
1201 if (mode == DL_LASER_ENABLED)
1204 // after the laser was deleted, the "damaged" graphics must be restored
1205 if (laser.num_damages)
1207 int damage_start = 0;
1210 // determine the starting edge, from which graphics need to be restored
1213 for (i = 0; i < laser.num_damages; i++)
1215 if (laser.damage[i].edge == start_edge + 1)
1224 // restore graphics from this starting edge to the end of damage list
1225 for (i = damage_start; i < laser.num_damages; i++)
1227 int lx = laser.damage[i].x;
1228 int ly = laser.damage[i].y;
1229 int element = Tile[lx][ly];
1231 if (Hit[lx][ly] == laser.damage[i].edge)
1232 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1235 if (Box[lx][ly] == laser.damage[i].edge)
1238 if (IS_DRAWABLE(element))
1239 DrawField_MM(lx, ly);
1242 elx = laser.damage[damage_start].x;
1243 ely = laser.damage[damage_start].y;
1244 element = Tile[elx][ely];
1247 if (IS_BEAMER(element))
1251 for (i = 0; i < laser.num_beamers; i++)
1252 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1254 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1255 mode, elx, ely, Hit[elx][ely], start_edge);
1256 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1257 get_element_angle(element), laser.damage[damage_start].angle);
1261 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1262 laser.num_beamers > 0 &&
1263 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1265 // element is outgoing beamer
1266 laser.num_damages = damage_start + 1;
1268 if (IS_BEAMER(element))
1269 laser.current_angle = get_element_angle(element);
1273 // element is incoming beamer or other element
1274 laser.num_damages = damage_start;
1275 laser.current_angle = laser.damage[laser.num_damages].angle;
1280 // no damages but McDuffin himself (who needs to be redrawn anyway)
1282 elx = laser.start_edge.x;
1283 ely = laser.start_edge.y;
1284 element = Tile[elx][ely];
1287 laser.num_edges = start_edge + 1;
1288 if (start_edge == 0)
1289 laser.current_angle = laser.start_angle;
1291 LX = laser.edge[start_edge].x - cSX2;
1292 LY = laser.edge[start_edge].y - cSY2;
1293 XS = 2 * Step[laser.current_angle].x;
1294 YS = 2 * Step[laser.current_angle].y;
1297 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1303 if (IS_BEAMER(element) ||
1304 IS_FIBRE_OPTIC(element) ||
1305 IS_PACMAN(element) ||
1306 IS_POLAR(element) ||
1307 IS_POLAR_CROSS(element) ||
1308 element == EL_FUSE_ON)
1313 Debug("game:mm:DrawLaserExt", "element == %d", element);
1316 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1317 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1321 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1322 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1323 (laser.num_beamers == 0 ||
1324 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1326 // element is incoming beamer or other element
1327 step_size = -step_size;
1332 if (IS_BEAMER(element))
1333 Debug("game:mm:DrawLaserExt",
1334 "start_edge == %d, laser.beamer_edge == %d",
1335 start_edge, laser.beamer_edge);
1338 LX += step_size * XS;
1339 LY += step_size * YS;
1341 else if (element != EL_EMPTY)
1350 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1355 void DrawLaser(int start_edge, int mode)
1357 // do not draw laser if fuse is off
1358 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1361 if (mode == DL_LASER_DISABLED)
1362 DeactivateLaserTargetElement();
1364 if (laser.num_edges - start_edge < 0)
1366 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1371 // check if laser is interrupted by beamer element
1372 if (laser.num_beamers > 0 &&
1373 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1375 if (mode == DL_LASER_ENABLED)
1378 int tmp_start_edge = start_edge;
1380 // draw laser segments forward from the start to the last beamer
1381 for (i = 0; i < laser.num_beamers; i++)
1383 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1385 if (tmp_num_edges <= 0)
1389 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1390 i, laser.beamer_edge[i], tmp_start_edge);
1393 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1395 tmp_start_edge = laser.beamer_edge[i];
1398 // draw last segment from last beamer to the end
1399 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1405 int last_num_edges = laser.num_edges;
1406 int num_beamers = laser.num_beamers;
1408 // delete laser segments backward from the end to the first beamer
1409 for (i = num_beamers - 1; i >= 0; i--)
1411 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1413 if (laser.beamer_edge[i] - start_edge <= 0)
1416 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1418 last_num_edges = laser.beamer_edge[i];
1419 laser.num_beamers--;
1423 if (last_num_edges - start_edge <= 0)
1424 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1425 last_num_edges, start_edge);
1428 // special case when rotating first beamer: delete laser edge on beamer
1429 // (but do not start scanning on previous edge to prevent mirror sound)
1430 if (last_num_edges - start_edge == 1 && start_edge > 0)
1431 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1433 // delete first segment from start to the first beamer
1434 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1439 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1442 game_mm.laser_enabled = mode;
1445 void DrawLaser_MM(void)
1447 DrawLaser(0, game_mm.laser_enabled);
1450 boolean HitElement(int element, int hit_mask)
1452 if (HitOnlyAnEdge(hit_mask))
1455 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1456 element = MovingOrBlocked2Element_MM(ELX, ELY);
1459 Debug("game:mm:HitElement", "(1): element == %d", element);
1463 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1464 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1467 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1471 AddDamagedField(ELX, ELY);
1473 // this is more precise: check if laser would go through the center
1474 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1476 // prevent cutting through laser emitter with laser beam
1477 if (IS_LASER(element))
1480 // skip the whole element before continuing the scan
1486 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1488 if (LX/TILEX > ELX || LY/TILEY > ELY)
1490 /* skipping scan positions to the right and down skips one scan
1491 position too much, because this is only the top left scan position
1492 of totally four scan positions (plus one to the right, one to the
1493 bottom and one to the bottom right) */
1503 Debug("game:mm:HitElement", "(2): element == %d", element);
1506 if (LX + 5 * XS < 0 ||
1516 Debug("game:mm:HitElement", "(3): element == %d", element);
1519 if (IS_POLAR(element) &&
1520 ((element - EL_POLAR_START) % 2 ||
1521 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1523 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1525 laser.num_damages--;
1530 if (IS_POLAR_CROSS(element) &&
1531 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1533 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1535 laser.num_damages--;
1540 if (!IS_BEAMER(element) &&
1541 !IS_FIBRE_OPTIC(element) &&
1542 !IS_GRID_WOOD(element) &&
1543 element != EL_FUEL_EMPTY)
1546 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1547 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1549 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1552 LX = ELX * TILEX + 14;
1553 LY = ELY * TILEY + 14;
1555 AddLaserEdge(LX, LY);
1558 if (IS_MIRROR(element) ||
1559 IS_MIRROR_FIXED(element) ||
1560 IS_POLAR(element) ||
1561 IS_POLAR_CROSS(element) ||
1562 IS_DF_MIRROR(element) ||
1563 IS_DF_MIRROR_AUTO(element) ||
1564 element == EL_PRISM ||
1565 element == EL_REFRACTOR)
1567 int current_angle = laser.current_angle;
1570 laser.num_damages--;
1572 AddDamagedField(ELX, ELY);
1574 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1577 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1579 if (IS_MIRROR(element) ||
1580 IS_MIRROR_FIXED(element) ||
1581 IS_DF_MIRROR(element) ||
1582 IS_DF_MIRROR_AUTO(element))
1583 laser.current_angle = get_mirrored_angle(laser.current_angle,
1584 get_element_angle(element));
1586 if (element == EL_PRISM || element == EL_REFRACTOR)
1587 laser.current_angle = RND(16);
1589 XS = 2 * Step[laser.current_angle].x;
1590 YS = 2 * Step[laser.current_angle].y;
1592 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1597 LX += step_size * XS;
1598 LY += step_size * YS;
1600 // draw sparkles on mirror
1601 if ((IS_MIRROR(element) ||
1602 IS_MIRROR_FIXED(element) ||
1603 element == EL_PRISM) &&
1604 current_angle != laser.current_angle)
1606 MovDelay[ELX][ELY] = 11; // start animation
1609 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1610 current_angle != laser.current_angle)
1611 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1614 (get_opposite_angle(laser.current_angle) ==
1615 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1617 return (laser.overloaded ? TRUE : FALSE);
1620 if (element == EL_FUEL_FULL)
1622 laser.stops_inside_element = TRUE;
1627 if (element == EL_BOMB || element == EL_MINE)
1629 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1631 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1633 laser.dest_element_last = Tile[ELX][ELY];
1634 laser.dest_element_last_x = ELX;
1635 laser.dest_element_last_y = ELY;
1637 if (element == EL_MINE)
1638 laser.overloaded = TRUE;
1641 if (element == EL_KETTLE ||
1642 element == EL_CELL ||
1643 element == EL_KEY ||
1644 element == EL_LIGHTBALL ||
1645 element == EL_PACMAN ||
1648 if (!IS_PACMAN(element))
1651 if (element == EL_PACMAN)
1654 if (element == EL_KETTLE || element == EL_CELL)
1656 if (game_mm.kettles_still_needed > 0)
1657 game_mm.kettles_still_needed--;
1659 game.snapshot.collected_item = TRUE;
1661 if (game_mm.kettles_still_needed == 0)
1665 DrawLaser(0, DL_LASER_ENABLED);
1668 else if (element == EL_KEY)
1672 else if (IS_PACMAN(element))
1674 DeletePacMan(ELX, ELY);
1677 RaiseScoreElement_MM(element);
1682 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1684 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1686 DrawLaser(0, DL_LASER_ENABLED);
1688 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1690 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1691 game_mm.lights_still_needed--;
1695 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1696 game_mm.lights_still_needed++;
1699 DrawField_MM(ELX, ELY);
1700 DrawLaser(0, DL_LASER_ENABLED);
1705 laser.stops_inside_element = TRUE;
1711 Debug("game:mm:HitElement", "(4): element == %d", element);
1714 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1715 laser.num_beamers < MAX_NUM_BEAMERS &&
1716 laser.beamer[BEAMER_NR(element)][1].num)
1718 int beamer_angle = get_element_angle(element);
1719 int beamer_nr = BEAMER_NR(element);
1723 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1726 laser.num_damages--;
1728 if (IS_FIBRE_OPTIC(element) ||
1729 laser.current_angle == get_opposite_angle(beamer_angle))
1733 LX = ELX * TILEX + 14;
1734 LY = ELY * TILEY + 14;
1736 AddLaserEdge(LX, LY);
1737 AddDamagedField(ELX, ELY);
1739 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1742 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1744 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1745 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1746 ELX = laser.beamer[beamer_nr][pos].x;
1747 ELY = laser.beamer[beamer_nr][pos].y;
1748 LX = ELX * TILEX + 14;
1749 LY = ELY * TILEY + 14;
1751 if (IS_BEAMER(element))
1753 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1754 XS = 2 * Step[laser.current_angle].x;
1755 YS = 2 * Step[laser.current_angle].y;
1758 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1760 AddLaserEdge(LX, LY);
1761 AddDamagedField(ELX, ELY);
1763 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1766 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1768 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1773 LX += step_size * XS;
1774 LY += step_size * YS;
1776 laser.num_beamers++;
1785 boolean HitOnlyAnEdge(int hit_mask)
1787 // check if the laser hit only the edge of an element and, if so, go on
1790 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1794 if ((hit_mask == HIT_MASK_TOPLEFT ||
1795 hit_mask == HIT_MASK_TOPRIGHT ||
1796 hit_mask == HIT_MASK_BOTTOMLEFT ||
1797 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1798 laser.current_angle % 4) // angle is not 90°
1802 if (hit_mask == HIT_MASK_TOPLEFT)
1807 else if (hit_mask == HIT_MASK_TOPRIGHT)
1812 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1817 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1823 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1829 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1836 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1842 boolean HitPolarizer(int element, int hit_mask)
1844 if (HitOnlyAnEdge(hit_mask))
1847 if (IS_DF_GRID(element))
1849 int grid_angle = get_element_angle(element);
1852 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1853 grid_angle, laser.current_angle);
1856 AddLaserEdge(LX, LY);
1857 AddDamagedField(ELX, ELY);
1860 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1862 if (laser.current_angle == grid_angle ||
1863 laser.current_angle == get_opposite_angle(grid_angle))
1865 // skip the whole element before continuing the scan
1871 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1873 if (LX/TILEX > ELX || LY/TILEY > ELY)
1875 /* skipping scan positions to the right and down skips one scan
1876 position too much, because this is only the top left scan position
1877 of totally four scan positions (plus one to the right, one to the
1878 bottom and one to the bottom right) */
1884 AddLaserEdge(LX, LY);
1890 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1892 LX / TILEX, LY / TILEY,
1893 LX % TILEX, LY % TILEY);
1898 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1900 return HitReflectingWalls(element, hit_mask);
1904 return HitAbsorbingWalls(element, hit_mask);
1907 else if (IS_GRID_STEEL(element))
1909 return HitReflectingWalls(element, hit_mask);
1911 else // IS_GRID_WOOD
1913 return HitAbsorbingWalls(element, hit_mask);
1919 boolean HitBlock(int element, int hit_mask)
1921 boolean check = FALSE;
1923 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1924 game_mm.num_keys == 0)
1927 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1930 int ex = ELX * TILEX + 14;
1931 int ey = ELY * TILEY + 14;
1935 for (i = 1; i < 32; i++)
1940 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1945 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1946 return HitAbsorbingWalls(element, hit_mask);
1950 AddLaserEdge(LX - XS, LY - YS);
1951 AddDamagedField(ELX, ELY);
1954 Box[ELX][ELY] = laser.num_edges;
1956 return HitReflectingWalls(element, hit_mask);
1959 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1961 int xs = XS / 2, ys = YS / 2;
1962 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1963 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1965 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1966 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1968 laser.overloaded = (element == EL_GATE_STONE);
1973 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1974 (hit_mask == HIT_MASK_TOP ||
1975 hit_mask == HIT_MASK_LEFT ||
1976 hit_mask == HIT_MASK_RIGHT ||
1977 hit_mask == HIT_MASK_BOTTOM))
1978 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1979 hit_mask == HIT_MASK_BOTTOM),
1980 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1981 hit_mask == HIT_MASK_RIGHT));
1982 AddLaserEdge(LX, LY);
1988 if (element == EL_GATE_STONE && Box[ELX][ELY])
1990 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2002 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2004 int xs = XS / 2, ys = YS / 2;
2005 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2006 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2008 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2009 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2011 laser.overloaded = (element == EL_BLOCK_STONE);
2016 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2017 (hit_mask == HIT_MASK_TOP ||
2018 hit_mask == HIT_MASK_LEFT ||
2019 hit_mask == HIT_MASK_RIGHT ||
2020 hit_mask == HIT_MASK_BOTTOM))
2021 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2022 hit_mask == HIT_MASK_BOTTOM),
2023 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2024 hit_mask == HIT_MASK_RIGHT));
2025 AddDamagedField(ELX, ELY);
2027 LX = ELX * TILEX + 14;
2028 LY = ELY * TILEY + 14;
2030 AddLaserEdge(LX, LY);
2032 laser.stops_inside_element = TRUE;
2040 boolean HitLaserSource(int element, int hit_mask)
2042 if (HitOnlyAnEdge(hit_mask))
2045 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2047 laser.overloaded = TRUE;
2052 boolean HitLaserDestination(int element, int hit_mask)
2054 if (HitOnlyAnEdge(hit_mask))
2057 if (element != EL_EXIT_OPEN &&
2058 !(IS_RECEIVER(element) &&
2059 game_mm.kettles_still_needed == 0 &&
2060 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2062 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2067 if (IS_RECEIVER(element) ||
2068 (IS_22_5_ANGLE(laser.current_angle) &&
2069 (ELX != (LX + 6 * XS) / TILEX ||
2070 ELY != (LY + 6 * YS) / TILEY ||
2079 LX = ELX * TILEX + 14;
2080 LY = ELY * TILEY + 14;
2082 laser.stops_inside_element = TRUE;
2085 AddLaserEdge(LX, LY);
2086 AddDamagedField(ELX, ELY);
2088 if (game_mm.lights_still_needed == 0)
2090 game_mm.level_solved = TRUE;
2092 SetTileCursorActive(FALSE);
2098 boolean HitReflectingWalls(int element, int hit_mask)
2100 // check if laser hits side of a wall with an angle that is not 90°
2101 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2102 hit_mask == HIT_MASK_LEFT ||
2103 hit_mask == HIT_MASK_RIGHT ||
2104 hit_mask == HIT_MASK_BOTTOM))
2106 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2111 if (!IS_DF_GRID(element))
2112 AddLaserEdge(LX, LY);
2114 // check if laser hits wall with an angle of 45°
2115 if (!IS_22_5_ANGLE(laser.current_angle))
2117 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2120 laser.current_angle = get_mirrored_angle(laser.current_angle,
2123 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2126 laser.current_angle = get_mirrored_angle(laser.current_angle,
2130 AddLaserEdge(LX, LY);
2132 XS = 2 * Step[laser.current_angle].x;
2133 YS = 2 * Step[laser.current_angle].y;
2137 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2139 laser.current_angle = get_mirrored_angle(laser.current_angle,
2144 if (!IS_DF_GRID(element))
2145 AddLaserEdge(LX, LY);
2150 if (!IS_DF_GRID(element))
2151 AddLaserEdge(LX, LY + YS / 2);
2154 if (!IS_DF_GRID(element))
2155 AddLaserEdge(LX, LY);
2158 YS = 2 * Step[laser.current_angle].y;
2162 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2164 laser.current_angle = get_mirrored_angle(laser.current_angle,
2169 if (!IS_DF_GRID(element))
2170 AddLaserEdge(LX, LY);
2175 if (!IS_DF_GRID(element))
2176 AddLaserEdge(LX + XS / 2, LY);
2179 if (!IS_DF_GRID(element))
2180 AddLaserEdge(LX, LY);
2183 XS = 2 * Step[laser.current_angle].x;
2189 // reflection at the edge of reflecting DF style wall
2190 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2192 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2193 hit_mask == HIT_MASK_TOPRIGHT) ||
2194 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2195 hit_mask == HIT_MASK_TOPLEFT) ||
2196 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2197 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2198 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2199 hit_mask == HIT_MASK_BOTTOMRIGHT))
2202 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2203 ANG_MIRROR_135 : ANG_MIRROR_45);
2205 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2207 AddDamagedField(ELX, ELY);
2208 AddLaserEdge(LX, LY);
2210 laser.current_angle = get_mirrored_angle(laser.current_angle,
2218 AddLaserEdge(LX, LY);
2224 // reflection inside an edge of reflecting DF style wall
2225 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2227 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2228 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2229 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2230 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2231 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2232 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2233 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2234 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2237 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2238 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2239 ANG_MIRROR_135 : ANG_MIRROR_45);
2241 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2244 AddDamagedField(ELX, ELY);
2247 AddLaserEdge(LX - XS, LY - YS);
2248 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2249 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2251 laser.current_angle = get_mirrored_angle(laser.current_angle,
2259 AddLaserEdge(LX, LY);
2265 // check if laser hits DF style wall with an angle of 90°
2266 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2268 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2269 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2270 (IS_VERT_ANGLE(laser.current_angle) &&
2271 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2273 // laser at last step touched nothing or the same side of the wall
2274 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2276 AddDamagedField(ELX, ELY);
2283 last_hit_mask = hit_mask;
2290 if (!HitOnlyAnEdge(hit_mask))
2292 laser.overloaded = TRUE;
2300 boolean HitAbsorbingWalls(int element, int hit_mask)
2302 if (HitOnlyAnEdge(hit_mask))
2306 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2308 AddLaserEdge(LX - XS, LY - YS);
2315 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2317 AddLaserEdge(LX - XS, LY - YS);
2323 if (IS_WALL_WOOD(element) ||
2324 IS_DF_WALL_WOOD(element) ||
2325 IS_GRID_WOOD(element) ||
2326 IS_GRID_WOOD_FIXED(element) ||
2327 IS_GRID_WOOD_AUTO(element) ||
2328 element == EL_FUSE_ON ||
2329 element == EL_BLOCK_WOOD ||
2330 element == EL_GATE_WOOD)
2332 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2337 if (IS_WALL_ICE(element))
2341 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2342 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2344 // check if laser hits wall with an angle of 90°
2345 if (IS_90_ANGLE(laser.current_angle))
2346 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2348 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2352 for (i = 0; i < 4; i++)
2354 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2355 mask = 15 - (8 >> i);
2356 else if (ABS(XS) == 4 &&
2358 (XS > 0) == (i % 2) &&
2359 (YS < 0) == (i / 2))
2360 mask = 3 + (i / 2) * 9;
2361 else if (ABS(YS) == 4 &&
2363 (XS < 0) == (i % 2) &&
2364 (YS > 0) == (i / 2))
2365 mask = 5 + (i % 2) * 5;
2369 laser.wall_mask = mask;
2371 else if (IS_WALL_AMOEBA(element))
2373 int elx = (LX - 2 * XS) / TILEX;
2374 int ely = (LY - 2 * YS) / TILEY;
2375 int element2 = Tile[elx][ely];
2378 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2380 laser.dest_element = EL_EMPTY;
2388 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2389 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2391 if (IS_90_ANGLE(laser.current_angle))
2392 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2394 laser.dest_element = element2 | EL_WALL_AMOEBA;
2396 laser.wall_mask = mask;
2402 static void OpenExit(int x, int y)
2406 if (!MovDelay[x][y]) // next animation frame
2407 MovDelay[x][y] = 4 * delay;
2409 if (MovDelay[x][y]) // wait some time before next frame
2414 phase = MovDelay[x][y] / delay;
2416 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2417 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2419 if (!MovDelay[x][y])
2421 Tile[x][y] = EL_EXIT_OPEN;
2427 static void OpenSurpriseBall(int x, int y)
2431 if (!MovDelay[x][y]) // next animation frame
2432 MovDelay[x][y] = 50 * delay;
2434 if (MovDelay[x][y]) // wait some time before next frame
2438 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2441 int graphic = el2gfx(Store[x][y]);
2443 int dx = RND(26), dy = RND(26);
2445 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2447 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2448 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2450 MarkTileDirty(x, y);
2453 if (!MovDelay[x][y])
2455 Tile[x][y] = Store[x][y];
2456 Store[x][y] = Store2[x][y] = 0;
2465 static void MeltIce(int x, int y)
2470 if (!MovDelay[x][y]) // next animation frame
2471 MovDelay[x][y] = frames * delay;
2473 if (MovDelay[x][y]) // wait some time before next frame
2476 int wall_mask = Store2[x][y];
2477 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2480 phase = frames - MovDelay[x][y] / delay - 1;
2482 if (!MovDelay[x][y])
2486 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2487 Store[x][y] = Store2[x][y] = 0;
2489 DrawWalls_MM(x, y, Tile[x][y]);
2491 if (Tile[x][y] == EL_WALL_ICE)
2492 Tile[x][y] = EL_EMPTY;
2494 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2495 if (laser.damage[i].is_mirror)
2499 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2501 DrawLaser(0, DL_LASER_DISABLED);
2505 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2507 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2509 laser.redraw = TRUE;
2514 static void GrowAmoeba(int x, int y)
2519 if (!MovDelay[x][y]) // next animation frame
2520 MovDelay[x][y] = frames * delay;
2522 if (MovDelay[x][y]) // wait some time before next frame
2525 int wall_mask = Store2[x][y];
2526 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2529 phase = MovDelay[x][y] / delay;
2531 if (!MovDelay[x][y])
2533 Tile[x][y] = real_element;
2534 Store[x][y] = Store2[x][y] = 0;
2536 DrawWalls_MM(x, y, Tile[x][y]);
2537 DrawLaser(0, DL_LASER_ENABLED);
2539 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2541 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2546 static void DrawFieldAnimated_MM(int x, int y)
2548 int element = Tile[x][y];
2550 if (IS_BLOCKED(x, y))
2555 if (IS_MIRROR(element) ||
2556 IS_MIRROR_FIXED(element) ||
2557 element == EL_PRISM)
2559 if (MovDelay[x][y] != 0) // wait some time before next frame
2563 if (MovDelay[x][y] != 0)
2565 int graphic = IMG_TWINKLE_WHITE;
2566 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2568 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2573 laser.redraw = TRUE;
2576 static void Explode_MM(int x, int y, int phase, int mode)
2578 int num_phase = 9, delay = 2;
2579 int last_phase = num_phase * delay;
2580 int half_phase = (num_phase / 2) * delay;
2582 laser.redraw = TRUE;
2584 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2586 int center_element = Tile[x][y];
2588 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2590 // put moving element to center field (and let it explode there)
2591 center_element = MovingOrBlocked2Element_MM(x, y);
2592 RemoveMovingField_MM(x, y);
2594 Tile[x][y] = center_element;
2597 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2598 Store[x][y] = center_element;
2600 Store[x][y] = EL_EMPTY;
2602 Store2[x][y] = mode;
2604 Tile[x][y] = EL_EXPLODING_OPAQUE;
2605 GfxElement[x][y] = center_element;
2607 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2609 ExplodePhase[x][y] = 1;
2615 GfxFrame[x][y] = 0; // restart explosion animation
2617 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2619 if (phase == half_phase)
2621 Tile[x][y] = EL_EXPLODING_TRANSP;
2623 if (x == ELX && y == ELY)
2627 if (phase == last_phase)
2629 if (Store[x][y] == EL_BOMB_ACTIVE)
2631 DrawLaser(0, DL_LASER_DISABLED);
2634 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2635 Store[x][y] = EL_EMPTY;
2637 GameOver_MM(GAME_OVER_DELAYED);
2639 laser.overloaded = FALSE;
2641 else if (IS_MCDUFFIN(Store[x][y]))
2643 Store[x][y] = EL_EMPTY;
2645 GameOver_MM(GAME_OVER_BOMB);
2648 Tile[x][y] = Store[x][y];
2649 Store[x][y] = Store2[x][y] = 0;
2650 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2655 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2657 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2658 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2660 DrawGraphicAnimation_MM(x, y, graphic, frame);
2662 MarkTileDirty(x, y);
2666 static void Bang_MM(int x, int y)
2668 int element = Tile[x][y];
2671 DrawLaser(0, DL_LASER_ENABLED);
2674 if (IS_PACMAN(element))
2675 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2676 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2677 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2678 else if (element == EL_KEY)
2679 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2681 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2683 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2686 void TurnRound(int x, int y)
2698 { 0, 0 }, { 0, 0 }, { 0, 0 },
2703 int left, right, back;
2707 { MV_DOWN, MV_UP, MV_RIGHT },
2708 { MV_UP, MV_DOWN, MV_LEFT },
2710 { MV_LEFT, MV_RIGHT, MV_DOWN },
2711 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2712 { MV_RIGHT, MV_LEFT, MV_UP }
2715 int element = Tile[x][y];
2716 int old_move_dir = MovDir[x][y];
2717 int right_dir = turn[old_move_dir].right;
2718 int back_dir = turn[old_move_dir].back;
2719 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2720 int right_x = x + right_dx, right_y = y + right_dy;
2722 if (element == EL_PACMAN)
2724 boolean can_turn_right = FALSE;
2726 if (IN_LEV_FIELD(right_x, right_y) &&
2727 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2728 can_turn_right = TRUE;
2731 MovDir[x][y] = right_dir;
2733 MovDir[x][y] = back_dir;
2739 static void StartMoving_MM(int x, int y)
2741 int element = Tile[x][y];
2746 if (CAN_MOVE(element))
2750 if (MovDelay[x][y]) // wait some time before next movement
2758 // now make next step
2760 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2762 if (element == EL_PACMAN &&
2763 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2764 !ObjHit(newx, newy, HIT_POS_CENTER))
2766 Store[newx][newy] = Tile[newx][newy];
2767 Tile[newx][newy] = EL_EMPTY;
2769 DrawField_MM(newx, newy);
2771 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2772 ObjHit(newx, newy, HIT_POS_CENTER))
2774 // object was running against a wall
2781 InitMovingField_MM(x, y, MovDir[x][y]);
2785 ContinueMoving_MM(x, y);
2788 static void ContinueMoving_MM(int x, int y)
2790 int element = Tile[x][y];
2791 int direction = MovDir[x][y];
2792 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2793 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2794 int horiz_move = (dx!=0);
2795 int newx = x + dx, newy = y + dy;
2796 int step = (horiz_move ? dx : dy) * TILEX / 8;
2798 MovPos[x][y] += step;
2800 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2802 Tile[x][y] = EL_EMPTY;
2803 Tile[newx][newy] = element;
2805 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2806 MovDelay[newx][newy] = 0;
2808 if (!CAN_MOVE(element))
2809 MovDir[newx][newy] = 0;
2812 DrawField_MM(newx, newy);
2814 Stop[newx][newy] = TRUE;
2816 if (element == EL_PACMAN)
2818 if (Store[newx][newy] == EL_BOMB)
2819 Bang_MM(newx, newy);
2821 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2822 (LX + 2 * XS) / TILEX == newx &&
2823 (LY + 2 * YS) / TILEY == newy)
2830 else // still moving on
2835 laser.redraw = TRUE;
2838 boolean ClickElement(int x, int y, int button)
2840 static DelayCounter click_delay = { CLICK_DELAY };
2841 static boolean new_button = TRUE;
2842 boolean element_clicked = FALSE;
2847 // initialize static variables
2848 click_delay.count = 0;
2849 click_delay.value = CLICK_DELAY;
2855 // do not rotate objects hit by the laser after the game was solved
2856 if (game_mm.level_solved && Hit[x][y])
2859 if (button == MB_RELEASED)
2862 click_delay.value = CLICK_DELAY;
2864 // release eventually hold auto-rotating mirror
2865 RotateMirror(x, y, MB_RELEASED);
2870 if (!FrameReached(&click_delay) && !new_button)
2873 if (button == MB_MIDDLEBUTTON) // middle button has no function
2876 if (!IN_LEV_FIELD(x, y))
2879 if (Tile[x][y] == EL_EMPTY)
2882 element = Tile[x][y];
2884 if (IS_MIRROR(element) ||
2885 IS_BEAMER(element) ||
2886 IS_POLAR(element) ||
2887 IS_POLAR_CROSS(element) ||
2888 IS_DF_MIRROR(element) ||
2889 IS_DF_MIRROR_AUTO(element))
2891 RotateMirror(x, y, button);
2893 element_clicked = TRUE;
2895 else if (IS_MCDUFFIN(element))
2897 if (!laser.fuse_off)
2899 DrawLaser(0, DL_LASER_DISABLED);
2906 element = get_rotated_element(element, BUTTON_ROTATION(button));
2907 laser.start_angle = get_element_angle(element);
2911 Tile[x][y] = element;
2918 if (!laser.fuse_off)
2921 element_clicked = TRUE;
2923 else if (element == EL_FUSE_ON && laser.fuse_off)
2925 if (x != laser.fuse_x || y != laser.fuse_y)
2928 laser.fuse_off = FALSE;
2929 laser.fuse_x = laser.fuse_y = -1;
2931 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2934 element_clicked = TRUE;
2936 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2938 laser.fuse_off = TRUE;
2941 laser.overloaded = FALSE;
2943 DrawLaser(0, DL_LASER_DISABLED);
2944 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2946 element_clicked = TRUE;
2948 else if (element == EL_LIGHTBALL)
2951 RaiseScoreElement_MM(element);
2952 DrawLaser(0, DL_LASER_ENABLED);
2954 element_clicked = TRUE;
2957 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2960 return element_clicked;
2963 void RotateMirror(int x, int y, int button)
2965 if (button == MB_RELEASED)
2967 // release eventually hold auto-rotating mirror
2974 if (IS_MIRROR(Tile[x][y]) ||
2975 IS_POLAR_CROSS(Tile[x][y]) ||
2976 IS_POLAR(Tile[x][y]) ||
2977 IS_BEAMER(Tile[x][y]) ||
2978 IS_DF_MIRROR(Tile[x][y]) ||
2979 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2980 IS_GRID_WOOD_AUTO(Tile[x][y]))
2982 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2984 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2986 if (button == MB_LEFTBUTTON)
2988 // left mouse button only for manual adjustment, no auto-rotating;
2989 // freeze mirror for until mouse button released
2993 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2995 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2999 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3001 int edge = Hit[x][y];
3007 DrawLaser(edge - 1, DL_LASER_DISABLED);
3011 else if (ObjHit(x, y, HIT_POS_CENTER))
3013 int edge = Hit[x][y];
3017 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3022 DrawLaser(edge - 1, DL_LASER_DISABLED);
3029 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3034 if ((IS_BEAMER(Tile[x][y]) ||
3035 IS_POLAR(Tile[x][y]) ||
3036 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3040 if (IS_BEAMER(Tile[x][y]))
3043 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3044 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3056 DrawLaser(0, DL_LASER_ENABLED);
3060 static void AutoRotateMirrors(void)
3064 if (!FrameReached(&rotate_delay))
3067 for (x = 0; x < lev_fieldx; x++)
3069 for (y = 0; y < lev_fieldy; y++)
3071 int element = Tile[x][y];
3073 // do not rotate objects hit by the laser after the game was solved
3074 if (game_mm.level_solved && Hit[x][y])
3077 if (IS_DF_MIRROR_AUTO(element) ||
3078 IS_GRID_WOOD_AUTO(element) ||
3079 IS_GRID_STEEL_AUTO(element) ||
3080 element == EL_REFRACTOR)
3081 RotateMirror(x, y, MB_RIGHTBUTTON);
3086 boolean ObjHit(int obx, int oby, int bits)
3093 if (bits & HIT_POS_CENTER)
3095 if (CheckLaserPixel(cSX + obx + 15,
3100 if (bits & HIT_POS_EDGE)
3102 for (i = 0; i < 4; i++)
3103 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3104 cSY + oby + 31 * (i / 2)))
3108 if (bits & HIT_POS_BETWEEN)
3110 for (i = 0; i < 4; i++)
3111 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3112 cSY + 4 + oby + 22 * (i / 2)))
3119 void DeletePacMan(int px, int py)
3125 if (game_mm.num_pacman <= 1)
3127 game_mm.num_pacman = 0;
3131 for (i = 0; i < game_mm.num_pacman; i++)
3132 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3135 game_mm.num_pacman--;
3137 for (j = i; j < game_mm.num_pacman; j++)
3139 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3140 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3141 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3145 void ColorCycling(void)
3147 static int CC, Cc = 0;
3149 static int color, old = 0xF00, new = 0x010, mult = 1;
3150 static unsigned short red, green, blue;
3152 if (color_status == STATIC_COLORS)
3157 if (CC < Cc || CC > Cc + 2)
3161 color = old + new * mult;
3167 if (ABS(mult) == 16)
3177 red = 0x0e00 * ((color & 0xF00) >> 8);
3178 green = 0x0e00 * ((color & 0x0F0) >> 4);
3179 blue = 0x0e00 * ((color & 0x00F));
3180 SetRGB(pen_magicolor[0], red, green, blue);
3182 red = 0x1111 * ((color & 0xF00) >> 8);
3183 green = 0x1111 * ((color & 0x0F0) >> 4);
3184 blue = 0x1111 * ((color & 0x00F));
3185 SetRGB(pen_magicolor[1], red, green, blue);
3189 static void GameActions_MM_Ext(void)
3196 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3199 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3201 element = Tile[x][y];
3203 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3204 StartMoving_MM(x, y);
3205 else if (IS_MOVING(x, y))
3206 ContinueMoving_MM(x, y);
3207 else if (IS_EXPLODING(element))
3208 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3209 else if (element == EL_EXIT_OPENING)
3211 else if (element == EL_GRAY_BALL_OPENING)
3212 OpenSurpriseBall(x, y);
3213 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3215 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3218 DrawFieldAnimated_MM(x, y);
3221 AutoRotateMirrors();
3224 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3226 // redraw after Explode_MM() ...
3228 DrawLaser(0, DL_LASER_ENABLED);
3229 laser.redraw = FALSE;
3234 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3238 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3240 DrawLaser(0, DL_LASER_DISABLED);
3245 // skip all following game actions if game is over
3246 if (game_mm.game_over)
3249 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3253 GameOver_MM(GAME_OVER_NO_ENERGY);
3258 if (FrameReached(&energy_delay))
3260 if (game_mm.energy_left > 0)
3261 game_mm.energy_left--;
3263 // when out of energy, wait another frame to play "out of time" sound
3266 element = laser.dest_element;
3269 if (element != Tile[ELX][ELY])
3271 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3272 element, Tile[ELX][ELY]);
3276 if (!laser.overloaded && laser.overload_value == 0 &&
3277 element != EL_BOMB &&
3278 element != EL_BOMB_ACTIVE &&
3279 element != EL_MINE &&
3280 element != EL_MINE_ACTIVE &&
3281 element != EL_BALL_GRAY &&
3282 element != EL_BLOCK_STONE &&
3283 element != EL_BLOCK_WOOD &&
3284 element != EL_FUSE_ON &&
3285 element != EL_FUEL_FULL &&
3286 !IS_WALL_ICE(element) &&
3287 !IS_WALL_AMOEBA(element))
3290 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3292 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3293 (!laser.overloaded && laser.overload_value > 0)) &&
3294 FrameReached(&overload_delay))
3296 if (laser.overloaded)
3297 laser.overload_value++;
3299 laser.overload_value--;
3301 if (game_mm.cheat_no_overload)
3303 laser.overloaded = FALSE;
3304 laser.overload_value = 0;
3307 game_mm.laser_overload_value = laser.overload_value;
3309 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3311 SetLaserColor(0xFF);
3313 DrawLaser(0, DL_LASER_ENABLED);
3316 if (!laser.overloaded)
3317 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3318 else if (setup.sound_loops)
3319 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3321 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3323 if (laser.overloaded)
3326 BlitBitmap(pix[PIX_DOOR], drawto,
3327 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3328 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3329 - laser.overload_value,
3330 OVERLOAD_XSIZE, laser.overload_value,
3331 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3332 - laser.overload_value);
3334 redraw_mask |= REDRAW_DOOR_1;
3339 BlitBitmap(pix[PIX_DOOR], drawto,
3340 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3341 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3342 DX_OVERLOAD, DY_OVERLOAD);
3344 redraw_mask |= REDRAW_DOOR_1;
3347 if (laser.overload_value == MAX_LASER_OVERLOAD)
3349 UpdateAndDisplayGameControlValues();
3353 GameOver_MM(GAME_OVER_OVERLOADED);
3364 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3366 if (game_mm.cheat_no_explosion)
3371 laser.dest_element = EL_EXPLODING_OPAQUE;
3376 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3378 laser.fuse_off = TRUE;
3382 DrawLaser(0, DL_LASER_DISABLED);
3383 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3386 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3388 if (!Store2[ELX][ELY]) // check if content element not yet determined
3390 int last_anim_random_frame = gfx.anim_random_frame;
3393 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3394 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3396 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3397 native_mm_level.ball_choice_mode, 0,
3398 game_mm.ball_choice_pos);
3400 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3401 gfx.anim_random_frame = last_anim_random_frame;
3403 game_mm.ball_choice_pos++;
3405 int new_element = native_mm_level.ball_content[element_pos];
3407 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3408 Store2[ELX][ELY] = TRUE;
3411 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3413 // !!! CHECK AGAIN: Laser on Polarizer !!!
3416 laser.dest_element_last = Tile[ELX][ELY];
3417 laser.dest_element_last_x = ELX;
3418 laser.dest_element_last_y = ELY;
3428 element = EL_MIRROR_START + RND(16);
3434 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3441 element = (rnd == 0 ? EL_FUSE_ON :
3442 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3443 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3444 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3445 EL_MIRROR_FIXED_START + rnd - 25);
3450 graphic = el2gfx(element);
3452 for (i = 0; i < 50; i++)
3458 BlitBitmap(pix[PIX_BACK], drawto,
3459 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3460 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3461 SX + ELX * TILEX + x,
3462 SY + ELY * TILEY + y);
3464 MarkTileDirty(ELX, ELY);
3467 DrawLaser(0, DL_LASER_ENABLED);
3469 Delay_WithScreenUpdates(50);
3472 Tile[ELX][ELY] = element;
3473 DrawField_MM(ELX, ELY);
3476 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3479 // above stuff: GRAY BALL -> PRISM !!!
3481 LX = ELX * TILEX + 14;
3482 LY = ELY * TILEY + 14;
3483 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3490 laser.num_edges -= 2;
3491 laser.num_damages--;
3495 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3496 if (laser.damage[i].is_mirror)
3500 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3502 DrawLaser(0, DL_LASER_DISABLED);
3504 DrawLaser(0, DL_LASER_DISABLED);
3513 if (IS_WALL_ICE(element) && CT > 50)
3515 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3518 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3519 Store[ELX][ELY] = EL_WALL_ICE;
3520 Store2[ELX][ELY] = laser.wall_mask;
3522 laser.dest_element = Tile[ELX][ELY];
3527 for (i = 0; i < 5; i++)
3533 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3537 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3539 Delay_WithScreenUpdates(100);
3542 if (Tile[ELX][ELY] == EL_WALL_ICE)
3543 Tile[ELX][ELY] = EL_EMPTY;
3547 LX = laser.edge[laser.num_edges].x - cSX2;
3548 LY = laser.edge[laser.num_edges].y - cSY2;
3551 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3552 if (laser.damage[i].is_mirror)
3556 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3558 DrawLaser(0, DL_LASER_DISABLED);
3565 if (IS_WALL_AMOEBA(element) && CT > 60)
3567 int k1, k2, k3, dx, dy, de, dm;
3568 int element2 = Tile[ELX][ELY];
3570 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3573 for (i = laser.num_damages - 1; i >= 0; i--)
3574 if (laser.damage[i].is_mirror)
3577 r = laser.num_edges;
3578 d = laser.num_damages;
3585 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3588 DrawLaser(0, DL_LASER_ENABLED);
3591 x = laser.damage[k1].x;
3592 y = laser.damage[k1].y;
3597 for (i = 0; i < 4; i++)
3599 if (laser.wall_mask & (1 << i))
3601 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3602 cSY + ELY * TILEY + 31 * (i / 2)))
3605 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3606 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3613 for (i = 0; i < 4; i++)
3615 if (laser.wall_mask & (1 << i))
3617 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3618 cSY + ELY * TILEY + 31 * (i / 2)))
3625 if (laser.num_beamers > 0 ||
3626 k1 < 1 || k2 < 4 || k3 < 4 ||
3627 CheckLaserPixel(cSX + ELX * TILEX + 14,
3628 cSY + ELY * TILEY + 14))
3630 laser.num_edges = r;
3631 laser.num_damages = d;
3633 DrawLaser(0, DL_LASER_DISABLED);
3636 Tile[ELX][ELY] = element | laser.wall_mask;
3640 de = Tile[ELX][ELY];
3641 dm = laser.wall_mask;
3645 int x = ELX, y = ELY;
3646 int wall_mask = laser.wall_mask;
3649 DrawLaser(0, DL_LASER_ENABLED);
3651 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3653 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3654 Store[x][y] = EL_WALL_AMOEBA;
3655 Store2[x][y] = wall_mask;
3661 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3663 DrawLaser(0, DL_LASER_ENABLED);
3665 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3667 for (i = 4; i >= 0; i--)
3669 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3672 Delay_WithScreenUpdates(20);
3675 DrawLaser(0, DL_LASER_ENABLED);
3680 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3681 laser.stops_inside_element && CT > native_mm_level.time_block)
3686 if (ABS(XS) > ABS(YS))
3693 for (i = 0; i < 4; i++)
3700 x = ELX + Step[k * 4].x;
3701 y = ELY + Step[k * 4].y;
3703 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3706 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3714 laser.overloaded = (element == EL_BLOCK_STONE);
3719 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3722 Tile[x][y] = element;
3724 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3727 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3729 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3730 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3738 if (element == EL_FUEL_FULL && CT > 10)
3740 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3741 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3743 for (i = start; i <= num_init_game_frames; i++)
3745 if (i == num_init_game_frames)
3746 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3747 else if (setup.sound_loops)
3748 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3750 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3752 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3754 UpdateAndDisplayGameControlValues();
3759 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3761 DrawField_MM(ELX, ELY);
3763 DrawLaser(0, DL_LASER_ENABLED);
3771 void GameActions_MM(struct MouseActionInfo action)
3773 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3774 boolean button_released = (action.button == MB_RELEASED);
3776 GameActions_MM_Ext();
3778 CheckSingleStepMode_MM(element_clicked, button_released);
3781 void MovePacMen(void)
3783 int mx, my, ox, oy, nx, ny;
3787 if (++pacman_nr >= game_mm.num_pacman)
3790 game_mm.pacman[pacman_nr].dir--;
3792 for (l = 1; l < 5; l++)
3794 game_mm.pacman[pacman_nr].dir++;
3796 if (game_mm.pacman[pacman_nr].dir > 4)
3797 game_mm.pacman[pacman_nr].dir = 1;
3799 if (game_mm.pacman[pacman_nr].dir % 2)
3802 my = game_mm.pacman[pacman_nr].dir - 2;
3807 mx = 3 - game_mm.pacman[pacman_nr].dir;
3810 ox = game_mm.pacman[pacman_nr].x;
3811 oy = game_mm.pacman[pacman_nr].y;
3814 element = Tile[nx][ny];
3816 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3819 if (!IS_EATABLE4PACMAN(element))
3822 if (ObjHit(nx, ny, HIT_POS_CENTER))
3825 Tile[ox][oy] = EL_EMPTY;
3827 EL_PACMAN_RIGHT - 1 +
3828 (game_mm.pacman[pacman_nr].dir - 1 +
3829 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3831 game_mm.pacman[pacman_nr].x = nx;
3832 game_mm.pacman[pacman_nr].y = ny;
3834 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3836 if (element != EL_EMPTY)
3838 int graphic = el2gfx(Tile[nx][ny]);
3843 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3846 ox = cSX + ox * TILEX;
3847 oy = cSY + oy * TILEY;
3849 for (i = 1; i < 33; i += 2)
3850 BlitBitmap(bitmap, window,
3851 src_x, src_y, TILEX, TILEY,
3852 ox + i * mx, oy + i * my);
3853 Ct = Ct + FrameCounter - CT;
3856 DrawField_MM(nx, ny);
3859 if (!laser.fuse_off)
3861 DrawLaser(0, DL_LASER_ENABLED);
3863 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3865 AddDamagedField(nx, ny);
3867 laser.damage[laser.num_damages - 1].edge = 0;
3871 if (element == EL_BOMB)
3872 DeletePacMan(nx, ny);
3874 if (IS_WALL_AMOEBA(element) &&
3875 (LX + 2 * XS) / TILEX == nx &&
3876 (LY + 2 * YS) / TILEY == ny)
3886 static void InitMovingField_MM(int x, int y, int direction)
3888 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3889 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3891 MovDir[x][y] = direction;
3892 MovDir[newx][newy] = direction;
3894 if (Tile[newx][newy] == EL_EMPTY)
3895 Tile[newx][newy] = EL_BLOCKED;
3898 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3900 int direction = MovDir[x][y];
3901 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3902 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3908 static void Blocked2Moving_MM(int x, int y,
3909 int *comes_from_x, int *comes_from_y)
3911 int oldx = x, oldy = y;
3912 int direction = MovDir[x][y];
3914 if (direction == MV_LEFT)
3916 else if (direction == MV_RIGHT)
3918 else if (direction == MV_UP)
3920 else if (direction == MV_DOWN)
3923 *comes_from_x = oldx;
3924 *comes_from_y = oldy;
3927 static int MovingOrBlocked2Element_MM(int x, int y)
3929 int element = Tile[x][y];
3931 if (element == EL_BLOCKED)
3935 Blocked2Moving_MM(x, y, &oldx, &oldy);
3937 return Tile[oldx][oldy];
3944 static void RemoveField(int x, int y)
3946 Tile[x][y] = EL_EMPTY;
3953 static void RemoveMovingField_MM(int x, int y)
3955 int oldx = x, oldy = y, newx = x, newy = y;
3957 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3960 if (IS_MOVING(x, y))
3962 Moving2Blocked_MM(x, y, &newx, &newy);
3963 if (Tile[newx][newy] != EL_BLOCKED)
3966 else if (Tile[x][y] == EL_BLOCKED)
3968 Blocked2Moving_MM(x, y, &oldx, &oldy);
3969 if (!IS_MOVING(oldx, oldy))
3973 Tile[oldx][oldy] = EL_EMPTY;
3974 Tile[newx][newy] = EL_EMPTY;
3975 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3976 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3978 DrawLevelField_MM(oldx, oldy);
3979 DrawLevelField_MM(newx, newy);
3982 void PlaySoundLevel(int x, int y, int sound_nr)
3984 int sx = SCREENX(x), sy = SCREENY(y);
3986 int silence_distance = 8;
3988 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3989 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3992 if (!IN_LEV_FIELD(x, y) ||
3993 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3994 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3997 volume = SOUND_MAX_VOLUME;
4000 stereo = (sx - SCR_FIELDX/2) * 12;
4002 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4003 if (stereo > SOUND_MAX_RIGHT)
4004 stereo = SOUND_MAX_RIGHT;
4005 if (stereo < SOUND_MAX_LEFT)
4006 stereo = SOUND_MAX_LEFT;
4009 if (!IN_SCR_FIELD(sx, sy))
4011 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4012 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4014 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4017 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4020 static void RaiseScore_MM(int value)
4022 game_mm.score += value;
4025 void RaiseScoreElement_MM(int element)
4030 case EL_PACMAN_RIGHT:
4032 case EL_PACMAN_LEFT:
4033 case EL_PACMAN_DOWN:
4034 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4038 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4043 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4047 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4056 // ----------------------------------------------------------------------------
4057 // Mirror Magic game engine snapshot handling functions
4058 // ----------------------------------------------------------------------------
4060 void SaveEngineSnapshotValues_MM(void)
4064 engine_snapshot_mm.game_mm = game_mm;
4065 engine_snapshot_mm.laser = laser;
4067 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4069 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4071 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4072 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4073 engine_snapshot_mm.Box[x][y] = Box[x][y];
4074 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4078 engine_snapshot_mm.LX = LX;
4079 engine_snapshot_mm.LY = LY;
4080 engine_snapshot_mm.XS = XS;
4081 engine_snapshot_mm.YS = YS;
4082 engine_snapshot_mm.ELX = ELX;
4083 engine_snapshot_mm.ELY = ELY;
4084 engine_snapshot_mm.CT = CT;
4085 engine_snapshot_mm.Ct = Ct;
4087 engine_snapshot_mm.last_LX = last_LX;
4088 engine_snapshot_mm.last_LY = last_LY;
4089 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4090 engine_snapshot_mm.hold_x = hold_x;
4091 engine_snapshot_mm.hold_y = hold_y;
4092 engine_snapshot_mm.pacman_nr = pacman_nr;
4094 engine_snapshot_mm.rotate_delay = rotate_delay;
4095 engine_snapshot_mm.pacman_delay = pacman_delay;
4096 engine_snapshot_mm.energy_delay = energy_delay;
4097 engine_snapshot_mm.overload_delay = overload_delay;
4100 void LoadEngineSnapshotValues_MM(void)
4104 // stored engine snapshot buffers already restored at this point
4106 game_mm = engine_snapshot_mm.game_mm;
4107 laser = engine_snapshot_mm.laser;
4109 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4111 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4113 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4114 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4115 Box[x][y] = engine_snapshot_mm.Box[x][y];
4116 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4120 LX = engine_snapshot_mm.LX;
4121 LY = engine_snapshot_mm.LY;
4122 XS = engine_snapshot_mm.XS;
4123 YS = engine_snapshot_mm.YS;
4124 ELX = engine_snapshot_mm.ELX;
4125 ELY = engine_snapshot_mm.ELY;
4126 CT = engine_snapshot_mm.CT;
4127 Ct = engine_snapshot_mm.Ct;
4129 last_LX = engine_snapshot_mm.last_LX;
4130 last_LY = engine_snapshot_mm.last_LY;
4131 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4132 hold_x = engine_snapshot_mm.hold_x;
4133 hold_y = engine_snapshot_mm.hold_y;
4134 pacman_nr = engine_snapshot_mm.pacman_nr;
4136 rotate_delay = engine_snapshot_mm.rotate_delay;
4137 pacman_delay = engine_snapshot_mm.pacman_delay;
4138 energy_delay = engine_snapshot_mm.energy_delay;
4139 overload_delay = engine_snapshot_mm.overload_delay;
4141 RedrawPlayfield_MM();
4144 static int getAngleFromTouchDelta(int dx, int dy, int base)
4146 double pi = 3.141592653;
4147 double rad = atan2((double)-dy, (double)dx);
4148 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4149 double deg = rad2 * 180.0 / pi;
4151 return (int)(deg * base / 360.0 + 0.5) % base;
4154 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4156 // calculate start (source) position to be at the middle of the tile
4157 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4158 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4159 int dx = dst_mx - src_mx;
4160 int dy = dst_my - src_my;
4169 if (!IN_LEV_FIELD(x, y))
4172 element = Tile[x][y];
4174 if (!IS_MCDUFFIN(element) &&
4175 !IS_MIRROR(element) &&
4176 !IS_BEAMER(element) &&
4177 !IS_POLAR(element) &&
4178 !IS_POLAR_CROSS(element) &&
4179 !IS_DF_MIRROR(element))
4182 angle_old = get_element_angle(element);
4184 if (IS_MCDUFFIN(element))
4186 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4187 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4188 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4189 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4192 else if (IS_MIRROR(element) ||
4193 IS_DF_MIRROR(element))
4195 for (i = 0; i < laser.num_damages; i++)
4197 if (laser.damage[i].x == x &&
4198 laser.damage[i].y == y &&
4199 ObjHit(x, y, HIT_POS_CENTER))
4201 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4202 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4209 if (angle_new == -1)
4211 if (IS_MIRROR(element) ||
4212 IS_DF_MIRROR(element) ||
4216 if (IS_POLAR_CROSS(element))
4219 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4222 button = (angle_new == angle_old ? 0 :
4223 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4224 MB_LEFTBUTTON : MB_RIGHTBUTTON);