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_mm, 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_mm->width, drawto_mm->height);
648 ClearRectangle(laser_bitmap, 0, 0, drawto_mm->width, drawto_mm->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_mm, 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_mm, 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;
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 boolean has_laser = (x == laser.start_edge.x && y == laser.start_edge.y);
3093 if (has_laser && !laser.fuse_off)
3094 DrawLaser(0, DL_LASER_DISABLED);
3096 element = get_rotated_element(element, BUTTON_ROTATION(button));
3098 Tile[x][y] = element;
3103 laser.start_angle = get_element_angle(element);
3107 if (!laser.fuse_off)
3111 element_clicked = TRUE;
3113 else if (element == EL_FUSE_ON && laser.fuse_off)
3115 if (x != laser.fuse_x || y != laser.fuse_y)
3118 laser.fuse_off = FALSE;
3119 laser.fuse_x = laser.fuse_y = -1;
3121 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3124 element_clicked = TRUE;
3126 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3128 laser.fuse_off = TRUE;
3131 laser.overloaded = FALSE;
3133 DrawLaser(0, DL_LASER_DISABLED);
3134 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3136 element_clicked = TRUE;
3138 else if (element == EL_LIGHTBALL)
3141 RaiseScoreElement_MM(element);
3142 DrawLaser(0, DL_LASER_ENABLED);
3144 element_clicked = TRUE;
3146 else if (IS_ENVELOPE(element))
3148 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3150 element_clicked = TRUE;
3153 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3156 return element_clicked;
3159 static void RotateMirror(int x, int y, int button)
3161 if (button == MB_RELEASED)
3163 // release eventually hold auto-rotating mirror
3170 if (IS_MIRROR(Tile[x][y]) ||
3171 IS_POLAR_CROSS(Tile[x][y]) ||
3172 IS_POLAR(Tile[x][y]) ||
3173 IS_BEAMER(Tile[x][y]) ||
3174 IS_DF_MIRROR(Tile[x][y]) ||
3175 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3176 IS_GRID_WOOD_AUTO(Tile[x][y]))
3178 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3180 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3182 if (button == MB_LEFTBUTTON)
3184 // left mouse button only for manual adjustment, no auto-rotating;
3185 // freeze mirror for until mouse button released
3189 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3191 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3195 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3197 int edge = Hit[x][y];
3203 DrawLaser(edge - 1, DL_LASER_DISABLED);
3207 else if (ObjHit(x, y, HIT_POS_CENTER))
3209 int edge = Hit[x][y];
3213 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3218 DrawLaser(edge - 1, DL_LASER_DISABLED);
3225 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3230 if ((IS_BEAMER(Tile[x][y]) ||
3231 IS_POLAR(Tile[x][y]) ||
3232 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3234 if (IS_BEAMER(Tile[x][y]))
3237 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3238 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3251 DrawLaser(0, DL_LASER_ENABLED);
3255 static void AutoRotateMirrors(void)
3259 if (!FrameReached(&rotate_delay))
3262 for (x = 0; x < lev_fieldx; x++)
3264 for (y = 0; y < lev_fieldy; y++)
3266 int element = Tile[x][y];
3268 // do not rotate objects hit by the laser after the game was solved
3269 if (game_mm.level_solved && Hit[x][y])
3272 if (IS_DF_MIRROR_AUTO(element) ||
3273 IS_GRID_WOOD_AUTO(element) ||
3274 IS_GRID_STEEL_AUTO(element) ||
3275 element == EL_REFRACTOR)
3276 RotateMirror(x, y, MB_RIGHTBUTTON);
3281 static boolean ObjHit(int obx, int oby, int bits)
3288 if (bits & HIT_POS_CENTER)
3290 if (CheckLaserPixel(cSX + obx + 15,
3295 if (bits & HIT_POS_EDGE)
3297 for (i = 0; i < 4; i++)
3298 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3299 cSY + oby + 31 * (i / 2)))
3303 if (bits & HIT_POS_BETWEEN)
3305 for (i = 0; i < 4; i++)
3306 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3307 cSY + 4 + oby + 22 * (i / 2)))
3314 static void DeletePacMan(int px, int py)
3320 if (game_mm.num_pacman <= 1)
3322 game_mm.num_pacman = 0;
3326 for (i = 0; i < game_mm.num_pacman; i++)
3327 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3330 game_mm.num_pacman--;
3332 for (j = i; j < game_mm.num_pacman; j++)
3334 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3335 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3336 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3340 static void GameActions_MM_Ext(void)
3347 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3350 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3352 element = Tile[x][y];
3354 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3355 StartMoving_MM(x, y);
3356 else if (IS_MOVING(x, y))
3357 ContinueMoving_MM(x, y);
3358 else if (IS_EXPLODING(element))
3359 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3360 else if (element == EL_EXIT_OPENING)
3362 else if (element == EL_GRAY_BALL_OPENING)
3364 else if (IS_ENVELOPE_OPENING(element))
3366 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3368 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3370 else if (IS_MIRROR(element) ||
3371 IS_MIRROR_FIXED(element) ||
3372 element == EL_PRISM)
3373 DrawFieldTwinkle(x, y);
3374 else if (element == EL_GRAY_BALL_ACTIVE ||
3375 element == EL_BOMB_ACTIVE ||
3376 element == EL_MINE_ACTIVE)
3377 DrawFieldAnimated_MM(x, y);
3378 else if (!IS_BLOCKED(x, y))
3379 DrawFieldAnimatedIfNeeded_MM(x, y);
3382 AutoRotateMirrors();
3385 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3387 // redraw after Explode_MM() ...
3389 DrawLaser(0, DL_LASER_ENABLED);
3390 laser.redraw = FALSE;
3395 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3399 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3401 DrawLaser(0, DL_LASER_DISABLED);
3406 // skip all following game actions if game is over
3407 if (game_mm.game_over)
3410 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3414 GameOver_MM(GAME_OVER_NO_ENERGY);
3419 if (FrameReached(&energy_delay))
3421 if (game_mm.energy_left > 0)
3422 game_mm.energy_left--;
3424 // when out of energy, wait another frame to play "out of time" sound
3427 element = laser.dest_element;
3430 if (element != Tile[ELX][ELY])
3432 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3433 element, Tile[ELX][ELY]);
3437 if (!laser.overloaded && laser.overload_value == 0 &&
3438 element != EL_BOMB &&
3439 element != EL_BOMB_ACTIVE &&
3440 element != EL_MINE &&
3441 element != EL_MINE_ACTIVE &&
3442 element != EL_GRAY_BALL &&
3443 element != EL_GRAY_BALL_ACTIVE &&
3444 element != EL_BLOCK_STONE &&
3445 element != EL_BLOCK_WOOD &&
3446 element != EL_FUSE_ON &&
3447 element != EL_FUEL_FULL &&
3448 !IS_WALL_ICE(element) &&
3449 !IS_WALL_AMOEBA(element))
3452 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3454 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3455 (!laser.overloaded && laser.overload_value > 0)) &&
3456 FrameReached(&overload_delay))
3458 if (laser.overloaded)
3459 laser.overload_value++;
3461 laser.overload_value--;
3463 if (game_mm.cheat_no_overload)
3465 laser.overloaded = FALSE;
3466 laser.overload_value = 0;
3469 game_mm.laser_overload_value = laser.overload_value;
3471 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3473 SetLaserColor(0xFF);
3475 DrawLaser(0, DL_LASER_ENABLED);
3478 if (!laser.overloaded)
3479 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3480 else if (setup.sound_loops)
3481 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3483 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3485 if (laser.overload_value == MAX_LASER_OVERLOAD)
3487 UpdateAndDisplayGameControlValues();
3491 GameOver_MM(GAME_OVER_OVERLOADED);
3502 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3504 if (game_mm.cheat_no_explosion)
3509 laser.dest_element = EL_EXPLODING_OPAQUE;
3514 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3516 laser.fuse_off = TRUE;
3520 DrawLaser(0, DL_LASER_DISABLED);
3521 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3524 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3526 if (!Store2[ELX][ELY]) // check if content element not yet determined
3528 int last_anim_random_frame = gfx.anim_random_frame;
3531 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3532 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3534 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3535 native_mm_level.ball_choice_mode, 0,
3536 game_mm.ball_choice_pos);
3538 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3539 gfx.anim_random_frame = last_anim_random_frame;
3541 game_mm.ball_choice_pos++;
3543 int new_element = native_mm_level.ball_content[element_pos];
3544 int new_element_base = map_wall_to_base_element(new_element);
3546 if (IS_WALL(new_element_base))
3548 // always use completely filled wall element
3549 new_element = new_element_base | 0x000f;
3551 else if (native_mm_level.rotate_ball_content &&
3552 get_num_elements(new_element) > 1)
3554 // randomly rotate newly created game element
3555 new_element = get_rotated_element(new_element, RND(16));
3558 Store[ELX][ELY] = new_element;
3559 Store2[ELX][ELY] = TRUE;
3562 if (native_mm_level.explode_ball)
3565 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3567 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3572 if (IS_WALL_ICE(element) && CT > 50)
3574 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3576 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3577 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3578 Store2[ELX][ELY] = laser.wall_mask;
3580 laser.dest_element = Tile[ELX][ELY];
3585 if (IS_WALL_AMOEBA(element) && CT > 60)
3588 int element2 = Tile[ELX][ELY];
3590 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3593 for (i = laser.num_damages - 1; i >= 0; i--)
3594 if (laser.damage[i].is_mirror)
3597 r = laser.num_edges;
3598 d = laser.num_damages;
3605 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3608 DrawLaser(0, DL_LASER_ENABLED);
3611 x = laser.damage[k1].x;
3612 y = laser.damage[k1].y;
3617 for (i = 0; i < 4; i++)
3619 if (laser.wall_mask & (1 << i))
3621 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3622 cSY + ELY * TILEY + 31 * (i / 2)))
3625 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3626 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3633 for (i = 0; i < 4; i++)
3635 if (laser.wall_mask & (1 << i))
3637 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3638 cSY + ELY * TILEY + 31 * (i / 2)))
3645 if (laser.num_beamers > 0 ||
3646 k1 < 1 || k2 < 4 || k3 < 4 ||
3647 CheckLaserPixel(cSX + ELX * TILEX + 14,
3648 cSY + ELY * TILEY + 14))
3650 laser.num_edges = r;
3651 laser.num_damages = d;
3653 DrawLaser(0, DL_LASER_DISABLED);
3656 Tile[ELX][ELY] = element | laser.wall_mask;
3658 int x = ELX, y = ELY;
3659 int wall_mask = laser.wall_mask;
3662 DrawLaser(0, DL_LASER_ENABLED);
3664 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3666 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3667 Store[x][y] = EL_WALL_AMOEBA_BASE;
3668 Store2[x][y] = wall_mask;
3673 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3674 laser.stops_inside_element && CT > native_mm_level.time_block)
3679 if (ABS(XS) > ABS(YS))
3686 for (i = 0; i < 4; i++)
3693 x = ELX + Step[k * 4].x;
3694 y = ELY + Step[k * 4].y;
3696 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3699 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3707 laser.overloaded = (element == EL_BLOCK_STONE);
3712 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3715 Tile[x][y] = element;
3717 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3720 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3722 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3723 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3731 if (element == EL_FUEL_FULL && CT > 10)
3733 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3734 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3736 for (i = start; i <= num_init_game_frames; i++)
3738 if (i == num_init_game_frames)
3739 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3740 else if (setup.sound_loops)
3741 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3743 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3745 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3747 UpdateAndDisplayGameControlValues();
3752 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3754 DrawField_MM(ELX, ELY);
3756 DrawLaser(0, DL_LASER_ENABLED);
3762 void GameActions_MM(struct MouseActionInfo action)
3764 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3765 boolean button_released = (action.button == MB_RELEASED);
3767 GameActions_MM_Ext();
3769 CheckSingleStepMode_MM(element_clicked, button_released);
3772 static void MovePacMen(void)
3774 int mx, my, ox, oy, nx, ny;
3778 if (++pacman_nr >= game_mm.num_pacman)
3781 game_mm.pacman[pacman_nr].dir--;
3783 for (l = 1; l < 5; l++)
3785 game_mm.pacman[pacman_nr].dir++;
3787 if (game_mm.pacman[pacman_nr].dir > 4)
3788 game_mm.pacman[pacman_nr].dir = 1;
3790 if (game_mm.pacman[pacman_nr].dir % 2)
3793 my = game_mm.pacman[pacman_nr].dir - 2;
3798 mx = 3 - game_mm.pacman[pacman_nr].dir;
3801 ox = game_mm.pacman[pacman_nr].x;
3802 oy = game_mm.pacman[pacman_nr].y;
3805 element = Tile[nx][ny];
3807 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3810 if (!IS_EATABLE4PACMAN(element))
3813 if (ObjHit(nx, ny, HIT_POS_CENTER))
3816 Tile[ox][oy] = EL_EMPTY;
3818 EL_PACMAN_RIGHT - 1 +
3819 (game_mm.pacman[pacman_nr].dir - 1 +
3820 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3822 game_mm.pacman[pacman_nr].x = nx;
3823 game_mm.pacman[pacman_nr].y = ny;
3825 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3827 if (element != EL_EMPTY)
3829 int graphic = el2gfx(Tile[nx][ny]);
3834 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3837 ox = cSX + ox * TILEX;
3838 oy = cSY + oy * TILEY;
3840 for (i = 1; i < 33; i += 2)
3841 BlitBitmap(bitmap, window,
3842 src_x, src_y, TILEX, TILEY,
3843 ox + i * mx, oy + i * my);
3844 Ct = Ct + FrameCounter - CT;
3847 DrawField_MM(nx, ny);
3850 if (!laser.fuse_off)
3852 DrawLaser(0, DL_LASER_ENABLED);
3854 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3856 AddDamagedField(nx, ny);
3858 laser.damage[laser.num_damages - 1].edge = 0;
3862 if (element == EL_BOMB)
3863 DeletePacMan(nx, ny);
3865 if (IS_WALL_AMOEBA(element) &&
3866 (LX + 2 * XS) / TILEX == nx &&
3867 (LY + 2 * YS) / TILEY == ny)
3877 static void InitMovingField_MM(int x, int y, int direction)
3879 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3880 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3882 MovDir[x][y] = direction;
3883 MovDir[newx][newy] = direction;
3885 if (Tile[newx][newy] == EL_EMPTY)
3886 Tile[newx][newy] = EL_BLOCKED;
3889 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3891 int direction = MovDir[x][y];
3892 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3893 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3899 static void Blocked2Moving_MM(int x, int y,
3900 int *comes_from_x, int *comes_from_y)
3902 int oldx = x, oldy = y;
3903 int direction = MovDir[x][y];
3905 if (direction == MV_LEFT)
3907 else if (direction == MV_RIGHT)
3909 else if (direction == MV_UP)
3911 else if (direction == MV_DOWN)
3914 *comes_from_x = oldx;
3915 *comes_from_y = oldy;
3918 static int MovingOrBlocked2Element_MM(int x, int y)
3920 int element = Tile[x][y];
3922 if (element == EL_BLOCKED)
3926 Blocked2Moving_MM(x, y, &oldx, &oldy);
3928 return Tile[oldx][oldy];
3934 static void RemoveMovingField_MM(int x, int y)
3936 int oldx = x, oldy = y, newx = x, newy = y;
3938 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3941 if (IS_MOVING(x, y))
3943 Moving2Blocked_MM(x, y, &newx, &newy);
3944 if (Tile[newx][newy] != EL_BLOCKED)
3947 else if (Tile[x][y] == EL_BLOCKED)
3949 Blocked2Moving_MM(x, y, &oldx, &oldy);
3950 if (!IS_MOVING(oldx, oldy))
3954 Tile[oldx][oldy] = EL_EMPTY;
3955 Tile[newx][newy] = EL_EMPTY;
3956 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3957 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3959 DrawLevelField_MM(oldx, oldy);
3960 DrawLevelField_MM(newx, newy);
3963 static void RaiseScore_MM(int value)
3965 game_mm.score += value;
3968 void RaiseScoreElement_MM(int element)
3973 case EL_PACMAN_RIGHT:
3975 case EL_PACMAN_LEFT:
3976 case EL_PACMAN_DOWN:
3977 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3981 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3986 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3990 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3999 // ----------------------------------------------------------------------------
4000 // Mirror Magic game engine snapshot handling functions
4001 // ----------------------------------------------------------------------------
4003 void SaveEngineSnapshotValues_MM(void)
4007 engine_snapshot_mm.game_mm = game_mm;
4008 engine_snapshot_mm.laser = laser;
4010 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4012 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4014 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4015 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4016 engine_snapshot_mm.Box[x][y] = Box[x][y];
4017 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4021 engine_snapshot_mm.LX = LX;
4022 engine_snapshot_mm.LY = LY;
4023 engine_snapshot_mm.XS = XS;
4024 engine_snapshot_mm.YS = YS;
4025 engine_snapshot_mm.ELX = ELX;
4026 engine_snapshot_mm.ELY = ELY;
4027 engine_snapshot_mm.CT = CT;
4028 engine_snapshot_mm.Ct = Ct;
4030 engine_snapshot_mm.last_LX = last_LX;
4031 engine_snapshot_mm.last_LY = last_LY;
4032 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4033 engine_snapshot_mm.hold_x = hold_x;
4034 engine_snapshot_mm.hold_y = hold_y;
4035 engine_snapshot_mm.pacman_nr = pacman_nr;
4037 engine_snapshot_mm.rotate_delay = rotate_delay;
4038 engine_snapshot_mm.pacman_delay = pacman_delay;
4039 engine_snapshot_mm.energy_delay = energy_delay;
4040 engine_snapshot_mm.overload_delay = overload_delay;
4043 void LoadEngineSnapshotValues_MM(void)
4047 // stored engine snapshot buffers already restored at this point
4049 game_mm = engine_snapshot_mm.game_mm;
4050 laser = engine_snapshot_mm.laser;
4052 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4054 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4056 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4057 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4058 Box[x][y] = engine_snapshot_mm.Box[x][y];
4059 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4063 LX = engine_snapshot_mm.LX;
4064 LY = engine_snapshot_mm.LY;
4065 XS = engine_snapshot_mm.XS;
4066 YS = engine_snapshot_mm.YS;
4067 ELX = engine_snapshot_mm.ELX;
4068 ELY = engine_snapshot_mm.ELY;
4069 CT = engine_snapshot_mm.CT;
4070 Ct = engine_snapshot_mm.Ct;
4072 last_LX = engine_snapshot_mm.last_LX;
4073 last_LY = engine_snapshot_mm.last_LY;
4074 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4075 hold_x = engine_snapshot_mm.hold_x;
4076 hold_y = engine_snapshot_mm.hold_y;
4077 pacman_nr = engine_snapshot_mm.pacman_nr;
4079 rotate_delay = engine_snapshot_mm.rotate_delay;
4080 pacman_delay = engine_snapshot_mm.pacman_delay;
4081 energy_delay = engine_snapshot_mm.energy_delay;
4082 overload_delay = engine_snapshot_mm.overload_delay;
4084 RedrawPlayfield_MM();
4087 static int getAngleFromTouchDelta(int dx, int dy, int base)
4089 double pi = 3.141592653;
4090 double rad = atan2((double)-dy, (double)dx);
4091 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4092 double deg = rad2 * 180.0 / pi;
4094 return (int)(deg * base / 360.0 + 0.5) % base;
4097 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4099 // calculate start (source) position to be at the middle of the tile
4100 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4101 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4102 int dx = dst_mx - src_mx;
4103 int dy = dst_my - src_my;
4112 if (!IN_LEV_FIELD(x, y))
4115 element = Tile[x][y];
4117 if (!IS_MCDUFFIN(element) &&
4118 !IS_MIRROR(element) &&
4119 !IS_BEAMER(element) &&
4120 !IS_POLAR(element) &&
4121 !IS_POLAR_CROSS(element) &&
4122 !IS_DF_MIRROR(element))
4125 angle_old = get_element_angle(element);
4127 if (IS_MCDUFFIN(element))
4129 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4130 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4131 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4132 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4135 else if (IS_MIRROR(element) ||
4136 IS_DF_MIRROR(element))
4138 for (i = 0; i < laser.num_damages; i++)
4140 if (laser.damage[i].x == x &&
4141 laser.damage[i].y == y &&
4142 ObjHit(x, y, HIT_POS_CENTER))
4144 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4145 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4152 if (angle_new == -1)
4154 if (IS_MIRROR(element) ||
4155 IS_DF_MIRROR(element) ||
4159 if (IS_POLAR_CROSS(element))
4162 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4165 button = (angle_new == angle_old ? 0 :
4166 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4167 MB_LEFTBUTTON : MB_RIGHTBUTTON);