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 DeactivateLaserTargetElement();
953 laser.overloaded = FALSE;
954 laser.stops_inside_element = FALSE;
956 DrawLaser(0, DL_LASER_ENABLED);
959 Debug("game:mm:ScanLaser",
960 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
968 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
971 laser.overloaded = TRUE;
976 hit_mask = ScanPixel();
979 Debug("game:mm:ScanLaser",
980 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
984 // hit something -- check out what it was
985 ELX = (LX + XS) / TILEX;
986 ELY = (LY + YS) / TILEY;
989 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
990 hit_mask, LX, LY, ELX, ELY);
993 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
996 laser.dest_element = element;
1001 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
1003 /* we have hit the top-right and bottom-left element --
1004 choose the bottom-left one */
1005 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1006 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1007 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1008 ELX = (LX - 2) / TILEX;
1009 ELY = (LY + 2) / TILEY;
1012 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1014 /* we have hit the top-left and bottom-right element --
1015 choose the top-left one */
1016 // !!! SEE ABOVE !!!
1017 ELX = (LX - 2) / TILEX;
1018 ELY = (LY - 2) / TILEY;
1022 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1023 hit_mask, LX, LY, ELX, ELY);
1026 element = Tile[ELX][ELY];
1027 laser.dest_element = element;
1030 Debug("game:mm:ScanLaser",
1031 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1034 LX % TILEX, LY % TILEY,
1039 if (!IN_LEV_FIELD(ELX, ELY))
1040 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1044 if (element == EL_EMPTY)
1046 if (!HitOnlyAnEdge(hit_mask))
1049 else if (element == EL_FUSE_ON)
1051 if (HitPolarizer(element, hit_mask))
1054 else if (IS_GRID(element) || IS_DF_GRID(element))
1056 if (HitPolarizer(element, hit_mask))
1059 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1060 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1062 if (HitBlock(element, hit_mask))
1069 else if (IS_MCDUFFIN(element))
1071 if (HitLaserSource(element, hit_mask))
1074 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1075 IS_RECEIVER(element))
1077 if (HitLaserDestination(element, hit_mask))
1080 else if (IS_WALL(element))
1082 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1084 if (HitReflectingWalls(element, hit_mask))
1089 if (HitAbsorbingWalls(element, hit_mask))
1095 if (HitElement(element, hit_mask))
1100 DrawLaser(rf - 1, DL_LASER_ENABLED);
1101 rf = laser.num_edges;
1105 if (laser.dest_element != Tile[ELX][ELY])
1107 Debug("game:mm:ScanLaser",
1108 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1109 laser.dest_element, Tile[ELX][ELY]);
1113 if (!end && !laser.stops_inside_element && !StepBehind())
1116 Debug("game:mm:ScanLaser", "Go one step back");
1122 AddLaserEdge(LX, LY);
1126 DrawLaser(rf - 1, DL_LASER_ENABLED);
1128 Ct = CT = FrameCounter;
1131 if (!IN_LEV_FIELD(ELX, ELY))
1132 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1136 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1142 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1143 start_edge, num_edges, mode);
1148 Warn("DrawLaserExt: start_edge < 0");
1155 Warn("DrawLaserExt: num_edges < 0");
1161 if (mode == DL_LASER_DISABLED)
1163 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1167 // now draw the laser to the backbuffer and (if enabled) to the screen
1168 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1170 redraw_mask |= REDRAW_FIELD;
1172 if (mode == DL_LASER_ENABLED)
1175 // after the laser was deleted, the "damaged" graphics must be restored
1176 if (laser.num_damages)
1178 int damage_start = 0;
1181 // determine the starting edge, from which graphics need to be restored
1184 for (i = 0; i < laser.num_damages; i++)
1186 if (laser.damage[i].edge == start_edge + 1)
1195 // restore graphics from this starting edge to the end of damage list
1196 for (i = damage_start; i < laser.num_damages; i++)
1198 int lx = laser.damage[i].x;
1199 int ly = laser.damage[i].y;
1200 int element = Tile[lx][ly];
1202 if (Hit[lx][ly] == laser.damage[i].edge)
1203 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1206 if (Box[lx][ly] == laser.damage[i].edge)
1209 if (IS_DRAWABLE(element))
1210 DrawField_MM(lx, ly);
1213 elx = laser.damage[damage_start].x;
1214 ely = laser.damage[damage_start].y;
1215 element = Tile[elx][ely];
1218 if (IS_BEAMER(element))
1222 for (i = 0; i < laser.num_beamers; i++)
1223 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1225 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1226 mode, elx, ely, Hit[elx][ely], start_edge);
1227 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1228 get_element_angle(element), laser.damage[damage_start].angle);
1232 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1233 laser.num_beamers > 0 &&
1234 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1236 // element is outgoing beamer
1237 laser.num_damages = damage_start + 1;
1239 if (IS_BEAMER(element))
1240 laser.current_angle = get_element_angle(element);
1244 // element is incoming beamer or other element
1245 laser.num_damages = damage_start;
1246 laser.current_angle = laser.damage[laser.num_damages].angle;
1251 // no damages but McDuffin himself (who needs to be redrawn anyway)
1253 elx = laser.start_edge.x;
1254 ely = laser.start_edge.y;
1255 element = Tile[elx][ely];
1258 laser.num_edges = start_edge + 1;
1259 if (start_edge == 0)
1260 laser.current_angle = laser.start_angle;
1262 LX = laser.edge[start_edge].x - cSX2;
1263 LY = laser.edge[start_edge].y - cSY2;
1264 XS = 2 * Step[laser.current_angle].x;
1265 YS = 2 * Step[laser.current_angle].y;
1268 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1274 if (IS_BEAMER(element) ||
1275 IS_FIBRE_OPTIC(element) ||
1276 IS_PACMAN(element) ||
1277 IS_POLAR(element) ||
1278 IS_POLAR_CROSS(element) ||
1279 element == EL_FUSE_ON)
1284 Debug("game:mm:DrawLaserExt", "element == %d", element);
1287 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1288 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1292 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1293 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1294 (laser.num_beamers == 0 ||
1295 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1297 // element is incoming beamer or other element
1298 step_size = -step_size;
1303 if (IS_BEAMER(element))
1304 Debug("game:mm:DrawLaserExt",
1305 "start_edge == %d, laser.beamer_edge == %d",
1306 start_edge, laser.beamer_edge);
1309 LX += step_size * XS;
1310 LY += step_size * YS;
1312 else if (element != EL_EMPTY)
1321 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1326 void DrawLaser(int start_edge, int mode)
1328 if (laser.num_edges - start_edge < 0)
1330 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1335 // check if laser is interrupted by beamer element
1336 if (laser.num_beamers > 0 &&
1337 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1339 if (mode == DL_LASER_ENABLED)
1342 int tmp_start_edge = start_edge;
1344 // draw laser segments forward from the start to the last beamer
1345 for (i = 0; i < laser.num_beamers; i++)
1347 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1349 if (tmp_num_edges <= 0)
1353 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1354 i, laser.beamer_edge[i], tmp_start_edge);
1357 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1359 tmp_start_edge = laser.beamer_edge[i];
1362 // draw last segment from last beamer to the end
1363 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1369 int last_num_edges = laser.num_edges;
1370 int num_beamers = laser.num_beamers;
1372 // delete laser segments backward from the end to the first beamer
1373 for (i = num_beamers - 1; i >= 0; i--)
1375 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1377 if (laser.beamer_edge[i] - start_edge <= 0)
1380 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1382 last_num_edges = laser.beamer_edge[i];
1383 laser.num_beamers--;
1387 if (last_num_edges - start_edge <= 0)
1388 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1389 last_num_edges, start_edge);
1392 // special case when rotating first beamer: delete laser edge on beamer
1393 // (but do not start scanning on previous edge to prevent mirror sound)
1394 if (last_num_edges - start_edge == 1 && start_edge > 0)
1395 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1397 // delete first segment from start to the first beamer
1398 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1403 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1406 game_mm.laser_enabled = mode;
1409 void DrawLaser_MM(void)
1411 DrawLaser(0, game_mm.laser_enabled);
1414 boolean HitElement(int element, int hit_mask)
1416 if (HitOnlyAnEdge(hit_mask))
1419 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1420 element = MovingOrBlocked2Element_MM(ELX, ELY);
1423 Debug("game:mm:HitElement", "(1): element == %d", element);
1427 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1428 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1431 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1435 AddDamagedField(ELX, ELY);
1437 // this is more precise: check if laser would go through the center
1438 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1440 // skip the whole element before continuing the scan
1446 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1448 if (LX/TILEX > ELX || LY/TILEY > ELY)
1450 /* skipping scan positions to the right and down skips one scan
1451 position too much, because this is only the top left scan position
1452 of totally four scan positions (plus one to the right, one to the
1453 bottom and one to the bottom right) */
1463 Debug("game:mm:HitElement", "(2): element == %d", element);
1466 if (LX + 5 * XS < 0 ||
1476 Debug("game:mm:HitElement", "(3): element == %d", element);
1479 if (IS_POLAR(element) &&
1480 ((element - EL_POLAR_START) % 2 ||
1481 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1483 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1485 laser.num_damages--;
1490 if (IS_POLAR_CROSS(element) &&
1491 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1493 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1495 laser.num_damages--;
1500 if (!IS_BEAMER(element) &&
1501 !IS_FIBRE_OPTIC(element) &&
1502 !IS_GRID_WOOD(element) &&
1503 element != EL_FUEL_EMPTY)
1506 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1507 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1509 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1512 LX = ELX * TILEX + 14;
1513 LY = ELY * TILEY + 14;
1515 AddLaserEdge(LX, LY);
1518 if (IS_MIRROR(element) ||
1519 IS_MIRROR_FIXED(element) ||
1520 IS_POLAR(element) ||
1521 IS_POLAR_CROSS(element) ||
1522 IS_DF_MIRROR(element) ||
1523 IS_DF_MIRROR_AUTO(element) ||
1524 element == EL_PRISM ||
1525 element == EL_REFRACTOR)
1527 int current_angle = laser.current_angle;
1530 laser.num_damages--;
1532 AddDamagedField(ELX, ELY);
1534 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1537 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1539 if (IS_MIRROR(element) ||
1540 IS_MIRROR_FIXED(element) ||
1541 IS_DF_MIRROR(element) ||
1542 IS_DF_MIRROR_AUTO(element))
1543 laser.current_angle = get_mirrored_angle(laser.current_angle,
1544 get_element_angle(element));
1546 if (element == EL_PRISM || element == EL_REFRACTOR)
1547 laser.current_angle = RND(16);
1549 XS = 2 * Step[laser.current_angle].x;
1550 YS = 2 * Step[laser.current_angle].y;
1552 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1557 LX += step_size * XS;
1558 LY += step_size * YS;
1560 // draw sparkles on mirror
1561 if ((IS_MIRROR(element) ||
1562 IS_MIRROR_FIXED(element) ||
1563 element == EL_PRISM) &&
1564 current_angle != laser.current_angle)
1566 MovDelay[ELX][ELY] = 11; // start animation
1569 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1570 current_angle != laser.current_angle)
1571 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1574 (get_opposite_angle(laser.current_angle) ==
1575 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1577 return (laser.overloaded ? TRUE : FALSE);
1580 if (element == EL_FUEL_FULL)
1582 laser.stops_inside_element = TRUE;
1587 if (element == EL_BOMB || element == EL_MINE)
1589 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1591 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1593 laser.dest_element_last = Tile[ELX][ELY];
1594 laser.dest_element_last_x = ELX;
1595 laser.dest_element_last_y = ELY;
1597 if (element == EL_MINE)
1598 laser.overloaded = TRUE;
1601 if (element == EL_KETTLE ||
1602 element == EL_CELL ||
1603 element == EL_KEY ||
1604 element == EL_LIGHTBALL ||
1605 element == EL_PACMAN ||
1608 if (!IS_PACMAN(element))
1611 if (element == EL_PACMAN)
1614 if (element == EL_KETTLE || element == EL_CELL)
1616 if (game_mm.kettles_still_needed > 0)
1617 game_mm.kettles_still_needed--;
1619 game.snapshot.collected_item = TRUE;
1621 if (game_mm.kettles_still_needed == 0)
1625 DrawLaser(0, DL_LASER_ENABLED);
1628 else if (element == EL_KEY)
1632 else if (IS_PACMAN(element))
1634 DeletePacMan(ELX, ELY);
1637 RaiseScoreElement_MM(element);
1642 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1644 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1646 DrawLaser(0, DL_LASER_ENABLED);
1648 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1650 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1651 game_mm.lights_still_needed--;
1655 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1656 game_mm.lights_still_needed++;
1659 DrawField_MM(ELX, ELY);
1660 DrawLaser(0, DL_LASER_ENABLED);
1665 laser.stops_inside_element = TRUE;
1671 Debug("game:mm:HitElement", "(4): element == %d", element);
1674 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1675 laser.num_beamers < MAX_NUM_BEAMERS &&
1676 laser.beamer[BEAMER_NR(element)][1].num)
1678 int beamer_angle = get_element_angle(element);
1679 int beamer_nr = BEAMER_NR(element);
1683 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1686 laser.num_damages--;
1688 if (IS_FIBRE_OPTIC(element) ||
1689 laser.current_angle == get_opposite_angle(beamer_angle))
1693 LX = ELX * TILEX + 14;
1694 LY = ELY * TILEY + 14;
1696 AddLaserEdge(LX, LY);
1697 AddDamagedField(ELX, ELY);
1699 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1702 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1704 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1705 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1706 ELX = laser.beamer[beamer_nr][pos].x;
1707 ELY = laser.beamer[beamer_nr][pos].y;
1708 LX = ELX * TILEX + 14;
1709 LY = ELY * TILEY + 14;
1711 if (IS_BEAMER(element))
1713 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1714 XS = 2 * Step[laser.current_angle].x;
1715 YS = 2 * Step[laser.current_angle].y;
1718 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1720 AddLaserEdge(LX, LY);
1721 AddDamagedField(ELX, ELY);
1723 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1726 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1728 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1733 LX += step_size * XS;
1734 LY += step_size * YS;
1736 laser.num_beamers++;
1745 boolean HitOnlyAnEdge(int hit_mask)
1747 // check if the laser hit only the edge of an element and, if so, go on
1750 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1754 if ((hit_mask == HIT_MASK_TOPLEFT ||
1755 hit_mask == HIT_MASK_TOPRIGHT ||
1756 hit_mask == HIT_MASK_BOTTOMLEFT ||
1757 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1758 laser.current_angle % 4) // angle is not 90°
1762 if (hit_mask == HIT_MASK_TOPLEFT)
1767 else if (hit_mask == HIT_MASK_TOPRIGHT)
1772 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1777 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1783 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1789 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1796 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1802 boolean HitPolarizer(int element, int hit_mask)
1804 if (HitOnlyAnEdge(hit_mask))
1807 if (IS_DF_GRID(element))
1809 int grid_angle = get_element_angle(element);
1812 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1813 grid_angle, laser.current_angle);
1816 AddLaserEdge(LX, LY);
1817 AddDamagedField(ELX, ELY);
1820 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1822 if (laser.current_angle == grid_angle ||
1823 laser.current_angle == get_opposite_angle(grid_angle))
1825 // skip the whole element before continuing the scan
1831 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1833 if (LX/TILEX > ELX || LY/TILEY > ELY)
1835 /* skipping scan positions to the right and down skips one scan
1836 position too much, because this is only the top left scan position
1837 of totally four scan positions (plus one to the right, one to the
1838 bottom and one to the bottom right) */
1844 AddLaserEdge(LX, LY);
1850 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1852 LX / TILEX, LY / TILEY,
1853 LX % TILEX, LY % TILEY);
1858 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1860 return HitReflectingWalls(element, hit_mask);
1864 return HitAbsorbingWalls(element, hit_mask);
1867 else if (IS_GRID_STEEL(element))
1869 return HitReflectingWalls(element, hit_mask);
1871 else // IS_GRID_WOOD
1873 return HitAbsorbingWalls(element, hit_mask);
1879 boolean HitBlock(int element, int hit_mask)
1881 boolean check = FALSE;
1883 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1884 game_mm.num_keys == 0)
1887 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1890 int ex = ELX * TILEX + 14;
1891 int ey = ELY * TILEY + 14;
1895 for (i = 1; i < 32; i++)
1900 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1905 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1906 return HitAbsorbingWalls(element, hit_mask);
1910 AddLaserEdge(LX - XS, LY - YS);
1911 AddDamagedField(ELX, ELY);
1914 Box[ELX][ELY] = laser.num_edges;
1916 return HitReflectingWalls(element, hit_mask);
1919 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1921 int xs = XS / 2, ys = YS / 2;
1922 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1923 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1925 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1926 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1928 laser.overloaded = (element == EL_GATE_STONE);
1933 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1934 (hit_mask == HIT_MASK_TOP ||
1935 hit_mask == HIT_MASK_LEFT ||
1936 hit_mask == HIT_MASK_RIGHT ||
1937 hit_mask == HIT_MASK_BOTTOM))
1938 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1939 hit_mask == HIT_MASK_BOTTOM),
1940 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1941 hit_mask == HIT_MASK_RIGHT));
1942 AddLaserEdge(LX, LY);
1948 if (element == EL_GATE_STONE && Box[ELX][ELY])
1950 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1962 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1964 int xs = XS / 2, ys = YS / 2;
1965 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1966 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1968 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1969 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1971 laser.overloaded = (element == EL_BLOCK_STONE);
1976 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1977 (hit_mask == HIT_MASK_TOP ||
1978 hit_mask == HIT_MASK_LEFT ||
1979 hit_mask == HIT_MASK_RIGHT ||
1980 hit_mask == HIT_MASK_BOTTOM))
1981 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1982 hit_mask == HIT_MASK_BOTTOM),
1983 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1984 hit_mask == HIT_MASK_RIGHT));
1985 AddDamagedField(ELX, ELY);
1987 LX = ELX * TILEX + 14;
1988 LY = ELY * TILEY + 14;
1990 AddLaserEdge(LX, LY);
1992 laser.stops_inside_element = TRUE;
2000 boolean HitLaserSource(int element, int hit_mask)
2002 if (HitOnlyAnEdge(hit_mask))
2005 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2007 laser.overloaded = TRUE;
2012 boolean HitLaserDestination(int element, int hit_mask)
2014 if (HitOnlyAnEdge(hit_mask))
2017 if (element != EL_EXIT_OPEN &&
2018 !(IS_RECEIVER(element) &&
2019 game_mm.kettles_still_needed == 0 &&
2020 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2022 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2027 if (IS_RECEIVER(element) ||
2028 (IS_22_5_ANGLE(laser.current_angle) &&
2029 (ELX != (LX + 6 * XS) / TILEX ||
2030 ELY != (LY + 6 * YS) / TILEY ||
2039 LX = ELX * TILEX + 14;
2040 LY = ELY * TILEY + 14;
2042 laser.stops_inside_element = TRUE;
2045 AddLaserEdge(LX, LY);
2046 AddDamagedField(ELX, ELY);
2048 if (game_mm.lights_still_needed == 0)
2050 game_mm.level_solved = TRUE;
2052 SetTileCursorActive(FALSE);
2058 boolean HitReflectingWalls(int element, int hit_mask)
2060 // check if laser hits side of a wall with an angle that is not 90°
2061 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2062 hit_mask == HIT_MASK_LEFT ||
2063 hit_mask == HIT_MASK_RIGHT ||
2064 hit_mask == HIT_MASK_BOTTOM))
2066 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2071 if (!IS_DF_GRID(element))
2072 AddLaserEdge(LX, LY);
2074 // check if laser hits wall with an angle of 45°
2075 if (!IS_22_5_ANGLE(laser.current_angle))
2077 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2080 laser.current_angle = get_mirrored_angle(laser.current_angle,
2083 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2086 laser.current_angle = get_mirrored_angle(laser.current_angle,
2090 AddLaserEdge(LX, LY);
2092 XS = 2 * Step[laser.current_angle].x;
2093 YS = 2 * Step[laser.current_angle].y;
2097 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2099 laser.current_angle = get_mirrored_angle(laser.current_angle,
2104 if (!IS_DF_GRID(element))
2105 AddLaserEdge(LX, LY);
2110 if (!IS_DF_GRID(element))
2111 AddLaserEdge(LX, LY + YS / 2);
2114 if (!IS_DF_GRID(element))
2115 AddLaserEdge(LX, LY);
2118 YS = 2 * Step[laser.current_angle].y;
2122 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2124 laser.current_angle = get_mirrored_angle(laser.current_angle,
2129 if (!IS_DF_GRID(element))
2130 AddLaserEdge(LX, LY);
2135 if (!IS_DF_GRID(element))
2136 AddLaserEdge(LX + XS / 2, LY);
2139 if (!IS_DF_GRID(element))
2140 AddLaserEdge(LX, LY);
2143 XS = 2 * Step[laser.current_angle].x;
2149 // reflection at the edge of reflecting DF style wall
2150 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2152 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2153 hit_mask == HIT_MASK_TOPRIGHT) ||
2154 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2155 hit_mask == HIT_MASK_TOPLEFT) ||
2156 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2157 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2158 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2159 hit_mask == HIT_MASK_BOTTOMRIGHT))
2162 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2163 ANG_MIRROR_135 : ANG_MIRROR_45);
2165 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2167 AddDamagedField(ELX, ELY);
2168 AddLaserEdge(LX, LY);
2170 laser.current_angle = get_mirrored_angle(laser.current_angle,
2178 AddLaserEdge(LX, LY);
2184 // reflection inside an edge of reflecting DF style wall
2185 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2187 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2188 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2189 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2190 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2191 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2192 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2193 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2194 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2197 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2198 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2199 ANG_MIRROR_135 : ANG_MIRROR_45);
2201 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2204 AddDamagedField(ELX, ELY);
2207 AddLaserEdge(LX - XS, LY - YS);
2208 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2209 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2211 laser.current_angle = get_mirrored_angle(laser.current_angle,
2219 AddLaserEdge(LX, LY);
2225 // check if laser hits DF style wall with an angle of 90°
2226 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2228 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2229 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2230 (IS_VERT_ANGLE(laser.current_angle) &&
2231 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2233 // laser at last step touched nothing or the same side of the wall
2234 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2236 AddDamagedField(ELX, ELY);
2243 last_hit_mask = hit_mask;
2250 if (!HitOnlyAnEdge(hit_mask))
2252 laser.overloaded = TRUE;
2260 boolean HitAbsorbingWalls(int element, int hit_mask)
2262 if (HitOnlyAnEdge(hit_mask))
2266 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2268 AddLaserEdge(LX - XS, LY - YS);
2275 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2277 AddLaserEdge(LX - XS, LY - YS);
2283 if (IS_WALL_WOOD(element) ||
2284 IS_DF_WALL_WOOD(element) ||
2285 IS_GRID_WOOD(element) ||
2286 IS_GRID_WOOD_FIXED(element) ||
2287 IS_GRID_WOOD_AUTO(element) ||
2288 element == EL_FUSE_ON ||
2289 element == EL_BLOCK_WOOD ||
2290 element == EL_GATE_WOOD)
2292 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2297 if (IS_WALL_ICE(element))
2301 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2302 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2304 // check if laser hits wall with an angle of 90°
2305 if (IS_90_ANGLE(laser.current_angle))
2306 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2308 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2312 for (i = 0; i < 4; i++)
2314 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2315 mask = 15 - (8 >> i);
2316 else if (ABS(XS) == 4 &&
2318 (XS > 0) == (i % 2) &&
2319 (YS < 0) == (i / 2))
2320 mask = 3 + (i / 2) * 9;
2321 else if (ABS(YS) == 4 &&
2323 (XS < 0) == (i % 2) &&
2324 (YS > 0) == (i / 2))
2325 mask = 5 + (i % 2) * 5;
2329 laser.wall_mask = mask;
2331 else if (IS_WALL_AMOEBA(element))
2333 int elx = (LX - 2 * XS) / TILEX;
2334 int ely = (LY - 2 * YS) / TILEY;
2335 int element2 = Tile[elx][ely];
2338 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2340 laser.dest_element = EL_EMPTY;
2348 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2349 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2351 if (IS_90_ANGLE(laser.current_angle))
2352 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2354 laser.dest_element = element2 | EL_WALL_AMOEBA;
2356 laser.wall_mask = mask;
2362 static void OpenExit(int x, int y)
2366 if (!MovDelay[x][y]) // next animation frame
2367 MovDelay[x][y] = 4 * delay;
2369 if (MovDelay[x][y]) // wait some time before next frame
2374 phase = MovDelay[x][y] / delay;
2376 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2377 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2379 if (!MovDelay[x][y])
2381 Tile[x][y] = EL_EXIT_OPEN;
2387 static void OpenSurpriseBall(int x, int y)
2391 if (!MovDelay[x][y]) // next animation frame
2392 MovDelay[x][y] = 50 * delay;
2394 if (MovDelay[x][y]) // wait some time before next frame
2398 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2401 int graphic = el2gfx(Store[x][y]);
2403 int dx = RND(26), dy = RND(26);
2405 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2407 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2408 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2410 MarkTileDirty(x, y);
2413 if (!MovDelay[x][y])
2415 Tile[x][y] = Store[x][y];
2416 Store[x][y] = Store2[x][y] = 0;
2425 static void MeltIce(int x, int y)
2430 if (!MovDelay[x][y]) // next animation frame
2431 MovDelay[x][y] = frames * delay;
2433 if (MovDelay[x][y]) // wait some time before next frame
2436 int wall_mask = Store2[x][y];
2437 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2440 phase = frames - MovDelay[x][y] / delay - 1;
2442 if (!MovDelay[x][y])
2446 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2447 Store[x][y] = Store2[x][y] = 0;
2449 DrawWalls_MM(x, y, Tile[x][y]);
2451 if (Tile[x][y] == EL_WALL_ICE)
2452 Tile[x][y] = EL_EMPTY;
2454 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2455 if (laser.damage[i].is_mirror)
2459 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2461 DrawLaser(0, DL_LASER_DISABLED);
2465 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2467 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2469 laser.redraw = TRUE;
2474 static void GrowAmoeba(int x, int y)
2479 if (!MovDelay[x][y]) // next animation frame
2480 MovDelay[x][y] = frames * delay;
2482 if (MovDelay[x][y]) // wait some time before next frame
2485 int wall_mask = Store2[x][y];
2486 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2489 phase = MovDelay[x][y] / delay;
2491 if (!MovDelay[x][y])
2493 Tile[x][y] = real_element;
2494 Store[x][y] = Store2[x][y] = 0;
2496 DrawWalls_MM(x, y, Tile[x][y]);
2497 DrawLaser(0, DL_LASER_ENABLED);
2499 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2501 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2506 static void DrawFieldAnimated_MM(int x, int y)
2508 int element = Tile[x][y];
2510 if (IS_BLOCKED(x, y))
2515 if (IS_MIRROR(element) ||
2516 IS_MIRROR_FIXED(element) ||
2517 element == EL_PRISM)
2519 if (MovDelay[x][y] != 0) // wait some time before next frame
2523 if (MovDelay[x][y] != 0)
2525 int graphic = IMG_TWINKLE_WHITE;
2526 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2528 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2533 laser.redraw = TRUE;
2536 static void Explode_MM(int x, int y, int phase, int mode)
2538 int num_phase = 9, delay = 2;
2539 int last_phase = num_phase * delay;
2540 int half_phase = (num_phase / 2) * delay;
2542 laser.redraw = TRUE;
2544 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2546 int center_element = Tile[x][y];
2548 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2550 // put moving element to center field (and let it explode there)
2551 center_element = MovingOrBlocked2Element_MM(x, y);
2552 RemoveMovingField_MM(x, y);
2554 Tile[x][y] = center_element;
2557 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2558 Store[x][y] = center_element;
2560 Store[x][y] = EL_EMPTY;
2562 Store2[x][y] = mode;
2564 Tile[x][y] = EL_EXPLODING_OPAQUE;
2565 GfxElement[x][y] = center_element;
2567 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2569 ExplodePhase[x][y] = 1;
2575 GfxFrame[x][y] = 0; // restart explosion animation
2577 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2579 if (phase == half_phase)
2581 Tile[x][y] = EL_EXPLODING_TRANSP;
2583 if (x == ELX && y == ELY)
2587 if (phase == last_phase)
2589 if (Store[x][y] == EL_BOMB_ACTIVE)
2591 DrawLaser(0, DL_LASER_DISABLED);
2594 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2595 Store[x][y] = EL_EMPTY;
2597 GameOver_MM(GAME_OVER_DELAYED);
2599 laser.overloaded = FALSE;
2601 else if (IS_MCDUFFIN(Store[x][y]))
2603 Store[x][y] = EL_EMPTY;
2605 GameOver_MM(GAME_OVER_BOMB);
2608 Tile[x][y] = Store[x][y];
2609 Store[x][y] = Store2[x][y] = 0;
2610 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2615 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2617 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2618 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2620 DrawGraphicAnimation_MM(x, y, graphic, frame);
2622 MarkTileDirty(x, y);
2626 static void Bang_MM(int x, int y)
2628 int element = Tile[x][y];
2631 DrawLaser(0, DL_LASER_ENABLED);
2634 if (IS_PACMAN(element))
2635 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2636 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2637 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2638 else if (element == EL_KEY)
2639 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2641 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2643 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2646 void TurnRound(int x, int y)
2658 { 0, 0 }, { 0, 0 }, { 0, 0 },
2663 int left, right, back;
2667 { MV_DOWN, MV_UP, MV_RIGHT },
2668 { MV_UP, MV_DOWN, MV_LEFT },
2670 { MV_LEFT, MV_RIGHT, MV_DOWN },
2671 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2672 { MV_RIGHT, MV_LEFT, MV_UP }
2675 int element = Tile[x][y];
2676 int old_move_dir = MovDir[x][y];
2677 int right_dir = turn[old_move_dir].right;
2678 int back_dir = turn[old_move_dir].back;
2679 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2680 int right_x = x + right_dx, right_y = y + right_dy;
2682 if (element == EL_PACMAN)
2684 boolean can_turn_right = FALSE;
2686 if (IN_LEV_FIELD(right_x, right_y) &&
2687 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2688 can_turn_right = TRUE;
2691 MovDir[x][y] = right_dir;
2693 MovDir[x][y] = back_dir;
2699 static void StartMoving_MM(int x, int y)
2701 int element = Tile[x][y];
2706 if (CAN_MOVE(element))
2710 if (MovDelay[x][y]) // wait some time before next movement
2718 // now make next step
2720 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2722 if (element == EL_PACMAN &&
2723 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2724 !ObjHit(newx, newy, HIT_POS_CENTER))
2726 Store[newx][newy] = Tile[newx][newy];
2727 Tile[newx][newy] = EL_EMPTY;
2729 DrawField_MM(newx, newy);
2731 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2732 ObjHit(newx, newy, HIT_POS_CENTER))
2734 // object was running against a wall
2741 InitMovingField_MM(x, y, MovDir[x][y]);
2745 ContinueMoving_MM(x, y);
2748 static void ContinueMoving_MM(int x, int y)
2750 int element = Tile[x][y];
2751 int direction = MovDir[x][y];
2752 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2753 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2754 int horiz_move = (dx!=0);
2755 int newx = x + dx, newy = y + dy;
2756 int step = (horiz_move ? dx : dy) * TILEX / 8;
2758 MovPos[x][y] += step;
2760 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2762 Tile[x][y] = EL_EMPTY;
2763 Tile[newx][newy] = element;
2765 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2766 MovDelay[newx][newy] = 0;
2768 if (!CAN_MOVE(element))
2769 MovDir[newx][newy] = 0;
2772 DrawField_MM(newx, newy);
2774 Stop[newx][newy] = TRUE;
2776 if (element == EL_PACMAN)
2778 if (Store[newx][newy] == EL_BOMB)
2779 Bang_MM(newx, newy);
2781 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2782 (LX + 2 * XS) / TILEX == newx &&
2783 (LY + 2 * YS) / TILEY == newy)
2790 else // still moving on
2795 laser.redraw = TRUE;
2798 boolean ClickElement(int x, int y, int button)
2800 static DelayCounter click_delay = { CLICK_DELAY };
2801 static boolean new_button = TRUE;
2802 boolean element_clicked = FALSE;
2807 // initialize static variables
2808 click_delay.count = 0;
2809 click_delay.value = CLICK_DELAY;
2815 // do not rotate objects hit by the laser after the game was solved
2816 if (game_mm.level_solved && Hit[x][y])
2819 if (button == MB_RELEASED)
2822 click_delay.value = CLICK_DELAY;
2824 // release eventually hold auto-rotating mirror
2825 RotateMirror(x, y, MB_RELEASED);
2830 if (!FrameReached(&click_delay) && !new_button)
2833 if (button == MB_MIDDLEBUTTON) // middle button has no function
2836 if (!IN_LEV_FIELD(x, y))
2839 if (Tile[x][y] == EL_EMPTY)
2842 element = Tile[x][y];
2844 if (IS_MIRROR(element) ||
2845 IS_BEAMER(element) ||
2846 IS_POLAR(element) ||
2847 IS_POLAR_CROSS(element) ||
2848 IS_DF_MIRROR(element) ||
2849 IS_DF_MIRROR_AUTO(element))
2851 RotateMirror(x, y, button);
2853 element_clicked = TRUE;
2855 else if (IS_MCDUFFIN(element))
2857 if (!laser.fuse_off)
2859 DrawLaser(0, DL_LASER_DISABLED);
2866 element = get_rotated_element(element, BUTTON_ROTATION(button));
2867 laser.start_angle = get_element_angle(element);
2871 Tile[x][y] = element;
2878 if (!laser.fuse_off)
2881 element_clicked = TRUE;
2883 else if (element == EL_FUSE_ON && laser.fuse_off)
2885 if (x != laser.fuse_x || y != laser.fuse_y)
2888 laser.fuse_off = FALSE;
2889 laser.fuse_x = laser.fuse_y = -1;
2891 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2894 element_clicked = TRUE;
2896 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2898 laser.fuse_off = TRUE;
2901 laser.overloaded = FALSE;
2903 DrawLaser(0, DL_LASER_DISABLED);
2904 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2906 element_clicked = TRUE;
2908 else if (element == EL_LIGHTBALL)
2911 RaiseScoreElement_MM(element);
2912 DrawLaser(0, DL_LASER_ENABLED);
2914 element_clicked = TRUE;
2917 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2920 return element_clicked;
2923 void RotateMirror(int x, int y, int button)
2925 if (button == MB_RELEASED)
2927 // release eventually hold auto-rotating mirror
2934 if (IS_MIRROR(Tile[x][y]) ||
2935 IS_POLAR_CROSS(Tile[x][y]) ||
2936 IS_POLAR(Tile[x][y]) ||
2937 IS_BEAMER(Tile[x][y]) ||
2938 IS_DF_MIRROR(Tile[x][y]) ||
2939 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2940 IS_GRID_WOOD_AUTO(Tile[x][y]))
2942 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2944 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2946 if (button == MB_LEFTBUTTON)
2948 // left mouse button only for manual adjustment, no auto-rotating;
2949 // freeze mirror for until mouse button released
2953 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2955 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2959 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2961 int edge = Hit[x][y];
2967 DrawLaser(edge - 1, DL_LASER_DISABLED);
2971 else if (ObjHit(x, y, HIT_POS_CENTER))
2973 int edge = Hit[x][y];
2977 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2982 DrawLaser(edge - 1, DL_LASER_DISABLED);
2989 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2994 if ((IS_BEAMER(Tile[x][y]) ||
2995 IS_POLAR(Tile[x][y]) ||
2996 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3000 if (IS_BEAMER(Tile[x][y]))
3003 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3004 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3016 DrawLaser(0, DL_LASER_ENABLED);
3020 static void AutoRotateMirrors(void)
3024 if (!FrameReached(&rotate_delay))
3027 for (x = 0; x < lev_fieldx; x++)
3029 for (y = 0; y < lev_fieldy; y++)
3031 int element = Tile[x][y];
3033 // do not rotate objects hit by the laser after the game was solved
3034 if (game_mm.level_solved && Hit[x][y])
3037 if (IS_DF_MIRROR_AUTO(element) ||
3038 IS_GRID_WOOD_AUTO(element) ||
3039 IS_GRID_STEEL_AUTO(element) ||
3040 element == EL_REFRACTOR)
3041 RotateMirror(x, y, MB_RIGHTBUTTON);
3046 boolean ObjHit(int obx, int oby, int bits)
3053 if (bits & HIT_POS_CENTER)
3055 if (CheckLaserPixel(cSX + obx + 15,
3060 if (bits & HIT_POS_EDGE)
3062 for (i = 0; i < 4; i++)
3063 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3064 cSY + oby + 31 * (i / 2)))
3068 if (bits & HIT_POS_BETWEEN)
3070 for (i = 0; i < 4; i++)
3071 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3072 cSY + 4 + oby + 22 * (i / 2)))
3079 void DeletePacMan(int px, int py)
3085 if (game_mm.num_pacman <= 1)
3087 game_mm.num_pacman = 0;
3091 for (i = 0; i < game_mm.num_pacman; i++)
3092 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3095 game_mm.num_pacman--;
3097 for (j = i; j < game_mm.num_pacman; j++)
3099 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3100 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3101 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3105 void ColorCycling(void)
3107 static int CC, Cc = 0;
3109 static int color, old = 0xF00, new = 0x010, mult = 1;
3110 static unsigned short red, green, blue;
3112 if (color_status == STATIC_COLORS)
3117 if (CC < Cc || CC > Cc + 2)
3121 color = old + new * mult;
3127 if (ABS(mult) == 16)
3137 red = 0x0e00 * ((color & 0xF00) >> 8);
3138 green = 0x0e00 * ((color & 0x0F0) >> 4);
3139 blue = 0x0e00 * ((color & 0x00F));
3140 SetRGB(pen_magicolor[0], red, green, blue);
3142 red = 0x1111 * ((color & 0xF00) >> 8);
3143 green = 0x1111 * ((color & 0x0F0) >> 4);
3144 blue = 0x1111 * ((color & 0x00F));
3145 SetRGB(pen_magicolor[1], red, green, blue);
3149 static void GameActions_MM_Ext(void)
3156 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3159 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3161 element = Tile[x][y];
3163 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3164 StartMoving_MM(x, y);
3165 else if (IS_MOVING(x, y))
3166 ContinueMoving_MM(x, y);
3167 else if (IS_EXPLODING(element))
3168 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3169 else if (element == EL_EXIT_OPENING)
3171 else if (element == EL_GRAY_BALL_OPENING)
3172 OpenSurpriseBall(x, y);
3173 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3175 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3178 DrawFieldAnimated_MM(x, y);
3181 AutoRotateMirrors();
3184 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3186 // redraw after Explode_MM() ...
3188 DrawLaser(0, DL_LASER_ENABLED);
3189 laser.redraw = FALSE;
3194 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3198 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3200 DrawLaser(0, DL_LASER_DISABLED);
3205 // skip all following game actions if game is over
3206 if (game_mm.game_over)
3209 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3213 GameOver_MM(GAME_OVER_NO_ENERGY);
3218 if (FrameReached(&energy_delay))
3220 if (game_mm.energy_left > 0)
3221 game_mm.energy_left--;
3223 // when out of energy, wait another frame to play "out of time" sound
3226 element = laser.dest_element;
3229 if (element != Tile[ELX][ELY])
3231 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3232 element, Tile[ELX][ELY]);
3236 if (!laser.overloaded && laser.overload_value == 0 &&
3237 element != EL_BOMB &&
3238 element != EL_BOMB_ACTIVE &&
3239 element != EL_MINE &&
3240 element != EL_MINE_ACTIVE &&
3241 element != EL_BALL_GRAY &&
3242 element != EL_BLOCK_STONE &&
3243 element != EL_BLOCK_WOOD &&
3244 element != EL_FUSE_ON &&
3245 element != EL_FUEL_FULL &&
3246 !IS_WALL_ICE(element) &&
3247 !IS_WALL_AMOEBA(element))
3250 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3252 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3253 (!laser.overloaded && laser.overload_value > 0)) &&
3254 FrameReached(&overload_delay))
3256 if (laser.overloaded)
3257 laser.overload_value++;
3259 laser.overload_value--;
3261 if (game_mm.cheat_no_overload)
3263 laser.overloaded = FALSE;
3264 laser.overload_value = 0;
3267 game_mm.laser_overload_value = laser.overload_value;
3269 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3271 SetLaserColor(0xFF);
3273 DrawLaser(0, DL_LASER_ENABLED);
3276 if (!laser.overloaded)
3277 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3278 else if (setup.sound_loops)
3279 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3281 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3283 if (laser.overloaded)
3286 BlitBitmap(pix[PIX_DOOR], drawto,
3287 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3288 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3289 - laser.overload_value,
3290 OVERLOAD_XSIZE, laser.overload_value,
3291 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3292 - laser.overload_value);
3294 redraw_mask |= REDRAW_DOOR_1;
3299 BlitBitmap(pix[PIX_DOOR], drawto,
3300 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3301 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3302 DX_OVERLOAD, DY_OVERLOAD);
3304 redraw_mask |= REDRAW_DOOR_1;
3307 if (laser.overload_value == MAX_LASER_OVERLOAD)
3309 UpdateAndDisplayGameControlValues();
3313 GameOver_MM(GAME_OVER_OVERLOADED);
3324 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3326 if (game_mm.cheat_no_explosion)
3331 laser.dest_element = EL_EXPLODING_OPAQUE;
3336 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3338 laser.fuse_off = TRUE;
3342 DrawLaser(0, DL_LASER_DISABLED);
3343 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3346 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3348 if (!Store2[ELX][ELY]) // check if content element not yet determined
3350 int last_anim_random_frame = gfx.anim_random_frame;
3353 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3354 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3356 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3357 native_mm_level.ball_choice_mode, 0,
3358 game_mm.ball_choice_pos);
3360 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3361 gfx.anim_random_frame = last_anim_random_frame;
3363 game_mm.ball_choice_pos++;
3365 int new_element = native_mm_level.ball_content[element_pos];
3367 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3368 Store2[ELX][ELY] = TRUE;
3371 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3373 // !!! CHECK AGAIN: Laser on Polarizer !!!
3376 laser.dest_element_last = Tile[ELX][ELY];
3377 laser.dest_element_last_x = ELX;
3378 laser.dest_element_last_y = ELY;
3388 element = EL_MIRROR_START + RND(16);
3394 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3401 element = (rnd == 0 ? EL_FUSE_ON :
3402 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3403 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3404 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3405 EL_MIRROR_FIXED_START + rnd - 25);
3410 graphic = el2gfx(element);
3412 for (i = 0; i < 50; i++)
3418 BlitBitmap(pix[PIX_BACK], drawto,
3419 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3420 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3421 SX + ELX * TILEX + x,
3422 SY + ELY * TILEY + y);
3424 MarkTileDirty(ELX, ELY);
3427 DrawLaser(0, DL_LASER_ENABLED);
3429 Delay_WithScreenUpdates(50);
3432 Tile[ELX][ELY] = element;
3433 DrawField_MM(ELX, ELY);
3436 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3439 // above stuff: GRAY BALL -> PRISM !!!
3441 LX = ELX * TILEX + 14;
3442 LY = ELY * TILEY + 14;
3443 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3450 laser.num_edges -= 2;
3451 laser.num_damages--;
3455 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3456 if (laser.damage[i].is_mirror)
3460 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3462 DrawLaser(0, DL_LASER_DISABLED);
3464 DrawLaser(0, DL_LASER_DISABLED);
3473 if (IS_WALL_ICE(element) && CT > 50)
3475 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3478 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3479 Store[ELX][ELY] = EL_WALL_ICE;
3480 Store2[ELX][ELY] = laser.wall_mask;
3482 laser.dest_element = Tile[ELX][ELY];
3487 for (i = 0; i < 5; i++)
3493 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3497 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3499 Delay_WithScreenUpdates(100);
3502 if (Tile[ELX][ELY] == EL_WALL_ICE)
3503 Tile[ELX][ELY] = EL_EMPTY;
3507 LX = laser.edge[laser.num_edges].x - cSX2;
3508 LY = laser.edge[laser.num_edges].y - cSY2;
3511 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3512 if (laser.damage[i].is_mirror)
3516 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3518 DrawLaser(0, DL_LASER_DISABLED);
3525 if (IS_WALL_AMOEBA(element) && CT > 60)
3527 int k1, k2, k3, dx, dy, de, dm;
3528 int element2 = Tile[ELX][ELY];
3530 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3533 for (i = laser.num_damages - 1; i >= 0; i--)
3534 if (laser.damage[i].is_mirror)
3537 r = laser.num_edges;
3538 d = laser.num_damages;
3545 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3548 DrawLaser(0, DL_LASER_ENABLED);
3551 x = laser.damage[k1].x;
3552 y = laser.damage[k1].y;
3557 for (i = 0; i < 4; i++)
3559 if (laser.wall_mask & (1 << i))
3561 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3562 cSY + ELY * TILEY + 31 * (i / 2)))
3565 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3566 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3573 for (i = 0; i < 4; i++)
3575 if (laser.wall_mask & (1 << i))
3577 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3578 cSY + ELY * TILEY + 31 * (i / 2)))
3585 if (laser.num_beamers > 0 ||
3586 k1 < 1 || k2 < 4 || k3 < 4 ||
3587 CheckLaserPixel(cSX + ELX * TILEX + 14,
3588 cSY + ELY * TILEY + 14))
3590 laser.num_edges = r;
3591 laser.num_damages = d;
3593 DrawLaser(0, DL_LASER_DISABLED);
3596 Tile[ELX][ELY] = element | laser.wall_mask;
3600 de = Tile[ELX][ELY];
3601 dm = laser.wall_mask;
3605 int x = ELX, y = ELY;
3606 int wall_mask = laser.wall_mask;
3609 DrawLaser(0, DL_LASER_ENABLED);
3611 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3613 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3614 Store[x][y] = EL_WALL_AMOEBA;
3615 Store2[x][y] = wall_mask;
3621 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3623 DrawLaser(0, DL_LASER_ENABLED);
3625 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3627 for (i = 4; i >= 0; i--)
3629 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3632 Delay_WithScreenUpdates(20);
3635 DrawLaser(0, DL_LASER_ENABLED);
3640 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3641 laser.stops_inside_element && CT > native_mm_level.time_block)
3646 if (ABS(XS) > ABS(YS))
3653 for (i = 0; i < 4; i++)
3660 x = ELX + Step[k * 4].x;
3661 y = ELY + Step[k * 4].y;
3663 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3666 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3674 laser.overloaded = (element == EL_BLOCK_STONE);
3679 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3682 Tile[x][y] = element;
3684 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3687 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3689 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3690 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3698 if (element == EL_FUEL_FULL && CT > 10)
3700 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3701 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3703 for (i = start; i <= num_init_game_frames; i++)
3705 if (i == num_init_game_frames)
3706 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3707 else if (setup.sound_loops)
3708 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3710 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3712 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3714 UpdateAndDisplayGameControlValues();
3719 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3721 DrawField_MM(ELX, ELY);
3723 DrawLaser(0, DL_LASER_ENABLED);
3731 void GameActions_MM(struct MouseActionInfo action)
3733 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3734 boolean button_released = (action.button == MB_RELEASED);
3736 GameActions_MM_Ext();
3738 CheckSingleStepMode_MM(element_clicked, button_released);
3741 void MovePacMen(void)
3743 int mx, my, ox, oy, nx, ny;
3747 if (++pacman_nr >= game_mm.num_pacman)
3750 game_mm.pacman[pacman_nr].dir--;
3752 for (l = 1; l < 5; l++)
3754 game_mm.pacman[pacman_nr].dir++;
3756 if (game_mm.pacman[pacman_nr].dir > 4)
3757 game_mm.pacman[pacman_nr].dir = 1;
3759 if (game_mm.pacman[pacman_nr].dir % 2)
3762 my = game_mm.pacman[pacman_nr].dir - 2;
3767 mx = 3 - game_mm.pacman[pacman_nr].dir;
3770 ox = game_mm.pacman[pacman_nr].x;
3771 oy = game_mm.pacman[pacman_nr].y;
3774 element = Tile[nx][ny];
3776 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3779 if (!IS_EATABLE4PACMAN(element))
3782 if (ObjHit(nx, ny, HIT_POS_CENTER))
3785 Tile[ox][oy] = EL_EMPTY;
3787 EL_PACMAN_RIGHT - 1 +
3788 (game_mm.pacman[pacman_nr].dir - 1 +
3789 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3791 game_mm.pacman[pacman_nr].x = nx;
3792 game_mm.pacman[pacman_nr].y = ny;
3794 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3796 if (element != EL_EMPTY)
3798 int graphic = el2gfx(Tile[nx][ny]);
3803 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3806 ox = cSX + ox * TILEX;
3807 oy = cSY + oy * TILEY;
3809 for (i = 1; i < 33; i += 2)
3810 BlitBitmap(bitmap, window,
3811 src_x, src_y, TILEX, TILEY,
3812 ox + i * mx, oy + i * my);
3813 Ct = Ct + FrameCounter - CT;
3816 DrawField_MM(nx, ny);
3819 if (!laser.fuse_off)
3821 DrawLaser(0, DL_LASER_ENABLED);
3823 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3825 AddDamagedField(nx, ny);
3827 laser.damage[laser.num_damages - 1].edge = 0;
3831 if (element == EL_BOMB)
3832 DeletePacMan(nx, ny);
3834 if (IS_WALL_AMOEBA(element) &&
3835 (LX + 2 * XS) / TILEX == nx &&
3836 (LY + 2 * YS) / TILEY == ny)
3846 static void InitMovingField_MM(int x, int y, int direction)
3848 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3849 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3851 MovDir[x][y] = direction;
3852 MovDir[newx][newy] = direction;
3854 if (Tile[newx][newy] == EL_EMPTY)
3855 Tile[newx][newy] = EL_BLOCKED;
3858 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3860 int direction = MovDir[x][y];
3861 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3862 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3868 static void Blocked2Moving_MM(int x, int y,
3869 int *comes_from_x, int *comes_from_y)
3871 int oldx = x, oldy = y;
3872 int direction = MovDir[x][y];
3874 if (direction == MV_LEFT)
3876 else if (direction == MV_RIGHT)
3878 else if (direction == MV_UP)
3880 else if (direction == MV_DOWN)
3883 *comes_from_x = oldx;
3884 *comes_from_y = oldy;
3887 static int MovingOrBlocked2Element_MM(int x, int y)
3889 int element = Tile[x][y];
3891 if (element == EL_BLOCKED)
3895 Blocked2Moving_MM(x, y, &oldx, &oldy);
3897 return Tile[oldx][oldy];
3904 static void RemoveField(int x, int y)
3906 Tile[x][y] = EL_EMPTY;
3913 static void RemoveMovingField_MM(int x, int y)
3915 int oldx = x, oldy = y, newx = x, newy = y;
3917 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3920 if (IS_MOVING(x, y))
3922 Moving2Blocked_MM(x, y, &newx, &newy);
3923 if (Tile[newx][newy] != EL_BLOCKED)
3926 else if (Tile[x][y] == EL_BLOCKED)
3928 Blocked2Moving_MM(x, y, &oldx, &oldy);
3929 if (!IS_MOVING(oldx, oldy))
3933 Tile[oldx][oldy] = EL_EMPTY;
3934 Tile[newx][newy] = EL_EMPTY;
3935 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3936 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3938 DrawLevelField_MM(oldx, oldy);
3939 DrawLevelField_MM(newx, newy);
3942 void PlaySoundLevel(int x, int y, int sound_nr)
3944 int sx = SCREENX(x), sy = SCREENY(y);
3946 int silence_distance = 8;
3948 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3949 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3952 if (!IN_LEV_FIELD(x, y) ||
3953 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3954 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3957 volume = SOUND_MAX_VOLUME;
3960 stereo = (sx - SCR_FIELDX/2) * 12;
3962 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3963 if (stereo > SOUND_MAX_RIGHT)
3964 stereo = SOUND_MAX_RIGHT;
3965 if (stereo < SOUND_MAX_LEFT)
3966 stereo = SOUND_MAX_LEFT;
3969 if (!IN_SCR_FIELD(sx, sy))
3971 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3972 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3974 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3977 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3980 static void RaiseScore_MM(int value)
3982 game_mm.score += value;
3985 void RaiseScoreElement_MM(int element)
3990 case EL_PACMAN_RIGHT:
3992 case EL_PACMAN_LEFT:
3993 case EL_PACMAN_DOWN:
3994 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3998 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4003 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4007 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4016 // ----------------------------------------------------------------------------
4017 // Mirror Magic game engine snapshot handling functions
4018 // ----------------------------------------------------------------------------
4020 void SaveEngineSnapshotValues_MM(void)
4024 engine_snapshot_mm.game_mm = game_mm;
4025 engine_snapshot_mm.laser = laser;
4027 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4029 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4031 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4032 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4033 engine_snapshot_mm.Box[x][y] = Box[x][y];
4034 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4038 engine_snapshot_mm.LX = LX;
4039 engine_snapshot_mm.LY = LY;
4040 engine_snapshot_mm.XS = XS;
4041 engine_snapshot_mm.YS = YS;
4042 engine_snapshot_mm.ELX = ELX;
4043 engine_snapshot_mm.ELY = ELY;
4044 engine_snapshot_mm.CT = CT;
4045 engine_snapshot_mm.Ct = Ct;
4047 engine_snapshot_mm.last_LX = last_LX;
4048 engine_snapshot_mm.last_LY = last_LY;
4049 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4050 engine_snapshot_mm.hold_x = hold_x;
4051 engine_snapshot_mm.hold_y = hold_y;
4052 engine_snapshot_mm.pacman_nr = pacman_nr;
4054 engine_snapshot_mm.rotate_delay = rotate_delay;
4055 engine_snapshot_mm.pacman_delay = pacman_delay;
4056 engine_snapshot_mm.energy_delay = energy_delay;
4057 engine_snapshot_mm.overload_delay = overload_delay;
4060 void LoadEngineSnapshotValues_MM(void)
4064 // stored engine snapshot buffers already restored at this point
4066 game_mm = engine_snapshot_mm.game_mm;
4067 laser = engine_snapshot_mm.laser;
4069 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4071 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4073 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4074 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4075 Box[x][y] = engine_snapshot_mm.Box[x][y];
4076 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4080 LX = engine_snapshot_mm.LX;
4081 LY = engine_snapshot_mm.LY;
4082 XS = engine_snapshot_mm.XS;
4083 YS = engine_snapshot_mm.YS;
4084 ELX = engine_snapshot_mm.ELX;
4085 ELY = engine_snapshot_mm.ELY;
4086 CT = engine_snapshot_mm.CT;
4087 Ct = engine_snapshot_mm.Ct;
4089 last_LX = engine_snapshot_mm.last_LX;
4090 last_LY = engine_snapshot_mm.last_LY;
4091 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4092 hold_x = engine_snapshot_mm.hold_x;
4093 hold_y = engine_snapshot_mm.hold_y;
4094 pacman_nr = engine_snapshot_mm.pacman_nr;
4096 rotate_delay = engine_snapshot_mm.rotate_delay;
4097 pacman_delay = engine_snapshot_mm.pacman_delay;
4098 energy_delay = engine_snapshot_mm.energy_delay;
4099 overload_delay = engine_snapshot_mm.overload_delay;
4101 RedrawPlayfield_MM();
4104 static int getAngleFromTouchDelta(int dx, int dy, int base)
4106 double pi = 3.141592653;
4107 double rad = atan2((double)-dy, (double)dx);
4108 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4109 double deg = rad2 * 180.0 / pi;
4111 return (int)(deg * base / 360.0 + 0.5) % base;
4114 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4116 // calculate start (source) position to be at the middle of the tile
4117 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4118 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4119 int dx = dst_mx - src_mx;
4120 int dy = dst_my - src_my;
4129 if (!IN_LEV_FIELD(x, y))
4132 element = Tile[x][y];
4134 if (!IS_MCDUFFIN(element) &&
4135 !IS_MIRROR(element) &&
4136 !IS_BEAMER(element) &&
4137 !IS_POLAR(element) &&
4138 !IS_POLAR_CROSS(element) &&
4139 !IS_DF_MIRROR(element))
4142 angle_old = get_element_angle(element);
4144 if (IS_MCDUFFIN(element))
4146 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4147 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4148 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4149 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4152 else if (IS_MIRROR(element) ||
4153 IS_DF_MIRROR(element))
4155 for (i = 0; i < laser.num_damages; i++)
4157 if (laser.damage[i].x == x &&
4158 laser.damage[i].y == y &&
4159 ObjHit(x, y, HIT_POS_CENTER))
4161 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4162 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4169 if (angle_new == -1)
4171 if (IS_MIRROR(element) ||
4172 IS_DF_MIRROR(element) ||
4176 if (IS_POLAR_CROSS(element))
4179 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4182 button = (angle_new == angle_old ? 0 :
4183 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4184 MB_LEFTBUTTON : MB_RIGHTBUTTON);