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 static void AddLaserEdge(int, int);
103 static void ScanLaser(void);
104 static void DrawLaser(int, int);
105 static boolean HitElement(int, int);
106 static boolean HitOnlyAnEdge(int);
107 static boolean HitPolarizer(int, int);
108 static boolean HitBlock(int, int);
109 static boolean HitLaserSource(int, int);
110 static boolean HitLaserDestination(int, int);
111 static boolean HitReflectingWalls(int, int);
112 static boolean HitAbsorbingWalls(int, int);
113 static void RotateMirror(int, int, int);
114 static boolean ObjHit(int, int, int);
115 static void DeletePacMan(int, int);
116 static void MovePacMen(void);
118 // bitmap for laser beam detection
119 static Bitmap *laser_bitmap = NULL;
121 // variables for laser control
122 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
123 static int hold_x = -1, hold_y = -1;
125 // variables for pacman control
126 static int pacman_nr = -1;
128 // various game engine delay counters
129 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
130 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
131 static DelayCounter energy_delay = { ENERGY_DELAY };
132 static DelayCounter overload_delay = { 0 };
134 // element mask positions for scanning pixels of MM elements
135 #define MM_MASK_MCDUFFIN_RIGHT 0
136 #define MM_MASK_MCDUFFIN_UP 1
137 #define MM_MASK_MCDUFFIN_LEFT 2
138 #define MM_MASK_MCDUFFIN_DOWN 3
139 #define MM_MASK_GRID_1 4
140 #define MM_MASK_GRID_2 5
141 #define MM_MASK_GRID_3 6
142 #define MM_MASK_GRID_4 7
143 #define MM_MASK_RECTANGLE 8
144 #define MM_MASK_CIRCLE 9
146 #define NUM_MM_MASKS 10
148 // element masks for scanning pixels of MM elements
149 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
333 static int get_element_angle(int element)
335 int element_phase = get_element_phase(element);
337 if (IS_MIRROR_FIXED(element) ||
338 IS_MCDUFFIN(element) ||
340 IS_RECEIVER(element))
341 return 4 * element_phase;
343 return element_phase;
346 static int get_opposite_angle(int angle)
348 int opposite_angle = angle + ANG_RAY_180;
350 // make sure "opposite_angle" is in valid interval [0, 15]
351 return (opposite_angle + 16) % 16;
354 static int get_mirrored_angle(int laser_angle, int mirror_angle)
356 int reflected_angle = 16 - laser_angle + mirror_angle;
358 // make sure "reflected_angle" is in valid interval [0, 15]
359 return (reflected_angle + 16) % 16;
362 static void DrawLaserLines(struct XY *points, int num_points, int mode)
364 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
365 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
367 DrawLines(drawto, points, num_points, pixel_drawto);
371 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
376 static boolean CheckLaserPixel(int x, int y)
382 pixel = ReadPixel(laser_bitmap, x, y);
386 return (pixel == WHITE_PIXEL);
389 static void CheckExitMM(void)
391 int exit_element = EL_EMPTY;
395 static int xy[4][2] =
403 for (y = 0; y < lev_fieldy; y++)
405 for (x = 0; x < lev_fieldx; x++)
407 if (Tile[x][y] == EL_EXIT_CLOSED)
409 // initiate opening animation of exit door
410 Tile[x][y] = EL_EXIT_OPENING;
412 exit_element = EL_EXIT_OPEN;
416 else if (IS_RECEIVER(Tile[x][y]))
418 // remove field that blocks receiver
419 int phase = Tile[x][y] - EL_RECEIVER_START;
420 int blocking_x, blocking_y;
422 blocking_x = x + xy[phase][0];
423 blocking_y = y + xy[phase][1];
425 if (IN_LEV_FIELD(blocking_x, blocking_y))
427 Tile[blocking_x][blocking_y] = EL_EMPTY;
429 DrawField_MM(blocking_x, blocking_y);
432 exit_element = EL_RECEIVER;
439 if (exit_element != EL_EMPTY)
440 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
443 static void SetLaserColor(int brightness)
445 int color_min = 0x00;
446 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
447 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
448 int color_down = color_max - color_up;
451 GetPixelFromRGB(window,
452 (game_mm.laser_red ? color_max : color_up),
453 (game_mm.laser_green ? color_down : color_min),
454 (game_mm.laser_blue ? color_down : color_min));
457 static void InitMovDir_MM(int x, int y)
459 int element = Tile[x][y];
460 static int direction[3][4] =
462 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
463 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
464 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
469 case EL_PACMAN_RIGHT:
473 Tile[x][y] = EL_PACMAN;
474 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
482 static void InitField(int x, int y, boolean init_game)
484 int element = Tile[x][y];
489 Tile[x][y] = EL_EMPTY;
494 if (init_game && native_mm_level.auto_count_kettles)
495 game_mm.kettles_still_needed++;
498 case EL_LIGHTBULB_OFF:
499 game_mm.lights_still_needed++;
503 if (IS_MIRROR(element) ||
504 IS_BEAMER_OLD(element) ||
505 IS_BEAMER(element) ||
507 IS_POLAR_CROSS(element) ||
508 IS_DF_MIRROR(element) ||
509 IS_DF_MIRROR_AUTO(element) ||
510 IS_GRID_STEEL_AUTO(element) ||
511 IS_GRID_WOOD_AUTO(element) ||
512 IS_FIBRE_OPTIC(element))
514 if (IS_BEAMER_OLD(element))
516 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
517 element = Tile[x][y];
520 if (!IS_FIBRE_OPTIC(element))
522 static int steps_grid_auto = 0;
524 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
525 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
527 if (IS_GRID_STEEL_AUTO(element) ||
528 IS_GRID_WOOD_AUTO(element))
529 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
531 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
533 game_mm.cycle[game_mm.num_cycle].x = x;
534 game_mm.cycle[game_mm.num_cycle].y = y;
538 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
540 int beamer_nr = BEAMER_NR(element);
541 int nr = laser.beamer[beamer_nr][0].num;
543 laser.beamer[beamer_nr][nr].x = x;
544 laser.beamer[beamer_nr][nr].y = y;
545 laser.beamer[beamer_nr][nr].num = 1;
548 else if (IS_PACMAN(element))
552 else if (IS_MCDUFFIN(element) || IS_LASER(element))
556 laser.start_edge.x = x;
557 laser.start_edge.y = y;
558 laser.start_angle = get_element_angle(element);
561 if (IS_MCDUFFIN(element))
563 game_mm.laser_red = native_mm_level.mm_laser_red;
564 game_mm.laser_green = native_mm_level.mm_laser_green;
565 game_mm.laser_blue = native_mm_level.mm_laser_blue;
569 game_mm.laser_red = native_mm_level.df_laser_red;
570 game_mm.laser_green = native_mm_level.df_laser_green;
571 game_mm.laser_blue = native_mm_level.df_laser_blue;
574 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
581 static void InitCycleElements_RotateSingleStep(void)
585 if (game_mm.num_cycle == 0) // no elements to cycle
588 for (i = 0; i < game_mm.num_cycle; i++)
590 int x = game_mm.cycle[i].x;
591 int y = game_mm.cycle[i].y;
592 int step = SIGN(game_mm.cycle[i].steps);
593 int last_element = Tile[x][y];
594 int next_element = get_rotated_element(last_element, step);
596 if (!game_mm.cycle[i].steps)
599 Tile[x][y] = next_element;
601 game_mm.cycle[i].steps -= step;
605 static void InitLaser(void)
607 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
608 int step = (IS_LASER(start_element) ? 4 : 0);
610 LX = laser.start_edge.x * TILEX;
611 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
614 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
616 LY = laser.start_edge.y * TILEY;
617 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
618 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
622 XS = 2 * Step[laser.start_angle].x;
623 YS = 2 * Step[laser.start_angle].y;
625 laser.current_angle = laser.start_angle;
627 laser.num_damages = 0;
629 laser.num_beamers = 0;
630 laser.beamer_edge[0] = 0;
632 laser.dest_element = EL_EMPTY;
635 AddLaserEdge(LX, LY); // set laser starting edge
640 void InitGameEngine_MM(void)
646 // initialize laser bitmap to current playfield (screen) size
647 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
648 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
652 // set global game control values
653 game_mm.num_cycle = 0;
654 game_mm.num_pacman = 0;
657 game_mm.energy_left = 0; // later set to "native_mm_level.time"
658 game_mm.kettles_still_needed =
659 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
660 game_mm.lights_still_needed = 0;
661 game_mm.num_keys = 0;
662 game_mm.ball_choice_pos = 0;
664 game_mm.laser_red = FALSE;
665 game_mm.laser_green = FALSE;
666 game_mm.laser_blue = TRUE;
667 game_mm.has_mcduffin = TRUE;
669 game_mm.level_solved = FALSE;
670 game_mm.game_over = FALSE;
671 game_mm.game_over_cause = 0;
672 game_mm.game_over_message = NULL;
674 game_mm.laser_overload_value = 0;
675 game_mm.laser_enabled = FALSE;
677 // set global laser control values (must be set before "InitLaser()")
678 laser.start_edge.x = 0;
679 laser.start_edge.y = 0;
680 laser.start_angle = 0;
682 for (i = 0; i < MAX_NUM_BEAMERS; i++)
683 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
685 laser.overloaded = FALSE;
686 laser.overload_value = 0;
687 laser.fuse_off = FALSE;
688 laser.fuse_x = laser.fuse_y = -1;
690 laser.dest_element = EL_EMPTY;
691 laser.dest_element_last = EL_EMPTY;
692 laser.dest_element_last_x = -1;
693 laser.dest_element_last_y = -1;
707 rotate_delay.count = 0;
708 pacman_delay.count = 0;
709 energy_delay.count = 0;
710 overload_delay.count = 0;
712 ClickElement(-1, -1, -1);
714 for (x = 0; x < lev_fieldx; x++)
716 for (y = 0; y < lev_fieldy; y++)
718 Tile[x][y] = Ur[x][y];
719 Hit[x][y] = Box[x][y] = 0;
721 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
722 Store[x][y] = Store2[x][y] = 0;
725 InitField(x, y, TRUE);
732 void InitGameActions_MM(void)
734 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
735 int cycle_steps_done = 0;
740 for (i = 0; i <= num_init_game_frames; i++)
742 if (i == num_init_game_frames)
743 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
744 else if (setup.sound_loops)
745 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
747 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
749 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
751 UpdateAndDisplayGameControlValues();
753 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
755 InitCycleElements_RotateSingleStep();
760 AdvanceFrameCounter();
768 if (setup.quick_doors)
775 if (game_mm.kettles_still_needed == 0)
778 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
779 SetTileCursorActive(TRUE);
781 // restart all delay counters after initially cycling game elements
782 ResetFrameCounter(&rotate_delay);
783 ResetFrameCounter(&pacman_delay);
784 ResetFrameCounter(&energy_delay);
785 ResetFrameCounter(&overload_delay);
788 static void FadeOutLaser(void)
792 for (i = 15; i >= 0; i--)
794 SetLaserColor(0x11 * i);
796 DrawLaser(0, DL_LASER_ENABLED);
799 Delay_WithScreenUpdates(50);
802 DrawLaser(0, DL_LASER_DISABLED);
804 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
807 static void GameOver_MM(int game_over_cause)
809 game_mm.game_over = TRUE;
810 game_mm.game_over_cause = game_over_cause;
811 game_mm.game_over_message = (game_mm.has_mcduffin ?
812 (game_over_cause == GAME_OVER_BOMB ?
813 "Bomb killed Mc Duffin!" :
814 game_over_cause == GAME_OVER_NO_ENERGY ?
815 "Out of magic energy!" :
816 game_over_cause == GAME_OVER_OVERLOADED ?
817 "Magic spell hit Mc Duffin!" :
819 (game_over_cause == GAME_OVER_BOMB ?
820 "Bomb destroyed laser cannon!" :
821 game_over_cause == GAME_OVER_NO_ENERGY ?
822 "Out of laser energy!" :
823 game_over_cause == GAME_OVER_OVERLOADED ?
824 "Laser beam hit laser cannon!" :
827 SetTileCursorActive(FALSE);
830 static void AddLaserEdge(int lx, int ly)
835 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
837 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
842 laser.edge[laser.num_edges].x = cSX2 + lx;
843 laser.edge[laser.num_edges].y = cSY2 + ly;
849 static void AddDamagedField(int ex, int ey)
851 // prevent adding the same field position again
852 if (laser.num_damages > 0 &&
853 laser.damage[laser.num_damages - 1].x == ex &&
854 laser.damage[laser.num_damages - 1].y == ey &&
855 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
858 laser.damage[laser.num_damages].is_mirror = FALSE;
859 laser.damage[laser.num_damages].angle = laser.current_angle;
860 laser.damage[laser.num_damages].edge = laser.num_edges;
861 laser.damage[laser.num_damages].x = ex;
862 laser.damage[laser.num_damages].y = ey;
866 static boolean StepBehind(void)
872 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
873 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
875 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
881 static int getMaskFromElement(int element)
883 if (IS_GRID(element))
884 return MM_MASK_GRID_1 + get_element_phase(element);
885 else if (IS_MCDUFFIN(element))
886 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
887 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
888 return MM_MASK_RECTANGLE;
890 return MM_MASK_CIRCLE;
893 static int ScanPixel(void)
898 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
899 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
902 // follow laser beam until it hits something (at least the screen border)
903 while (hit_mask == HIT_MASK_NO_HIT)
909 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
910 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
912 Debug("game:mm:ScanPixel", "touched screen border!");
918 // check if laser scan has crossed element boundaries (not just mini tiles)
919 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
920 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
922 if (cross_x && cross_y)
924 int elx1 = (LX - XS) / TILEX;
925 int ely1 = (LY + YS) / TILEY;
926 int elx2 = (LX + XS) / TILEX;
927 int ely2 = (LY - YS) / TILEY;
929 // add element corners left and right from the laser beam to damage list
931 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
932 AddDamagedField(elx1, ely1);
934 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
935 AddDamagedField(elx2, ely2);
938 for (i = 0; i < 4; i++)
940 int px = LX + (i % 2) * 2;
941 int py = LY + (i / 2) * 2;
944 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
945 int ly = (py + TILEY) / TILEY - 1; // negative values!
948 if (IN_LEV_FIELD(lx, ly))
950 int element = Tile[lx][ly];
952 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
956 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
958 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
960 pixel = ((element & (1 << pos)) ? 1 : 0);
964 int pos = getMaskFromElement(element);
966 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
971 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
972 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
975 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
976 hit_mask |= (1 << i);
979 if (hit_mask == HIT_MASK_NO_HIT)
981 // hit nothing -- go on with another step
990 static void DeactivateLaserTargetElement(void)
992 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
993 laser.dest_element_last == EL_MINE_ACTIVE ||
994 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
995 laser.dest_element_last == EL_GRAY_BALL_OPENING)
997 int x = laser.dest_element_last_x;
998 int y = laser.dest_element_last_y;
999 int element = laser.dest_element_last;
1001 if (Tile[x][y] == element)
1002 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1003 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1005 if (Tile[x][y] == EL_GRAY_BALL)
1008 laser.dest_element_last = EL_EMPTY;
1009 laser.dest_element_last_x = -1;
1010 laser.dest_element_last_y = -1;
1014 static void ScanLaser(void)
1016 int element = EL_EMPTY;
1017 int last_element = EL_EMPTY;
1018 int end = 0, rf = laser.num_edges;
1020 // do not scan laser again after the game was lost for whatever reason
1021 if (game_mm.game_over)
1024 // do not scan laser if fuse is off
1028 DeactivateLaserTargetElement();
1030 laser.overloaded = FALSE;
1031 laser.stops_inside_element = FALSE;
1033 DrawLaser(0, DL_LASER_ENABLED);
1036 Debug("game:mm:ScanLaser",
1037 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1045 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1048 laser.overloaded = TRUE;
1053 hit_mask = ScanPixel();
1056 Debug("game:mm:ScanLaser",
1057 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1061 // hit something -- check out what it was
1062 ELX = (LX + XS) / TILEX;
1063 ELY = (LY + YS) / TILEY;
1066 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1067 hit_mask, LX, LY, ELX, ELY);
1070 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1073 laser.dest_element = element;
1078 // check if laser scan has hit two diagonally adjacent element corners
1079 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1080 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1082 // check if laser scan has crossed element boundaries (not just mini tiles)
1083 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1084 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1086 // handle special case of laser hitting two diagonally adjacent elements
1087 // (with or without a third corner element behind these two elements)
1088 if ((diag_1 || diag_2) && cross_x && cross_y)
1090 // compare the two diagonally adjacent elements
1092 int yoffset = 2 * (diag_1 ? -1 : +1);
1093 int elx1 = (LX - xoffset) / TILEX;
1094 int ely1 = (LY + yoffset) / TILEY;
1095 int elx2 = (LX + xoffset) / TILEX;
1096 int ely2 = (LY - yoffset) / TILEY;
1097 int e1 = Tile[elx1][ely1];
1098 int e2 = Tile[elx2][ely2];
1099 boolean use_element_1 = FALSE;
1101 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1103 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1104 use_element_1 = (RND(2) ? TRUE : FALSE);
1105 else if (IS_WALL_ICE(e1))
1106 use_element_1 = TRUE;
1108 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1110 // if both tiles match, we can just select the first one
1111 if (IS_WALL_AMOEBA(e1))
1112 use_element_1 = TRUE;
1114 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1116 // if both tiles match, we can just select the first one
1117 if (IS_ABSORBING_BLOCK(e1))
1118 use_element_1 = TRUE;
1121 ELX = (use_element_1 ? elx1 : elx2);
1122 ELY = (use_element_1 ? ely1 : ely2);
1126 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1127 hit_mask, LX, LY, ELX, ELY);
1130 last_element = element;
1132 element = Tile[ELX][ELY];
1133 laser.dest_element = element;
1136 Debug("game:mm:ScanLaser",
1137 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1140 LX % TILEX, LY % TILEY,
1145 if (!IN_LEV_FIELD(ELX, ELY))
1146 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1150 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1151 if (element == EL_EMPTY &&
1152 IS_GRID_STEEL(last_element) &&
1153 laser.current_angle % 4) // angle is not 90°
1154 element = last_element;
1156 if (element == EL_EMPTY)
1158 if (!HitOnlyAnEdge(hit_mask))
1161 else if (element == EL_FUSE_ON)
1163 if (HitPolarizer(element, hit_mask))
1166 else if (IS_GRID(element) || IS_DF_GRID(element))
1168 if (HitPolarizer(element, hit_mask))
1171 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1172 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1174 if (HitBlock(element, hit_mask))
1181 else if (IS_MCDUFFIN(element))
1183 if (HitLaserSource(element, hit_mask))
1186 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1187 IS_RECEIVER(element))
1189 if (HitLaserDestination(element, hit_mask))
1192 else if (IS_WALL(element))
1194 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1196 if (HitReflectingWalls(element, hit_mask))
1201 if (HitAbsorbingWalls(element, hit_mask))
1207 if (HitElement(element, hit_mask))
1212 DrawLaser(rf - 1, DL_LASER_ENABLED);
1213 rf = laser.num_edges;
1215 if (!IS_DF_WALL_STEEL(element))
1217 // only used for scanning DF steel walls; reset for all other elements
1225 if (laser.dest_element != Tile[ELX][ELY])
1227 Debug("game:mm:ScanLaser",
1228 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1229 laser.dest_element, Tile[ELX][ELY]);
1233 if (!end && !laser.stops_inside_element && !StepBehind())
1236 Debug("game:mm:ScanLaser", "Go one step back");
1242 AddLaserEdge(LX, LY);
1246 DrawLaser(rf - 1, DL_LASER_ENABLED);
1248 Ct = CT = FrameCounter;
1251 if (!IN_LEV_FIELD(ELX, ELY))
1252 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1256 static void ScanLaser_FromLastMirror(void)
1258 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1261 for (i = start_pos; i >= 0; i--)
1262 if (laser.damage[i].is_mirror)
1265 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1267 DrawLaser(start_edge, DL_LASER_DISABLED);
1272 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1278 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1279 start_edge, num_edges, mode);
1284 Warn("DrawLaserExt: start_edge < 0");
1291 Warn("DrawLaserExt: num_edges < 0");
1297 if (mode == DL_LASER_DISABLED)
1299 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1303 // now draw the laser to the backbuffer and (if enabled) to the screen
1304 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1306 redraw_mask |= REDRAW_FIELD;
1308 if (mode == DL_LASER_ENABLED)
1311 // after the laser was deleted, the "damaged" graphics must be restored
1312 if (laser.num_damages)
1314 int damage_start = 0;
1317 // determine the starting edge, from which graphics need to be restored
1320 for (i = 0; i < laser.num_damages; i++)
1322 if (laser.damage[i].edge == start_edge + 1)
1331 // restore graphics from this starting edge to the end of damage list
1332 for (i = damage_start; i < laser.num_damages; i++)
1334 int lx = laser.damage[i].x;
1335 int ly = laser.damage[i].y;
1336 int element = Tile[lx][ly];
1338 if (Hit[lx][ly] == laser.damage[i].edge)
1339 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1342 if (Box[lx][ly] == laser.damage[i].edge)
1345 if (IS_DRAWABLE(element))
1346 DrawField_MM(lx, ly);
1349 elx = laser.damage[damage_start].x;
1350 ely = laser.damage[damage_start].y;
1351 element = Tile[elx][ely];
1354 if (IS_BEAMER(element))
1358 for (i = 0; i < laser.num_beamers; i++)
1359 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1361 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1362 mode, elx, ely, Hit[elx][ely], start_edge);
1363 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1364 get_element_angle(element), laser.damage[damage_start].angle);
1368 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1369 laser.num_beamers > 0 &&
1370 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1372 // element is outgoing beamer
1373 laser.num_damages = damage_start + 1;
1375 if (IS_BEAMER(element))
1376 laser.current_angle = get_element_angle(element);
1380 // element is incoming beamer or other element
1381 laser.num_damages = damage_start;
1382 laser.current_angle = laser.damage[laser.num_damages].angle;
1387 // no damages but McDuffin himself (who needs to be redrawn anyway)
1389 elx = laser.start_edge.x;
1390 ely = laser.start_edge.y;
1391 element = Tile[elx][ely];
1394 laser.num_edges = start_edge + 1;
1395 if (start_edge == 0)
1396 laser.current_angle = laser.start_angle;
1398 LX = laser.edge[start_edge].x - cSX2;
1399 LY = laser.edge[start_edge].y - cSY2;
1400 XS = 2 * Step[laser.current_angle].x;
1401 YS = 2 * Step[laser.current_angle].y;
1404 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1410 if (IS_BEAMER(element) ||
1411 IS_FIBRE_OPTIC(element) ||
1412 IS_PACMAN(element) ||
1413 IS_POLAR(element) ||
1414 IS_POLAR_CROSS(element) ||
1415 element == EL_FUSE_ON)
1420 Debug("game:mm:DrawLaserExt", "element == %d", element);
1423 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1424 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1428 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1429 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1430 (laser.num_beamers == 0 ||
1431 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1433 // element is incoming beamer or other element
1434 step_size = -step_size;
1439 if (IS_BEAMER(element))
1440 Debug("game:mm:DrawLaserExt",
1441 "start_edge == %d, laser.beamer_edge == %d",
1442 start_edge, laser.beamer_edge);
1445 LX += step_size * XS;
1446 LY += step_size * YS;
1448 else if (element != EL_EMPTY)
1457 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1462 void DrawLaser(int start_edge, int mode)
1464 // do not draw laser if fuse is off
1465 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1468 if (mode == DL_LASER_DISABLED)
1469 DeactivateLaserTargetElement();
1471 if (laser.num_edges - start_edge < 0)
1473 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1478 // check if laser is interrupted by beamer element
1479 if (laser.num_beamers > 0 &&
1480 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1482 if (mode == DL_LASER_ENABLED)
1485 int tmp_start_edge = start_edge;
1487 // draw laser segments forward from the start to the last beamer
1488 for (i = 0; i < laser.num_beamers; i++)
1490 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1492 if (tmp_num_edges <= 0)
1496 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1497 i, laser.beamer_edge[i], tmp_start_edge);
1500 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1502 tmp_start_edge = laser.beamer_edge[i];
1505 // draw last segment from last beamer to the end
1506 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1512 int last_num_edges = laser.num_edges;
1513 int num_beamers = laser.num_beamers;
1515 // delete laser segments backward from the end to the first beamer
1516 for (i = num_beamers - 1; i >= 0; i--)
1518 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1520 if (laser.beamer_edge[i] - start_edge <= 0)
1523 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1525 last_num_edges = laser.beamer_edge[i];
1526 laser.num_beamers--;
1530 if (last_num_edges - start_edge <= 0)
1531 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1532 last_num_edges, start_edge);
1535 // special case when rotating first beamer: delete laser edge on beamer
1536 // (but do not start scanning on previous edge to prevent mirror sound)
1537 if (last_num_edges - start_edge == 1 && start_edge > 0)
1538 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1540 // delete first segment from start to the first beamer
1541 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1546 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1549 game_mm.laser_enabled = mode;
1552 void DrawLaser_MM(void)
1554 DrawLaser(0, game_mm.laser_enabled);
1557 static boolean HitElement(int element, int hit_mask)
1559 if (HitOnlyAnEdge(hit_mask))
1562 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1563 element = MovingOrBlocked2Element_MM(ELX, ELY);
1566 Debug("game:mm:HitElement", "(1): element == %d", element);
1570 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1571 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1574 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1578 AddDamagedField(ELX, ELY);
1580 // this is more precise: check if laser would go through the center
1581 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1585 // prevent cutting through laser emitter with laser beam
1586 if (IS_LASER(element))
1589 // skip the whole element before continuing the scan
1597 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1599 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1601 /* skipping scan positions to the right and down skips one scan
1602 position too much, because this is only the top left scan position
1603 of totally four scan positions (plus one to the right, one to the
1604 bottom and one to the bottom right) */
1605 /* ... but only roll back scan position if more than one step done */
1615 Debug("game:mm:HitElement", "(2): element == %d", element);
1618 if (LX + 5 * XS < 0 ||
1628 Debug("game:mm:HitElement", "(3): element == %d", element);
1631 if (IS_POLAR(element) &&
1632 ((element - EL_POLAR_START) % 2 ||
1633 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1635 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1637 laser.num_damages--;
1642 if (IS_POLAR_CROSS(element) &&
1643 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1645 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1647 laser.num_damages--;
1652 if (!IS_BEAMER(element) &&
1653 !IS_FIBRE_OPTIC(element) &&
1654 !IS_GRID_WOOD(element) &&
1655 element != EL_FUEL_EMPTY)
1658 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1659 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1661 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1664 LX = ELX * TILEX + 14;
1665 LY = ELY * TILEY + 14;
1667 AddLaserEdge(LX, LY);
1670 if (IS_MIRROR(element) ||
1671 IS_MIRROR_FIXED(element) ||
1672 IS_POLAR(element) ||
1673 IS_POLAR_CROSS(element) ||
1674 IS_DF_MIRROR(element) ||
1675 IS_DF_MIRROR_AUTO(element) ||
1676 element == EL_PRISM ||
1677 element == EL_REFRACTOR)
1679 int current_angle = laser.current_angle;
1682 laser.num_damages--;
1684 AddDamagedField(ELX, ELY);
1686 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1689 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1691 if (IS_MIRROR(element) ||
1692 IS_MIRROR_FIXED(element) ||
1693 IS_DF_MIRROR(element) ||
1694 IS_DF_MIRROR_AUTO(element))
1695 laser.current_angle = get_mirrored_angle(laser.current_angle,
1696 get_element_angle(element));
1698 if (element == EL_PRISM || element == EL_REFRACTOR)
1699 laser.current_angle = RND(16);
1701 XS = 2 * Step[laser.current_angle].x;
1702 YS = 2 * Step[laser.current_angle].y;
1704 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1709 LX += step_size * XS;
1710 LY += step_size * YS;
1712 // draw sparkles on mirror
1713 if ((IS_MIRROR(element) ||
1714 IS_MIRROR_FIXED(element) ||
1715 element == EL_PRISM) &&
1716 current_angle != laser.current_angle)
1718 MovDelay[ELX][ELY] = 11; // start animation
1721 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1722 current_angle != laser.current_angle)
1723 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1726 (get_opposite_angle(laser.current_angle) ==
1727 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1729 return (laser.overloaded ? TRUE : FALSE);
1732 if (element == EL_FUEL_FULL)
1734 laser.stops_inside_element = TRUE;
1739 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1741 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1743 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1744 element == EL_MINE ? EL_MINE_ACTIVE :
1745 EL_GRAY_BALL_ACTIVE);
1747 GfxFrame[ELX][ELY] = 0; // restart animation
1749 laser.dest_element_last = Tile[ELX][ELY];
1750 laser.dest_element_last_x = ELX;
1751 laser.dest_element_last_y = ELY;
1753 if (element == EL_MINE)
1754 laser.overloaded = TRUE;
1757 if (element == EL_KETTLE ||
1758 element == EL_CELL ||
1759 element == EL_KEY ||
1760 element == EL_LIGHTBALL ||
1761 element == EL_PACMAN ||
1762 IS_PACMAN(element) ||
1763 IS_ENVELOPE(element))
1765 if (!IS_PACMAN(element) &&
1766 !IS_ENVELOPE(element))
1769 if (element == EL_PACMAN)
1772 if (element == EL_KETTLE || element == EL_CELL)
1774 if (game_mm.kettles_still_needed > 0)
1775 game_mm.kettles_still_needed--;
1777 game.snapshot.collected_item = TRUE;
1779 if (game_mm.kettles_still_needed == 0)
1783 DrawLaser(0, DL_LASER_ENABLED);
1786 else if (element == EL_KEY)
1790 else if (IS_PACMAN(element))
1792 DeletePacMan(ELX, ELY);
1794 else if (IS_ENVELOPE(element))
1796 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1799 RaiseScoreElement_MM(element);
1804 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1806 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1808 DrawLaser(0, DL_LASER_ENABLED);
1810 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1812 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1813 game_mm.lights_still_needed--;
1817 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1818 game_mm.lights_still_needed++;
1821 DrawField_MM(ELX, ELY);
1822 DrawLaser(0, DL_LASER_ENABLED);
1827 laser.stops_inside_element = TRUE;
1833 Debug("game:mm:HitElement", "(4): element == %d", element);
1836 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1837 laser.num_beamers < MAX_NUM_BEAMERS &&
1838 laser.beamer[BEAMER_NR(element)][1].num)
1840 int beamer_angle = get_element_angle(element);
1841 int beamer_nr = BEAMER_NR(element);
1845 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1848 laser.num_damages--;
1850 if (IS_FIBRE_OPTIC(element) ||
1851 laser.current_angle == get_opposite_angle(beamer_angle))
1855 LX = ELX * TILEX + 14;
1856 LY = ELY * TILEY + 14;
1858 AddLaserEdge(LX, LY);
1859 AddDamagedField(ELX, ELY);
1861 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1864 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1866 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1867 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1868 ELX = laser.beamer[beamer_nr][pos].x;
1869 ELY = laser.beamer[beamer_nr][pos].y;
1870 LX = ELX * TILEX + 14;
1871 LY = ELY * TILEY + 14;
1873 if (IS_BEAMER(element))
1875 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1876 XS = 2 * Step[laser.current_angle].x;
1877 YS = 2 * Step[laser.current_angle].y;
1880 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1882 AddLaserEdge(LX, LY);
1883 AddDamagedField(ELX, ELY);
1885 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1888 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1890 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1895 LX += step_size * XS;
1896 LY += step_size * YS;
1898 laser.num_beamers++;
1907 static boolean HitOnlyAnEdge(int hit_mask)
1909 // check if the laser hit only the edge of an element and, if so, go on
1912 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1916 if ((hit_mask == HIT_MASK_TOPLEFT ||
1917 hit_mask == HIT_MASK_TOPRIGHT ||
1918 hit_mask == HIT_MASK_BOTTOMLEFT ||
1919 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1920 laser.current_angle % 4) // angle is not 90°
1924 if (hit_mask == HIT_MASK_TOPLEFT)
1929 else if (hit_mask == HIT_MASK_TOPRIGHT)
1934 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1939 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1945 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1951 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1958 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1964 static boolean HitPolarizer(int element, int hit_mask)
1966 if (HitOnlyAnEdge(hit_mask))
1969 if (IS_DF_GRID(element))
1971 int grid_angle = get_element_angle(element);
1974 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1975 grid_angle, laser.current_angle);
1978 AddLaserEdge(LX, LY);
1979 AddDamagedField(ELX, ELY);
1982 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1984 if (laser.current_angle == grid_angle ||
1985 laser.current_angle == get_opposite_angle(grid_angle))
1987 // skip the whole element before continuing the scan
1993 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1995 if (LX/TILEX > ELX || LY/TILEY > ELY)
1997 /* skipping scan positions to the right and down skips one scan
1998 position too much, because this is only the top left scan position
1999 of totally four scan positions (plus one to the right, one to the
2000 bottom and one to the bottom right) */
2006 AddLaserEdge(LX, LY);
2012 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2014 LX / TILEX, LY / TILEY,
2015 LX % TILEX, LY % TILEY);
2020 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2022 return HitReflectingWalls(element, hit_mask);
2026 return HitAbsorbingWalls(element, hit_mask);
2029 else if (IS_GRID_STEEL(element))
2031 // may be required if graphics for steel grid redefined
2032 AddDamagedField(ELX, ELY);
2034 return HitReflectingWalls(element, hit_mask);
2036 else // IS_GRID_WOOD
2038 // may be required if graphics for wooden grid redefined
2039 AddDamagedField(ELX, ELY);
2041 return HitAbsorbingWalls(element, hit_mask);
2047 static boolean HitBlock(int element, int hit_mask)
2049 boolean check = FALSE;
2051 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2052 game_mm.num_keys == 0)
2055 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2058 int ex = ELX * TILEX + 14;
2059 int ey = ELY * TILEY + 14;
2063 for (i = 1; i < 32; i++)
2068 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2073 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2074 return HitAbsorbingWalls(element, hit_mask);
2078 AddLaserEdge(LX - XS, LY - YS);
2079 AddDamagedField(ELX, ELY);
2082 Box[ELX][ELY] = laser.num_edges;
2084 return HitReflectingWalls(element, hit_mask);
2087 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2089 int xs = XS / 2, ys = YS / 2;
2091 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2092 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2094 laser.overloaded = (element == EL_GATE_STONE);
2099 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2100 (hit_mask == HIT_MASK_TOP ||
2101 hit_mask == HIT_MASK_LEFT ||
2102 hit_mask == HIT_MASK_RIGHT ||
2103 hit_mask == HIT_MASK_BOTTOM))
2104 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2105 hit_mask == HIT_MASK_BOTTOM),
2106 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2107 hit_mask == HIT_MASK_RIGHT));
2108 AddLaserEdge(LX, LY);
2114 if (element == EL_GATE_STONE && Box[ELX][ELY])
2116 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2128 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2130 int xs = XS / 2, ys = YS / 2;
2132 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2133 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2135 laser.overloaded = (element == EL_BLOCK_STONE);
2140 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2141 (hit_mask == HIT_MASK_TOP ||
2142 hit_mask == HIT_MASK_LEFT ||
2143 hit_mask == HIT_MASK_RIGHT ||
2144 hit_mask == HIT_MASK_BOTTOM))
2145 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2146 hit_mask == HIT_MASK_BOTTOM),
2147 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2148 hit_mask == HIT_MASK_RIGHT));
2149 AddDamagedField(ELX, ELY);
2151 LX = ELX * TILEX + 14;
2152 LY = ELY * TILEY + 14;
2154 AddLaserEdge(LX, LY);
2156 laser.stops_inside_element = TRUE;
2164 static boolean HitLaserSource(int element, int hit_mask)
2166 if (HitOnlyAnEdge(hit_mask))
2169 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2171 laser.overloaded = TRUE;
2176 static boolean HitLaserDestination(int element, int hit_mask)
2178 if (HitOnlyAnEdge(hit_mask))
2181 if (element != EL_EXIT_OPEN &&
2182 !(IS_RECEIVER(element) &&
2183 game_mm.kettles_still_needed == 0 &&
2184 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2186 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2191 if (IS_RECEIVER(element) ||
2192 (IS_22_5_ANGLE(laser.current_angle) &&
2193 (ELX != (LX + 6 * XS) / TILEX ||
2194 ELY != (LY + 6 * YS) / TILEY ||
2203 LX = ELX * TILEX + 14;
2204 LY = ELY * TILEY + 14;
2206 laser.stops_inside_element = TRUE;
2209 AddLaserEdge(LX, LY);
2210 AddDamagedField(ELX, ELY);
2212 if (game_mm.lights_still_needed == 0)
2214 game_mm.level_solved = TRUE;
2216 SetTileCursorActive(FALSE);
2222 static boolean HitReflectingWalls(int element, int hit_mask)
2224 // check if laser hits side of a wall with an angle that is not 90°
2225 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2226 hit_mask == HIT_MASK_LEFT ||
2227 hit_mask == HIT_MASK_RIGHT ||
2228 hit_mask == HIT_MASK_BOTTOM))
2230 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2235 if (!IS_DF_GRID(element))
2236 AddLaserEdge(LX, LY);
2238 // check if laser hits wall with an angle of 45°
2239 if (!IS_22_5_ANGLE(laser.current_angle))
2241 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2244 laser.current_angle = get_mirrored_angle(laser.current_angle,
2247 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2250 laser.current_angle = get_mirrored_angle(laser.current_angle,
2254 AddLaserEdge(LX, LY);
2256 XS = 2 * Step[laser.current_angle].x;
2257 YS = 2 * Step[laser.current_angle].y;
2261 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2263 laser.current_angle = get_mirrored_angle(laser.current_angle,
2268 if (!IS_DF_GRID(element))
2269 AddLaserEdge(LX, LY);
2274 if (!IS_DF_GRID(element))
2275 AddLaserEdge(LX, LY + YS / 2);
2278 if (!IS_DF_GRID(element))
2279 AddLaserEdge(LX, LY);
2282 YS = 2 * Step[laser.current_angle].y;
2286 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2288 laser.current_angle = get_mirrored_angle(laser.current_angle,
2293 if (!IS_DF_GRID(element))
2294 AddLaserEdge(LX, LY);
2299 if (!IS_DF_GRID(element))
2300 AddLaserEdge(LX + XS / 2, LY);
2303 if (!IS_DF_GRID(element))
2304 AddLaserEdge(LX, LY);
2307 XS = 2 * Step[laser.current_angle].x;
2313 // reflection at the edge of reflecting DF style wall
2314 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2316 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2317 hit_mask == HIT_MASK_TOPRIGHT) ||
2318 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2319 hit_mask == HIT_MASK_TOPLEFT) ||
2320 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2321 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2322 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2323 hit_mask == HIT_MASK_BOTTOMRIGHT))
2326 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2327 ANG_MIRROR_135 : ANG_MIRROR_45);
2329 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2331 AddDamagedField(ELX, ELY);
2332 AddLaserEdge(LX, LY);
2334 laser.current_angle = get_mirrored_angle(laser.current_angle,
2342 AddLaserEdge(LX, LY);
2348 // reflection inside an edge of reflecting DF style wall
2349 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2351 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2352 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2353 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2354 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2355 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2356 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2357 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2358 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2361 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2362 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2363 ANG_MIRROR_135 : ANG_MIRROR_45);
2365 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2368 AddDamagedField(ELX, ELY);
2371 AddLaserEdge(LX - XS, LY - YS);
2372 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2373 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2375 laser.current_angle = get_mirrored_angle(laser.current_angle,
2383 AddLaserEdge(LX, LY);
2389 // check if laser hits DF style wall with an angle of 90°
2390 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2392 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2393 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2394 (IS_VERT_ANGLE(laser.current_angle) &&
2395 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2397 // laser at last step touched nothing or the same side of the wall
2398 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2400 AddDamagedField(ELX, ELY);
2407 last_hit_mask = hit_mask;
2414 if (!HitOnlyAnEdge(hit_mask))
2416 laser.overloaded = TRUE;
2424 static boolean HitAbsorbingWalls(int element, int hit_mask)
2426 if (HitOnlyAnEdge(hit_mask))
2430 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2432 AddLaserEdge(LX - XS, LY - YS);
2439 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2441 AddLaserEdge(LX - XS, LY - YS);
2447 if (IS_WALL_WOOD(element) ||
2448 IS_DF_WALL_WOOD(element) ||
2449 IS_GRID_WOOD(element) ||
2450 IS_GRID_WOOD_FIXED(element) ||
2451 IS_GRID_WOOD_AUTO(element) ||
2452 element == EL_FUSE_ON ||
2453 element == EL_BLOCK_WOOD ||
2454 element == EL_GATE_WOOD)
2456 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2461 if (IS_WALL_ICE(element))
2467 // check if laser hit adjacent edges of two diagonal tiles
2468 if (ELX != lx / TILEX)
2470 if (ELY != ly / TILEY)
2473 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2474 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2476 // check if laser hits wall with an angle of 90°
2477 if (IS_90_ANGLE(laser.current_angle))
2478 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2480 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2484 for (i = 0; i < 4; i++)
2486 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2487 mask = 15 - (8 >> i);
2488 else if (ABS(XS) == 4 &&
2490 (XS > 0) == (i % 2) &&
2491 (YS < 0) == (i / 2))
2492 mask = 3 + (i / 2) * 9;
2493 else if (ABS(YS) == 4 &&
2495 (XS < 0) == (i % 2) &&
2496 (YS > 0) == (i / 2))
2497 mask = 5 + (i % 2) * 5;
2501 laser.wall_mask = mask;
2503 else if (IS_WALL_AMOEBA(element))
2505 int elx = (LX - 2 * XS) / TILEX;
2506 int ely = (LY - 2 * YS) / TILEY;
2507 int element2 = Tile[elx][ely];
2510 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2512 laser.dest_element = EL_EMPTY;
2520 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2521 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2523 if (IS_90_ANGLE(laser.current_angle))
2524 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2526 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2528 laser.wall_mask = mask;
2534 static void OpenExit(int x, int y)
2538 if (!MovDelay[x][y]) // next animation frame
2539 MovDelay[x][y] = 4 * delay;
2541 if (MovDelay[x][y]) // wait some time before next frame
2546 phase = MovDelay[x][y] / delay;
2548 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2549 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2551 if (!MovDelay[x][y])
2553 Tile[x][y] = EL_EXIT_OPEN;
2559 static void OpenGrayBall(int x, int y)
2563 if (!MovDelay[x][y]) // next animation frame
2565 if (IS_WALL(Store[x][y]))
2567 DrawWalls_MM(x, y, Store[x][y]);
2569 // copy wall tile to spare bitmap for "melting" animation
2570 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2571 TILEX, TILEY, x * TILEX, y * TILEY);
2573 DrawElement_MM(x, y, EL_GRAY_BALL);
2576 MovDelay[x][y] = 50 * delay;
2579 if (MovDelay[x][y]) // wait some time before next frame
2583 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2587 int dx = RND(26), dy = RND(26);
2589 if (IS_WALL(Store[x][y]))
2591 // copy wall tile from spare bitmap for "melting" animation
2592 bitmap = bitmap_db_field;
2598 int graphic = el2gfx(Store[x][y]);
2600 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2603 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2604 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2606 laser.redraw = TRUE;
2608 MarkTileDirty(x, y);
2611 if (!MovDelay[x][y])
2613 Tile[x][y] = Store[x][y];
2614 Store[x][y] = Store2[x][y] = 0;
2615 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2617 InitField(x, y, FALSE);
2620 ScanLaser_FromLastMirror();
2625 static void OpenEnvelope(int x, int y)
2627 int num_frames = 8; // seven frames plus final empty space
2629 if (!MovDelay[x][y]) // next animation frame
2630 MovDelay[x][y] = num_frames;
2632 if (MovDelay[x][y]) // wait some time before next frame
2634 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2638 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2640 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2641 int frame = num_frames - MovDelay[x][y] - 1;
2643 DrawGraphicAnimation_MM(x, y, graphic, frame);
2645 laser.redraw = TRUE;
2648 if (MovDelay[x][y] == 0)
2650 Tile[x][y] = EL_EMPTY;
2656 ShowEnvelope_MM(nr);
2661 static void MeltIce(int x, int y)
2666 if (!MovDelay[x][y]) // next animation frame
2667 MovDelay[x][y] = frames * delay;
2669 if (MovDelay[x][y]) // wait some time before next frame
2672 int wall_mask = Store2[x][y];
2673 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2676 phase = frames - MovDelay[x][y] / delay - 1;
2678 if (!MovDelay[x][y])
2680 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2681 Store[x][y] = Store2[x][y] = 0;
2683 DrawWalls_MM(x, y, Tile[x][y]);
2685 if (Tile[x][y] == EL_WALL_ICE_BASE)
2686 Tile[x][y] = EL_EMPTY;
2688 ScanLaser_FromLastMirror();
2690 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2692 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2694 laser.redraw = TRUE;
2699 static void GrowAmoeba(int x, int y)
2704 if (!MovDelay[x][y]) // next animation frame
2705 MovDelay[x][y] = frames * delay;
2707 if (MovDelay[x][y]) // wait some time before next frame
2710 int wall_mask = Store2[x][y];
2711 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2714 phase = MovDelay[x][y] / delay;
2716 if (!MovDelay[x][y])
2718 Tile[x][y] = real_element;
2719 Store[x][y] = Store2[x][y] = 0;
2721 DrawWalls_MM(x, y, Tile[x][y]);
2722 DrawLaser(0, DL_LASER_ENABLED);
2724 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2726 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2731 static void DrawFieldAnimated_MM(int x, int y)
2735 laser.redraw = TRUE;
2738 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2740 int element = Tile[x][y];
2741 int graphic = el2gfx(element);
2743 if (!getGraphicInfo_NewFrame(x, y, graphic))
2748 laser.redraw = TRUE;
2751 static void DrawFieldTwinkle(int x, int y)
2753 if (MovDelay[x][y] != 0) // wait some time before next frame
2759 if (MovDelay[x][y] != 0)
2761 int graphic = IMG_TWINKLE_WHITE;
2762 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2764 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2767 laser.redraw = TRUE;
2771 static void Explode_MM(int x, int y, int phase, int mode)
2773 int num_phase = 9, delay = 2;
2774 int last_phase = num_phase * delay;
2775 int half_phase = (num_phase / 2) * delay;
2778 laser.redraw = TRUE;
2780 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2782 center_element = Tile[x][y];
2784 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2786 // put moving element to center field (and let it explode there)
2787 center_element = MovingOrBlocked2Element_MM(x, y);
2788 RemoveMovingField_MM(x, y);
2790 Tile[x][y] = center_element;
2793 if (center_element != EL_GRAY_BALL_ACTIVE)
2794 Store[x][y] = EL_EMPTY;
2795 Store2[x][y] = center_element;
2797 Tile[x][y] = EL_EXPLODING_OPAQUE;
2799 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2800 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2801 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2804 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2806 ExplodePhase[x][y] = 1;
2812 GfxFrame[x][y] = 0; // restart explosion animation
2814 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2816 center_element = Store2[x][y];
2818 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2820 Tile[x][y] = EL_EXPLODING_TRANSP;
2822 if (x == ELX && y == ELY)
2826 if (phase == last_phase)
2828 if (center_element == EL_BOMB_ACTIVE)
2830 DrawLaser(0, DL_LASER_DISABLED);
2833 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2835 laser.overloaded = FALSE;
2837 else if (IS_MCDUFFIN(center_element) || IS_LASER(center_element))
2839 GameOver_MM(GAME_OVER_BOMB);
2842 Tile[x][y] = Store[x][y];
2844 Store[x][y] = Store2[x][y] = 0;
2845 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2847 InitField(x, y, FALSE);
2850 if (center_element == EL_GRAY_BALL_ACTIVE)
2851 ScanLaser_FromLastMirror();
2853 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2855 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2856 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2858 DrawGraphicAnimation_MM(x, y, graphic, frame);
2860 MarkTileDirty(x, y);
2864 static void Bang_MM(int x, int y)
2866 int element = Tile[x][y];
2868 if (IS_PACMAN(element))
2869 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2870 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2871 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2872 else if (element == EL_KEY)
2873 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2875 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2877 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2880 static void TurnRound(int x, int y)
2892 { 0, 0 }, { 0, 0 }, { 0, 0 },
2897 int left, right, back;
2901 { MV_DOWN, MV_UP, MV_RIGHT },
2902 { MV_UP, MV_DOWN, MV_LEFT },
2904 { MV_LEFT, MV_RIGHT, MV_DOWN },
2905 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2906 { MV_RIGHT, MV_LEFT, MV_UP }
2909 int element = Tile[x][y];
2910 int old_move_dir = MovDir[x][y];
2911 int right_dir = turn[old_move_dir].right;
2912 int back_dir = turn[old_move_dir].back;
2913 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2914 int right_x = x + right_dx, right_y = y + right_dy;
2916 if (element == EL_PACMAN)
2918 boolean can_turn_right = FALSE;
2920 if (IN_LEV_FIELD(right_x, right_y) &&
2921 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2922 can_turn_right = TRUE;
2925 MovDir[x][y] = right_dir;
2927 MovDir[x][y] = back_dir;
2933 static void StartMoving_MM(int x, int y)
2935 int element = Tile[x][y];
2940 if (CAN_MOVE(element))
2944 if (MovDelay[x][y]) // wait some time before next movement
2952 // now make next step
2954 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2956 if (element == EL_PACMAN &&
2957 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2958 !ObjHit(newx, newy, HIT_POS_CENTER))
2960 Store[newx][newy] = Tile[newx][newy];
2961 Tile[newx][newy] = EL_EMPTY;
2963 DrawField_MM(newx, newy);
2965 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2966 ObjHit(newx, newy, HIT_POS_CENTER))
2968 // object was running against a wall
2975 InitMovingField_MM(x, y, MovDir[x][y]);
2979 ContinueMoving_MM(x, y);
2982 static void ContinueMoving_MM(int x, int y)
2984 int element = Tile[x][y];
2985 int direction = MovDir[x][y];
2986 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2987 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2988 int horiz_move = (dx!=0);
2989 int newx = x + dx, newy = y + dy;
2990 int step = (horiz_move ? dx : dy) * TILEX / 8;
2992 MovPos[x][y] += step;
2994 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2996 Tile[x][y] = EL_EMPTY;
2997 Tile[newx][newy] = element;
2999 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3000 MovDelay[newx][newy] = 0;
3002 if (!CAN_MOVE(element))
3003 MovDir[newx][newy] = 0;
3006 DrawField_MM(newx, newy);
3008 Stop[newx][newy] = TRUE;
3010 if (element == EL_PACMAN)
3012 if (Store[newx][newy] == EL_BOMB)
3013 Bang_MM(newx, newy);
3015 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3016 (LX + 2 * XS) / TILEX == newx &&
3017 (LY + 2 * YS) / TILEY == newy)
3024 else // still moving on
3029 laser.redraw = TRUE;
3032 boolean ClickElement(int x, int y, int button)
3034 static DelayCounter click_delay = { CLICK_DELAY };
3035 static boolean new_button = TRUE;
3036 boolean element_clicked = FALSE;
3041 // initialize static variables
3042 click_delay.count = 0;
3043 click_delay.value = CLICK_DELAY;
3049 // do not rotate objects hit by the laser after the game was solved
3050 if (game_mm.level_solved && Hit[x][y])
3053 if (button == MB_RELEASED)
3056 click_delay.value = CLICK_DELAY;
3058 // release eventually hold auto-rotating mirror
3059 RotateMirror(x, y, MB_RELEASED);
3064 if (!FrameReached(&click_delay) && !new_button)
3067 if (button == MB_MIDDLEBUTTON) // middle button has no function
3070 if (!IN_LEV_FIELD(x, y))
3073 if (Tile[x][y] == EL_EMPTY)
3076 element = Tile[x][y];
3078 if (IS_MIRROR(element) ||
3079 IS_BEAMER(element) ||
3080 IS_POLAR(element) ||
3081 IS_POLAR_CROSS(element) ||
3082 IS_DF_MIRROR(element) ||
3083 IS_DF_MIRROR_AUTO(element))
3085 RotateMirror(x, y, button);
3087 element_clicked = TRUE;
3089 else if (IS_MCDUFFIN(element))
3091 if (!laser.fuse_off)
3093 DrawLaser(0, DL_LASER_DISABLED);
3100 element = get_rotated_element(element, BUTTON_ROTATION(button));
3101 laser.start_angle = get_element_angle(element);
3105 Tile[x][y] = element;
3112 if (!laser.fuse_off)
3115 element_clicked = TRUE;
3117 else if (element == EL_FUSE_ON && laser.fuse_off)
3119 if (x != laser.fuse_x || y != laser.fuse_y)
3122 laser.fuse_off = FALSE;
3123 laser.fuse_x = laser.fuse_y = -1;
3125 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3128 element_clicked = TRUE;
3130 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3132 laser.fuse_off = TRUE;
3135 laser.overloaded = FALSE;
3137 DrawLaser(0, DL_LASER_DISABLED);
3138 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3140 element_clicked = TRUE;
3142 else if (element == EL_LIGHTBALL)
3145 RaiseScoreElement_MM(element);
3146 DrawLaser(0, DL_LASER_ENABLED);
3148 element_clicked = TRUE;
3150 else if (IS_ENVELOPE(element))
3152 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3154 element_clicked = TRUE;
3157 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3160 return element_clicked;
3163 static void RotateMirror(int x, int y, int button)
3165 if (button == MB_RELEASED)
3167 // release eventually hold auto-rotating mirror
3174 if (IS_MIRROR(Tile[x][y]) ||
3175 IS_POLAR_CROSS(Tile[x][y]) ||
3176 IS_POLAR(Tile[x][y]) ||
3177 IS_BEAMER(Tile[x][y]) ||
3178 IS_DF_MIRROR(Tile[x][y]) ||
3179 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3180 IS_GRID_WOOD_AUTO(Tile[x][y]))
3182 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3184 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3186 if (button == MB_LEFTBUTTON)
3188 // left mouse button only for manual adjustment, no auto-rotating;
3189 // freeze mirror for until mouse button released
3193 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3195 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3199 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3201 int edge = Hit[x][y];
3207 DrawLaser(edge - 1, DL_LASER_DISABLED);
3211 else if (ObjHit(x, y, HIT_POS_CENTER))
3213 int edge = Hit[x][y];
3217 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3222 DrawLaser(edge - 1, DL_LASER_DISABLED);
3229 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3234 if ((IS_BEAMER(Tile[x][y]) ||
3235 IS_POLAR(Tile[x][y]) ||
3236 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3238 if (IS_BEAMER(Tile[x][y]))
3241 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3242 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3255 DrawLaser(0, DL_LASER_ENABLED);
3259 static void AutoRotateMirrors(void)
3263 if (!FrameReached(&rotate_delay))
3266 for (x = 0; x < lev_fieldx; x++)
3268 for (y = 0; y < lev_fieldy; y++)
3270 int element = Tile[x][y];
3272 // do not rotate objects hit by the laser after the game was solved
3273 if (game_mm.level_solved && Hit[x][y])
3276 if (IS_DF_MIRROR_AUTO(element) ||
3277 IS_GRID_WOOD_AUTO(element) ||
3278 IS_GRID_STEEL_AUTO(element) ||
3279 element == EL_REFRACTOR)
3280 RotateMirror(x, y, MB_RIGHTBUTTON);
3285 static boolean ObjHit(int obx, int oby, int bits)
3292 if (bits & HIT_POS_CENTER)
3294 if (CheckLaserPixel(cSX + obx + 15,
3299 if (bits & HIT_POS_EDGE)
3301 for (i = 0; i < 4; i++)
3302 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3303 cSY + oby + 31 * (i / 2)))
3307 if (bits & HIT_POS_BETWEEN)
3309 for (i = 0; i < 4; i++)
3310 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3311 cSY + 4 + oby + 22 * (i / 2)))
3318 static void DeletePacMan(int px, int py)
3324 if (game_mm.num_pacman <= 1)
3326 game_mm.num_pacman = 0;
3330 for (i = 0; i < game_mm.num_pacman; i++)
3331 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3334 game_mm.num_pacman--;
3336 for (j = i; j < game_mm.num_pacman; j++)
3338 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3339 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3340 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3344 static void GameActions_MM_Ext(void)
3351 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3354 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3356 element = Tile[x][y];
3358 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3359 StartMoving_MM(x, y);
3360 else if (IS_MOVING(x, y))
3361 ContinueMoving_MM(x, y);
3362 else if (IS_EXPLODING(element))
3363 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3364 else if (element == EL_EXIT_OPENING)
3366 else if (element == EL_GRAY_BALL_OPENING)
3368 else if (IS_ENVELOPE_OPENING(element))
3370 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3372 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3374 else if (IS_MIRROR(element) ||
3375 IS_MIRROR_FIXED(element) ||
3376 element == EL_PRISM)
3377 DrawFieldTwinkle(x, y);
3378 else if (element == EL_GRAY_BALL_ACTIVE ||
3379 element == EL_BOMB_ACTIVE ||
3380 element == EL_MINE_ACTIVE)
3381 DrawFieldAnimated_MM(x, y);
3382 else if (!IS_BLOCKED(x, y))
3383 DrawFieldAnimatedIfNeeded_MM(x, y);
3386 AutoRotateMirrors();
3389 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3391 // redraw after Explode_MM() ...
3393 DrawLaser(0, DL_LASER_ENABLED);
3394 laser.redraw = FALSE;
3399 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3403 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3405 DrawLaser(0, DL_LASER_DISABLED);
3410 // skip all following game actions if game is over
3411 if (game_mm.game_over)
3414 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3418 GameOver_MM(GAME_OVER_NO_ENERGY);
3423 if (FrameReached(&energy_delay))
3425 if (game_mm.energy_left > 0)
3426 game_mm.energy_left--;
3428 // when out of energy, wait another frame to play "out of time" sound
3431 element = laser.dest_element;
3434 if (element != Tile[ELX][ELY])
3436 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3437 element, Tile[ELX][ELY]);
3441 if (!laser.overloaded && laser.overload_value == 0 &&
3442 element != EL_BOMB &&
3443 element != EL_BOMB_ACTIVE &&
3444 element != EL_MINE &&
3445 element != EL_MINE_ACTIVE &&
3446 element != EL_GRAY_BALL &&
3447 element != EL_GRAY_BALL_ACTIVE &&
3448 element != EL_BLOCK_STONE &&
3449 element != EL_BLOCK_WOOD &&
3450 element != EL_FUSE_ON &&
3451 element != EL_FUEL_FULL &&
3452 !IS_WALL_ICE(element) &&
3453 !IS_WALL_AMOEBA(element))
3456 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3458 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3459 (!laser.overloaded && laser.overload_value > 0)) &&
3460 FrameReached(&overload_delay))
3462 if (laser.overloaded)
3463 laser.overload_value++;
3465 laser.overload_value--;
3467 if (game_mm.cheat_no_overload)
3469 laser.overloaded = FALSE;
3470 laser.overload_value = 0;
3473 game_mm.laser_overload_value = laser.overload_value;
3475 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3477 SetLaserColor(0xFF);
3479 DrawLaser(0, DL_LASER_ENABLED);
3482 if (!laser.overloaded)
3483 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3484 else if (setup.sound_loops)
3485 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3487 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3489 if (laser.overload_value == MAX_LASER_OVERLOAD)
3491 UpdateAndDisplayGameControlValues();
3495 GameOver_MM(GAME_OVER_OVERLOADED);
3506 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3508 if (game_mm.cheat_no_explosion)
3513 laser.dest_element = EL_EXPLODING_OPAQUE;
3518 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3520 laser.fuse_off = TRUE;
3524 DrawLaser(0, DL_LASER_DISABLED);
3525 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3528 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3530 if (!Store2[ELX][ELY]) // check if content element not yet determined
3532 int last_anim_random_frame = gfx.anim_random_frame;
3535 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3536 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3538 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3539 native_mm_level.ball_choice_mode, 0,
3540 game_mm.ball_choice_pos);
3542 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3543 gfx.anim_random_frame = last_anim_random_frame;
3545 game_mm.ball_choice_pos++;
3547 int new_element = native_mm_level.ball_content[element_pos];
3548 int new_element_base = map_wall_to_base_element(new_element);
3550 if (IS_WALL(new_element_base))
3552 // always use completely filled wall element
3553 new_element = new_element_base | 0x000f;
3555 else if (native_mm_level.rotate_ball_content &&
3556 get_num_elements(new_element) > 1)
3558 // randomly rotate newly created game element
3559 new_element = get_rotated_element(new_element, RND(16));
3562 Store[ELX][ELY] = new_element;
3563 Store2[ELX][ELY] = TRUE;
3566 if (native_mm_level.explode_ball)
3569 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3571 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3576 if (IS_WALL_ICE(element) && CT > 50)
3578 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3580 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3581 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3582 Store2[ELX][ELY] = laser.wall_mask;
3584 laser.dest_element = Tile[ELX][ELY];
3589 if (IS_WALL_AMOEBA(element) && CT > 60)
3592 int element2 = Tile[ELX][ELY];
3594 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3597 for (i = laser.num_damages - 1; i >= 0; i--)
3598 if (laser.damage[i].is_mirror)
3601 r = laser.num_edges;
3602 d = laser.num_damages;
3609 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3612 DrawLaser(0, DL_LASER_ENABLED);
3615 x = laser.damage[k1].x;
3616 y = laser.damage[k1].y;
3621 for (i = 0; i < 4; i++)
3623 if (laser.wall_mask & (1 << i))
3625 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3626 cSY + ELY * TILEY + 31 * (i / 2)))
3629 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3630 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3637 for (i = 0; i < 4; i++)
3639 if (laser.wall_mask & (1 << i))
3641 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3642 cSY + ELY * TILEY + 31 * (i / 2)))
3649 if (laser.num_beamers > 0 ||
3650 k1 < 1 || k2 < 4 || k3 < 4 ||
3651 CheckLaserPixel(cSX + ELX * TILEX + 14,
3652 cSY + ELY * TILEY + 14))
3654 laser.num_edges = r;
3655 laser.num_damages = d;
3657 DrawLaser(0, DL_LASER_DISABLED);
3660 Tile[ELX][ELY] = element | laser.wall_mask;
3662 int x = ELX, y = ELY;
3663 int wall_mask = laser.wall_mask;
3666 DrawLaser(0, DL_LASER_ENABLED);
3668 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3670 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3671 Store[x][y] = EL_WALL_AMOEBA_BASE;
3672 Store2[x][y] = wall_mask;
3677 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3678 laser.stops_inside_element && CT > native_mm_level.time_block)
3683 if (ABS(XS) > ABS(YS))
3690 for (i = 0; i < 4; i++)
3697 x = ELX + Step[k * 4].x;
3698 y = ELY + Step[k * 4].y;
3700 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3703 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3711 laser.overloaded = (element == EL_BLOCK_STONE);
3716 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3719 Tile[x][y] = element;
3721 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3724 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3726 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3727 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3735 if (element == EL_FUEL_FULL && CT > 10)
3737 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3738 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3740 for (i = start; i <= num_init_game_frames; i++)
3742 if (i == num_init_game_frames)
3743 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3744 else if (setup.sound_loops)
3745 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3747 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3749 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3751 UpdateAndDisplayGameControlValues();
3756 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3758 DrawField_MM(ELX, ELY);
3760 DrawLaser(0, DL_LASER_ENABLED);
3766 void GameActions_MM(struct MouseActionInfo action)
3768 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3769 boolean button_released = (action.button == MB_RELEASED);
3771 GameActions_MM_Ext();
3773 CheckSingleStepMode_MM(element_clicked, button_released);
3776 static void MovePacMen(void)
3778 int mx, my, ox, oy, nx, ny;
3782 if (++pacman_nr >= game_mm.num_pacman)
3785 game_mm.pacman[pacman_nr].dir--;
3787 for (l = 1; l < 5; l++)
3789 game_mm.pacman[pacman_nr].dir++;
3791 if (game_mm.pacman[pacman_nr].dir > 4)
3792 game_mm.pacman[pacman_nr].dir = 1;
3794 if (game_mm.pacman[pacman_nr].dir % 2)
3797 my = game_mm.pacman[pacman_nr].dir - 2;
3802 mx = 3 - game_mm.pacman[pacman_nr].dir;
3805 ox = game_mm.pacman[pacman_nr].x;
3806 oy = game_mm.pacman[pacman_nr].y;
3809 element = Tile[nx][ny];
3811 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3814 if (!IS_EATABLE4PACMAN(element))
3817 if (ObjHit(nx, ny, HIT_POS_CENTER))
3820 Tile[ox][oy] = EL_EMPTY;
3822 EL_PACMAN_RIGHT - 1 +
3823 (game_mm.pacman[pacman_nr].dir - 1 +
3824 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3826 game_mm.pacman[pacman_nr].x = nx;
3827 game_mm.pacman[pacman_nr].y = ny;
3829 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3831 if (element != EL_EMPTY)
3833 int graphic = el2gfx(Tile[nx][ny]);
3838 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3841 ox = cSX + ox * TILEX;
3842 oy = cSY + oy * TILEY;
3844 for (i = 1; i < 33; i += 2)
3845 BlitBitmap(bitmap, window,
3846 src_x, src_y, TILEX, TILEY,
3847 ox + i * mx, oy + i * my);
3848 Ct = Ct + FrameCounter - CT;
3851 DrawField_MM(nx, ny);
3854 if (!laser.fuse_off)
3856 DrawLaser(0, DL_LASER_ENABLED);
3858 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3860 AddDamagedField(nx, ny);
3862 laser.damage[laser.num_damages - 1].edge = 0;
3866 if (element == EL_BOMB)
3867 DeletePacMan(nx, ny);
3869 if (IS_WALL_AMOEBA(element) &&
3870 (LX + 2 * XS) / TILEX == nx &&
3871 (LY + 2 * YS) / TILEY == ny)
3881 static void InitMovingField_MM(int x, int y, int direction)
3883 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3884 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3886 MovDir[x][y] = direction;
3887 MovDir[newx][newy] = direction;
3889 if (Tile[newx][newy] == EL_EMPTY)
3890 Tile[newx][newy] = EL_BLOCKED;
3893 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3895 int direction = MovDir[x][y];
3896 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3897 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3903 static void Blocked2Moving_MM(int x, int y,
3904 int *comes_from_x, int *comes_from_y)
3906 int oldx = x, oldy = y;
3907 int direction = MovDir[x][y];
3909 if (direction == MV_LEFT)
3911 else if (direction == MV_RIGHT)
3913 else if (direction == MV_UP)
3915 else if (direction == MV_DOWN)
3918 *comes_from_x = oldx;
3919 *comes_from_y = oldy;
3922 static int MovingOrBlocked2Element_MM(int x, int y)
3924 int element = Tile[x][y];
3926 if (element == EL_BLOCKED)
3930 Blocked2Moving_MM(x, y, &oldx, &oldy);
3932 return Tile[oldx][oldy];
3938 static void RemoveMovingField_MM(int x, int y)
3940 int oldx = x, oldy = y, newx = x, newy = y;
3942 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3945 if (IS_MOVING(x, y))
3947 Moving2Blocked_MM(x, y, &newx, &newy);
3948 if (Tile[newx][newy] != EL_BLOCKED)
3951 else if (Tile[x][y] == EL_BLOCKED)
3953 Blocked2Moving_MM(x, y, &oldx, &oldy);
3954 if (!IS_MOVING(oldx, oldy))
3958 Tile[oldx][oldy] = EL_EMPTY;
3959 Tile[newx][newy] = EL_EMPTY;
3960 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3961 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3963 DrawLevelField_MM(oldx, oldy);
3964 DrawLevelField_MM(newx, newy);
3967 static void RaiseScore_MM(int value)
3969 game_mm.score += value;
3972 void RaiseScoreElement_MM(int element)
3977 case EL_PACMAN_RIGHT:
3979 case EL_PACMAN_LEFT:
3980 case EL_PACMAN_DOWN:
3981 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3985 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3990 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3994 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4003 // ----------------------------------------------------------------------------
4004 // Mirror Magic game engine snapshot handling functions
4005 // ----------------------------------------------------------------------------
4007 void SaveEngineSnapshotValues_MM(void)
4011 engine_snapshot_mm.game_mm = game_mm;
4012 engine_snapshot_mm.laser = laser;
4014 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4016 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4018 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4019 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4020 engine_snapshot_mm.Box[x][y] = Box[x][y];
4021 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4025 engine_snapshot_mm.LX = LX;
4026 engine_snapshot_mm.LY = LY;
4027 engine_snapshot_mm.XS = XS;
4028 engine_snapshot_mm.YS = YS;
4029 engine_snapshot_mm.ELX = ELX;
4030 engine_snapshot_mm.ELY = ELY;
4031 engine_snapshot_mm.CT = CT;
4032 engine_snapshot_mm.Ct = Ct;
4034 engine_snapshot_mm.last_LX = last_LX;
4035 engine_snapshot_mm.last_LY = last_LY;
4036 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4037 engine_snapshot_mm.hold_x = hold_x;
4038 engine_snapshot_mm.hold_y = hold_y;
4039 engine_snapshot_mm.pacman_nr = pacman_nr;
4041 engine_snapshot_mm.rotate_delay = rotate_delay;
4042 engine_snapshot_mm.pacman_delay = pacman_delay;
4043 engine_snapshot_mm.energy_delay = energy_delay;
4044 engine_snapshot_mm.overload_delay = overload_delay;
4047 void LoadEngineSnapshotValues_MM(void)
4051 // stored engine snapshot buffers already restored at this point
4053 game_mm = engine_snapshot_mm.game_mm;
4054 laser = engine_snapshot_mm.laser;
4056 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4058 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4060 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4061 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4062 Box[x][y] = engine_snapshot_mm.Box[x][y];
4063 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4067 LX = engine_snapshot_mm.LX;
4068 LY = engine_snapshot_mm.LY;
4069 XS = engine_snapshot_mm.XS;
4070 YS = engine_snapshot_mm.YS;
4071 ELX = engine_snapshot_mm.ELX;
4072 ELY = engine_snapshot_mm.ELY;
4073 CT = engine_snapshot_mm.CT;
4074 Ct = engine_snapshot_mm.Ct;
4076 last_LX = engine_snapshot_mm.last_LX;
4077 last_LY = engine_snapshot_mm.last_LY;
4078 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4079 hold_x = engine_snapshot_mm.hold_x;
4080 hold_y = engine_snapshot_mm.hold_y;
4081 pacman_nr = engine_snapshot_mm.pacman_nr;
4083 rotate_delay = engine_snapshot_mm.rotate_delay;
4084 pacman_delay = engine_snapshot_mm.pacman_delay;
4085 energy_delay = engine_snapshot_mm.energy_delay;
4086 overload_delay = engine_snapshot_mm.overload_delay;
4088 RedrawPlayfield_MM();
4091 static int getAngleFromTouchDelta(int dx, int dy, int base)
4093 double pi = 3.141592653;
4094 double rad = atan2((double)-dy, (double)dx);
4095 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4096 double deg = rad2 * 180.0 / pi;
4098 return (int)(deg * base / 360.0 + 0.5) % base;
4101 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4103 // calculate start (source) position to be at the middle of the tile
4104 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4105 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4106 int dx = dst_mx - src_mx;
4107 int dy = dst_my - src_my;
4116 if (!IN_LEV_FIELD(x, y))
4119 element = Tile[x][y];
4121 if (!IS_MCDUFFIN(element) &&
4122 !IS_MIRROR(element) &&
4123 !IS_BEAMER(element) &&
4124 !IS_POLAR(element) &&
4125 !IS_POLAR_CROSS(element) &&
4126 !IS_DF_MIRROR(element))
4129 angle_old = get_element_angle(element);
4131 if (IS_MCDUFFIN(element))
4133 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4134 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4135 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4136 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4139 else if (IS_MIRROR(element) ||
4140 IS_DF_MIRROR(element))
4142 for (i = 0; i < laser.num_damages; i++)
4144 if (laser.damage[i].x == x &&
4145 laser.damage[i].y == y &&
4146 ObjHit(x, y, HIT_POS_CENTER))
4148 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4149 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4156 if (angle_new == -1)
4158 if (IS_MIRROR(element) ||
4159 IS_DF_MIRROR(element) ||
4163 if (IS_POLAR_CROSS(element))
4166 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4169 button = (angle_new == angle_old ? 0 :
4170 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4171 MB_LEFTBUTTON : MB_RIGHTBUTTON);