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 // skip the whole element before continuing the scan
1482 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1484 if (LX/TILEX > ELX || LY/TILEY > ELY)
1486 /* skipping scan positions to the right and down skips one scan
1487 position too much, because this is only the top left scan position
1488 of totally four scan positions (plus one to the right, one to the
1489 bottom and one to the bottom right) */
1499 Debug("game:mm:HitElement", "(2): element == %d", element);
1502 if (LX + 5 * XS < 0 ||
1512 Debug("game:mm:HitElement", "(3): element == %d", element);
1515 if (IS_POLAR(element) &&
1516 ((element - EL_POLAR_START) % 2 ||
1517 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1519 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1521 laser.num_damages--;
1526 if (IS_POLAR_CROSS(element) &&
1527 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1529 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1531 laser.num_damages--;
1536 if (!IS_BEAMER(element) &&
1537 !IS_FIBRE_OPTIC(element) &&
1538 !IS_GRID_WOOD(element) &&
1539 element != EL_FUEL_EMPTY)
1542 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1543 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1545 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1548 LX = ELX * TILEX + 14;
1549 LY = ELY * TILEY + 14;
1551 AddLaserEdge(LX, LY);
1554 if (IS_MIRROR(element) ||
1555 IS_MIRROR_FIXED(element) ||
1556 IS_POLAR(element) ||
1557 IS_POLAR_CROSS(element) ||
1558 IS_DF_MIRROR(element) ||
1559 IS_DF_MIRROR_AUTO(element) ||
1560 element == EL_PRISM ||
1561 element == EL_REFRACTOR)
1563 int current_angle = laser.current_angle;
1566 laser.num_damages--;
1568 AddDamagedField(ELX, ELY);
1570 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1573 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1575 if (IS_MIRROR(element) ||
1576 IS_MIRROR_FIXED(element) ||
1577 IS_DF_MIRROR(element) ||
1578 IS_DF_MIRROR_AUTO(element))
1579 laser.current_angle = get_mirrored_angle(laser.current_angle,
1580 get_element_angle(element));
1582 if (element == EL_PRISM || element == EL_REFRACTOR)
1583 laser.current_angle = RND(16);
1585 XS = 2 * Step[laser.current_angle].x;
1586 YS = 2 * Step[laser.current_angle].y;
1588 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1593 LX += step_size * XS;
1594 LY += step_size * YS;
1596 // draw sparkles on mirror
1597 if ((IS_MIRROR(element) ||
1598 IS_MIRROR_FIXED(element) ||
1599 element == EL_PRISM) &&
1600 current_angle != laser.current_angle)
1602 MovDelay[ELX][ELY] = 11; // start animation
1605 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1606 current_angle != laser.current_angle)
1607 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1610 (get_opposite_angle(laser.current_angle) ==
1611 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1613 return (laser.overloaded ? TRUE : FALSE);
1616 if (element == EL_FUEL_FULL)
1618 laser.stops_inside_element = TRUE;
1623 if (element == EL_BOMB || element == EL_MINE)
1625 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1627 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1629 laser.dest_element_last = Tile[ELX][ELY];
1630 laser.dest_element_last_x = ELX;
1631 laser.dest_element_last_y = ELY;
1633 if (element == EL_MINE)
1634 laser.overloaded = TRUE;
1637 if (element == EL_KETTLE ||
1638 element == EL_CELL ||
1639 element == EL_KEY ||
1640 element == EL_LIGHTBALL ||
1641 element == EL_PACMAN ||
1644 if (!IS_PACMAN(element))
1647 if (element == EL_PACMAN)
1650 if (element == EL_KETTLE || element == EL_CELL)
1652 if (game_mm.kettles_still_needed > 0)
1653 game_mm.kettles_still_needed--;
1655 game.snapshot.collected_item = TRUE;
1657 if (game_mm.kettles_still_needed == 0)
1661 DrawLaser(0, DL_LASER_ENABLED);
1664 else if (element == EL_KEY)
1668 else if (IS_PACMAN(element))
1670 DeletePacMan(ELX, ELY);
1673 RaiseScoreElement_MM(element);
1678 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1680 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1682 DrawLaser(0, DL_LASER_ENABLED);
1684 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1686 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1687 game_mm.lights_still_needed--;
1691 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1692 game_mm.lights_still_needed++;
1695 DrawField_MM(ELX, ELY);
1696 DrawLaser(0, DL_LASER_ENABLED);
1701 laser.stops_inside_element = TRUE;
1707 Debug("game:mm:HitElement", "(4): element == %d", element);
1710 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1711 laser.num_beamers < MAX_NUM_BEAMERS &&
1712 laser.beamer[BEAMER_NR(element)][1].num)
1714 int beamer_angle = get_element_angle(element);
1715 int beamer_nr = BEAMER_NR(element);
1719 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1722 laser.num_damages--;
1724 if (IS_FIBRE_OPTIC(element) ||
1725 laser.current_angle == get_opposite_angle(beamer_angle))
1729 LX = ELX * TILEX + 14;
1730 LY = ELY * TILEY + 14;
1732 AddLaserEdge(LX, LY);
1733 AddDamagedField(ELX, ELY);
1735 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1738 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1740 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1741 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1742 ELX = laser.beamer[beamer_nr][pos].x;
1743 ELY = laser.beamer[beamer_nr][pos].y;
1744 LX = ELX * TILEX + 14;
1745 LY = ELY * TILEY + 14;
1747 if (IS_BEAMER(element))
1749 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1750 XS = 2 * Step[laser.current_angle].x;
1751 YS = 2 * Step[laser.current_angle].y;
1754 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1756 AddLaserEdge(LX, LY);
1757 AddDamagedField(ELX, ELY);
1759 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1762 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1764 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1769 LX += step_size * XS;
1770 LY += step_size * YS;
1772 laser.num_beamers++;
1781 boolean HitOnlyAnEdge(int hit_mask)
1783 // check if the laser hit only the edge of an element and, if so, go on
1786 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1790 if ((hit_mask == HIT_MASK_TOPLEFT ||
1791 hit_mask == HIT_MASK_TOPRIGHT ||
1792 hit_mask == HIT_MASK_BOTTOMLEFT ||
1793 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1794 laser.current_angle % 4) // angle is not 90°
1798 if (hit_mask == HIT_MASK_TOPLEFT)
1803 else if (hit_mask == HIT_MASK_TOPRIGHT)
1808 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1813 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1819 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1825 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1832 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1838 boolean HitPolarizer(int element, int hit_mask)
1840 if (HitOnlyAnEdge(hit_mask))
1843 if (IS_DF_GRID(element))
1845 int grid_angle = get_element_angle(element);
1848 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1849 grid_angle, laser.current_angle);
1852 AddLaserEdge(LX, LY);
1853 AddDamagedField(ELX, ELY);
1856 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1858 if (laser.current_angle == grid_angle ||
1859 laser.current_angle == get_opposite_angle(grid_angle))
1861 // skip the whole element before continuing the scan
1867 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1869 if (LX/TILEX > ELX || LY/TILEY > ELY)
1871 /* skipping scan positions to the right and down skips one scan
1872 position too much, because this is only the top left scan position
1873 of totally four scan positions (plus one to the right, one to the
1874 bottom and one to the bottom right) */
1880 AddLaserEdge(LX, LY);
1886 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1888 LX / TILEX, LY / TILEY,
1889 LX % TILEX, LY % TILEY);
1894 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1896 return HitReflectingWalls(element, hit_mask);
1900 return HitAbsorbingWalls(element, hit_mask);
1903 else if (IS_GRID_STEEL(element))
1905 return HitReflectingWalls(element, hit_mask);
1907 else // IS_GRID_WOOD
1909 return HitAbsorbingWalls(element, hit_mask);
1915 boolean HitBlock(int element, int hit_mask)
1917 boolean check = FALSE;
1919 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1920 game_mm.num_keys == 0)
1923 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1926 int ex = ELX * TILEX + 14;
1927 int ey = ELY * TILEY + 14;
1931 for (i = 1; i < 32; i++)
1936 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1941 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1942 return HitAbsorbingWalls(element, hit_mask);
1946 AddLaserEdge(LX - XS, LY - YS);
1947 AddDamagedField(ELX, ELY);
1950 Box[ELX][ELY] = laser.num_edges;
1952 return HitReflectingWalls(element, hit_mask);
1955 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1957 int xs = XS / 2, ys = YS / 2;
1958 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1959 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1961 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1962 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1964 laser.overloaded = (element == EL_GATE_STONE);
1969 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1970 (hit_mask == HIT_MASK_TOP ||
1971 hit_mask == HIT_MASK_LEFT ||
1972 hit_mask == HIT_MASK_RIGHT ||
1973 hit_mask == HIT_MASK_BOTTOM))
1974 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1975 hit_mask == HIT_MASK_BOTTOM),
1976 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1977 hit_mask == HIT_MASK_RIGHT));
1978 AddLaserEdge(LX, LY);
1984 if (element == EL_GATE_STONE && Box[ELX][ELY])
1986 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1998 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2000 int xs = XS / 2, ys = YS / 2;
2001 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
2002 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
2004 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
2005 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
2007 laser.overloaded = (element == EL_BLOCK_STONE);
2012 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2013 (hit_mask == HIT_MASK_TOP ||
2014 hit_mask == HIT_MASK_LEFT ||
2015 hit_mask == HIT_MASK_RIGHT ||
2016 hit_mask == HIT_MASK_BOTTOM))
2017 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2018 hit_mask == HIT_MASK_BOTTOM),
2019 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2020 hit_mask == HIT_MASK_RIGHT));
2021 AddDamagedField(ELX, ELY);
2023 LX = ELX * TILEX + 14;
2024 LY = ELY * TILEY + 14;
2026 AddLaserEdge(LX, LY);
2028 laser.stops_inside_element = TRUE;
2036 boolean HitLaserSource(int element, int hit_mask)
2038 if (HitOnlyAnEdge(hit_mask))
2041 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2043 laser.overloaded = TRUE;
2048 boolean HitLaserDestination(int element, int hit_mask)
2050 if (HitOnlyAnEdge(hit_mask))
2053 if (element != EL_EXIT_OPEN &&
2054 !(IS_RECEIVER(element) &&
2055 game_mm.kettles_still_needed == 0 &&
2056 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2058 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2063 if (IS_RECEIVER(element) ||
2064 (IS_22_5_ANGLE(laser.current_angle) &&
2065 (ELX != (LX + 6 * XS) / TILEX ||
2066 ELY != (LY + 6 * YS) / TILEY ||
2075 LX = ELX * TILEX + 14;
2076 LY = ELY * TILEY + 14;
2078 laser.stops_inside_element = TRUE;
2081 AddLaserEdge(LX, LY);
2082 AddDamagedField(ELX, ELY);
2084 if (game_mm.lights_still_needed == 0)
2086 game_mm.level_solved = TRUE;
2088 SetTileCursorActive(FALSE);
2094 boolean HitReflectingWalls(int element, int hit_mask)
2096 // check if laser hits side of a wall with an angle that is not 90°
2097 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2098 hit_mask == HIT_MASK_LEFT ||
2099 hit_mask == HIT_MASK_RIGHT ||
2100 hit_mask == HIT_MASK_BOTTOM))
2102 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2107 if (!IS_DF_GRID(element))
2108 AddLaserEdge(LX, LY);
2110 // check if laser hits wall with an angle of 45°
2111 if (!IS_22_5_ANGLE(laser.current_angle))
2113 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2116 laser.current_angle = get_mirrored_angle(laser.current_angle,
2119 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2122 laser.current_angle = get_mirrored_angle(laser.current_angle,
2126 AddLaserEdge(LX, LY);
2128 XS = 2 * Step[laser.current_angle].x;
2129 YS = 2 * Step[laser.current_angle].y;
2133 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2135 laser.current_angle = get_mirrored_angle(laser.current_angle,
2140 if (!IS_DF_GRID(element))
2141 AddLaserEdge(LX, LY);
2146 if (!IS_DF_GRID(element))
2147 AddLaserEdge(LX, LY + YS / 2);
2150 if (!IS_DF_GRID(element))
2151 AddLaserEdge(LX, LY);
2154 YS = 2 * Step[laser.current_angle].y;
2158 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2160 laser.current_angle = get_mirrored_angle(laser.current_angle,
2165 if (!IS_DF_GRID(element))
2166 AddLaserEdge(LX, LY);
2171 if (!IS_DF_GRID(element))
2172 AddLaserEdge(LX + XS / 2, LY);
2175 if (!IS_DF_GRID(element))
2176 AddLaserEdge(LX, LY);
2179 XS = 2 * Step[laser.current_angle].x;
2185 // reflection at the edge of reflecting DF style wall
2186 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2188 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2189 hit_mask == HIT_MASK_TOPRIGHT) ||
2190 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2191 hit_mask == HIT_MASK_TOPLEFT) ||
2192 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2193 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2194 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2195 hit_mask == HIT_MASK_BOTTOMRIGHT))
2198 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2199 ANG_MIRROR_135 : ANG_MIRROR_45);
2201 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2203 AddDamagedField(ELX, ELY);
2204 AddLaserEdge(LX, LY);
2206 laser.current_angle = get_mirrored_angle(laser.current_angle,
2214 AddLaserEdge(LX, LY);
2220 // reflection inside an edge of reflecting DF style wall
2221 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2223 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2224 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2225 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2226 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2227 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2228 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2229 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2230 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2233 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2234 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2235 ANG_MIRROR_135 : ANG_MIRROR_45);
2237 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2240 AddDamagedField(ELX, ELY);
2243 AddLaserEdge(LX - XS, LY - YS);
2244 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2245 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2247 laser.current_angle = get_mirrored_angle(laser.current_angle,
2255 AddLaserEdge(LX, LY);
2261 // check if laser hits DF style wall with an angle of 90°
2262 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2264 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2265 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2266 (IS_VERT_ANGLE(laser.current_angle) &&
2267 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2269 // laser at last step touched nothing or the same side of the wall
2270 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2272 AddDamagedField(ELX, ELY);
2279 last_hit_mask = hit_mask;
2286 if (!HitOnlyAnEdge(hit_mask))
2288 laser.overloaded = TRUE;
2296 boolean HitAbsorbingWalls(int element, int hit_mask)
2298 if (HitOnlyAnEdge(hit_mask))
2302 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2304 AddLaserEdge(LX - XS, LY - YS);
2311 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2313 AddLaserEdge(LX - XS, LY - YS);
2319 if (IS_WALL_WOOD(element) ||
2320 IS_DF_WALL_WOOD(element) ||
2321 IS_GRID_WOOD(element) ||
2322 IS_GRID_WOOD_FIXED(element) ||
2323 IS_GRID_WOOD_AUTO(element) ||
2324 element == EL_FUSE_ON ||
2325 element == EL_BLOCK_WOOD ||
2326 element == EL_GATE_WOOD)
2328 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2333 if (IS_WALL_ICE(element))
2337 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2338 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2340 // check if laser hits wall with an angle of 90°
2341 if (IS_90_ANGLE(laser.current_angle))
2342 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2344 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2348 for (i = 0; i < 4; i++)
2350 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2351 mask = 15 - (8 >> i);
2352 else if (ABS(XS) == 4 &&
2354 (XS > 0) == (i % 2) &&
2355 (YS < 0) == (i / 2))
2356 mask = 3 + (i / 2) * 9;
2357 else if (ABS(YS) == 4 &&
2359 (XS < 0) == (i % 2) &&
2360 (YS > 0) == (i / 2))
2361 mask = 5 + (i % 2) * 5;
2365 laser.wall_mask = mask;
2367 else if (IS_WALL_AMOEBA(element))
2369 int elx = (LX - 2 * XS) / TILEX;
2370 int ely = (LY - 2 * YS) / TILEY;
2371 int element2 = Tile[elx][ely];
2374 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2376 laser.dest_element = EL_EMPTY;
2384 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2385 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2387 if (IS_90_ANGLE(laser.current_angle))
2388 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2390 laser.dest_element = element2 | EL_WALL_AMOEBA;
2392 laser.wall_mask = mask;
2398 static void OpenExit(int x, int y)
2402 if (!MovDelay[x][y]) // next animation frame
2403 MovDelay[x][y] = 4 * delay;
2405 if (MovDelay[x][y]) // wait some time before next frame
2410 phase = MovDelay[x][y] / delay;
2412 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2413 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2415 if (!MovDelay[x][y])
2417 Tile[x][y] = EL_EXIT_OPEN;
2423 static void OpenSurpriseBall(int x, int y)
2427 if (!MovDelay[x][y]) // next animation frame
2428 MovDelay[x][y] = 50 * delay;
2430 if (MovDelay[x][y]) // wait some time before next frame
2434 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2437 int graphic = el2gfx(Store[x][y]);
2439 int dx = RND(26), dy = RND(26);
2441 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2443 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2444 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2446 MarkTileDirty(x, y);
2449 if (!MovDelay[x][y])
2451 Tile[x][y] = Store[x][y];
2452 Store[x][y] = Store2[x][y] = 0;
2461 static void MeltIce(int x, int y)
2466 if (!MovDelay[x][y]) // next animation frame
2467 MovDelay[x][y] = frames * delay;
2469 if (MovDelay[x][y]) // wait some time before next frame
2472 int wall_mask = Store2[x][y];
2473 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2476 phase = frames - MovDelay[x][y] / delay - 1;
2478 if (!MovDelay[x][y])
2482 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2483 Store[x][y] = Store2[x][y] = 0;
2485 DrawWalls_MM(x, y, Tile[x][y]);
2487 if (Tile[x][y] == EL_WALL_ICE)
2488 Tile[x][y] = EL_EMPTY;
2490 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2491 if (laser.damage[i].is_mirror)
2495 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2497 DrawLaser(0, DL_LASER_DISABLED);
2501 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2503 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2505 laser.redraw = TRUE;
2510 static void GrowAmoeba(int x, int y)
2515 if (!MovDelay[x][y]) // next animation frame
2516 MovDelay[x][y] = frames * delay;
2518 if (MovDelay[x][y]) // wait some time before next frame
2521 int wall_mask = Store2[x][y];
2522 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2525 phase = MovDelay[x][y] / delay;
2527 if (!MovDelay[x][y])
2529 Tile[x][y] = real_element;
2530 Store[x][y] = Store2[x][y] = 0;
2532 DrawWalls_MM(x, y, Tile[x][y]);
2533 DrawLaser(0, DL_LASER_ENABLED);
2535 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2537 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2542 static void DrawFieldAnimated_MM(int x, int y)
2544 int element = Tile[x][y];
2546 if (IS_BLOCKED(x, y))
2551 if (IS_MIRROR(element) ||
2552 IS_MIRROR_FIXED(element) ||
2553 element == EL_PRISM)
2555 if (MovDelay[x][y] != 0) // wait some time before next frame
2559 if (MovDelay[x][y] != 0)
2561 int graphic = IMG_TWINKLE_WHITE;
2562 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2564 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2569 laser.redraw = TRUE;
2572 static void Explode_MM(int x, int y, int phase, int mode)
2574 int num_phase = 9, delay = 2;
2575 int last_phase = num_phase * delay;
2576 int half_phase = (num_phase / 2) * delay;
2578 laser.redraw = TRUE;
2580 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2582 int center_element = Tile[x][y];
2584 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2586 // put moving element to center field (and let it explode there)
2587 center_element = MovingOrBlocked2Element_MM(x, y);
2588 RemoveMovingField_MM(x, y);
2590 Tile[x][y] = center_element;
2593 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2594 Store[x][y] = center_element;
2596 Store[x][y] = EL_EMPTY;
2598 Store2[x][y] = mode;
2600 Tile[x][y] = EL_EXPLODING_OPAQUE;
2601 GfxElement[x][y] = center_element;
2603 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2605 ExplodePhase[x][y] = 1;
2611 GfxFrame[x][y] = 0; // restart explosion animation
2613 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2615 if (phase == half_phase)
2617 Tile[x][y] = EL_EXPLODING_TRANSP;
2619 if (x == ELX && y == ELY)
2623 if (phase == last_phase)
2625 if (Store[x][y] == EL_BOMB_ACTIVE)
2627 DrawLaser(0, DL_LASER_DISABLED);
2630 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2631 Store[x][y] = EL_EMPTY;
2633 GameOver_MM(GAME_OVER_DELAYED);
2635 laser.overloaded = FALSE;
2637 else if (IS_MCDUFFIN(Store[x][y]))
2639 Store[x][y] = EL_EMPTY;
2641 GameOver_MM(GAME_OVER_BOMB);
2644 Tile[x][y] = Store[x][y];
2645 Store[x][y] = Store2[x][y] = 0;
2646 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2651 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2653 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2654 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2656 DrawGraphicAnimation_MM(x, y, graphic, frame);
2658 MarkTileDirty(x, y);
2662 static void Bang_MM(int x, int y)
2664 int element = Tile[x][y];
2667 DrawLaser(0, DL_LASER_ENABLED);
2670 if (IS_PACMAN(element))
2671 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2672 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2673 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2674 else if (element == EL_KEY)
2675 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2677 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2679 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2682 void TurnRound(int x, int y)
2694 { 0, 0 }, { 0, 0 }, { 0, 0 },
2699 int left, right, back;
2703 { MV_DOWN, MV_UP, MV_RIGHT },
2704 { MV_UP, MV_DOWN, MV_LEFT },
2706 { MV_LEFT, MV_RIGHT, MV_DOWN },
2707 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2708 { MV_RIGHT, MV_LEFT, MV_UP }
2711 int element = Tile[x][y];
2712 int old_move_dir = MovDir[x][y];
2713 int right_dir = turn[old_move_dir].right;
2714 int back_dir = turn[old_move_dir].back;
2715 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2716 int right_x = x + right_dx, right_y = y + right_dy;
2718 if (element == EL_PACMAN)
2720 boolean can_turn_right = FALSE;
2722 if (IN_LEV_FIELD(right_x, right_y) &&
2723 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2724 can_turn_right = TRUE;
2727 MovDir[x][y] = right_dir;
2729 MovDir[x][y] = back_dir;
2735 static void StartMoving_MM(int x, int y)
2737 int element = Tile[x][y];
2742 if (CAN_MOVE(element))
2746 if (MovDelay[x][y]) // wait some time before next movement
2754 // now make next step
2756 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2758 if (element == EL_PACMAN &&
2759 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2760 !ObjHit(newx, newy, HIT_POS_CENTER))
2762 Store[newx][newy] = Tile[newx][newy];
2763 Tile[newx][newy] = EL_EMPTY;
2765 DrawField_MM(newx, newy);
2767 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2768 ObjHit(newx, newy, HIT_POS_CENTER))
2770 // object was running against a wall
2777 InitMovingField_MM(x, y, MovDir[x][y]);
2781 ContinueMoving_MM(x, y);
2784 static void ContinueMoving_MM(int x, int y)
2786 int element = Tile[x][y];
2787 int direction = MovDir[x][y];
2788 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2789 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2790 int horiz_move = (dx!=0);
2791 int newx = x + dx, newy = y + dy;
2792 int step = (horiz_move ? dx : dy) * TILEX / 8;
2794 MovPos[x][y] += step;
2796 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2798 Tile[x][y] = EL_EMPTY;
2799 Tile[newx][newy] = element;
2801 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2802 MovDelay[newx][newy] = 0;
2804 if (!CAN_MOVE(element))
2805 MovDir[newx][newy] = 0;
2808 DrawField_MM(newx, newy);
2810 Stop[newx][newy] = TRUE;
2812 if (element == EL_PACMAN)
2814 if (Store[newx][newy] == EL_BOMB)
2815 Bang_MM(newx, newy);
2817 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2818 (LX + 2 * XS) / TILEX == newx &&
2819 (LY + 2 * YS) / TILEY == newy)
2826 else // still moving on
2831 laser.redraw = TRUE;
2834 boolean ClickElement(int x, int y, int button)
2836 static DelayCounter click_delay = { CLICK_DELAY };
2837 static boolean new_button = TRUE;
2838 boolean element_clicked = FALSE;
2843 // initialize static variables
2844 click_delay.count = 0;
2845 click_delay.value = CLICK_DELAY;
2851 // do not rotate objects hit by the laser after the game was solved
2852 if (game_mm.level_solved && Hit[x][y])
2855 if (button == MB_RELEASED)
2858 click_delay.value = CLICK_DELAY;
2860 // release eventually hold auto-rotating mirror
2861 RotateMirror(x, y, MB_RELEASED);
2866 if (!FrameReached(&click_delay) && !new_button)
2869 if (button == MB_MIDDLEBUTTON) // middle button has no function
2872 if (!IN_LEV_FIELD(x, y))
2875 if (Tile[x][y] == EL_EMPTY)
2878 element = Tile[x][y];
2880 if (IS_MIRROR(element) ||
2881 IS_BEAMER(element) ||
2882 IS_POLAR(element) ||
2883 IS_POLAR_CROSS(element) ||
2884 IS_DF_MIRROR(element) ||
2885 IS_DF_MIRROR_AUTO(element))
2887 RotateMirror(x, y, button);
2889 element_clicked = TRUE;
2891 else if (IS_MCDUFFIN(element))
2893 if (!laser.fuse_off)
2895 DrawLaser(0, DL_LASER_DISABLED);
2902 element = get_rotated_element(element, BUTTON_ROTATION(button));
2903 laser.start_angle = get_element_angle(element);
2907 Tile[x][y] = element;
2914 if (!laser.fuse_off)
2917 element_clicked = TRUE;
2919 else if (element == EL_FUSE_ON && laser.fuse_off)
2921 if (x != laser.fuse_x || y != laser.fuse_y)
2924 laser.fuse_off = FALSE;
2925 laser.fuse_x = laser.fuse_y = -1;
2927 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2930 element_clicked = TRUE;
2932 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2934 laser.fuse_off = TRUE;
2937 laser.overloaded = FALSE;
2939 DrawLaser(0, DL_LASER_DISABLED);
2940 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2942 element_clicked = TRUE;
2944 else if (element == EL_LIGHTBALL)
2947 RaiseScoreElement_MM(element);
2948 DrawLaser(0, DL_LASER_ENABLED);
2950 element_clicked = TRUE;
2953 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2956 return element_clicked;
2959 void RotateMirror(int x, int y, int button)
2961 if (button == MB_RELEASED)
2963 // release eventually hold auto-rotating mirror
2970 if (IS_MIRROR(Tile[x][y]) ||
2971 IS_POLAR_CROSS(Tile[x][y]) ||
2972 IS_POLAR(Tile[x][y]) ||
2973 IS_BEAMER(Tile[x][y]) ||
2974 IS_DF_MIRROR(Tile[x][y]) ||
2975 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2976 IS_GRID_WOOD_AUTO(Tile[x][y]))
2978 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2980 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2982 if (button == MB_LEFTBUTTON)
2984 // left mouse button only for manual adjustment, no auto-rotating;
2985 // freeze mirror for until mouse button released
2989 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2991 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2995 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2997 int edge = Hit[x][y];
3003 DrawLaser(edge - 1, DL_LASER_DISABLED);
3007 else if (ObjHit(x, y, HIT_POS_CENTER))
3009 int edge = Hit[x][y];
3013 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3018 DrawLaser(edge - 1, DL_LASER_DISABLED);
3025 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3030 if ((IS_BEAMER(Tile[x][y]) ||
3031 IS_POLAR(Tile[x][y]) ||
3032 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3036 if (IS_BEAMER(Tile[x][y]))
3039 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3040 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3052 DrawLaser(0, DL_LASER_ENABLED);
3056 static void AutoRotateMirrors(void)
3060 if (!FrameReached(&rotate_delay))
3063 for (x = 0; x < lev_fieldx; x++)
3065 for (y = 0; y < lev_fieldy; y++)
3067 int element = Tile[x][y];
3069 // do not rotate objects hit by the laser after the game was solved
3070 if (game_mm.level_solved && Hit[x][y])
3073 if (IS_DF_MIRROR_AUTO(element) ||
3074 IS_GRID_WOOD_AUTO(element) ||
3075 IS_GRID_STEEL_AUTO(element) ||
3076 element == EL_REFRACTOR)
3077 RotateMirror(x, y, MB_RIGHTBUTTON);
3082 boolean ObjHit(int obx, int oby, int bits)
3089 if (bits & HIT_POS_CENTER)
3091 if (CheckLaserPixel(cSX + obx + 15,
3096 if (bits & HIT_POS_EDGE)
3098 for (i = 0; i < 4; i++)
3099 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3100 cSY + oby + 31 * (i / 2)))
3104 if (bits & HIT_POS_BETWEEN)
3106 for (i = 0; i < 4; i++)
3107 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3108 cSY + 4 + oby + 22 * (i / 2)))
3115 void DeletePacMan(int px, int py)
3121 if (game_mm.num_pacman <= 1)
3123 game_mm.num_pacman = 0;
3127 for (i = 0; i < game_mm.num_pacman; i++)
3128 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3131 game_mm.num_pacman--;
3133 for (j = i; j < game_mm.num_pacman; j++)
3135 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3136 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3137 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3141 void ColorCycling(void)
3143 static int CC, Cc = 0;
3145 static int color, old = 0xF00, new = 0x010, mult = 1;
3146 static unsigned short red, green, blue;
3148 if (color_status == STATIC_COLORS)
3153 if (CC < Cc || CC > Cc + 2)
3157 color = old + new * mult;
3163 if (ABS(mult) == 16)
3173 red = 0x0e00 * ((color & 0xF00) >> 8);
3174 green = 0x0e00 * ((color & 0x0F0) >> 4);
3175 blue = 0x0e00 * ((color & 0x00F));
3176 SetRGB(pen_magicolor[0], red, green, blue);
3178 red = 0x1111 * ((color & 0xF00) >> 8);
3179 green = 0x1111 * ((color & 0x0F0) >> 4);
3180 blue = 0x1111 * ((color & 0x00F));
3181 SetRGB(pen_magicolor[1], red, green, blue);
3185 static void GameActions_MM_Ext(void)
3192 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3195 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3197 element = Tile[x][y];
3199 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3200 StartMoving_MM(x, y);
3201 else if (IS_MOVING(x, y))
3202 ContinueMoving_MM(x, y);
3203 else if (IS_EXPLODING(element))
3204 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3205 else if (element == EL_EXIT_OPENING)
3207 else if (element == EL_GRAY_BALL_OPENING)
3208 OpenSurpriseBall(x, y);
3209 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3211 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3214 DrawFieldAnimated_MM(x, y);
3217 AutoRotateMirrors();
3220 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3222 // redraw after Explode_MM() ...
3224 DrawLaser(0, DL_LASER_ENABLED);
3225 laser.redraw = FALSE;
3230 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3234 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3236 DrawLaser(0, DL_LASER_DISABLED);
3241 // skip all following game actions if game is over
3242 if (game_mm.game_over)
3245 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3249 GameOver_MM(GAME_OVER_NO_ENERGY);
3254 if (FrameReached(&energy_delay))
3256 if (game_mm.energy_left > 0)
3257 game_mm.energy_left--;
3259 // when out of energy, wait another frame to play "out of time" sound
3262 element = laser.dest_element;
3265 if (element != Tile[ELX][ELY])
3267 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3268 element, Tile[ELX][ELY]);
3272 if (!laser.overloaded && laser.overload_value == 0 &&
3273 element != EL_BOMB &&
3274 element != EL_BOMB_ACTIVE &&
3275 element != EL_MINE &&
3276 element != EL_MINE_ACTIVE &&
3277 element != EL_BALL_GRAY &&
3278 element != EL_BLOCK_STONE &&
3279 element != EL_BLOCK_WOOD &&
3280 element != EL_FUSE_ON &&
3281 element != EL_FUEL_FULL &&
3282 !IS_WALL_ICE(element) &&
3283 !IS_WALL_AMOEBA(element))
3286 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3288 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3289 (!laser.overloaded && laser.overload_value > 0)) &&
3290 FrameReached(&overload_delay))
3292 if (laser.overloaded)
3293 laser.overload_value++;
3295 laser.overload_value--;
3297 if (game_mm.cheat_no_overload)
3299 laser.overloaded = FALSE;
3300 laser.overload_value = 0;
3303 game_mm.laser_overload_value = laser.overload_value;
3305 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3307 SetLaserColor(0xFF);
3309 DrawLaser(0, DL_LASER_ENABLED);
3312 if (!laser.overloaded)
3313 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3314 else if (setup.sound_loops)
3315 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3317 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3319 if (laser.overloaded)
3322 BlitBitmap(pix[PIX_DOOR], drawto,
3323 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3324 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3325 - laser.overload_value,
3326 OVERLOAD_XSIZE, laser.overload_value,
3327 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3328 - laser.overload_value);
3330 redraw_mask |= REDRAW_DOOR_1;
3335 BlitBitmap(pix[PIX_DOOR], drawto,
3336 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3337 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3338 DX_OVERLOAD, DY_OVERLOAD);
3340 redraw_mask |= REDRAW_DOOR_1;
3343 if (laser.overload_value == MAX_LASER_OVERLOAD)
3345 UpdateAndDisplayGameControlValues();
3349 GameOver_MM(GAME_OVER_OVERLOADED);
3360 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3362 if (game_mm.cheat_no_explosion)
3367 laser.dest_element = EL_EXPLODING_OPAQUE;
3372 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3374 laser.fuse_off = TRUE;
3378 DrawLaser(0, DL_LASER_DISABLED);
3379 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3382 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3384 if (!Store2[ELX][ELY]) // check if content element not yet determined
3386 int last_anim_random_frame = gfx.anim_random_frame;
3389 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3390 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3392 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3393 native_mm_level.ball_choice_mode, 0,
3394 game_mm.ball_choice_pos);
3396 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3397 gfx.anim_random_frame = last_anim_random_frame;
3399 game_mm.ball_choice_pos++;
3401 int new_element = native_mm_level.ball_content[element_pos];
3403 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3404 Store2[ELX][ELY] = TRUE;
3407 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3409 // !!! CHECK AGAIN: Laser on Polarizer !!!
3412 laser.dest_element_last = Tile[ELX][ELY];
3413 laser.dest_element_last_x = ELX;
3414 laser.dest_element_last_y = ELY;
3424 element = EL_MIRROR_START + RND(16);
3430 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3437 element = (rnd == 0 ? EL_FUSE_ON :
3438 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3439 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3440 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3441 EL_MIRROR_FIXED_START + rnd - 25);
3446 graphic = el2gfx(element);
3448 for (i = 0; i < 50; i++)
3454 BlitBitmap(pix[PIX_BACK], drawto,
3455 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3456 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3457 SX + ELX * TILEX + x,
3458 SY + ELY * TILEY + y);
3460 MarkTileDirty(ELX, ELY);
3463 DrawLaser(0, DL_LASER_ENABLED);
3465 Delay_WithScreenUpdates(50);
3468 Tile[ELX][ELY] = element;
3469 DrawField_MM(ELX, ELY);
3472 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3475 // above stuff: GRAY BALL -> PRISM !!!
3477 LX = ELX * TILEX + 14;
3478 LY = ELY * TILEY + 14;
3479 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3486 laser.num_edges -= 2;
3487 laser.num_damages--;
3491 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3492 if (laser.damage[i].is_mirror)
3496 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3498 DrawLaser(0, DL_LASER_DISABLED);
3500 DrawLaser(0, DL_LASER_DISABLED);
3509 if (IS_WALL_ICE(element) && CT > 50)
3511 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3514 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3515 Store[ELX][ELY] = EL_WALL_ICE;
3516 Store2[ELX][ELY] = laser.wall_mask;
3518 laser.dest_element = Tile[ELX][ELY];
3523 for (i = 0; i < 5; i++)
3529 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3533 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3535 Delay_WithScreenUpdates(100);
3538 if (Tile[ELX][ELY] == EL_WALL_ICE)
3539 Tile[ELX][ELY] = EL_EMPTY;
3543 LX = laser.edge[laser.num_edges].x - cSX2;
3544 LY = laser.edge[laser.num_edges].y - cSY2;
3547 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3548 if (laser.damage[i].is_mirror)
3552 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3554 DrawLaser(0, DL_LASER_DISABLED);
3561 if (IS_WALL_AMOEBA(element) && CT > 60)
3563 int k1, k2, k3, dx, dy, de, dm;
3564 int element2 = Tile[ELX][ELY];
3566 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3569 for (i = laser.num_damages - 1; i >= 0; i--)
3570 if (laser.damage[i].is_mirror)
3573 r = laser.num_edges;
3574 d = laser.num_damages;
3581 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3584 DrawLaser(0, DL_LASER_ENABLED);
3587 x = laser.damage[k1].x;
3588 y = laser.damage[k1].y;
3593 for (i = 0; i < 4; i++)
3595 if (laser.wall_mask & (1 << i))
3597 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3598 cSY + ELY * TILEY + 31 * (i / 2)))
3601 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3602 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3609 for (i = 0; i < 4; i++)
3611 if (laser.wall_mask & (1 << i))
3613 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3614 cSY + ELY * TILEY + 31 * (i / 2)))
3621 if (laser.num_beamers > 0 ||
3622 k1 < 1 || k2 < 4 || k3 < 4 ||
3623 CheckLaserPixel(cSX + ELX * TILEX + 14,
3624 cSY + ELY * TILEY + 14))
3626 laser.num_edges = r;
3627 laser.num_damages = d;
3629 DrawLaser(0, DL_LASER_DISABLED);
3632 Tile[ELX][ELY] = element | laser.wall_mask;
3636 de = Tile[ELX][ELY];
3637 dm = laser.wall_mask;
3641 int x = ELX, y = ELY;
3642 int wall_mask = laser.wall_mask;
3645 DrawLaser(0, DL_LASER_ENABLED);
3647 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3649 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3650 Store[x][y] = EL_WALL_AMOEBA;
3651 Store2[x][y] = wall_mask;
3657 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3659 DrawLaser(0, DL_LASER_ENABLED);
3661 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3663 for (i = 4; i >= 0; i--)
3665 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3668 Delay_WithScreenUpdates(20);
3671 DrawLaser(0, DL_LASER_ENABLED);
3676 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3677 laser.stops_inside_element && CT > native_mm_level.time_block)
3682 if (ABS(XS) > ABS(YS))
3689 for (i = 0; i < 4; i++)
3696 x = ELX + Step[k * 4].x;
3697 y = ELY + Step[k * 4].y;
3699 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3702 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3710 laser.overloaded = (element == EL_BLOCK_STONE);
3715 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3718 Tile[x][y] = element;
3720 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3723 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3725 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3726 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3734 if (element == EL_FUEL_FULL && CT > 10)
3736 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3737 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3739 for (i = start; i <= num_init_game_frames; i++)
3741 if (i == num_init_game_frames)
3742 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3743 else if (setup.sound_loops)
3744 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3746 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3748 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3750 UpdateAndDisplayGameControlValues();
3755 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3757 DrawField_MM(ELX, ELY);
3759 DrawLaser(0, DL_LASER_ENABLED);
3767 void GameActions_MM(struct MouseActionInfo action)
3769 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3770 boolean button_released = (action.button == MB_RELEASED);
3772 GameActions_MM_Ext();
3774 CheckSingleStepMode_MM(element_clicked, button_released);
3777 void MovePacMen(void)
3779 int mx, my, ox, oy, nx, ny;
3783 if (++pacman_nr >= game_mm.num_pacman)
3786 game_mm.pacman[pacman_nr].dir--;
3788 for (l = 1; l < 5; l++)
3790 game_mm.pacman[pacman_nr].dir++;
3792 if (game_mm.pacman[pacman_nr].dir > 4)
3793 game_mm.pacman[pacman_nr].dir = 1;
3795 if (game_mm.pacman[pacman_nr].dir % 2)
3798 my = game_mm.pacman[pacman_nr].dir - 2;
3803 mx = 3 - game_mm.pacman[pacman_nr].dir;
3806 ox = game_mm.pacman[pacman_nr].x;
3807 oy = game_mm.pacman[pacman_nr].y;
3810 element = Tile[nx][ny];
3812 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3815 if (!IS_EATABLE4PACMAN(element))
3818 if (ObjHit(nx, ny, HIT_POS_CENTER))
3821 Tile[ox][oy] = EL_EMPTY;
3823 EL_PACMAN_RIGHT - 1 +
3824 (game_mm.pacman[pacman_nr].dir - 1 +
3825 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3827 game_mm.pacman[pacman_nr].x = nx;
3828 game_mm.pacman[pacman_nr].y = ny;
3830 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3832 if (element != EL_EMPTY)
3834 int graphic = el2gfx(Tile[nx][ny]);
3839 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3842 ox = cSX + ox * TILEX;
3843 oy = cSY + oy * TILEY;
3845 for (i = 1; i < 33; i += 2)
3846 BlitBitmap(bitmap, window,
3847 src_x, src_y, TILEX, TILEY,
3848 ox + i * mx, oy + i * my);
3849 Ct = Ct + FrameCounter - CT;
3852 DrawField_MM(nx, ny);
3855 if (!laser.fuse_off)
3857 DrawLaser(0, DL_LASER_ENABLED);
3859 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3861 AddDamagedField(nx, ny);
3863 laser.damage[laser.num_damages - 1].edge = 0;
3867 if (element == EL_BOMB)
3868 DeletePacMan(nx, ny);
3870 if (IS_WALL_AMOEBA(element) &&
3871 (LX + 2 * XS) / TILEX == nx &&
3872 (LY + 2 * YS) / TILEY == ny)
3882 static void InitMovingField_MM(int x, int y, int direction)
3884 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3885 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3887 MovDir[x][y] = direction;
3888 MovDir[newx][newy] = direction;
3890 if (Tile[newx][newy] == EL_EMPTY)
3891 Tile[newx][newy] = EL_BLOCKED;
3894 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3896 int direction = MovDir[x][y];
3897 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3898 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3904 static void Blocked2Moving_MM(int x, int y,
3905 int *comes_from_x, int *comes_from_y)
3907 int oldx = x, oldy = y;
3908 int direction = MovDir[x][y];
3910 if (direction == MV_LEFT)
3912 else if (direction == MV_RIGHT)
3914 else if (direction == MV_UP)
3916 else if (direction == MV_DOWN)
3919 *comes_from_x = oldx;
3920 *comes_from_y = oldy;
3923 static int MovingOrBlocked2Element_MM(int x, int y)
3925 int element = Tile[x][y];
3927 if (element == EL_BLOCKED)
3931 Blocked2Moving_MM(x, y, &oldx, &oldy);
3933 return Tile[oldx][oldy];
3940 static void RemoveField(int x, int y)
3942 Tile[x][y] = EL_EMPTY;
3949 static void RemoveMovingField_MM(int x, int y)
3951 int oldx = x, oldy = y, newx = x, newy = y;
3953 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3956 if (IS_MOVING(x, y))
3958 Moving2Blocked_MM(x, y, &newx, &newy);
3959 if (Tile[newx][newy] != EL_BLOCKED)
3962 else if (Tile[x][y] == EL_BLOCKED)
3964 Blocked2Moving_MM(x, y, &oldx, &oldy);
3965 if (!IS_MOVING(oldx, oldy))
3969 Tile[oldx][oldy] = EL_EMPTY;
3970 Tile[newx][newy] = EL_EMPTY;
3971 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3972 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3974 DrawLevelField_MM(oldx, oldy);
3975 DrawLevelField_MM(newx, newy);
3978 void PlaySoundLevel(int x, int y, int sound_nr)
3980 int sx = SCREENX(x), sy = SCREENY(y);
3982 int silence_distance = 8;
3984 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3985 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3988 if (!IN_LEV_FIELD(x, y) ||
3989 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3990 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3993 volume = SOUND_MAX_VOLUME;
3996 stereo = (sx - SCR_FIELDX/2) * 12;
3998 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3999 if (stereo > SOUND_MAX_RIGHT)
4000 stereo = SOUND_MAX_RIGHT;
4001 if (stereo < SOUND_MAX_LEFT)
4002 stereo = SOUND_MAX_LEFT;
4005 if (!IN_SCR_FIELD(sx, sy))
4007 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4008 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4010 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4013 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4016 static void RaiseScore_MM(int value)
4018 game_mm.score += value;
4021 void RaiseScoreElement_MM(int element)
4026 case EL_PACMAN_RIGHT:
4028 case EL_PACMAN_LEFT:
4029 case EL_PACMAN_DOWN:
4030 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4034 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4039 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4043 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4052 // ----------------------------------------------------------------------------
4053 // Mirror Magic game engine snapshot handling functions
4054 // ----------------------------------------------------------------------------
4056 void SaveEngineSnapshotValues_MM(void)
4060 engine_snapshot_mm.game_mm = game_mm;
4061 engine_snapshot_mm.laser = laser;
4063 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4065 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4067 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4068 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4069 engine_snapshot_mm.Box[x][y] = Box[x][y];
4070 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4074 engine_snapshot_mm.LX = LX;
4075 engine_snapshot_mm.LY = LY;
4076 engine_snapshot_mm.XS = XS;
4077 engine_snapshot_mm.YS = YS;
4078 engine_snapshot_mm.ELX = ELX;
4079 engine_snapshot_mm.ELY = ELY;
4080 engine_snapshot_mm.CT = CT;
4081 engine_snapshot_mm.Ct = Ct;
4083 engine_snapshot_mm.last_LX = last_LX;
4084 engine_snapshot_mm.last_LY = last_LY;
4085 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4086 engine_snapshot_mm.hold_x = hold_x;
4087 engine_snapshot_mm.hold_y = hold_y;
4088 engine_snapshot_mm.pacman_nr = pacman_nr;
4090 engine_snapshot_mm.rotate_delay = rotate_delay;
4091 engine_snapshot_mm.pacman_delay = pacman_delay;
4092 engine_snapshot_mm.energy_delay = energy_delay;
4093 engine_snapshot_mm.overload_delay = overload_delay;
4096 void LoadEngineSnapshotValues_MM(void)
4100 // stored engine snapshot buffers already restored at this point
4102 game_mm = engine_snapshot_mm.game_mm;
4103 laser = engine_snapshot_mm.laser;
4105 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4107 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4109 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4110 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4111 Box[x][y] = engine_snapshot_mm.Box[x][y];
4112 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4116 LX = engine_snapshot_mm.LX;
4117 LY = engine_snapshot_mm.LY;
4118 XS = engine_snapshot_mm.XS;
4119 YS = engine_snapshot_mm.YS;
4120 ELX = engine_snapshot_mm.ELX;
4121 ELY = engine_snapshot_mm.ELY;
4122 CT = engine_snapshot_mm.CT;
4123 Ct = engine_snapshot_mm.Ct;
4125 last_LX = engine_snapshot_mm.last_LX;
4126 last_LY = engine_snapshot_mm.last_LY;
4127 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4128 hold_x = engine_snapshot_mm.hold_x;
4129 hold_y = engine_snapshot_mm.hold_y;
4130 pacman_nr = engine_snapshot_mm.pacman_nr;
4132 rotate_delay = engine_snapshot_mm.rotate_delay;
4133 pacman_delay = engine_snapshot_mm.pacman_delay;
4134 energy_delay = engine_snapshot_mm.energy_delay;
4135 overload_delay = engine_snapshot_mm.overload_delay;
4137 RedrawPlayfield_MM();
4140 static int getAngleFromTouchDelta(int dx, int dy, int base)
4142 double pi = 3.141592653;
4143 double rad = atan2((double)-dy, (double)dx);
4144 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4145 double deg = rad2 * 180.0 / pi;
4147 return (int)(deg * base / 360.0 + 0.5) % base;
4150 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4152 // calculate start (source) position to be at the middle of the tile
4153 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4154 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4155 int dx = dst_mx - src_mx;
4156 int dy = dst_my - src_my;
4165 if (!IN_LEV_FIELD(x, y))
4168 element = Tile[x][y];
4170 if (!IS_MCDUFFIN(element) &&
4171 !IS_MIRROR(element) &&
4172 !IS_BEAMER(element) &&
4173 !IS_POLAR(element) &&
4174 !IS_POLAR_CROSS(element) &&
4175 !IS_DF_MIRROR(element))
4178 angle_old = get_element_angle(element);
4180 if (IS_MCDUFFIN(element))
4182 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4183 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4184 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4185 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4188 else if (IS_MIRROR(element) ||
4189 IS_DF_MIRROR(element))
4191 for (i = 0; i < laser.num_damages; i++)
4193 if (laser.damage[i].x == x &&
4194 laser.damage[i].y == y &&
4195 ObjHit(x, y, HIT_POS_CENTER))
4197 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4198 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4205 if (angle_new == -1)
4207 if (IS_MIRROR(element) ||
4208 IS_DF_MIRROR(element) ||
4212 if (IS_POLAR_CROSS(element))
4215 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4218 button = (angle_new == angle_old ? 0 :
4219 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4220 MB_LEFTBUTTON : MB_RIGHTBUTTON);