1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
28 #define EX_TYPE_NONE 0
29 #define EX_TYPE_NORMAL (1 << 0)
31 // special positions in the game control window (relative to control window)
40 #define XX_OVERLOAD 60
41 #define YY_OVERLOAD YY_ENERGY
43 // special positions in the game control window (relative to main window)
44 #define DX_LEVEL (DX + XX_LEVEL)
45 #define DY_LEVEL (DY + YY_LEVEL)
46 #define DX_KETTLES (DX + XX_KETTLES)
47 #define DY_KETTLES (DY + YY_KETTLES)
48 #define DX_SCORE (DX + XX_SCORE)
49 #define DY_SCORE (DY + YY_SCORE)
50 #define DX_ENERGY (DX + XX_ENERGY)
51 #define DY_ENERGY (DY + YY_ENERGY)
52 #define DX_OVERLOAD (DX + XX_OVERLOAD)
53 #define DY_OVERLOAD (DY + YY_OVERLOAD)
55 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
56 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
58 // game button identifiers
59 #define GAME_CTRL_ID_LEFT 0
60 #define GAME_CTRL_ID_MIDDLE 1
61 #define GAME_CTRL_ID_RIGHT 2
63 #define NUM_GAME_BUTTONS 3
65 // values for DrawLaser()
66 #define DL_LASER_DISABLED 0
67 #define DL_LASER_ENABLED 1
69 // values for 'click_delay_value' in ClickElement()
70 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
71 #define CLICK_DELAY 6 // delay (frames) for pressed butten
73 #define AUTO_ROTATE_DELAY CLICK_DELAY
74 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
75 #define NUM_INIT_CYCLE_STEPS 16
76 #define PACMAN_MOVE_DELAY 12
77 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
78 #define HEALTH_DEC_DELAY 3
79 #define HEALTH_INC_DELAY 9
80 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
82 #define BEGIN_NO_HEADLESS \
84 boolean last_headless = program.headless; \
86 program.headless = FALSE; \
88 #define END_NO_HEADLESS \
89 program.headless = last_headless; \
92 // forward declaration for internal use
93 static int MovingOrBlocked2Element_MM(int, int);
94 static void Bang_MM(int, int);
95 static void RaiseScore_MM(int);
96 static void RaiseScoreElement_MM(int);
97 static void RemoveMovingField_MM(int, int);
98 static void InitMovingField_MM(int, int, int);
99 static void ContinueMoving_MM(int, int);
100 static void Moving2Blocked_MM(int, int, int *, int *);
102 static void AddLaserEdge(int, int);
103 static void ScanLaser(void);
104 static void DrawLaser(int, int);
105 static boolean HitElement(int, int);
106 static boolean HitOnlyAnEdge(int);
107 static boolean HitPolarizer(int, int);
108 static boolean HitBlock(int, int);
109 static boolean HitLaserSource(int, int);
110 static boolean HitLaserDestination(int, int);
111 static boolean HitReflectingWalls(int, int);
112 static boolean HitAbsorbingWalls(int, int);
113 static void RotateMirror(int, int, int);
114 static boolean ObjHit(int, int, int);
115 static void DeletePacMan(int, int);
116 static void MovePacMen(void);
118 // bitmap for laser beam detection
119 static Bitmap *laser_bitmap = NULL;
121 // variables for laser control
122 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
123 static int hold_x = -1, hold_y = -1;
125 // variables for pacman control
126 static int pacman_nr = -1;
128 // various game engine delay counters
129 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
130 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
131 static DelayCounter energy_delay = { ENERGY_DELAY };
132 static DelayCounter overload_delay = { 0 };
134 // element mask positions for scanning pixels of MM elements
135 #define MM_MASK_MCDUFFIN_RIGHT 0
136 #define MM_MASK_MCDUFFIN_UP 1
137 #define MM_MASK_MCDUFFIN_LEFT 2
138 #define MM_MASK_MCDUFFIN_DOWN 3
139 #define MM_MASK_GRID_1 4
140 #define MM_MASK_GRID_2 5
141 #define MM_MASK_GRID_3 6
142 #define MM_MASK_GRID_4 7
143 #define MM_MASK_RECTANGLE 8
144 #define MM_MASK_CIRCLE 9
146 #define NUM_MM_MASKS 10
148 // element masks for scanning pixels of MM elements
149 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
333 static int get_element_angle(int element)
335 int element_phase = get_element_phase(element);
337 if (IS_MIRROR_FIXED(element) ||
338 IS_MCDUFFIN(element) ||
340 IS_RECEIVER(element))
341 return 4 * element_phase;
343 return element_phase;
346 static int get_opposite_angle(int angle)
348 int opposite_angle = angle + ANG_RAY_180;
350 // make sure "opposite_angle" is in valid interval [0, 15]
351 return (opposite_angle + 16) % 16;
354 static int get_mirrored_angle(int laser_angle, int mirror_angle)
356 int reflected_angle = 16 - laser_angle + mirror_angle;
358 // make sure "reflected_angle" is in valid interval [0, 15]
359 return (reflected_angle + 16) % 16;
362 static void DrawLaserLines(struct XY *points, int num_points, int mode)
364 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
365 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
367 DrawLines(drawto, points, num_points, pixel_drawto);
371 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
376 static boolean CheckLaserPixel(int x, int y)
382 pixel = ReadPixel(laser_bitmap, x, y);
386 return (pixel == WHITE_PIXEL);
389 static void CheckExitMM(void)
391 int exit_element = EL_EMPTY;
395 static int xy[4][2] =
403 for (y = 0; y < lev_fieldy; y++)
405 for (x = 0; x < lev_fieldx; x++)
407 if (Tile[x][y] == EL_EXIT_CLOSED)
409 // initiate opening animation of exit door
410 Tile[x][y] = EL_EXIT_OPENING;
412 exit_element = EL_EXIT_OPEN;
416 else if (IS_RECEIVER(Tile[x][y]))
418 // remove field that blocks receiver
419 int phase = Tile[x][y] - EL_RECEIVER_START;
420 int blocking_x, blocking_y;
422 blocking_x = x + xy[phase][0];
423 blocking_y = y + xy[phase][1];
425 if (IN_LEV_FIELD(blocking_x, blocking_y))
427 Tile[blocking_x][blocking_y] = EL_EMPTY;
429 DrawField_MM(blocking_x, blocking_y);
432 exit_element = EL_RECEIVER;
439 if (exit_element != EL_EMPTY)
440 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
443 static void SetLaserColor(int brightness)
445 int color_min = 0x00;
446 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
447 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
448 int color_down = color_max - color_up;
451 GetPixelFromRGB(window,
452 (game_mm.laser_red ? color_max : color_up),
453 (game_mm.laser_green ? color_down : color_min),
454 (game_mm.laser_blue ? color_down : color_min));
457 static void InitMovDir_MM(int x, int y)
459 int element = Tile[x][y];
460 static int direction[3][4] =
462 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
463 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
464 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
469 case EL_PACMAN_RIGHT:
473 Tile[x][y] = EL_PACMAN;
474 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
482 static void InitField(int x, int y, boolean init_game)
484 int element = Tile[x][y];
489 Tile[x][y] = EL_EMPTY;
494 if (init_game && native_mm_level.auto_count_kettles)
495 game_mm.kettles_still_needed++;
498 case EL_LIGHTBULB_OFF:
499 game_mm.lights_still_needed++;
503 if (IS_MIRROR(element) ||
504 IS_BEAMER_OLD(element) ||
505 IS_BEAMER(element) ||
507 IS_POLAR_CROSS(element) ||
508 IS_DF_MIRROR(element) ||
509 IS_DF_MIRROR_AUTO(element) ||
510 IS_GRID_STEEL_AUTO(element) ||
511 IS_GRID_WOOD_AUTO(element) ||
512 IS_FIBRE_OPTIC(element))
514 if (IS_BEAMER_OLD(element))
516 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
517 element = Tile[x][y];
520 if (!IS_FIBRE_OPTIC(element))
522 static int steps_grid_auto = 0;
524 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
525 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
527 if (IS_GRID_STEEL_AUTO(element) ||
528 IS_GRID_WOOD_AUTO(element))
529 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
531 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
533 game_mm.cycle[game_mm.num_cycle].x = x;
534 game_mm.cycle[game_mm.num_cycle].y = y;
538 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
540 int beamer_nr = BEAMER_NR(element);
541 int nr = laser.beamer[beamer_nr][0].num;
543 laser.beamer[beamer_nr][nr].x = x;
544 laser.beamer[beamer_nr][nr].y = y;
545 laser.beamer[beamer_nr][nr].num = 1;
548 else if (IS_PACMAN(element))
552 else if (IS_MCDUFFIN(element) || IS_LASER(element))
556 laser.start_edge.x = x;
557 laser.start_edge.y = y;
558 laser.start_angle = get_element_angle(element);
561 if (IS_MCDUFFIN(element))
563 game_mm.laser_red = native_mm_level.mm_laser_red;
564 game_mm.laser_green = native_mm_level.mm_laser_green;
565 game_mm.laser_blue = native_mm_level.mm_laser_blue;
569 game_mm.laser_red = native_mm_level.df_laser_red;
570 game_mm.laser_green = native_mm_level.df_laser_green;
571 game_mm.laser_blue = native_mm_level.df_laser_blue;
574 game_mm.has_mcduffin = (IS_MCDUFFIN(element));
581 static void InitCycleElements_RotateSingleStep(void)
585 if (game_mm.num_cycle == 0) // no elements to cycle
588 for (i = 0; i < game_mm.num_cycle; i++)
590 int x = game_mm.cycle[i].x;
591 int y = game_mm.cycle[i].y;
592 int step = SIGN(game_mm.cycle[i].steps);
593 int last_element = Tile[x][y];
594 int next_element = get_rotated_element(last_element, step);
596 if (!game_mm.cycle[i].steps)
599 Tile[x][y] = next_element;
601 game_mm.cycle[i].steps -= step;
605 static void InitLaser(void)
607 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
608 int step = (IS_LASER(start_element) ? 4 : 0);
610 LX = laser.start_edge.x * TILEX;
611 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
614 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
616 LY = laser.start_edge.y * TILEY;
617 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
618 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
622 XS = 2 * Step[laser.start_angle].x;
623 YS = 2 * Step[laser.start_angle].y;
625 laser.current_angle = laser.start_angle;
627 laser.num_damages = 0;
629 laser.num_beamers = 0;
630 laser.beamer_edge[0] = 0;
632 laser.dest_element = EL_EMPTY;
635 AddLaserEdge(LX, LY); // set laser starting edge
640 void InitGameEngine_MM(void)
646 // initialize laser bitmap to current playfield (screen) size
647 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
648 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
652 // set global game control values
653 game_mm.num_cycle = 0;
654 game_mm.num_pacman = 0;
657 game_mm.energy_left = 0; // later set to "native_mm_level.time"
658 game_mm.kettles_still_needed =
659 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
660 game_mm.lights_still_needed = 0;
661 game_mm.num_keys = 0;
662 game_mm.ball_choice_pos = 0;
664 game_mm.laser_red = FALSE;
665 game_mm.laser_green = FALSE;
666 game_mm.laser_blue = TRUE;
667 game_mm.has_mcduffin = TRUE;
669 game_mm.level_solved = FALSE;
670 game_mm.game_over = FALSE;
671 game_mm.game_over_cause = 0;
673 game_mm.laser_overload_value = 0;
674 game_mm.laser_enabled = FALSE;
676 // set global laser control values (must be set before "InitLaser()")
677 laser.start_edge.x = 0;
678 laser.start_edge.y = 0;
679 laser.start_angle = 0;
681 for (i = 0; i < MAX_NUM_BEAMERS; i++)
682 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
684 laser.overloaded = FALSE;
685 laser.overload_value = 0;
686 laser.fuse_off = FALSE;
687 laser.fuse_x = laser.fuse_y = -1;
689 laser.dest_element = EL_EMPTY;
690 laser.dest_element_last = EL_EMPTY;
691 laser.dest_element_last_x = -1;
692 laser.dest_element_last_y = -1;
706 rotate_delay.count = 0;
707 pacman_delay.count = 0;
708 energy_delay.count = 0;
709 overload_delay.count = 0;
711 ClickElement(-1, -1, -1);
713 for (x = 0; x < lev_fieldx; x++)
715 for (y = 0; y < lev_fieldy; y++)
717 Tile[x][y] = Ur[x][y];
718 Hit[x][y] = Box[x][y] = 0;
720 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
721 Store[x][y] = Store2[x][y] = 0;
724 InitField(x, y, TRUE);
731 void InitGameActions_MM(void)
733 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
734 int cycle_steps_done = 0;
739 for (i = 0; i <= num_init_game_frames; i++)
741 if (i == num_init_game_frames)
742 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
743 else if (setup.sound_loops)
744 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
746 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
748 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
750 UpdateAndDisplayGameControlValues();
752 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
754 InitCycleElements_RotateSingleStep();
759 AdvanceFrameCounter();
767 if (setup.quick_doors)
774 if (game_mm.kettles_still_needed == 0)
777 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
778 SetTileCursorActive(TRUE);
780 // restart all delay counters after initially cycling game elements
781 ResetFrameCounter(&rotate_delay);
782 ResetFrameCounter(&pacman_delay);
783 ResetFrameCounter(&energy_delay);
784 ResetFrameCounter(&overload_delay);
787 static void FadeOutLaser(void)
791 for (i = 15; i >= 0; i--)
793 SetLaserColor(0x11 * i);
795 DrawLaser(0, DL_LASER_ENABLED);
798 Delay_WithScreenUpdates(50);
801 DrawLaser(0, DL_LASER_DISABLED);
803 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
806 static void GameOver_MM(int game_over_cause)
808 // do not handle game over if request dialog is already active
809 if (game.request_active)
812 game_mm.game_over = TRUE;
813 game_mm.game_over_cause = game_over_cause;
815 // do not ask to play again if game was never actually played
816 if (!game.GamePlayed)
819 if (setup.ask_on_game_over)
820 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
821 "Bomb killed Mc Duffin! Play it again?" :
822 game_over_cause == GAME_OVER_NO_ENERGY ?
823 "Out of magic energy! Play it again?" :
824 game_over_cause == GAME_OVER_OVERLOADED ?
825 "Magic spell hit Mc Duffin! Play it again?" :
828 SetTileCursorActive(FALSE);
831 static void AddLaserEdge(int lx, int ly)
836 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
838 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
843 laser.edge[laser.num_edges].x = cSX2 + lx;
844 laser.edge[laser.num_edges].y = cSY2 + ly;
850 static void AddDamagedField(int ex, int ey)
852 // prevent adding the same field position again
853 if (laser.num_damages > 0 &&
854 laser.damage[laser.num_damages - 1].x == ex &&
855 laser.damage[laser.num_damages - 1].y == ey &&
856 laser.damage[laser.num_damages - 1].edge == laser.num_edges)
859 laser.damage[laser.num_damages].is_mirror = FALSE;
860 laser.damage[laser.num_damages].angle = laser.current_angle;
861 laser.damage[laser.num_damages].edge = laser.num_edges;
862 laser.damage[laser.num_damages].x = ex;
863 laser.damage[laser.num_damages].y = ey;
867 static boolean StepBehind(void)
873 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
874 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
876 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
882 static int getMaskFromElement(int element)
884 if (IS_GRID(element))
885 return MM_MASK_GRID_1 + get_element_phase(element);
886 else if (IS_MCDUFFIN(element))
887 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
888 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
889 return MM_MASK_RECTANGLE;
891 return MM_MASK_CIRCLE;
894 static int ScanPixel(void)
899 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
900 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
903 // follow laser beam until it hits something (at least the screen border)
904 while (hit_mask == HIT_MASK_NO_HIT)
910 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
911 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
913 Debug("game:mm:ScanPixel", "touched screen border!");
919 // check if laser scan has crossed element boundaries (not just mini tiles)
920 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
921 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
923 if (cross_x && cross_y)
925 int elx1 = (LX - XS) / TILEX;
926 int ely1 = (LY + YS) / TILEY;
927 int elx2 = (LX + XS) / TILEX;
928 int ely2 = (LY - YS) / TILEY;
930 // add element corners left and right from the laser beam to damage list
932 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
933 AddDamagedField(elx1, ely1);
935 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
936 AddDamagedField(elx2, ely2);
939 for (i = 0; i < 4; i++)
941 int px = LX + (i % 2) * 2;
942 int py = LY + (i / 2) * 2;
945 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
946 int ly = (py + TILEY) / TILEY - 1; // negative values!
949 if (IN_LEV_FIELD(lx, ly))
951 int element = Tile[lx][ly];
953 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
957 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
959 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
961 pixel = ((element & (1 << pos)) ? 1 : 0);
965 int pos = getMaskFromElement(element);
967 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
972 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
973 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
976 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
977 hit_mask |= (1 << i);
980 if (hit_mask == HIT_MASK_NO_HIT)
982 // hit nothing -- go on with another step
991 static void DeactivateLaserTargetElement(void)
993 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
994 laser.dest_element_last == EL_MINE_ACTIVE ||
995 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
996 laser.dest_element_last == EL_GRAY_BALL_OPENING)
998 int x = laser.dest_element_last_x;
999 int y = laser.dest_element_last_y;
1000 int element = laser.dest_element_last;
1002 if (Tile[x][y] == element)
1003 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1004 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1006 if (Tile[x][y] == EL_GRAY_BALL)
1009 laser.dest_element_last = EL_EMPTY;
1010 laser.dest_element_last_x = -1;
1011 laser.dest_element_last_y = -1;
1015 static void ScanLaser(void)
1017 int element = EL_EMPTY;
1018 int last_element = EL_EMPTY;
1019 int end = 0, rf = laser.num_edges;
1021 // do not scan laser again after the game was lost for whatever reason
1022 if (game_mm.game_over)
1025 // do not scan laser if fuse is off
1029 DeactivateLaserTargetElement();
1031 laser.overloaded = FALSE;
1032 laser.stops_inside_element = FALSE;
1034 DrawLaser(0, DL_LASER_ENABLED);
1037 Debug("game:mm:ScanLaser",
1038 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1046 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1049 laser.overloaded = TRUE;
1054 hit_mask = ScanPixel();
1057 Debug("game:mm:ScanLaser",
1058 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1062 // hit something -- check out what it was
1063 ELX = (LX + XS) / TILEX;
1064 ELY = (LY + YS) / TILEY;
1067 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1068 hit_mask, LX, LY, ELX, ELY);
1071 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1074 laser.dest_element = element;
1079 // check if laser scan has hit two diagonally adjacent element corners
1080 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1081 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1083 // check if laser scan has crossed element boundaries (not just mini tiles)
1084 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1085 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1087 // handle special case of laser hitting two diagonally adjacent elements
1088 // (with or without a third corner element behind these two elements)
1089 if ((diag_1 || diag_2) && cross_x && cross_y)
1091 // compare the two diagonally adjacent elements
1093 int yoffset = 2 * (diag_1 ? -1 : +1);
1094 int elx1 = (LX - xoffset) / TILEX;
1095 int ely1 = (LY + yoffset) / TILEY;
1096 int elx2 = (LX + xoffset) / TILEX;
1097 int ely2 = (LY - yoffset) / TILEY;
1098 int e1 = Tile[elx1][ely1];
1099 int e2 = Tile[elx2][ely2];
1100 boolean use_element_1 = FALSE;
1102 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1104 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1105 use_element_1 = (RND(2) ? TRUE : FALSE);
1106 else if (IS_WALL_ICE(e1))
1107 use_element_1 = TRUE;
1109 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1111 // if both tiles match, we can just select the first one
1112 if (IS_WALL_AMOEBA(e1))
1113 use_element_1 = TRUE;
1115 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1117 // if both tiles match, we can just select the first one
1118 if (IS_ABSORBING_BLOCK(e1))
1119 use_element_1 = TRUE;
1122 ELX = (use_element_1 ? elx1 : elx2);
1123 ELY = (use_element_1 ? ely1 : ely2);
1127 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1128 hit_mask, LX, LY, ELX, ELY);
1131 last_element = element;
1133 element = Tile[ELX][ELY];
1134 laser.dest_element = element;
1137 Debug("game:mm:ScanLaser",
1138 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1141 LX % TILEX, LY % TILEY,
1146 if (!IN_LEV_FIELD(ELX, ELY))
1147 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1151 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1152 if (element == EL_EMPTY &&
1153 IS_GRID_STEEL(last_element) &&
1154 laser.current_angle % 4) // angle is not 90°
1155 element = last_element;
1157 if (element == EL_EMPTY)
1159 if (!HitOnlyAnEdge(hit_mask))
1162 else if (element == EL_FUSE_ON)
1164 if (HitPolarizer(element, hit_mask))
1167 else if (IS_GRID(element) || IS_DF_GRID(element))
1169 if (HitPolarizer(element, hit_mask))
1172 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1173 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1175 if (HitBlock(element, hit_mask))
1182 else if (IS_MCDUFFIN(element))
1184 if (HitLaserSource(element, hit_mask))
1187 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1188 IS_RECEIVER(element))
1190 if (HitLaserDestination(element, hit_mask))
1193 else if (IS_WALL(element))
1195 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1197 if (HitReflectingWalls(element, hit_mask))
1202 if (HitAbsorbingWalls(element, hit_mask))
1208 if (HitElement(element, hit_mask))
1213 DrawLaser(rf - 1, DL_LASER_ENABLED);
1214 rf = laser.num_edges;
1216 if (!IS_DF_WALL_STEEL(element))
1218 // only used for scanning DF steel walls; reset for all other elements
1226 if (laser.dest_element != Tile[ELX][ELY])
1228 Debug("game:mm:ScanLaser",
1229 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1230 laser.dest_element, Tile[ELX][ELY]);
1234 if (!end && !laser.stops_inside_element && !StepBehind())
1237 Debug("game:mm:ScanLaser", "Go one step back");
1243 AddLaserEdge(LX, LY);
1247 DrawLaser(rf - 1, DL_LASER_ENABLED);
1249 Ct = CT = FrameCounter;
1252 if (!IN_LEV_FIELD(ELX, ELY))
1253 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1257 static void ScanLaser_FromLastMirror(void)
1259 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1262 for (i = start_pos; i >= 0; i--)
1263 if (laser.damage[i].is_mirror)
1266 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1268 DrawLaser(start_edge, DL_LASER_DISABLED);
1273 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1279 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1280 start_edge, num_edges, mode);
1285 Warn("DrawLaserExt: start_edge < 0");
1292 Warn("DrawLaserExt: num_edges < 0");
1298 if (mode == DL_LASER_DISABLED)
1300 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1304 // now draw the laser to the backbuffer and (if enabled) to the screen
1305 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1307 redraw_mask |= REDRAW_FIELD;
1309 if (mode == DL_LASER_ENABLED)
1312 // after the laser was deleted, the "damaged" graphics must be restored
1313 if (laser.num_damages)
1315 int damage_start = 0;
1318 // determine the starting edge, from which graphics need to be restored
1321 for (i = 0; i < laser.num_damages; i++)
1323 if (laser.damage[i].edge == start_edge + 1)
1332 // restore graphics from this starting edge to the end of damage list
1333 for (i = damage_start; i < laser.num_damages; i++)
1335 int lx = laser.damage[i].x;
1336 int ly = laser.damage[i].y;
1337 int element = Tile[lx][ly];
1339 if (Hit[lx][ly] == laser.damage[i].edge)
1340 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1343 if (Box[lx][ly] == laser.damage[i].edge)
1346 if (IS_DRAWABLE(element))
1347 DrawField_MM(lx, ly);
1350 elx = laser.damage[damage_start].x;
1351 ely = laser.damage[damage_start].y;
1352 element = Tile[elx][ely];
1355 if (IS_BEAMER(element))
1359 for (i = 0; i < laser.num_beamers; i++)
1360 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1362 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1363 mode, elx, ely, Hit[elx][ely], start_edge);
1364 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1365 get_element_angle(element), laser.damage[damage_start].angle);
1369 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1370 laser.num_beamers > 0 &&
1371 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1373 // element is outgoing beamer
1374 laser.num_damages = damage_start + 1;
1376 if (IS_BEAMER(element))
1377 laser.current_angle = get_element_angle(element);
1381 // element is incoming beamer or other element
1382 laser.num_damages = damage_start;
1383 laser.current_angle = laser.damage[laser.num_damages].angle;
1388 // no damages but McDuffin himself (who needs to be redrawn anyway)
1390 elx = laser.start_edge.x;
1391 ely = laser.start_edge.y;
1392 element = Tile[elx][ely];
1395 laser.num_edges = start_edge + 1;
1396 if (start_edge == 0)
1397 laser.current_angle = laser.start_angle;
1399 LX = laser.edge[start_edge].x - cSX2;
1400 LY = laser.edge[start_edge].y - cSY2;
1401 XS = 2 * Step[laser.current_angle].x;
1402 YS = 2 * Step[laser.current_angle].y;
1405 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1411 if (IS_BEAMER(element) ||
1412 IS_FIBRE_OPTIC(element) ||
1413 IS_PACMAN(element) ||
1414 IS_POLAR(element) ||
1415 IS_POLAR_CROSS(element) ||
1416 element == EL_FUSE_ON)
1421 Debug("game:mm:DrawLaserExt", "element == %d", element);
1424 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1425 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1429 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1430 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1431 (laser.num_beamers == 0 ||
1432 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1434 // element is incoming beamer or other element
1435 step_size = -step_size;
1440 if (IS_BEAMER(element))
1441 Debug("game:mm:DrawLaserExt",
1442 "start_edge == %d, laser.beamer_edge == %d",
1443 start_edge, laser.beamer_edge);
1446 LX += step_size * XS;
1447 LY += step_size * YS;
1449 else if (element != EL_EMPTY)
1458 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1463 void DrawLaser(int start_edge, int mode)
1465 // do not draw laser if fuse is off
1466 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1469 if (mode == DL_LASER_DISABLED)
1470 DeactivateLaserTargetElement();
1472 if (laser.num_edges - start_edge < 0)
1474 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1479 // check if laser is interrupted by beamer element
1480 if (laser.num_beamers > 0 &&
1481 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1483 if (mode == DL_LASER_ENABLED)
1486 int tmp_start_edge = start_edge;
1488 // draw laser segments forward from the start to the last beamer
1489 for (i = 0; i < laser.num_beamers; i++)
1491 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1493 if (tmp_num_edges <= 0)
1497 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1498 i, laser.beamer_edge[i], tmp_start_edge);
1501 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1503 tmp_start_edge = laser.beamer_edge[i];
1506 // draw last segment from last beamer to the end
1507 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1513 int last_num_edges = laser.num_edges;
1514 int num_beamers = laser.num_beamers;
1516 // delete laser segments backward from the end to the first beamer
1517 for (i = num_beamers - 1; i >= 0; i--)
1519 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1521 if (laser.beamer_edge[i] - start_edge <= 0)
1524 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1526 last_num_edges = laser.beamer_edge[i];
1527 laser.num_beamers--;
1531 if (last_num_edges - start_edge <= 0)
1532 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1533 last_num_edges, start_edge);
1536 // special case when rotating first beamer: delete laser edge on beamer
1537 // (but do not start scanning on previous edge to prevent mirror sound)
1538 if (last_num_edges - start_edge == 1 && start_edge > 0)
1539 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1541 // delete first segment from start to the first beamer
1542 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1547 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1550 game_mm.laser_enabled = mode;
1553 void DrawLaser_MM(void)
1555 DrawLaser(0, game_mm.laser_enabled);
1558 static boolean HitElement(int element, int hit_mask)
1560 if (HitOnlyAnEdge(hit_mask))
1563 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1564 element = MovingOrBlocked2Element_MM(ELX, ELY);
1567 Debug("game:mm:HitElement", "(1): element == %d", element);
1571 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1572 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1575 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1579 AddDamagedField(ELX, ELY);
1581 // this is more precise: check if laser would go through the center
1582 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1586 // prevent cutting through laser emitter with laser beam
1587 if (IS_LASER(element))
1590 // skip the whole element before continuing the scan
1598 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1600 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1602 /* skipping scan positions to the right and down skips one scan
1603 position too much, because this is only the top left scan position
1604 of totally four scan positions (plus one to the right, one to the
1605 bottom and one to the bottom right) */
1606 /* ... but only roll back scan position if more than one step done */
1616 Debug("game:mm:HitElement", "(2): element == %d", element);
1619 if (LX + 5 * XS < 0 ||
1629 Debug("game:mm:HitElement", "(3): element == %d", element);
1632 if (IS_POLAR(element) &&
1633 ((element - EL_POLAR_START) % 2 ||
1634 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1636 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1638 laser.num_damages--;
1643 if (IS_POLAR_CROSS(element) &&
1644 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1646 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1648 laser.num_damages--;
1653 if (!IS_BEAMER(element) &&
1654 !IS_FIBRE_OPTIC(element) &&
1655 !IS_GRID_WOOD(element) &&
1656 element != EL_FUEL_EMPTY)
1659 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1660 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1662 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1665 LX = ELX * TILEX + 14;
1666 LY = ELY * TILEY + 14;
1668 AddLaserEdge(LX, LY);
1671 if (IS_MIRROR(element) ||
1672 IS_MIRROR_FIXED(element) ||
1673 IS_POLAR(element) ||
1674 IS_POLAR_CROSS(element) ||
1675 IS_DF_MIRROR(element) ||
1676 IS_DF_MIRROR_AUTO(element) ||
1677 element == EL_PRISM ||
1678 element == EL_REFRACTOR)
1680 int current_angle = laser.current_angle;
1683 laser.num_damages--;
1685 AddDamagedField(ELX, ELY);
1687 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1690 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1692 if (IS_MIRROR(element) ||
1693 IS_MIRROR_FIXED(element) ||
1694 IS_DF_MIRROR(element) ||
1695 IS_DF_MIRROR_AUTO(element))
1696 laser.current_angle = get_mirrored_angle(laser.current_angle,
1697 get_element_angle(element));
1699 if (element == EL_PRISM || element == EL_REFRACTOR)
1700 laser.current_angle = RND(16);
1702 XS = 2 * Step[laser.current_angle].x;
1703 YS = 2 * Step[laser.current_angle].y;
1705 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1710 LX += step_size * XS;
1711 LY += step_size * YS;
1713 // draw sparkles on mirror
1714 if ((IS_MIRROR(element) ||
1715 IS_MIRROR_FIXED(element) ||
1716 element == EL_PRISM) &&
1717 current_angle != laser.current_angle)
1719 MovDelay[ELX][ELY] = 11; // start animation
1722 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1723 current_angle != laser.current_angle)
1724 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1727 (get_opposite_angle(laser.current_angle) ==
1728 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1730 return (laser.overloaded ? TRUE : FALSE);
1733 if (element == EL_FUEL_FULL)
1735 laser.stops_inside_element = TRUE;
1740 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1742 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1744 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1745 element == EL_MINE ? EL_MINE_ACTIVE :
1746 EL_GRAY_BALL_ACTIVE);
1748 GfxFrame[ELX][ELY] = 0; // restart animation
1750 laser.dest_element_last = Tile[ELX][ELY];
1751 laser.dest_element_last_x = ELX;
1752 laser.dest_element_last_y = ELY;
1754 if (element == EL_MINE)
1755 laser.overloaded = TRUE;
1758 if (element == EL_KETTLE ||
1759 element == EL_CELL ||
1760 element == EL_KEY ||
1761 element == EL_LIGHTBALL ||
1762 element == EL_PACMAN ||
1763 IS_PACMAN(element) ||
1764 IS_ENVELOPE(element))
1766 if (!IS_PACMAN(element) &&
1767 !IS_ENVELOPE(element))
1770 if (element == EL_PACMAN)
1773 if (element == EL_KETTLE || element == EL_CELL)
1775 if (game_mm.kettles_still_needed > 0)
1776 game_mm.kettles_still_needed--;
1778 game.snapshot.collected_item = TRUE;
1780 if (game_mm.kettles_still_needed == 0)
1784 DrawLaser(0, DL_LASER_ENABLED);
1787 else if (element == EL_KEY)
1791 else if (IS_PACMAN(element))
1793 DeletePacMan(ELX, ELY);
1795 else if (IS_ENVELOPE(element))
1797 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1800 RaiseScoreElement_MM(element);
1805 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1807 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1809 DrawLaser(0, DL_LASER_ENABLED);
1811 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1813 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1814 game_mm.lights_still_needed--;
1818 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1819 game_mm.lights_still_needed++;
1822 DrawField_MM(ELX, ELY);
1823 DrawLaser(0, DL_LASER_ENABLED);
1828 laser.stops_inside_element = TRUE;
1834 Debug("game:mm:HitElement", "(4): element == %d", element);
1837 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1838 laser.num_beamers < MAX_NUM_BEAMERS &&
1839 laser.beamer[BEAMER_NR(element)][1].num)
1841 int beamer_angle = get_element_angle(element);
1842 int beamer_nr = BEAMER_NR(element);
1846 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1849 laser.num_damages--;
1851 if (IS_FIBRE_OPTIC(element) ||
1852 laser.current_angle == get_opposite_angle(beamer_angle))
1856 LX = ELX * TILEX + 14;
1857 LY = ELY * TILEY + 14;
1859 AddLaserEdge(LX, LY);
1860 AddDamagedField(ELX, ELY);
1862 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1865 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1867 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1868 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1869 ELX = laser.beamer[beamer_nr][pos].x;
1870 ELY = laser.beamer[beamer_nr][pos].y;
1871 LX = ELX * TILEX + 14;
1872 LY = ELY * TILEY + 14;
1874 if (IS_BEAMER(element))
1876 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1877 XS = 2 * Step[laser.current_angle].x;
1878 YS = 2 * Step[laser.current_angle].y;
1881 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1883 AddLaserEdge(LX, LY);
1884 AddDamagedField(ELX, ELY);
1886 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1889 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1891 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1896 LX += step_size * XS;
1897 LY += step_size * YS;
1899 laser.num_beamers++;
1908 static boolean HitOnlyAnEdge(int hit_mask)
1910 // check if the laser hit only the edge of an element and, if so, go on
1913 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1917 if ((hit_mask == HIT_MASK_TOPLEFT ||
1918 hit_mask == HIT_MASK_TOPRIGHT ||
1919 hit_mask == HIT_MASK_BOTTOMLEFT ||
1920 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1921 laser.current_angle % 4) // angle is not 90°
1925 if (hit_mask == HIT_MASK_TOPLEFT)
1930 else if (hit_mask == HIT_MASK_TOPRIGHT)
1935 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1940 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1946 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1952 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1959 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1965 static boolean HitPolarizer(int element, int hit_mask)
1967 if (HitOnlyAnEdge(hit_mask))
1970 if (IS_DF_GRID(element))
1972 int grid_angle = get_element_angle(element);
1975 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1976 grid_angle, laser.current_angle);
1979 AddLaserEdge(LX, LY);
1980 AddDamagedField(ELX, ELY);
1983 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1985 if (laser.current_angle == grid_angle ||
1986 laser.current_angle == get_opposite_angle(grid_angle))
1988 // skip the whole element before continuing the scan
1994 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1996 if (LX/TILEX > ELX || LY/TILEY > ELY)
1998 /* skipping scan positions to the right and down skips one scan
1999 position too much, because this is only the top left scan position
2000 of totally four scan positions (plus one to the right, one to the
2001 bottom and one to the bottom right) */
2007 AddLaserEdge(LX, LY);
2013 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2015 LX / TILEX, LY / TILEY,
2016 LX % TILEX, LY % TILEY);
2021 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2023 return HitReflectingWalls(element, hit_mask);
2027 return HitAbsorbingWalls(element, hit_mask);
2030 else if (IS_GRID_STEEL(element))
2032 // may be required if graphics for steel grid redefined
2033 AddDamagedField(ELX, ELY);
2035 return HitReflectingWalls(element, hit_mask);
2037 else // IS_GRID_WOOD
2039 // may be required if graphics for wooden grid redefined
2040 AddDamagedField(ELX, ELY);
2042 return HitAbsorbingWalls(element, hit_mask);
2048 static boolean HitBlock(int element, int hit_mask)
2050 boolean check = FALSE;
2052 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2053 game_mm.num_keys == 0)
2056 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2059 int ex = ELX * TILEX + 14;
2060 int ey = ELY * TILEY + 14;
2064 for (i = 1; i < 32; i++)
2069 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2074 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2075 return HitAbsorbingWalls(element, hit_mask);
2079 AddLaserEdge(LX - XS, LY - YS);
2080 AddDamagedField(ELX, ELY);
2083 Box[ELX][ELY] = laser.num_edges;
2085 return HitReflectingWalls(element, hit_mask);
2088 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2090 int xs = XS / 2, ys = YS / 2;
2092 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2093 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2095 laser.overloaded = (element == EL_GATE_STONE);
2100 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2101 (hit_mask == HIT_MASK_TOP ||
2102 hit_mask == HIT_MASK_LEFT ||
2103 hit_mask == HIT_MASK_RIGHT ||
2104 hit_mask == HIT_MASK_BOTTOM))
2105 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2106 hit_mask == HIT_MASK_BOTTOM),
2107 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2108 hit_mask == HIT_MASK_RIGHT));
2109 AddLaserEdge(LX, LY);
2115 if (element == EL_GATE_STONE && Box[ELX][ELY])
2117 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2129 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2131 int xs = XS / 2, ys = YS / 2;
2133 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2134 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2136 laser.overloaded = (element == EL_BLOCK_STONE);
2141 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2142 (hit_mask == HIT_MASK_TOP ||
2143 hit_mask == HIT_MASK_LEFT ||
2144 hit_mask == HIT_MASK_RIGHT ||
2145 hit_mask == HIT_MASK_BOTTOM))
2146 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2147 hit_mask == HIT_MASK_BOTTOM),
2148 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2149 hit_mask == HIT_MASK_RIGHT));
2150 AddDamagedField(ELX, ELY);
2152 LX = ELX * TILEX + 14;
2153 LY = ELY * TILEY + 14;
2155 AddLaserEdge(LX, LY);
2157 laser.stops_inside_element = TRUE;
2165 static boolean HitLaserSource(int element, int hit_mask)
2167 if (HitOnlyAnEdge(hit_mask))
2170 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2172 laser.overloaded = TRUE;
2177 static boolean HitLaserDestination(int element, int hit_mask)
2179 if (HitOnlyAnEdge(hit_mask))
2182 if (element != EL_EXIT_OPEN &&
2183 !(IS_RECEIVER(element) &&
2184 game_mm.kettles_still_needed == 0 &&
2185 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2187 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2192 if (IS_RECEIVER(element) ||
2193 (IS_22_5_ANGLE(laser.current_angle) &&
2194 (ELX != (LX + 6 * XS) / TILEX ||
2195 ELY != (LY + 6 * YS) / TILEY ||
2204 LX = ELX * TILEX + 14;
2205 LY = ELY * TILEY + 14;
2207 laser.stops_inside_element = TRUE;
2210 AddLaserEdge(LX, LY);
2211 AddDamagedField(ELX, ELY);
2213 if (game_mm.lights_still_needed == 0)
2215 game_mm.level_solved = TRUE;
2217 SetTileCursorActive(FALSE);
2223 static boolean HitReflectingWalls(int element, int hit_mask)
2225 // check if laser hits side of a wall with an angle that is not 90°
2226 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2227 hit_mask == HIT_MASK_LEFT ||
2228 hit_mask == HIT_MASK_RIGHT ||
2229 hit_mask == HIT_MASK_BOTTOM))
2231 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2236 if (!IS_DF_GRID(element))
2237 AddLaserEdge(LX, LY);
2239 // check if laser hits wall with an angle of 45°
2240 if (!IS_22_5_ANGLE(laser.current_angle))
2242 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2245 laser.current_angle = get_mirrored_angle(laser.current_angle,
2248 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2251 laser.current_angle = get_mirrored_angle(laser.current_angle,
2255 AddLaserEdge(LX, LY);
2257 XS = 2 * Step[laser.current_angle].x;
2258 YS = 2 * Step[laser.current_angle].y;
2262 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2264 laser.current_angle = get_mirrored_angle(laser.current_angle,
2269 if (!IS_DF_GRID(element))
2270 AddLaserEdge(LX, LY);
2275 if (!IS_DF_GRID(element))
2276 AddLaserEdge(LX, LY + YS / 2);
2279 if (!IS_DF_GRID(element))
2280 AddLaserEdge(LX, LY);
2283 YS = 2 * Step[laser.current_angle].y;
2287 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2289 laser.current_angle = get_mirrored_angle(laser.current_angle,
2294 if (!IS_DF_GRID(element))
2295 AddLaserEdge(LX, LY);
2300 if (!IS_DF_GRID(element))
2301 AddLaserEdge(LX + XS / 2, LY);
2304 if (!IS_DF_GRID(element))
2305 AddLaserEdge(LX, LY);
2308 XS = 2 * Step[laser.current_angle].x;
2314 // reflection at the edge of reflecting DF style wall
2315 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2317 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2318 hit_mask == HIT_MASK_TOPRIGHT) ||
2319 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2320 hit_mask == HIT_MASK_TOPLEFT) ||
2321 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2322 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2323 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2324 hit_mask == HIT_MASK_BOTTOMRIGHT))
2327 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2328 ANG_MIRROR_135 : ANG_MIRROR_45);
2330 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2332 AddDamagedField(ELX, ELY);
2333 AddLaserEdge(LX, LY);
2335 laser.current_angle = get_mirrored_angle(laser.current_angle,
2343 AddLaserEdge(LX, LY);
2349 // reflection inside an edge of reflecting DF style wall
2350 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2352 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2353 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2354 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2355 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2356 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2357 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2358 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2359 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2362 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2363 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2364 ANG_MIRROR_135 : ANG_MIRROR_45);
2366 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2369 AddDamagedField(ELX, ELY);
2372 AddLaserEdge(LX - XS, LY - YS);
2373 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2374 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2376 laser.current_angle = get_mirrored_angle(laser.current_angle,
2384 AddLaserEdge(LX, LY);
2390 // check if laser hits DF style wall with an angle of 90°
2391 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2393 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2394 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2395 (IS_VERT_ANGLE(laser.current_angle) &&
2396 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2398 // laser at last step touched nothing or the same side of the wall
2399 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2401 AddDamagedField(ELX, ELY);
2408 last_hit_mask = hit_mask;
2415 if (!HitOnlyAnEdge(hit_mask))
2417 laser.overloaded = TRUE;
2425 static boolean HitAbsorbingWalls(int element, int hit_mask)
2427 if (HitOnlyAnEdge(hit_mask))
2431 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2433 AddLaserEdge(LX - XS, LY - YS);
2440 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2442 AddLaserEdge(LX - XS, LY - YS);
2448 if (IS_WALL_WOOD(element) ||
2449 IS_DF_WALL_WOOD(element) ||
2450 IS_GRID_WOOD(element) ||
2451 IS_GRID_WOOD_FIXED(element) ||
2452 IS_GRID_WOOD_AUTO(element) ||
2453 element == EL_FUSE_ON ||
2454 element == EL_BLOCK_WOOD ||
2455 element == EL_GATE_WOOD)
2457 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2462 if (IS_WALL_ICE(element))
2468 // check if laser hit adjacent edges of two diagonal tiles
2469 if (ELX != lx / TILEX)
2471 if (ELY != ly / TILEY)
2474 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2475 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2477 // check if laser hits wall with an angle of 90°
2478 if (IS_90_ANGLE(laser.current_angle))
2479 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2481 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2485 for (i = 0; i < 4; i++)
2487 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2488 mask = 15 - (8 >> i);
2489 else if (ABS(XS) == 4 &&
2491 (XS > 0) == (i % 2) &&
2492 (YS < 0) == (i / 2))
2493 mask = 3 + (i / 2) * 9;
2494 else if (ABS(YS) == 4 &&
2496 (XS < 0) == (i % 2) &&
2497 (YS > 0) == (i / 2))
2498 mask = 5 + (i % 2) * 5;
2502 laser.wall_mask = mask;
2504 else if (IS_WALL_AMOEBA(element))
2506 int elx = (LX - 2 * XS) / TILEX;
2507 int ely = (LY - 2 * YS) / TILEY;
2508 int element2 = Tile[elx][ely];
2511 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2513 laser.dest_element = EL_EMPTY;
2521 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2522 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2524 if (IS_90_ANGLE(laser.current_angle))
2525 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2527 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2529 laser.wall_mask = mask;
2535 static void OpenExit(int x, int y)
2539 if (!MovDelay[x][y]) // next animation frame
2540 MovDelay[x][y] = 4 * delay;
2542 if (MovDelay[x][y]) // wait some time before next frame
2547 phase = MovDelay[x][y] / delay;
2549 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2550 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2552 if (!MovDelay[x][y])
2554 Tile[x][y] = EL_EXIT_OPEN;
2560 static void OpenGrayBall(int x, int y)
2564 if (!MovDelay[x][y]) // next animation frame
2566 if (IS_WALL(Store[x][y]))
2568 DrawWalls_MM(x, y, Store[x][y]);
2570 // copy wall tile to spare bitmap for "melting" animation
2571 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2572 TILEX, TILEY, x * TILEX, y * TILEY);
2574 DrawElement_MM(x, y, EL_GRAY_BALL);
2577 MovDelay[x][y] = 50 * delay;
2580 if (MovDelay[x][y]) // wait some time before next frame
2584 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2588 int dx = RND(26), dy = RND(26);
2590 if (IS_WALL(Store[x][y]))
2592 // copy wall tile from spare bitmap for "melting" animation
2593 bitmap = bitmap_db_field;
2599 int graphic = el2gfx(Store[x][y]);
2601 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2604 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2605 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2607 laser.redraw = TRUE;
2609 MarkTileDirty(x, y);
2612 if (!MovDelay[x][y])
2614 Tile[x][y] = Store[x][y];
2615 Store[x][y] = Store2[x][y] = 0;
2616 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2618 InitField(x, y, FALSE);
2621 ScanLaser_FromLastMirror();
2626 static void OpenEnvelope(int x, int y)
2628 int num_frames = 8; // seven frames plus final empty space
2630 if (!MovDelay[x][y]) // next animation frame
2631 MovDelay[x][y] = num_frames;
2633 if (MovDelay[x][y]) // wait some time before next frame
2635 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2639 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2641 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2642 int frame = num_frames - MovDelay[x][y] - 1;
2644 DrawGraphicAnimation_MM(x, y, graphic, frame);
2646 laser.redraw = TRUE;
2649 if (MovDelay[x][y] == 0)
2651 Tile[x][y] = EL_EMPTY;
2657 ShowEnvelope_MM(nr);
2662 static void MeltIce(int x, int y)
2667 if (!MovDelay[x][y]) // next animation frame
2668 MovDelay[x][y] = frames * delay;
2670 if (MovDelay[x][y]) // wait some time before next frame
2673 int wall_mask = Store2[x][y];
2674 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2677 phase = frames - MovDelay[x][y] / delay - 1;
2679 if (!MovDelay[x][y])
2681 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2682 Store[x][y] = Store2[x][y] = 0;
2684 DrawWalls_MM(x, y, Tile[x][y]);
2686 if (Tile[x][y] == EL_WALL_ICE_BASE)
2687 Tile[x][y] = EL_EMPTY;
2689 ScanLaser_FromLastMirror();
2691 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2693 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2695 laser.redraw = TRUE;
2700 static void GrowAmoeba(int x, int y)
2705 if (!MovDelay[x][y]) // next animation frame
2706 MovDelay[x][y] = frames * delay;
2708 if (MovDelay[x][y]) // wait some time before next frame
2711 int wall_mask = Store2[x][y];
2712 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2715 phase = MovDelay[x][y] / delay;
2717 if (!MovDelay[x][y])
2719 Tile[x][y] = real_element;
2720 Store[x][y] = Store2[x][y] = 0;
2722 DrawWalls_MM(x, y, Tile[x][y]);
2723 DrawLaser(0, DL_LASER_ENABLED);
2725 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2727 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2732 static void DrawFieldAnimated_MM(int x, int y)
2736 laser.redraw = TRUE;
2739 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2741 int element = Tile[x][y];
2742 int graphic = el2gfx(element);
2744 if (!getGraphicInfo_NewFrame(x, y, graphic))
2749 laser.redraw = TRUE;
2752 static void DrawFieldTwinkle(int x, int y)
2754 if (MovDelay[x][y] != 0) // wait some time before next frame
2760 if (MovDelay[x][y] != 0)
2762 int graphic = IMG_TWINKLE_WHITE;
2763 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2765 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2768 laser.redraw = TRUE;
2772 static void Explode_MM(int x, int y, int phase, int mode)
2774 int num_phase = 9, delay = 2;
2775 int last_phase = num_phase * delay;
2776 int half_phase = (num_phase / 2) * delay;
2779 laser.redraw = TRUE;
2781 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2783 center_element = Tile[x][y];
2785 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2787 // put moving element to center field (and let it explode there)
2788 center_element = MovingOrBlocked2Element_MM(x, y);
2789 RemoveMovingField_MM(x, y);
2791 Tile[x][y] = center_element;
2794 if (center_element != EL_GRAY_BALL_ACTIVE)
2795 Store[x][y] = EL_EMPTY;
2796 Store2[x][y] = center_element;
2798 Tile[x][y] = EL_EXPLODING_OPAQUE;
2800 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2801 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2802 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2805 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2807 ExplodePhase[x][y] = 1;
2813 GfxFrame[x][y] = 0; // restart explosion animation
2815 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2817 center_element = Store2[x][y];
2819 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2821 Tile[x][y] = EL_EXPLODING_TRANSP;
2823 if (x == ELX && y == ELY)
2827 if (phase == last_phase)
2829 if (center_element == EL_BOMB_ACTIVE)
2831 DrawLaser(0, DL_LASER_DISABLED);
2834 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2836 GameOver_MM(GAME_OVER_DELAYED);
2838 laser.overloaded = FALSE;
2840 else if (IS_MCDUFFIN(center_element))
2842 GameOver_MM(GAME_OVER_BOMB);
2845 Tile[x][y] = Store[x][y];
2847 Store[x][y] = Store2[x][y] = 0;
2848 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2850 InitField(x, y, FALSE);
2853 if (center_element == EL_GRAY_BALL_ACTIVE)
2854 ScanLaser_FromLastMirror();
2856 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2858 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2859 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2861 DrawGraphicAnimation_MM(x, y, graphic, frame);
2863 MarkTileDirty(x, y);
2867 static void Bang_MM(int x, int y)
2869 int element = Tile[x][y];
2871 if (IS_PACMAN(element))
2872 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2873 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2874 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2875 else if (element == EL_KEY)
2876 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2878 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2880 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2883 static void TurnRound(int x, int y)
2895 { 0, 0 }, { 0, 0 }, { 0, 0 },
2900 int left, right, back;
2904 { MV_DOWN, MV_UP, MV_RIGHT },
2905 { MV_UP, MV_DOWN, MV_LEFT },
2907 { MV_LEFT, MV_RIGHT, MV_DOWN },
2908 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2909 { MV_RIGHT, MV_LEFT, MV_UP }
2912 int element = Tile[x][y];
2913 int old_move_dir = MovDir[x][y];
2914 int right_dir = turn[old_move_dir].right;
2915 int back_dir = turn[old_move_dir].back;
2916 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2917 int right_x = x + right_dx, right_y = y + right_dy;
2919 if (element == EL_PACMAN)
2921 boolean can_turn_right = FALSE;
2923 if (IN_LEV_FIELD(right_x, right_y) &&
2924 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2925 can_turn_right = TRUE;
2928 MovDir[x][y] = right_dir;
2930 MovDir[x][y] = back_dir;
2936 static void StartMoving_MM(int x, int y)
2938 int element = Tile[x][y];
2943 if (CAN_MOVE(element))
2947 if (MovDelay[x][y]) // wait some time before next movement
2955 // now make next step
2957 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2959 if (element == EL_PACMAN &&
2960 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2961 !ObjHit(newx, newy, HIT_POS_CENTER))
2963 Store[newx][newy] = Tile[newx][newy];
2964 Tile[newx][newy] = EL_EMPTY;
2966 DrawField_MM(newx, newy);
2968 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2969 ObjHit(newx, newy, HIT_POS_CENTER))
2971 // object was running against a wall
2978 InitMovingField_MM(x, y, MovDir[x][y]);
2982 ContinueMoving_MM(x, y);
2985 static void ContinueMoving_MM(int x, int y)
2987 int element = Tile[x][y];
2988 int direction = MovDir[x][y];
2989 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2990 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2991 int horiz_move = (dx!=0);
2992 int newx = x + dx, newy = y + dy;
2993 int step = (horiz_move ? dx : dy) * TILEX / 8;
2995 MovPos[x][y] += step;
2997 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2999 Tile[x][y] = EL_EMPTY;
3000 Tile[newx][newy] = element;
3002 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3003 MovDelay[newx][newy] = 0;
3005 if (!CAN_MOVE(element))
3006 MovDir[newx][newy] = 0;
3009 DrawField_MM(newx, newy);
3011 Stop[newx][newy] = TRUE;
3013 if (element == EL_PACMAN)
3015 if (Store[newx][newy] == EL_BOMB)
3016 Bang_MM(newx, newy);
3018 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3019 (LX + 2 * XS) / TILEX == newx &&
3020 (LY + 2 * YS) / TILEY == newy)
3027 else // still moving on
3032 laser.redraw = TRUE;
3035 boolean ClickElement(int x, int y, int button)
3037 static DelayCounter click_delay = { CLICK_DELAY };
3038 static boolean new_button = TRUE;
3039 boolean element_clicked = FALSE;
3044 // initialize static variables
3045 click_delay.count = 0;
3046 click_delay.value = CLICK_DELAY;
3052 // do not rotate objects hit by the laser after the game was solved
3053 if (game_mm.level_solved && Hit[x][y])
3056 if (button == MB_RELEASED)
3059 click_delay.value = CLICK_DELAY;
3061 // release eventually hold auto-rotating mirror
3062 RotateMirror(x, y, MB_RELEASED);
3067 if (!FrameReached(&click_delay) && !new_button)
3070 if (button == MB_MIDDLEBUTTON) // middle button has no function
3073 if (!IN_LEV_FIELD(x, y))
3076 if (Tile[x][y] == EL_EMPTY)
3079 element = Tile[x][y];
3081 if (IS_MIRROR(element) ||
3082 IS_BEAMER(element) ||
3083 IS_POLAR(element) ||
3084 IS_POLAR_CROSS(element) ||
3085 IS_DF_MIRROR(element) ||
3086 IS_DF_MIRROR_AUTO(element))
3088 RotateMirror(x, y, button);
3090 element_clicked = TRUE;
3092 else if (IS_MCDUFFIN(element))
3094 if (!laser.fuse_off)
3096 DrawLaser(0, DL_LASER_DISABLED);
3103 element = get_rotated_element(element, BUTTON_ROTATION(button));
3104 laser.start_angle = get_element_angle(element);
3108 Tile[x][y] = element;
3115 if (!laser.fuse_off)
3118 element_clicked = TRUE;
3120 else if (element == EL_FUSE_ON && laser.fuse_off)
3122 if (x != laser.fuse_x || y != laser.fuse_y)
3125 laser.fuse_off = FALSE;
3126 laser.fuse_x = laser.fuse_y = -1;
3128 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3131 element_clicked = TRUE;
3133 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3135 laser.fuse_off = TRUE;
3138 laser.overloaded = FALSE;
3140 DrawLaser(0, DL_LASER_DISABLED);
3141 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3143 element_clicked = TRUE;
3145 else if (element == EL_LIGHTBALL)
3148 RaiseScoreElement_MM(element);
3149 DrawLaser(0, DL_LASER_ENABLED);
3151 element_clicked = TRUE;
3153 else if (IS_ENVELOPE(element))
3155 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3157 element_clicked = TRUE;
3160 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3163 return element_clicked;
3166 static void RotateMirror(int x, int y, int button)
3168 if (button == MB_RELEASED)
3170 // release eventually hold auto-rotating mirror
3177 if (IS_MIRROR(Tile[x][y]) ||
3178 IS_POLAR_CROSS(Tile[x][y]) ||
3179 IS_POLAR(Tile[x][y]) ||
3180 IS_BEAMER(Tile[x][y]) ||
3181 IS_DF_MIRROR(Tile[x][y]) ||
3182 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3183 IS_GRID_WOOD_AUTO(Tile[x][y]))
3185 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3187 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3189 if (button == MB_LEFTBUTTON)
3191 // left mouse button only for manual adjustment, no auto-rotating;
3192 // freeze mirror for until mouse button released
3196 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3198 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3202 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3204 int edge = Hit[x][y];
3210 DrawLaser(edge - 1, DL_LASER_DISABLED);
3214 else if (ObjHit(x, y, HIT_POS_CENTER))
3216 int edge = Hit[x][y];
3220 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3225 DrawLaser(edge - 1, DL_LASER_DISABLED);
3232 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3237 if ((IS_BEAMER(Tile[x][y]) ||
3238 IS_POLAR(Tile[x][y]) ||
3239 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3241 if (IS_BEAMER(Tile[x][y]))
3244 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3245 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3258 DrawLaser(0, DL_LASER_ENABLED);
3262 static void AutoRotateMirrors(void)
3266 if (!FrameReached(&rotate_delay))
3269 for (x = 0; x < lev_fieldx; x++)
3271 for (y = 0; y < lev_fieldy; y++)
3273 int element = Tile[x][y];
3275 // do not rotate objects hit by the laser after the game was solved
3276 if (game_mm.level_solved && Hit[x][y])
3279 if (IS_DF_MIRROR_AUTO(element) ||
3280 IS_GRID_WOOD_AUTO(element) ||
3281 IS_GRID_STEEL_AUTO(element) ||
3282 element == EL_REFRACTOR)
3283 RotateMirror(x, y, MB_RIGHTBUTTON);
3288 static boolean ObjHit(int obx, int oby, int bits)
3295 if (bits & HIT_POS_CENTER)
3297 if (CheckLaserPixel(cSX + obx + 15,
3302 if (bits & HIT_POS_EDGE)
3304 for (i = 0; i < 4; i++)
3305 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3306 cSY + oby + 31 * (i / 2)))
3310 if (bits & HIT_POS_BETWEEN)
3312 for (i = 0; i < 4; i++)
3313 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3314 cSY + 4 + oby + 22 * (i / 2)))
3321 static void DeletePacMan(int px, int py)
3327 if (game_mm.num_pacman <= 1)
3329 game_mm.num_pacman = 0;
3333 for (i = 0; i < game_mm.num_pacman; i++)
3334 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3337 game_mm.num_pacman--;
3339 for (j = i; j < game_mm.num_pacman; j++)
3341 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3342 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3343 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3347 static void GameActions_MM_Ext(void)
3354 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3357 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3359 element = Tile[x][y];
3361 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3362 StartMoving_MM(x, y);
3363 else if (IS_MOVING(x, y))
3364 ContinueMoving_MM(x, y);
3365 else if (IS_EXPLODING(element))
3366 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3367 else if (element == EL_EXIT_OPENING)
3369 else if (element == EL_GRAY_BALL_OPENING)
3371 else if (IS_ENVELOPE_OPENING(element))
3373 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3375 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3377 else if (IS_MIRROR(element) ||
3378 IS_MIRROR_FIXED(element) ||
3379 element == EL_PRISM)
3380 DrawFieldTwinkle(x, y);
3381 else if (element == EL_GRAY_BALL_ACTIVE ||
3382 element == EL_BOMB_ACTIVE ||
3383 element == EL_MINE_ACTIVE)
3384 DrawFieldAnimated_MM(x, y);
3385 else if (!IS_BLOCKED(x, y))
3386 DrawFieldAnimatedIfNeeded_MM(x, y);
3389 AutoRotateMirrors();
3392 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3394 // redraw after Explode_MM() ...
3396 DrawLaser(0, DL_LASER_ENABLED);
3397 laser.redraw = FALSE;
3402 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3406 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3408 DrawLaser(0, DL_LASER_DISABLED);
3413 // skip all following game actions if game is over
3414 if (game_mm.game_over)
3417 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3421 GameOver_MM(GAME_OVER_NO_ENERGY);
3426 if (FrameReached(&energy_delay))
3428 if (game_mm.energy_left > 0)
3429 game_mm.energy_left--;
3431 // when out of energy, wait another frame to play "out of time" sound
3434 element = laser.dest_element;
3437 if (element != Tile[ELX][ELY])
3439 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3440 element, Tile[ELX][ELY]);
3444 if (!laser.overloaded && laser.overload_value == 0 &&
3445 element != EL_BOMB &&
3446 element != EL_BOMB_ACTIVE &&
3447 element != EL_MINE &&
3448 element != EL_MINE_ACTIVE &&
3449 element != EL_GRAY_BALL &&
3450 element != EL_GRAY_BALL_ACTIVE &&
3451 element != EL_BLOCK_STONE &&
3452 element != EL_BLOCK_WOOD &&
3453 element != EL_FUSE_ON &&
3454 element != EL_FUEL_FULL &&
3455 !IS_WALL_ICE(element) &&
3456 !IS_WALL_AMOEBA(element))
3459 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3461 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3462 (!laser.overloaded && laser.overload_value > 0)) &&
3463 FrameReached(&overload_delay))
3465 if (laser.overloaded)
3466 laser.overload_value++;
3468 laser.overload_value--;
3470 if (game_mm.cheat_no_overload)
3472 laser.overloaded = FALSE;
3473 laser.overload_value = 0;
3476 game_mm.laser_overload_value = laser.overload_value;
3478 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3480 SetLaserColor(0xFF);
3482 DrawLaser(0, DL_LASER_ENABLED);
3485 if (!laser.overloaded)
3486 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3487 else if (setup.sound_loops)
3488 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3490 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3492 if (laser.overload_value == MAX_LASER_OVERLOAD)
3494 UpdateAndDisplayGameControlValues();
3498 GameOver_MM(GAME_OVER_OVERLOADED);
3509 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3511 if (game_mm.cheat_no_explosion)
3516 laser.dest_element = EL_EXPLODING_OPAQUE;
3521 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3523 laser.fuse_off = TRUE;
3527 DrawLaser(0, DL_LASER_DISABLED);
3528 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3531 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3533 if (!Store2[ELX][ELY]) // check if content element not yet determined
3535 int last_anim_random_frame = gfx.anim_random_frame;
3538 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3539 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3541 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3542 native_mm_level.ball_choice_mode, 0,
3543 game_mm.ball_choice_pos);
3545 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3546 gfx.anim_random_frame = last_anim_random_frame;
3548 game_mm.ball_choice_pos++;
3550 int new_element = native_mm_level.ball_content[element_pos];
3551 int new_element_base = map_wall_to_base_element(new_element);
3553 if (IS_WALL(new_element_base))
3555 // always use completely filled wall element
3556 new_element = new_element_base | 0x000f;
3558 else if (native_mm_level.rotate_ball_content &&
3559 get_num_elements(new_element) > 1)
3561 // randomly rotate newly created game element
3562 new_element = get_rotated_element(new_element, RND(16));
3565 Store[ELX][ELY] = new_element;
3566 Store2[ELX][ELY] = TRUE;
3569 if (native_mm_level.explode_ball)
3572 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3574 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3579 if (IS_WALL_ICE(element) && CT > 50)
3581 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3583 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3584 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3585 Store2[ELX][ELY] = laser.wall_mask;
3587 laser.dest_element = Tile[ELX][ELY];
3592 if (IS_WALL_AMOEBA(element) && CT > 60)
3595 int element2 = Tile[ELX][ELY];
3597 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3600 for (i = laser.num_damages - 1; i >= 0; i--)
3601 if (laser.damage[i].is_mirror)
3604 r = laser.num_edges;
3605 d = laser.num_damages;
3612 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3615 DrawLaser(0, DL_LASER_ENABLED);
3618 x = laser.damage[k1].x;
3619 y = laser.damage[k1].y;
3624 for (i = 0; i < 4; i++)
3626 if (laser.wall_mask & (1 << i))
3628 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3629 cSY + ELY * TILEY + 31 * (i / 2)))
3632 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3633 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3640 for (i = 0; i < 4; i++)
3642 if (laser.wall_mask & (1 << i))
3644 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3645 cSY + ELY * TILEY + 31 * (i / 2)))
3652 if (laser.num_beamers > 0 ||
3653 k1 < 1 || k2 < 4 || k3 < 4 ||
3654 CheckLaserPixel(cSX + ELX * TILEX + 14,
3655 cSY + ELY * TILEY + 14))
3657 laser.num_edges = r;
3658 laser.num_damages = d;
3660 DrawLaser(0, DL_LASER_DISABLED);
3663 Tile[ELX][ELY] = element | laser.wall_mask;
3665 int x = ELX, y = ELY;
3666 int wall_mask = laser.wall_mask;
3669 DrawLaser(0, DL_LASER_ENABLED);
3671 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3673 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3674 Store[x][y] = EL_WALL_AMOEBA_BASE;
3675 Store2[x][y] = wall_mask;
3680 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3681 laser.stops_inside_element && CT > native_mm_level.time_block)
3686 if (ABS(XS) > ABS(YS))
3693 for (i = 0; i < 4; i++)
3700 x = ELX + Step[k * 4].x;
3701 y = ELY + Step[k * 4].y;
3703 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3706 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3714 laser.overloaded = (element == EL_BLOCK_STONE);
3719 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3722 Tile[x][y] = element;
3724 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3727 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3729 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3730 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3738 if (element == EL_FUEL_FULL && CT > 10)
3740 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3741 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3743 for (i = start; i <= num_init_game_frames; i++)
3745 if (i == num_init_game_frames)
3746 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3747 else if (setup.sound_loops)
3748 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3750 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3752 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3754 UpdateAndDisplayGameControlValues();
3759 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3761 DrawField_MM(ELX, ELY);
3763 DrawLaser(0, DL_LASER_ENABLED);
3769 void GameActions_MM(struct MouseActionInfo action)
3771 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3772 boolean button_released = (action.button == MB_RELEASED);
3774 GameActions_MM_Ext();
3776 CheckSingleStepMode_MM(element_clicked, button_released);
3779 static void MovePacMen(void)
3781 int mx, my, ox, oy, nx, ny;
3785 if (++pacman_nr >= game_mm.num_pacman)
3788 game_mm.pacman[pacman_nr].dir--;
3790 for (l = 1; l < 5; l++)
3792 game_mm.pacman[pacman_nr].dir++;
3794 if (game_mm.pacman[pacman_nr].dir > 4)
3795 game_mm.pacman[pacman_nr].dir = 1;
3797 if (game_mm.pacman[pacman_nr].dir % 2)
3800 my = game_mm.pacman[pacman_nr].dir - 2;
3805 mx = 3 - game_mm.pacman[pacman_nr].dir;
3808 ox = game_mm.pacman[pacman_nr].x;
3809 oy = game_mm.pacman[pacman_nr].y;
3812 element = Tile[nx][ny];
3814 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3817 if (!IS_EATABLE4PACMAN(element))
3820 if (ObjHit(nx, ny, HIT_POS_CENTER))
3823 Tile[ox][oy] = EL_EMPTY;
3825 EL_PACMAN_RIGHT - 1 +
3826 (game_mm.pacman[pacman_nr].dir - 1 +
3827 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3829 game_mm.pacman[pacman_nr].x = nx;
3830 game_mm.pacman[pacman_nr].y = ny;
3832 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3834 if (element != EL_EMPTY)
3836 int graphic = el2gfx(Tile[nx][ny]);
3841 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3844 ox = cSX + ox * TILEX;
3845 oy = cSY + oy * TILEY;
3847 for (i = 1; i < 33; i += 2)
3848 BlitBitmap(bitmap, window,
3849 src_x, src_y, TILEX, TILEY,
3850 ox + i * mx, oy + i * my);
3851 Ct = Ct + FrameCounter - CT;
3854 DrawField_MM(nx, ny);
3857 if (!laser.fuse_off)
3859 DrawLaser(0, DL_LASER_ENABLED);
3861 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3863 AddDamagedField(nx, ny);
3865 laser.damage[laser.num_damages - 1].edge = 0;
3869 if (element == EL_BOMB)
3870 DeletePacMan(nx, ny);
3872 if (IS_WALL_AMOEBA(element) &&
3873 (LX + 2 * XS) / TILEX == nx &&
3874 (LY + 2 * YS) / TILEY == ny)
3884 static void InitMovingField_MM(int x, int y, int direction)
3886 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3887 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3889 MovDir[x][y] = direction;
3890 MovDir[newx][newy] = direction;
3892 if (Tile[newx][newy] == EL_EMPTY)
3893 Tile[newx][newy] = EL_BLOCKED;
3896 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3898 int direction = MovDir[x][y];
3899 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3900 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3906 static void Blocked2Moving_MM(int x, int y,
3907 int *comes_from_x, int *comes_from_y)
3909 int oldx = x, oldy = y;
3910 int direction = MovDir[x][y];
3912 if (direction == MV_LEFT)
3914 else if (direction == MV_RIGHT)
3916 else if (direction == MV_UP)
3918 else if (direction == MV_DOWN)
3921 *comes_from_x = oldx;
3922 *comes_from_y = oldy;
3925 static int MovingOrBlocked2Element_MM(int x, int y)
3927 int element = Tile[x][y];
3929 if (element == EL_BLOCKED)
3933 Blocked2Moving_MM(x, y, &oldx, &oldy);
3935 return Tile[oldx][oldy];
3941 static void RemoveMovingField_MM(int x, int y)
3943 int oldx = x, oldy = y, newx = x, newy = y;
3945 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3948 if (IS_MOVING(x, y))
3950 Moving2Blocked_MM(x, y, &newx, &newy);
3951 if (Tile[newx][newy] != EL_BLOCKED)
3954 else if (Tile[x][y] == EL_BLOCKED)
3956 Blocked2Moving_MM(x, y, &oldx, &oldy);
3957 if (!IS_MOVING(oldx, oldy))
3961 Tile[oldx][oldy] = EL_EMPTY;
3962 Tile[newx][newy] = EL_EMPTY;
3963 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3964 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3966 DrawLevelField_MM(oldx, oldy);
3967 DrawLevelField_MM(newx, newy);
3970 static void RaiseScore_MM(int value)
3972 game_mm.score += value;
3975 void RaiseScoreElement_MM(int element)
3980 case EL_PACMAN_RIGHT:
3982 case EL_PACMAN_LEFT:
3983 case EL_PACMAN_DOWN:
3984 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3988 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3993 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3997 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4006 // ----------------------------------------------------------------------------
4007 // Mirror Magic game engine snapshot handling functions
4008 // ----------------------------------------------------------------------------
4010 void SaveEngineSnapshotValues_MM(void)
4014 engine_snapshot_mm.game_mm = game_mm;
4015 engine_snapshot_mm.laser = laser;
4017 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4019 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4021 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4022 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4023 engine_snapshot_mm.Box[x][y] = Box[x][y];
4024 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4028 engine_snapshot_mm.LX = LX;
4029 engine_snapshot_mm.LY = LY;
4030 engine_snapshot_mm.XS = XS;
4031 engine_snapshot_mm.YS = YS;
4032 engine_snapshot_mm.ELX = ELX;
4033 engine_snapshot_mm.ELY = ELY;
4034 engine_snapshot_mm.CT = CT;
4035 engine_snapshot_mm.Ct = Ct;
4037 engine_snapshot_mm.last_LX = last_LX;
4038 engine_snapshot_mm.last_LY = last_LY;
4039 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4040 engine_snapshot_mm.hold_x = hold_x;
4041 engine_snapshot_mm.hold_y = hold_y;
4042 engine_snapshot_mm.pacman_nr = pacman_nr;
4044 engine_snapshot_mm.rotate_delay = rotate_delay;
4045 engine_snapshot_mm.pacman_delay = pacman_delay;
4046 engine_snapshot_mm.energy_delay = energy_delay;
4047 engine_snapshot_mm.overload_delay = overload_delay;
4050 void LoadEngineSnapshotValues_MM(void)
4054 // stored engine snapshot buffers already restored at this point
4056 game_mm = engine_snapshot_mm.game_mm;
4057 laser = engine_snapshot_mm.laser;
4059 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4061 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4063 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4064 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4065 Box[x][y] = engine_snapshot_mm.Box[x][y];
4066 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4070 LX = engine_snapshot_mm.LX;
4071 LY = engine_snapshot_mm.LY;
4072 XS = engine_snapshot_mm.XS;
4073 YS = engine_snapshot_mm.YS;
4074 ELX = engine_snapshot_mm.ELX;
4075 ELY = engine_snapshot_mm.ELY;
4076 CT = engine_snapshot_mm.CT;
4077 Ct = engine_snapshot_mm.Ct;
4079 last_LX = engine_snapshot_mm.last_LX;
4080 last_LY = engine_snapshot_mm.last_LY;
4081 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4082 hold_x = engine_snapshot_mm.hold_x;
4083 hold_y = engine_snapshot_mm.hold_y;
4084 pacman_nr = engine_snapshot_mm.pacman_nr;
4086 rotate_delay = engine_snapshot_mm.rotate_delay;
4087 pacman_delay = engine_snapshot_mm.pacman_delay;
4088 energy_delay = engine_snapshot_mm.energy_delay;
4089 overload_delay = engine_snapshot_mm.overload_delay;
4091 RedrawPlayfield_MM();
4094 static int getAngleFromTouchDelta(int dx, int dy, int base)
4096 double pi = 3.141592653;
4097 double rad = atan2((double)-dy, (double)dx);
4098 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4099 double deg = rad2 * 180.0 / pi;
4101 return (int)(deg * base / 360.0 + 0.5) % base;
4104 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4106 // calculate start (source) position to be at the middle of the tile
4107 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4108 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4109 int dx = dst_mx - src_mx;
4110 int dy = dst_my - src_my;
4119 if (!IN_LEV_FIELD(x, y))
4122 element = Tile[x][y];
4124 if (!IS_MCDUFFIN(element) &&
4125 !IS_MIRROR(element) &&
4126 !IS_BEAMER(element) &&
4127 !IS_POLAR(element) &&
4128 !IS_POLAR_CROSS(element) &&
4129 !IS_DF_MIRROR(element))
4132 angle_old = get_element_angle(element);
4134 if (IS_MCDUFFIN(element))
4136 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4137 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4138 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4139 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4142 else if (IS_MIRROR(element) ||
4143 IS_DF_MIRROR(element))
4145 for (i = 0; i < laser.num_damages; i++)
4147 if (laser.damage[i].x == x &&
4148 laser.damage[i].y == y &&
4149 ObjHit(x, y, HIT_POS_CENTER))
4151 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4152 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4159 if (angle_new == -1)
4161 if (IS_MIRROR(element) ||
4162 IS_DF_MIRROR(element) ||
4166 if (IS_POLAR_CROSS(element))
4169 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4172 button = (angle_new == angle_old ? 0 :
4173 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4174 MB_LEFTBUTTON : MB_RIGHTBUTTON);