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;
629 game_mm.level_solved = FALSE;
630 game_mm.game_over = FALSE;
631 game_mm.game_over_cause = 0;
633 game_mm.laser_overload_value = 0;
634 game_mm.laser_enabled = FALSE;
636 // set global laser control values (must be set before "InitLaser()")
637 laser.start_edge.x = 0;
638 laser.start_edge.y = 0;
639 laser.start_angle = 0;
641 for (i = 0; i < MAX_NUM_BEAMERS; i++)
642 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
644 laser.overloaded = FALSE;
645 laser.overload_value = 0;
646 laser.fuse_off = FALSE;
647 laser.fuse_x = laser.fuse_y = -1;
649 laser.dest_element = EL_EMPTY;
650 laser.dest_element_last = EL_EMPTY;
651 laser.dest_element_last_x = -1;
652 laser.dest_element_last_y = -1;
666 rotate_delay.count = 0;
667 pacman_delay.count = 0;
668 energy_delay.count = 0;
669 overload_delay.count = 0;
671 ClickElement(-1, -1, -1);
673 for (x = 0; x < lev_fieldx; x++)
675 for (y = 0; y < lev_fieldy; y++)
677 Tile[x][y] = Ur[x][y];
678 Hit[x][y] = Box[x][y] = 0;
680 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
681 Store[x][y] = Store2[x][y] = 0;
691 void InitGameActions_MM(void)
693 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
694 int cycle_steps_done = 0;
699 for (i = 0; i <= num_init_game_frames; i++)
701 if (i == num_init_game_frames)
702 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
703 else if (setup.sound_loops)
704 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
706 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
708 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
710 UpdateAndDisplayGameControlValues();
712 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
714 InitCycleElements_RotateSingleStep();
719 AdvanceFrameCounter();
729 if (setup.quick_doors)
736 if (game_mm.kettles_still_needed == 0)
739 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
740 SetTileCursorActive(TRUE);
742 ResetFrameCounter(&energy_delay);
745 static void FadeOutLaser(void)
749 for (i = 15; i >= 0; i--)
751 SetLaserColor(0x11 * i);
753 DrawLaser(0, DL_LASER_ENABLED);
756 Delay_WithScreenUpdates(50);
759 DrawLaser(0, DL_LASER_DISABLED);
761 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
764 static void GameOver_MM(int game_over_cause)
766 // do not handle game over if request dialog is already active
767 if (game.request_active)
770 game_mm.game_over = TRUE;
771 game_mm.game_over_cause = game_over_cause;
773 if (setup.ask_on_game_over)
774 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
775 "Bomb killed Mc Duffin! Play it again?" :
776 game_over_cause == GAME_OVER_NO_ENERGY ?
777 "Out of magic energy! Play it again?" :
778 game_over_cause == GAME_OVER_OVERLOADED ?
779 "Magic spell hit Mc Duffin! Play it again?" :
782 SetTileCursorActive(FALSE);
785 void AddLaserEdge(int lx, int ly)
790 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
792 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
797 laser.edge[laser.num_edges].x = cSX2 + lx;
798 laser.edge[laser.num_edges].y = cSY2 + ly;
804 void AddDamagedField(int ex, int ey)
806 laser.damage[laser.num_damages].is_mirror = FALSE;
807 laser.damage[laser.num_damages].angle = laser.current_angle;
808 laser.damage[laser.num_damages].edge = laser.num_edges;
809 laser.damage[laser.num_damages].x = ex;
810 laser.damage[laser.num_damages].y = ey;
814 static boolean StepBehind(void)
820 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
821 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
823 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
829 static int getMaskFromElement(int element)
831 if (IS_GRID(element))
832 return MM_MASK_GRID_1 + get_element_phase(element);
833 else if (IS_MCDUFFIN(element))
834 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
835 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
836 return MM_MASK_RECTANGLE;
838 return MM_MASK_CIRCLE;
841 static int ScanPixel(void)
846 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
847 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
850 // follow laser beam until it hits something (at least the screen border)
851 while (hit_mask == HIT_MASK_NO_HIT)
857 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
858 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
860 Debug("game:mm:ScanPixel", "touched screen border!");
866 for (i = 0; i < 4; i++)
868 int px = LX + (i % 2) * 2;
869 int py = LY + (i / 2) * 2;
872 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
873 int ly = (py + TILEY) / TILEY - 1; // negative values!
876 if (IN_LEV_FIELD(lx, ly))
878 int element = Tile[lx][ly];
880 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
884 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
886 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
888 pixel = ((element & (1 << pos)) ? 1 : 0);
892 int pos = getMaskFromElement(element);
894 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
899 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
900 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
903 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
904 hit_mask |= (1 << i);
907 if (hit_mask == HIT_MASK_NO_HIT)
909 // hit nothing -- go on with another step
921 int end = 0, rf = laser.num_edges;
923 // do not scan laser again after the game was lost for whatever reason
924 if (game_mm.game_over)
927 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
928 laser.dest_element_last == EL_MINE_ACTIVE)
930 int x = laser.dest_element_last_x;
931 int y = laser.dest_element_last_y;
932 int element = laser.dest_element_last;
934 if (Tile[x][y] == element)
935 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB : EL_MINE);
937 laser.dest_element_last = EL_EMPTY;
938 laser.dest_element_last_x = -1;
939 laser.dest_element_last_y = -1;
942 laser.overloaded = FALSE;
943 laser.stops_inside_element = FALSE;
945 DrawLaser(0, DL_LASER_ENABLED);
948 Debug("game:mm:ScanLaser",
949 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
957 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
960 laser.overloaded = TRUE;
965 hit_mask = ScanPixel();
968 Debug("game:mm:ScanLaser",
969 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
973 // hit something -- check out what it was
974 ELX = (LX + XS) / TILEX;
975 ELY = (LY + YS) / TILEY;
978 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
979 hit_mask, LX, LY, ELX, ELY);
982 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
985 laser.dest_element = element;
990 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
992 /* we have hit the top-right and bottom-left element --
993 choose the bottom-left one */
994 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
995 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
996 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
997 ELX = (LX - 2) / TILEX;
998 ELY = (LY + 2) / TILEY;
1001 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1003 /* we have hit the top-left and bottom-right element --
1004 choose the top-left one */
1005 // !!! SEE ABOVE !!!
1006 ELX = (LX - 2) / TILEX;
1007 ELY = (LY - 2) / TILEY;
1011 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1012 hit_mask, LX, LY, ELX, ELY);
1015 element = Tile[ELX][ELY];
1016 laser.dest_element = element;
1019 Debug("game:mm:ScanLaser",
1020 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1023 LX % TILEX, LY % TILEY,
1028 if (!IN_LEV_FIELD(ELX, ELY))
1029 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1033 if (element == EL_EMPTY)
1035 if (!HitOnlyAnEdge(hit_mask))
1038 else if (element == EL_FUSE_ON)
1040 if (HitPolarizer(element, hit_mask))
1043 else if (IS_GRID(element) || IS_DF_GRID(element))
1045 if (HitPolarizer(element, hit_mask))
1048 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1049 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1051 if (HitBlock(element, hit_mask))
1058 else if (IS_MCDUFFIN(element))
1060 if (HitLaserSource(element, hit_mask))
1063 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1064 IS_RECEIVER(element))
1066 if (HitLaserDestination(element, hit_mask))
1069 else if (IS_WALL(element))
1071 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1073 if (HitReflectingWalls(element, hit_mask))
1078 if (HitAbsorbingWalls(element, hit_mask))
1084 if (HitElement(element, hit_mask))
1089 DrawLaser(rf - 1, DL_LASER_ENABLED);
1090 rf = laser.num_edges;
1094 if (laser.dest_element != Tile[ELX][ELY])
1096 Debug("game:mm:ScanLaser",
1097 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1098 laser.dest_element, Tile[ELX][ELY]);
1102 if (!end && !laser.stops_inside_element && !StepBehind())
1105 Debug("game:mm:ScanLaser", "Go one step back");
1111 AddLaserEdge(LX, LY);
1115 DrawLaser(rf - 1, DL_LASER_ENABLED);
1117 Ct = CT = FrameCounter;
1120 if (!IN_LEV_FIELD(ELX, ELY))
1121 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1125 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1131 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1132 start_edge, num_edges, mode);
1137 Warn("DrawLaserExt: start_edge < 0");
1144 Warn("DrawLaserExt: num_edges < 0");
1150 if (mode == DL_LASER_DISABLED)
1152 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1156 // now draw the laser to the backbuffer and (if enabled) to the screen
1157 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1159 redraw_mask |= REDRAW_FIELD;
1161 if (mode == DL_LASER_ENABLED)
1164 // after the laser was deleted, the "damaged" graphics must be restored
1165 if (laser.num_damages)
1167 int damage_start = 0;
1170 // determine the starting edge, from which graphics need to be restored
1173 for (i = 0; i < laser.num_damages; i++)
1175 if (laser.damage[i].edge == start_edge + 1)
1184 // restore graphics from this starting edge to the end of damage list
1185 for (i = damage_start; i < laser.num_damages; i++)
1187 int lx = laser.damage[i].x;
1188 int ly = laser.damage[i].y;
1189 int element = Tile[lx][ly];
1191 if (Hit[lx][ly] == laser.damage[i].edge)
1192 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1195 if (Box[lx][ly] == laser.damage[i].edge)
1198 if (IS_DRAWABLE(element))
1199 DrawField_MM(lx, ly);
1202 elx = laser.damage[damage_start].x;
1203 ely = laser.damage[damage_start].y;
1204 element = Tile[elx][ely];
1207 if (IS_BEAMER(element))
1211 for (i = 0; i < laser.num_beamers; i++)
1212 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1214 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1215 mode, elx, ely, Hit[elx][ely], start_edge);
1216 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1217 get_element_angle(element), laser.damage[damage_start].angle);
1221 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1222 laser.num_beamers > 0 &&
1223 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1225 // element is outgoing beamer
1226 laser.num_damages = damage_start + 1;
1228 if (IS_BEAMER(element))
1229 laser.current_angle = get_element_angle(element);
1233 // element is incoming beamer or other element
1234 laser.num_damages = damage_start;
1235 laser.current_angle = laser.damage[laser.num_damages].angle;
1240 // no damages but McDuffin himself (who needs to be redrawn anyway)
1242 elx = laser.start_edge.x;
1243 ely = laser.start_edge.y;
1244 element = Tile[elx][ely];
1247 laser.num_edges = start_edge + 1;
1248 if (start_edge == 0)
1249 laser.current_angle = laser.start_angle;
1251 LX = laser.edge[start_edge].x - cSX2;
1252 LY = laser.edge[start_edge].y - cSY2;
1253 XS = 2 * Step[laser.current_angle].x;
1254 YS = 2 * Step[laser.current_angle].y;
1257 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1263 if (IS_BEAMER(element) ||
1264 IS_FIBRE_OPTIC(element) ||
1265 IS_PACMAN(element) ||
1266 IS_POLAR(element) ||
1267 IS_POLAR_CROSS(element) ||
1268 element == EL_FUSE_ON)
1273 Debug("game:mm:DrawLaserExt", "element == %d", element);
1276 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1277 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1281 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1282 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1283 (laser.num_beamers == 0 ||
1284 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1286 // element is incoming beamer or other element
1287 step_size = -step_size;
1292 if (IS_BEAMER(element))
1293 Debug("game:mm:DrawLaserExt",
1294 "start_edge == %d, laser.beamer_edge == %d",
1295 start_edge, laser.beamer_edge);
1298 LX += step_size * XS;
1299 LY += step_size * YS;
1301 else if (element != EL_EMPTY)
1310 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1315 void DrawLaser(int start_edge, int mode)
1317 if (laser.num_edges - start_edge < 0)
1319 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1324 // check if laser is interrupted by beamer element
1325 if (laser.num_beamers > 0 &&
1326 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1328 if (mode == DL_LASER_ENABLED)
1331 int tmp_start_edge = start_edge;
1333 // draw laser segments forward from the start to the last beamer
1334 for (i = 0; i < laser.num_beamers; i++)
1336 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1338 if (tmp_num_edges <= 0)
1342 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1343 i, laser.beamer_edge[i], tmp_start_edge);
1346 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1348 tmp_start_edge = laser.beamer_edge[i];
1351 // draw last segment from last beamer to the end
1352 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1358 int last_num_edges = laser.num_edges;
1359 int num_beamers = laser.num_beamers;
1361 // delete laser segments backward from the end to the first beamer
1362 for (i = num_beamers - 1; i >= 0; i--)
1364 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1366 if (laser.beamer_edge[i] - start_edge <= 0)
1369 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1371 last_num_edges = laser.beamer_edge[i];
1372 laser.num_beamers--;
1376 if (last_num_edges - start_edge <= 0)
1377 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1378 last_num_edges, start_edge);
1381 // special case when rotating first beamer: delete laser edge on beamer
1382 // (but do not start scanning on previous edge to prevent mirror sound)
1383 if (last_num_edges - start_edge == 1 && start_edge > 0)
1384 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1386 // delete first segment from start to the first beamer
1387 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1392 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1395 game_mm.laser_enabled = mode;
1398 void DrawLaser_MM(void)
1400 DrawLaser(0, game_mm.laser_enabled);
1403 boolean HitElement(int element, int hit_mask)
1405 if (HitOnlyAnEdge(hit_mask))
1408 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1409 element = MovingOrBlocked2Element_MM(ELX, ELY);
1412 Debug("game:mm:HitElement", "(1): element == %d", element);
1416 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1417 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1420 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1424 AddDamagedField(ELX, ELY);
1426 // this is more precise: check if laser would go through the center
1427 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1429 // skip the whole element before continuing the scan
1435 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1437 if (LX/TILEX > ELX || LY/TILEY > ELY)
1439 /* skipping scan positions to the right and down skips one scan
1440 position too much, because this is only the top left scan position
1441 of totally four scan positions (plus one to the right, one to the
1442 bottom and one to the bottom right) */
1452 Debug("game:mm:HitElement", "(2): element == %d", element);
1455 if (LX + 5 * XS < 0 ||
1465 Debug("game:mm:HitElement", "(3): element == %d", element);
1468 if (IS_POLAR(element) &&
1469 ((element - EL_POLAR_START) % 2 ||
1470 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1472 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1474 laser.num_damages--;
1479 if (IS_POLAR_CROSS(element) &&
1480 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1482 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1484 laser.num_damages--;
1489 if (!IS_BEAMER(element) &&
1490 !IS_FIBRE_OPTIC(element) &&
1491 !IS_GRID_WOOD(element) &&
1492 element != EL_FUEL_EMPTY)
1495 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1496 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1498 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1501 LX = ELX * TILEX + 14;
1502 LY = ELY * TILEY + 14;
1504 AddLaserEdge(LX, LY);
1507 if (IS_MIRROR(element) ||
1508 IS_MIRROR_FIXED(element) ||
1509 IS_POLAR(element) ||
1510 IS_POLAR_CROSS(element) ||
1511 IS_DF_MIRROR(element) ||
1512 IS_DF_MIRROR_AUTO(element) ||
1513 element == EL_PRISM ||
1514 element == EL_REFRACTOR)
1516 int current_angle = laser.current_angle;
1519 laser.num_damages--;
1521 AddDamagedField(ELX, ELY);
1523 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1526 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1528 if (IS_MIRROR(element) ||
1529 IS_MIRROR_FIXED(element) ||
1530 IS_DF_MIRROR(element) ||
1531 IS_DF_MIRROR_AUTO(element))
1532 laser.current_angle = get_mirrored_angle(laser.current_angle,
1533 get_element_angle(element));
1535 if (element == EL_PRISM || element == EL_REFRACTOR)
1536 laser.current_angle = RND(16);
1538 XS = 2 * Step[laser.current_angle].x;
1539 YS = 2 * Step[laser.current_angle].y;
1541 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1546 LX += step_size * XS;
1547 LY += step_size * YS;
1549 // draw sparkles on mirror
1550 if ((IS_MIRROR(element) ||
1551 IS_MIRROR_FIXED(element) ||
1552 element == EL_PRISM) &&
1553 current_angle != laser.current_angle)
1555 MovDelay[ELX][ELY] = 11; // start animation
1558 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1559 current_angle != laser.current_angle)
1560 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1563 (get_opposite_angle(laser.current_angle) ==
1564 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1566 return (laser.overloaded ? TRUE : FALSE);
1569 if (element == EL_FUEL_FULL)
1571 laser.stops_inside_element = TRUE;
1576 if (element == EL_BOMB || element == EL_MINE)
1578 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1580 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1582 laser.dest_element_last = Tile[ELX][ELY];
1583 laser.dest_element_last_x = ELX;
1584 laser.dest_element_last_y = ELY;
1586 if (element == EL_MINE)
1587 laser.overloaded = TRUE;
1590 if (element == EL_KETTLE ||
1591 element == EL_CELL ||
1592 element == EL_KEY ||
1593 element == EL_LIGHTBALL ||
1594 element == EL_PACMAN ||
1597 if (!IS_PACMAN(element))
1600 if (element == EL_PACMAN)
1603 if (element == EL_KETTLE || element == EL_CELL)
1605 if (game_mm.kettles_still_needed > 0)
1606 game_mm.kettles_still_needed--;
1608 game.snapshot.collected_item = TRUE;
1610 if (game_mm.kettles_still_needed == 0)
1614 DrawLaser(0, DL_LASER_ENABLED);
1617 else if (element == EL_KEY)
1621 else if (IS_PACMAN(element))
1623 DeletePacMan(ELX, ELY);
1626 RaiseScoreElement_MM(element);
1631 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1633 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1635 DrawLaser(0, DL_LASER_ENABLED);
1637 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1639 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1640 game_mm.lights_still_needed--;
1644 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1645 game_mm.lights_still_needed++;
1648 DrawField_MM(ELX, ELY);
1649 DrawLaser(0, DL_LASER_ENABLED);
1654 laser.stops_inside_element = TRUE;
1660 Debug("game:mm:HitElement", "(4): element == %d", element);
1663 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1664 laser.num_beamers < MAX_NUM_BEAMERS &&
1665 laser.beamer[BEAMER_NR(element)][1].num)
1667 int beamer_angle = get_element_angle(element);
1668 int beamer_nr = BEAMER_NR(element);
1672 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1675 laser.num_damages--;
1677 if (IS_FIBRE_OPTIC(element) ||
1678 laser.current_angle == get_opposite_angle(beamer_angle))
1682 LX = ELX * TILEX + 14;
1683 LY = ELY * TILEY + 14;
1685 AddLaserEdge(LX, LY);
1686 AddDamagedField(ELX, ELY);
1688 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1691 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1693 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1694 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1695 ELX = laser.beamer[beamer_nr][pos].x;
1696 ELY = laser.beamer[beamer_nr][pos].y;
1697 LX = ELX * TILEX + 14;
1698 LY = ELY * TILEY + 14;
1700 if (IS_BEAMER(element))
1702 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1703 XS = 2 * Step[laser.current_angle].x;
1704 YS = 2 * Step[laser.current_angle].y;
1707 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1709 AddLaserEdge(LX, LY);
1710 AddDamagedField(ELX, ELY);
1712 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1715 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1717 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1722 LX += step_size * XS;
1723 LY += step_size * YS;
1725 laser.num_beamers++;
1734 boolean HitOnlyAnEdge(int hit_mask)
1736 // check if the laser hit only the edge of an element and, if so, go on
1739 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1743 if ((hit_mask == HIT_MASK_TOPLEFT ||
1744 hit_mask == HIT_MASK_TOPRIGHT ||
1745 hit_mask == HIT_MASK_BOTTOMLEFT ||
1746 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1747 laser.current_angle % 4) // angle is not 90°
1751 if (hit_mask == HIT_MASK_TOPLEFT)
1756 else if (hit_mask == HIT_MASK_TOPRIGHT)
1761 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1766 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1772 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1778 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1785 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1791 boolean HitPolarizer(int element, int hit_mask)
1793 if (HitOnlyAnEdge(hit_mask))
1796 if (IS_DF_GRID(element))
1798 int grid_angle = get_element_angle(element);
1801 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1802 grid_angle, laser.current_angle);
1805 AddLaserEdge(LX, LY);
1806 AddDamagedField(ELX, ELY);
1809 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1811 if (laser.current_angle == grid_angle ||
1812 laser.current_angle == get_opposite_angle(grid_angle))
1814 // skip the whole element before continuing the scan
1820 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1822 if (LX/TILEX > ELX || LY/TILEY > ELY)
1824 /* skipping scan positions to the right and down skips one scan
1825 position too much, because this is only the top left scan position
1826 of totally four scan positions (plus one to the right, one to the
1827 bottom and one to the bottom right) */
1833 AddLaserEdge(LX, LY);
1839 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1841 LX / TILEX, LY / TILEY,
1842 LX % TILEX, LY % TILEY);
1847 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1849 return HitReflectingWalls(element, hit_mask);
1853 return HitAbsorbingWalls(element, hit_mask);
1856 else if (IS_GRID_STEEL(element))
1858 return HitReflectingWalls(element, hit_mask);
1860 else // IS_GRID_WOOD
1862 return HitAbsorbingWalls(element, hit_mask);
1868 boolean HitBlock(int element, int hit_mask)
1870 boolean check = FALSE;
1872 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1873 game_mm.num_keys == 0)
1876 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1879 int ex = ELX * TILEX + 14;
1880 int ey = ELY * TILEY + 14;
1884 for (i = 1; i < 32; i++)
1889 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1894 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1895 return HitAbsorbingWalls(element, hit_mask);
1899 AddLaserEdge(LX - XS, LY - YS);
1900 AddDamagedField(ELX, ELY);
1903 Box[ELX][ELY] = laser.num_edges;
1905 return HitReflectingWalls(element, hit_mask);
1908 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1910 int xs = XS / 2, ys = YS / 2;
1911 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1912 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1914 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1915 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1917 laser.overloaded = (element == EL_GATE_STONE);
1922 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1923 (hit_mask == HIT_MASK_TOP ||
1924 hit_mask == HIT_MASK_LEFT ||
1925 hit_mask == HIT_MASK_RIGHT ||
1926 hit_mask == HIT_MASK_BOTTOM))
1927 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1928 hit_mask == HIT_MASK_BOTTOM),
1929 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1930 hit_mask == HIT_MASK_RIGHT));
1931 AddLaserEdge(LX, LY);
1937 if (element == EL_GATE_STONE && Box[ELX][ELY])
1939 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1951 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1953 int xs = XS / 2, ys = YS / 2;
1954 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1955 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1957 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1958 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1960 laser.overloaded = (element == EL_BLOCK_STONE);
1965 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1966 (hit_mask == HIT_MASK_TOP ||
1967 hit_mask == HIT_MASK_LEFT ||
1968 hit_mask == HIT_MASK_RIGHT ||
1969 hit_mask == HIT_MASK_BOTTOM))
1970 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1971 hit_mask == HIT_MASK_BOTTOM),
1972 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1973 hit_mask == HIT_MASK_RIGHT));
1974 AddDamagedField(ELX, ELY);
1976 LX = ELX * TILEX + 14;
1977 LY = ELY * TILEY + 14;
1979 AddLaserEdge(LX, LY);
1981 laser.stops_inside_element = TRUE;
1989 boolean HitLaserSource(int element, int hit_mask)
1991 if (HitOnlyAnEdge(hit_mask))
1994 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1996 laser.overloaded = TRUE;
2001 boolean HitLaserDestination(int element, int hit_mask)
2003 if (HitOnlyAnEdge(hit_mask))
2006 if (element != EL_EXIT_OPEN &&
2007 !(IS_RECEIVER(element) &&
2008 game_mm.kettles_still_needed == 0 &&
2009 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2011 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2016 if (IS_RECEIVER(element) ||
2017 (IS_22_5_ANGLE(laser.current_angle) &&
2018 (ELX != (LX + 6 * XS) / TILEX ||
2019 ELY != (LY + 6 * YS) / TILEY ||
2028 LX = ELX * TILEX + 14;
2029 LY = ELY * TILEY + 14;
2031 laser.stops_inside_element = TRUE;
2034 AddLaserEdge(LX, LY);
2035 AddDamagedField(ELX, ELY);
2037 if (game_mm.lights_still_needed == 0)
2039 game_mm.level_solved = TRUE;
2041 SetTileCursorActive(FALSE);
2047 boolean HitReflectingWalls(int element, int hit_mask)
2049 // check if laser hits side of a wall with an angle that is not 90°
2050 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2051 hit_mask == HIT_MASK_LEFT ||
2052 hit_mask == HIT_MASK_RIGHT ||
2053 hit_mask == HIT_MASK_BOTTOM))
2055 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2060 if (!IS_DF_GRID(element))
2061 AddLaserEdge(LX, LY);
2063 // check if laser hits wall with an angle of 45°
2064 if (!IS_22_5_ANGLE(laser.current_angle))
2066 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2069 laser.current_angle = get_mirrored_angle(laser.current_angle,
2072 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2075 laser.current_angle = get_mirrored_angle(laser.current_angle,
2079 AddLaserEdge(LX, LY);
2081 XS = 2 * Step[laser.current_angle].x;
2082 YS = 2 * Step[laser.current_angle].y;
2086 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2088 laser.current_angle = get_mirrored_angle(laser.current_angle,
2093 if (!IS_DF_GRID(element))
2094 AddLaserEdge(LX, LY);
2099 if (!IS_DF_GRID(element))
2100 AddLaserEdge(LX, LY + YS / 2);
2103 if (!IS_DF_GRID(element))
2104 AddLaserEdge(LX, LY);
2107 YS = 2 * Step[laser.current_angle].y;
2111 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2113 laser.current_angle = get_mirrored_angle(laser.current_angle,
2118 if (!IS_DF_GRID(element))
2119 AddLaserEdge(LX, LY);
2124 if (!IS_DF_GRID(element))
2125 AddLaserEdge(LX + XS / 2, LY);
2128 if (!IS_DF_GRID(element))
2129 AddLaserEdge(LX, LY);
2132 XS = 2 * Step[laser.current_angle].x;
2138 // reflection at the edge of reflecting DF style wall
2139 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2141 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2142 hit_mask == HIT_MASK_TOPRIGHT) ||
2143 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2144 hit_mask == HIT_MASK_TOPLEFT) ||
2145 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2146 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2147 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2148 hit_mask == HIT_MASK_BOTTOMRIGHT))
2151 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2152 ANG_MIRROR_135 : ANG_MIRROR_45);
2154 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2156 AddDamagedField(ELX, ELY);
2157 AddLaserEdge(LX, LY);
2159 laser.current_angle = get_mirrored_angle(laser.current_angle,
2167 AddLaserEdge(LX, LY);
2173 // reflection inside an edge of reflecting DF style wall
2174 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2176 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2177 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2178 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2179 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2180 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2181 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2182 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2183 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2186 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2187 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2188 ANG_MIRROR_135 : ANG_MIRROR_45);
2190 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2193 AddDamagedField(ELX, ELY);
2196 AddLaserEdge(LX - XS, LY - YS);
2197 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2198 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2200 laser.current_angle = get_mirrored_angle(laser.current_angle,
2208 AddLaserEdge(LX, LY);
2214 // check if laser hits DF style wall with an angle of 90°
2215 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2217 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2218 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2219 (IS_VERT_ANGLE(laser.current_angle) &&
2220 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2222 // laser at last step touched nothing or the same side of the wall
2223 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2225 AddDamagedField(ELX, ELY);
2232 last_hit_mask = hit_mask;
2239 if (!HitOnlyAnEdge(hit_mask))
2241 laser.overloaded = TRUE;
2249 boolean HitAbsorbingWalls(int element, int hit_mask)
2251 if (HitOnlyAnEdge(hit_mask))
2255 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2257 AddLaserEdge(LX - XS, LY - YS);
2264 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2266 AddLaserEdge(LX - XS, LY - YS);
2272 if (IS_WALL_WOOD(element) ||
2273 IS_DF_WALL_WOOD(element) ||
2274 IS_GRID_WOOD(element) ||
2275 IS_GRID_WOOD_FIXED(element) ||
2276 IS_GRID_WOOD_AUTO(element) ||
2277 element == EL_FUSE_ON ||
2278 element == EL_BLOCK_WOOD ||
2279 element == EL_GATE_WOOD)
2281 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2286 if (IS_WALL_ICE(element))
2290 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2291 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2293 // check if laser hits wall with an angle of 90°
2294 if (IS_90_ANGLE(laser.current_angle))
2295 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2297 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2301 for (i = 0; i < 4; i++)
2303 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2304 mask = 15 - (8 >> i);
2305 else if (ABS(XS) == 4 &&
2307 (XS > 0) == (i % 2) &&
2308 (YS < 0) == (i / 2))
2309 mask = 3 + (i / 2) * 9;
2310 else if (ABS(YS) == 4 &&
2312 (XS < 0) == (i % 2) &&
2313 (YS > 0) == (i / 2))
2314 mask = 5 + (i % 2) * 5;
2318 laser.wall_mask = mask;
2320 else if (IS_WALL_AMOEBA(element))
2322 int elx = (LX - 2 * XS) / TILEX;
2323 int ely = (LY - 2 * YS) / TILEY;
2324 int element2 = Tile[elx][ely];
2327 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2329 laser.dest_element = EL_EMPTY;
2337 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2338 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2340 if (IS_90_ANGLE(laser.current_angle))
2341 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2343 laser.dest_element = element2 | EL_WALL_AMOEBA;
2345 laser.wall_mask = mask;
2351 static void OpenExit(int x, int y)
2355 if (!MovDelay[x][y]) // next animation frame
2356 MovDelay[x][y] = 4 * delay;
2358 if (MovDelay[x][y]) // wait some time before next frame
2363 phase = MovDelay[x][y] / delay;
2365 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2366 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2368 if (!MovDelay[x][y])
2370 Tile[x][y] = EL_EXIT_OPEN;
2376 static void OpenSurpriseBall(int x, int y)
2380 if (!MovDelay[x][y]) // next animation frame
2381 MovDelay[x][y] = 50 * delay;
2383 if (MovDelay[x][y]) // wait some time before next frame
2387 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2390 int graphic = el2gfx(Store[x][y]);
2392 int dx = RND(26), dy = RND(26);
2394 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2396 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2397 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2399 MarkTileDirty(x, y);
2402 if (!MovDelay[x][y])
2404 Tile[x][y] = Store[x][y];
2413 static void MeltIce(int x, int y)
2418 if (!MovDelay[x][y]) // next animation frame
2419 MovDelay[x][y] = frames * delay;
2421 if (MovDelay[x][y]) // wait some time before next frame
2424 int wall_mask = Store2[x][y];
2425 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2428 phase = frames - MovDelay[x][y] / delay - 1;
2430 if (!MovDelay[x][y])
2434 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2435 Store[x][y] = Store2[x][y] = 0;
2437 DrawWalls_MM(x, y, Tile[x][y]);
2439 if (Tile[x][y] == EL_WALL_ICE)
2440 Tile[x][y] = EL_EMPTY;
2442 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2443 if (laser.damage[i].is_mirror)
2447 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2449 DrawLaser(0, DL_LASER_DISABLED);
2453 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2455 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2457 laser.redraw = TRUE;
2462 static void GrowAmoeba(int x, int y)
2467 if (!MovDelay[x][y]) // next animation frame
2468 MovDelay[x][y] = frames * delay;
2470 if (MovDelay[x][y]) // wait some time before next frame
2473 int wall_mask = Store2[x][y];
2474 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2477 phase = MovDelay[x][y] / delay;
2479 if (!MovDelay[x][y])
2481 Tile[x][y] = real_element;
2482 Store[x][y] = Store2[x][y] = 0;
2484 DrawWalls_MM(x, y, Tile[x][y]);
2485 DrawLaser(0, DL_LASER_ENABLED);
2487 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2489 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2494 static void DrawFieldAnimated_MM(int x, int y)
2496 int element = Tile[x][y];
2498 if (IS_BLOCKED(x, y))
2503 if (IS_MIRROR(element) ||
2504 IS_MIRROR_FIXED(element) ||
2505 element == EL_PRISM)
2507 if (MovDelay[x][y] != 0) // wait some time before next frame
2511 if (MovDelay[x][y] != 0)
2513 int graphic = IMG_TWINKLE_WHITE;
2514 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2516 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2521 laser.redraw = TRUE;
2524 static void Explode_MM(int x, int y, int phase, int mode)
2526 int num_phase = 9, delay = 2;
2527 int last_phase = num_phase * delay;
2528 int half_phase = (num_phase / 2) * delay;
2530 laser.redraw = TRUE;
2532 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2534 int center_element = Tile[x][y];
2536 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2538 // put moving element to center field (and let it explode there)
2539 center_element = MovingOrBlocked2Element_MM(x, y);
2540 RemoveMovingField_MM(x, y);
2542 Tile[x][y] = center_element;
2545 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2546 Store[x][y] = center_element;
2548 Store[x][y] = EL_EMPTY;
2550 Store2[x][y] = mode;
2552 Tile[x][y] = EL_EXPLODING_OPAQUE;
2553 GfxElement[x][y] = center_element;
2555 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2557 ExplodePhase[x][y] = 1;
2563 GfxFrame[x][y] = 0; // restart explosion animation
2565 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2567 if (phase == half_phase)
2569 Tile[x][y] = EL_EXPLODING_TRANSP;
2571 if (x == ELX && y == ELY)
2575 if (phase == last_phase)
2577 if (Store[x][y] == EL_BOMB_ACTIVE)
2579 DrawLaser(0, DL_LASER_DISABLED);
2582 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2583 Store[x][y] = EL_EMPTY;
2585 GameOver_MM(GAME_OVER_DELAYED);
2587 laser.overloaded = FALSE;
2589 else if (IS_MCDUFFIN(Store[x][y]))
2591 Store[x][y] = EL_EMPTY;
2593 GameOver_MM(GAME_OVER_BOMB);
2596 Tile[x][y] = Store[x][y];
2597 Store[x][y] = Store2[x][y] = 0;
2598 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2603 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2605 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2606 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2608 DrawGraphicAnimation_MM(x, y, graphic, frame);
2610 MarkTileDirty(x, y);
2614 static void Bang_MM(int x, int y)
2616 int element = Tile[x][y];
2619 DrawLaser(0, DL_LASER_ENABLED);
2622 if (IS_PACMAN(element))
2623 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2624 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2625 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2626 else if (element == EL_KEY)
2627 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2629 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2631 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2634 void TurnRound(int x, int y)
2646 { 0, 0 }, { 0, 0 }, { 0, 0 },
2651 int left, right, back;
2655 { MV_DOWN, MV_UP, MV_RIGHT },
2656 { MV_UP, MV_DOWN, MV_LEFT },
2658 { MV_LEFT, MV_RIGHT, MV_DOWN },
2659 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2660 { MV_RIGHT, MV_LEFT, MV_UP }
2663 int element = Tile[x][y];
2664 int old_move_dir = MovDir[x][y];
2665 int right_dir = turn[old_move_dir].right;
2666 int back_dir = turn[old_move_dir].back;
2667 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2668 int right_x = x + right_dx, right_y = y + right_dy;
2670 if (element == EL_PACMAN)
2672 boolean can_turn_right = FALSE;
2674 if (IN_LEV_FIELD(right_x, right_y) &&
2675 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2676 can_turn_right = TRUE;
2679 MovDir[x][y] = right_dir;
2681 MovDir[x][y] = back_dir;
2687 static void StartMoving_MM(int x, int y)
2689 int element = Tile[x][y];
2694 if (CAN_MOVE(element))
2698 if (MovDelay[x][y]) // wait some time before next movement
2706 // now make next step
2708 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2710 if (element == EL_PACMAN &&
2711 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2712 !ObjHit(newx, newy, HIT_POS_CENTER))
2714 Store[newx][newy] = Tile[newx][newy];
2715 Tile[newx][newy] = EL_EMPTY;
2717 DrawField_MM(newx, newy);
2719 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2720 ObjHit(newx, newy, HIT_POS_CENTER))
2722 // object was running against a wall
2729 InitMovingField_MM(x, y, MovDir[x][y]);
2733 ContinueMoving_MM(x, y);
2736 static void ContinueMoving_MM(int x, int y)
2738 int element = Tile[x][y];
2739 int direction = MovDir[x][y];
2740 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2741 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2742 int horiz_move = (dx!=0);
2743 int newx = x + dx, newy = y + dy;
2744 int step = (horiz_move ? dx : dy) * TILEX / 8;
2746 MovPos[x][y] += step;
2748 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2750 Tile[x][y] = EL_EMPTY;
2751 Tile[newx][newy] = element;
2753 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2754 MovDelay[newx][newy] = 0;
2756 if (!CAN_MOVE(element))
2757 MovDir[newx][newy] = 0;
2760 DrawField_MM(newx, newy);
2762 Stop[newx][newy] = TRUE;
2764 if (element == EL_PACMAN)
2766 if (Store[newx][newy] == EL_BOMB)
2767 Bang_MM(newx, newy);
2769 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2770 (LX + 2 * XS) / TILEX == newx &&
2771 (LY + 2 * YS) / TILEY == newy)
2778 else // still moving on
2783 laser.redraw = TRUE;
2786 boolean ClickElement(int x, int y, int button)
2788 static DelayCounter click_delay = { CLICK_DELAY };
2789 static boolean new_button = TRUE;
2790 boolean element_clicked = FALSE;
2795 // initialize static variables
2796 click_delay.count = 0;
2797 click_delay.value = CLICK_DELAY;
2803 // do not rotate objects hit by the laser after the game was solved
2804 if (game_mm.level_solved && Hit[x][y])
2807 if (button == MB_RELEASED)
2810 click_delay.value = CLICK_DELAY;
2812 // release eventually hold auto-rotating mirror
2813 RotateMirror(x, y, MB_RELEASED);
2818 if (!FrameReached(&click_delay) && !new_button)
2821 if (button == MB_MIDDLEBUTTON) // middle button has no function
2824 if (!IN_LEV_FIELD(x, y))
2827 if (Tile[x][y] == EL_EMPTY)
2830 element = Tile[x][y];
2832 if (IS_MIRROR(element) ||
2833 IS_BEAMER(element) ||
2834 IS_POLAR(element) ||
2835 IS_POLAR_CROSS(element) ||
2836 IS_DF_MIRROR(element) ||
2837 IS_DF_MIRROR_AUTO(element))
2839 RotateMirror(x, y, button);
2841 element_clicked = TRUE;
2843 else if (IS_MCDUFFIN(element))
2845 if (!laser.fuse_off)
2847 DrawLaser(0, DL_LASER_DISABLED);
2854 element = get_rotated_element(element, BUTTON_ROTATION(button));
2855 laser.start_angle = get_element_angle(element);
2859 Tile[x][y] = element;
2866 if (!laser.fuse_off)
2869 element_clicked = TRUE;
2871 else if (element == EL_FUSE_ON && laser.fuse_off)
2873 if (x != laser.fuse_x || y != laser.fuse_y)
2876 laser.fuse_off = FALSE;
2877 laser.fuse_x = laser.fuse_y = -1;
2879 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2882 element_clicked = TRUE;
2884 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2886 laser.fuse_off = TRUE;
2889 laser.overloaded = FALSE;
2891 DrawLaser(0, DL_LASER_DISABLED);
2892 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2894 element_clicked = TRUE;
2896 else if (element == EL_LIGHTBALL)
2899 RaiseScoreElement_MM(element);
2900 DrawLaser(0, DL_LASER_ENABLED);
2902 element_clicked = TRUE;
2905 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2908 return element_clicked;
2911 void RotateMirror(int x, int y, int button)
2913 if (button == MB_RELEASED)
2915 // release eventually hold auto-rotating mirror
2922 if (IS_MIRROR(Tile[x][y]) ||
2923 IS_POLAR_CROSS(Tile[x][y]) ||
2924 IS_POLAR(Tile[x][y]) ||
2925 IS_BEAMER(Tile[x][y]) ||
2926 IS_DF_MIRROR(Tile[x][y]) ||
2927 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2928 IS_GRID_WOOD_AUTO(Tile[x][y]))
2930 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2932 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2934 if (button == MB_LEFTBUTTON)
2936 // left mouse button only for manual adjustment, no auto-rotating;
2937 // freeze mirror for until mouse button released
2941 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2943 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2947 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2949 int edge = Hit[x][y];
2955 DrawLaser(edge - 1, DL_LASER_DISABLED);
2959 else if (ObjHit(x, y, HIT_POS_CENTER))
2961 int edge = Hit[x][y];
2965 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2970 DrawLaser(edge - 1, DL_LASER_DISABLED);
2977 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2982 if ((IS_BEAMER(Tile[x][y]) ||
2983 IS_POLAR(Tile[x][y]) ||
2984 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2988 if (IS_BEAMER(Tile[x][y]))
2991 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2992 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3004 DrawLaser(0, DL_LASER_ENABLED);
3008 static void AutoRotateMirrors(void)
3012 if (!FrameReached(&rotate_delay))
3015 for (x = 0; x < lev_fieldx; x++)
3017 for (y = 0; y < lev_fieldy; y++)
3019 int element = Tile[x][y];
3021 // do not rotate objects hit by the laser after the game was solved
3022 if (game_mm.level_solved && Hit[x][y])
3025 if (IS_DF_MIRROR_AUTO(element) ||
3026 IS_GRID_WOOD_AUTO(element) ||
3027 IS_GRID_STEEL_AUTO(element) ||
3028 element == EL_REFRACTOR)
3029 RotateMirror(x, y, MB_RIGHTBUTTON);
3034 boolean ObjHit(int obx, int oby, int bits)
3041 if (bits & HIT_POS_CENTER)
3043 if (CheckLaserPixel(cSX + obx + 15,
3048 if (bits & HIT_POS_EDGE)
3050 for (i = 0; i < 4; i++)
3051 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3052 cSY + oby + 31 * (i / 2)))
3056 if (bits & HIT_POS_BETWEEN)
3058 for (i = 0; i < 4; i++)
3059 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3060 cSY + 4 + oby + 22 * (i / 2)))
3067 void DeletePacMan(int px, int py)
3073 if (game_mm.num_pacman <= 1)
3075 game_mm.num_pacman = 0;
3079 for (i = 0; i < game_mm.num_pacman; i++)
3080 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3083 game_mm.num_pacman--;
3085 for (j = i; j < game_mm.num_pacman; j++)
3087 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3088 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3089 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3093 void ColorCycling(void)
3095 static int CC, Cc = 0;
3097 static int color, old = 0xF00, new = 0x010, mult = 1;
3098 static unsigned short red, green, blue;
3100 if (color_status == STATIC_COLORS)
3105 if (CC < Cc || CC > Cc + 2)
3109 color = old + new * mult;
3115 if (ABS(mult) == 16)
3125 red = 0x0e00 * ((color & 0xF00) >> 8);
3126 green = 0x0e00 * ((color & 0x0F0) >> 4);
3127 blue = 0x0e00 * ((color & 0x00F));
3128 SetRGB(pen_magicolor[0], red, green, blue);
3130 red = 0x1111 * ((color & 0xF00) >> 8);
3131 green = 0x1111 * ((color & 0x0F0) >> 4);
3132 blue = 0x1111 * ((color & 0x00F));
3133 SetRGB(pen_magicolor[1], red, green, blue);
3137 static void GameActions_MM_Ext(void)
3144 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3147 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3149 element = Tile[x][y];
3151 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3152 StartMoving_MM(x, y);
3153 else if (IS_MOVING(x, y))
3154 ContinueMoving_MM(x, y);
3155 else if (IS_EXPLODING(element))
3156 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3157 else if (element == EL_EXIT_OPENING)
3159 else if (element == EL_GRAY_BALL_OPENING)
3160 OpenSurpriseBall(x, y);
3161 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3163 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3166 DrawFieldAnimated_MM(x, y);
3169 AutoRotateMirrors();
3172 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3174 // redraw after Explode_MM() ...
3176 DrawLaser(0, DL_LASER_ENABLED);
3177 laser.redraw = FALSE;
3182 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3186 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3188 DrawLaser(0, DL_LASER_DISABLED);
3193 // skip all following game actions if game is over
3194 if (game_mm.game_over)
3197 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3201 GameOver_MM(GAME_OVER_NO_ENERGY);
3206 if (FrameReached(&energy_delay))
3208 if (game_mm.energy_left > 0)
3209 game_mm.energy_left--;
3211 // when out of energy, wait another frame to play "out of time" sound
3214 element = laser.dest_element;
3217 if (element != Tile[ELX][ELY])
3219 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3220 element, Tile[ELX][ELY]);
3224 if (!laser.overloaded && laser.overload_value == 0 &&
3225 element != EL_BOMB &&
3226 element != EL_BOMB_ACTIVE &&
3227 element != EL_MINE &&
3228 element != EL_MINE_ACTIVE &&
3229 element != EL_BALL_GRAY &&
3230 element != EL_BLOCK_STONE &&
3231 element != EL_BLOCK_WOOD &&
3232 element != EL_FUSE_ON &&
3233 element != EL_FUEL_FULL &&
3234 !IS_WALL_ICE(element) &&
3235 !IS_WALL_AMOEBA(element))
3238 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3240 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3241 (!laser.overloaded && laser.overload_value > 0)) &&
3242 FrameReached(&overload_delay))
3244 if (laser.overloaded)
3245 laser.overload_value++;
3247 laser.overload_value--;
3249 if (game_mm.cheat_no_overload)
3251 laser.overloaded = FALSE;
3252 laser.overload_value = 0;
3255 game_mm.laser_overload_value = laser.overload_value;
3257 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3259 SetLaserColor(0xFF);
3261 DrawLaser(0, DL_LASER_ENABLED);
3264 if (!laser.overloaded)
3265 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3266 else if (setup.sound_loops)
3267 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3269 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3271 if (laser.overloaded)
3274 BlitBitmap(pix[PIX_DOOR], drawto,
3275 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3276 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3277 - laser.overload_value,
3278 OVERLOAD_XSIZE, laser.overload_value,
3279 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3280 - laser.overload_value);
3282 redraw_mask |= REDRAW_DOOR_1;
3287 BlitBitmap(pix[PIX_DOOR], drawto,
3288 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3289 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3290 DX_OVERLOAD, DY_OVERLOAD);
3292 redraw_mask |= REDRAW_DOOR_1;
3295 if (laser.overload_value == MAX_LASER_OVERLOAD)
3297 UpdateAndDisplayGameControlValues();
3301 GameOver_MM(GAME_OVER_OVERLOADED);
3312 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3314 if (game_mm.cheat_no_explosion)
3319 laser.dest_element = EL_EXPLODING_OPAQUE;
3324 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3326 laser.fuse_off = TRUE;
3330 DrawLaser(0, DL_LASER_DISABLED);
3331 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3334 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3336 static int new_elements[] =
3339 EL_MIRROR_FIXED_START,
3341 EL_POLAR_CROSS_START,
3347 int num_new_elements = sizeof(new_elements) / sizeof(int);
3348 int new_element = new_elements[RND(num_new_elements)];
3350 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3351 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3353 // !!! CHECK AGAIN: Laser on Polarizer !!!
3364 element = EL_MIRROR_START + RND(16);
3370 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3377 element = (rnd == 0 ? EL_FUSE_ON :
3378 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3379 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3380 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3381 EL_MIRROR_FIXED_START + rnd - 25);
3386 graphic = el2gfx(element);
3388 for (i = 0; i < 50; i++)
3394 BlitBitmap(pix[PIX_BACK], drawto,
3395 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3396 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3397 SX + ELX * TILEX + x,
3398 SY + ELY * TILEY + y);
3400 MarkTileDirty(ELX, ELY);
3403 DrawLaser(0, DL_LASER_ENABLED);
3405 Delay_WithScreenUpdates(50);
3408 Tile[ELX][ELY] = element;
3409 DrawField_MM(ELX, ELY);
3412 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3415 // above stuff: GRAY BALL -> PRISM !!!
3417 LX = ELX * TILEX + 14;
3418 LY = ELY * TILEY + 14;
3419 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3426 laser.num_edges -= 2;
3427 laser.num_damages--;
3431 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3432 if (laser.damage[i].is_mirror)
3436 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3438 DrawLaser(0, DL_LASER_DISABLED);
3440 DrawLaser(0, DL_LASER_DISABLED);
3449 if (IS_WALL_ICE(element) && CT > 50)
3451 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3454 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3455 Store[ELX][ELY] = EL_WALL_ICE;
3456 Store2[ELX][ELY] = laser.wall_mask;
3458 laser.dest_element = Tile[ELX][ELY];
3463 for (i = 0; i < 5; i++)
3469 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3473 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3475 Delay_WithScreenUpdates(100);
3478 if (Tile[ELX][ELY] == EL_WALL_ICE)
3479 Tile[ELX][ELY] = EL_EMPTY;
3483 LX = laser.edge[laser.num_edges].x - cSX2;
3484 LY = laser.edge[laser.num_edges].y - cSY2;
3487 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3488 if (laser.damage[i].is_mirror)
3492 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3494 DrawLaser(0, DL_LASER_DISABLED);
3501 if (IS_WALL_AMOEBA(element) && CT > 60)
3503 int k1, k2, k3, dx, dy, de, dm;
3504 int element2 = Tile[ELX][ELY];
3506 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3509 for (i = laser.num_damages - 1; i >= 0; i--)
3510 if (laser.damage[i].is_mirror)
3513 r = laser.num_edges;
3514 d = laser.num_damages;
3521 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3524 DrawLaser(0, DL_LASER_ENABLED);
3527 x = laser.damage[k1].x;
3528 y = laser.damage[k1].y;
3533 for (i = 0; i < 4; i++)
3535 if (laser.wall_mask & (1 << i))
3537 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3538 cSY + ELY * TILEY + 31 * (i / 2)))
3541 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3542 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3549 for (i = 0; i < 4; i++)
3551 if (laser.wall_mask & (1 << i))
3553 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3554 cSY + ELY * TILEY + 31 * (i / 2)))
3561 if (laser.num_beamers > 0 ||
3562 k1 < 1 || k2 < 4 || k3 < 4 ||
3563 CheckLaserPixel(cSX + ELX * TILEX + 14,
3564 cSY + ELY * TILEY + 14))
3566 laser.num_edges = r;
3567 laser.num_damages = d;
3569 DrawLaser(0, DL_LASER_DISABLED);
3572 Tile[ELX][ELY] = element | laser.wall_mask;
3576 de = Tile[ELX][ELY];
3577 dm = laser.wall_mask;
3581 int x = ELX, y = ELY;
3582 int wall_mask = laser.wall_mask;
3585 DrawLaser(0, DL_LASER_ENABLED);
3587 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3589 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3590 Store[x][y] = EL_WALL_AMOEBA;
3591 Store2[x][y] = wall_mask;
3597 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3599 DrawLaser(0, DL_LASER_ENABLED);
3601 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3603 for (i = 4; i >= 0; i--)
3605 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3608 Delay_WithScreenUpdates(20);
3611 DrawLaser(0, DL_LASER_ENABLED);
3616 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3617 laser.stops_inside_element && CT > native_mm_level.time_block)
3622 if (ABS(XS) > ABS(YS))
3629 for (i = 0; i < 4; i++)
3636 x = ELX + Step[k * 4].x;
3637 y = ELY + Step[k * 4].y;
3639 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3642 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3650 laser.overloaded = (element == EL_BLOCK_STONE);
3655 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3658 Tile[x][y] = element;
3660 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3663 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3665 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3666 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3674 if (element == EL_FUEL_FULL && CT > 10)
3676 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3677 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3679 for (i = start; i <= num_init_game_frames; i++)
3681 if (i == num_init_game_frames)
3682 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3683 else if (setup.sound_loops)
3684 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3686 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3688 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3690 UpdateAndDisplayGameControlValues();
3695 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3697 DrawField_MM(ELX, ELY);
3699 DrawLaser(0, DL_LASER_ENABLED);
3707 void GameActions_MM(struct MouseActionInfo action)
3709 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3710 boolean button_released = (action.button == MB_RELEASED);
3712 GameActions_MM_Ext();
3714 CheckSingleStepMode_MM(element_clicked, button_released);
3717 void MovePacMen(void)
3719 int mx, my, ox, oy, nx, ny;
3723 if (++pacman_nr >= game_mm.num_pacman)
3726 game_mm.pacman[pacman_nr].dir--;
3728 for (l = 1; l < 5; l++)
3730 game_mm.pacman[pacman_nr].dir++;
3732 if (game_mm.pacman[pacman_nr].dir > 4)
3733 game_mm.pacman[pacman_nr].dir = 1;
3735 if (game_mm.pacman[pacman_nr].dir % 2)
3738 my = game_mm.pacman[pacman_nr].dir - 2;
3743 mx = 3 - game_mm.pacman[pacman_nr].dir;
3746 ox = game_mm.pacman[pacman_nr].x;
3747 oy = game_mm.pacman[pacman_nr].y;
3750 element = Tile[nx][ny];
3752 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3755 if (!IS_EATABLE4PACMAN(element))
3758 if (ObjHit(nx, ny, HIT_POS_CENTER))
3761 Tile[ox][oy] = EL_EMPTY;
3763 EL_PACMAN_RIGHT - 1 +
3764 (game_mm.pacman[pacman_nr].dir - 1 +
3765 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3767 game_mm.pacman[pacman_nr].x = nx;
3768 game_mm.pacman[pacman_nr].y = ny;
3770 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3772 if (element != EL_EMPTY)
3774 int graphic = el2gfx(Tile[nx][ny]);
3779 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3782 ox = cSX + ox * TILEX;
3783 oy = cSY + oy * TILEY;
3785 for (i = 1; i < 33; i += 2)
3786 BlitBitmap(bitmap, window,
3787 src_x, src_y, TILEX, TILEY,
3788 ox + i * mx, oy + i * my);
3789 Ct = Ct + FrameCounter - CT;
3792 DrawField_MM(nx, ny);
3795 if (!laser.fuse_off)
3797 DrawLaser(0, DL_LASER_ENABLED);
3799 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3801 AddDamagedField(nx, ny);
3803 laser.damage[laser.num_damages - 1].edge = 0;
3807 if (element == EL_BOMB)
3808 DeletePacMan(nx, ny);
3810 if (IS_WALL_AMOEBA(element) &&
3811 (LX + 2 * XS) / TILEX == nx &&
3812 (LY + 2 * YS) / TILEY == ny)
3822 static void InitMovingField_MM(int x, int y, int direction)
3824 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3825 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3827 MovDir[x][y] = direction;
3828 MovDir[newx][newy] = direction;
3830 if (Tile[newx][newy] == EL_EMPTY)
3831 Tile[newx][newy] = EL_BLOCKED;
3834 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3836 int direction = MovDir[x][y];
3837 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3838 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3844 static void Blocked2Moving_MM(int x, int y,
3845 int *comes_from_x, int *comes_from_y)
3847 int oldx = x, oldy = y;
3848 int direction = MovDir[x][y];
3850 if (direction == MV_LEFT)
3852 else if (direction == MV_RIGHT)
3854 else if (direction == MV_UP)
3856 else if (direction == MV_DOWN)
3859 *comes_from_x = oldx;
3860 *comes_from_y = oldy;
3863 static int MovingOrBlocked2Element_MM(int x, int y)
3865 int element = Tile[x][y];
3867 if (element == EL_BLOCKED)
3871 Blocked2Moving_MM(x, y, &oldx, &oldy);
3873 return Tile[oldx][oldy];
3880 static void RemoveField(int x, int y)
3882 Tile[x][y] = EL_EMPTY;
3889 static void RemoveMovingField_MM(int x, int y)
3891 int oldx = x, oldy = y, newx = x, newy = y;
3893 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3896 if (IS_MOVING(x, y))
3898 Moving2Blocked_MM(x, y, &newx, &newy);
3899 if (Tile[newx][newy] != EL_BLOCKED)
3902 else if (Tile[x][y] == EL_BLOCKED)
3904 Blocked2Moving_MM(x, y, &oldx, &oldy);
3905 if (!IS_MOVING(oldx, oldy))
3909 Tile[oldx][oldy] = EL_EMPTY;
3910 Tile[newx][newy] = EL_EMPTY;
3911 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3912 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3914 DrawLevelField_MM(oldx, oldy);
3915 DrawLevelField_MM(newx, newy);
3918 void PlaySoundLevel(int x, int y, int sound_nr)
3920 int sx = SCREENX(x), sy = SCREENY(y);
3922 int silence_distance = 8;
3924 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3925 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3928 if (!IN_LEV_FIELD(x, y) ||
3929 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3930 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3933 volume = SOUND_MAX_VOLUME;
3936 stereo = (sx - SCR_FIELDX/2) * 12;
3938 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3939 if (stereo > SOUND_MAX_RIGHT)
3940 stereo = SOUND_MAX_RIGHT;
3941 if (stereo < SOUND_MAX_LEFT)
3942 stereo = SOUND_MAX_LEFT;
3945 if (!IN_SCR_FIELD(sx, sy))
3947 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3948 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3950 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3953 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3956 static void RaiseScore_MM(int value)
3958 game_mm.score += value;
3961 void RaiseScoreElement_MM(int element)
3966 case EL_PACMAN_RIGHT:
3968 case EL_PACMAN_LEFT:
3969 case EL_PACMAN_DOWN:
3970 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3974 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3979 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3983 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3992 // ----------------------------------------------------------------------------
3993 // Mirror Magic game engine snapshot handling functions
3994 // ----------------------------------------------------------------------------
3996 void SaveEngineSnapshotValues_MM(void)
4000 engine_snapshot_mm.game_mm = game_mm;
4001 engine_snapshot_mm.laser = laser;
4003 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4005 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4007 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4008 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4009 engine_snapshot_mm.Box[x][y] = Box[x][y];
4010 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4014 engine_snapshot_mm.LX = LX;
4015 engine_snapshot_mm.LY = LY;
4016 engine_snapshot_mm.XS = XS;
4017 engine_snapshot_mm.YS = YS;
4018 engine_snapshot_mm.ELX = ELX;
4019 engine_snapshot_mm.ELY = ELY;
4020 engine_snapshot_mm.CT = CT;
4021 engine_snapshot_mm.Ct = Ct;
4023 engine_snapshot_mm.last_LX = last_LX;
4024 engine_snapshot_mm.last_LY = last_LY;
4025 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4026 engine_snapshot_mm.hold_x = hold_x;
4027 engine_snapshot_mm.hold_y = hold_y;
4028 engine_snapshot_mm.pacman_nr = pacman_nr;
4030 engine_snapshot_mm.rotate_delay = rotate_delay;
4031 engine_snapshot_mm.pacman_delay = pacman_delay;
4032 engine_snapshot_mm.energy_delay = energy_delay;
4033 engine_snapshot_mm.overload_delay = overload_delay;
4036 void LoadEngineSnapshotValues_MM(void)
4040 // stored engine snapshot buffers already restored at this point
4042 game_mm = engine_snapshot_mm.game_mm;
4043 laser = engine_snapshot_mm.laser;
4045 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4047 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4049 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4050 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4051 Box[x][y] = engine_snapshot_mm.Box[x][y];
4052 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4056 LX = engine_snapshot_mm.LX;
4057 LY = engine_snapshot_mm.LY;
4058 XS = engine_snapshot_mm.XS;
4059 YS = engine_snapshot_mm.YS;
4060 ELX = engine_snapshot_mm.ELX;
4061 ELY = engine_snapshot_mm.ELY;
4062 CT = engine_snapshot_mm.CT;
4063 Ct = engine_snapshot_mm.Ct;
4065 last_LX = engine_snapshot_mm.last_LX;
4066 last_LY = engine_snapshot_mm.last_LY;
4067 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4068 hold_x = engine_snapshot_mm.hold_x;
4069 hold_y = engine_snapshot_mm.hold_y;
4070 pacman_nr = engine_snapshot_mm.pacman_nr;
4072 rotate_delay = engine_snapshot_mm.rotate_delay;
4073 pacman_delay = engine_snapshot_mm.pacman_delay;
4074 energy_delay = engine_snapshot_mm.energy_delay;
4075 overload_delay = engine_snapshot_mm.overload_delay;
4077 RedrawPlayfield_MM();
4080 static int getAngleFromTouchDelta(int dx, int dy, int base)
4082 double pi = 3.141592653;
4083 double rad = atan2((double)-dy, (double)dx);
4084 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4085 double deg = rad2 * 180.0 / pi;
4087 return (int)(deg * base / 360.0 + 0.5) % base;
4090 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4092 // calculate start (source) position to be at the middle of the tile
4093 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4094 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4095 int dx = dst_mx - src_mx;
4096 int dy = dst_my - src_my;
4105 if (!IN_LEV_FIELD(x, y))
4108 element = Tile[x][y];
4110 if (!IS_MCDUFFIN(element) &&
4111 !IS_MIRROR(element) &&
4112 !IS_BEAMER(element) &&
4113 !IS_POLAR(element) &&
4114 !IS_POLAR_CROSS(element) &&
4115 !IS_DF_MIRROR(element))
4118 angle_old = get_element_angle(element);
4120 if (IS_MCDUFFIN(element))
4122 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4123 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4124 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4125 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4128 else if (IS_MIRROR(element) ||
4129 IS_DF_MIRROR(element))
4131 for (i = 0; i < laser.num_damages; i++)
4133 if (laser.damage[i].x == x &&
4134 laser.damage[i].y == y &&
4135 ObjHit(x, y, HIT_POS_CENTER))
4137 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4138 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4145 if (angle_new == -1)
4147 if (IS_MIRROR(element) ||
4148 IS_DF_MIRROR(element) ||
4152 if (IS_POLAR_CROSS(element))
4155 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4158 button = (angle_new == angle_old ? 0 :
4159 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4160 MB_LEFTBUTTON : MB_RIGHTBUTTON);