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);
101 static void AddLaserEdge(int, int);
102 static void ScanLaser(void);
103 static void DrawLaser(int, int);
104 static boolean HitElement(int, int);
105 static boolean HitOnlyAnEdge(int);
106 static boolean HitPolarizer(int, int);
107 static boolean HitBlock(int, int);
108 static boolean HitLaserSource(int, int);
109 static boolean HitLaserDestination(int, int);
110 static boolean HitReflectingWalls(int, int);
111 static boolean HitAbsorbingWalls(int, int);
112 static void RotateMirror(int, int, int);
113 static boolean ObjHit(int, int, int);
114 static void DeletePacMan(int, int);
115 static void MovePacMen(void);
117 // bitmap for laser beam detection
118 static Bitmap *laser_bitmap = NULL;
120 // variables for laser control
121 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
122 static int hold_x = -1, hold_y = -1;
124 // variables for pacman control
125 static int pacman_nr = -1;
127 // various game engine delay counters
128 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
129 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
130 static DelayCounter energy_delay = { ENERGY_DELAY };
131 static DelayCounter overload_delay = { 0 };
133 // element mask positions for scanning pixels of MM elements
134 #define MM_MASK_MCDUFFIN_RIGHT 0
135 #define MM_MASK_MCDUFFIN_UP 1
136 #define MM_MASK_MCDUFFIN_LEFT 2
137 #define MM_MASK_MCDUFFIN_DOWN 3
138 #define MM_MASK_GRID_1 4
139 #define MM_MASK_GRID_2 5
140 #define MM_MASK_GRID_3 6
141 #define MM_MASK_GRID_4 7
142 #define MM_MASK_GRID_CLOSED 8
143 #define MM_MASK_RECTANGLE 9
144 #define MM_MASK_CIRCLE 10
146 #define NUM_MM_MASKS 11
148 // element masks for scanning pixels of MM elements
149 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
351 static int get_element_angle(int element)
353 int element_phase = get_element_phase(element);
355 if (IS_MIRROR_FIXED(element) ||
356 IS_MCDUFFIN(element) ||
358 IS_RECEIVER(element))
359 return 4 * element_phase;
361 return element_phase;
364 static int get_opposite_angle(int angle)
366 int opposite_angle = angle + ANG_RAY_180;
368 // make sure "opposite_angle" is in valid interval [0, 15]
369 return (opposite_angle + 16) % 16;
372 static int get_mirrored_angle(int laser_angle, int mirror_angle)
374 int reflected_angle = 16 - laser_angle + mirror_angle;
376 // make sure "reflected_angle" is in valid interval [0, 15]
377 return (reflected_angle + 16) % 16;
380 static void DrawLaserLines(struct XY *points, int num_points, int mode)
382 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
383 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
385 DrawLines(drawto_mm, points, num_points, pixel_drawto);
389 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
394 static boolean CheckLaserPixel(int x, int y)
400 pixel = ReadPixel(laser_bitmap, x, y);
404 return (pixel == WHITE_PIXEL);
407 static void CheckExitMM(void)
409 int exit_element = EL_EMPTY;
413 static int xy[4][2] =
421 for (y = 0; y < lev_fieldy; y++)
423 for (x = 0; x < lev_fieldx; x++)
425 if (Tile[x][y] == EL_EXIT_CLOSED)
427 // initiate opening animation of exit door
428 Tile[x][y] = EL_EXIT_OPENING;
430 exit_element = EL_EXIT_OPEN;
434 else if (IS_RECEIVER(Tile[x][y]))
436 // remove field that blocks receiver
437 int phase = Tile[x][y] - EL_RECEIVER_START;
438 int blocking_x, blocking_y;
440 blocking_x = x + xy[phase][0];
441 blocking_y = y + xy[phase][1];
443 if (IN_LEV_FIELD(blocking_x, blocking_y))
445 Tile[blocking_x][blocking_y] = EL_EMPTY;
447 DrawField_MM(blocking_x, blocking_y);
450 exit_element = EL_RECEIVER;
457 if (exit_element != EL_EMPTY)
458 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
461 static void SetLaserColor(int brightness)
463 int color_min = 0x00;
464 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
465 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
466 int color_down = color_max - color_up;
469 GetPixelFromRGB(window,
470 (game_mm.laser_red ? color_max : color_up),
471 (game_mm.laser_green ? color_down : color_min),
472 (game_mm.laser_blue ? color_down : color_min));
475 static void InitMovDir_MM(int x, int y)
477 int element = Tile[x][y];
478 static int direction[3][4] =
480 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
481 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
482 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
487 case EL_PACMAN_RIGHT:
491 Tile[x][y] = EL_PACMAN;
492 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
500 static void InitField(int x, int y, boolean init_game)
502 int element = Tile[x][y];
507 Tile[x][y] = EL_EMPTY;
512 if (init_game && native_mm_level.auto_count_kettles)
513 game_mm.kettles_still_needed++;
516 case EL_LIGHTBULB_OFF:
517 game_mm.lights_still_needed++;
521 if (IS_MIRROR(element) ||
522 IS_BEAMER_OLD(element) ||
523 IS_BEAMER(element) ||
525 IS_POLAR_CROSS(element) ||
526 IS_DF_MIRROR(element) ||
527 IS_DF_MIRROR_AUTO(element) ||
528 IS_GRID_STEEL_AUTO(element) ||
529 IS_GRID_WOOD_AUTO(element) ||
530 IS_FIBRE_OPTIC(element))
532 if (IS_BEAMER_OLD(element))
534 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
535 element = Tile[x][y];
538 if (!IS_FIBRE_OPTIC(element))
540 static int steps_grid_auto = 0;
542 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
543 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
545 if (IS_GRID_STEEL_AUTO(element) ||
546 IS_GRID_WOOD_AUTO(element))
547 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
549 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
551 game_mm.cycle[game_mm.num_cycle].x = x;
552 game_mm.cycle[game_mm.num_cycle].y = y;
556 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
558 int beamer_nr = BEAMER_NR(element);
559 int nr = laser.beamer[beamer_nr][0].num;
561 laser.beamer[beamer_nr][nr].x = x;
562 laser.beamer[beamer_nr][nr].y = y;
563 laser.beamer[beamer_nr][nr].num = 1;
566 else if (IS_PACMAN(element))
570 else if (IS_MCDUFFIN(element) || IS_LASER(element))
574 laser.start_edge.x = x;
575 laser.start_edge.y = y;
576 laser.start_angle = get_element_angle(element);
579 if (IS_MCDUFFIN(element))
581 game_mm.laser_red = native_mm_level.mm_laser_red;
582 game_mm.laser_green = native_mm_level.mm_laser_green;
583 game_mm.laser_blue = native_mm_level.mm_laser_blue;
587 game_mm.laser_red = native_mm_level.df_laser_red;
588 game_mm.laser_green = native_mm_level.df_laser_green;
589 game_mm.laser_blue = native_mm_level.df_laser_blue;
592 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
599 static void InitCycleElements_RotateSingleStep(void)
603 if (game_mm.num_cycle == 0) // no elements to cycle
606 for (i = 0; i < game_mm.num_cycle; i++)
608 int x = game_mm.cycle[i].x;
609 int y = game_mm.cycle[i].y;
610 int step = SIGN(game_mm.cycle[i].steps);
611 int last_element = Tile[x][y];
612 int next_element = get_rotated_element(last_element, step);
614 if (!game_mm.cycle[i].steps)
617 Tile[x][y] = next_element;
619 game_mm.cycle[i].steps -= step;
623 static void InitLaser(void)
625 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
626 int step = (IS_LASER(start_element) ? 4 : 0);
628 LX = laser.start_edge.x * TILEX;
629 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
632 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
634 LY = laser.start_edge.y * TILEY;
635 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
636 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
640 XS = 2 * Step[laser.start_angle].x;
641 YS = 2 * Step[laser.start_angle].y;
643 laser.current_angle = laser.start_angle;
645 laser.num_damages = 0;
647 laser.num_beamers = 0;
648 laser.beamer_edge[0] = 0;
650 laser.dest_element = EL_EMPTY;
653 AddLaserEdge(LX, LY); // set laser starting edge
658 void InitGameEngine_MM(void)
664 // initialize laser bitmap to current playfield (screen) size
665 ReCreateBitmap(&laser_bitmap, drawto_mm->width, drawto_mm->height);
666 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->height);
670 // set global game control values
671 game_mm.num_cycle = 0;
672 game_mm.num_pacman = 0;
675 game_mm.energy_left = 0; // later set to "native_mm_level.time"
676 game_mm.kettles_still_needed =
677 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
678 game_mm.lights_still_needed = 0;
679 game_mm.num_keys = 0;
680 game_mm.ball_choice_pos = 0;
682 game_mm.laser_red = FALSE;
683 game_mm.laser_green = FALSE;
684 game_mm.laser_blue = TRUE;
685 game_mm.has_mcduffin = TRUE;
687 game_mm.level_solved = FALSE;
688 game_mm.game_over = FALSE;
689 game_mm.game_over_cause = 0;
690 game_mm.game_over_message = NULL;
692 game_mm.laser_overload_value = 0;
693 game_mm.laser_enabled = FALSE;
695 // set global laser control values (must be set before "InitLaser()")
696 laser.start_edge.x = 0;
697 laser.start_edge.y = 0;
698 laser.start_angle = 0;
700 for (i = 0; i < MAX_NUM_BEAMERS; i++)
701 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
703 laser.overloaded = FALSE;
704 laser.overload_value = 0;
705 laser.fuse_off = FALSE;
706 laser.fuse_x = laser.fuse_y = -1;
708 laser.dest_element = EL_EMPTY;
709 laser.dest_element_last = EL_EMPTY;
710 laser.dest_element_last_x = -1;
711 laser.dest_element_last_y = -1;
725 rotate_delay.count = 0;
726 pacman_delay.count = 0;
727 energy_delay.count = 0;
728 overload_delay.count = 0;
730 ClickElement(-1, -1, -1);
732 for (x = 0; x < lev_fieldx; x++)
734 for (y = 0; y < lev_fieldy; y++)
736 Tile[x][y] = Ur[x][y];
737 Hit[x][y] = Box[x][y] = 0;
739 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
740 Store[x][y] = Store2[x][y] = 0;
743 InitField(x, y, TRUE);
750 void InitGameActions_MM(void)
752 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
753 int cycle_steps_done = 0;
758 for (i = 0; i <= num_init_game_frames; i++)
760 if (i == num_init_game_frames)
761 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
762 else if (setup.sound_loops)
763 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
765 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
767 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
769 UpdateAndDisplayGameControlValues();
771 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
773 InitCycleElements_RotateSingleStep();
778 AdvanceFrameCounter();
786 if (setup.quick_doors)
793 if (game_mm.kettles_still_needed == 0)
796 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
797 SetTileCursorActive(TRUE);
799 // restart all delay counters after initially cycling game elements
800 ResetFrameCounter(&rotate_delay);
801 ResetFrameCounter(&pacman_delay);
802 ResetFrameCounter(&energy_delay);
803 ResetFrameCounter(&overload_delay);
806 static void FadeOutLaser(void)
810 for (i = 15; i >= 0; i--)
812 SetLaserColor(0x11 * i);
814 DrawLaser(0, DL_LASER_ENABLED);
817 Delay_WithScreenUpdates(50);
820 DrawLaser(0, DL_LASER_DISABLED);
822 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
825 static void GameOver_MM(int game_over_cause)
827 game_mm.game_over = TRUE;
828 game_mm.game_over_cause = game_over_cause;
829 game_mm.game_over_message = (game_mm.has_mcduffin ?
830 (game_over_cause == GAME_OVER_BOMB ?
831 "Bomb killed Mc Duffin!" :
832 game_over_cause == GAME_OVER_NO_ENERGY ?
833 "Out of magic energy!" :
834 game_over_cause == GAME_OVER_OVERLOADED ?
835 "Magic spell hit Mc Duffin!" :
837 (game_over_cause == GAME_OVER_BOMB ?
838 "Bomb destroyed laser cannon!" :
839 game_over_cause == GAME_OVER_NO_ENERGY ?
840 "Out of laser energy!" :
841 game_over_cause == GAME_OVER_OVERLOADED ?
842 "Laser beam hit laser cannon!" :
845 SetTileCursorActive(FALSE);
848 static void AddLaserEdge(int lx, int ly)
850 int full_sxsize = MAX(FULL_SXSIZE, lev_fieldx * TILEX);
851 int full_sysize = MAX(FULL_SYSIZE, lev_fieldy * TILEY);
853 // check if laser is still inside visible playfield area (or inside level)
854 if (cSX + lx < REAL_SX || cSX + lx >= REAL_SX + full_sxsize ||
855 cSY + ly < REAL_SY || cSY + ly >= REAL_SY + full_sysize)
857 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
862 laser.edge[laser.num_edges].x = cSX2 + lx;
863 laser.edge[laser.num_edges].y = cSY2 + ly;
869 static void AddDamagedField(int ex, int ey)
871 // prevent adding the same field position again
872 if (laser.num_damages > 0 &&
873 laser.damage[laser.num_damages - 1].x == ex &&
874 laser.damage[laser.num_damages - 1].y == ey &&
875 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
878 laser.damage[laser.num_damages].is_mirror = FALSE;
879 laser.damage[laser.num_damages].angle = laser.current_angle;
880 laser.damage[laser.num_damages].edge = laser.num_edges;
881 laser.damage[laser.num_damages].x = ex;
882 laser.damage[laser.num_damages].y = ey;
886 static boolean StepBehind(void)
892 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
893 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
895 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
901 static int getMaskFromElement(int element)
903 if (IS_MCDUFFIN(element))
904 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
905 else if (IS_GRID(element))
906 return MM_MASK_GRID_1 + get_element_phase(element);
907 else if (IS_DF_GRID(element))
908 return MM_MASK_GRID_CLOSED;
909 else if (IS_RECTANGLE(element))
910 return MM_MASK_RECTANGLE;
912 return MM_MASK_CIRCLE;
915 static int ScanPixel(void)
920 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
921 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
924 // follow laser beam until it hits something (at least the screen border)
925 while (hit_mask == HIT_MASK_NO_HIT)
931 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
932 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
934 Debug("game:mm:ScanPixel", "touched screen border!");
940 // check if laser scan has crossed element boundaries (not just mini tiles)
941 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
942 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
944 if (cross_x && cross_y)
946 int elx1 = (LX - XS) / TILEX;
947 int ely1 = (LY + YS) / TILEY;
948 int elx2 = (LX + XS) / TILEX;
949 int ely2 = (LY - YS) / TILEY;
951 // add element corners left and right from the laser beam to damage list
953 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
954 AddDamagedField(elx1, ely1);
956 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
957 AddDamagedField(elx2, ely2);
960 for (i = 0; i < 4; i++)
962 int px = LX + (i % 2) * 2;
963 int py = LY + (i / 2) * 2;
966 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
967 int ly = (py + TILEY) / TILEY - 1; // negative values!
970 if (IN_LEV_FIELD(lx, ly))
972 int element = Tile[lx][ly];
974 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
978 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
980 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
982 pixel = ((element & (1 << pos)) ? 1 : 0);
986 int pos = getMaskFromElement(element);
988 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
993 // check if laser is still inside visible playfield area
994 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
995 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
998 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
999 hit_mask |= (1 << i);
1002 if (hit_mask == HIT_MASK_NO_HIT)
1004 // hit nothing -- go on with another step
1013 static void DeactivateLaserTargetElement(void)
1015 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
1016 laser.dest_element_last == EL_MINE_ACTIVE ||
1017 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
1018 laser.dest_element_last == EL_GRAY_BALL_OPENING)
1020 int x = laser.dest_element_last_x;
1021 int y = laser.dest_element_last_y;
1022 int element = laser.dest_element_last;
1024 if (Tile[x][y] == element)
1025 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1026 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1028 if (Tile[x][y] == EL_GRAY_BALL)
1031 laser.dest_element_last = EL_EMPTY;
1032 laser.dest_element_last_x = -1;
1033 laser.dest_element_last_y = -1;
1037 static void ScanLaser(void)
1039 int element = EL_EMPTY;
1040 int last_element = EL_EMPTY;
1041 int end = 0, rf = laser.num_edges;
1043 // do not scan laser again after the game was lost for whatever reason
1044 if (game_mm.game_over)
1047 // do not scan laser if fuse is off
1051 DeactivateLaserTargetElement();
1053 laser.overloaded = FALSE;
1054 laser.stops_inside_element = FALSE;
1056 DrawLaser(0, DL_LASER_ENABLED);
1059 Debug("game:mm:ScanLaser",
1060 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1068 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1071 laser.overloaded = TRUE;
1076 hit_mask = ScanPixel();
1079 Debug("game:mm:ScanLaser",
1080 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1084 // hit something -- check out what it was
1085 ELX = (LX + XS + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
1086 ELY = (LY + YS + TILEY) / TILEY - 1; // negative values!
1089 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1090 hit_mask, LX, LY, ELX, ELY);
1093 if (!IN_LEV_FIELD(ELX, ELY))
1095 // check if laser is still inside visible playfield area
1096 if (cSX + LX >= REAL_SX && cSX + LX < REAL_SX + FULL_SXSIZE &&
1097 cSY + LY >= REAL_SY && cSY + LY < REAL_SY + FULL_SYSIZE)
1099 // go on with another step
1107 laser.dest_element = element;
1112 // check if laser scan has hit two diagonally adjacent element corners
1113 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1114 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1116 // check if laser scan has crossed element boundaries (not just mini tiles)
1117 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1118 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1120 // handle special case of laser hitting two diagonally adjacent elements
1121 // (with or without a third corner element behind these two elements)
1122 if ((diag_1 || diag_2) && cross_x && cross_y)
1124 // compare the two diagonally adjacent elements
1126 int yoffset = 2 * (diag_1 ? -1 : +1);
1127 int elx1 = (LX - xoffset) / TILEX;
1128 int ely1 = (LY + yoffset) / TILEY;
1129 int elx2 = (LX + xoffset) / TILEX;
1130 int ely2 = (LY - yoffset) / TILEY;
1131 int e1 = Tile[elx1][ely1];
1132 int e2 = Tile[elx2][ely2];
1133 boolean use_element_1 = FALSE;
1135 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1137 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1138 use_element_1 = (RND(2) ? TRUE : FALSE);
1139 else if (IS_WALL_ICE(e1))
1140 use_element_1 = TRUE;
1142 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1144 // if both tiles match, we can just select the first one
1145 if (IS_WALL_AMOEBA(e1))
1146 use_element_1 = TRUE;
1148 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1150 // if both tiles match, we can just select the first one
1151 if (IS_ABSORBING_BLOCK(e1))
1152 use_element_1 = TRUE;
1155 ELX = (use_element_1 ? elx1 : elx2);
1156 ELY = (use_element_1 ? ely1 : ely2);
1160 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1161 hit_mask, LX, LY, ELX, ELY);
1164 last_element = element;
1166 element = Tile[ELX][ELY];
1167 laser.dest_element = element;
1170 Debug("game:mm:ScanLaser",
1171 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1174 LX % TILEX, LY % TILEY,
1179 if (!IN_LEV_FIELD(ELX, ELY))
1180 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1184 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1185 if (element == EL_EMPTY &&
1186 IS_GRID_STEEL(last_element) &&
1187 laser.current_angle % 4) // angle is not 90°
1188 element = last_element;
1190 if (element == EL_EMPTY)
1192 if (!HitOnlyAnEdge(hit_mask))
1195 else if (element == EL_FUSE_ON)
1197 if (HitPolarizer(element, hit_mask))
1200 else if (IS_GRID(element) || IS_DF_GRID(element))
1202 if (HitPolarizer(element, hit_mask))
1205 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1206 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1208 if (HitBlock(element, hit_mask))
1215 else if (IS_MCDUFFIN(element))
1217 if (HitLaserSource(element, hit_mask))
1220 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1221 IS_RECEIVER(element))
1223 if (HitLaserDestination(element, hit_mask))
1226 else if (IS_WALL(element))
1228 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1230 if (HitReflectingWalls(element, hit_mask))
1235 if (HitAbsorbingWalls(element, hit_mask))
1241 if (HitElement(element, hit_mask))
1246 DrawLaser(rf - 1, DL_LASER_ENABLED);
1247 rf = laser.num_edges;
1249 if (!IS_DF_WALL_STEEL(element))
1251 // only used for scanning DF steel walls; reset for all other elements
1259 if (laser.dest_element != Tile[ELX][ELY])
1261 Debug("game:mm:ScanLaser",
1262 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1263 laser.dest_element, Tile[ELX][ELY]);
1267 if (!end && !laser.stops_inside_element && !StepBehind())
1270 Debug("game:mm:ScanLaser", "Go one step back");
1276 AddLaserEdge(LX, LY);
1280 DrawLaser(rf - 1, DL_LASER_ENABLED);
1282 Ct = CT = FrameCounter;
1285 if (!IN_LEV_FIELD(ELX, ELY))
1286 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1290 static void ScanLaser_FromLastMirror(void)
1292 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1295 for (i = start_pos; i >= 0; i--)
1296 if (laser.damage[i].is_mirror)
1299 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1301 DrawLaser(start_edge, DL_LASER_DISABLED);
1306 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1312 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1313 start_edge, num_edges, mode);
1318 Warn("DrawLaserExt: start_edge < 0");
1325 Warn("DrawLaserExt: num_edges < 0");
1331 if (mode == DL_LASER_DISABLED)
1333 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1337 // now draw the laser to the backbuffer and (if enabled) to the screen
1338 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1340 redraw_mask |= REDRAW_FIELD;
1342 if (mode == DL_LASER_ENABLED)
1345 // after the laser was deleted, the "damaged" graphics must be restored
1346 if (laser.num_damages)
1348 int damage_start = 0;
1351 // determine the starting edge, from which graphics need to be restored
1354 for (i = 0; i < laser.num_damages; i++)
1356 if (laser.damage[i].edge == start_edge + 1)
1365 // restore graphics from this starting edge to the end of damage list
1366 for (i = damage_start; i < laser.num_damages; i++)
1368 int lx = laser.damage[i].x;
1369 int ly = laser.damage[i].y;
1370 int element = Tile[lx][ly];
1372 if (Hit[lx][ly] == laser.damage[i].edge)
1373 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1376 if (Box[lx][ly] == laser.damage[i].edge)
1379 if (IS_DRAWABLE(element))
1380 DrawField_MM(lx, ly);
1383 elx = laser.damage[damage_start].x;
1384 ely = laser.damage[damage_start].y;
1385 element = Tile[elx][ely];
1388 if (IS_BEAMER(element))
1392 for (i = 0; i < laser.num_beamers; i++)
1393 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1395 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1396 mode, elx, ely, Hit[elx][ely], start_edge);
1397 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1398 get_element_angle(element), laser.damage[damage_start].angle);
1402 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1403 laser.num_beamers > 0 &&
1404 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1406 // element is outgoing beamer
1407 laser.num_damages = damage_start + 1;
1409 if (IS_BEAMER(element))
1410 laser.current_angle = get_element_angle(element);
1414 // element is incoming beamer or other element
1415 laser.num_damages = damage_start;
1416 laser.current_angle = laser.damage[laser.num_damages].angle;
1421 // no damages but McDuffin himself (who needs to be redrawn anyway)
1423 elx = laser.start_edge.x;
1424 ely = laser.start_edge.y;
1425 element = Tile[elx][ely];
1428 laser.num_edges = start_edge + 1;
1429 if (start_edge == 0)
1430 laser.current_angle = laser.start_angle;
1432 LX = laser.edge[start_edge].x - cSX2;
1433 LY = laser.edge[start_edge].y - cSY2;
1434 XS = 2 * Step[laser.current_angle].x;
1435 YS = 2 * Step[laser.current_angle].y;
1438 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1444 if (IS_BEAMER(element) ||
1445 IS_FIBRE_OPTIC(element) ||
1446 IS_PACMAN(element) ||
1447 IS_POLAR(element) ||
1448 IS_POLAR_CROSS(element) ||
1449 element == EL_FUSE_ON)
1454 Debug("game:mm:DrawLaserExt", "element == %d", element);
1457 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1458 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1462 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1463 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1464 (laser.num_beamers == 0 ||
1465 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1467 // element is incoming beamer or other element
1468 step_size = -step_size;
1473 if (IS_BEAMER(element))
1474 Debug("game:mm:DrawLaserExt",
1475 "start_edge == %d, laser.beamer_edge == %d",
1476 start_edge, laser.beamer_edge);
1479 LX += step_size * XS;
1480 LY += step_size * YS;
1482 else if (element != EL_EMPTY)
1491 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1496 void DrawLaser(int start_edge, int mode)
1498 // do not draw laser if fuse is off
1499 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1502 if (mode == DL_LASER_DISABLED)
1503 DeactivateLaserTargetElement();
1505 if (laser.num_edges - start_edge < 0)
1507 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1512 // check if laser is interrupted by beamer element
1513 if (laser.num_beamers > 0 &&
1514 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1516 if (mode == DL_LASER_ENABLED)
1519 int tmp_start_edge = start_edge;
1521 // draw laser segments forward from the start to the last beamer
1522 for (i = 0; i < laser.num_beamers; i++)
1524 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1526 if (tmp_num_edges <= 0)
1530 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1531 i, laser.beamer_edge[i], tmp_start_edge);
1534 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1536 tmp_start_edge = laser.beamer_edge[i];
1539 // draw last segment from last beamer to the end
1540 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1546 int last_num_edges = laser.num_edges;
1547 int num_beamers = laser.num_beamers;
1549 // delete laser segments backward from the end to the first beamer
1550 for (i = num_beamers - 1; i >= 0; i--)
1552 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1554 if (laser.beamer_edge[i] - start_edge <= 0)
1557 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1559 last_num_edges = laser.beamer_edge[i];
1560 laser.num_beamers--;
1564 if (last_num_edges - start_edge <= 0)
1565 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1566 last_num_edges, start_edge);
1569 // special case when rotating first beamer: delete laser edge on beamer
1570 // (but do not start scanning on previous edge to prevent mirror sound)
1571 if (last_num_edges - start_edge == 1 && start_edge > 0)
1572 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1574 // delete first segment from start to the first beamer
1575 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1580 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1583 game_mm.laser_enabled = mode;
1586 void DrawLaser_MM(void)
1588 DrawLaser(0, game_mm.laser_enabled);
1591 static boolean HitElement(int element, int hit_mask)
1593 if (HitOnlyAnEdge(hit_mask))
1596 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1597 element = MovingOrBlocked2Element_MM(ELX, ELY);
1600 Debug("game:mm:HitElement", "(1): element == %d", element);
1604 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1605 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1608 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1612 AddDamagedField(ELX, ELY);
1614 // this is more precise: check if laser would go through the center
1615 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1619 // prevent cutting through laser emitter with laser beam
1620 if (IS_LASER(element))
1623 // skip the whole element before continuing the scan
1631 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1633 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1635 /* skipping scan positions to the right and down skips one scan
1636 position too much, because this is only the top left scan position
1637 of totally four scan positions (plus one to the right, one to the
1638 bottom and one to the bottom right) */
1639 /* ... but only roll back scan position if more than one step done */
1649 Debug("game:mm:HitElement", "(2): element == %d", element);
1652 if (LX + 5 * XS < 0 ||
1662 Debug("game:mm:HitElement", "(3): element == %d", element);
1665 if (IS_POLAR(element) &&
1666 ((element - EL_POLAR_START) % 2 ||
1667 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1669 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1671 laser.num_damages--;
1676 if (IS_POLAR_CROSS(element) &&
1677 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1679 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1681 laser.num_damages--;
1686 if (!IS_BEAMER(element) &&
1687 !IS_FIBRE_OPTIC(element) &&
1688 !IS_GRID_WOOD(element) &&
1689 element != EL_FUEL_EMPTY)
1692 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1693 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1695 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1698 LX = ELX * TILEX + 14;
1699 LY = ELY * TILEY + 14;
1701 AddLaserEdge(LX, LY);
1704 if (IS_MIRROR(element) ||
1705 IS_MIRROR_FIXED(element) ||
1706 IS_POLAR(element) ||
1707 IS_POLAR_CROSS(element) ||
1708 IS_DF_MIRROR(element) ||
1709 IS_DF_MIRROR_AUTO(element) ||
1710 element == EL_PRISM ||
1711 element == EL_REFRACTOR)
1713 int current_angle = laser.current_angle;
1716 laser.num_damages--;
1718 AddDamagedField(ELX, ELY);
1720 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1723 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1725 if (IS_MIRROR(element) ||
1726 IS_MIRROR_FIXED(element) ||
1727 IS_DF_MIRROR(element) ||
1728 IS_DF_MIRROR_AUTO(element))
1729 laser.current_angle = get_mirrored_angle(laser.current_angle,
1730 get_element_angle(element));
1732 if (element == EL_PRISM || element == EL_REFRACTOR)
1733 laser.current_angle = RND(16);
1735 XS = 2 * Step[laser.current_angle].x;
1736 YS = 2 * Step[laser.current_angle].y;
1738 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1743 LX += step_size * XS;
1744 LY += step_size * YS;
1746 // draw sparkles on mirror
1747 if ((IS_MIRROR(element) ||
1748 IS_MIRROR_FIXED(element) ||
1749 element == EL_PRISM) &&
1750 current_angle != laser.current_angle)
1752 MovDelay[ELX][ELY] = 11; // start animation
1755 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1756 current_angle != laser.current_angle)
1757 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1760 (get_opposite_angle(laser.current_angle) ==
1761 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1763 return (laser.overloaded ? TRUE : FALSE);
1766 if (element == EL_FUEL_FULL)
1768 laser.stops_inside_element = TRUE;
1773 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1775 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1777 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1778 element == EL_MINE ? EL_MINE_ACTIVE :
1779 EL_GRAY_BALL_ACTIVE);
1781 GfxFrame[ELX][ELY] = 0; // restart animation
1783 laser.dest_element_last = Tile[ELX][ELY];
1784 laser.dest_element_last_x = ELX;
1785 laser.dest_element_last_y = ELY;
1787 if (element == EL_MINE)
1788 laser.overloaded = TRUE;
1791 if (element == EL_KETTLE ||
1792 element == EL_CELL ||
1793 element == EL_KEY ||
1794 element == EL_LIGHTBALL ||
1795 element == EL_PACMAN ||
1796 IS_PACMAN(element) ||
1797 IS_ENVELOPE(element))
1799 if (!IS_PACMAN(element) &&
1800 !IS_ENVELOPE(element))
1803 if (element == EL_PACMAN)
1806 if (element == EL_KETTLE || element == EL_CELL)
1808 if (game_mm.kettles_still_needed > 0)
1809 game_mm.kettles_still_needed--;
1811 game.snapshot.collected_item = TRUE;
1813 if (game_mm.kettles_still_needed == 0)
1817 DrawLaser(0, DL_LASER_ENABLED);
1820 else if (element == EL_KEY)
1824 else if (IS_PACMAN(element))
1826 DeletePacMan(ELX, ELY);
1828 else if (IS_ENVELOPE(element))
1830 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1833 RaiseScoreElement_MM(element);
1838 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1840 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1842 DrawLaser(0, DL_LASER_ENABLED);
1844 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1846 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1847 game_mm.lights_still_needed--;
1851 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1852 game_mm.lights_still_needed++;
1855 DrawField_MM(ELX, ELY);
1856 DrawLaser(0, DL_LASER_ENABLED);
1861 laser.stops_inside_element = TRUE;
1867 Debug("game:mm:HitElement", "(4): element == %d", element);
1870 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1871 laser.num_beamers < MAX_NUM_BEAMERS &&
1872 laser.beamer[BEAMER_NR(element)][1].num)
1874 int beamer_angle = get_element_angle(element);
1875 int beamer_nr = BEAMER_NR(element);
1879 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1882 laser.num_damages--;
1884 if (IS_FIBRE_OPTIC(element) ||
1885 laser.current_angle == get_opposite_angle(beamer_angle))
1889 LX = ELX * TILEX + 14;
1890 LY = ELY * TILEY + 14;
1892 AddLaserEdge(LX, LY);
1893 AddDamagedField(ELX, ELY);
1895 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1898 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1900 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1901 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1902 ELX = laser.beamer[beamer_nr][pos].x;
1903 ELY = laser.beamer[beamer_nr][pos].y;
1904 LX = ELX * TILEX + 14;
1905 LY = ELY * TILEY + 14;
1907 if (IS_BEAMER(element))
1909 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1910 XS = 2 * Step[laser.current_angle].x;
1911 YS = 2 * Step[laser.current_angle].y;
1914 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1916 AddLaserEdge(LX, LY);
1917 AddDamagedField(ELX, ELY);
1919 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1922 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1924 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1929 LX += step_size * XS;
1930 LY += step_size * YS;
1932 laser.num_beamers++;
1941 static boolean HitOnlyAnEdge(int hit_mask)
1943 // check if the laser hit only the edge of an element and, if so, go on
1946 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1950 if ((hit_mask == HIT_MASK_TOPLEFT ||
1951 hit_mask == HIT_MASK_TOPRIGHT ||
1952 hit_mask == HIT_MASK_BOTTOMLEFT ||
1953 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1954 laser.current_angle % 4) // angle is not 90°
1958 if (hit_mask == HIT_MASK_TOPLEFT)
1963 else if (hit_mask == HIT_MASK_TOPRIGHT)
1968 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1973 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1979 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1985 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1992 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1998 static boolean HitPolarizer(int element, int hit_mask)
2000 if (HitOnlyAnEdge(hit_mask))
2003 if (IS_DF_GRID(element))
2005 int grid_angle = get_element_angle(element);
2008 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
2009 grid_angle, laser.current_angle);
2012 AddLaserEdge(LX, LY);
2013 AddDamagedField(ELX, ELY);
2016 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
2018 if (laser.current_angle == grid_angle ||
2019 laser.current_angle == get_opposite_angle(grid_angle))
2021 // skip the whole element before continuing the scan
2027 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
2029 if (LX/TILEX > ELX || LY/TILEY > ELY)
2031 /* skipping scan positions to the right and down skips one scan
2032 position too much, because this is only the top left scan position
2033 of totally four scan positions (plus one to the right, one to the
2034 bottom and one to the bottom right) */
2040 AddLaserEdge(LX, LY);
2046 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2048 LX / TILEX, LY / TILEY,
2049 LX % TILEX, LY % TILEY);
2054 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2056 return HitReflectingWalls(element, hit_mask);
2060 return HitAbsorbingWalls(element, hit_mask);
2063 else if (IS_GRID_STEEL(element))
2065 // may be required if graphics for steel grid redefined
2066 AddDamagedField(ELX, ELY);
2068 return HitReflectingWalls(element, hit_mask);
2070 else // IS_GRID_WOOD
2072 // may be required if graphics for wooden grid redefined
2073 AddDamagedField(ELX, ELY);
2075 return HitAbsorbingWalls(element, hit_mask);
2081 static boolean HitBlock(int element, int hit_mask)
2083 boolean check = FALSE;
2085 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2086 game_mm.num_keys == 0)
2089 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2092 int ex = ELX * TILEX + 14;
2093 int ey = ELY * TILEY + 14;
2097 for (i = 1; i < 32; i++)
2102 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2107 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2108 return HitAbsorbingWalls(element, hit_mask);
2112 AddLaserEdge(LX - XS, LY - YS);
2113 AddDamagedField(ELX, ELY);
2116 Box[ELX][ELY] = laser.num_edges;
2118 return HitReflectingWalls(element, hit_mask);
2121 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2123 int xs = XS / 2, ys = YS / 2;
2125 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2126 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2128 laser.overloaded = (element == EL_GATE_STONE);
2133 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2134 (hit_mask == HIT_MASK_TOP ||
2135 hit_mask == HIT_MASK_LEFT ||
2136 hit_mask == HIT_MASK_RIGHT ||
2137 hit_mask == HIT_MASK_BOTTOM))
2138 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2139 hit_mask == HIT_MASK_BOTTOM),
2140 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2141 hit_mask == HIT_MASK_RIGHT));
2142 AddLaserEdge(LX, LY);
2148 if (element == EL_GATE_STONE && Box[ELX][ELY])
2150 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2162 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2164 int xs = XS / 2, ys = YS / 2;
2166 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2167 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2169 laser.overloaded = (element == EL_BLOCK_STONE);
2174 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2175 (hit_mask == HIT_MASK_TOP ||
2176 hit_mask == HIT_MASK_LEFT ||
2177 hit_mask == HIT_MASK_RIGHT ||
2178 hit_mask == HIT_MASK_BOTTOM))
2179 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2180 hit_mask == HIT_MASK_BOTTOM),
2181 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2182 hit_mask == HIT_MASK_RIGHT));
2183 AddDamagedField(ELX, ELY);
2185 LX = ELX * TILEX + 14;
2186 LY = ELY * TILEY + 14;
2188 AddLaserEdge(LX, LY);
2190 laser.stops_inside_element = TRUE;
2198 static boolean HitLaserSource(int element, int hit_mask)
2200 if (HitOnlyAnEdge(hit_mask))
2203 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2205 laser.overloaded = TRUE;
2210 static boolean HitLaserDestination(int element, int hit_mask)
2212 if (HitOnlyAnEdge(hit_mask))
2215 if (element != EL_EXIT_OPEN &&
2216 !(IS_RECEIVER(element) &&
2217 game_mm.kettles_still_needed == 0 &&
2218 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2220 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2225 if (IS_RECEIVER(element) ||
2226 (IS_22_5_ANGLE(laser.current_angle) &&
2227 (ELX != (LX + 6 * XS) / TILEX ||
2228 ELY != (LY + 6 * YS) / TILEY ||
2237 LX = ELX * TILEX + 14;
2238 LY = ELY * TILEY + 14;
2240 laser.stops_inside_element = TRUE;
2243 AddLaserEdge(LX, LY);
2244 AddDamagedField(ELX, ELY);
2246 if (game_mm.lights_still_needed == 0)
2248 game_mm.level_solved = TRUE;
2250 SetTileCursorActive(FALSE);
2256 static boolean HitReflectingWalls(int element, int hit_mask)
2258 // check if laser hits side of a wall with an angle that is not 90°
2259 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2260 hit_mask == HIT_MASK_LEFT ||
2261 hit_mask == HIT_MASK_RIGHT ||
2262 hit_mask == HIT_MASK_BOTTOM))
2264 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2269 if (!IS_DF_GRID(element))
2270 AddLaserEdge(LX, LY);
2272 // check if laser hits wall with an angle of 45°
2273 if (!IS_22_5_ANGLE(laser.current_angle))
2275 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2278 laser.current_angle = get_mirrored_angle(laser.current_angle,
2281 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2284 laser.current_angle = get_mirrored_angle(laser.current_angle,
2288 AddLaserEdge(LX, LY);
2290 XS = 2 * Step[laser.current_angle].x;
2291 YS = 2 * Step[laser.current_angle].y;
2295 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2297 laser.current_angle = get_mirrored_angle(laser.current_angle,
2302 if (!IS_DF_GRID(element))
2303 AddLaserEdge(LX, LY);
2308 if (!IS_DF_GRID(element))
2309 AddLaserEdge(LX, LY + YS / 2);
2312 if (!IS_DF_GRID(element))
2313 AddLaserEdge(LX, LY);
2316 YS = 2 * Step[laser.current_angle].y;
2320 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2322 laser.current_angle = get_mirrored_angle(laser.current_angle,
2327 if (!IS_DF_GRID(element))
2328 AddLaserEdge(LX, LY);
2333 if (!IS_DF_GRID(element))
2334 AddLaserEdge(LX + XS / 2, LY);
2337 if (!IS_DF_GRID(element))
2338 AddLaserEdge(LX, LY);
2341 XS = 2 * Step[laser.current_angle].x;
2347 // reflection at the edge of reflecting DF style wall
2348 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2350 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2351 hit_mask == HIT_MASK_TOPRIGHT) ||
2352 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2353 hit_mask == HIT_MASK_TOPLEFT) ||
2354 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2355 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2356 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2357 hit_mask == HIT_MASK_BOTTOMRIGHT))
2360 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2361 ANG_MIRROR_135 : ANG_MIRROR_45);
2363 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2365 AddDamagedField(ELX, ELY);
2366 AddLaserEdge(LX, LY);
2368 laser.current_angle = get_mirrored_angle(laser.current_angle,
2376 AddLaserEdge(LX, LY);
2382 // reflection inside an edge of reflecting DF style wall
2383 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2385 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2386 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2387 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2388 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2389 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2390 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2391 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2392 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2395 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2396 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2397 ANG_MIRROR_135 : ANG_MIRROR_45);
2399 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2402 AddDamagedField(ELX, ELY);
2405 AddLaserEdge(LX - XS, LY - YS);
2406 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2407 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2409 laser.current_angle = get_mirrored_angle(laser.current_angle,
2417 AddLaserEdge(LX, LY);
2423 // check if laser hits DF style wall with an angle of 90°
2424 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2426 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2427 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2428 (IS_VERT_ANGLE(laser.current_angle) &&
2429 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2431 // laser at last step touched nothing or the same side of the wall
2432 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2434 AddDamagedField(ELX, ELY);
2441 last_hit_mask = hit_mask;
2448 if (!HitOnlyAnEdge(hit_mask))
2450 laser.overloaded = TRUE;
2458 static boolean HitAbsorbingWalls(int element, int hit_mask)
2460 if (HitOnlyAnEdge(hit_mask))
2464 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2466 AddLaserEdge(LX - XS, LY - YS);
2473 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2475 AddLaserEdge(LX - XS, LY - YS);
2481 if (IS_WALL_WOOD(element) ||
2482 IS_DF_WALL_WOOD(element) ||
2483 IS_GRID_WOOD(element) ||
2484 IS_GRID_WOOD_FIXED(element) ||
2485 IS_GRID_WOOD_AUTO(element) ||
2486 element == EL_FUSE_ON ||
2487 element == EL_BLOCK_WOOD ||
2488 element == EL_GATE_WOOD)
2490 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2495 if (IS_WALL_ICE(element))
2501 // check if laser hit adjacent edges of two diagonal tiles
2502 if (ELX != lx / TILEX)
2504 if (ELY != ly / TILEY)
2507 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2508 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2510 // check if laser hits wall with an angle of 90°
2511 if (IS_90_ANGLE(laser.current_angle))
2512 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2514 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2518 for (i = 0; i < 4; i++)
2520 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2521 mask = 15 - (8 >> i);
2522 else if (ABS(XS) == 4 &&
2524 (XS > 0) == (i % 2) &&
2525 (YS < 0) == (i / 2))
2526 mask = 3 + (i / 2) * 9;
2527 else if (ABS(YS) == 4 &&
2529 (XS < 0) == (i % 2) &&
2530 (YS > 0) == (i / 2))
2531 mask = 5 + (i % 2) * 5;
2535 laser.wall_mask = mask;
2537 else if (IS_WALL_AMOEBA(element))
2539 int elx = (LX - 2 * XS) / TILEX;
2540 int ely = (LY - 2 * YS) / TILEY;
2541 int element2 = Tile[elx][ely];
2544 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2546 laser.dest_element = EL_EMPTY;
2554 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2555 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2557 if (IS_90_ANGLE(laser.current_angle))
2558 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2560 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2562 laser.wall_mask = mask;
2568 static void OpenExit(int x, int y)
2572 if (!MovDelay[x][y]) // next animation frame
2573 MovDelay[x][y] = 4 * delay;
2575 if (MovDelay[x][y]) // wait some time before next frame
2580 phase = MovDelay[x][y] / delay;
2582 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2583 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2585 if (!MovDelay[x][y])
2587 Tile[x][y] = EL_EXIT_OPEN;
2593 static void OpenGrayBall(int x, int y)
2597 if (!MovDelay[x][y]) // next animation frame
2599 if (IS_WALL(Store[x][y]))
2601 DrawWalls_MM(x, y, Store[x][y]);
2603 // copy wall tile to spare bitmap for "melting" animation
2604 BlitBitmap(drawto_mm, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2605 TILEX, TILEY, x * TILEX, y * TILEY);
2607 DrawElement_MM(x, y, EL_GRAY_BALL);
2610 MovDelay[x][y] = 50 * delay;
2613 if (MovDelay[x][y]) // wait some time before next frame
2617 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2621 int dx = RND(26), dy = RND(26);
2623 if (IS_WALL(Store[x][y]))
2625 // copy wall tile from spare bitmap for "melting" animation
2626 bitmap = bitmap_db_field;
2632 int graphic = el2gfx(Store[x][y]);
2634 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2637 BlitBitmap(bitmap, drawto_mm, gx + dx, gy + dy, 6, 6,
2638 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2640 laser.redraw = TRUE;
2642 MarkTileDirty(x, y);
2645 if (!MovDelay[x][y])
2647 Tile[x][y] = Store[x][y];
2648 Store[x][y] = Store2[x][y] = 0;
2649 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2651 InitField(x, y, FALSE);
2654 ScanLaser_FromLastMirror();
2659 static void OpenEnvelope(int x, int y)
2661 int num_frames = 8; // seven frames plus final empty space
2663 if (!MovDelay[x][y]) // next animation frame
2664 MovDelay[x][y] = num_frames;
2666 if (MovDelay[x][y]) // wait some time before next frame
2668 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2672 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2674 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2675 int frame = num_frames - MovDelay[x][y] - 1;
2677 DrawGraphicAnimation_MM(x, y, graphic, frame);
2679 laser.redraw = TRUE;
2682 if (MovDelay[x][y] == 0)
2684 Tile[x][y] = EL_EMPTY;
2695 static void MeltIce(int x, int y)
2700 if (!MovDelay[x][y]) // next animation frame
2701 MovDelay[x][y] = frames * delay;
2703 if (MovDelay[x][y]) // wait some time before next frame
2706 int wall_mask = Store2[x][y];
2707 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2710 phase = frames - MovDelay[x][y] / delay - 1;
2712 if (!MovDelay[x][y])
2714 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2715 Store[x][y] = Store2[x][y] = 0;
2717 DrawWalls_MM(x, y, Tile[x][y]);
2719 if (Tile[x][y] == EL_WALL_ICE_BASE)
2720 Tile[x][y] = EL_EMPTY;
2722 ScanLaser_FromLastMirror();
2724 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2726 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2728 laser.redraw = TRUE;
2733 static void GrowAmoeba(int x, int y)
2738 if (!MovDelay[x][y]) // next animation frame
2739 MovDelay[x][y] = frames * delay;
2741 if (MovDelay[x][y]) // wait some time before next frame
2744 int wall_mask = Store2[x][y];
2745 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2748 phase = MovDelay[x][y] / delay;
2750 if (!MovDelay[x][y])
2752 Tile[x][y] = real_element;
2753 Store[x][y] = Store2[x][y] = 0;
2755 DrawWalls_MM(x, y, Tile[x][y]);
2756 DrawLaser(0, DL_LASER_ENABLED);
2758 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2760 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2765 static void DrawFieldAnimated_MM(int x, int y)
2769 laser.redraw = TRUE;
2772 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2774 int element = Tile[x][y];
2775 int graphic = el2gfx(element);
2777 if (!getGraphicInfo_NewFrame(x, y, graphic))
2782 laser.redraw = TRUE;
2785 static void DrawFieldTwinkle(int x, int y)
2787 if (MovDelay[x][y] != 0) // wait some time before next frame
2793 if (MovDelay[x][y] != 0)
2795 int graphic = IMG_TWINKLE_WHITE;
2796 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2798 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2801 laser.redraw = TRUE;
2805 static void Explode_MM(int x, int y, int phase, int mode)
2807 int num_phase = 9, delay = 2;
2808 int last_phase = num_phase * delay;
2809 int half_phase = (num_phase / 2) * delay;
2812 laser.redraw = TRUE;
2814 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2816 center_element = Tile[x][y];
2818 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2820 // put moving element to center field (and let it explode there)
2821 center_element = MovingOrBlocked2Element_MM(x, y);
2822 RemoveMovingField_MM(x, y);
2824 Tile[x][y] = center_element;
2827 if (center_element != EL_GRAY_BALL_ACTIVE)
2828 Store[x][y] = EL_EMPTY;
2829 Store2[x][y] = center_element;
2831 Tile[x][y] = EL_EXPLODING_OPAQUE;
2833 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2834 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2835 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2838 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2840 ExplodePhase[x][y] = 1;
2846 GfxFrame[x][y] = 0; // restart explosion animation
2848 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2850 center_element = Store2[x][y];
2852 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2854 Tile[x][y] = EL_EXPLODING_TRANSP;
2856 if (x == ELX && y == ELY)
2860 if (phase == last_phase)
2862 if (center_element == EL_BOMB_ACTIVE)
2864 DrawLaser(0, DL_LASER_DISABLED);
2867 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2869 laser.overloaded = FALSE;
2871 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2873 GameOver_MM(GAME_OVER_BOMB);
2876 Tile[x][y] = Store[x][y];
2878 Store[x][y] = Store2[x][y] = 0;
2879 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2881 InitField(x, y, FALSE);
2884 if (center_element == EL_GRAY_BALL_ACTIVE)
2885 ScanLaser_FromLastMirror();
2887 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2889 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2890 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2892 DrawGraphicAnimation_MM(x, y, graphic, frame);
2894 MarkTileDirty(x, y);
2898 static void Bang_MM(int x, int y)
2900 int element = Tile[x][y];
2902 if (IS_PACMAN(element))
2903 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2904 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2905 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2906 else if (element == EL_KEY)
2907 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2909 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2911 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2914 static void TurnRound(int x, int y)
2926 { 0, 0 }, { 0, 0 }, { 0, 0 },
2931 int left, right, back;
2935 { MV_DOWN, MV_UP, MV_RIGHT },
2936 { MV_UP, MV_DOWN, MV_LEFT },
2938 { MV_LEFT, MV_RIGHT, MV_DOWN },
2942 { MV_RIGHT, MV_LEFT, MV_UP }
2945 int element = Tile[x][y];
2946 int old_move_dir = MovDir[x][y];
2947 int right_dir = turn[old_move_dir].right;
2948 int back_dir = turn[old_move_dir].back;
2949 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2950 int right_x = x + right_dx, right_y = y + right_dy;
2952 if (element == EL_PACMAN)
2954 boolean can_turn_right = FALSE;
2956 if (IN_LEV_FIELD(right_x, right_y) &&
2957 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2958 can_turn_right = TRUE;
2961 MovDir[x][y] = right_dir;
2963 MovDir[x][y] = back_dir;
2969 static void StartMoving_MM(int x, int y)
2971 int element = Tile[x][y];
2976 if (CAN_MOVE(element))
2980 if (MovDelay[x][y]) // wait some time before next movement
2988 // now make next step
2990 Moving2Blocked(x, y, &newx, &newy); // get next screen position
2992 if (element == EL_PACMAN &&
2993 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2994 !ObjHit(newx, newy, HIT_POS_CENTER))
2996 Store[newx][newy] = Tile[newx][newy];
2997 Tile[newx][newy] = EL_EMPTY;
2999 DrawField_MM(newx, newy);
3001 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
3002 ObjHit(newx, newy, HIT_POS_CENTER))
3004 // object was running against a wall
3011 InitMovingField_MM(x, y, MovDir[x][y]);
3015 ContinueMoving_MM(x, y);
3018 static void ContinueMoving_MM(int x, int y)
3020 int element = Tile[x][y];
3021 int direction = MovDir[x][y];
3022 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3023 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3024 int horiz_move = (dx!=0);
3025 int newx = x + dx, newy = y + dy;
3026 int step = (horiz_move ? dx : dy) * TILEX / 8;
3028 MovPos[x][y] += step;
3030 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
3032 Tile[x][y] = EL_EMPTY;
3033 Tile[newx][newy] = element;
3035 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3036 MovDelay[newx][newy] = 0;
3038 if (!CAN_MOVE(element))
3039 MovDir[newx][newy] = 0;
3042 DrawField_MM(newx, newy);
3044 Stop[newx][newy] = TRUE;
3046 if (element == EL_PACMAN)
3048 if (Store[newx][newy] == EL_BOMB)
3049 Bang_MM(newx, newy);
3051 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3052 (LX + 2 * XS) / TILEX == newx &&
3053 (LY + 2 * YS) / TILEY == newy)
3060 else // still moving on
3065 laser.redraw = TRUE;
3068 boolean ClickElement(int x, int y, int button)
3070 static DelayCounter click_delay = { CLICK_DELAY };
3071 static boolean new_button = TRUE;
3072 boolean element_clicked = FALSE;
3077 // initialize static variables
3078 click_delay.count = 0;
3079 click_delay.value = CLICK_DELAY;
3085 // do not rotate objects hit by the laser after the game was solved
3086 if (game_mm.level_solved && Hit[x][y])
3089 if (button == MB_RELEASED)
3092 click_delay.value = CLICK_DELAY;
3094 // release eventually hold auto-rotating mirror
3095 RotateMirror(x, y, MB_RELEASED);
3100 if (!FrameReached(&click_delay) && !new_button)
3103 if (button == MB_MIDDLEBUTTON) // middle button has no function
3106 if (!IN_LEV_FIELD(x, y))
3109 if (Tile[x][y] == EL_EMPTY)
3112 element = Tile[x][y];
3114 if (IS_MIRROR(element) ||
3115 IS_BEAMER(element) ||
3116 IS_POLAR(element) ||
3117 IS_POLAR_CROSS(element) ||
3118 IS_DF_MIRROR(element) ||
3119 IS_DF_MIRROR_AUTO(element))
3121 RotateMirror(x, y, button);
3123 element_clicked = TRUE;
3125 else if (IS_MCDUFFIN(element))
3127 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3129 if (has_laser && !laser.fuse_off)
3130 DrawLaser(0, DL_LASER_DISABLED);
3132 element = get_rotated_element(element, BUTTON_ROTATION(button));
3134 Tile[x][y] = element;
3139 laser.start_angle = get_element_angle(element);
3143 if (!laser.fuse_off)
3147 element_clicked = TRUE;
3149 else if (element == EL_FUSE_ON && laser.fuse_off)
3151 if (x != laser.fuse_x || y != laser.fuse_y)
3154 laser.fuse_off = FALSE;
3155 laser.fuse_x = laser.fuse_y = -1;
3157 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3160 element_clicked = TRUE;
3162 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3164 laser.fuse_off = TRUE;
3167 laser.overloaded = FALSE;
3169 DrawLaser(0, DL_LASER_DISABLED);
3170 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3172 element_clicked = TRUE;
3174 else if (element == EL_LIGHTBALL)
3177 RaiseScoreElement_MM(element);
3178 DrawLaser(0, DL_LASER_ENABLED);
3180 element_clicked = TRUE;
3182 else if (IS_ENVELOPE(element))
3184 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3186 element_clicked = TRUE;
3189 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3192 return element_clicked;
3195 static void RotateMirror(int x, int y, int button)
3197 if (button == MB_RELEASED)
3199 // release eventually hold auto-rotating mirror
3206 if (IS_MIRROR(Tile[x][y]) ||
3207 IS_POLAR_CROSS(Tile[x][y]) ||
3208 IS_POLAR(Tile[x][y]) ||
3209 IS_BEAMER(Tile[x][y]) ||
3210 IS_DF_MIRROR(Tile[x][y]) ||
3211 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3212 IS_GRID_WOOD_AUTO(Tile[x][y]))
3214 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3216 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3218 if (button == MB_LEFTBUTTON)
3220 // left mouse button only for manual adjustment, no auto-rotating;
3221 // freeze mirror for until mouse button released
3225 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3227 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3231 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3233 int edge = Hit[x][y];
3239 DrawLaser(edge - 1, DL_LASER_DISABLED);
3243 else if (ObjHit(x, y, HIT_POS_CENTER))
3245 int edge = Hit[x][y];
3249 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3254 DrawLaser(edge - 1, DL_LASER_DISABLED);
3261 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3266 if ((IS_BEAMER(Tile[x][y]) ||
3267 IS_POLAR(Tile[x][y]) ||
3268 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3270 if (IS_BEAMER(Tile[x][y]))
3273 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3274 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3287 DrawLaser(0, DL_LASER_ENABLED);
3291 static void AutoRotateMirrors(void)
3295 if (!FrameReached(&rotate_delay))
3298 for (x = 0; x < lev_fieldx; x++)
3300 for (y = 0; y < lev_fieldy; y++)
3302 int element = Tile[x][y];
3304 // do not rotate objects hit by the laser after the game was solved
3305 if (game_mm.level_solved && Hit[x][y])
3308 if (IS_DF_MIRROR_AUTO(element) ||
3309 IS_GRID_WOOD_AUTO(element) ||
3310 IS_GRID_STEEL_AUTO(element) ||
3311 element == EL_REFRACTOR)
3313 RotateMirror(x, y, MB_RIGHTBUTTON);
3315 laser.redraw = TRUE;
3321 static boolean ObjHit(int obx, int oby, int bits)
3328 if (bits & HIT_POS_CENTER)
3330 if (CheckLaserPixel(cSX + obx + 15,
3335 if (bits & HIT_POS_EDGE)
3337 for (i = 0; i < 4; i++)
3338 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3339 cSY + oby + 31 * (i / 2)))
3343 if (bits & HIT_POS_BETWEEN)
3345 for (i = 0; i < 4; i++)
3346 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3347 cSY + 4 + oby + 22 * (i / 2)))
3354 static void DeletePacMan(int px, int py)
3360 if (game_mm.num_pacman <= 1)
3362 game_mm.num_pacman = 0;
3366 for (i = 0; i < game_mm.num_pacman; i++)
3367 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3370 game_mm.num_pacman--;
3372 for (j = i; j < game_mm.num_pacman; j++)
3374 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3375 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3376 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3380 static void GameActions_MM_Ext(void)
3387 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3390 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3392 element = Tile[x][y];
3394 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3395 StartMoving_MM(x, y);
3396 else if (IS_MOVING(x, y))
3397 ContinueMoving_MM(x, y);
3398 else if (IS_EXPLODING(element))
3399 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3400 else if (element == EL_EXIT_OPENING)
3402 else if (element == EL_GRAY_BALL_OPENING)
3404 else if (IS_ENVELOPE_OPENING(element))
3406 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3408 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3410 else if (IS_MIRROR(element) ||
3411 IS_MIRROR_FIXED(element) ||
3412 element == EL_PRISM)
3413 DrawFieldTwinkle(x, y);
3414 else if (element == EL_GRAY_BALL_ACTIVE ||
3415 element == EL_BOMB_ACTIVE ||
3416 element == EL_MINE_ACTIVE)
3417 DrawFieldAnimated_MM(x, y);
3418 else if (!IS_BLOCKED(x, y))
3419 DrawFieldAnimatedIfNeeded_MM(x, y);
3422 AutoRotateMirrors();
3425 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3427 // redraw after Explode_MM() ...
3429 DrawLaser(0, DL_LASER_ENABLED);
3430 laser.redraw = FALSE;
3435 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3439 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3441 DrawLaser(0, DL_LASER_DISABLED);
3446 // skip all following game actions if game is over
3447 if (game_mm.game_over)
3450 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3454 GameOver_MM(GAME_OVER_NO_ENERGY);
3459 if (FrameReached(&energy_delay))
3461 if (game_mm.energy_left > 0)
3462 game_mm.energy_left--;
3464 // when out of energy, wait another frame to play "out of time" sound
3467 element = laser.dest_element;
3470 if (element != Tile[ELX][ELY])
3472 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3473 element, Tile[ELX][ELY]);
3477 if (!laser.overloaded && laser.overload_value == 0 &&
3478 element != EL_BOMB &&
3479 element != EL_BOMB_ACTIVE &&
3480 element != EL_MINE &&
3481 element != EL_MINE_ACTIVE &&
3482 element != EL_GRAY_BALL &&
3483 element != EL_GRAY_BALL_ACTIVE &&
3484 element != EL_BLOCK_STONE &&
3485 element != EL_BLOCK_WOOD &&
3486 element != EL_FUSE_ON &&
3487 element != EL_FUEL_FULL &&
3488 !IS_WALL_ICE(element) &&
3489 !IS_WALL_AMOEBA(element))
3492 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3494 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3495 (!laser.overloaded && laser.overload_value > 0)) &&
3496 FrameReached(&overload_delay))
3498 if (laser.overloaded)
3499 laser.overload_value++;
3501 laser.overload_value--;
3503 if (game_mm.cheat_no_overload)
3505 laser.overloaded = FALSE;
3506 laser.overload_value = 0;
3509 game_mm.laser_overload_value = laser.overload_value;
3511 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3513 SetLaserColor(0xFF);
3515 DrawLaser(0, DL_LASER_ENABLED);
3518 if (!laser.overloaded)
3519 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3520 else if (setup.sound_loops)
3521 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3523 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3525 if (laser.overload_value == MAX_LASER_OVERLOAD)
3527 UpdateAndDisplayGameControlValues();
3531 GameOver_MM(GAME_OVER_OVERLOADED);
3542 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3544 if (game_mm.cheat_no_explosion)
3549 laser.dest_element = EL_EXPLODING_OPAQUE;
3554 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3556 laser.fuse_off = TRUE;
3560 DrawLaser(0, DL_LASER_DISABLED);
3561 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3564 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3566 if (!Store2[ELX][ELY]) // check if content element not yet determined
3568 int last_anim_random_frame = gfx.anim_random_frame;
3571 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3572 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3574 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3575 native_mm_level.ball_choice_mode, 0,
3576 game_mm.ball_choice_pos);
3578 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3579 gfx.anim_random_frame = last_anim_random_frame;
3581 game_mm.ball_choice_pos++;
3583 int new_element = native_mm_level.ball_content[element_pos];
3584 int new_element_base = map_wall_to_base_element(new_element);
3586 if (IS_WALL(new_element_base))
3588 // always use completely filled wall element
3589 new_element = new_element_base | 0x000f;
3591 else if (native_mm_level.rotate_ball_content &&
3592 get_num_elements(new_element) > 1)
3594 // randomly rotate newly created game element
3595 new_element = get_rotated_element(new_element, RND(16));
3598 Store[ELX][ELY] = new_element;
3599 Store2[ELX][ELY] = TRUE;
3602 if (native_mm_level.explode_ball)
3605 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3607 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3612 if (IS_WALL_ICE(element) && CT > 50)
3614 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3616 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3617 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3618 Store2[ELX][ELY] = laser.wall_mask;
3620 laser.dest_element = Tile[ELX][ELY];
3625 if (IS_WALL_AMOEBA(element) && CT > 60)
3628 int element2 = Tile[ELX][ELY];
3630 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3633 for (i = laser.num_damages - 1; i >= 0; i--)
3634 if (laser.damage[i].is_mirror)
3637 r = laser.num_edges;
3638 d = laser.num_damages;
3645 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3648 DrawLaser(0, DL_LASER_ENABLED);
3651 x = laser.damage[k1].x;
3652 y = laser.damage[k1].y;
3657 for (i = 0; i < 4; i++)
3659 if (laser.wall_mask & (1 << i))
3661 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3662 cSY + ELY * TILEY + 31 * (i / 2)))
3665 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3666 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3673 for (i = 0; i < 4; i++)
3675 if (laser.wall_mask & (1 << i))
3677 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3678 cSY + ELY * TILEY + 31 * (i / 2)))
3685 if (laser.num_beamers > 0 ||
3686 k1 < 1 || k2 < 4 || k3 < 4 ||
3687 CheckLaserPixel(cSX + ELX * TILEX + 14,
3688 cSY + ELY * TILEY + 14))
3690 laser.num_edges = r;
3691 laser.num_damages = d;
3693 DrawLaser(0, DL_LASER_DISABLED);
3696 Tile[ELX][ELY] = element | laser.wall_mask;
3698 int x = ELX, y = ELY;
3699 int wall_mask = laser.wall_mask;
3702 DrawLaser(0, DL_LASER_ENABLED);
3704 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3706 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3707 Store[x][y] = EL_WALL_AMOEBA_BASE;
3708 Store2[x][y] = wall_mask;
3713 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3714 laser.stops_inside_element && CT > native_mm_level.time_block)
3719 if (ABS(XS) > ABS(YS))
3726 for (i = 0; i < 4; i++)
3733 x = ELX + Step[k * 4].x;
3734 y = ELY + Step[k * 4].y;
3736 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3739 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3747 laser.overloaded = (element == EL_BLOCK_STONE);
3752 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3755 Tile[x][y] = element;
3757 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3760 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3762 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3763 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3771 if (element == EL_FUEL_FULL && CT > 10)
3773 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3774 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3776 for (i = start; i <= num_init_game_frames; i++)
3778 if (i == num_init_game_frames)
3779 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3780 else if (setup.sound_loops)
3781 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3783 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3785 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3787 UpdateAndDisplayGameControlValues();
3792 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3794 DrawField_MM(ELX, ELY);
3796 DrawLaser(0, DL_LASER_ENABLED);
3802 void GameActions_MM(struct MouseActionInfo action)
3804 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3805 boolean button_released = (action.button == MB_RELEASED);
3807 GameActions_MM_Ext();
3809 CheckSingleStepMode_MM(element_clicked, button_released);
3812 static void MovePacMen(void)
3814 int mx, my, ox, oy, nx, ny;
3818 if (++pacman_nr >= game_mm.num_pacman)
3821 game_mm.pacman[pacman_nr].dir--;
3823 for (l = 1; l < 5; l++)
3825 game_mm.pacman[pacman_nr].dir++;
3827 if (game_mm.pacman[pacman_nr].dir > 4)
3828 game_mm.pacman[pacman_nr].dir = 1;
3830 if (game_mm.pacman[pacman_nr].dir % 2)
3833 my = game_mm.pacman[pacman_nr].dir - 2;
3838 mx = 3 - game_mm.pacman[pacman_nr].dir;
3841 ox = game_mm.pacman[pacman_nr].x;
3842 oy = game_mm.pacman[pacman_nr].y;
3845 element = Tile[nx][ny];
3847 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3850 if (!IS_EATABLE4PACMAN(element))
3853 if (ObjHit(nx, ny, HIT_POS_CENTER))
3856 Tile[ox][oy] = EL_EMPTY;
3858 EL_PACMAN_RIGHT - 1 +
3859 (game_mm.pacman[pacman_nr].dir - 1 +
3860 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3862 game_mm.pacman[pacman_nr].x = nx;
3863 game_mm.pacman[pacman_nr].y = ny;
3865 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3867 if (element != EL_EMPTY)
3869 int graphic = el2gfx(Tile[nx][ny]);
3874 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3877 ox = cSX + ox * TILEX;
3878 oy = cSY + oy * TILEY;
3880 for (i = 1; i < 33; i += 2)
3881 BlitBitmap(bitmap, window,
3882 src_x, src_y, TILEX, TILEY,
3883 ox + i * mx, oy + i * my);
3884 Ct = Ct + FrameCounter - CT;
3887 DrawField_MM(nx, ny);
3890 if (!laser.fuse_off)
3892 DrawLaser(0, DL_LASER_ENABLED);
3894 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3896 AddDamagedField(nx, ny);
3898 laser.damage[laser.num_damages - 1].edge = 0;
3902 if (element == EL_BOMB)
3903 DeletePacMan(nx, ny);
3905 if (IS_WALL_AMOEBA(element) &&
3906 (LX + 2 * XS) / TILEX == nx &&
3907 (LY + 2 * YS) / TILEY == ny)
3917 static void InitMovingField_MM(int x, int y, int direction)
3919 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3920 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3922 MovDir[x][y] = direction;
3923 MovDir[newx][newy] = direction;
3925 if (Tile[newx][newy] == EL_EMPTY)
3926 Tile[newx][newy] = EL_BLOCKED;
3929 static int MovingOrBlocked2Element_MM(int x, int y)
3931 int element = Tile[x][y];
3933 if (element == EL_BLOCKED)
3937 Blocked2Moving(x, y, &oldx, &oldy);
3939 return Tile[oldx][oldy];
3945 static void RemoveMovingField_MM(int x, int y)
3947 int oldx = x, oldy = y, newx = x, newy = y;
3949 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3952 if (IS_MOVING(x, y))
3954 Moving2Blocked(x, y, &newx, &newy);
3955 if (Tile[newx][newy] != EL_BLOCKED)
3958 else if (Tile[x][y] == EL_BLOCKED)
3960 Blocked2Moving(x, y, &oldx, &oldy);
3961 if (!IS_MOVING(oldx, oldy))
3965 Tile[oldx][oldy] = EL_EMPTY;
3966 Tile[newx][newy] = EL_EMPTY;
3967 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3968 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3970 DrawLevelField_MM(oldx, oldy);
3971 DrawLevelField_MM(newx, newy);
3974 static void RaiseScore_MM(int value)
3976 game_mm.score += value;
3979 void RaiseScoreElement_MM(int element)
3984 case EL_PACMAN_RIGHT:
3986 case EL_PACMAN_LEFT:
3987 case EL_PACMAN_DOWN:
3988 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3992 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3997 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4001 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4010 // ----------------------------------------------------------------------------
4011 // Mirror Magic game engine snapshot handling functions
4012 // ----------------------------------------------------------------------------
4014 void SaveEngineSnapshotValues_MM(void)
4018 engine_snapshot_mm.game_mm = game_mm;
4019 engine_snapshot_mm.laser = laser;
4021 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4023 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4025 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4026 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4027 engine_snapshot_mm.Box[x][y] = Box[x][y];
4028 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4032 engine_snapshot_mm.LX = LX;
4033 engine_snapshot_mm.LY = LY;
4034 engine_snapshot_mm.XS = XS;
4035 engine_snapshot_mm.YS = YS;
4036 engine_snapshot_mm.ELX = ELX;
4037 engine_snapshot_mm.ELY = ELY;
4038 engine_snapshot_mm.CT = CT;
4039 engine_snapshot_mm.Ct = Ct;
4041 engine_snapshot_mm.last_LX = last_LX;
4042 engine_snapshot_mm.last_LY = last_LY;
4043 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4044 engine_snapshot_mm.hold_x = hold_x;
4045 engine_snapshot_mm.hold_y = hold_y;
4046 engine_snapshot_mm.pacman_nr = pacman_nr;
4048 engine_snapshot_mm.rotate_delay = rotate_delay;
4049 engine_snapshot_mm.pacman_delay = pacman_delay;
4050 engine_snapshot_mm.energy_delay = energy_delay;
4051 engine_snapshot_mm.overload_delay = overload_delay;
4054 void LoadEngineSnapshotValues_MM(void)
4058 // stored engine snapshot buffers already restored at this point
4060 game_mm = engine_snapshot_mm.game_mm;
4061 laser = engine_snapshot_mm.laser;
4063 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4065 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4067 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4068 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4069 Box[x][y] = engine_snapshot_mm.Box[x][y];
4070 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4074 LX = engine_snapshot_mm.LX;
4075 LY = engine_snapshot_mm.LY;
4076 XS = engine_snapshot_mm.XS;
4077 YS = engine_snapshot_mm.YS;
4078 ELX = engine_snapshot_mm.ELX;
4079 ELY = engine_snapshot_mm.ELY;
4080 CT = engine_snapshot_mm.CT;
4081 Ct = engine_snapshot_mm.Ct;
4083 last_LX = engine_snapshot_mm.last_LX;
4084 last_LY = engine_snapshot_mm.last_LY;
4085 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4086 hold_x = engine_snapshot_mm.hold_x;
4087 hold_y = engine_snapshot_mm.hold_y;
4088 pacman_nr = engine_snapshot_mm.pacman_nr;
4090 rotate_delay = engine_snapshot_mm.rotate_delay;
4091 pacman_delay = engine_snapshot_mm.pacman_delay;
4092 energy_delay = engine_snapshot_mm.energy_delay;
4093 overload_delay = engine_snapshot_mm.overload_delay;
4095 RedrawPlayfield_MM();
4098 static int getAngleFromTouchDelta(int dx, int dy, int base)
4100 double pi = 3.141592653;
4101 double rad = atan2((double)-dy, (double)dx);
4102 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4103 double deg = rad2 * 180.0 / pi;
4105 return (int)(deg * base / 360.0 + 0.5) % base;
4108 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4110 // calculate start (source) position to be at the middle of the tile
4111 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4112 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4113 int dx = dst_mx - src_mx;
4114 int dy = dst_my - src_my;
4123 if (!IN_LEV_FIELD(x, y))
4126 element = Tile[x][y];
4128 if (!IS_MCDUFFIN(element) &&
4129 !IS_MIRROR(element) &&
4130 !IS_BEAMER(element) &&
4131 !IS_POLAR(element) &&
4132 !IS_POLAR_CROSS(element) &&
4133 !IS_DF_MIRROR(element))
4136 angle_old = get_element_angle(element);
4138 if (IS_MCDUFFIN(element))
4140 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4141 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4142 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4143 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4146 else if (IS_MIRROR(element) ||
4147 IS_DF_MIRROR(element))
4149 for (i = 0; i < laser.num_damages; i++)
4151 if (laser.damage[i].x == x &&
4152 laser.damage[i].y == y &&
4153 ObjHit(x, y, HIT_POS_CENTER))
4155 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4156 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4163 if (angle_new == -1)
4165 if (IS_MIRROR(element) ||
4166 IS_DF_MIRROR(element) ||
4170 if (IS_POLAR_CROSS(element))
4173 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4176 button = (angle_new == angle_old ? 0 :
4177 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4178 MB_LEFTBUTTON : MB_RIGHTBUTTON);