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;
579 static void InitCycleElements_RotateSingleStep(void)
583 if (game_mm.num_cycle == 0) // no elements to cycle
586 for (i = 0; i < game_mm.num_cycle; i++)
588 int x = game_mm.cycle[i].x;
589 int y = game_mm.cycle[i].y;
590 int step = SIGN(game_mm.cycle[i].steps);
591 int last_element = Tile[x][y];
592 int next_element = get_rotated_element(last_element, step);
594 if (!game_mm.cycle[i].steps)
597 Tile[x][y] = next_element;
599 game_mm.cycle[i].steps -= step;
603 static void InitLaser(void)
605 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
606 int step = (IS_LASER(start_element) ? 4 : 0);
608 LX = laser.start_edge.x * TILEX;
609 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
612 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
614 LY = laser.start_edge.y * TILEY;
615 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
616 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
620 XS = 2 * Step[laser.start_angle].x;
621 YS = 2 * Step[laser.start_angle].y;
623 laser.current_angle = laser.start_angle;
625 laser.num_damages = 0;
627 laser.num_beamers = 0;
628 laser.beamer_edge[0] = 0;
630 laser.dest_element = EL_EMPTY;
633 AddLaserEdge(LX, LY); // set laser starting edge
638 void InitGameEngine_MM(void)
644 // initialize laser bitmap to current playfield (screen) size
645 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
646 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
650 // set global game control values
651 game_mm.num_cycle = 0;
652 game_mm.num_pacman = 0;
655 game_mm.energy_left = 0; // later set to "native_mm_level.time"
656 game_mm.kettles_still_needed =
657 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
658 game_mm.lights_still_needed = 0;
659 game_mm.num_keys = 0;
660 game_mm.ball_choice_pos = 0;
662 game_mm.laser_red = FALSE;
663 game_mm.laser_green = FALSE;
664 game_mm.laser_blue = TRUE;
666 game_mm.level_solved = FALSE;
667 game_mm.game_over = FALSE;
668 game_mm.game_over_cause = 0;
670 game_mm.laser_overload_value = 0;
671 game_mm.laser_enabled = FALSE;
673 // set global laser control values (must be set before "InitLaser()")
674 laser.start_edge.x = 0;
675 laser.start_edge.y = 0;
676 laser.start_angle = 0;
678 for (i = 0; i < MAX_NUM_BEAMERS; i++)
679 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
681 laser.overloaded = FALSE;
682 laser.overload_value = 0;
683 laser.fuse_off = FALSE;
684 laser.fuse_x = laser.fuse_y = -1;
686 laser.dest_element = EL_EMPTY;
687 laser.dest_element_last = EL_EMPTY;
688 laser.dest_element_last_x = -1;
689 laser.dest_element_last_y = -1;
703 rotate_delay.count = 0;
704 pacman_delay.count = 0;
705 energy_delay.count = 0;
706 overload_delay.count = 0;
708 ClickElement(-1, -1, -1);
710 for (x = 0; x < lev_fieldx; x++)
712 for (y = 0; y < lev_fieldy; y++)
714 Tile[x][y] = Ur[x][y];
715 Hit[x][y] = Box[x][y] = 0;
717 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
718 Store[x][y] = Store2[x][y] = 0;
721 InitField(x, y, TRUE);
728 void InitGameActions_MM(void)
730 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
731 int cycle_steps_done = 0;
736 for (i = 0; i <= num_init_game_frames; i++)
738 if (i == num_init_game_frames)
739 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
740 else if (setup.sound_loops)
741 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
743 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
745 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
747 UpdateAndDisplayGameControlValues();
749 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
751 InitCycleElements_RotateSingleStep();
756 AdvanceFrameCounter();
764 if (setup.quick_doors)
771 if (game_mm.kettles_still_needed == 0)
774 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
775 SetTileCursorActive(TRUE);
777 // restart all delay counters after initially cycling game elements
778 ResetFrameCounter(&rotate_delay);
779 ResetFrameCounter(&pacman_delay);
780 ResetFrameCounter(&energy_delay);
781 ResetFrameCounter(&overload_delay);
784 static void FadeOutLaser(void)
788 for (i = 15; i >= 0; i--)
790 SetLaserColor(0x11 * i);
792 DrawLaser(0, DL_LASER_ENABLED);
795 Delay_WithScreenUpdates(50);
798 DrawLaser(0, DL_LASER_DISABLED);
800 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
803 static void GameOver_MM(int game_over_cause)
805 // do not handle game over if request dialog is already active
806 if (game.request_active)
809 game_mm.game_over = TRUE;
810 game_mm.game_over_cause = game_over_cause;
812 // do not ask to play again if game was never actually played
813 if (!game.GamePlayed)
816 if (setup.ask_on_game_over)
817 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
818 "Bomb killed Mc Duffin! Play it again?" :
819 game_over_cause == GAME_OVER_NO_ENERGY ?
820 "Out of magic energy! Play it again?" :
821 game_over_cause == GAME_OVER_OVERLOADED ?
822 "Magic spell hit Mc Duffin! Play it again?" :
825 SetTileCursorActive(FALSE);
828 static void AddLaserEdge(int lx, int ly)
833 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
835 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
840 laser.edge[laser.num_edges].x = cSX2 + lx;
841 laser.edge[laser.num_edges].y = cSY2 + ly;
847 static void AddDamagedField(int ex, int ey)
849 // prevent adding the same field position again
850 if (laser.num_damages > 0 &&
851 laser.damage[laser.num_damages - 1].x == ex &&
852 laser.damage[laser.num_damages - 1].y == ey &&
853 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
856 laser.damage[laser.num_damages].is_mirror = FALSE;
857 laser.damage[laser.num_damages].angle = laser.current_angle;
858 laser.damage[laser.num_damages].edge = laser.num_edges;
859 laser.damage[laser.num_damages].x = ex;
860 laser.damage[laser.num_damages].y = ey;
864 static boolean StepBehind(void)
870 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
871 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
873 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
879 static int getMaskFromElement(int element)
881 if (IS_GRID(element))
882 return MM_MASK_GRID_1 + get_element_phase(element);
883 else if (IS_MCDUFFIN(element))
884 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
885 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
886 return MM_MASK_RECTANGLE;
888 return MM_MASK_CIRCLE;
891 static int ScanPixel(void)
896 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
897 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
900 // follow laser beam until it hits something (at least the screen border)
901 while (hit_mask == HIT_MASK_NO_HIT)
907 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
908 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
910 Debug("game:mm:ScanPixel", "touched screen border!");
916 for (i = 0; i < 4; i++)
918 int px = LX + (i % 2) * 2;
919 int py = LY + (i / 2) * 2;
922 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
923 int ly = (py + TILEY) / TILEY - 1; // negative values!
926 if (IN_LEV_FIELD(lx, ly))
928 int element = Tile[lx][ly];
930 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
934 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
936 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
938 pixel = ((element & (1 << pos)) ? 1 : 0);
942 int pos = getMaskFromElement(element);
944 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
949 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
950 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
953 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
954 hit_mask |= (1 << i);
957 if (hit_mask == HIT_MASK_NO_HIT)
959 // hit nothing -- go on with another step
968 static void DeactivateLaserTargetElement(void)
970 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
971 laser.dest_element_last == EL_MINE_ACTIVE ||
972 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
973 laser.dest_element_last == EL_GRAY_BALL_OPENING)
975 int x = laser.dest_element_last_x;
976 int y = laser.dest_element_last_y;
977 int element = laser.dest_element_last;
979 if (Tile[x][y] == element)
980 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
981 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
983 if (Tile[x][y] == EL_GRAY_BALL)
986 laser.dest_element_last = EL_EMPTY;
987 laser.dest_element_last_x = -1;
988 laser.dest_element_last_y = -1;
992 static void ScanLaser(void)
994 int element = EL_EMPTY;
995 int last_element = EL_EMPTY;
996 int end = 0, rf = laser.num_edges;
998 // do not scan laser again after the game was lost for whatever reason
999 if (game_mm.game_over)
1002 // do not scan laser if fuse is off
1006 DeactivateLaserTargetElement();
1008 laser.overloaded = FALSE;
1009 laser.stops_inside_element = FALSE;
1011 DrawLaser(0, DL_LASER_ENABLED);
1014 Debug("game:mm:ScanLaser",
1015 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1023 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1026 laser.overloaded = TRUE;
1031 hit_mask = ScanPixel();
1034 Debug("game:mm:ScanLaser",
1035 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1039 // hit something -- check out what it was
1040 ELX = (LX + XS) / TILEX;
1041 ELY = (LY + YS) / TILEY;
1044 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1045 hit_mask, LX, LY, ELX, ELY);
1048 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1051 laser.dest_element = element;
1056 // check if laser scan has hit two diagonally adjacent element corners
1057 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1058 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1060 // check if laser scan has crossed element boundaries (not just mini tiles)
1061 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1062 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1064 // handle special case of laser hitting two diagonally adjacent elements
1065 // (with or without a third corner element behind these two elements)
1066 if ((diag_1 || diag_2) && cross_x && cross_y)
1068 // compare the two diagonally adjacent elements
1070 int yoffset = 2 * (diag_1 ? -1 : +1);
1071 int elx1 = (LX - xoffset) / TILEX;
1072 int ely1 = (LY + yoffset) / TILEY;
1073 int elx2 = (LX + xoffset) / TILEX;
1074 int ely2 = (LY - yoffset) / TILEY;
1075 int e1 = Tile[elx1][ely1];
1076 int e2 = Tile[elx2][ely2];
1077 boolean use_element_1 = FALSE;
1079 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1081 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1082 use_element_1 = (RND(2) ? TRUE : FALSE);
1083 else if (IS_WALL_ICE(e1))
1084 use_element_1 = TRUE;
1086 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1088 // if both tiles match, we can just select the first one
1089 if (IS_WALL_AMOEBA(e1))
1090 use_element_1 = TRUE;
1092 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1094 // if both tiles match, we can just select the first one
1095 if (IS_ABSORBING_BLOCK(e1))
1096 use_element_1 = TRUE;
1099 ELX = (use_element_1 ? elx1 : elx2);
1100 ELY = (use_element_1 ? ely1 : ely2);
1104 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1105 hit_mask, LX, LY, ELX, ELY);
1108 last_element = element;
1110 element = Tile[ELX][ELY];
1111 laser.dest_element = element;
1114 Debug("game:mm:ScanLaser",
1115 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1118 LX % TILEX, LY % TILEY,
1123 if (!IN_LEV_FIELD(ELX, ELY))
1124 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1128 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1129 if (element == EL_EMPTY &&
1130 IS_GRID_STEEL(last_element) &&
1131 laser.current_angle % 4) // angle is not 90°
1132 element = last_element;
1134 if (element == EL_EMPTY)
1136 if (!HitOnlyAnEdge(hit_mask))
1139 else if (element == EL_FUSE_ON)
1141 if (HitPolarizer(element, hit_mask))
1144 else if (IS_GRID(element) || IS_DF_GRID(element))
1146 if (HitPolarizer(element, hit_mask))
1149 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1150 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1152 if (HitBlock(element, hit_mask))
1159 else if (IS_MCDUFFIN(element))
1161 if (HitLaserSource(element, hit_mask))
1164 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1165 IS_RECEIVER(element))
1167 if (HitLaserDestination(element, hit_mask))
1170 else if (IS_WALL(element))
1172 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1174 if (HitReflectingWalls(element, hit_mask))
1179 if (HitAbsorbingWalls(element, hit_mask))
1185 if (HitElement(element, hit_mask))
1190 DrawLaser(rf - 1, DL_LASER_ENABLED);
1191 rf = laser.num_edges;
1193 if (!IS_DF_WALL_STEEL(element))
1195 // only used for scanning DF steel walls; reset for all other elements
1203 if (laser.dest_element != Tile[ELX][ELY])
1205 Debug("game:mm:ScanLaser",
1206 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1207 laser.dest_element, Tile[ELX][ELY]);
1211 if (!end && !laser.stops_inside_element && !StepBehind())
1214 Debug("game:mm:ScanLaser", "Go one step back");
1220 AddLaserEdge(LX, LY);
1224 DrawLaser(rf - 1, DL_LASER_ENABLED);
1226 Ct = CT = FrameCounter;
1229 if (!IN_LEV_FIELD(ELX, ELY))
1230 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1234 static void ScanLaser_FromLastMirror(void)
1236 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1239 for (i = start_pos; i >= 0; i--)
1240 if (laser.damage[i].is_mirror)
1243 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1245 DrawLaser(start_edge, DL_LASER_DISABLED);
1250 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1256 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1257 start_edge, num_edges, mode);
1262 Warn("DrawLaserExt: start_edge < 0");
1269 Warn("DrawLaserExt: num_edges < 0");
1275 if (mode == DL_LASER_DISABLED)
1277 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1281 // now draw the laser to the backbuffer and (if enabled) to the screen
1282 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1284 redraw_mask |= REDRAW_FIELD;
1286 if (mode == DL_LASER_ENABLED)
1289 // after the laser was deleted, the "damaged" graphics must be restored
1290 if (laser.num_damages)
1292 int damage_start = 0;
1295 // determine the starting edge, from which graphics need to be restored
1298 for (i = 0; i < laser.num_damages; i++)
1300 if (laser.damage[i].edge == start_edge + 1)
1309 // restore graphics from this starting edge to the end of damage list
1310 for (i = damage_start; i < laser.num_damages; i++)
1312 int lx = laser.damage[i].x;
1313 int ly = laser.damage[i].y;
1314 int element = Tile[lx][ly];
1316 if (Hit[lx][ly] == laser.damage[i].edge)
1317 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1320 if (Box[lx][ly] == laser.damage[i].edge)
1323 if (IS_DRAWABLE(element))
1324 DrawField_MM(lx, ly);
1327 elx = laser.damage[damage_start].x;
1328 ely = laser.damage[damage_start].y;
1329 element = Tile[elx][ely];
1332 if (IS_BEAMER(element))
1336 for (i = 0; i < laser.num_beamers; i++)
1337 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1339 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1340 mode, elx, ely, Hit[elx][ely], start_edge);
1341 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1342 get_element_angle(element), laser.damage[damage_start].angle);
1346 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1347 laser.num_beamers > 0 &&
1348 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1350 // element is outgoing beamer
1351 laser.num_damages = damage_start + 1;
1353 if (IS_BEAMER(element))
1354 laser.current_angle = get_element_angle(element);
1358 // element is incoming beamer or other element
1359 laser.num_damages = damage_start;
1360 laser.current_angle = laser.damage[laser.num_damages].angle;
1365 // no damages but McDuffin himself (who needs to be redrawn anyway)
1367 elx = laser.start_edge.x;
1368 ely = laser.start_edge.y;
1369 element = Tile[elx][ely];
1372 laser.num_edges = start_edge + 1;
1373 if (start_edge == 0)
1374 laser.current_angle = laser.start_angle;
1376 LX = laser.edge[start_edge].x - cSX2;
1377 LY = laser.edge[start_edge].y - cSY2;
1378 XS = 2 * Step[laser.current_angle].x;
1379 YS = 2 * Step[laser.current_angle].y;
1382 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1388 if (IS_BEAMER(element) ||
1389 IS_FIBRE_OPTIC(element) ||
1390 IS_PACMAN(element) ||
1391 IS_POLAR(element) ||
1392 IS_POLAR_CROSS(element) ||
1393 element == EL_FUSE_ON)
1398 Debug("game:mm:DrawLaserExt", "element == %d", element);
1401 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1402 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1406 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1407 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1408 (laser.num_beamers == 0 ||
1409 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1411 // element is incoming beamer or other element
1412 step_size = -step_size;
1417 if (IS_BEAMER(element))
1418 Debug("game:mm:DrawLaserExt",
1419 "start_edge == %d, laser.beamer_edge == %d",
1420 start_edge, laser.beamer_edge);
1423 LX += step_size * XS;
1424 LY += step_size * YS;
1426 else if (element != EL_EMPTY)
1435 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1440 void DrawLaser(int start_edge, int mode)
1442 // do not draw laser if fuse is off
1443 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1446 if (mode == DL_LASER_DISABLED)
1447 DeactivateLaserTargetElement();
1449 if (laser.num_edges - start_edge < 0)
1451 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1456 // check if laser is interrupted by beamer element
1457 if (laser.num_beamers > 0 &&
1458 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1460 if (mode == DL_LASER_ENABLED)
1463 int tmp_start_edge = start_edge;
1465 // draw laser segments forward from the start to the last beamer
1466 for (i = 0; i < laser.num_beamers; i++)
1468 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1470 if (tmp_num_edges <= 0)
1474 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1475 i, laser.beamer_edge[i], tmp_start_edge);
1478 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1480 tmp_start_edge = laser.beamer_edge[i];
1483 // draw last segment from last beamer to the end
1484 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1490 int last_num_edges = laser.num_edges;
1491 int num_beamers = laser.num_beamers;
1493 // delete laser segments backward from the end to the first beamer
1494 for (i = num_beamers - 1; i >= 0; i--)
1496 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1498 if (laser.beamer_edge[i] - start_edge <= 0)
1501 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1503 last_num_edges = laser.beamer_edge[i];
1504 laser.num_beamers--;
1508 if (last_num_edges - start_edge <= 0)
1509 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1510 last_num_edges, start_edge);
1513 // special case when rotating first beamer: delete laser edge on beamer
1514 // (but do not start scanning on previous edge to prevent mirror sound)
1515 if (last_num_edges - start_edge == 1 && start_edge > 0)
1516 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1518 // delete first segment from start to the first beamer
1519 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1524 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1527 game_mm.laser_enabled = mode;
1530 void DrawLaser_MM(void)
1532 DrawLaser(0, game_mm.laser_enabled);
1535 static boolean HitElement(int element, int hit_mask)
1537 if (HitOnlyAnEdge(hit_mask))
1540 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1541 element = MovingOrBlocked2Element_MM(ELX, ELY);
1544 Debug("game:mm:HitElement", "(1): element == %d", element);
1548 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1549 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1552 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1556 AddDamagedField(ELX, ELY);
1558 // this is more precise: check if laser would go through the center
1559 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1563 // prevent cutting through laser emitter with laser beam
1564 if (IS_LASER(element))
1567 // skip the whole element before continuing the scan
1575 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1577 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1579 /* skipping scan positions to the right and down skips one scan
1580 position too much, because this is only the top left scan position
1581 of totally four scan positions (plus one to the right, one to the
1582 bottom and one to the bottom right) */
1583 /* ... but only roll back scan position if more than one step done */
1593 Debug("game:mm:HitElement", "(2): element == %d", element);
1596 if (LX + 5 * XS < 0 ||
1606 Debug("game:mm:HitElement", "(3): element == %d", element);
1609 if (IS_POLAR(element) &&
1610 ((element - EL_POLAR_START) % 2 ||
1611 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1613 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1615 laser.num_damages--;
1620 if (IS_POLAR_CROSS(element) &&
1621 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1623 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1625 laser.num_damages--;
1630 if (!IS_BEAMER(element) &&
1631 !IS_FIBRE_OPTIC(element) &&
1632 !IS_GRID_WOOD(element) &&
1633 element != EL_FUEL_EMPTY)
1636 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1637 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1639 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1642 LX = ELX * TILEX + 14;
1643 LY = ELY * TILEY + 14;
1645 AddLaserEdge(LX, LY);
1648 if (IS_MIRROR(element) ||
1649 IS_MIRROR_FIXED(element) ||
1650 IS_POLAR(element) ||
1651 IS_POLAR_CROSS(element) ||
1652 IS_DF_MIRROR(element) ||
1653 IS_DF_MIRROR_AUTO(element) ||
1654 element == EL_PRISM ||
1655 element == EL_REFRACTOR)
1657 int current_angle = laser.current_angle;
1660 laser.num_damages--;
1662 AddDamagedField(ELX, ELY);
1664 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1667 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1669 if (IS_MIRROR(element) ||
1670 IS_MIRROR_FIXED(element) ||
1671 IS_DF_MIRROR(element) ||
1672 IS_DF_MIRROR_AUTO(element))
1673 laser.current_angle = get_mirrored_angle(laser.current_angle,
1674 get_element_angle(element));
1676 if (element == EL_PRISM || element == EL_REFRACTOR)
1677 laser.current_angle = RND(16);
1679 XS = 2 * Step[laser.current_angle].x;
1680 YS = 2 * Step[laser.current_angle].y;
1682 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1687 LX += step_size * XS;
1688 LY += step_size * YS;
1690 // draw sparkles on mirror
1691 if ((IS_MIRROR(element) ||
1692 IS_MIRROR_FIXED(element) ||
1693 element == EL_PRISM) &&
1694 current_angle != laser.current_angle)
1696 MovDelay[ELX][ELY] = 11; // start animation
1699 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1700 current_angle != laser.current_angle)
1701 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1704 (get_opposite_angle(laser.current_angle) ==
1705 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1707 return (laser.overloaded ? TRUE : FALSE);
1710 if (element == EL_FUEL_FULL)
1712 laser.stops_inside_element = TRUE;
1717 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1719 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1721 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1722 element == EL_MINE ? EL_MINE_ACTIVE :
1723 EL_GRAY_BALL_ACTIVE);
1725 GfxFrame[ELX][ELY] = 0; // restart animation
1727 laser.dest_element_last = Tile[ELX][ELY];
1728 laser.dest_element_last_x = ELX;
1729 laser.dest_element_last_y = ELY;
1731 if (element == EL_MINE)
1732 laser.overloaded = TRUE;
1735 if (element == EL_KETTLE ||
1736 element == EL_CELL ||
1737 element == EL_KEY ||
1738 element == EL_LIGHTBALL ||
1739 element == EL_PACMAN ||
1740 IS_PACMAN(element) ||
1741 IS_ENVELOPE(element))
1743 if (!IS_PACMAN(element) &&
1744 !IS_ENVELOPE(element))
1747 if (element == EL_PACMAN)
1750 if (element == EL_KETTLE || element == EL_CELL)
1752 if (game_mm.kettles_still_needed > 0)
1753 game_mm.kettles_still_needed--;
1755 game.snapshot.collected_item = TRUE;
1757 if (game_mm.kettles_still_needed == 0)
1761 DrawLaser(0, DL_LASER_ENABLED);
1764 else if (element == EL_KEY)
1768 else if (IS_PACMAN(element))
1770 DeletePacMan(ELX, ELY);
1772 else if (IS_ENVELOPE(element))
1774 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1777 RaiseScoreElement_MM(element);
1782 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1784 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1786 DrawLaser(0, DL_LASER_ENABLED);
1788 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1790 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1791 game_mm.lights_still_needed--;
1795 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1796 game_mm.lights_still_needed++;
1799 DrawField_MM(ELX, ELY);
1800 DrawLaser(0, DL_LASER_ENABLED);
1805 laser.stops_inside_element = TRUE;
1811 Debug("game:mm:HitElement", "(4): element == %d", element);
1814 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1815 laser.num_beamers < MAX_NUM_BEAMERS &&
1816 laser.beamer[BEAMER_NR(element)][1].num)
1818 int beamer_angle = get_element_angle(element);
1819 int beamer_nr = BEAMER_NR(element);
1823 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1826 laser.num_damages--;
1828 if (IS_FIBRE_OPTIC(element) ||
1829 laser.current_angle == get_opposite_angle(beamer_angle))
1833 LX = ELX * TILEX + 14;
1834 LY = ELY * TILEY + 14;
1836 AddLaserEdge(LX, LY);
1837 AddDamagedField(ELX, ELY);
1839 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1842 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1844 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1845 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1846 ELX = laser.beamer[beamer_nr][pos].x;
1847 ELY = laser.beamer[beamer_nr][pos].y;
1848 LX = ELX * TILEX + 14;
1849 LY = ELY * TILEY + 14;
1851 if (IS_BEAMER(element))
1853 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1854 XS = 2 * Step[laser.current_angle].x;
1855 YS = 2 * Step[laser.current_angle].y;
1858 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1860 AddLaserEdge(LX, LY);
1861 AddDamagedField(ELX, ELY);
1863 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1866 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1868 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1873 LX += step_size * XS;
1874 LY += step_size * YS;
1876 laser.num_beamers++;
1885 static boolean HitOnlyAnEdge(int hit_mask)
1887 // check if the laser hit only the edge of an element and, if so, go on
1890 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1894 if ((hit_mask == HIT_MASK_TOPLEFT ||
1895 hit_mask == HIT_MASK_TOPRIGHT ||
1896 hit_mask == HIT_MASK_BOTTOMLEFT ||
1897 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1898 laser.current_angle % 4) // angle is not 90°
1902 if (hit_mask == HIT_MASK_TOPLEFT)
1907 else if (hit_mask == HIT_MASK_TOPRIGHT)
1912 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1917 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1923 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1929 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1936 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1942 static boolean HitPolarizer(int element, int hit_mask)
1944 if (HitOnlyAnEdge(hit_mask))
1947 if (IS_DF_GRID(element))
1949 int grid_angle = get_element_angle(element);
1952 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1953 grid_angle, laser.current_angle);
1956 AddLaserEdge(LX, LY);
1957 AddDamagedField(ELX, ELY);
1960 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1962 if (laser.current_angle == grid_angle ||
1963 laser.current_angle == get_opposite_angle(grid_angle))
1965 // skip the whole element before continuing the scan
1971 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1973 if (LX/TILEX > ELX || LY/TILEY > ELY)
1975 /* skipping scan positions to the right and down skips one scan
1976 position too much, because this is only the top left scan position
1977 of totally four scan positions (plus one to the right, one to the
1978 bottom and one to the bottom right) */
1984 AddLaserEdge(LX, LY);
1990 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1992 LX / TILEX, LY / TILEY,
1993 LX % TILEX, LY % TILEY);
1998 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2000 return HitReflectingWalls(element, hit_mask);
2004 return HitAbsorbingWalls(element, hit_mask);
2007 else if (IS_GRID_STEEL(element))
2009 // may be required if graphics for steel grid redefined
2010 AddDamagedField(ELX, ELY);
2012 return HitReflectingWalls(element, hit_mask);
2014 else // IS_GRID_WOOD
2016 // may be required if graphics for wooden grid redefined
2017 AddDamagedField(ELX, ELY);
2019 return HitAbsorbingWalls(element, hit_mask);
2025 static boolean HitBlock(int element, int hit_mask)
2027 boolean check = FALSE;
2029 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2030 game_mm.num_keys == 0)
2033 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2036 int ex = ELX * TILEX + 14;
2037 int ey = ELY * TILEY + 14;
2041 for (i = 1; i < 32; i++)
2046 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2051 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2052 return HitAbsorbingWalls(element, hit_mask);
2056 AddLaserEdge(LX - XS, LY - YS);
2057 AddDamagedField(ELX, ELY);
2060 Box[ELX][ELY] = laser.num_edges;
2062 return HitReflectingWalls(element, hit_mask);
2065 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2067 int xs = XS / 2, ys = YS / 2;
2069 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2070 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2072 laser.overloaded = (element == EL_GATE_STONE);
2077 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2078 (hit_mask == HIT_MASK_TOP ||
2079 hit_mask == HIT_MASK_LEFT ||
2080 hit_mask == HIT_MASK_RIGHT ||
2081 hit_mask == HIT_MASK_BOTTOM))
2082 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2083 hit_mask == HIT_MASK_BOTTOM),
2084 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2085 hit_mask == HIT_MASK_RIGHT));
2086 AddLaserEdge(LX, LY);
2092 if (element == EL_GATE_STONE && Box[ELX][ELY])
2094 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2106 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2108 int xs = XS / 2, ys = YS / 2;
2110 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2111 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2113 laser.overloaded = (element == EL_BLOCK_STONE);
2118 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2119 (hit_mask == HIT_MASK_TOP ||
2120 hit_mask == HIT_MASK_LEFT ||
2121 hit_mask == HIT_MASK_RIGHT ||
2122 hit_mask == HIT_MASK_BOTTOM))
2123 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2124 hit_mask == HIT_MASK_BOTTOM),
2125 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2126 hit_mask == HIT_MASK_RIGHT));
2127 AddDamagedField(ELX, ELY);
2129 LX = ELX * TILEX + 14;
2130 LY = ELY * TILEY + 14;
2132 AddLaserEdge(LX, LY);
2134 laser.stops_inside_element = TRUE;
2142 static boolean HitLaserSource(int element, int hit_mask)
2144 if (HitOnlyAnEdge(hit_mask))
2147 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2149 laser.overloaded = TRUE;
2154 static boolean HitLaserDestination(int element, int hit_mask)
2156 if (HitOnlyAnEdge(hit_mask))
2159 if (element != EL_EXIT_OPEN &&
2160 !(IS_RECEIVER(element) &&
2161 game_mm.kettles_still_needed == 0 &&
2162 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2164 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2169 if (IS_RECEIVER(element) ||
2170 (IS_22_5_ANGLE(laser.current_angle) &&
2171 (ELX != (LX + 6 * XS) / TILEX ||
2172 ELY != (LY + 6 * YS) / TILEY ||
2181 LX = ELX * TILEX + 14;
2182 LY = ELY * TILEY + 14;
2184 laser.stops_inside_element = TRUE;
2187 AddLaserEdge(LX, LY);
2188 AddDamagedField(ELX, ELY);
2190 if (game_mm.lights_still_needed == 0)
2192 game_mm.level_solved = TRUE;
2194 SetTileCursorActive(FALSE);
2200 static boolean HitReflectingWalls(int element, int hit_mask)
2202 // check if laser hits side of a wall with an angle that is not 90°
2203 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2204 hit_mask == HIT_MASK_LEFT ||
2205 hit_mask == HIT_MASK_RIGHT ||
2206 hit_mask == HIT_MASK_BOTTOM))
2208 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2213 if (!IS_DF_GRID(element))
2214 AddLaserEdge(LX, LY);
2216 // check if laser hits wall with an angle of 45°
2217 if (!IS_22_5_ANGLE(laser.current_angle))
2219 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2222 laser.current_angle = get_mirrored_angle(laser.current_angle,
2225 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2228 laser.current_angle = get_mirrored_angle(laser.current_angle,
2232 AddLaserEdge(LX, LY);
2234 XS = 2 * Step[laser.current_angle].x;
2235 YS = 2 * Step[laser.current_angle].y;
2239 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2241 laser.current_angle = get_mirrored_angle(laser.current_angle,
2246 if (!IS_DF_GRID(element))
2247 AddLaserEdge(LX, LY);
2252 if (!IS_DF_GRID(element))
2253 AddLaserEdge(LX, LY + YS / 2);
2256 if (!IS_DF_GRID(element))
2257 AddLaserEdge(LX, LY);
2260 YS = 2 * Step[laser.current_angle].y;
2264 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2266 laser.current_angle = get_mirrored_angle(laser.current_angle,
2271 if (!IS_DF_GRID(element))
2272 AddLaserEdge(LX, LY);
2277 if (!IS_DF_GRID(element))
2278 AddLaserEdge(LX + XS / 2, LY);
2281 if (!IS_DF_GRID(element))
2282 AddLaserEdge(LX, LY);
2285 XS = 2 * Step[laser.current_angle].x;
2291 // reflection at the edge of reflecting DF style wall
2292 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2294 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2295 hit_mask == HIT_MASK_TOPRIGHT) ||
2296 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2297 hit_mask == HIT_MASK_TOPLEFT) ||
2298 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2299 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2300 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2301 hit_mask == HIT_MASK_BOTTOMRIGHT))
2304 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2305 ANG_MIRROR_135 : ANG_MIRROR_45);
2307 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2309 AddDamagedField(ELX, ELY);
2310 AddLaserEdge(LX, LY);
2312 laser.current_angle = get_mirrored_angle(laser.current_angle,
2320 AddLaserEdge(LX, LY);
2326 // reflection inside an edge of reflecting DF style wall
2327 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2329 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2330 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2331 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2332 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2333 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2334 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2335 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2336 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2339 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2340 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2341 ANG_MIRROR_135 : ANG_MIRROR_45);
2343 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2346 AddDamagedField(ELX, ELY);
2349 AddLaserEdge(LX - XS, LY - YS);
2350 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2351 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2353 laser.current_angle = get_mirrored_angle(laser.current_angle,
2361 AddLaserEdge(LX, LY);
2367 // check if laser hits DF style wall with an angle of 90°
2368 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2370 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2371 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2372 (IS_VERT_ANGLE(laser.current_angle) &&
2373 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2375 // laser at last step touched nothing or the same side of the wall
2376 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2378 AddDamagedField(ELX, ELY);
2385 last_hit_mask = hit_mask;
2392 if (!HitOnlyAnEdge(hit_mask))
2394 laser.overloaded = TRUE;
2402 static boolean HitAbsorbingWalls(int element, int hit_mask)
2404 if (HitOnlyAnEdge(hit_mask))
2408 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2410 AddLaserEdge(LX - XS, LY - YS);
2417 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2419 AddLaserEdge(LX - XS, LY - YS);
2425 if (IS_WALL_WOOD(element) ||
2426 IS_DF_WALL_WOOD(element) ||
2427 IS_GRID_WOOD(element) ||
2428 IS_GRID_WOOD_FIXED(element) ||
2429 IS_GRID_WOOD_AUTO(element) ||
2430 element == EL_FUSE_ON ||
2431 element == EL_BLOCK_WOOD ||
2432 element == EL_GATE_WOOD)
2434 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2439 if (IS_WALL_ICE(element))
2445 // check if laser hit adjacent edges of two diagonal tiles
2446 if (ELX != lx / TILEX)
2448 if (ELY != ly / TILEY)
2451 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2452 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2454 // check if laser hits wall with an angle of 90°
2455 if (IS_90_ANGLE(laser.current_angle))
2456 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2458 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2462 for (i = 0; i < 4; i++)
2464 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2465 mask = 15 - (8 >> i);
2466 else if (ABS(XS) == 4 &&
2468 (XS > 0) == (i % 2) &&
2469 (YS < 0) == (i / 2))
2470 mask = 3 + (i / 2) * 9;
2471 else if (ABS(YS) == 4 &&
2473 (XS < 0) == (i % 2) &&
2474 (YS > 0) == (i / 2))
2475 mask = 5 + (i % 2) * 5;
2479 laser.wall_mask = mask;
2481 else if (IS_WALL_AMOEBA(element))
2483 int elx = (LX - 2 * XS) / TILEX;
2484 int ely = (LY - 2 * YS) / TILEY;
2485 int element2 = Tile[elx][ely];
2488 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2490 laser.dest_element = EL_EMPTY;
2498 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2499 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2501 if (IS_90_ANGLE(laser.current_angle))
2502 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2504 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2506 laser.wall_mask = mask;
2512 static void OpenExit(int x, int y)
2516 if (!MovDelay[x][y]) // next animation frame
2517 MovDelay[x][y] = 4 * delay;
2519 if (MovDelay[x][y]) // wait some time before next frame
2524 phase = MovDelay[x][y] / delay;
2526 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2527 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2529 if (!MovDelay[x][y])
2531 Tile[x][y] = EL_EXIT_OPEN;
2537 static void OpenGrayBall(int x, int y)
2541 if (!MovDelay[x][y]) // next animation frame
2543 if (IS_WALL(Store[x][y]))
2545 DrawWalls_MM(x, y, Store[x][y]);
2547 // copy wall tile to spare bitmap for "melting" animation
2548 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2549 TILEX, TILEY, x * TILEX, y * TILEY);
2551 DrawElement_MM(x, y, EL_GRAY_BALL);
2554 MovDelay[x][y] = 50 * delay;
2557 if (MovDelay[x][y]) // wait some time before next frame
2561 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2565 int dx = RND(26), dy = RND(26);
2567 if (IS_WALL(Store[x][y]))
2569 // copy wall tile from spare bitmap for "melting" animation
2570 bitmap = bitmap_db_field;
2576 int graphic = el2gfx(Store[x][y]);
2578 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2581 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2582 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2584 laser.redraw = TRUE;
2586 MarkTileDirty(x, y);
2589 if (!MovDelay[x][y])
2591 Tile[x][y] = Store[x][y];
2592 Store[x][y] = Store2[x][y] = 0;
2593 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2595 InitField(x, y, FALSE);
2598 ScanLaser_FromLastMirror();
2603 static void OpenEnvelope(int x, int y)
2605 int num_frames = 8; // seven frames plus final empty space
2607 if (!MovDelay[x][y]) // next animation frame
2608 MovDelay[x][y] = num_frames;
2610 if (MovDelay[x][y]) // wait some time before next frame
2612 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2616 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2618 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2619 int frame = num_frames - MovDelay[x][y] - 1;
2621 DrawGraphicAnimation_MM(x, y, graphic, frame);
2623 laser.redraw = TRUE;
2626 if (MovDelay[x][y] == 0)
2628 Tile[x][y] = EL_EMPTY;
2634 ShowEnvelope_MM(nr);
2639 static void MeltIce(int x, int y)
2644 if (!MovDelay[x][y]) // next animation frame
2645 MovDelay[x][y] = frames * delay;
2647 if (MovDelay[x][y]) // wait some time before next frame
2650 int wall_mask = Store2[x][y];
2651 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2654 phase = frames - MovDelay[x][y] / delay - 1;
2656 if (!MovDelay[x][y])
2658 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2659 Store[x][y] = Store2[x][y] = 0;
2661 DrawWalls_MM(x, y, Tile[x][y]);
2663 if (Tile[x][y] == EL_WALL_ICE_BASE)
2664 Tile[x][y] = EL_EMPTY;
2666 ScanLaser_FromLastMirror();
2668 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2670 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2672 laser.redraw = TRUE;
2677 static void GrowAmoeba(int x, int y)
2682 if (!MovDelay[x][y]) // next animation frame
2683 MovDelay[x][y] = frames * delay;
2685 if (MovDelay[x][y]) // wait some time before next frame
2688 int wall_mask = Store2[x][y];
2689 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2692 phase = MovDelay[x][y] / delay;
2694 if (!MovDelay[x][y])
2696 Tile[x][y] = real_element;
2697 Store[x][y] = Store2[x][y] = 0;
2699 DrawWalls_MM(x, y, Tile[x][y]);
2700 DrawLaser(0, DL_LASER_ENABLED);
2702 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2704 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2709 static void DrawFieldAnimated_MM(int x, int y)
2713 laser.redraw = TRUE;
2716 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2718 int element = Tile[x][y];
2719 int graphic = el2gfx(element);
2721 if (!getGraphicInfo_NewFrame(x, y, graphic))
2726 laser.redraw = TRUE;
2729 static void DrawFieldTwinkle(int x, int y)
2731 if (MovDelay[x][y] != 0) // wait some time before next frame
2737 if (MovDelay[x][y] != 0)
2739 int graphic = IMG_TWINKLE_WHITE;
2740 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2742 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2745 laser.redraw = TRUE;
2749 static void Explode_MM(int x, int y, int phase, int mode)
2751 int num_phase = 9, delay = 2;
2752 int last_phase = num_phase * delay;
2753 int half_phase = (num_phase / 2) * delay;
2756 laser.redraw = TRUE;
2758 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2760 center_element = Tile[x][y];
2762 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2764 // put moving element to center field (and let it explode there)
2765 center_element = MovingOrBlocked2Element_MM(x, y);
2766 RemoveMovingField_MM(x, y);
2768 Tile[x][y] = center_element;
2771 if (center_element != EL_GRAY_BALL_ACTIVE)
2772 Store[x][y] = EL_EMPTY;
2773 Store2[x][y] = center_element;
2775 Tile[x][y] = EL_EXPLODING_OPAQUE;
2777 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2778 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2779 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2782 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2784 ExplodePhase[x][y] = 1;
2790 GfxFrame[x][y] = 0; // restart explosion animation
2792 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2794 center_element = Store2[x][y];
2796 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2798 Tile[x][y] = EL_EXPLODING_TRANSP;
2800 if (x == ELX && y == ELY)
2804 if (phase == last_phase)
2806 if (center_element == EL_BOMB_ACTIVE)
2808 DrawLaser(0, DL_LASER_DISABLED);
2811 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2813 GameOver_MM(GAME_OVER_DELAYED);
2815 laser.overloaded = FALSE;
2817 else if (IS_MCDUFFIN(center_element))
2819 GameOver_MM(GAME_OVER_BOMB);
2822 Tile[x][y] = Store[x][y];
2824 Store[x][y] = Store2[x][y] = 0;
2825 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2827 InitField(x, y, FALSE);
2830 if (center_element == EL_GRAY_BALL_ACTIVE)
2831 ScanLaser_FromLastMirror();
2833 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2835 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2836 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2838 DrawGraphicAnimation_MM(x, y, graphic, frame);
2840 MarkTileDirty(x, y);
2844 static void Bang_MM(int x, int y)
2846 int element = Tile[x][y];
2848 if (IS_PACMAN(element))
2849 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2850 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2851 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2852 else if (element == EL_KEY)
2853 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2855 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2857 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2860 static void TurnRound(int x, int y)
2872 { 0, 0 }, { 0, 0 }, { 0, 0 },
2877 int left, right, back;
2881 { MV_DOWN, MV_UP, MV_RIGHT },
2882 { MV_UP, MV_DOWN, MV_LEFT },
2884 { MV_LEFT, MV_RIGHT, MV_DOWN },
2885 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2886 { MV_RIGHT, MV_LEFT, MV_UP }
2889 int element = Tile[x][y];
2890 int old_move_dir = MovDir[x][y];
2891 int right_dir = turn[old_move_dir].right;
2892 int back_dir = turn[old_move_dir].back;
2893 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2894 int right_x = x + right_dx, right_y = y + right_dy;
2896 if (element == EL_PACMAN)
2898 boolean can_turn_right = FALSE;
2900 if (IN_LEV_FIELD(right_x, right_y) &&
2901 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2902 can_turn_right = TRUE;
2905 MovDir[x][y] = right_dir;
2907 MovDir[x][y] = back_dir;
2913 static void StartMoving_MM(int x, int y)
2915 int element = Tile[x][y];
2920 if (CAN_MOVE(element))
2924 if (MovDelay[x][y]) // wait some time before next movement
2932 // now make next step
2934 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2936 if (element == EL_PACMAN &&
2937 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2938 !ObjHit(newx, newy, HIT_POS_CENTER))
2940 Store[newx][newy] = Tile[newx][newy];
2941 Tile[newx][newy] = EL_EMPTY;
2943 DrawField_MM(newx, newy);
2945 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2946 ObjHit(newx, newy, HIT_POS_CENTER))
2948 // object was running against a wall
2955 InitMovingField_MM(x, y, MovDir[x][y]);
2959 ContinueMoving_MM(x, y);
2962 static void ContinueMoving_MM(int x, int y)
2964 int element = Tile[x][y];
2965 int direction = MovDir[x][y];
2966 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2967 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2968 int horiz_move = (dx!=0);
2969 int newx = x + dx, newy = y + dy;
2970 int step = (horiz_move ? dx : dy) * TILEX / 8;
2972 MovPos[x][y] += step;
2974 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2976 Tile[x][y] = EL_EMPTY;
2977 Tile[newx][newy] = element;
2979 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2980 MovDelay[newx][newy] = 0;
2982 if (!CAN_MOVE(element))
2983 MovDir[newx][newy] = 0;
2986 DrawField_MM(newx, newy);
2988 Stop[newx][newy] = TRUE;
2990 if (element == EL_PACMAN)
2992 if (Store[newx][newy] == EL_BOMB)
2993 Bang_MM(newx, newy);
2995 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2996 (LX + 2 * XS) / TILEX == newx &&
2997 (LY + 2 * YS) / TILEY == newy)
3004 else // still moving on
3009 laser.redraw = TRUE;
3012 boolean ClickElement(int x, int y, int button)
3014 static DelayCounter click_delay = { CLICK_DELAY };
3015 static boolean new_button = TRUE;
3016 boolean element_clicked = FALSE;
3021 // initialize static variables
3022 click_delay.count = 0;
3023 click_delay.value = CLICK_DELAY;
3029 // do not rotate objects hit by the laser after the game was solved
3030 if (game_mm.level_solved && Hit[x][y])
3033 if (button == MB_RELEASED)
3036 click_delay.value = CLICK_DELAY;
3038 // release eventually hold auto-rotating mirror
3039 RotateMirror(x, y, MB_RELEASED);
3044 if (!FrameReached(&click_delay) && !new_button)
3047 if (button == MB_MIDDLEBUTTON) // middle button has no function
3050 if (!IN_LEV_FIELD(x, y))
3053 if (Tile[x][y] == EL_EMPTY)
3056 element = Tile[x][y];
3058 if (IS_MIRROR(element) ||
3059 IS_BEAMER(element) ||
3060 IS_POLAR(element) ||
3061 IS_POLAR_CROSS(element) ||
3062 IS_DF_MIRROR(element) ||
3063 IS_DF_MIRROR_AUTO(element))
3065 RotateMirror(x, y, button);
3067 element_clicked = TRUE;
3069 else if (IS_MCDUFFIN(element))
3071 if (!laser.fuse_off)
3073 DrawLaser(0, DL_LASER_DISABLED);
3080 element = get_rotated_element(element, BUTTON_ROTATION(button));
3081 laser.start_angle = get_element_angle(element);
3085 Tile[x][y] = element;
3092 if (!laser.fuse_off)
3095 element_clicked = TRUE;
3097 else if (element == EL_FUSE_ON && laser.fuse_off)
3099 if (x != laser.fuse_x || y != laser.fuse_y)
3102 laser.fuse_off = FALSE;
3103 laser.fuse_x = laser.fuse_y = -1;
3105 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3108 element_clicked = TRUE;
3110 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3112 laser.fuse_off = TRUE;
3115 laser.overloaded = FALSE;
3117 DrawLaser(0, DL_LASER_DISABLED);
3118 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3120 element_clicked = TRUE;
3122 else if (element == EL_LIGHTBALL)
3125 RaiseScoreElement_MM(element);
3126 DrawLaser(0, DL_LASER_ENABLED);
3128 element_clicked = TRUE;
3130 else if (IS_ENVELOPE(element))
3132 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3134 element_clicked = TRUE;
3137 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3140 return element_clicked;
3143 static void RotateMirror(int x, int y, int button)
3145 if (button == MB_RELEASED)
3147 // release eventually hold auto-rotating mirror
3154 if (IS_MIRROR(Tile[x][y]) ||
3155 IS_POLAR_CROSS(Tile[x][y]) ||
3156 IS_POLAR(Tile[x][y]) ||
3157 IS_BEAMER(Tile[x][y]) ||
3158 IS_DF_MIRROR(Tile[x][y]) ||
3159 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3160 IS_GRID_WOOD_AUTO(Tile[x][y]))
3162 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3164 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3166 if (button == MB_LEFTBUTTON)
3168 // left mouse button only for manual adjustment, no auto-rotating;
3169 // freeze mirror for until mouse button released
3173 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3175 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3179 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3181 int edge = Hit[x][y];
3187 DrawLaser(edge - 1, DL_LASER_DISABLED);
3191 else if (ObjHit(x, y, HIT_POS_CENTER))
3193 int edge = Hit[x][y];
3197 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3202 DrawLaser(edge - 1, DL_LASER_DISABLED);
3209 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3214 if ((IS_BEAMER(Tile[x][y]) ||
3215 IS_POLAR(Tile[x][y]) ||
3216 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3218 if (IS_BEAMER(Tile[x][y]))
3221 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3222 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3235 DrawLaser(0, DL_LASER_ENABLED);
3239 static void AutoRotateMirrors(void)
3243 if (!FrameReached(&rotate_delay))
3246 for (x = 0; x < lev_fieldx; x++)
3248 for (y = 0; y < lev_fieldy; y++)
3250 int element = Tile[x][y];
3252 // do not rotate objects hit by the laser after the game was solved
3253 if (game_mm.level_solved && Hit[x][y])
3256 if (IS_DF_MIRROR_AUTO(element) ||
3257 IS_GRID_WOOD_AUTO(element) ||
3258 IS_GRID_STEEL_AUTO(element) ||
3259 element == EL_REFRACTOR)
3260 RotateMirror(x, y, MB_RIGHTBUTTON);
3265 static boolean ObjHit(int obx, int oby, int bits)
3272 if (bits & HIT_POS_CENTER)
3274 if (CheckLaserPixel(cSX + obx + 15,
3279 if (bits & HIT_POS_EDGE)
3281 for (i = 0; i < 4; i++)
3282 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3283 cSY + oby + 31 * (i / 2)))
3287 if (bits & HIT_POS_BETWEEN)
3289 for (i = 0; i < 4; i++)
3290 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3291 cSY + 4 + oby + 22 * (i / 2)))
3298 static void DeletePacMan(int px, int py)
3304 if (game_mm.num_pacman <= 1)
3306 game_mm.num_pacman = 0;
3310 for (i = 0; i < game_mm.num_pacman; i++)
3311 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3314 game_mm.num_pacman--;
3316 for (j = i; j < game_mm.num_pacman; j++)
3318 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3319 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3320 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3324 static void GameActions_MM_Ext(void)
3331 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3334 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3336 element = Tile[x][y];
3338 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3339 StartMoving_MM(x, y);
3340 else if (IS_MOVING(x, y))
3341 ContinueMoving_MM(x, y);
3342 else if (IS_EXPLODING(element))
3343 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3344 else if (element == EL_EXIT_OPENING)
3346 else if (element == EL_GRAY_BALL_OPENING)
3348 else if (IS_ENVELOPE_OPENING(element))
3350 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3352 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3354 else if (IS_MIRROR(element) ||
3355 IS_MIRROR_FIXED(element) ||
3356 element == EL_PRISM)
3357 DrawFieldTwinkle(x, y);
3358 else if (element == EL_GRAY_BALL_ACTIVE ||
3359 element == EL_BOMB_ACTIVE ||
3360 element == EL_MINE_ACTIVE)
3361 DrawFieldAnimated_MM(x, y);
3362 else if (!IS_BLOCKED(x, y))
3363 DrawFieldAnimatedIfNeeded_MM(x, y);
3366 AutoRotateMirrors();
3369 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3371 // redraw after Explode_MM() ...
3373 DrawLaser(0, DL_LASER_ENABLED);
3374 laser.redraw = FALSE;
3379 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3383 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3385 DrawLaser(0, DL_LASER_DISABLED);
3390 // skip all following game actions if game is over
3391 if (game_mm.game_over)
3394 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3398 GameOver_MM(GAME_OVER_NO_ENERGY);
3403 if (FrameReached(&energy_delay))
3405 if (game_mm.energy_left > 0)
3406 game_mm.energy_left--;
3408 // when out of energy, wait another frame to play "out of time" sound
3411 element = laser.dest_element;
3414 if (element != Tile[ELX][ELY])
3416 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3417 element, Tile[ELX][ELY]);
3421 if (!laser.overloaded && laser.overload_value == 0 &&
3422 element != EL_BOMB &&
3423 element != EL_BOMB_ACTIVE &&
3424 element != EL_MINE &&
3425 element != EL_MINE_ACTIVE &&
3426 element != EL_GRAY_BALL &&
3427 element != EL_GRAY_BALL_ACTIVE &&
3428 element != EL_BLOCK_STONE &&
3429 element != EL_BLOCK_WOOD &&
3430 element != EL_FUSE_ON &&
3431 element != EL_FUEL_FULL &&
3432 !IS_WALL_ICE(element) &&
3433 !IS_WALL_AMOEBA(element))
3436 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3438 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3439 (!laser.overloaded && laser.overload_value > 0)) &&
3440 FrameReached(&overload_delay))
3442 if (laser.overloaded)
3443 laser.overload_value++;
3445 laser.overload_value--;
3447 if (game_mm.cheat_no_overload)
3449 laser.overloaded = FALSE;
3450 laser.overload_value = 0;
3453 game_mm.laser_overload_value = laser.overload_value;
3455 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3457 SetLaserColor(0xFF);
3459 DrawLaser(0, DL_LASER_ENABLED);
3462 if (!laser.overloaded)
3463 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3464 else if (setup.sound_loops)
3465 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3467 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3469 if (laser.overload_value == MAX_LASER_OVERLOAD)
3471 UpdateAndDisplayGameControlValues();
3475 GameOver_MM(GAME_OVER_OVERLOADED);
3486 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3488 if (game_mm.cheat_no_explosion)
3493 laser.dest_element = EL_EXPLODING_OPAQUE;
3498 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3500 laser.fuse_off = TRUE;
3504 DrawLaser(0, DL_LASER_DISABLED);
3505 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3508 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3510 if (!Store2[ELX][ELY]) // check if content element not yet determined
3512 int last_anim_random_frame = gfx.anim_random_frame;
3515 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3516 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3518 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3519 native_mm_level.ball_choice_mode, 0,
3520 game_mm.ball_choice_pos);
3522 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3523 gfx.anim_random_frame = last_anim_random_frame;
3525 game_mm.ball_choice_pos++;
3527 int new_element = native_mm_level.ball_content[element_pos];
3528 int new_element_base = map_wall_to_base_element(new_element);
3530 if (IS_WALL(new_element_base))
3532 // always use completely filled wall element
3533 new_element = new_element_base | 0x000f;
3535 else if (native_mm_level.rotate_ball_content &&
3536 get_num_elements(new_element) > 1)
3538 // randomly rotate newly created game element
3539 new_element = get_rotated_element(new_element, RND(16));
3542 Store[ELX][ELY] = new_element;
3543 Store2[ELX][ELY] = TRUE;
3546 if (native_mm_level.explode_ball)
3549 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3551 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3556 if (IS_WALL_ICE(element) && CT > 50)
3558 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3560 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3561 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3562 Store2[ELX][ELY] = laser.wall_mask;
3564 laser.dest_element = Tile[ELX][ELY];
3569 if (IS_WALL_AMOEBA(element) && CT > 60)
3572 int element2 = Tile[ELX][ELY];
3574 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3577 for (i = laser.num_damages - 1; i >= 0; i--)
3578 if (laser.damage[i].is_mirror)
3581 r = laser.num_edges;
3582 d = laser.num_damages;
3589 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3592 DrawLaser(0, DL_LASER_ENABLED);
3595 x = laser.damage[k1].x;
3596 y = laser.damage[k1].y;
3601 for (i = 0; i < 4; i++)
3603 if (laser.wall_mask & (1 << i))
3605 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3606 cSY + ELY * TILEY + 31 * (i / 2)))
3609 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3610 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3617 for (i = 0; i < 4; i++)
3619 if (laser.wall_mask & (1 << i))
3621 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3622 cSY + ELY * TILEY + 31 * (i / 2)))
3629 if (laser.num_beamers > 0 ||
3630 k1 < 1 || k2 < 4 || k3 < 4 ||
3631 CheckLaserPixel(cSX + ELX * TILEX + 14,
3632 cSY + ELY * TILEY + 14))
3634 laser.num_edges = r;
3635 laser.num_damages = d;
3637 DrawLaser(0, DL_LASER_DISABLED);
3640 Tile[ELX][ELY] = element | laser.wall_mask;
3642 int x = ELX, y = ELY;
3643 int wall_mask = laser.wall_mask;
3646 DrawLaser(0, DL_LASER_ENABLED);
3648 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3650 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3651 Store[x][y] = EL_WALL_AMOEBA_BASE;
3652 Store2[x][y] = wall_mask;
3657 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3658 laser.stops_inside_element && CT > native_mm_level.time_block)
3663 if (ABS(XS) > ABS(YS))
3670 for (i = 0; i < 4; i++)
3677 x = ELX + Step[k * 4].x;
3678 y = ELY + Step[k * 4].y;
3680 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3683 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3691 laser.overloaded = (element == EL_BLOCK_STONE);
3696 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3699 Tile[x][y] = element;
3701 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3704 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3706 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3707 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3715 if (element == EL_FUEL_FULL && CT > 10)
3717 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3718 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3720 for (i = start; i <= num_init_game_frames; i++)
3722 if (i == num_init_game_frames)
3723 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3724 else if (setup.sound_loops)
3725 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3727 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3729 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3731 UpdateAndDisplayGameControlValues();
3736 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3738 DrawField_MM(ELX, ELY);
3740 DrawLaser(0, DL_LASER_ENABLED);
3746 void GameActions_MM(struct MouseActionInfo action)
3748 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3749 boolean button_released = (action.button == MB_RELEASED);
3751 GameActions_MM_Ext();
3753 CheckSingleStepMode_MM(element_clicked, button_released);
3756 static void MovePacMen(void)
3758 int mx, my, ox, oy, nx, ny;
3762 if (++pacman_nr >= game_mm.num_pacman)
3765 game_mm.pacman[pacman_nr].dir--;
3767 for (l = 1; l < 5; l++)
3769 game_mm.pacman[pacman_nr].dir++;
3771 if (game_mm.pacman[pacman_nr].dir > 4)
3772 game_mm.pacman[pacman_nr].dir = 1;
3774 if (game_mm.pacman[pacman_nr].dir % 2)
3777 my = game_mm.pacman[pacman_nr].dir - 2;
3782 mx = 3 - game_mm.pacman[pacman_nr].dir;
3785 ox = game_mm.pacman[pacman_nr].x;
3786 oy = game_mm.pacman[pacman_nr].y;
3789 element = Tile[nx][ny];
3791 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3794 if (!IS_EATABLE4PACMAN(element))
3797 if (ObjHit(nx, ny, HIT_POS_CENTER))
3800 Tile[ox][oy] = EL_EMPTY;
3802 EL_PACMAN_RIGHT - 1 +
3803 (game_mm.pacman[pacman_nr].dir - 1 +
3804 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3806 game_mm.pacman[pacman_nr].x = nx;
3807 game_mm.pacman[pacman_nr].y = ny;
3809 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3811 if (element != EL_EMPTY)
3813 int graphic = el2gfx(Tile[nx][ny]);
3818 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3821 ox = cSX + ox * TILEX;
3822 oy = cSY + oy * TILEY;
3824 for (i = 1; i < 33; i += 2)
3825 BlitBitmap(bitmap, window,
3826 src_x, src_y, TILEX, TILEY,
3827 ox + i * mx, oy + i * my);
3828 Ct = Ct + FrameCounter - CT;
3831 DrawField_MM(nx, ny);
3834 if (!laser.fuse_off)
3836 DrawLaser(0, DL_LASER_ENABLED);
3838 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3840 AddDamagedField(nx, ny);
3842 laser.damage[laser.num_damages - 1].edge = 0;
3846 if (element == EL_BOMB)
3847 DeletePacMan(nx, ny);
3849 if (IS_WALL_AMOEBA(element) &&
3850 (LX + 2 * XS) / TILEX == nx &&
3851 (LY + 2 * YS) / TILEY == ny)
3861 static void InitMovingField_MM(int x, int y, int direction)
3863 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3864 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3866 MovDir[x][y] = direction;
3867 MovDir[newx][newy] = direction;
3869 if (Tile[newx][newy] == EL_EMPTY)
3870 Tile[newx][newy] = EL_BLOCKED;
3873 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3875 int direction = MovDir[x][y];
3876 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3877 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3883 static void Blocked2Moving_MM(int x, int y,
3884 int *comes_from_x, int *comes_from_y)
3886 int oldx = x, oldy = y;
3887 int direction = MovDir[x][y];
3889 if (direction == MV_LEFT)
3891 else if (direction == MV_RIGHT)
3893 else if (direction == MV_UP)
3895 else if (direction == MV_DOWN)
3898 *comes_from_x = oldx;
3899 *comes_from_y = oldy;
3902 static int MovingOrBlocked2Element_MM(int x, int y)
3904 int element = Tile[x][y];
3906 if (element == EL_BLOCKED)
3910 Blocked2Moving_MM(x, y, &oldx, &oldy);
3912 return Tile[oldx][oldy];
3918 static void RemoveMovingField_MM(int x, int y)
3920 int oldx = x, oldy = y, newx = x, newy = y;
3922 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3925 if (IS_MOVING(x, y))
3927 Moving2Blocked_MM(x, y, &newx, &newy);
3928 if (Tile[newx][newy] != EL_BLOCKED)
3931 else if (Tile[x][y] == EL_BLOCKED)
3933 Blocked2Moving_MM(x, y, &oldx, &oldy);
3934 if (!IS_MOVING(oldx, oldy))
3938 Tile[oldx][oldy] = EL_EMPTY;
3939 Tile[newx][newy] = EL_EMPTY;
3940 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3941 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3943 DrawLevelField_MM(oldx, oldy);
3944 DrawLevelField_MM(newx, newy);
3947 static void RaiseScore_MM(int value)
3949 game_mm.score += value;
3952 void RaiseScoreElement_MM(int element)
3957 case EL_PACMAN_RIGHT:
3959 case EL_PACMAN_LEFT:
3960 case EL_PACMAN_DOWN:
3961 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3965 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3970 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3974 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3983 // ----------------------------------------------------------------------------
3984 // Mirror Magic game engine snapshot handling functions
3985 // ----------------------------------------------------------------------------
3987 void SaveEngineSnapshotValues_MM(void)
3991 engine_snapshot_mm.game_mm = game_mm;
3992 engine_snapshot_mm.laser = laser;
3994 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3996 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3998 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3999 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4000 engine_snapshot_mm.Box[x][y] = Box[x][y];
4001 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4005 engine_snapshot_mm.LX = LX;
4006 engine_snapshot_mm.LY = LY;
4007 engine_snapshot_mm.XS = XS;
4008 engine_snapshot_mm.YS = YS;
4009 engine_snapshot_mm.ELX = ELX;
4010 engine_snapshot_mm.ELY = ELY;
4011 engine_snapshot_mm.CT = CT;
4012 engine_snapshot_mm.Ct = Ct;
4014 engine_snapshot_mm.last_LX = last_LX;
4015 engine_snapshot_mm.last_LY = last_LY;
4016 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4017 engine_snapshot_mm.hold_x = hold_x;
4018 engine_snapshot_mm.hold_y = hold_y;
4019 engine_snapshot_mm.pacman_nr = pacman_nr;
4021 engine_snapshot_mm.rotate_delay = rotate_delay;
4022 engine_snapshot_mm.pacman_delay = pacman_delay;
4023 engine_snapshot_mm.energy_delay = energy_delay;
4024 engine_snapshot_mm.overload_delay = overload_delay;
4027 void LoadEngineSnapshotValues_MM(void)
4031 // stored engine snapshot buffers already restored at this point
4033 game_mm = engine_snapshot_mm.game_mm;
4034 laser = engine_snapshot_mm.laser;
4036 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4038 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4040 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4041 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4042 Box[x][y] = engine_snapshot_mm.Box[x][y];
4043 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4047 LX = engine_snapshot_mm.LX;
4048 LY = engine_snapshot_mm.LY;
4049 XS = engine_snapshot_mm.XS;
4050 YS = engine_snapshot_mm.YS;
4051 ELX = engine_snapshot_mm.ELX;
4052 ELY = engine_snapshot_mm.ELY;
4053 CT = engine_snapshot_mm.CT;
4054 Ct = engine_snapshot_mm.Ct;
4056 last_LX = engine_snapshot_mm.last_LX;
4057 last_LY = engine_snapshot_mm.last_LY;
4058 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4059 hold_x = engine_snapshot_mm.hold_x;
4060 hold_y = engine_snapshot_mm.hold_y;
4061 pacman_nr = engine_snapshot_mm.pacman_nr;
4063 rotate_delay = engine_snapshot_mm.rotate_delay;
4064 pacman_delay = engine_snapshot_mm.pacman_delay;
4065 energy_delay = engine_snapshot_mm.energy_delay;
4066 overload_delay = engine_snapshot_mm.overload_delay;
4068 RedrawPlayfield_MM();
4071 static int getAngleFromTouchDelta(int dx, int dy, int base)
4073 double pi = 3.141592653;
4074 double rad = atan2((double)-dy, (double)dx);
4075 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4076 double deg = rad2 * 180.0 / pi;
4078 return (int)(deg * base / 360.0 + 0.5) % base;
4081 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4083 // calculate start (source) position to be at the middle of the tile
4084 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4085 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4086 int dx = dst_mx - src_mx;
4087 int dy = dst_my - src_my;
4096 if (!IN_LEV_FIELD(x, y))
4099 element = Tile[x][y];
4101 if (!IS_MCDUFFIN(element) &&
4102 !IS_MIRROR(element) &&
4103 !IS_BEAMER(element) &&
4104 !IS_POLAR(element) &&
4105 !IS_POLAR_CROSS(element) &&
4106 !IS_DF_MIRROR(element))
4109 angle_old = get_element_angle(element);
4111 if (IS_MCDUFFIN(element))
4113 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4114 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4115 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4116 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4119 else if (IS_MIRROR(element) ||
4120 IS_DF_MIRROR(element))
4122 for (i = 0; i < laser.num_damages; i++)
4124 if (laser.damage[i].x == x &&
4125 laser.damage[i].y == y &&
4126 ObjHit(x, y, HIT_POS_CENTER))
4128 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4129 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4136 if (angle_new == -1)
4138 if (IS_MIRROR(element) ||
4139 IS_DF_MIRROR(element) ||
4143 if (IS_POLAR_CROSS(element))
4146 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4149 button = (angle_new == angle_old ? 0 :
4150 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4151 MB_LEFTBUTTON : MB_RIGHTBUTTON);