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 return HitReflectingWalls(element, hit_mask);
2011 else // IS_GRID_WOOD
2013 return HitAbsorbingWalls(element, hit_mask);
2019 static boolean HitBlock(int element, int hit_mask)
2021 boolean check = FALSE;
2023 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2024 game_mm.num_keys == 0)
2027 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2030 int ex = ELX * TILEX + 14;
2031 int ey = ELY * TILEY + 14;
2035 for (i = 1; i < 32; i++)
2040 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2045 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2046 return HitAbsorbingWalls(element, hit_mask);
2050 AddLaserEdge(LX - XS, LY - YS);
2051 AddDamagedField(ELX, ELY);
2054 Box[ELX][ELY] = laser.num_edges;
2056 return HitReflectingWalls(element, hit_mask);
2059 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2061 int xs = XS / 2, ys = YS / 2;
2063 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2064 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2066 laser.overloaded = (element == EL_GATE_STONE);
2071 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2072 (hit_mask == HIT_MASK_TOP ||
2073 hit_mask == HIT_MASK_LEFT ||
2074 hit_mask == HIT_MASK_RIGHT ||
2075 hit_mask == HIT_MASK_BOTTOM))
2076 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2077 hit_mask == HIT_MASK_BOTTOM),
2078 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2079 hit_mask == HIT_MASK_RIGHT));
2080 AddLaserEdge(LX, LY);
2086 if (element == EL_GATE_STONE && Box[ELX][ELY])
2088 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2100 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2102 int xs = XS / 2, ys = YS / 2;
2104 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2105 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2107 laser.overloaded = (element == EL_BLOCK_STONE);
2112 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2113 (hit_mask == HIT_MASK_TOP ||
2114 hit_mask == HIT_MASK_LEFT ||
2115 hit_mask == HIT_MASK_RIGHT ||
2116 hit_mask == HIT_MASK_BOTTOM))
2117 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2118 hit_mask == HIT_MASK_BOTTOM),
2119 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2120 hit_mask == HIT_MASK_RIGHT));
2121 AddDamagedField(ELX, ELY);
2123 LX = ELX * TILEX + 14;
2124 LY = ELY * TILEY + 14;
2126 AddLaserEdge(LX, LY);
2128 laser.stops_inside_element = TRUE;
2136 static boolean HitLaserSource(int element, int hit_mask)
2138 if (HitOnlyAnEdge(hit_mask))
2141 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2143 laser.overloaded = TRUE;
2148 static boolean HitLaserDestination(int element, int hit_mask)
2150 if (HitOnlyAnEdge(hit_mask))
2153 if (element != EL_EXIT_OPEN &&
2154 !(IS_RECEIVER(element) &&
2155 game_mm.kettles_still_needed == 0 &&
2156 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2158 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2163 if (IS_RECEIVER(element) ||
2164 (IS_22_5_ANGLE(laser.current_angle) &&
2165 (ELX != (LX + 6 * XS) / TILEX ||
2166 ELY != (LY + 6 * YS) / TILEY ||
2175 LX = ELX * TILEX + 14;
2176 LY = ELY * TILEY + 14;
2178 laser.stops_inside_element = TRUE;
2181 AddLaserEdge(LX, LY);
2182 AddDamagedField(ELX, ELY);
2184 if (game_mm.lights_still_needed == 0)
2186 game_mm.level_solved = TRUE;
2188 SetTileCursorActive(FALSE);
2194 static boolean HitReflectingWalls(int element, int hit_mask)
2196 // check if laser hits side of a wall with an angle that is not 90°
2197 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2198 hit_mask == HIT_MASK_LEFT ||
2199 hit_mask == HIT_MASK_RIGHT ||
2200 hit_mask == HIT_MASK_BOTTOM))
2202 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2207 if (!IS_DF_GRID(element))
2208 AddLaserEdge(LX, LY);
2210 // check if laser hits wall with an angle of 45°
2211 if (!IS_22_5_ANGLE(laser.current_angle))
2213 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2216 laser.current_angle = get_mirrored_angle(laser.current_angle,
2219 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2222 laser.current_angle = get_mirrored_angle(laser.current_angle,
2226 AddLaserEdge(LX, LY);
2228 XS = 2 * Step[laser.current_angle].x;
2229 YS = 2 * Step[laser.current_angle].y;
2233 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2235 laser.current_angle = get_mirrored_angle(laser.current_angle,
2240 if (!IS_DF_GRID(element))
2241 AddLaserEdge(LX, LY);
2246 if (!IS_DF_GRID(element))
2247 AddLaserEdge(LX, LY + YS / 2);
2250 if (!IS_DF_GRID(element))
2251 AddLaserEdge(LX, LY);
2254 YS = 2 * Step[laser.current_angle].y;
2258 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2260 laser.current_angle = get_mirrored_angle(laser.current_angle,
2265 if (!IS_DF_GRID(element))
2266 AddLaserEdge(LX, LY);
2271 if (!IS_DF_GRID(element))
2272 AddLaserEdge(LX + XS / 2, LY);
2275 if (!IS_DF_GRID(element))
2276 AddLaserEdge(LX, LY);
2279 XS = 2 * Step[laser.current_angle].x;
2285 // reflection at the edge of reflecting DF style wall
2286 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2288 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2289 hit_mask == HIT_MASK_TOPRIGHT) ||
2290 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2291 hit_mask == HIT_MASK_TOPLEFT) ||
2292 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2293 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2294 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2295 hit_mask == HIT_MASK_BOTTOMRIGHT))
2298 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2299 ANG_MIRROR_135 : ANG_MIRROR_45);
2301 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2303 AddDamagedField(ELX, ELY);
2304 AddLaserEdge(LX, LY);
2306 laser.current_angle = get_mirrored_angle(laser.current_angle,
2314 AddLaserEdge(LX, LY);
2320 // reflection inside an edge of reflecting DF style wall
2321 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2323 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2324 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2325 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2326 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2327 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2328 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2329 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2330 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2333 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2334 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2335 ANG_MIRROR_135 : ANG_MIRROR_45);
2337 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2340 AddDamagedField(ELX, ELY);
2343 AddLaserEdge(LX - XS, LY - YS);
2344 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2345 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2347 laser.current_angle = get_mirrored_angle(laser.current_angle,
2355 AddLaserEdge(LX, LY);
2361 // check if laser hits DF style wall with an angle of 90°
2362 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2364 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2365 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2366 (IS_VERT_ANGLE(laser.current_angle) &&
2367 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2369 // laser at last step touched nothing or the same side of the wall
2370 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2372 AddDamagedField(ELX, ELY);
2379 last_hit_mask = hit_mask;
2386 if (!HitOnlyAnEdge(hit_mask))
2388 laser.overloaded = TRUE;
2396 static boolean HitAbsorbingWalls(int element, int hit_mask)
2398 if (HitOnlyAnEdge(hit_mask))
2402 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2404 AddLaserEdge(LX - XS, LY - YS);
2411 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2413 AddLaserEdge(LX - XS, LY - YS);
2419 if (IS_WALL_WOOD(element) ||
2420 IS_DF_WALL_WOOD(element) ||
2421 IS_GRID_WOOD(element) ||
2422 IS_GRID_WOOD_FIXED(element) ||
2423 IS_GRID_WOOD_AUTO(element) ||
2424 element == EL_FUSE_ON ||
2425 element == EL_BLOCK_WOOD ||
2426 element == EL_GATE_WOOD)
2428 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2433 if (IS_WALL_ICE(element))
2439 // check if laser hit adjacent edges of two diagonal tiles
2440 if (ELX != lx / TILEX)
2442 if (ELY != ly / TILEY)
2445 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2446 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2448 // check if laser hits wall with an angle of 90°
2449 if (IS_90_ANGLE(laser.current_angle))
2450 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2452 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2456 for (i = 0; i < 4; i++)
2458 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2459 mask = 15 - (8 >> i);
2460 else if (ABS(XS) == 4 &&
2462 (XS > 0) == (i % 2) &&
2463 (YS < 0) == (i / 2))
2464 mask = 3 + (i / 2) * 9;
2465 else if (ABS(YS) == 4 &&
2467 (XS < 0) == (i % 2) &&
2468 (YS > 0) == (i / 2))
2469 mask = 5 + (i % 2) * 5;
2473 laser.wall_mask = mask;
2475 else if (IS_WALL_AMOEBA(element))
2477 int elx = (LX - 2 * XS) / TILEX;
2478 int ely = (LY - 2 * YS) / TILEY;
2479 int element2 = Tile[elx][ely];
2482 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2484 laser.dest_element = EL_EMPTY;
2492 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2493 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2495 if (IS_90_ANGLE(laser.current_angle))
2496 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2498 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2500 laser.wall_mask = mask;
2506 static void OpenExit(int x, int y)
2510 if (!MovDelay[x][y]) // next animation frame
2511 MovDelay[x][y] = 4 * delay;
2513 if (MovDelay[x][y]) // wait some time before next frame
2518 phase = MovDelay[x][y] / delay;
2520 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2521 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2523 if (!MovDelay[x][y])
2525 Tile[x][y] = EL_EXIT_OPEN;
2531 static void OpenGrayBall(int x, int y)
2535 if (!MovDelay[x][y]) // next animation frame
2537 if (IS_WALL(Store[x][y]))
2539 DrawWalls_MM(x, y, Store[x][y]);
2541 // copy wall tile to spare bitmap for "melting" animation
2542 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2543 TILEX, TILEY, x * TILEX, y * TILEY);
2545 DrawElement_MM(x, y, EL_GRAY_BALL);
2548 MovDelay[x][y] = 50 * delay;
2551 if (MovDelay[x][y]) // wait some time before next frame
2555 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2559 int dx = RND(26), dy = RND(26);
2561 if (IS_WALL(Store[x][y]))
2563 // copy wall tile from spare bitmap for "melting" animation
2564 bitmap = bitmap_db_field;
2570 int graphic = el2gfx(Store[x][y]);
2572 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2575 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2576 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2578 laser.redraw = TRUE;
2580 MarkTileDirty(x, y);
2583 if (!MovDelay[x][y])
2585 Tile[x][y] = Store[x][y];
2586 Store[x][y] = Store2[x][y] = 0;
2587 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2589 InitField(x, y, FALSE);
2592 ScanLaser_FromLastMirror();
2597 static void OpenEnvelope(int x, int y)
2599 int num_frames = 8; // seven frames plus final empty space
2601 if (!MovDelay[x][y]) // next animation frame
2602 MovDelay[x][y] = num_frames;
2604 if (MovDelay[x][y]) // wait some time before next frame
2606 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2610 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2612 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2613 int frame = num_frames - MovDelay[x][y] - 1;
2615 DrawGraphicAnimation_MM(x, y, graphic, frame);
2617 laser.redraw = TRUE;
2620 if (MovDelay[x][y] == 0)
2622 Tile[x][y] = EL_EMPTY;
2628 ShowEnvelope_MM(nr);
2633 static void MeltIce(int x, int y)
2638 if (!MovDelay[x][y]) // next animation frame
2639 MovDelay[x][y] = frames * delay;
2641 if (MovDelay[x][y]) // wait some time before next frame
2644 int wall_mask = Store2[x][y];
2645 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2648 phase = frames - MovDelay[x][y] / delay - 1;
2650 if (!MovDelay[x][y])
2652 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2653 Store[x][y] = Store2[x][y] = 0;
2655 DrawWalls_MM(x, y, Tile[x][y]);
2657 if (Tile[x][y] == EL_WALL_ICE_BASE)
2658 Tile[x][y] = EL_EMPTY;
2660 ScanLaser_FromLastMirror();
2662 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2664 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2666 laser.redraw = TRUE;
2671 static void GrowAmoeba(int x, int y)
2676 if (!MovDelay[x][y]) // next animation frame
2677 MovDelay[x][y] = frames * delay;
2679 if (MovDelay[x][y]) // wait some time before next frame
2682 int wall_mask = Store2[x][y];
2683 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2686 phase = MovDelay[x][y] / delay;
2688 if (!MovDelay[x][y])
2690 Tile[x][y] = real_element;
2691 Store[x][y] = Store2[x][y] = 0;
2693 DrawWalls_MM(x, y, Tile[x][y]);
2694 DrawLaser(0, DL_LASER_ENABLED);
2696 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2698 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2703 static void DrawFieldAnimated_MM(int x, int y)
2707 laser.redraw = TRUE;
2710 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2712 int element = Tile[x][y];
2713 int graphic = el2gfx(element);
2715 if (!getGraphicInfo_NewFrame(x, y, graphic))
2720 laser.redraw = TRUE;
2723 static void DrawFieldTwinkle(int x, int y)
2725 if (MovDelay[x][y] != 0) // wait some time before next frame
2731 if (MovDelay[x][y] != 0)
2733 int graphic = IMG_TWINKLE_WHITE;
2734 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2736 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2739 laser.redraw = TRUE;
2743 static void Explode_MM(int x, int y, int phase, int mode)
2745 int num_phase = 9, delay = 2;
2746 int last_phase = num_phase * delay;
2747 int half_phase = (num_phase / 2) * delay;
2750 laser.redraw = TRUE;
2752 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2754 center_element = Tile[x][y];
2756 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2758 // put moving element to center field (and let it explode there)
2759 center_element = MovingOrBlocked2Element_MM(x, y);
2760 RemoveMovingField_MM(x, y);
2762 Tile[x][y] = center_element;
2765 if (center_element != EL_GRAY_BALL_ACTIVE)
2766 Store[x][y] = EL_EMPTY;
2767 Store2[x][y] = center_element;
2769 Tile[x][y] = EL_EXPLODING_OPAQUE;
2771 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2772 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2773 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2776 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2778 ExplodePhase[x][y] = 1;
2784 GfxFrame[x][y] = 0; // restart explosion animation
2786 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2788 center_element = Store2[x][y];
2790 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2792 Tile[x][y] = EL_EXPLODING_TRANSP;
2794 if (x == ELX && y == ELY)
2798 if (phase == last_phase)
2800 if (center_element == EL_BOMB_ACTIVE)
2802 DrawLaser(0, DL_LASER_DISABLED);
2805 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2807 GameOver_MM(GAME_OVER_DELAYED);
2809 laser.overloaded = FALSE;
2811 else if (IS_MCDUFFIN(center_element))
2813 GameOver_MM(GAME_OVER_BOMB);
2816 Tile[x][y] = Store[x][y];
2818 Store[x][y] = Store2[x][y] = 0;
2819 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2821 InitField(x, y, FALSE);
2824 if (center_element == EL_GRAY_BALL_ACTIVE)
2825 ScanLaser_FromLastMirror();
2827 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2829 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2830 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2832 DrawGraphicAnimation_MM(x, y, graphic, frame);
2834 MarkTileDirty(x, y);
2838 static void Bang_MM(int x, int y)
2840 int element = Tile[x][y];
2842 if (IS_PACMAN(element))
2843 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2844 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2845 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2846 else if (element == EL_KEY)
2847 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2849 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2851 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2854 static void TurnRound(int x, int y)
2866 { 0, 0 }, { 0, 0 }, { 0, 0 },
2871 int left, right, back;
2875 { MV_DOWN, MV_UP, MV_RIGHT },
2876 { MV_UP, MV_DOWN, MV_LEFT },
2878 { MV_LEFT, MV_RIGHT, MV_DOWN },
2879 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2880 { MV_RIGHT, MV_LEFT, MV_UP }
2883 int element = Tile[x][y];
2884 int old_move_dir = MovDir[x][y];
2885 int right_dir = turn[old_move_dir].right;
2886 int back_dir = turn[old_move_dir].back;
2887 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2888 int right_x = x + right_dx, right_y = y + right_dy;
2890 if (element == EL_PACMAN)
2892 boolean can_turn_right = FALSE;
2894 if (IN_LEV_FIELD(right_x, right_y) &&
2895 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2896 can_turn_right = TRUE;
2899 MovDir[x][y] = right_dir;
2901 MovDir[x][y] = back_dir;
2907 static void StartMoving_MM(int x, int y)
2909 int element = Tile[x][y];
2914 if (CAN_MOVE(element))
2918 if (MovDelay[x][y]) // wait some time before next movement
2926 // now make next step
2928 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2930 if (element == EL_PACMAN &&
2931 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2932 !ObjHit(newx, newy, HIT_POS_CENTER))
2934 Store[newx][newy] = Tile[newx][newy];
2935 Tile[newx][newy] = EL_EMPTY;
2937 DrawField_MM(newx, newy);
2939 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2940 ObjHit(newx, newy, HIT_POS_CENTER))
2942 // object was running against a wall
2949 InitMovingField_MM(x, y, MovDir[x][y]);
2953 ContinueMoving_MM(x, y);
2956 static void ContinueMoving_MM(int x, int y)
2958 int element = Tile[x][y];
2959 int direction = MovDir[x][y];
2960 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2961 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2962 int horiz_move = (dx!=0);
2963 int newx = x + dx, newy = y + dy;
2964 int step = (horiz_move ? dx : dy) * TILEX / 8;
2966 MovPos[x][y] += step;
2968 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2970 Tile[x][y] = EL_EMPTY;
2971 Tile[newx][newy] = element;
2973 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2974 MovDelay[newx][newy] = 0;
2976 if (!CAN_MOVE(element))
2977 MovDir[newx][newy] = 0;
2980 DrawField_MM(newx, newy);
2982 Stop[newx][newy] = TRUE;
2984 if (element == EL_PACMAN)
2986 if (Store[newx][newy] == EL_BOMB)
2987 Bang_MM(newx, newy);
2989 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2990 (LX + 2 * XS) / TILEX == newx &&
2991 (LY + 2 * YS) / TILEY == newy)
2998 else // still moving on
3003 laser.redraw = TRUE;
3006 boolean ClickElement(int x, int y, int button)
3008 static DelayCounter click_delay = { CLICK_DELAY };
3009 static boolean new_button = TRUE;
3010 boolean element_clicked = FALSE;
3015 // initialize static variables
3016 click_delay.count = 0;
3017 click_delay.value = CLICK_DELAY;
3023 // do not rotate objects hit by the laser after the game was solved
3024 if (game_mm.level_solved && Hit[x][y])
3027 if (button == MB_RELEASED)
3030 click_delay.value = CLICK_DELAY;
3032 // release eventually hold auto-rotating mirror
3033 RotateMirror(x, y, MB_RELEASED);
3038 if (!FrameReached(&click_delay) && !new_button)
3041 if (button == MB_MIDDLEBUTTON) // middle button has no function
3044 if (!IN_LEV_FIELD(x, y))
3047 if (Tile[x][y] == EL_EMPTY)
3050 element = Tile[x][y];
3052 if (IS_MIRROR(element) ||
3053 IS_BEAMER(element) ||
3054 IS_POLAR(element) ||
3055 IS_POLAR_CROSS(element) ||
3056 IS_DF_MIRROR(element) ||
3057 IS_DF_MIRROR_AUTO(element))
3059 RotateMirror(x, y, button);
3061 element_clicked = TRUE;
3063 else if (IS_MCDUFFIN(element))
3065 if (!laser.fuse_off)
3067 DrawLaser(0, DL_LASER_DISABLED);
3074 element = get_rotated_element(element, BUTTON_ROTATION(button));
3075 laser.start_angle = get_element_angle(element);
3079 Tile[x][y] = element;
3086 if (!laser.fuse_off)
3089 element_clicked = TRUE;
3091 else if (element == EL_FUSE_ON && laser.fuse_off)
3093 if (x != laser.fuse_x || y != laser.fuse_y)
3096 laser.fuse_off = FALSE;
3097 laser.fuse_x = laser.fuse_y = -1;
3099 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3102 element_clicked = TRUE;
3104 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3106 laser.fuse_off = TRUE;
3109 laser.overloaded = FALSE;
3111 DrawLaser(0, DL_LASER_DISABLED);
3112 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3114 element_clicked = TRUE;
3116 else if (element == EL_LIGHTBALL)
3119 RaiseScoreElement_MM(element);
3120 DrawLaser(0, DL_LASER_ENABLED);
3122 element_clicked = TRUE;
3124 else if (IS_ENVELOPE(element))
3126 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3128 element_clicked = TRUE;
3131 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3134 return element_clicked;
3137 static void RotateMirror(int x, int y, int button)
3139 if (button == MB_RELEASED)
3141 // release eventually hold auto-rotating mirror
3148 if (IS_MIRROR(Tile[x][y]) ||
3149 IS_POLAR_CROSS(Tile[x][y]) ||
3150 IS_POLAR(Tile[x][y]) ||
3151 IS_BEAMER(Tile[x][y]) ||
3152 IS_DF_MIRROR(Tile[x][y]) ||
3153 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3154 IS_GRID_WOOD_AUTO(Tile[x][y]))
3156 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3158 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3160 if (button == MB_LEFTBUTTON)
3162 // left mouse button only for manual adjustment, no auto-rotating;
3163 // freeze mirror for until mouse button released
3167 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3169 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3173 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3175 int edge = Hit[x][y];
3181 DrawLaser(edge - 1, DL_LASER_DISABLED);
3185 else if (ObjHit(x, y, HIT_POS_CENTER))
3187 int edge = Hit[x][y];
3191 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3196 DrawLaser(edge - 1, DL_LASER_DISABLED);
3203 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3208 if ((IS_BEAMER(Tile[x][y]) ||
3209 IS_POLAR(Tile[x][y]) ||
3210 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3212 if (IS_BEAMER(Tile[x][y]))
3215 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3216 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3229 DrawLaser(0, DL_LASER_ENABLED);
3233 static void AutoRotateMirrors(void)
3237 if (!FrameReached(&rotate_delay))
3240 for (x = 0; x < lev_fieldx; x++)
3242 for (y = 0; y < lev_fieldy; y++)
3244 int element = Tile[x][y];
3246 // do not rotate objects hit by the laser after the game was solved
3247 if (game_mm.level_solved && Hit[x][y])
3250 if (IS_DF_MIRROR_AUTO(element) ||
3251 IS_GRID_WOOD_AUTO(element) ||
3252 IS_GRID_STEEL_AUTO(element) ||
3253 element == EL_REFRACTOR)
3254 RotateMirror(x, y, MB_RIGHTBUTTON);
3259 static boolean ObjHit(int obx, int oby, int bits)
3266 if (bits & HIT_POS_CENTER)
3268 if (CheckLaserPixel(cSX + obx + 15,
3273 if (bits & HIT_POS_EDGE)
3275 for (i = 0; i < 4; i++)
3276 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3277 cSY + oby + 31 * (i / 2)))
3281 if (bits & HIT_POS_BETWEEN)
3283 for (i = 0; i < 4; i++)
3284 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3285 cSY + 4 + oby + 22 * (i / 2)))
3292 static void DeletePacMan(int px, int py)
3298 if (game_mm.num_pacman <= 1)
3300 game_mm.num_pacman = 0;
3304 for (i = 0; i < game_mm.num_pacman; i++)
3305 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3308 game_mm.num_pacman--;
3310 for (j = i; j < game_mm.num_pacman; j++)
3312 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3313 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3314 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3318 static void GameActions_MM_Ext(void)
3325 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3328 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3330 element = Tile[x][y];
3332 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3333 StartMoving_MM(x, y);
3334 else if (IS_MOVING(x, y))
3335 ContinueMoving_MM(x, y);
3336 else if (IS_EXPLODING(element))
3337 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3338 else if (element == EL_EXIT_OPENING)
3340 else if (element == EL_GRAY_BALL_OPENING)
3342 else if (IS_ENVELOPE_OPENING(element))
3344 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3346 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3348 else if (IS_MIRROR(element) ||
3349 IS_MIRROR_FIXED(element) ||
3350 element == EL_PRISM)
3351 DrawFieldTwinkle(x, y);
3352 else if (element == EL_GRAY_BALL_ACTIVE ||
3353 element == EL_BOMB_ACTIVE ||
3354 element == EL_MINE_ACTIVE)
3355 DrawFieldAnimated_MM(x, y);
3356 else if (!IS_BLOCKED(x, y))
3357 DrawFieldAnimatedIfNeeded_MM(x, y);
3360 AutoRotateMirrors();
3363 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3365 // redraw after Explode_MM() ...
3367 DrawLaser(0, DL_LASER_ENABLED);
3368 laser.redraw = FALSE;
3373 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3377 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3379 DrawLaser(0, DL_LASER_DISABLED);
3384 // skip all following game actions if game is over
3385 if (game_mm.game_over)
3388 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3392 GameOver_MM(GAME_OVER_NO_ENERGY);
3397 if (FrameReached(&energy_delay))
3399 if (game_mm.energy_left > 0)
3400 game_mm.energy_left--;
3402 // when out of energy, wait another frame to play "out of time" sound
3405 element = laser.dest_element;
3408 if (element != Tile[ELX][ELY])
3410 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3411 element, Tile[ELX][ELY]);
3415 if (!laser.overloaded && laser.overload_value == 0 &&
3416 element != EL_BOMB &&
3417 element != EL_BOMB_ACTIVE &&
3418 element != EL_MINE &&
3419 element != EL_MINE_ACTIVE &&
3420 element != EL_GRAY_BALL &&
3421 element != EL_GRAY_BALL_ACTIVE &&
3422 element != EL_BLOCK_STONE &&
3423 element != EL_BLOCK_WOOD &&
3424 element != EL_FUSE_ON &&
3425 element != EL_FUEL_FULL &&
3426 !IS_WALL_ICE(element) &&
3427 !IS_WALL_AMOEBA(element))
3430 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3432 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3433 (!laser.overloaded && laser.overload_value > 0)) &&
3434 FrameReached(&overload_delay))
3436 if (laser.overloaded)
3437 laser.overload_value++;
3439 laser.overload_value--;
3441 if (game_mm.cheat_no_overload)
3443 laser.overloaded = FALSE;
3444 laser.overload_value = 0;
3447 game_mm.laser_overload_value = laser.overload_value;
3449 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3451 SetLaserColor(0xFF);
3453 DrawLaser(0, DL_LASER_ENABLED);
3456 if (!laser.overloaded)
3457 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3458 else if (setup.sound_loops)
3459 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3461 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3463 if (laser.overload_value == MAX_LASER_OVERLOAD)
3465 UpdateAndDisplayGameControlValues();
3469 GameOver_MM(GAME_OVER_OVERLOADED);
3480 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3482 if (game_mm.cheat_no_explosion)
3487 laser.dest_element = EL_EXPLODING_OPAQUE;
3492 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3494 laser.fuse_off = TRUE;
3498 DrawLaser(0, DL_LASER_DISABLED);
3499 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3502 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3504 if (!Store2[ELX][ELY]) // check if content element not yet determined
3506 int last_anim_random_frame = gfx.anim_random_frame;
3509 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3510 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3512 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3513 native_mm_level.ball_choice_mode, 0,
3514 game_mm.ball_choice_pos);
3516 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3517 gfx.anim_random_frame = last_anim_random_frame;
3519 game_mm.ball_choice_pos++;
3521 int new_element = native_mm_level.ball_content[element_pos];
3522 int new_element_base = map_wall_to_base_element(new_element);
3524 if (IS_WALL(new_element_base))
3526 // always use completely filled wall element
3527 new_element = new_element_base | 0x000f;
3529 else if (native_mm_level.rotate_ball_content &&
3530 get_num_elements(new_element) > 1)
3532 // randomly rotate newly created game element
3533 new_element = get_rotated_element(new_element, RND(16));
3536 Store[ELX][ELY] = new_element;
3537 Store2[ELX][ELY] = TRUE;
3540 if (native_mm_level.explode_ball)
3543 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3545 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3550 if (IS_WALL_ICE(element) && CT > 50)
3552 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3554 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3555 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3556 Store2[ELX][ELY] = laser.wall_mask;
3558 laser.dest_element = Tile[ELX][ELY];
3563 if (IS_WALL_AMOEBA(element) && CT > 60)
3566 int element2 = Tile[ELX][ELY];
3568 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3571 for (i = laser.num_damages - 1; i >= 0; i--)
3572 if (laser.damage[i].is_mirror)
3575 r = laser.num_edges;
3576 d = laser.num_damages;
3583 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3586 DrawLaser(0, DL_LASER_ENABLED);
3589 x = laser.damage[k1].x;
3590 y = laser.damage[k1].y;
3595 for (i = 0; i < 4; i++)
3597 if (laser.wall_mask & (1 << i))
3599 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3600 cSY + ELY * TILEY + 31 * (i / 2)))
3603 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3604 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3611 for (i = 0; i < 4; i++)
3613 if (laser.wall_mask & (1 << i))
3615 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3616 cSY + ELY * TILEY + 31 * (i / 2)))
3623 if (laser.num_beamers > 0 ||
3624 k1 < 1 || k2 < 4 || k3 < 4 ||
3625 CheckLaserPixel(cSX + ELX * TILEX + 14,
3626 cSY + ELY * TILEY + 14))
3628 laser.num_edges = r;
3629 laser.num_damages = d;
3631 DrawLaser(0, DL_LASER_DISABLED);
3634 Tile[ELX][ELY] = element | laser.wall_mask;
3636 int x = ELX, y = ELY;
3637 int wall_mask = laser.wall_mask;
3640 DrawLaser(0, DL_LASER_ENABLED);
3642 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3644 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3645 Store[x][y] = EL_WALL_AMOEBA_BASE;
3646 Store2[x][y] = wall_mask;
3651 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3652 laser.stops_inside_element && CT > native_mm_level.time_block)
3657 if (ABS(XS) > ABS(YS))
3664 for (i = 0; i < 4; i++)
3671 x = ELX + Step[k * 4].x;
3672 y = ELY + Step[k * 4].y;
3674 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3677 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3685 laser.overloaded = (element == EL_BLOCK_STONE);
3690 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3693 Tile[x][y] = element;
3695 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3698 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3700 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3701 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3709 if (element == EL_FUEL_FULL && CT > 10)
3711 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3712 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3714 for (i = start; i <= num_init_game_frames; i++)
3716 if (i == num_init_game_frames)
3717 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3718 else if (setup.sound_loops)
3719 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3721 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3723 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3725 UpdateAndDisplayGameControlValues();
3730 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3732 DrawField_MM(ELX, ELY);
3734 DrawLaser(0, DL_LASER_ENABLED);
3740 void GameActions_MM(struct MouseActionInfo action)
3742 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3743 boolean button_released = (action.button == MB_RELEASED);
3745 GameActions_MM_Ext();
3747 CheckSingleStepMode_MM(element_clicked, button_released);
3750 static void MovePacMen(void)
3752 int mx, my, ox, oy, nx, ny;
3756 if (++pacman_nr >= game_mm.num_pacman)
3759 game_mm.pacman[pacman_nr].dir--;
3761 for (l = 1; l < 5; l++)
3763 game_mm.pacman[pacman_nr].dir++;
3765 if (game_mm.pacman[pacman_nr].dir > 4)
3766 game_mm.pacman[pacman_nr].dir = 1;
3768 if (game_mm.pacman[pacman_nr].dir % 2)
3771 my = game_mm.pacman[pacman_nr].dir - 2;
3776 mx = 3 - game_mm.pacman[pacman_nr].dir;
3779 ox = game_mm.pacman[pacman_nr].x;
3780 oy = game_mm.pacman[pacman_nr].y;
3783 element = Tile[nx][ny];
3785 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3788 if (!IS_EATABLE4PACMAN(element))
3791 if (ObjHit(nx, ny, HIT_POS_CENTER))
3794 Tile[ox][oy] = EL_EMPTY;
3796 EL_PACMAN_RIGHT - 1 +
3797 (game_mm.pacman[pacman_nr].dir - 1 +
3798 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3800 game_mm.pacman[pacman_nr].x = nx;
3801 game_mm.pacman[pacman_nr].y = ny;
3803 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3805 if (element != EL_EMPTY)
3807 int graphic = el2gfx(Tile[nx][ny]);
3812 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3815 ox = cSX + ox * TILEX;
3816 oy = cSY + oy * TILEY;
3818 for (i = 1; i < 33; i += 2)
3819 BlitBitmap(bitmap, window,
3820 src_x, src_y, TILEX, TILEY,
3821 ox + i * mx, oy + i * my);
3822 Ct = Ct + FrameCounter - CT;
3825 DrawField_MM(nx, ny);
3828 if (!laser.fuse_off)
3830 DrawLaser(0, DL_LASER_ENABLED);
3832 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3834 AddDamagedField(nx, ny);
3836 laser.damage[laser.num_damages - 1].edge = 0;
3840 if (element == EL_BOMB)
3841 DeletePacMan(nx, ny);
3843 if (IS_WALL_AMOEBA(element) &&
3844 (LX + 2 * XS) / TILEX == nx &&
3845 (LY + 2 * YS) / TILEY == ny)
3855 static void InitMovingField_MM(int x, int y, int direction)
3857 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3858 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3860 MovDir[x][y] = direction;
3861 MovDir[newx][newy] = direction;
3863 if (Tile[newx][newy] == EL_EMPTY)
3864 Tile[newx][newy] = EL_BLOCKED;
3867 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3869 int direction = MovDir[x][y];
3870 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3871 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3877 static void Blocked2Moving_MM(int x, int y,
3878 int *comes_from_x, int *comes_from_y)
3880 int oldx = x, oldy = y;
3881 int direction = MovDir[x][y];
3883 if (direction == MV_LEFT)
3885 else if (direction == MV_RIGHT)
3887 else if (direction == MV_UP)
3889 else if (direction == MV_DOWN)
3892 *comes_from_x = oldx;
3893 *comes_from_y = oldy;
3896 static int MovingOrBlocked2Element_MM(int x, int y)
3898 int element = Tile[x][y];
3900 if (element == EL_BLOCKED)
3904 Blocked2Moving_MM(x, y, &oldx, &oldy);
3906 return Tile[oldx][oldy];
3912 static void RemoveMovingField_MM(int x, int y)
3914 int oldx = x, oldy = y, newx = x, newy = y;
3916 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3919 if (IS_MOVING(x, y))
3921 Moving2Blocked_MM(x, y, &newx, &newy);
3922 if (Tile[newx][newy] != EL_BLOCKED)
3925 else if (Tile[x][y] == EL_BLOCKED)
3927 Blocked2Moving_MM(x, y, &oldx, &oldy);
3928 if (!IS_MOVING(oldx, oldy))
3932 Tile[oldx][oldy] = EL_EMPTY;
3933 Tile[newx][newy] = EL_EMPTY;
3934 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3935 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3937 DrawLevelField_MM(oldx, oldy);
3938 DrawLevelField_MM(newx, newy);
3941 static void RaiseScore_MM(int value)
3943 game_mm.score += value;
3946 void RaiseScoreElement_MM(int element)
3951 case EL_PACMAN_RIGHT:
3953 case EL_PACMAN_LEFT:
3954 case EL_PACMAN_DOWN:
3955 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3959 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3964 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3968 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3977 // ----------------------------------------------------------------------------
3978 // Mirror Magic game engine snapshot handling functions
3979 // ----------------------------------------------------------------------------
3981 void SaveEngineSnapshotValues_MM(void)
3985 engine_snapshot_mm.game_mm = game_mm;
3986 engine_snapshot_mm.laser = laser;
3988 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3990 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3992 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3993 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3994 engine_snapshot_mm.Box[x][y] = Box[x][y];
3995 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3999 engine_snapshot_mm.LX = LX;
4000 engine_snapshot_mm.LY = LY;
4001 engine_snapshot_mm.XS = XS;
4002 engine_snapshot_mm.YS = YS;
4003 engine_snapshot_mm.ELX = ELX;
4004 engine_snapshot_mm.ELY = ELY;
4005 engine_snapshot_mm.CT = CT;
4006 engine_snapshot_mm.Ct = Ct;
4008 engine_snapshot_mm.last_LX = last_LX;
4009 engine_snapshot_mm.last_LY = last_LY;
4010 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4011 engine_snapshot_mm.hold_x = hold_x;
4012 engine_snapshot_mm.hold_y = hold_y;
4013 engine_snapshot_mm.pacman_nr = pacman_nr;
4015 engine_snapshot_mm.rotate_delay = rotate_delay;
4016 engine_snapshot_mm.pacman_delay = pacman_delay;
4017 engine_snapshot_mm.energy_delay = energy_delay;
4018 engine_snapshot_mm.overload_delay = overload_delay;
4021 void LoadEngineSnapshotValues_MM(void)
4025 // stored engine snapshot buffers already restored at this point
4027 game_mm = engine_snapshot_mm.game_mm;
4028 laser = engine_snapshot_mm.laser;
4030 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4032 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4034 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4035 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4036 Box[x][y] = engine_snapshot_mm.Box[x][y];
4037 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4041 LX = engine_snapshot_mm.LX;
4042 LY = engine_snapshot_mm.LY;
4043 XS = engine_snapshot_mm.XS;
4044 YS = engine_snapshot_mm.YS;
4045 ELX = engine_snapshot_mm.ELX;
4046 ELY = engine_snapshot_mm.ELY;
4047 CT = engine_snapshot_mm.CT;
4048 Ct = engine_snapshot_mm.Ct;
4050 last_LX = engine_snapshot_mm.last_LX;
4051 last_LY = engine_snapshot_mm.last_LY;
4052 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4053 hold_x = engine_snapshot_mm.hold_x;
4054 hold_y = engine_snapshot_mm.hold_y;
4055 pacman_nr = engine_snapshot_mm.pacman_nr;
4057 rotate_delay = engine_snapshot_mm.rotate_delay;
4058 pacman_delay = engine_snapshot_mm.pacman_delay;
4059 energy_delay = engine_snapshot_mm.energy_delay;
4060 overload_delay = engine_snapshot_mm.overload_delay;
4062 RedrawPlayfield_MM();
4065 static int getAngleFromTouchDelta(int dx, int dy, int base)
4067 double pi = 3.141592653;
4068 double rad = atan2((double)-dy, (double)dx);
4069 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4070 double deg = rad2 * 180.0 / pi;
4072 return (int)(deg * base / 360.0 + 0.5) % base;
4075 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4077 // calculate start (source) position to be at the middle of the tile
4078 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4079 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4080 int dx = dst_mx - src_mx;
4081 int dy = dst_my - src_my;
4090 if (!IN_LEV_FIELD(x, y))
4093 element = Tile[x][y];
4095 if (!IS_MCDUFFIN(element) &&
4096 !IS_MIRROR(element) &&
4097 !IS_BEAMER(element) &&
4098 !IS_POLAR(element) &&
4099 !IS_POLAR_CROSS(element) &&
4100 !IS_DF_MIRROR(element))
4103 angle_old = get_element_angle(element);
4105 if (IS_MCDUFFIN(element))
4107 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4108 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4109 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4110 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4113 else if (IS_MIRROR(element) ||
4114 IS_DF_MIRROR(element))
4116 for (i = 0; i < laser.num_damages; i++)
4118 if (laser.damage[i].x == x &&
4119 laser.damage[i].y == y &&
4120 ObjHit(x, y, HIT_POS_CENTER))
4122 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4123 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4130 if (angle_new == -1)
4132 if (IS_MIRROR(element) ||
4133 IS_DF_MIRROR(element) ||
4137 if (IS_POLAR_CROSS(element))
4140 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4143 button = (angle_new == angle_old ? 0 :
4144 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4145 MB_LEFTBUTTON : MB_RIGHTBUTTON);