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 // check if laser scan has crossed element boundaries (not just mini tiles)
917 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
918 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
920 if (cross_x && cross_y)
922 int elx1 = (LX - XS) / TILEX;
923 int ely1 = (LY + YS) / TILEY;
924 int elx2 = (LX + XS) / TILEX;
925 int ely2 = (LY - YS) / TILEY;
927 // add element corners left and right from the laser beam to damage list
929 if (IN_LEV_FIELD(elx1, ely1) && Tile[elx1][ely1] != EL_EMPTY)
930 AddDamagedField(elx1, ely1);
932 if (IN_LEV_FIELD(elx2, ely2) && Tile[elx2][ely2] != EL_EMPTY)
933 AddDamagedField(elx2, ely2);
936 for (i = 0; i < 4; i++)
938 int px = LX + (i % 2) * 2;
939 int py = LY + (i / 2) * 2;
942 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
943 int ly = (py + TILEY) / TILEY - 1; // negative values!
946 if (IN_LEV_FIELD(lx, ly))
948 int element = Tile[lx][ly];
950 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
954 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
956 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
958 pixel = ((element & (1 << pos)) ? 1 : 0);
962 int pos = getMaskFromElement(element);
964 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
969 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
970 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
973 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
974 hit_mask |= (1 << i);
977 if (hit_mask == HIT_MASK_NO_HIT)
979 // hit nothing -- go on with another step
988 static void DeactivateLaserTargetElement(void)
990 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
991 laser.dest_element_last == EL_MINE_ACTIVE ||
992 laser.dest_element_last == EL_GRAY_BALL_ACTIVE ||
993 laser.dest_element_last == EL_GRAY_BALL_OPENING)
995 int x = laser.dest_element_last_x;
996 int y = laser.dest_element_last_y;
997 int element = laser.dest_element_last;
999 if (Tile[x][y] == element)
1000 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
1001 element == EL_MINE_ACTIVE ? EL_MINE : EL_GRAY_BALL);
1003 if (Tile[x][y] == EL_GRAY_BALL)
1006 laser.dest_element_last = EL_EMPTY;
1007 laser.dest_element_last_x = -1;
1008 laser.dest_element_last_y = -1;
1012 static void ScanLaser(void)
1014 int element = EL_EMPTY;
1015 int last_element = EL_EMPTY;
1016 int end = 0, rf = laser.num_edges;
1018 // do not scan laser again after the game was lost for whatever reason
1019 if (game_mm.game_over)
1022 // do not scan laser if fuse is off
1026 DeactivateLaserTargetElement();
1028 laser.overloaded = FALSE;
1029 laser.stops_inside_element = FALSE;
1031 DrawLaser(0, DL_LASER_ENABLED);
1034 Debug("game:mm:ScanLaser",
1035 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
1043 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
1046 laser.overloaded = TRUE;
1051 hit_mask = ScanPixel();
1054 Debug("game:mm:ScanLaser",
1055 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
1059 // hit something -- check out what it was
1060 ELX = (LX + XS) / TILEX;
1061 ELY = (LY + YS) / TILEY;
1064 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
1065 hit_mask, LX, LY, ELX, ELY);
1068 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1071 laser.dest_element = element;
1076 // check if laser scan has hit two diagonally adjacent element corners
1077 boolean diag_1 = ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1);
1078 boolean diag_2 = ((hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2);
1080 // check if laser scan has crossed element boundaries (not just mini tiles)
1081 boolean cross_x = (LX / TILEX != (LX + 2) / TILEX);
1082 boolean cross_y = (LY / TILEY != (LY + 2) / TILEY);
1084 // handle special case of laser hitting two diagonally adjacent elements
1085 // (with or without a third corner element behind these two elements)
1086 if ((diag_1 || diag_2) && cross_x && cross_y)
1088 // compare the two diagonally adjacent elements
1090 int yoffset = 2 * (diag_1 ? -1 : +1);
1091 int elx1 = (LX - xoffset) / TILEX;
1092 int ely1 = (LY + yoffset) / TILEY;
1093 int elx2 = (LX + xoffset) / TILEX;
1094 int ely2 = (LY - yoffset) / TILEY;
1095 int e1 = Tile[elx1][ely1];
1096 int e2 = Tile[elx2][ely2];
1097 boolean use_element_1 = FALSE;
1099 if (IS_WALL_ICE(e1) || IS_WALL_ICE(e2))
1101 if (IS_WALL_ICE(e1) && IS_WALL_ICE(e2))
1102 use_element_1 = (RND(2) ? TRUE : FALSE);
1103 else if (IS_WALL_ICE(e1))
1104 use_element_1 = TRUE;
1106 else if (IS_WALL_AMOEBA(e1) || IS_WALL_AMOEBA(e2))
1108 // if both tiles match, we can just select the first one
1109 if (IS_WALL_AMOEBA(e1))
1110 use_element_1 = TRUE;
1112 else if (IS_ABSORBING_BLOCK(e1) || IS_ABSORBING_BLOCK(e2))
1114 // if both tiles match, we can just select the first one
1115 if (IS_ABSORBING_BLOCK(e1))
1116 use_element_1 = TRUE;
1119 ELX = (use_element_1 ? elx1 : elx2);
1120 ELY = (use_element_1 ? ely1 : ely2);
1124 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1125 hit_mask, LX, LY, ELX, ELY);
1128 last_element = element;
1130 element = Tile[ELX][ELY];
1131 laser.dest_element = element;
1134 Debug("game:mm:ScanLaser",
1135 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1138 LX % TILEX, LY % TILEY,
1143 if (!IN_LEV_FIELD(ELX, ELY))
1144 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1148 // special case: leaving fixed MM steel grid (upwards) with non-90° angle
1149 if (element == EL_EMPTY &&
1150 IS_GRID_STEEL(last_element) &&
1151 laser.current_angle % 4) // angle is not 90°
1152 element = last_element;
1154 if (element == EL_EMPTY)
1156 if (!HitOnlyAnEdge(hit_mask))
1159 else if (element == EL_FUSE_ON)
1161 if (HitPolarizer(element, hit_mask))
1164 else if (IS_GRID(element) || IS_DF_GRID(element))
1166 if (HitPolarizer(element, hit_mask))
1169 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1170 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1172 if (HitBlock(element, hit_mask))
1179 else if (IS_MCDUFFIN(element))
1181 if (HitLaserSource(element, hit_mask))
1184 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1185 IS_RECEIVER(element))
1187 if (HitLaserDestination(element, hit_mask))
1190 else if (IS_WALL(element))
1192 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1194 if (HitReflectingWalls(element, hit_mask))
1199 if (HitAbsorbingWalls(element, hit_mask))
1205 if (HitElement(element, hit_mask))
1210 DrawLaser(rf - 1, DL_LASER_ENABLED);
1211 rf = laser.num_edges;
1213 if (!IS_DF_WALL_STEEL(element))
1215 // only used for scanning DF steel walls; reset for all other elements
1223 if (laser.dest_element != Tile[ELX][ELY])
1225 Debug("game:mm:ScanLaser",
1226 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1227 laser.dest_element, Tile[ELX][ELY]);
1231 if (!end && !laser.stops_inside_element && !StepBehind())
1234 Debug("game:mm:ScanLaser", "Go one step back");
1240 AddLaserEdge(LX, LY);
1244 DrawLaser(rf - 1, DL_LASER_ENABLED);
1246 Ct = CT = FrameCounter;
1249 if (!IN_LEV_FIELD(ELX, ELY))
1250 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1254 static void ScanLaser_FromLastMirror(void)
1256 int start_pos = (laser.num_damages > 0 ? laser.num_damages - 1 : 0);
1259 for (i = start_pos; i >= 0; i--)
1260 if (laser.damage[i].is_mirror)
1263 int start_edge = (i > 0 ? laser.damage[i].edge - 1 : 0);
1265 DrawLaser(start_edge, DL_LASER_DISABLED);
1270 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1276 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1277 start_edge, num_edges, mode);
1282 Warn("DrawLaserExt: start_edge < 0");
1289 Warn("DrawLaserExt: num_edges < 0");
1295 if (mode == DL_LASER_DISABLED)
1297 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1301 // now draw the laser to the backbuffer and (if enabled) to the screen
1302 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1304 redraw_mask |= REDRAW_FIELD;
1306 if (mode == DL_LASER_ENABLED)
1309 // after the laser was deleted, the "damaged" graphics must be restored
1310 if (laser.num_damages)
1312 int damage_start = 0;
1315 // determine the starting edge, from which graphics need to be restored
1318 for (i = 0; i < laser.num_damages; i++)
1320 if (laser.damage[i].edge == start_edge + 1)
1329 // restore graphics from this starting edge to the end of damage list
1330 for (i = damage_start; i < laser.num_damages; i++)
1332 int lx = laser.damage[i].x;
1333 int ly = laser.damage[i].y;
1334 int element = Tile[lx][ly];
1336 if (Hit[lx][ly] == laser.damage[i].edge)
1337 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1340 if (Box[lx][ly] == laser.damage[i].edge)
1343 if (IS_DRAWABLE(element))
1344 DrawField_MM(lx, ly);
1347 elx = laser.damage[damage_start].x;
1348 ely = laser.damage[damage_start].y;
1349 element = Tile[elx][ely];
1352 if (IS_BEAMER(element))
1356 for (i = 0; i < laser.num_beamers; i++)
1357 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1359 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1360 mode, elx, ely, Hit[elx][ely], start_edge);
1361 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1362 get_element_angle(element), laser.damage[damage_start].angle);
1366 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1367 laser.num_beamers > 0 &&
1368 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1370 // element is outgoing beamer
1371 laser.num_damages = damage_start + 1;
1373 if (IS_BEAMER(element))
1374 laser.current_angle = get_element_angle(element);
1378 // element is incoming beamer or other element
1379 laser.num_damages = damage_start;
1380 laser.current_angle = laser.damage[laser.num_damages].angle;
1385 // no damages but McDuffin himself (who needs to be redrawn anyway)
1387 elx = laser.start_edge.x;
1388 ely = laser.start_edge.y;
1389 element = Tile[elx][ely];
1392 laser.num_edges = start_edge + 1;
1393 if (start_edge == 0)
1394 laser.current_angle = laser.start_angle;
1396 LX = laser.edge[start_edge].x - cSX2;
1397 LY = laser.edge[start_edge].y - cSY2;
1398 XS = 2 * Step[laser.current_angle].x;
1399 YS = 2 * Step[laser.current_angle].y;
1402 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1408 if (IS_BEAMER(element) ||
1409 IS_FIBRE_OPTIC(element) ||
1410 IS_PACMAN(element) ||
1411 IS_POLAR(element) ||
1412 IS_POLAR_CROSS(element) ||
1413 element == EL_FUSE_ON)
1418 Debug("game:mm:DrawLaserExt", "element == %d", element);
1421 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1422 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1426 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1427 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1428 (laser.num_beamers == 0 ||
1429 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1431 // element is incoming beamer or other element
1432 step_size = -step_size;
1437 if (IS_BEAMER(element))
1438 Debug("game:mm:DrawLaserExt",
1439 "start_edge == %d, laser.beamer_edge == %d",
1440 start_edge, laser.beamer_edge);
1443 LX += step_size * XS;
1444 LY += step_size * YS;
1446 else if (element != EL_EMPTY)
1455 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1460 void DrawLaser(int start_edge, int mode)
1462 // do not draw laser if fuse is off
1463 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1466 if (mode == DL_LASER_DISABLED)
1467 DeactivateLaserTargetElement();
1469 if (laser.num_edges - start_edge < 0)
1471 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1476 // check if laser is interrupted by beamer element
1477 if (laser.num_beamers > 0 &&
1478 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1480 if (mode == DL_LASER_ENABLED)
1483 int tmp_start_edge = start_edge;
1485 // draw laser segments forward from the start to the last beamer
1486 for (i = 0; i < laser.num_beamers; i++)
1488 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1490 if (tmp_num_edges <= 0)
1494 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1495 i, laser.beamer_edge[i], tmp_start_edge);
1498 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1500 tmp_start_edge = laser.beamer_edge[i];
1503 // draw last segment from last beamer to the end
1504 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1510 int last_num_edges = laser.num_edges;
1511 int num_beamers = laser.num_beamers;
1513 // delete laser segments backward from the end to the first beamer
1514 for (i = num_beamers - 1; i >= 0; i--)
1516 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1518 if (laser.beamer_edge[i] - start_edge <= 0)
1521 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1523 last_num_edges = laser.beamer_edge[i];
1524 laser.num_beamers--;
1528 if (last_num_edges - start_edge <= 0)
1529 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1530 last_num_edges, start_edge);
1533 // special case when rotating first beamer: delete laser edge on beamer
1534 // (but do not start scanning on previous edge to prevent mirror sound)
1535 if (last_num_edges - start_edge == 1 && start_edge > 0)
1536 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1538 // delete first segment from start to the first beamer
1539 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1544 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1547 game_mm.laser_enabled = mode;
1550 void DrawLaser_MM(void)
1552 DrawLaser(0, game_mm.laser_enabled);
1555 static boolean HitElement(int element, int hit_mask)
1557 if (HitOnlyAnEdge(hit_mask))
1560 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1561 element = MovingOrBlocked2Element_MM(ELX, ELY);
1564 Debug("game:mm:HitElement", "(1): element == %d", element);
1568 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1569 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1572 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1576 AddDamagedField(ELX, ELY);
1578 // this is more precise: check if laser would go through the center
1579 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1583 // prevent cutting through laser emitter with laser beam
1584 if (IS_LASER(element))
1587 // skip the whole element before continuing the scan
1595 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1597 if ((LX/TILEX > ELX || LY/TILEY > ELY) && skip_count > 1)
1599 /* skipping scan positions to the right and down skips one scan
1600 position too much, because this is only the top left scan position
1601 of totally four scan positions (plus one to the right, one to the
1602 bottom and one to the bottom right) */
1603 /* ... but only roll back scan position if more than one step done */
1613 Debug("game:mm:HitElement", "(2): element == %d", element);
1616 if (LX + 5 * XS < 0 ||
1626 Debug("game:mm:HitElement", "(3): element == %d", element);
1629 if (IS_POLAR(element) &&
1630 ((element - EL_POLAR_START) % 2 ||
1631 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1633 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1635 laser.num_damages--;
1640 if (IS_POLAR_CROSS(element) &&
1641 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1643 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1645 laser.num_damages--;
1650 if (!IS_BEAMER(element) &&
1651 !IS_FIBRE_OPTIC(element) &&
1652 !IS_GRID_WOOD(element) &&
1653 element != EL_FUEL_EMPTY)
1656 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1657 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1659 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1662 LX = ELX * TILEX + 14;
1663 LY = ELY * TILEY + 14;
1665 AddLaserEdge(LX, LY);
1668 if (IS_MIRROR(element) ||
1669 IS_MIRROR_FIXED(element) ||
1670 IS_POLAR(element) ||
1671 IS_POLAR_CROSS(element) ||
1672 IS_DF_MIRROR(element) ||
1673 IS_DF_MIRROR_AUTO(element) ||
1674 element == EL_PRISM ||
1675 element == EL_REFRACTOR)
1677 int current_angle = laser.current_angle;
1680 laser.num_damages--;
1682 AddDamagedField(ELX, ELY);
1684 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1687 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1689 if (IS_MIRROR(element) ||
1690 IS_MIRROR_FIXED(element) ||
1691 IS_DF_MIRROR(element) ||
1692 IS_DF_MIRROR_AUTO(element))
1693 laser.current_angle = get_mirrored_angle(laser.current_angle,
1694 get_element_angle(element));
1696 if (element == EL_PRISM || element == EL_REFRACTOR)
1697 laser.current_angle = RND(16);
1699 XS = 2 * Step[laser.current_angle].x;
1700 YS = 2 * Step[laser.current_angle].y;
1702 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1707 LX += step_size * XS;
1708 LY += step_size * YS;
1710 // draw sparkles on mirror
1711 if ((IS_MIRROR(element) ||
1712 IS_MIRROR_FIXED(element) ||
1713 element == EL_PRISM) &&
1714 current_angle != laser.current_angle)
1716 MovDelay[ELX][ELY] = 11; // start animation
1719 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1720 current_angle != laser.current_angle)
1721 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1724 (get_opposite_angle(laser.current_angle) ==
1725 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1727 return (laser.overloaded ? TRUE : FALSE);
1730 if (element == EL_FUEL_FULL)
1732 laser.stops_inside_element = TRUE;
1737 if (element == EL_BOMB || element == EL_MINE || element == EL_GRAY_BALL)
1739 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1741 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE :
1742 element == EL_MINE ? EL_MINE_ACTIVE :
1743 EL_GRAY_BALL_ACTIVE);
1745 GfxFrame[ELX][ELY] = 0; // restart animation
1747 laser.dest_element_last = Tile[ELX][ELY];
1748 laser.dest_element_last_x = ELX;
1749 laser.dest_element_last_y = ELY;
1751 if (element == EL_MINE)
1752 laser.overloaded = TRUE;
1755 if (element == EL_KETTLE ||
1756 element == EL_CELL ||
1757 element == EL_KEY ||
1758 element == EL_LIGHTBALL ||
1759 element == EL_PACMAN ||
1760 IS_PACMAN(element) ||
1761 IS_ENVELOPE(element))
1763 if (!IS_PACMAN(element) &&
1764 !IS_ENVELOPE(element))
1767 if (element == EL_PACMAN)
1770 if (element == EL_KETTLE || element == EL_CELL)
1772 if (game_mm.kettles_still_needed > 0)
1773 game_mm.kettles_still_needed--;
1775 game.snapshot.collected_item = TRUE;
1777 if (game_mm.kettles_still_needed == 0)
1781 DrawLaser(0, DL_LASER_ENABLED);
1784 else if (element == EL_KEY)
1788 else if (IS_PACMAN(element))
1790 DeletePacMan(ELX, ELY);
1792 else if (IS_ENVELOPE(element))
1794 Tile[ELX][ELY] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(Tile[ELX][ELY]);
1797 RaiseScoreElement_MM(element);
1802 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1804 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1806 DrawLaser(0, DL_LASER_ENABLED);
1808 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1810 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1811 game_mm.lights_still_needed--;
1815 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1816 game_mm.lights_still_needed++;
1819 DrawField_MM(ELX, ELY);
1820 DrawLaser(0, DL_LASER_ENABLED);
1825 laser.stops_inside_element = TRUE;
1831 Debug("game:mm:HitElement", "(4): element == %d", element);
1834 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1835 laser.num_beamers < MAX_NUM_BEAMERS &&
1836 laser.beamer[BEAMER_NR(element)][1].num)
1838 int beamer_angle = get_element_angle(element);
1839 int beamer_nr = BEAMER_NR(element);
1843 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1846 laser.num_damages--;
1848 if (IS_FIBRE_OPTIC(element) ||
1849 laser.current_angle == get_opposite_angle(beamer_angle))
1853 LX = ELX * TILEX + 14;
1854 LY = ELY * TILEY + 14;
1856 AddLaserEdge(LX, LY);
1857 AddDamagedField(ELX, ELY);
1859 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1862 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1864 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1865 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1866 ELX = laser.beamer[beamer_nr][pos].x;
1867 ELY = laser.beamer[beamer_nr][pos].y;
1868 LX = ELX * TILEX + 14;
1869 LY = ELY * TILEY + 14;
1871 if (IS_BEAMER(element))
1873 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1874 XS = 2 * Step[laser.current_angle].x;
1875 YS = 2 * Step[laser.current_angle].y;
1878 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1880 AddLaserEdge(LX, LY);
1881 AddDamagedField(ELX, ELY);
1883 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1886 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1888 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1893 LX += step_size * XS;
1894 LY += step_size * YS;
1896 laser.num_beamers++;
1905 static boolean HitOnlyAnEdge(int hit_mask)
1907 // check if the laser hit only the edge of an element and, if so, go on
1910 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1914 if ((hit_mask == HIT_MASK_TOPLEFT ||
1915 hit_mask == HIT_MASK_TOPRIGHT ||
1916 hit_mask == HIT_MASK_BOTTOMLEFT ||
1917 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1918 laser.current_angle % 4) // angle is not 90°
1922 if (hit_mask == HIT_MASK_TOPLEFT)
1927 else if (hit_mask == HIT_MASK_TOPRIGHT)
1932 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1937 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1943 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1949 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1956 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1962 static boolean HitPolarizer(int element, int hit_mask)
1964 if (HitOnlyAnEdge(hit_mask))
1967 if (IS_DF_GRID(element))
1969 int grid_angle = get_element_angle(element);
1972 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1973 grid_angle, laser.current_angle);
1976 AddLaserEdge(LX, LY);
1977 AddDamagedField(ELX, ELY);
1980 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1982 if (laser.current_angle == grid_angle ||
1983 laser.current_angle == get_opposite_angle(grid_angle))
1985 // skip the whole element before continuing the scan
1991 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1993 if (LX/TILEX > ELX || LY/TILEY > ELY)
1995 /* skipping scan positions to the right and down skips one scan
1996 position too much, because this is only the top left scan position
1997 of totally four scan positions (plus one to the right, one to the
1998 bottom and one to the bottom right) */
2004 AddLaserEdge(LX, LY);
2010 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
2012 LX / TILEX, LY / TILEY,
2013 LX % TILEX, LY % TILEY);
2018 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
2020 return HitReflectingWalls(element, hit_mask);
2024 return HitAbsorbingWalls(element, hit_mask);
2027 else if (IS_GRID_STEEL(element))
2029 // may be required if graphics for steel grid redefined
2030 AddDamagedField(ELX, ELY);
2032 return HitReflectingWalls(element, hit_mask);
2034 else // IS_GRID_WOOD
2036 // may be required if graphics for wooden grid redefined
2037 AddDamagedField(ELX, ELY);
2039 return HitAbsorbingWalls(element, hit_mask);
2045 static boolean HitBlock(int element, int hit_mask)
2047 boolean check = FALSE;
2049 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
2050 game_mm.num_keys == 0)
2053 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2056 int ex = ELX * TILEX + 14;
2057 int ey = ELY * TILEY + 14;
2061 for (i = 1; i < 32; i++)
2066 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
2071 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
2072 return HitAbsorbingWalls(element, hit_mask);
2076 AddLaserEdge(LX - XS, LY - YS);
2077 AddDamagedField(ELX, ELY);
2080 Box[ELX][ELY] = laser.num_edges;
2082 return HitReflectingWalls(element, hit_mask);
2085 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
2087 int xs = XS / 2, ys = YS / 2;
2089 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2090 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2092 laser.overloaded = (element == EL_GATE_STONE);
2097 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2098 (hit_mask == HIT_MASK_TOP ||
2099 hit_mask == HIT_MASK_LEFT ||
2100 hit_mask == HIT_MASK_RIGHT ||
2101 hit_mask == HIT_MASK_BOTTOM))
2102 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2103 hit_mask == HIT_MASK_BOTTOM),
2104 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2105 hit_mask == HIT_MASK_RIGHT));
2106 AddLaserEdge(LX, LY);
2112 if (element == EL_GATE_STONE && Box[ELX][ELY])
2114 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
2126 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
2128 int xs = XS / 2, ys = YS / 2;
2130 if ((hit_mask & HIT_MASK_DIAGONAL_1) == HIT_MASK_DIAGONAL_1 ||
2131 (hit_mask & HIT_MASK_DIAGONAL_2) == HIT_MASK_DIAGONAL_2)
2133 laser.overloaded = (element == EL_BLOCK_STONE);
2138 if (ABS(xs) == 1 && ABS(ys) == 1 &&
2139 (hit_mask == HIT_MASK_TOP ||
2140 hit_mask == HIT_MASK_LEFT ||
2141 hit_mask == HIT_MASK_RIGHT ||
2142 hit_mask == HIT_MASK_BOTTOM))
2143 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
2144 hit_mask == HIT_MASK_BOTTOM),
2145 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
2146 hit_mask == HIT_MASK_RIGHT));
2147 AddDamagedField(ELX, ELY);
2149 LX = ELX * TILEX + 14;
2150 LY = ELY * TILEY + 14;
2152 AddLaserEdge(LX, LY);
2154 laser.stops_inside_element = TRUE;
2162 static boolean HitLaserSource(int element, int hit_mask)
2164 if (HitOnlyAnEdge(hit_mask))
2167 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2169 laser.overloaded = TRUE;
2174 static boolean HitLaserDestination(int element, int hit_mask)
2176 if (HitOnlyAnEdge(hit_mask))
2179 if (element != EL_EXIT_OPEN &&
2180 !(IS_RECEIVER(element) &&
2181 game_mm.kettles_still_needed == 0 &&
2182 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2184 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2189 if (IS_RECEIVER(element) ||
2190 (IS_22_5_ANGLE(laser.current_angle) &&
2191 (ELX != (LX + 6 * XS) / TILEX ||
2192 ELY != (LY + 6 * YS) / TILEY ||
2201 LX = ELX * TILEX + 14;
2202 LY = ELY * TILEY + 14;
2204 laser.stops_inside_element = TRUE;
2207 AddLaserEdge(LX, LY);
2208 AddDamagedField(ELX, ELY);
2210 if (game_mm.lights_still_needed == 0)
2212 game_mm.level_solved = TRUE;
2214 SetTileCursorActive(FALSE);
2220 static boolean HitReflectingWalls(int element, int hit_mask)
2222 // check if laser hits side of a wall with an angle that is not 90°
2223 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2224 hit_mask == HIT_MASK_LEFT ||
2225 hit_mask == HIT_MASK_RIGHT ||
2226 hit_mask == HIT_MASK_BOTTOM))
2228 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2233 if (!IS_DF_GRID(element))
2234 AddLaserEdge(LX, LY);
2236 // check if laser hits wall with an angle of 45°
2237 if (!IS_22_5_ANGLE(laser.current_angle))
2239 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2242 laser.current_angle = get_mirrored_angle(laser.current_angle,
2245 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2248 laser.current_angle = get_mirrored_angle(laser.current_angle,
2252 AddLaserEdge(LX, LY);
2254 XS = 2 * Step[laser.current_angle].x;
2255 YS = 2 * Step[laser.current_angle].y;
2259 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2261 laser.current_angle = get_mirrored_angle(laser.current_angle,
2266 if (!IS_DF_GRID(element))
2267 AddLaserEdge(LX, LY);
2272 if (!IS_DF_GRID(element))
2273 AddLaserEdge(LX, LY + YS / 2);
2276 if (!IS_DF_GRID(element))
2277 AddLaserEdge(LX, LY);
2280 YS = 2 * Step[laser.current_angle].y;
2284 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2286 laser.current_angle = get_mirrored_angle(laser.current_angle,
2291 if (!IS_DF_GRID(element))
2292 AddLaserEdge(LX, LY);
2297 if (!IS_DF_GRID(element))
2298 AddLaserEdge(LX + XS / 2, LY);
2301 if (!IS_DF_GRID(element))
2302 AddLaserEdge(LX, LY);
2305 XS = 2 * Step[laser.current_angle].x;
2311 // reflection at the edge of reflecting DF style wall
2312 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2314 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2315 hit_mask == HIT_MASK_TOPRIGHT) ||
2316 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2317 hit_mask == HIT_MASK_TOPLEFT) ||
2318 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2319 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2320 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2321 hit_mask == HIT_MASK_BOTTOMRIGHT))
2324 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2325 ANG_MIRROR_135 : ANG_MIRROR_45);
2327 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2329 AddDamagedField(ELX, ELY);
2330 AddLaserEdge(LX, LY);
2332 laser.current_angle = get_mirrored_angle(laser.current_angle,
2340 AddLaserEdge(LX, LY);
2346 // reflection inside an edge of reflecting DF style wall
2347 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2349 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2350 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2351 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2352 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2353 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2354 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2355 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2356 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2359 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2360 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2361 ANG_MIRROR_135 : ANG_MIRROR_45);
2363 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2366 AddDamagedField(ELX, ELY);
2369 AddLaserEdge(LX - XS, LY - YS);
2370 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2371 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2373 laser.current_angle = get_mirrored_angle(laser.current_angle,
2381 AddLaserEdge(LX, LY);
2387 // check if laser hits DF style wall with an angle of 90°
2388 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2390 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2391 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2392 (IS_VERT_ANGLE(laser.current_angle) &&
2393 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2395 // laser at last step touched nothing or the same side of the wall
2396 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2398 AddDamagedField(ELX, ELY);
2405 last_hit_mask = hit_mask;
2412 if (!HitOnlyAnEdge(hit_mask))
2414 laser.overloaded = TRUE;
2422 static boolean HitAbsorbingWalls(int element, int hit_mask)
2424 if (HitOnlyAnEdge(hit_mask))
2428 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2430 AddLaserEdge(LX - XS, LY - YS);
2437 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2439 AddLaserEdge(LX - XS, LY - YS);
2445 if (IS_WALL_WOOD(element) ||
2446 IS_DF_WALL_WOOD(element) ||
2447 IS_GRID_WOOD(element) ||
2448 IS_GRID_WOOD_FIXED(element) ||
2449 IS_GRID_WOOD_AUTO(element) ||
2450 element == EL_FUSE_ON ||
2451 element == EL_BLOCK_WOOD ||
2452 element == EL_GATE_WOOD)
2454 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2459 if (IS_WALL_ICE(element))
2465 // check if laser hit adjacent edges of two diagonal tiles
2466 if (ELX != lx / TILEX)
2468 if (ELY != ly / TILEY)
2471 mask = lx / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2472 mask <<= ((ly / MINI_TILEY - ELY * 2) > 0 ? 2 : 0); // || (vertical)
2474 // check if laser hits wall with an angle of 90°
2475 if (IS_90_ANGLE(laser.current_angle))
2476 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2478 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2482 for (i = 0; i < 4; i++)
2484 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2485 mask = 15 - (8 >> i);
2486 else if (ABS(XS) == 4 &&
2488 (XS > 0) == (i % 2) &&
2489 (YS < 0) == (i / 2))
2490 mask = 3 + (i / 2) * 9;
2491 else if (ABS(YS) == 4 &&
2493 (XS < 0) == (i % 2) &&
2494 (YS > 0) == (i / 2))
2495 mask = 5 + (i % 2) * 5;
2499 laser.wall_mask = mask;
2501 else if (IS_WALL_AMOEBA(element))
2503 int elx = (LX - 2 * XS) / TILEX;
2504 int ely = (LY - 2 * YS) / TILEY;
2505 int element2 = Tile[elx][ely];
2508 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2510 laser.dest_element = EL_EMPTY;
2518 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2519 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2521 if (IS_90_ANGLE(laser.current_angle))
2522 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2524 laser.dest_element = element2 | EL_WALL_AMOEBA_BASE;
2526 laser.wall_mask = mask;
2532 static void OpenExit(int x, int y)
2536 if (!MovDelay[x][y]) // next animation frame
2537 MovDelay[x][y] = 4 * delay;
2539 if (MovDelay[x][y]) // wait some time before next frame
2544 phase = MovDelay[x][y] / delay;
2546 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2547 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2549 if (!MovDelay[x][y])
2551 Tile[x][y] = EL_EXIT_OPEN;
2557 static void OpenGrayBall(int x, int y)
2561 if (!MovDelay[x][y]) // next animation frame
2563 if (IS_WALL(Store[x][y]))
2565 DrawWalls_MM(x, y, Store[x][y]);
2567 // copy wall tile to spare bitmap for "melting" animation
2568 BlitBitmap(drawto, bitmap_db_field, cSX + x * TILEX, cSY + y * TILEY,
2569 TILEX, TILEY, x * TILEX, y * TILEY);
2571 DrawElement_MM(x, y, EL_GRAY_BALL);
2574 MovDelay[x][y] = 50 * delay;
2577 if (MovDelay[x][y]) // wait some time before next frame
2581 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2585 int dx = RND(26), dy = RND(26);
2587 if (IS_WALL(Store[x][y]))
2589 // copy wall tile from spare bitmap for "melting" animation
2590 bitmap = bitmap_db_field;
2596 int graphic = el2gfx(Store[x][y]);
2598 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2601 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2602 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2604 laser.redraw = TRUE;
2606 MarkTileDirty(x, y);
2609 if (!MovDelay[x][y])
2611 Tile[x][y] = Store[x][y];
2612 Store[x][y] = Store2[x][y] = 0;
2613 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2615 InitField(x, y, FALSE);
2618 ScanLaser_FromLastMirror();
2623 static void OpenEnvelope(int x, int y)
2625 int num_frames = 8; // seven frames plus final empty space
2627 if (!MovDelay[x][y]) // next animation frame
2628 MovDelay[x][y] = num_frames;
2630 if (MovDelay[x][y]) // wait some time before next frame
2632 int nr = ENVELOPE_OPENING_NR(Tile[x][y]);
2636 if (MovDelay[x][y] > 0 && IN_SCR_FIELD(x, y))
2638 int graphic = el_act2gfx(EL_ENVELOPE_1 + nr, MM_ACTION_COLLECTING);
2639 int frame = num_frames - MovDelay[x][y] - 1;
2641 DrawGraphicAnimation_MM(x, y, graphic, frame);
2643 laser.redraw = TRUE;
2646 if (MovDelay[x][y] == 0)
2648 Tile[x][y] = EL_EMPTY;
2654 ShowEnvelope_MM(nr);
2659 static void MeltIce(int x, int y)
2664 if (!MovDelay[x][y]) // next animation frame
2665 MovDelay[x][y] = frames * delay;
2667 if (MovDelay[x][y]) // wait some time before next frame
2670 int wall_mask = Store2[x][y];
2671 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_ICE_BASE;
2674 phase = frames - MovDelay[x][y] / delay - 1;
2676 if (!MovDelay[x][y])
2678 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2679 Store[x][y] = Store2[x][y] = 0;
2681 DrawWalls_MM(x, y, Tile[x][y]);
2683 if (Tile[x][y] == EL_WALL_ICE_BASE)
2684 Tile[x][y] = EL_EMPTY;
2686 ScanLaser_FromLastMirror();
2688 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2690 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2692 laser.redraw = TRUE;
2697 static void GrowAmoeba(int x, int y)
2702 if (!MovDelay[x][y]) // next animation frame
2703 MovDelay[x][y] = frames * delay;
2705 if (MovDelay[x][y]) // wait some time before next frame
2708 int wall_mask = Store2[x][y];
2709 int real_element = Tile[x][y] - EL_WALL_CHANGING_BASE + EL_WALL_AMOEBA_BASE;
2712 phase = MovDelay[x][y] / delay;
2714 if (!MovDelay[x][y])
2716 Tile[x][y] = real_element;
2717 Store[x][y] = Store2[x][y] = 0;
2719 DrawWalls_MM(x, y, Tile[x][y]);
2720 DrawLaser(0, DL_LASER_ENABLED);
2722 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2724 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2729 static void DrawFieldAnimated_MM(int x, int y)
2733 laser.redraw = TRUE;
2736 static void DrawFieldAnimatedIfNeeded_MM(int x, int y)
2738 int element = Tile[x][y];
2739 int graphic = el2gfx(element);
2741 if (!getGraphicInfo_NewFrame(x, y, graphic))
2746 laser.redraw = TRUE;
2749 static void DrawFieldTwinkle(int x, int y)
2751 if (MovDelay[x][y] != 0) // wait some time before next frame
2757 if (MovDelay[x][y] != 0)
2759 int graphic = IMG_TWINKLE_WHITE;
2760 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2762 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2765 laser.redraw = TRUE;
2769 static void Explode_MM(int x, int y, int phase, int mode)
2771 int num_phase = 9, delay = 2;
2772 int last_phase = num_phase * delay;
2773 int half_phase = (num_phase / 2) * delay;
2776 laser.redraw = TRUE;
2778 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2780 center_element = Tile[x][y];
2782 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2784 // put moving element to center field (and let it explode there)
2785 center_element = MovingOrBlocked2Element_MM(x, y);
2786 RemoveMovingField_MM(x, y);
2788 Tile[x][y] = center_element;
2791 if (center_element != EL_GRAY_BALL_ACTIVE)
2792 Store[x][y] = EL_EMPTY;
2793 Store2[x][y] = center_element;
2795 Tile[x][y] = EL_EXPLODING_OPAQUE;
2797 GfxElement[x][y] = (center_element == EL_BOMB_ACTIVE ? EL_BOMB :
2798 center_element == EL_GRAY_BALL_ACTIVE ? EL_GRAY_BALL :
2799 IS_MCDUFFIN(center_element) ? EL_MCDUFFIN :
2802 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2804 ExplodePhase[x][y] = 1;
2810 GfxFrame[x][y] = 0; // restart explosion animation
2812 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2814 center_element = Store2[x][y];
2816 if (phase == half_phase && Store[x][y] == EL_EMPTY)
2818 Tile[x][y] = EL_EXPLODING_TRANSP;
2820 if (x == ELX && y == ELY)
2824 if (phase == last_phase)
2826 if (center_element == EL_BOMB_ACTIVE)
2828 DrawLaser(0, DL_LASER_DISABLED);
2831 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2833 GameOver_MM(GAME_OVER_DELAYED);
2835 laser.overloaded = FALSE;
2837 else if (IS_MCDUFFIN(center_element))
2839 GameOver_MM(GAME_OVER_BOMB);
2842 Tile[x][y] = Store[x][y];
2844 Store[x][y] = Store2[x][y] = 0;
2845 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2847 InitField(x, y, FALSE);
2850 if (center_element == EL_GRAY_BALL_ACTIVE)
2851 ScanLaser_FromLastMirror();
2853 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2855 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2856 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2858 DrawGraphicAnimation_MM(x, y, graphic, frame);
2860 MarkTileDirty(x, y);
2864 static void Bang_MM(int x, int y)
2866 int element = Tile[x][y];
2868 if (IS_PACMAN(element))
2869 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2870 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2871 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2872 else if (element == EL_KEY)
2873 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2875 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2877 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2880 static void TurnRound(int x, int y)
2892 { 0, 0 }, { 0, 0 }, { 0, 0 },
2897 int left, right, back;
2901 { MV_DOWN, MV_UP, MV_RIGHT },
2902 { MV_UP, MV_DOWN, MV_LEFT },
2904 { MV_LEFT, MV_RIGHT, MV_DOWN },
2905 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2906 { MV_RIGHT, MV_LEFT, MV_UP }
2909 int element = Tile[x][y];
2910 int old_move_dir = MovDir[x][y];
2911 int right_dir = turn[old_move_dir].right;
2912 int back_dir = turn[old_move_dir].back;
2913 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2914 int right_x = x + right_dx, right_y = y + right_dy;
2916 if (element == EL_PACMAN)
2918 boolean can_turn_right = FALSE;
2920 if (IN_LEV_FIELD(right_x, right_y) &&
2921 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2922 can_turn_right = TRUE;
2925 MovDir[x][y] = right_dir;
2927 MovDir[x][y] = back_dir;
2933 static void StartMoving_MM(int x, int y)
2935 int element = Tile[x][y];
2940 if (CAN_MOVE(element))
2944 if (MovDelay[x][y]) // wait some time before next movement
2952 // now make next step
2954 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2956 if (element == EL_PACMAN &&
2957 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2958 !ObjHit(newx, newy, HIT_POS_CENTER))
2960 Store[newx][newy] = Tile[newx][newy];
2961 Tile[newx][newy] = EL_EMPTY;
2963 DrawField_MM(newx, newy);
2965 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2966 ObjHit(newx, newy, HIT_POS_CENTER))
2968 // object was running against a wall
2975 InitMovingField_MM(x, y, MovDir[x][y]);
2979 ContinueMoving_MM(x, y);
2982 static void ContinueMoving_MM(int x, int y)
2984 int element = Tile[x][y];
2985 int direction = MovDir[x][y];
2986 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2987 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2988 int horiz_move = (dx!=0);
2989 int newx = x + dx, newy = y + dy;
2990 int step = (horiz_move ? dx : dy) * TILEX / 8;
2992 MovPos[x][y] += step;
2994 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2996 Tile[x][y] = EL_EMPTY;
2997 Tile[newx][newy] = element;
2999 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3000 MovDelay[newx][newy] = 0;
3002 if (!CAN_MOVE(element))
3003 MovDir[newx][newy] = 0;
3006 DrawField_MM(newx, newy);
3008 Stop[newx][newy] = TRUE;
3010 if (element == EL_PACMAN)
3012 if (Store[newx][newy] == EL_BOMB)
3013 Bang_MM(newx, newy);
3015 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
3016 (LX + 2 * XS) / TILEX == newx &&
3017 (LY + 2 * YS) / TILEY == newy)
3024 else // still moving on
3029 laser.redraw = TRUE;
3032 boolean ClickElement(int x, int y, int button)
3034 static DelayCounter click_delay = { CLICK_DELAY };
3035 static boolean new_button = TRUE;
3036 boolean element_clicked = FALSE;
3041 // initialize static variables
3042 click_delay.count = 0;
3043 click_delay.value = CLICK_DELAY;
3049 // do not rotate objects hit by the laser after the game was solved
3050 if (game_mm.level_solved && Hit[x][y])
3053 if (button == MB_RELEASED)
3056 click_delay.value = CLICK_DELAY;
3058 // release eventually hold auto-rotating mirror
3059 RotateMirror(x, y, MB_RELEASED);
3064 if (!FrameReached(&click_delay) && !new_button)
3067 if (button == MB_MIDDLEBUTTON) // middle button has no function
3070 if (!IN_LEV_FIELD(x, y))
3073 if (Tile[x][y] == EL_EMPTY)
3076 element = Tile[x][y];
3078 if (IS_MIRROR(element) ||
3079 IS_BEAMER(element) ||
3080 IS_POLAR(element) ||
3081 IS_POLAR_CROSS(element) ||
3082 IS_DF_MIRROR(element) ||
3083 IS_DF_MIRROR_AUTO(element))
3085 RotateMirror(x, y, button);
3087 element_clicked = TRUE;
3089 else if (IS_MCDUFFIN(element))
3091 if (!laser.fuse_off)
3093 DrawLaser(0, DL_LASER_DISABLED);
3100 element = get_rotated_element(element, BUTTON_ROTATION(button));
3101 laser.start_angle = get_element_angle(element);
3105 Tile[x][y] = element;
3112 if (!laser.fuse_off)
3115 element_clicked = TRUE;
3117 else if (element == EL_FUSE_ON && laser.fuse_off)
3119 if (x != laser.fuse_x || y != laser.fuse_y)
3122 laser.fuse_off = FALSE;
3123 laser.fuse_x = laser.fuse_y = -1;
3125 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
3128 element_clicked = TRUE;
3130 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
3132 laser.fuse_off = TRUE;
3135 laser.overloaded = FALSE;
3137 DrawLaser(0, DL_LASER_DISABLED);
3138 DrawGraphic_MM(x, y, IMG_MM_FUSE);
3140 element_clicked = TRUE;
3142 else if (element == EL_LIGHTBALL)
3145 RaiseScoreElement_MM(element);
3146 DrawLaser(0, DL_LASER_ENABLED);
3148 element_clicked = TRUE;
3150 else if (IS_ENVELOPE(element))
3152 Tile[x][y] = EL_ENVELOPE_1_OPENING + ENVELOPE_NR(element);
3154 element_clicked = TRUE;
3157 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
3160 return element_clicked;
3163 static void RotateMirror(int x, int y, int button)
3165 if (button == MB_RELEASED)
3167 // release eventually hold auto-rotating mirror
3174 if (IS_MIRROR(Tile[x][y]) ||
3175 IS_POLAR_CROSS(Tile[x][y]) ||
3176 IS_POLAR(Tile[x][y]) ||
3177 IS_BEAMER(Tile[x][y]) ||
3178 IS_DF_MIRROR(Tile[x][y]) ||
3179 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
3180 IS_GRID_WOOD_AUTO(Tile[x][y]))
3182 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
3184 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
3186 if (button == MB_LEFTBUTTON)
3188 // left mouse button only for manual adjustment, no auto-rotating;
3189 // freeze mirror for until mouse button released
3193 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
3195 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
3199 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
3201 int edge = Hit[x][y];
3207 DrawLaser(edge - 1, DL_LASER_DISABLED);
3211 else if (ObjHit(x, y, HIT_POS_CENTER))
3213 int edge = Hit[x][y];
3217 Warn("RotateMirror: inconsistent field Hit[][]!\n");
3222 DrawLaser(edge - 1, DL_LASER_DISABLED);
3229 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3234 if ((IS_BEAMER(Tile[x][y]) ||
3235 IS_POLAR(Tile[x][y]) ||
3236 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3238 if (IS_BEAMER(Tile[x][y]))
3241 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3242 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3255 DrawLaser(0, DL_LASER_ENABLED);
3259 static void AutoRotateMirrors(void)
3263 if (!FrameReached(&rotate_delay))
3266 for (x = 0; x < lev_fieldx; x++)
3268 for (y = 0; y < lev_fieldy; y++)
3270 int element = Tile[x][y];
3272 // do not rotate objects hit by the laser after the game was solved
3273 if (game_mm.level_solved && Hit[x][y])
3276 if (IS_DF_MIRROR_AUTO(element) ||
3277 IS_GRID_WOOD_AUTO(element) ||
3278 IS_GRID_STEEL_AUTO(element) ||
3279 element == EL_REFRACTOR)
3280 RotateMirror(x, y, MB_RIGHTBUTTON);
3285 static boolean ObjHit(int obx, int oby, int bits)
3292 if (bits & HIT_POS_CENTER)
3294 if (CheckLaserPixel(cSX + obx + 15,
3299 if (bits & HIT_POS_EDGE)
3301 for (i = 0; i < 4; i++)
3302 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3303 cSY + oby + 31 * (i / 2)))
3307 if (bits & HIT_POS_BETWEEN)
3309 for (i = 0; i < 4; i++)
3310 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3311 cSY + 4 + oby + 22 * (i / 2)))
3318 static void DeletePacMan(int px, int py)
3324 if (game_mm.num_pacman <= 1)
3326 game_mm.num_pacman = 0;
3330 for (i = 0; i < game_mm.num_pacman; i++)
3331 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3334 game_mm.num_pacman--;
3336 for (j = i; j < game_mm.num_pacman; j++)
3338 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3339 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3340 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3344 static void GameActions_MM_Ext(void)
3351 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3354 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3356 element = Tile[x][y];
3358 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3359 StartMoving_MM(x, y);
3360 else if (IS_MOVING(x, y))
3361 ContinueMoving_MM(x, y);
3362 else if (IS_EXPLODING(element))
3363 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3364 else if (element == EL_EXIT_OPENING)
3366 else if (element == EL_GRAY_BALL_OPENING)
3368 else if (IS_ENVELOPE_OPENING(element))
3370 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE_BASE)
3372 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA_BASE)
3374 else if (IS_MIRROR(element) ||
3375 IS_MIRROR_FIXED(element) ||
3376 element == EL_PRISM)
3377 DrawFieldTwinkle(x, y);
3378 else if (element == EL_GRAY_BALL_ACTIVE ||
3379 element == EL_BOMB_ACTIVE ||
3380 element == EL_MINE_ACTIVE)
3381 DrawFieldAnimated_MM(x, y);
3382 else if (!IS_BLOCKED(x, y))
3383 DrawFieldAnimatedIfNeeded_MM(x, y);
3386 AutoRotateMirrors();
3389 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3391 // redraw after Explode_MM() ...
3393 DrawLaser(0, DL_LASER_ENABLED);
3394 laser.redraw = FALSE;
3399 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3403 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3405 DrawLaser(0, DL_LASER_DISABLED);
3410 // skip all following game actions if game is over
3411 if (game_mm.game_over)
3414 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3418 GameOver_MM(GAME_OVER_NO_ENERGY);
3423 if (FrameReached(&energy_delay))
3425 if (game_mm.energy_left > 0)
3426 game_mm.energy_left--;
3428 // when out of energy, wait another frame to play "out of time" sound
3431 element = laser.dest_element;
3434 if (element != Tile[ELX][ELY])
3436 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3437 element, Tile[ELX][ELY]);
3441 if (!laser.overloaded && laser.overload_value == 0 &&
3442 element != EL_BOMB &&
3443 element != EL_BOMB_ACTIVE &&
3444 element != EL_MINE &&
3445 element != EL_MINE_ACTIVE &&
3446 element != EL_GRAY_BALL &&
3447 element != EL_GRAY_BALL_ACTIVE &&
3448 element != EL_BLOCK_STONE &&
3449 element != EL_BLOCK_WOOD &&
3450 element != EL_FUSE_ON &&
3451 element != EL_FUEL_FULL &&
3452 !IS_WALL_ICE(element) &&
3453 !IS_WALL_AMOEBA(element))
3456 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3458 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3459 (!laser.overloaded && laser.overload_value > 0)) &&
3460 FrameReached(&overload_delay))
3462 if (laser.overloaded)
3463 laser.overload_value++;
3465 laser.overload_value--;
3467 if (game_mm.cheat_no_overload)
3469 laser.overloaded = FALSE;
3470 laser.overload_value = 0;
3473 game_mm.laser_overload_value = laser.overload_value;
3475 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3477 SetLaserColor(0xFF);
3479 DrawLaser(0, DL_LASER_ENABLED);
3482 if (!laser.overloaded)
3483 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3484 else if (setup.sound_loops)
3485 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3487 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3489 if (laser.overload_value == MAX_LASER_OVERLOAD)
3491 UpdateAndDisplayGameControlValues();
3495 GameOver_MM(GAME_OVER_OVERLOADED);
3506 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3508 if (game_mm.cheat_no_explosion)
3513 laser.dest_element = EL_EXPLODING_OPAQUE;
3518 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3520 laser.fuse_off = TRUE;
3524 DrawLaser(0, DL_LASER_DISABLED);
3525 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3528 if (element == EL_GRAY_BALL && CT > native_mm_level.time_ball)
3530 if (!Store2[ELX][ELY]) // check if content element not yet determined
3532 int last_anim_random_frame = gfx.anim_random_frame;
3535 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3536 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3538 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3539 native_mm_level.ball_choice_mode, 0,
3540 game_mm.ball_choice_pos);
3542 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3543 gfx.anim_random_frame = last_anim_random_frame;
3545 game_mm.ball_choice_pos++;
3547 int new_element = native_mm_level.ball_content[element_pos];
3548 int new_element_base = map_wall_to_base_element(new_element);
3550 if (IS_WALL(new_element_base))
3552 // always use completely filled wall element
3553 new_element = new_element_base | 0x000f;
3555 else if (native_mm_level.rotate_ball_content &&
3556 get_num_elements(new_element) > 1)
3558 // randomly rotate newly created game element
3559 new_element = get_rotated_element(new_element, RND(16));
3562 Store[ELX][ELY] = new_element;
3563 Store2[ELX][ELY] = TRUE;
3566 if (native_mm_level.explode_ball)
3569 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3571 laser.dest_element = laser.dest_element_last = Tile[ELX][ELY];
3576 if (IS_WALL_ICE(element) && CT > 50)
3578 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3580 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE_BASE + EL_WALL_CHANGING_BASE;
3581 Store[ELX][ELY] = EL_WALL_ICE_BASE;
3582 Store2[ELX][ELY] = laser.wall_mask;
3584 laser.dest_element = Tile[ELX][ELY];
3589 if (IS_WALL_AMOEBA(element) && CT > 60)
3592 int element2 = Tile[ELX][ELY];
3594 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3597 for (i = laser.num_damages - 1; i >= 0; i--)
3598 if (laser.damage[i].is_mirror)
3601 r = laser.num_edges;
3602 d = laser.num_damages;
3609 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3612 DrawLaser(0, DL_LASER_ENABLED);
3615 x = laser.damage[k1].x;
3616 y = laser.damage[k1].y;
3621 for (i = 0; i < 4; i++)
3623 if (laser.wall_mask & (1 << i))
3625 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3626 cSY + ELY * TILEY + 31 * (i / 2)))
3629 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3630 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3637 for (i = 0; i < 4; i++)
3639 if (laser.wall_mask & (1 << i))
3641 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3642 cSY + ELY * TILEY + 31 * (i / 2)))
3649 if (laser.num_beamers > 0 ||
3650 k1 < 1 || k2 < 4 || k3 < 4 ||
3651 CheckLaserPixel(cSX + ELX * TILEX + 14,
3652 cSY + ELY * TILEY + 14))
3654 laser.num_edges = r;
3655 laser.num_damages = d;
3657 DrawLaser(0, DL_LASER_DISABLED);
3660 Tile[ELX][ELY] = element | laser.wall_mask;
3662 int x = ELX, y = ELY;
3663 int wall_mask = laser.wall_mask;
3666 DrawLaser(0, DL_LASER_ENABLED);
3668 PlayLevelSound_MM(x, y, element, MM_ACTION_GROWING);
3670 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA_BASE + EL_WALL_CHANGING_BASE;
3671 Store[x][y] = EL_WALL_AMOEBA_BASE;
3672 Store2[x][y] = wall_mask;
3677 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3678 laser.stops_inside_element && CT > native_mm_level.time_block)
3683 if (ABS(XS) > ABS(YS))
3690 for (i = 0; i < 4; i++)
3697 x = ELX + Step[k * 4].x;
3698 y = ELY + Step[k * 4].y;
3700 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3703 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3711 laser.overloaded = (element == EL_BLOCK_STONE);
3716 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3719 Tile[x][y] = element;
3721 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3724 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3726 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3727 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3735 if (element == EL_FUEL_FULL && CT > 10)
3737 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3738 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3740 for (i = start; i <= num_init_game_frames; i++)
3742 if (i == num_init_game_frames)
3743 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3744 else if (setup.sound_loops)
3745 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3747 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3749 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3751 UpdateAndDisplayGameControlValues();
3756 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3758 DrawField_MM(ELX, ELY);
3760 DrawLaser(0, DL_LASER_ENABLED);
3766 void GameActions_MM(struct MouseActionInfo action)
3768 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3769 boolean button_released = (action.button == MB_RELEASED);
3771 GameActions_MM_Ext();
3773 CheckSingleStepMode_MM(element_clicked, button_released);
3776 static void MovePacMen(void)
3778 int mx, my, ox, oy, nx, ny;
3782 if (++pacman_nr >= game_mm.num_pacman)
3785 game_mm.pacman[pacman_nr].dir--;
3787 for (l = 1; l < 5; l++)
3789 game_mm.pacman[pacman_nr].dir++;
3791 if (game_mm.pacman[pacman_nr].dir > 4)
3792 game_mm.pacman[pacman_nr].dir = 1;
3794 if (game_mm.pacman[pacman_nr].dir % 2)
3797 my = game_mm.pacman[pacman_nr].dir - 2;
3802 mx = 3 - game_mm.pacman[pacman_nr].dir;
3805 ox = game_mm.pacman[pacman_nr].x;
3806 oy = game_mm.pacman[pacman_nr].y;
3809 element = Tile[nx][ny];
3811 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3814 if (!IS_EATABLE4PACMAN(element))
3817 if (ObjHit(nx, ny, HIT_POS_CENTER))
3820 Tile[ox][oy] = EL_EMPTY;
3822 EL_PACMAN_RIGHT - 1 +
3823 (game_mm.pacman[pacman_nr].dir - 1 +
3824 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3826 game_mm.pacman[pacman_nr].x = nx;
3827 game_mm.pacman[pacman_nr].y = ny;
3829 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3831 if (element != EL_EMPTY)
3833 int graphic = el2gfx(Tile[nx][ny]);
3838 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3841 ox = cSX + ox * TILEX;
3842 oy = cSY + oy * TILEY;
3844 for (i = 1; i < 33; i += 2)
3845 BlitBitmap(bitmap, window,
3846 src_x, src_y, TILEX, TILEY,
3847 ox + i * mx, oy + i * my);
3848 Ct = Ct + FrameCounter - CT;
3851 DrawField_MM(nx, ny);
3854 if (!laser.fuse_off)
3856 DrawLaser(0, DL_LASER_ENABLED);
3858 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3860 AddDamagedField(nx, ny);
3862 laser.damage[laser.num_damages - 1].edge = 0;
3866 if (element == EL_BOMB)
3867 DeletePacMan(nx, ny);
3869 if (IS_WALL_AMOEBA(element) &&
3870 (LX + 2 * XS) / TILEX == nx &&
3871 (LY + 2 * YS) / TILEY == ny)
3881 static void InitMovingField_MM(int x, int y, int direction)
3883 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3884 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3886 MovDir[x][y] = direction;
3887 MovDir[newx][newy] = direction;
3889 if (Tile[newx][newy] == EL_EMPTY)
3890 Tile[newx][newy] = EL_BLOCKED;
3893 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3895 int direction = MovDir[x][y];
3896 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3897 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3903 static void Blocked2Moving_MM(int x, int y,
3904 int *comes_from_x, int *comes_from_y)
3906 int oldx = x, oldy = y;
3907 int direction = MovDir[x][y];
3909 if (direction == MV_LEFT)
3911 else if (direction == MV_RIGHT)
3913 else if (direction == MV_UP)
3915 else if (direction == MV_DOWN)
3918 *comes_from_x = oldx;
3919 *comes_from_y = oldy;
3922 static int MovingOrBlocked2Element_MM(int x, int y)
3924 int element = Tile[x][y];
3926 if (element == EL_BLOCKED)
3930 Blocked2Moving_MM(x, y, &oldx, &oldy);
3932 return Tile[oldx][oldy];
3938 static void RemoveMovingField_MM(int x, int y)
3940 int oldx = x, oldy = y, newx = x, newy = y;
3942 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3945 if (IS_MOVING(x, y))
3947 Moving2Blocked_MM(x, y, &newx, &newy);
3948 if (Tile[newx][newy] != EL_BLOCKED)
3951 else if (Tile[x][y] == EL_BLOCKED)
3953 Blocked2Moving_MM(x, y, &oldx, &oldy);
3954 if (!IS_MOVING(oldx, oldy))
3958 Tile[oldx][oldy] = EL_EMPTY;
3959 Tile[newx][newy] = EL_EMPTY;
3960 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3961 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3963 DrawLevelField_MM(oldx, oldy);
3964 DrawLevelField_MM(newx, newy);
3967 static void RaiseScore_MM(int value)
3969 game_mm.score += value;
3972 void RaiseScoreElement_MM(int element)
3977 case EL_PACMAN_RIGHT:
3979 case EL_PACMAN_LEFT:
3980 case EL_PACMAN_DOWN:
3981 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3985 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3990 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3994 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4003 // ----------------------------------------------------------------------------
4004 // Mirror Magic game engine snapshot handling functions
4005 // ----------------------------------------------------------------------------
4007 void SaveEngineSnapshotValues_MM(void)
4011 engine_snapshot_mm.game_mm = game_mm;
4012 engine_snapshot_mm.laser = laser;
4014 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4016 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4018 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4019 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4020 engine_snapshot_mm.Box[x][y] = Box[x][y];
4021 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4025 engine_snapshot_mm.LX = LX;
4026 engine_snapshot_mm.LY = LY;
4027 engine_snapshot_mm.XS = XS;
4028 engine_snapshot_mm.YS = YS;
4029 engine_snapshot_mm.ELX = ELX;
4030 engine_snapshot_mm.ELY = ELY;
4031 engine_snapshot_mm.CT = CT;
4032 engine_snapshot_mm.Ct = Ct;
4034 engine_snapshot_mm.last_LX = last_LX;
4035 engine_snapshot_mm.last_LY = last_LY;
4036 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4037 engine_snapshot_mm.hold_x = hold_x;
4038 engine_snapshot_mm.hold_y = hold_y;
4039 engine_snapshot_mm.pacman_nr = pacman_nr;
4041 engine_snapshot_mm.rotate_delay = rotate_delay;
4042 engine_snapshot_mm.pacman_delay = pacman_delay;
4043 engine_snapshot_mm.energy_delay = energy_delay;
4044 engine_snapshot_mm.overload_delay = overload_delay;
4047 void LoadEngineSnapshotValues_MM(void)
4051 // stored engine snapshot buffers already restored at this point
4053 game_mm = engine_snapshot_mm.game_mm;
4054 laser = engine_snapshot_mm.laser;
4056 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4058 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4060 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4061 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4062 Box[x][y] = engine_snapshot_mm.Box[x][y];
4063 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4067 LX = engine_snapshot_mm.LX;
4068 LY = engine_snapshot_mm.LY;
4069 XS = engine_snapshot_mm.XS;
4070 YS = engine_snapshot_mm.YS;
4071 ELX = engine_snapshot_mm.ELX;
4072 ELY = engine_snapshot_mm.ELY;
4073 CT = engine_snapshot_mm.CT;
4074 Ct = engine_snapshot_mm.Ct;
4076 last_LX = engine_snapshot_mm.last_LX;
4077 last_LY = engine_snapshot_mm.last_LY;
4078 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4079 hold_x = engine_snapshot_mm.hold_x;
4080 hold_y = engine_snapshot_mm.hold_y;
4081 pacman_nr = engine_snapshot_mm.pacman_nr;
4083 rotate_delay = engine_snapshot_mm.rotate_delay;
4084 pacman_delay = engine_snapshot_mm.pacman_delay;
4085 energy_delay = engine_snapshot_mm.energy_delay;
4086 overload_delay = engine_snapshot_mm.overload_delay;
4088 RedrawPlayfield_MM();
4091 static int getAngleFromTouchDelta(int dx, int dy, int base)
4093 double pi = 3.141592653;
4094 double rad = atan2((double)-dy, (double)dx);
4095 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4096 double deg = rad2 * 180.0 / pi;
4098 return (int)(deg * base / 360.0 + 0.5) % base;
4101 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4103 // calculate start (source) position to be at the middle of the tile
4104 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4105 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4106 int dx = dst_mx - src_mx;
4107 int dy = dst_my - src_my;
4116 if (!IN_LEV_FIELD(x, y))
4119 element = Tile[x][y];
4121 if (!IS_MCDUFFIN(element) &&
4122 !IS_MIRROR(element) &&
4123 !IS_BEAMER(element) &&
4124 !IS_POLAR(element) &&
4125 !IS_POLAR_CROSS(element) &&
4126 !IS_DF_MIRROR(element))
4129 angle_old = get_element_angle(element);
4131 if (IS_MCDUFFIN(element))
4133 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4134 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4135 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4136 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4139 else if (IS_MIRROR(element) ||
4140 IS_DF_MIRROR(element))
4142 for (i = 0; i < laser.num_damages; i++)
4144 if (laser.damage[i].x == x &&
4145 laser.damage[i].y == y &&
4146 ObjHit(x, y, HIT_POS_CENTER))
4148 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4149 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4156 if (angle_new == -1)
4158 if (IS_MIRROR(element) ||
4159 IS_DF_MIRROR(element) ||
4163 if (IS_POLAR_CROSS(element))
4166 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4169 button = (angle_new == angle_old ? 0 :
4170 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4171 MB_LEFTBUTTON : MB_RIGHTBUTTON);