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 // bitmap for laser beam detection
103 static Bitmap *laser_bitmap = NULL;
105 // variables for laser control
106 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
107 static int hold_x = -1, hold_y = -1;
109 // variables for pacman control
110 static int pacman_nr = -1;
112 // various game engine delay counters
113 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
114 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
115 static DelayCounter energy_delay = { ENERGY_DELAY };
116 static DelayCounter overload_delay = { 0 };
118 // element mask positions for scanning pixels of MM elements
119 #define MM_MASK_MCDUFFIN_RIGHT 0
120 #define MM_MASK_MCDUFFIN_UP 1
121 #define MM_MASK_MCDUFFIN_LEFT 2
122 #define MM_MASK_MCDUFFIN_DOWN 3
123 #define MM_MASK_GRID_1 4
124 #define MM_MASK_GRID_2 5
125 #define MM_MASK_GRID_3 6
126 #define MM_MASK_GRID_4 7
127 #define MM_MASK_RECTANGLE 8
128 #define MM_MASK_CIRCLE 9
130 #define NUM_MM_MASKS 10
132 // element masks for scanning pixels of MM elements
133 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
317 static int get_element_angle(int element)
319 int element_phase = get_element_phase(element);
321 if (IS_MIRROR_FIXED(element) ||
322 IS_MCDUFFIN(element) ||
324 IS_RECEIVER(element))
325 return 4 * element_phase;
327 return element_phase;
330 static int get_opposite_angle(int angle)
332 int opposite_angle = angle + ANG_RAY_180;
334 // make sure "opposite_angle" is in valid interval [0, 15]
335 return (opposite_angle + 16) % 16;
338 static int get_mirrored_angle(int laser_angle, int mirror_angle)
340 int reflected_angle = 16 - laser_angle + mirror_angle;
342 // make sure "reflected_angle" is in valid interval [0, 15]
343 return (reflected_angle + 16) % 16;
346 static void DrawLaserLines(struct XY *points, int num_points, int mode)
348 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
349 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
351 DrawLines(drawto, points, num_points, pixel_drawto);
355 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
360 static boolean CheckLaserPixel(int x, int y)
366 pixel = ReadPixel(laser_bitmap, x, y);
370 return (pixel == WHITE_PIXEL);
373 static void CheckExitMM(void)
375 int exit_element = EL_EMPTY;
379 static int xy[4][2] =
387 for (y = 0; y < lev_fieldy; y++)
389 for (x = 0; x < lev_fieldx; x++)
391 if (Tile[x][y] == EL_EXIT_CLOSED)
393 // initiate opening animation of exit door
394 Tile[x][y] = EL_EXIT_OPENING;
396 exit_element = EL_EXIT_OPEN;
400 else if (IS_RECEIVER(Tile[x][y]))
402 // remove field that blocks receiver
403 int phase = Tile[x][y] - EL_RECEIVER_START;
404 int blocking_x, blocking_y;
406 blocking_x = x + xy[phase][0];
407 blocking_y = y + xy[phase][1];
409 if (IN_LEV_FIELD(blocking_x, blocking_y))
411 Tile[blocking_x][blocking_y] = EL_EMPTY;
413 DrawField_MM(blocking_x, blocking_y);
416 exit_element = EL_RECEIVER;
423 if (exit_element != EL_EMPTY)
424 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
427 static void SetLaserColor(int brightness)
429 int color_min = 0x00;
430 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
431 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
432 int color_down = color_max - color_up;
435 GetPixelFromRGB(window,
436 (native_mm_level.laser_red ? color_max : color_up),
437 (native_mm_level.laser_green ? color_down : color_min),
438 (native_mm_level.laser_blue ? color_down : color_min));
441 static void InitMovDir_MM(int x, int y)
443 int element = Tile[x][y];
444 static int direction[3][4] =
446 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
447 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
448 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
453 case EL_PACMAN_RIGHT:
457 Tile[x][y] = EL_PACMAN;
458 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
466 static void InitField(int x, int y)
468 int element = Tile[x][y];
473 Tile[x][y] = EL_EMPTY;
478 if (native_mm_level.auto_count_kettles)
479 game_mm.kettles_still_needed++;
482 case EL_LIGHTBULB_OFF:
483 game_mm.lights_still_needed++;
487 if (IS_MIRROR(element) ||
488 IS_BEAMER_OLD(element) ||
489 IS_BEAMER(element) ||
491 IS_POLAR_CROSS(element) ||
492 IS_DF_MIRROR(element) ||
493 IS_DF_MIRROR_AUTO(element) ||
494 IS_GRID_STEEL_AUTO(element) ||
495 IS_GRID_WOOD_AUTO(element) ||
496 IS_FIBRE_OPTIC(element))
498 if (IS_BEAMER_OLD(element))
500 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
501 element = Tile[x][y];
504 if (!IS_FIBRE_OPTIC(element))
506 static int steps_grid_auto = 0;
508 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
509 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
511 if (IS_GRID_STEEL_AUTO(element) ||
512 IS_GRID_WOOD_AUTO(element))
513 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
515 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
517 game_mm.cycle[game_mm.num_cycle].x = x;
518 game_mm.cycle[game_mm.num_cycle].y = y;
522 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
524 int beamer_nr = BEAMER_NR(element);
525 int nr = laser.beamer[beamer_nr][0].num;
527 laser.beamer[beamer_nr][nr].x = x;
528 laser.beamer[beamer_nr][nr].y = y;
529 laser.beamer[beamer_nr][nr].num = 1;
532 else if (IS_PACMAN(element))
536 else if (IS_MCDUFFIN(element) || IS_LASER(element))
538 laser.start_edge.x = x;
539 laser.start_edge.y = y;
540 laser.start_angle = get_element_angle(element);
547 static void InitCycleElements_RotateSingleStep(void)
551 if (game_mm.num_cycle == 0) // no elements to cycle
554 for (i = 0; i < game_mm.num_cycle; i++)
556 int x = game_mm.cycle[i].x;
557 int y = game_mm.cycle[i].y;
558 int step = SIGN(game_mm.cycle[i].steps);
559 int last_element = Tile[x][y];
560 int next_element = get_rotated_element(last_element, step);
562 if (!game_mm.cycle[i].steps)
565 Tile[x][y] = next_element;
567 game_mm.cycle[i].steps -= step;
571 static void InitLaser(void)
573 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
574 int step = (IS_LASER(start_element) ? 4 : 0);
576 LX = laser.start_edge.x * TILEX;
577 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
580 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
582 LY = laser.start_edge.y * TILEY;
583 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
584 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
588 XS = 2 * Step[laser.start_angle].x;
589 YS = 2 * Step[laser.start_angle].y;
591 laser.current_angle = laser.start_angle;
593 laser.num_damages = 0;
595 laser.num_beamers = 0;
596 laser.beamer_edge[0] = 0;
598 laser.dest_element = EL_EMPTY;
601 AddLaserEdge(LX, LY); // set laser starting edge
606 void InitGameEngine_MM(void)
612 // initialize laser bitmap to current playfield (screen) size
613 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
614 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
618 // set global game control values
619 game_mm.num_cycle = 0;
620 game_mm.num_pacman = 0;
623 game_mm.energy_left = 0; // later set to "native_mm_level.time"
624 game_mm.kettles_still_needed =
625 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
626 game_mm.lights_still_needed = 0;
627 game_mm.num_keys = 0;
628 game_mm.ball_choice_pos = 0;
630 game_mm.level_solved = FALSE;
631 game_mm.game_over = FALSE;
632 game_mm.game_over_cause = 0;
634 game_mm.laser_overload_value = 0;
635 game_mm.laser_enabled = FALSE;
637 // set global laser control values (must be set before "InitLaser()")
638 laser.start_edge.x = 0;
639 laser.start_edge.y = 0;
640 laser.start_angle = 0;
642 for (i = 0; i < MAX_NUM_BEAMERS; i++)
643 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
645 laser.overloaded = FALSE;
646 laser.overload_value = 0;
647 laser.fuse_off = FALSE;
648 laser.fuse_x = laser.fuse_y = -1;
650 laser.dest_element = EL_EMPTY;
651 laser.dest_element_last = EL_EMPTY;
652 laser.dest_element_last_x = -1;
653 laser.dest_element_last_y = -1;
667 rotate_delay.count = 0;
668 pacman_delay.count = 0;
669 energy_delay.count = 0;
670 overload_delay.count = 0;
672 ClickElement(-1, -1, -1);
674 for (x = 0; x < lev_fieldx; x++)
676 for (y = 0; y < lev_fieldy; y++)
678 Tile[x][y] = Ur[x][y];
679 Hit[x][y] = Box[x][y] = 0;
681 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
682 Store[x][y] = Store2[x][y] = 0;
692 void InitGameActions_MM(void)
694 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
695 int cycle_steps_done = 0;
700 for (i = 0; i <= num_init_game_frames; i++)
702 if (i == num_init_game_frames)
703 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
704 else if (setup.sound_loops)
705 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
707 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
709 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
711 UpdateAndDisplayGameControlValues();
713 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
715 InitCycleElements_RotateSingleStep();
720 AdvanceFrameCounter();
730 if (setup.quick_doors)
737 if (game_mm.kettles_still_needed == 0)
740 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
741 SetTileCursorActive(TRUE);
743 ResetFrameCounter(&energy_delay);
746 static void FadeOutLaser(void)
750 for (i = 15; i >= 0; i--)
752 SetLaserColor(0x11 * i);
754 DrawLaser(0, DL_LASER_ENABLED);
757 Delay_WithScreenUpdates(50);
760 DrawLaser(0, DL_LASER_DISABLED);
762 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
765 static void GameOver_MM(int game_over_cause)
767 // do not handle game over if request dialog is already active
768 if (game.request_active)
771 game_mm.game_over = TRUE;
772 game_mm.game_over_cause = game_over_cause;
774 if (setup.ask_on_game_over)
775 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
776 "Bomb killed Mc Duffin! Play it again?" :
777 game_over_cause == GAME_OVER_NO_ENERGY ?
778 "Out of magic energy! Play it again?" :
779 game_over_cause == GAME_OVER_OVERLOADED ?
780 "Magic spell hit Mc Duffin! Play it again?" :
783 SetTileCursorActive(FALSE);
786 void AddLaserEdge(int lx, int ly)
791 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
793 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
798 laser.edge[laser.num_edges].x = cSX2 + lx;
799 laser.edge[laser.num_edges].y = cSY2 + ly;
805 void AddDamagedField(int ex, int ey)
807 laser.damage[laser.num_damages].is_mirror = FALSE;
808 laser.damage[laser.num_damages].angle = laser.current_angle;
809 laser.damage[laser.num_damages].edge = laser.num_edges;
810 laser.damage[laser.num_damages].x = ex;
811 laser.damage[laser.num_damages].y = ey;
815 static boolean StepBehind(void)
821 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
822 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
824 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
830 static int getMaskFromElement(int element)
832 if (IS_GRID(element))
833 return MM_MASK_GRID_1 + get_element_phase(element);
834 else if (IS_MCDUFFIN(element))
835 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
836 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
837 return MM_MASK_RECTANGLE;
839 return MM_MASK_CIRCLE;
842 static int ScanPixel(void)
847 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
848 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
851 // follow laser beam until it hits something (at least the screen border)
852 while (hit_mask == HIT_MASK_NO_HIT)
858 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
859 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
861 Debug("game:mm:ScanPixel", "touched screen border!");
867 for (i = 0; i < 4; i++)
869 int px = LX + (i % 2) * 2;
870 int py = LY + (i / 2) * 2;
873 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
874 int ly = (py + TILEY) / TILEY - 1; // negative values!
877 if (IN_LEV_FIELD(lx, ly))
879 int element = Tile[lx][ly];
881 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
885 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
887 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
889 pixel = ((element & (1 << pos)) ? 1 : 0);
893 int pos = getMaskFromElement(element);
895 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
900 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
901 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
904 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
905 hit_mask |= (1 << i);
908 if (hit_mask == HIT_MASK_NO_HIT)
910 // hit nothing -- go on with another step
919 static void DeactivateLaserTargetElement(void)
921 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
922 laser.dest_element_last == EL_MINE_ACTIVE ||
923 laser.dest_element_last == EL_GRAY_BALL_OPENING)
925 int x = laser.dest_element_last_x;
926 int y = laser.dest_element_last_y;
927 int element = laser.dest_element_last;
929 if (Tile[x][y] == element)
930 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
931 element == EL_MINE_ACTIVE ? EL_MINE : EL_BALL_GRAY);
933 if (Tile[x][y] == EL_BALL_GRAY)
936 laser.dest_element_last = EL_EMPTY;
937 laser.dest_element_last_x = -1;
938 laser.dest_element_last_y = -1;
945 int end = 0, rf = laser.num_edges;
947 // do not scan laser again after the game was lost for whatever reason
948 if (game_mm.game_over)
951 // do not scan laser if fuse is off
955 DeactivateLaserTargetElement();
957 laser.overloaded = FALSE;
958 laser.stops_inside_element = FALSE;
960 DrawLaser(0, DL_LASER_ENABLED);
963 Debug("game:mm:ScanLaser",
964 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
972 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
975 laser.overloaded = TRUE;
980 hit_mask = ScanPixel();
983 Debug("game:mm:ScanLaser",
984 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
988 // hit something -- check out what it was
989 ELX = (LX + XS) / TILEX;
990 ELY = (LY + YS) / TILEY;
993 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
994 hit_mask, LX, LY, ELX, ELY);
997 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
1000 laser.dest_element = element;
1005 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1007 /* we have hit the top-right and bottom-left element --
1008 choose the bottom-left one */
1009 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1010 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1011 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1012 ELX = (LX - 2) / TILEX;
1013 ELY = (LY + 2) / TILEY;
1016 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1018 /* we have hit the top-left and bottom-right element --
1019 choose the top-left one */
1020 // !!! SEE ABOVE !!!
1021 ELX = (LX - 2) / TILEX;
1022 ELY = (LY - 2) / TILEY;
1026 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1027 hit_mask, LX, LY, ELX, ELY);
1030 element = Tile[ELX][ELY];
1031 laser.dest_element = element;
1034 Debug("game:mm:ScanLaser",
1035 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1038 LX % TILEX, LY % TILEY,
1043 if (!IN_LEV_FIELD(ELX, ELY))
1044 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1048 if (element == EL_EMPTY)
1050 if (!HitOnlyAnEdge(hit_mask))
1053 else if (element == EL_FUSE_ON)
1055 if (HitPolarizer(element, hit_mask))
1058 else if (IS_GRID(element) || IS_DF_GRID(element))
1060 if (HitPolarizer(element, hit_mask))
1063 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1064 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1066 if (HitBlock(element, hit_mask))
1073 else if (IS_MCDUFFIN(element))
1075 if (HitLaserSource(element, hit_mask))
1078 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1079 IS_RECEIVER(element))
1081 if (HitLaserDestination(element, hit_mask))
1084 else if (IS_WALL(element))
1086 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1088 if (HitReflectingWalls(element, hit_mask))
1093 if (HitAbsorbingWalls(element, hit_mask))
1099 if (HitElement(element, hit_mask))
1104 DrawLaser(rf - 1, DL_LASER_ENABLED);
1105 rf = laser.num_edges;
1109 if (laser.dest_element != Tile[ELX][ELY])
1111 Debug("game:mm:ScanLaser",
1112 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1113 laser.dest_element, Tile[ELX][ELY]);
1117 if (!end && !laser.stops_inside_element && !StepBehind())
1120 Debug("game:mm:ScanLaser", "Go one step back");
1126 AddLaserEdge(LX, LY);
1130 DrawLaser(rf - 1, DL_LASER_ENABLED);
1132 Ct = CT = FrameCounter;
1135 if (!IN_LEV_FIELD(ELX, ELY))
1136 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1140 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1146 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1147 start_edge, num_edges, mode);
1152 Warn("DrawLaserExt: start_edge < 0");
1159 Warn("DrawLaserExt: num_edges < 0");
1165 if (mode == DL_LASER_DISABLED)
1167 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1171 // now draw the laser to the backbuffer and (if enabled) to the screen
1172 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1174 redraw_mask |= REDRAW_FIELD;
1176 if (mode == DL_LASER_ENABLED)
1179 // after the laser was deleted, the "damaged" graphics must be restored
1180 if (laser.num_damages)
1182 int damage_start = 0;
1185 // determine the starting edge, from which graphics need to be restored
1188 for (i = 0; i < laser.num_damages; i++)
1190 if (laser.damage[i].edge == start_edge + 1)
1199 // restore graphics from this starting edge to the end of damage list
1200 for (i = damage_start; i < laser.num_damages; i++)
1202 int lx = laser.damage[i].x;
1203 int ly = laser.damage[i].y;
1204 int element = Tile[lx][ly];
1206 if (Hit[lx][ly] == laser.damage[i].edge)
1207 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1210 if (Box[lx][ly] == laser.damage[i].edge)
1213 if (IS_DRAWABLE(element))
1214 DrawField_MM(lx, ly);
1217 elx = laser.damage[damage_start].x;
1218 ely = laser.damage[damage_start].y;
1219 element = Tile[elx][ely];
1222 if (IS_BEAMER(element))
1226 for (i = 0; i < laser.num_beamers; i++)
1227 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1229 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1230 mode, elx, ely, Hit[elx][ely], start_edge);
1231 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1232 get_element_angle(element), laser.damage[damage_start].angle);
1236 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1237 laser.num_beamers > 0 &&
1238 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1240 // element is outgoing beamer
1241 laser.num_damages = damage_start + 1;
1243 if (IS_BEAMER(element))
1244 laser.current_angle = get_element_angle(element);
1248 // element is incoming beamer or other element
1249 laser.num_damages = damage_start;
1250 laser.current_angle = laser.damage[laser.num_damages].angle;
1255 // no damages but McDuffin himself (who needs to be redrawn anyway)
1257 elx = laser.start_edge.x;
1258 ely = laser.start_edge.y;
1259 element = Tile[elx][ely];
1262 laser.num_edges = start_edge + 1;
1263 if (start_edge == 0)
1264 laser.current_angle = laser.start_angle;
1266 LX = laser.edge[start_edge].x - cSX2;
1267 LY = laser.edge[start_edge].y - cSY2;
1268 XS = 2 * Step[laser.current_angle].x;
1269 YS = 2 * Step[laser.current_angle].y;
1272 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1278 if (IS_BEAMER(element) ||
1279 IS_FIBRE_OPTIC(element) ||
1280 IS_PACMAN(element) ||
1281 IS_POLAR(element) ||
1282 IS_POLAR_CROSS(element) ||
1283 element == EL_FUSE_ON)
1288 Debug("game:mm:DrawLaserExt", "element == %d", element);
1291 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1292 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1296 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1297 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1298 (laser.num_beamers == 0 ||
1299 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1301 // element is incoming beamer or other element
1302 step_size = -step_size;
1307 if (IS_BEAMER(element))
1308 Debug("game:mm:DrawLaserExt",
1309 "start_edge == %d, laser.beamer_edge == %d",
1310 start_edge, laser.beamer_edge);
1313 LX += step_size * XS;
1314 LY += step_size * YS;
1316 else if (element != EL_EMPTY)
1325 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1330 void DrawLaser(int start_edge, int mode)
1332 // do not draw laser if fuse is off
1333 if (laser.fuse_off && mode == DL_LASER_ENABLED)
1336 if (mode == DL_LASER_DISABLED)
1337 DeactivateLaserTargetElement();
1339 if (laser.num_edges - start_edge < 0)
1341 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1346 // check if laser is interrupted by beamer element
1347 if (laser.num_beamers > 0 &&
1348 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1350 if (mode == DL_LASER_ENABLED)
1353 int tmp_start_edge = start_edge;
1355 // draw laser segments forward from the start to the last beamer
1356 for (i = 0; i < laser.num_beamers; i++)
1358 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1360 if (tmp_num_edges <= 0)
1364 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1365 i, laser.beamer_edge[i], tmp_start_edge);
1368 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1370 tmp_start_edge = laser.beamer_edge[i];
1373 // draw last segment from last beamer to the end
1374 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1380 int last_num_edges = laser.num_edges;
1381 int num_beamers = laser.num_beamers;
1383 // delete laser segments backward from the end to the first beamer
1384 for (i = num_beamers - 1; i >= 0; i--)
1386 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1388 if (laser.beamer_edge[i] - start_edge <= 0)
1391 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1393 last_num_edges = laser.beamer_edge[i];
1394 laser.num_beamers--;
1398 if (last_num_edges - start_edge <= 0)
1399 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1400 last_num_edges, start_edge);
1403 // special case when rotating first beamer: delete laser edge on beamer
1404 // (but do not start scanning on previous edge to prevent mirror sound)
1405 if (last_num_edges - start_edge == 1 && start_edge > 0)
1406 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1408 // delete first segment from start to the first beamer
1409 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1414 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1417 game_mm.laser_enabled = mode;
1420 void DrawLaser_MM(void)
1422 DrawLaser(0, game_mm.laser_enabled);
1425 boolean HitElement(int element, int hit_mask)
1427 if (HitOnlyAnEdge(hit_mask))
1430 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1431 element = MovingOrBlocked2Element_MM(ELX, ELY);
1434 Debug("game:mm:HitElement", "(1): element == %d", element);
1438 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1439 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1442 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1446 AddDamagedField(ELX, ELY);
1448 // this is more precise: check if laser would go through the center
1449 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1451 // skip the whole element before continuing the scan
1457 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1459 if (LX/TILEX > ELX || LY/TILEY > ELY)
1461 /* skipping scan positions to the right and down skips one scan
1462 position too much, because this is only the top left scan position
1463 of totally four scan positions (plus one to the right, one to the
1464 bottom and one to the bottom right) */
1474 Debug("game:mm:HitElement", "(2): element == %d", element);
1477 if (LX + 5 * XS < 0 ||
1487 Debug("game:mm:HitElement", "(3): element == %d", element);
1490 if (IS_POLAR(element) &&
1491 ((element - EL_POLAR_START) % 2 ||
1492 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1494 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1496 laser.num_damages--;
1501 if (IS_POLAR_CROSS(element) &&
1502 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1504 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1506 laser.num_damages--;
1511 if (!IS_BEAMER(element) &&
1512 !IS_FIBRE_OPTIC(element) &&
1513 !IS_GRID_WOOD(element) &&
1514 element != EL_FUEL_EMPTY)
1517 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1518 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1520 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1523 LX = ELX * TILEX + 14;
1524 LY = ELY * TILEY + 14;
1526 AddLaserEdge(LX, LY);
1529 if (IS_MIRROR(element) ||
1530 IS_MIRROR_FIXED(element) ||
1531 IS_POLAR(element) ||
1532 IS_POLAR_CROSS(element) ||
1533 IS_DF_MIRROR(element) ||
1534 IS_DF_MIRROR_AUTO(element) ||
1535 element == EL_PRISM ||
1536 element == EL_REFRACTOR)
1538 int current_angle = laser.current_angle;
1541 laser.num_damages--;
1543 AddDamagedField(ELX, ELY);
1545 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1548 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1550 if (IS_MIRROR(element) ||
1551 IS_MIRROR_FIXED(element) ||
1552 IS_DF_MIRROR(element) ||
1553 IS_DF_MIRROR_AUTO(element))
1554 laser.current_angle = get_mirrored_angle(laser.current_angle,
1555 get_element_angle(element));
1557 if (element == EL_PRISM || element == EL_REFRACTOR)
1558 laser.current_angle = RND(16);
1560 XS = 2 * Step[laser.current_angle].x;
1561 YS = 2 * Step[laser.current_angle].y;
1563 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1568 LX += step_size * XS;
1569 LY += step_size * YS;
1571 // draw sparkles on mirror
1572 if ((IS_MIRROR(element) ||
1573 IS_MIRROR_FIXED(element) ||
1574 element == EL_PRISM) &&
1575 current_angle != laser.current_angle)
1577 MovDelay[ELX][ELY] = 11; // start animation
1580 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1581 current_angle != laser.current_angle)
1582 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1585 (get_opposite_angle(laser.current_angle) ==
1586 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1588 return (laser.overloaded ? TRUE : FALSE);
1591 if (element == EL_FUEL_FULL)
1593 laser.stops_inside_element = TRUE;
1598 if (element == EL_BOMB || element == EL_MINE)
1600 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1602 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1604 laser.dest_element_last = Tile[ELX][ELY];
1605 laser.dest_element_last_x = ELX;
1606 laser.dest_element_last_y = ELY;
1608 if (element == EL_MINE)
1609 laser.overloaded = TRUE;
1612 if (element == EL_KETTLE ||
1613 element == EL_CELL ||
1614 element == EL_KEY ||
1615 element == EL_LIGHTBALL ||
1616 element == EL_PACMAN ||
1619 if (!IS_PACMAN(element))
1622 if (element == EL_PACMAN)
1625 if (element == EL_KETTLE || element == EL_CELL)
1627 if (game_mm.kettles_still_needed > 0)
1628 game_mm.kettles_still_needed--;
1630 game.snapshot.collected_item = TRUE;
1632 if (game_mm.kettles_still_needed == 0)
1636 DrawLaser(0, DL_LASER_ENABLED);
1639 else if (element == EL_KEY)
1643 else if (IS_PACMAN(element))
1645 DeletePacMan(ELX, ELY);
1648 RaiseScoreElement_MM(element);
1653 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1655 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1657 DrawLaser(0, DL_LASER_ENABLED);
1659 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1661 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1662 game_mm.lights_still_needed--;
1666 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1667 game_mm.lights_still_needed++;
1670 DrawField_MM(ELX, ELY);
1671 DrawLaser(0, DL_LASER_ENABLED);
1676 laser.stops_inside_element = TRUE;
1682 Debug("game:mm:HitElement", "(4): element == %d", element);
1685 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1686 laser.num_beamers < MAX_NUM_BEAMERS &&
1687 laser.beamer[BEAMER_NR(element)][1].num)
1689 int beamer_angle = get_element_angle(element);
1690 int beamer_nr = BEAMER_NR(element);
1694 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1697 laser.num_damages--;
1699 if (IS_FIBRE_OPTIC(element) ||
1700 laser.current_angle == get_opposite_angle(beamer_angle))
1704 LX = ELX * TILEX + 14;
1705 LY = ELY * TILEY + 14;
1707 AddLaserEdge(LX, LY);
1708 AddDamagedField(ELX, ELY);
1710 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1713 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1715 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1716 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1717 ELX = laser.beamer[beamer_nr][pos].x;
1718 ELY = laser.beamer[beamer_nr][pos].y;
1719 LX = ELX * TILEX + 14;
1720 LY = ELY * TILEY + 14;
1722 if (IS_BEAMER(element))
1724 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1725 XS = 2 * Step[laser.current_angle].x;
1726 YS = 2 * Step[laser.current_angle].y;
1729 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1731 AddLaserEdge(LX, LY);
1732 AddDamagedField(ELX, ELY);
1734 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1737 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1739 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1744 LX += step_size * XS;
1745 LY += step_size * YS;
1747 laser.num_beamers++;
1756 boolean HitOnlyAnEdge(int hit_mask)
1758 // check if the laser hit only the edge of an element and, if so, go on
1761 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1765 if ((hit_mask == HIT_MASK_TOPLEFT ||
1766 hit_mask == HIT_MASK_TOPRIGHT ||
1767 hit_mask == HIT_MASK_BOTTOMLEFT ||
1768 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1769 laser.current_angle % 4) // angle is not 90°
1773 if (hit_mask == HIT_MASK_TOPLEFT)
1778 else if (hit_mask == HIT_MASK_TOPRIGHT)
1783 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1788 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1794 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1800 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1807 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1813 boolean HitPolarizer(int element, int hit_mask)
1815 if (HitOnlyAnEdge(hit_mask))
1818 if (IS_DF_GRID(element))
1820 int grid_angle = get_element_angle(element);
1823 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1824 grid_angle, laser.current_angle);
1827 AddLaserEdge(LX, LY);
1828 AddDamagedField(ELX, ELY);
1831 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1833 if (laser.current_angle == grid_angle ||
1834 laser.current_angle == get_opposite_angle(grid_angle))
1836 // skip the whole element before continuing the scan
1842 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1844 if (LX/TILEX > ELX || LY/TILEY > ELY)
1846 /* skipping scan positions to the right and down skips one scan
1847 position too much, because this is only the top left scan position
1848 of totally four scan positions (plus one to the right, one to the
1849 bottom and one to the bottom right) */
1855 AddLaserEdge(LX, LY);
1861 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1863 LX / TILEX, LY / TILEY,
1864 LX % TILEX, LY % TILEY);
1869 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1871 return HitReflectingWalls(element, hit_mask);
1875 return HitAbsorbingWalls(element, hit_mask);
1878 else if (IS_GRID_STEEL(element))
1880 return HitReflectingWalls(element, hit_mask);
1882 else // IS_GRID_WOOD
1884 return HitAbsorbingWalls(element, hit_mask);
1890 boolean HitBlock(int element, int hit_mask)
1892 boolean check = FALSE;
1894 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1895 game_mm.num_keys == 0)
1898 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1901 int ex = ELX * TILEX + 14;
1902 int ey = ELY * TILEY + 14;
1906 for (i = 1; i < 32; i++)
1911 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1916 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1917 return HitAbsorbingWalls(element, hit_mask);
1921 AddLaserEdge(LX - XS, LY - YS);
1922 AddDamagedField(ELX, ELY);
1925 Box[ELX][ELY] = laser.num_edges;
1927 return HitReflectingWalls(element, hit_mask);
1930 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1932 int xs = XS / 2, ys = YS / 2;
1933 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1934 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1936 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1937 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1939 laser.overloaded = (element == EL_GATE_STONE);
1944 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1945 (hit_mask == HIT_MASK_TOP ||
1946 hit_mask == HIT_MASK_LEFT ||
1947 hit_mask == HIT_MASK_RIGHT ||
1948 hit_mask == HIT_MASK_BOTTOM))
1949 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1950 hit_mask == HIT_MASK_BOTTOM),
1951 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1952 hit_mask == HIT_MASK_RIGHT));
1953 AddLaserEdge(LX, LY);
1959 if (element == EL_GATE_STONE && Box[ELX][ELY])
1961 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1973 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1975 int xs = XS / 2, ys = YS / 2;
1976 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1977 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1979 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1980 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1982 laser.overloaded = (element == EL_BLOCK_STONE);
1987 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1988 (hit_mask == HIT_MASK_TOP ||
1989 hit_mask == HIT_MASK_LEFT ||
1990 hit_mask == HIT_MASK_RIGHT ||
1991 hit_mask == HIT_MASK_BOTTOM))
1992 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1993 hit_mask == HIT_MASK_BOTTOM),
1994 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1995 hit_mask == HIT_MASK_RIGHT));
1996 AddDamagedField(ELX, ELY);
1998 LX = ELX * TILEX + 14;
1999 LY = ELY * TILEY + 14;
2001 AddLaserEdge(LX, LY);
2003 laser.stops_inside_element = TRUE;
2011 boolean HitLaserSource(int element, int hit_mask)
2013 if (HitOnlyAnEdge(hit_mask))
2016 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2018 laser.overloaded = TRUE;
2023 boolean HitLaserDestination(int element, int hit_mask)
2025 if (HitOnlyAnEdge(hit_mask))
2028 if (element != EL_EXIT_OPEN &&
2029 !(IS_RECEIVER(element) &&
2030 game_mm.kettles_still_needed == 0 &&
2031 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2033 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2038 if (IS_RECEIVER(element) ||
2039 (IS_22_5_ANGLE(laser.current_angle) &&
2040 (ELX != (LX + 6 * XS) / TILEX ||
2041 ELY != (LY + 6 * YS) / TILEY ||
2050 LX = ELX * TILEX + 14;
2051 LY = ELY * TILEY + 14;
2053 laser.stops_inside_element = TRUE;
2056 AddLaserEdge(LX, LY);
2057 AddDamagedField(ELX, ELY);
2059 if (game_mm.lights_still_needed == 0)
2061 game_mm.level_solved = TRUE;
2063 SetTileCursorActive(FALSE);
2069 boolean HitReflectingWalls(int element, int hit_mask)
2071 // check if laser hits side of a wall with an angle that is not 90°
2072 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2073 hit_mask == HIT_MASK_LEFT ||
2074 hit_mask == HIT_MASK_RIGHT ||
2075 hit_mask == HIT_MASK_BOTTOM))
2077 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2082 if (!IS_DF_GRID(element))
2083 AddLaserEdge(LX, LY);
2085 // check if laser hits wall with an angle of 45°
2086 if (!IS_22_5_ANGLE(laser.current_angle))
2088 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2091 laser.current_angle = get_mirrored_angle(laser.current_angle,
2094 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2097 laser.current_angle = get_mirrored_angle(laser.current_angle,
2101 AddLaserEdge(LX, LY);
2103 XS = 2 * Step[laser.current_angle].x;
2104 YS = 2 * Step[laser.current_angle].y;
2108 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2110 laser.current_angle = get_mirrored_angle(laser.current_angle,
2115 if (!IS_DF_GRID(element))
2116 AddLaserEdge(LX, LY);
2121 if (!IS_DF_GRID(element))
2122 AddLaserEdge(LX, LY + YS / 2);
2125 if (!IS_DF_GRID(element))
2126 AddLaserEdge(LX, LY);
2129 YS = 2 * Step[laser.current_angle].y;
2133 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2135 laser.current_angle = get_mirrored_angle(laser.current_angle,
2140 if (!IS_DF_GRID(element))
2141 AddLaserEdge(LX, LY);
2146 if (!IS_DF_GRID(element))
2147 AddLaserEdge(LX + XS / 2, LY);
2150 if (!IS_DF_GRID(element))
2151 AddLaserEdge(LX, LY);
2154 XS = 2 * Step[laser.current_angle].x;
2160 // reflection at the edge of reflecting DF style wall
2161 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2163 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2164 hit_mask == HIT_MASK_TOPRIGHT) ||
2165 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2166 hit_mask == HIT_MASK_TOPLEFT) ||
2167 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2168 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2169 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2170 hit_mask == HIT_MASK_BOTTOMRIGHT))
2173 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2174 ANG_MIRROR_135 : ANG_MIRROR_45);
2176 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2178 AddDamagedField(ELX, ELY);
2179 AddLaserEdge(LX, LY);
2181 laser.current_angle = get_mirrored_angle(laser.current_angle,
2189 AddLaserEdge(LX, LY);
2195 // reflection inside an edge of reflecting DF style wall
2196 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2198 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2199 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2200 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2201 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2202 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2203 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2204 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2205 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2208 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2209 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2210 ANG_MIRROR_135 : ANG_MIRROR_45);
2212 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2215 AddDamagedField(ELX, ELY);
2218 AddLaserEdge(LX - XS, LY - YS);
2219 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2220 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2222 laser.current_angle = get_mirrored_angle(laser.current_angle,
2230 AddLaserEdge(LX, LY);
2236 // check if laser hits DF style wall with an angle of 90°
2237 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2239 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2240 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2241 (IS_VERT_ANGLE(laser.current_angle) &&
2242 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2244 // laser at last step touched nothing or the same side of the wall
2245 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2247 AddDamagedField(ELX, ELY);
2254 last_hit_mask = hit_mask;
2261 if (!HitOnlyAnEdge(hit_mask))
2263 laser.overloaded = TRUE;
2271 boolean HitAbsorbingWalls(int element, int hit_mask)
2273 if (HitOnlyAnEdge(hit_mask))
2277 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2279 AddLaserEdge(LX - XS, LY - YS);
2286 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2288 AddLaserEdge(LX - XS, LY - YS);
2294 if (IS_WALL_WOOD(element) ||
2295 IS_DF_WALL_WOOD(element) ||
2296 IS_GRID_WOOD(element) ||
2297 IS_GRID_WOOD_FIXED(element) ||
2298 IS_GRID_WOOD_AUTO(element) ||
2299 element == EL_FUSE_ON ||
2300 element == EL_BLOCK_WOOD ||
2301 element == EL_GATE_WOOD)
2303 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2308 if (IS_WALL_ICE(element))
2312 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2313 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2315 // check if laser hits wall with an angle of 90°
2316 if (IS_90_ANGLE(laser.current_angle))
2317 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2319 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2323 for (i = 0; i < 4; i++)
2325 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2326 mask = 15 - (8 >> i);
2327 else if (ABS(XS) == 4 &&
2329 (XS > 0) == (i % 2) &&
2330 (YS < 0) == (i / 2))
2331 mask = 3 + (i / 2) * 9;
2332 else if (ABS(YS) == 4 &&
2334 (XS < 0) == (i % 2) &&
2335 (YS > 0) == (i / 2))
2336 mask = 5 + (i % 2) * 5;
2340 laser.wall_mask = mask;
2342 else if (IS_WALL_AMOEBA(element))
2344 int elx = (LX - 2 * XS) / TILEX;
2345 int ely = (LY - 2 * YS) / TILEY;
2346 int element2 = Tile[elx][ely];
2349 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2351 laser.dest_element = EL_EMPTY;
2359 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2360 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2362 if (IS_90_ANGLE(laser.current_angle))
2363 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2365 laser.dest_element = element2 | EL_WALL_AMOEBA;
2367 laser.wall_mask = mask;
2373 static void OpenExit(int x, int y)
2377 if (!MovDelay[x][y]) // next animation frame
2378 MovDelay[x][y] = 4 * delay;
2380 if (MovDelay[x][y]) // wait some time before next frame
2385 phase = MovDelay[x][y] / delay;
2387 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2388 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2390 if (!MovDelay[x][y])
2392 Tile[x][y] = EL_EXIT_OPEN;
2398 static void OpenSurpriseBall(int x, int y)
2402 if (!MovDelay[x][y]) // next animation frame
2403 MovDelay[x][y] = 50 * delay;
2405 if (MovDelay[x][y]) // wait some time before next frame
2409 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2412 int graphic = el2gfx(Store[x][y]);
2414 int dx = RND(26), dy = RND(26);
2416 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2418 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2419 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2421 MarkTileDirty(x, y);
2424 if (!MovDelay[x][y])
2426 Tile[x][y] = Store[x][y];
2427 Store[x][y] = Store2[x][y] = 0;
2436 static void MeltIce(int x, int y)
2441 if (!MovDelay[x][y]) // next animation frame
2442 MovDelay[x][y] = frames * delay;
2444 if (MovDelay[x][y]) // wait some time before next frame
2447 int wall_mask = Store2[x][y];
2448 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2451 phase = frames - MovDelay[x][y] / delay - 1;
2453 if (!MovDelay[x][y])
2457 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2458 Store[x][y] = Store2[x][y] = 0;
2460 DrawWalls_MM(x, y, Tile[x][y]);
2462 if (Tile[x][y] == EL_WALL_ICE)
2463 Tile[x][y] = EL_EMPTY;
2465 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2466 if (laser.damage[i].is_mirror)
2470 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2472 DrawLaser(0, DL_LASER_DISABLED);
2476 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2478 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2480 laser.redraw = TRUE;
2485 static void GrowAmoeba(int x, int y)
2490 if (!MovDelay[x][y]) // next animation frame
2491 MovDelay[x][y] = frames * delay;
2493 if (MovDelay[x][y]) // wait some time before next frame
2496 int wall_mask = Store2[x][y];
2497 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2500 phase = MovDelay[x][y] / delay;
2502 if (!MovDelay[x][y])
2504 Tile[x][y] = real_element;
2505 Store[x][y] = Store2[x][y] = 0;
2507 DrawWalls_MM(x, y, Tile[x][y]);
2508 DrawLaser(0, DL_LASER_ENABLED);
2510 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2512 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2517 static void DrawFieldAnimated_MM(int x, int y)
2519 int element = Tile[x][y];
2521 if (IS_BLOCKED(x, y))
2526 if (IS_MIRROR(element) ||
2527 IS_MIRROR_FIXED(element) ||
2528 element == EL_PRISM)
2530 if (MovDelay[x][y] != 0) // wait some time before next frame
2534 if (MovDelay[x][y] != 0)
2536 int graphic = IMG_TWINKLE_WHITE;
2537 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2539 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2544 laser.redraw = TRUE;
2547 static void Explode_MM(int x, int y, int phase, int mode)
2549 int num_phase = 9, delay = 2;
2550 int last_phase = num_phase * delay;
2551 int half_phase = (num_phase / 2) * delay;
2553 laser.redraw = TRUE;
2555 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2557 int center_element = Tile[x][y];
2559 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2561 // put moving element to center field (and let it explode there)
2562 center_element = MovingOrBlocked2Element_MM(x, y);
2563 RemoveMovingField_MM(x, y);
2565 Tile[x][y] = center_element;
2568 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2569 Store[x][y] = center_element;
2571 Store[x][y] = EL_EMPTY;
2573 Store2[x][y] = mode;
2575 Tile[x][y] = EL_EXPLODING_OPAQUE;
2576 GfxElement[x][y] = center_element;
2578 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2580 ExplodePhase[x][y] = 1;
2586 GfxFrame[x][y] = 0; // restart explosion animation
2588 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2590 if (phase == half_phase)
2592 Tile[x][y] = EL_EXPLODING_TRANSP;
2594 if (x == ELX && y == ELY)
2598 if (phase == last_phase)
2600 if (Store[x][y] == EL_BOMB_ACTIVE)
2602 DrawLaser(0, DL_LASER_DISABLED);
2605 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2606 Store[x][y] = EL_EMPTY;
2608 GameOver_MM(GAME_OVER_DELAYED);
2610 laser.overloaded = FALSE;
2612 else if (IS_MCDUFFIN(Store[x][y]))
2614 Store[x][y] = EL_EMPTY;
2616 GameOver_MM(GAME_OVER_BOMB);
2619 Tile[x][y] = Store[x][y];
2620 Store[x][y] = Store2[x][y] = 0;
2621 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2626 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2628 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2629 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2631 DrawGraphicAnimation_MM(x, y, graphic, frame);
2633 MarkTileDirty(x, y);
2637 static void Bang_MM(int x, int y)
2639 int element = Tile[x][y];
2642 DrawLaser(0, DL_LASER_ENABLED);
2645 if (IS_PACMAN(element))
2646 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2647 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2648 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2649 else if (element == EL_KEY)
2650 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2652 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2654 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2657 void TurnRound(int x, int y)
2669 { 0, 0 }, { 0, 0 }, { 0, 0 },
2674 int left, right, back;
2678 { MV_DOWN, MV_UP, MV_RIGHT },
2679 { MV_UP, MV_DOWN, MV_LEFT },
2681 { MV_LEFT, MV_RIGHT, MV_DOWN },
2682 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2683 { MV_RIGHT, MV_LEFT, MV_UP }
2686 int element = Tile[x][y];
2687 int old_move_dir = MovDir[x][y];
2688 int right_dir = turn[old_move_dir].right;
2689 int back_dir = turn[old_move_dir].back;
2690 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2691 int right_x = x + right_dx, right_y = y + right_dy;
2693 if (element == EL_PACMAN)
2695 boolean can_turn_right = FALSE;
2697 if (IN_LEV_FIELD(right_x, right_y) &&
2698 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2699 can_turn_right = TRUE;
2702 MovDir[x][y] = right_dir;
2704 MovDir[x][y] = back_dir;
2710 static void StartMoving_MM(int x, int y)
2712 int element = Tile[x][y];
2717 if (CAN_MOVE(element))
2721 if (MovDelay[x][y]) // wait some time before next movement
2729 // now make next step
2731 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2733 if (element == EL_PACMAN &&
2734 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2735 !ObjHit(newx, newy, HIT_POS_CENTER))
2737 Store[newx][newy] = Tile[newx][newy];
2738 Tile[newx][newy] = EL_EMPTY;
2740 DrawField_MM(newx, newy);
2742 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2743 ObjHit(newx, newy, HIT_POS_CENTER))
2745 // object was running against a wall
2752 InitMovingField_MM(x, y, MovDir[x][y]);
2756 ContinueMoving_MM(x, y);
2759 static void ContinueMoving_MM(int x, int y)
2761 int element = Tile[x][y];
2762 int direction = MovDir[x][y];
2763 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2764 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2765 int horiz_move = (dx!=0);
2766 int newx = x + dx, newy = y + dy;
2767 int step = (horiz_move ? dx : dy) * TILEX / 8;
2769 MovPos[x][y] += step;
2771 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2773 Tile[x][y] = EL_EMPTY;
2774 Tile[newx][newy] = element;
2776 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2777 MovDelay[newx][newy] = 0;
2779 if (!CAN_MOVE(element))
2780 MovDir[newx][newy] = 0;
2783 DrawField_MM(newx, newy);
2785 Stop[newx][newy] = TRUE;
2787 if (element == EL_PACMAN)
2789 if (Store[newx][newy] == EL_BOMB)
2790 Bang_MM(newx, newy);
2792 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2793 (LX + 2 * XS) / TILEX == newx &&
2794 (LY + 2 * YS) / TILEY == newy)
2801 else // still moving on
2806 laser.redraw = TRUE;
2809 boolean ClickElement(int x, int y, int button)
2811 static DelayCounter click_delay = { CLICK_DELAY };
2812 static boolean new_button = TRUE;
2813 boolean element_clicked = FALSE;
2818 // initialize static variables
2819 click_delay.count = 0;
2820 click_delay.value = CLICK_DELAY;
2826 // do not rotate objects hit by the laser after the game was solved
2827 if (game_mm.level_solved && Hit[x][y])
2830 if (button == MB_RELEASED)
2833 click_delay.value = CLICK_DELAY;
2835 // release eventually hold auto-rotating mirror
2836 RotateMirror(x, y, MB_RELEASED);
2841 if (!FrameReached(&click_delay) && !new_button)
2844 if (button == MB_MIDDLEBUTTON) // middle button has no function
2847 if (!IN_LEV_FIELD(x, y))
2850 if (Tile[x][y] == EL_EMPTY)
2853 element = Tile[x][y];
2855 if (IS_MIRROR(element) ||
2856 IS_BEAMER(element) ||
2857 IS_POLAR(element) ||
2858 IS_POLAR_CROSS(element) ||
2859 IS_DF_MIRROR(element) ||
2860 IS_DF_MIRROR_AUTO(element))
2862 RotateMirror(x, y, button);
2864 element_clicked = TRUE;
2866 else if (IS_MCDUFFIN(element))
2868 if (!laser.fuse_off)
2870 DrawLaser(0, DL_LASER_DISABLED);
2877 element = get_rotated_element(element, BUTTON_ROTATION(button));
2878 laser.start_angle = get_element_angle(element);
2882 Tile[x][y] = element;
2889 if (!laser.fuse_off)
2892 element_clicked = TRUE;
2894 else if (element == EL_FUSE_ON && laser.fuse_off)
2896 if (x != laser.fuse_x || y != laser.fuse_y)
2899 laser.fuse_off = FALSE;
2900 laser.fuse_x = laser.fuse_y = -1;
2902 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2905 element_clicked = TRUE;
2907 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2909 laser.fuse_off = TRUE;
2912 laser.overloaded = FALSE;
2914 DrawLaser(0, DL_LASER_DISABLED);
2915 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2917 element_clicked = TRUE;
2919 else if (element == EL_LIGHTBALL)
2922 RaiseScoreElement_MM(element);
2923 DrawLaser(0, DL_LASER_ENABLED);
2925 element_clicked = TRUE;
2928 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2931 return element_clicked;
2934 void RotateMirror(int x, int y, int button)
2936 if (button == MB_RELEASED)
2938 // release eventually hold auto-rotating mirror
2945 if (IS_MIRROR(Tile[x][y]) ||
2946 IS_POLAR_CROSS(Tile[x][y]) ||
2947 IS_POLAR(Tile[x][y]) ||
2948 IS_BEAMER(Tile[x][y]) ||
2949 IS_DF_MIRROR(Tile[x][y]) ||
2950 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2951 IS_GRID_WOOD_AUTO(Tile[x][y]))
2953 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2955 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2957 if (button == MB_LEFTBUTTON)
2959 // left mouse button only for manual adjustment, no auto-rotating;
2960 // freeze mirror for until mouse button released
2964 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2966 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2970 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2972 int edge = Hit[x][y];
2978 DrawLaser(edge - 1, DL_LASER_DISABLED);
2982 else if (ObjHit(x, y, HIT_POS_CENTER))
2984 int edge = Hit[x][y];
2988 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2993 DrawLaser(edge - 1, DL_LASER_DISABLED);
3000 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3005 if ((IS_BEAMER(Tile[x][y]) ||
3006 IS_POLAR(Tile[x][y]) ||
3007 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3011 if (IS_BEAMER(Tile[x][y]))
3014 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3015 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3027 DrawLaser(0, DL_LASER_ENABLED);
3031 static void AutoRotateMirrors(void)
3035 if (!FrameReached(&rotate_delay))
3038 for (x = 0; x < lev_fieldx; x++)
3040 for (y = 0; y < lev_fieldy; y++)
3042 int element = Tile[x][y];
3044 // do not rotate objects hit by the laser after the game was solved
3045 if (game_mm.level_solved && Hit[x][y])
3048 if (IS_DF_MIRROR_AUTO(element) ||
3049 IS_GRID_WOOD_AUTO(element) ||
3050 IS_GRID_STEEL_AUTO(element) ||
3051 element == EL_REFRACTOR)
3052 RotateMirror(x, y, MB_RIGHTBUTTON);
3057 boolean ObjHit(int obx, int oby, int bits)
3064 if (bits & HIT_POS_CENTER)
3066 if (CheckLaserPixel(cSX + obx + 15,
3071 if (bits & HIT_POS_EDGE)
3073 for (i = 0; i < 4; i++)
3074 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3075 cSY + oby + 31 * (i / 2)))
3079 if (bits & HIT_POS_BETWEEN)
3081 for (i = 0; i < 4; i++)
3082 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3083 cSY + 4 + oby + 22 * (i / 2)))
3090 void DeletePacMan(int px, int py)
3096 if (game_mm.num_pacman <= 1)
3098 game_mm.num_pacman = 0;
3102 for (i = 0; i < game_mm.num_pacman; i++)
3103 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3106 game_mm.num_pacman--;
3108 for (j = i; j < game_mm.num_pacman; j++)
3110 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3111 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3112 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3116 void ColorCycling(void)
3118 static int CC, Cc = 0;
3120 static int color, old = 0xF00, new = 0x010, mult = 1;
3121 static unsigned short red, green, blue;
3123 if (color_status == STATIC_COLORS)
3128 if (CC < Cc || CC > Cc + 2)
3132 color = old + new * mult;
3138 if (ABS(mult) == 16)
3148 red = 0x0e00 * ((color & 0xF00) >> 8);
3149 green = 0x0e00 * ((color & 0x0F0) >> 4);
3150 blue = 0x0e00 * ((color & 0x00F));
3151 SetRGB(pen_magicolor[0], red, green, blue);
3153 red = 0x1111 * ((color & 0xF00) >> 8);
3154 green = 0x1111 * ((color & 0x0F0) >> 4);
3155 blue = 0x1111 * ((color & 0x00F));
3156 SetRGB(pen_magicolor[1], red, green, blue);
3160 static void GameActions_MM_Ext(void)
3167 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3170 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3172 element = Tile[x][y];
3174 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3175 StartMoving_MM(x, y);
3176 else if (IS_MOVING(x, y))
3177 ContinueMoving_MM(x, y);
3178 else if (IS_EXPLODING(element))
3179 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3180 else if (element == EL_EXIT_OPENING)
3182 else if (element == EL_GRAY_BALL_OPENING)
3183 OpenSurpriseBall(x, y);
3184 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3186 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3189 DrawFieldAnimated_MM(x, y);
3192 AutoRotateMirrors();
3195 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3197 // redraw after Explode_MM() ...
3199 DrawLaser(0, DL_LASER_ENABLED);
3200 laser.redraw = FALSE;
3205 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3209 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3211 DrawLaser(0, DL_LASER_DISABLED);
3216 // skip all following game actions if game is over
3217 if (game_mm.game_over)
3220 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3224 GameOver_MM(GAME_OVER_NO_ENERGY);
3229 if (FrameReached(&energy_delay))
3231 if (game_mm.energy_left > 0)
3232 game_mm.energy_left--;
3234 // when out of energy, wait another frame to play "out of time" sound
3237 element = laser.dest_element;
3240 if (element != Tile[ELX][ELY])
3242 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3243 element, Tile[ELX][ELY]);
3247 if (!laser.overloaded && laser.overload_value == 0 &&
3248 element != EL_BOMB &&
3249 element != EL_BOMB_ACTIVE &&
3250 element != EL_MINE &&
3251 element != EL_MINE_ACTIVE &&
3252 element != EL_BALL_GRAY &&
3253 element != EL_BLOCK_STONE &&
3254 element != EL_BLOCK_WOOD &&
3255 element != EL_FUSE_ON &&
3256 element != EL_FUEL_FULL &&
3257 !IS_WALL_ICE(element) &&
3258 !IS_WALL_AMOEBA(element))
3261 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3263 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3264 (!laser.overloaded && laser.overload_value > 0)) &&
3265 FrameReached(&overload_delay))
3267 if (laser.overloaded)
3268 laser.overload_value++;
3270 laser.overload_value--;
3272 if (game_mm.cheat_no_overload)
3274 laser.overloaded = FALSE;
3275 laser.overload_value = 0;
3278 game_mm.laser_overload_value = laser.overload_value;
3280 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3282 SetLaserColor(0xFF);
3284 DrawLaser(0, DL_LASER_ENABLED);
3287 if (!laser.overloaded)
3288 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3289 else if (setup.sound_loops)
3290 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3292 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3294 if (laser.overloaded)
3297 BlitBitmap(pix[PIX_DOOR], drawto,
3298 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3299 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3300 - laser.overload_value,
3301 OVERLOAD_XSIZE, laser.overload_value,
3302 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3303 - laser.overload_value);
3305 redraw_mask |= REDRAW_DOOR_1;
3310 BlitBitmap(pix[PIX_DOOR], drawto,
3311 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3312 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3313 DX_OVERLOAD, DY_OVERLOAD);
3315 redraw_mask |= REDRAW_DOOR_1;
3318 if (laser.overload_value == MAX_LASER_OVERLOAD)
3320 UpdateAndDisplayGameControlValues();
3324 GameOver_MM(GAME_OVER_OVERLOADED);
3335 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3337 if (game_mm.cheat_no_explosion)
3342 laser.dest_element = EL_EXPLODING_OPAQUE;
3347 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3349 laser.fuse_off = TRUE;
3353 DrawLaser(0, DL_LASER_DISABLED);
3354 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3357 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3359 if (!Store2[ELX][ELY]) // check if content element not yet determined
3361 int last_anim_random_frame = gfx.anim_random_frame;
3364 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3365 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3367 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3368 native_mm_level.ball_choice_mode, 0,
3369 game_mm.ball_choice_pos);
3371 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3372 gfx.anim_random_frame = last_anim_random_frame;
3374 game_mm.ball_choice_pos++;
3376 int new_element = native_mm_level.ball_content[element_pos];
3378 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3379 Store2[ELX][ELY] = TRUE;
3382 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3384 // !!! CHECK AGAIN: Laser on Polarizer !!!
3387 laser.dest_element_last = Tile[ELX][ELY];
3388 laser.dest_element_last_x = ELX;
3389 laser.dest_element_last_y = ELY;
3399 element = EL_MIRROR_START + RND(16);
3405 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3412 element = (rnd == 0 ? EL_FUSE_ON :
3413 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3414 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3415 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3416 EL_MIRROR_FIXED_START + rnd - 25);
3421 graphic = el2gfx(element);
3423 for (i = 0; i < 50; i++)
3429 BlitBitmap(pix[PIX_BACK], drawto,
3430 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3431 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3432 SX + ELX * TILEX + x,
3433 SY + ELY * TILEY + y);
3435 MarkTileDirty(ELX, ELY);
3438 DrawLaser(0, DL_LASER_ENABLED);
3440 Delay_WithScreenUpdates(50);
3443 Tile[ELX][ELY] = element;
3444 DrawField_MM(ELX, ELY);
3447 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3450 // above stuff: GRAY BALL -> PRISM !!!
3452 LX = ELX * TILEX + 14;
3453 LY = ELY * TILEY + 14;
3454 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3461 laser.num_edges -= 2;
3462 laser.num_damages--;
3466 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3467 if (laser.damage[i].is_mirror)
3471 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3473 DrawLaser(0, DL_LASER_DISABLED);
3475 DrawLaser(0, DL_LASER_DISABLED);
3484 if (IS_WALL_ICE(element) && CT > 50)
3486 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3489 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3490 Store[ELX][ELY] = EL_WALL_ICE;
3491 Store2[ELX][ELY] = laser.wall_mask;
3493 laser.dest_element = Tile[ELX][ELY];
3498 for (i = 0; i < 5; i++)
3504 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3508 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3510 Delay_WithScreenUpdates(100);
3513 if (Tile[ELX][ELY] == EL_WALL_ICE)
3514 Tile[ELX][ELY] = EL_EMPTY;
3518 LX = laser.edge[laser.num_edges].x - cSX2;
3519 LY = laser.edge[laser.num_edges].y - cSY2;
3522 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3523 if (laser.damage[i].is_mirror)
3527 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3529 DrawLaser(0, DL_LASER_DISABLED);
3536 if (IS_WALL_AMOEBA(element) && CT > 60)
3538 int k1, k2, k3, dx, dy, de, dm;
3539 int element2 = Tile[ELX][ELY];
3541 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3544 for (i = laser.num_damages - 1; i >= 0; i--)
3545 if (laser.damage[i].is_mirror)
3548 r = laser.num_edges;
3549 d = laser.num_damages;
3556 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3559 DrawLaser(0, DL_LASER_ENABLED);
3562 x = laser.damage[k1].x;
3563 y = laser.damage[k1].y;
3568 for (i = 0; i < 4; i++)
3570 if (laser.wall_mask & (1 << i))
3572 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3573 cSY + ELY * TILEY + 31 * (i / 2)))
3576 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3577 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3584 for (i = 0; i < 4; i++)
3586 if (laser.wall_mask & (1 << i))
3588 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3589 cSY + ELY * TILEY + 31 * (i / 2)))
3596 if (laser.num_beamers > 0 ||
3597 k1 < 1 || k2 < 4 || k3 < 4 ||
3598 CheckLaserPixel(cSX + ELX * TILEX + 14,
3599 cSY + ELY * TILEY + 14))
3601 laser.num_edges = r;
3602 laser.num_damages = d;
3604 DrawLaser(0, DL_LASER_DISABLED);
3607 Tile[ELX][ELY] = element | laser.wall_mask;
3611 de = Tile[ELX][ELY];
3612 dm = laser.wall_mask;
3616 int x = ELX, y = ELY;
3617 int wall_mask = laser.wall_mask;
3620 DrawLaser(0, DL_LASER_ENABLED);
3622 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3624 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3625 Store[x][y] = EL_WALL_AMOEBA;
3626 Store2[x][y] = wall_mask;
3632 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3634 DrawLaser(0, DL_LASER_ENABLED);
3636 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3638 for (i = 4; i >= 0; i--)
3640 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3643 Delay_WithScreenUpdates(20);
3646 DrawLaser(0, DL_LASER_ENABLED);
3651 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3652 laser.stops_inside_element && CT > native_mm_level.time_block)
3657 if (ABS(XS) > ABS(YS))
3664 for (i = 0; i < 4; i++)
3671 x = ELX + Step[k * 4].x;
3672 y = ELY + Step[k * 4].y;
3674 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3677 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3685 laser.overloaded = (element == EL_BLOCK_STONE);
3690 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3693 Tile[x][y] = element;
3695 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3698 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3700 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3701 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3709 if (element == EL_FUEL_FULL && CT > 10)
3711 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3712 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3714 for (i = start; i <= num_init_game_frames; i++)
3716 if (i == num_init_game_frames)
3717 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3718 else if (setup.sound_loops)
3719 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3721 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3723 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3725 UpdateAndDisplayGameControlValues();
3730 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3732 DrawField_MM(ELX, ELY);
3734 DrawLaser(0, DL_LASER_ENABLED);
3742 void GameActions_MM(struct MouseActionInfo action)
3744 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3745 boolean button_released = (action.button == MB_RELEASED);
3747 GameActions_MM_Ext();
3749 CheckSingleStepMode_MM(element_clicked, button_released);
3752 void MovePacMen(void)
3754 int mx, my, ox, oy, nx, ny;
3758 if (++pacman_nr >= game_mm.num_pacman)
3761 game_mm.pacman[pacman_nr].dir--;
3763 for (l = 1; l < 5; l++)
3765 game_mm.pacman[pacman_nr].dir++;
3767 if (game_mm.pacman[pacman_nr].dir > 4)
3768 game_mm.pacman[pacman_nr].dir = 1;
3770 if (game_mm.pacman[pacman_nr].dir % 2)
3773 my = game_mm.pacman[pacman_nr].dir - 2;
3778 mx = 3 - game_mm.pacman[pacman_nr].dir;
3781 ox = game_mm.pacman[pacman_nr].x;
3782 oy = game_mm.pacman[pacman_nr].y;
3785 element = Tile[nx][ny];
3787 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3790 if (!IS_EATABLE4PACMAN(element))
3793 if (ObjHit(nx, ny, HIT_POS_CENTER))
3796 Tile[ox][oy] = EL_EMPTY;
3798 EL_PACMAN_RIGHT - 1 +
3799 (game_mm.pacman[pacman_nr].dir - 1 +
3800 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3802 game_mm.pacman[pacman_nr].x = nx;
3803 game_mm.pacman[pacman_nr].y = ny;
3805 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3807 if (element != EL_EMPTY)
3809 int graphic = el2gfx(Tile[nx][ny]);
3814 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3817 ox = cSX + ox * TILEX;
3818 oy = cSY + oy * TILEY;
3820 for (i = 1; i < 33; i += 2)
3821 BlitBitmap(bitmap, window,
3822 src_x, src_y, TILEX, TILEY,
3823 ox + i * mx, oy + i * my);
3824 Ct = Ct + FrameCounter - CT;
3827 DrawField_MM(nx, ny);
3830 if (!laser.fuse_off)
3832 DrawLaser(0, DL_LASER_ENABLED);
3834 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3836 AddDamagedField(nx, ny);
3838 laser.damage[laser.num_damages - 1].edge = 0;
3842 if (element == EL_BOMB)
3843 DeletePacMan(nx, ny);
3845 if (IS_WALL_AMOEBA(element) &&
3846 (LX + 2 * XS) / TILEX == nx &&
3847 (LY + 2 * YS) / TILEY == ny)
3857 static void InitMovingField_MM(int x, int y, int direction)
3859 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3860 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3862 MovDir[x][y] = direction;
3863 MovDir[newx][newy] = direction;
3865 if (Tile[newx][newy] == EL_EMPTY)
3866 Tile[newx][newy] = EL_BLOCKED;
3869 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3871 int direction = MovDir[x][y];
3872 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3873 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3879 static void Blocked2Moving_MM(int x, int y,
3880 int *comes_from_x, int *comes_from_y)
3882 int oldx = x, oldy = y;
3883 int direction = MovDir[x][y];
3885 if (direction == MV_LEFT)
3887 else if (direction == MV_RIGHT)
3889 else if (direction == MV_UP)
3891 else if (direction == MV_DOWN)
3894 *comes_from_x = oldx;
3895 *comes_from_y = oldy;
3898 static int MovingOrBlocked2Element_MM(int x, int y)
3900 int element = Tile[x][y];
3902 if (element == EL_BLOCKED)
3906 Blocked2Moving_MM(x, y, &oldx, &oldy);
3908 return Tile[oldx][oldy];
3915 static void RemoveField(int x, int y)
3917 Tile[x][y] = EL_EMPTY;
3924 static void RemoveMovingField_MM(int x, int y)
3926 int oldx = x, oldy = y, newx = x, newy = y;
3928 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3931 if (IS_MOVING(x, y))
3933 Moving2Blocked_MM(x, y, &newx, &newy);
3934 if (Tile[newx][newy] != EL_BLOCKED)
3937 else if (Tile[x][y] == EL_BLOCKED)
3939 Blocked2Moving_MM(x, y, &oldx, &oldy);
3940 if (!IS_MOVING(oldx, oldy))
3944 Tile[oldx][oldy] = EL_EMPTY;
3945 Tile[newx][newy] = EL_EMPTY;
3946 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3947 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3949 DrawLevelField_MM(oldx, oldy);
3950 DrawLevelField_MM(newx, newy);
3953 void PlaySoundLevel(int x, int y, int sound_nr)
3955 int sx = SCREENX(x), sy = SCREENY(y);
3957 int silence_distance = 8;
3959 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3960 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3963 if (!IN_LEV_FIELD(x, y) ||
3964 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3965 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3968 volume = SOUND_MAX_VOLUME;
3971 stereo = (sx - SCR_FIELDX/2) * 12;
3973 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3974 if (stereo > SOUND_MAX_RIGHT)
3975 stereo = SOUND_MAX_RIGHT;
3976 if (stereo < SOUND_MAX_LEFT)
3977 stereo = SOUND_MAX_LEFT;
3980 if (!IN_SCR_FIELD(sx, sy))
3982 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3983 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3985 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3988 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3991 static void RaiseScore_MM(int value)
3993 game_mm.score += value;
3996 void RaiseScoreElement_MM(int element)
4001 case EL_PACMAN_RIGHT:
4003 case EL_PACMAN_LEFT:
4004 case EL_PACMAN_DOWN:
4005 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4009 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4014 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4018 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4027 // ----------------------------------------------------------------------------
4028 // Mirror Magic game engine snapshot handling functions
4029 // ----------------------------------------------------------------------------
4031 void SaveEngineSnapshotValues_MM(void)
4035 engine_snapshot_mm.game_mm = game_mm;
4036 engine_snapshot_mm.laser = laser;
4038 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4040 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4042 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4043 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4044 engine_snapshot_mm.Box[x][y] = Box[x][y];
4045 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4049 engine_snapshot_mm.LX = LX;
4050 engine_snapshot_mm.LY = LY;
4051 engine_snapshot_mm.XS = XS;
4052 engine_snapshot_mm.YS = YS;
4053 engine_snapshot_mm.ELX = ELX;
4054 engine_snapshot_mm.ELY = ELY;
4055 engine_snapshot_mm.CT = CT;
4056 engine_snapshot_mm.Ct = Ct;
4058 engine_snapshot_mm.last_LX = last_LX;
4059 engine_snapshot_mm.last_LY = last_LY;
4060 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4061 engine_snapshot_mm.hold_x = hold_x;
4062 engine_snapshot_mm.hold_y = hold_y;
4063 engine_snapshot_mm.pacman_nr = pacman_nr;
4065 engine_snapshot_mm.rotate_delay = rotate_delay;
4066 engine_snapshot_mm.pacman_delay = pacman_delay;
4067 engine_snapshot_mm.energy_delay = energy_delay;
4068 engine_snapshot_mm.overload_delay = overload_delay;
4071 void LoadEngineSnapshotValues_MM(void)
4075 // stored engine snapshot buffers already restored at this point
4077 game_mm = engine_snapshot_mm.game_mm;
4078 laser = engine_snapshot_mm.laser;
4080 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4082 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4084 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4085 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4086 Box[x][y] = engine_snapshot_mm.Box[x][y];
4087 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4091 LX = engine_snapshot_mm.LX;
4092 LY = engine_snapshot_mm.LY;
4093 XS = engine_snapshot_mm.XS;
4094 YS = engine_snapshot_mm.YS;
4095 ELX = engine_snapshot_mm.ELX;
4096 ELY = engine_snapshot_mm.ELY;
4097 CT = engine_snapshot_mm.CT;
4098 Ct = engine_snapshot_mm.Ct;
4100 last_LX = engine_snapshot_mm.last_LX;
4101 last_LY = engine_snapshot_mm.last_LY;
4102 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4103 hold_x = engine_snapshot_mm.hold_x;
4104 hold_y = engine_snapshot_mm.hold_y;
4105 pacman_nr = engine_snapshot_mm.pacman_nr;
4107 rotate_delay = engine_snapshot_mm.rotate_delay;
4108 pacman_delay = engine_snapshot_mm.pacman_delay;
4109 energy_delay = engine_snapshot_mm.energy_delay;
4110 overload_delay = engine_snapshot_mm.overload_delay;
4112 RedrawPlayfield_MM();
4115 static int getAngleFromTouchDelta(int dx, int dy, int base)
4117 double pi = 3.141592653;
4118 double rad = atan2((double)-dy, (double)dx);
4119 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4120 double deg = rad2 * 180.0 / pi;
4122 return (int)(deg * base / 360.0 + 0.5) % base;
4125 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4127 // calculate start (source) position to be at the middle of the tile
4128 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4129 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4130 int dx = dst_mx - src_mx;
4131 int dy = dst_my - src_my;
4140 if (!IN_LEV_FIELD(x, y))
4143 element = Tile[x][y];
4145 if (!IS_MCDUFFIN(element) &&
4146 !IS_MIRROR(element) &&
4147 !IS_BEAMER(element) &&
4148 !IS_POLAR(element) &&
4149 !IS_POLAR_CROSS(element) &&
4150 !IS_DF_MIRROR(element))
4153 angle_old = get_element_angle(element);
4155 if (IS_MCDUFFIN(element))
4157 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4158 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4159 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4160 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4163 else if (IS_MIRROR(element) ||
4164 IS_DF_MIRROR(element))
4166 for (i = 0; i < laser.num_damages; i++)
4168 if (laser.damage[i].x == x &&
4169 laser.damage[i].y == y &&
4170 ObjHit(x, y, HIT_POS_CENTER))
4172 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4173 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4180 if (angle_new == -1)
4182 if (IS_MIRROR(element) ||
4183 IS_DF_MIRROR(element) ||
4187 if (IS_POLAR_CROSS(element))
4190 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4193 button = (angle_new == angle_old ? 0 :
4194 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4195 MB_LEFTBUTTON : MB_RIGHTBUTTON);