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 (mode == DL_LASER_DISABLED)
1329 DeactivateLaserTargetElement();
1331 if (laser.num_edges - start_edge < 0)
1333 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1338 // check if laser is interrupted by beamer element
1339 if (laser.num_beamers > 0 &&
1340 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1342 if (mode == DL_LASER_ENABLED)
1345 int tmp_start_edge = start_edge;
1347 // draw laser segments forward from the start to the last beamer
1348 for (i = 0; i < laser.num_beamers; i++)
1350 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1352 if (tmp_num_edges <= 0)
1356 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1357 i, laser.beamer_edge[i], tmp_start_edge);
1360 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1362 tmp_start_edge = laser.beamer_edge[i];
1365 // draw last segment from last beamer to the end
1366 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1372 int last_num_edges = laser.num_edges;
1373 int num_beamers = laser.num_beamers;
1375 // delete laser segments backward from the end to the first beamer
1376 for (i = num_beamers - 1; i >= 0; i--)
1378 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1380 if (laser.beamer_edge[i] - start_edge <= 0)
1383 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1385 last_num_edges = laser.beamer_edge[i];
1386 laser.num_beamers--;
1390 if (last_num_edges - start_edge <= 0)
1391 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1392 last_num_edges, start_edge);
1395 // special case when rotating first beamer: delete laser edge on beamer
1396 // (but do not start scanning on previous edge to prevent mirror sound)
1397 if (last_num_edges - start_edge == 1 && start_edge > 0)
1398 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1400 // delete first segment from start to the first beamer
1401 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1406 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1409 game_mm.laser_enabled = mode;
1412 void DrawLaser_MM(void)
1414 DrawLaser(0, game_mm.laser_enabled);
1417 boolean HitElement(int element, int hit_mask)
1419 if (HitOnlyAnEdge(hit_mask))
1422 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1423 element = MovingOrBlocked2Element_MM(ELX, ELY);
1426 Debug("game:mm:HitElement", "(1): element == %d", element);
1430 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1431 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1434 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1438 AddDamagedField(ELX, ELY);
1440 // this is more precise: check if laser would go through the center
1441 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1443 // skip the whole element before continuing the scan
1449 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1451 if (LX/TILEX > ELX || LY/TILEY > ELY)
1453 /* skipping scan positions to the right and down skips one scan
1454 position too much, because this is only the top left scan position
1455 of totally four scan positions (plus one to the right, one to the
1456 bottom and one to the bottom right) */
1466 Debug("game:mm:HitElement", "(2): element == %d", element);
1469 if (LX + 5 * XS < 0 ||
1479 Debug("game:mm:HitElement", "(3): element == %d", element);
1482 if (IS_POLAR(element) &&
1483 ((element - EL_POLAR_START) % 2 ||
1484 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1486 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1488 laser.num_damages--;
1493 if (IS_POLAR_CROSS(element) &&
1494 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1496 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1498 laser.num_damages--;
1503 if (!IS_BEAMER(element) &&
1504 !IS_FIBRE_OPTIC(element) &&
1505 !IS_GRID_WOOD(element) &&
1506 element != EL_FUEL_EMPTY)
1509 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1510 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1512 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1515 LX = ELX * TILEX + 14;
1516 LY = ELY * TILEY + 14;
1518 AddLaserEdge(LX, LY);
1521 if (IS_MIRROR(element) ||
1522 IS_MIRROR_FIXED(element) ||
1523 IS_POLAR(element) ||
1524 IS_POLAR_CROSS(element) ||
1525 IS_DF_MIRROR(element) ||
1526 IS_DF_MIRROR_AUTO(element) ||
1527 element == EL_PRISM ||
1528 element == EL_REFRACTOR)
1530 int current_angle = laser.current_angle;
1533 laser.num_damages--;
1535 AddDamagedField(ELX, ELY);
1537 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1540 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1542 if (IS_MIRROR(element) ||
1543 IS_MIRROR_FIXED(element) ||
1544 IS_DF_MIRROR(element) ||
1545 IS_DF_MIRROR_AUTO(element))
1546 laser.current_angle = get_mirrored_angle(laser.current_angle,
1547 get_element_angle(element));
1549 if (element == EL_PRISM || element == EL_REFRACTOR)
1550 laser.current_angle = RND(16);
1552 XS = 2 * Step[laser.current_angle].x;
1553 YS = 2 * Step[laser.current_angle].y;
1555 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1560 LX += step_size * XS;
1561 LY += step_size * YS;
1563 // draw sparkles on mirror
1564 if ((IS_MIRROR(element) ||
1565 IS_MIRROR_FIXED(element) ||
1566 element == EL_PRISM) &&
1567 current_angle != laser.current_angle)
1569 MovDelay[ELX][ELY] = 11; // start animation
1572 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1573 current_angle != laser.current_angle)
1574 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1577 (get_opposite_angle(laser.current_angle) ==
1578 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1580 return (laser.overloaded ? TRUE : FALSE);
1583 if (element == EL_FUEL_FULL)
1585 laser.stops_inside_element = TRUE;
1590 if (element == EL_BOMB || element == EL_MINE)
1592 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1594 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1596 laser.dest_element_last = Tile[ELX][ELY];
1597 laser.dest_element_last_x = ELX;
1598 laser.dest_element_last_y = ELY;
1600 if (element == EL_MINE)
1601 laser.overloaded = TRUE;
1604 if (element == EL_KETTLE ||
1605 element == EL_CELL ||
1606 element == EL_KEY ||
1607 element == EL_LIGHTBALL ||
1608 element == EL_PACMAN ||
1611 if (!IS_PACMAN(element))
1614 if (element == EL_PACMAN)
1617 if (element == EL_KETTLE || element == EL_CELL)
1619 if (game_mm.kettles_still_needed > 0)
1620 game_mm.kettles_still_needed--;
1622 game.snapshot.collected_item = TRUE;
1624 if (game_mm.kettles_still_needed == 0)
1628 DrawLaser(0, DL_LASER_ENABLED);
1631 else if (element == EL_KEY)
1635 else if (IS_PACMAN(element))
1637 DeletePacMan(ELX, ELY);
1640 RaiseScoreElement_MM(element);
1645 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1647 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1649 DrawLaser(0, DL_LASER_ENABLED);
1651 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1653 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1654 game_mm.lights_still_needed--;
1658 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1659 game_mm.lights_still_needed++;
1662 DrawField_MM(ELX, ELY);
1663 DrawLaser(0, DL_LASER_ENABLED);
1668 laser.stops_inside_element = TRUE;
1674 Debug("game:mm:HitElement", "(4): element == %d", element);
1677 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1678 laser.num_beamers < MAX_NUM_BEAMERS &&
1679 laser.beamer[BEAMER_NR(element)][1].num)
1681 int beamer_angle = get_element_angle(element);
1682 int beamer_nr = BEAMER_NR(element);
1686 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1689 laser.num_damages--;
1691 if (IS_FIBRE_OPTIC(element) ||
1692 laser.current_angle == get_opposite_angle(beamer_angle))
1696 LX = ELX * TILEX + 14;
1697 LY = ELY * TILEY + 14;
1699 AddLaserEdge(LX, LY);
1700 AddDamagedField(ELX, ELY);
1702 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1705 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1707 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1708 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1709 ELX = laser.beamer[beamer_nr][pos].x;
1710 ELY = laser.beamer[beamer_nr][pos].y;
1711 LX = ELX * TILEX + 14;
1712 LY = ELY * TILEY + 14;
1714 if (IS_BEAMER(element))
1716 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1717 XS = 2 * Step[laser.current_angle].x;
1718 YS = 2 * Step[laser.current_angle].y;
1721 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1723 AddLaserEdge(LX, LY);
1724 AddDamagedField(ELX, ELY);
1726 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1729 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1731 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1736 LX += step_size * XS;
1737 LY += step_size * YS;
1739 laser.num_beamers++;
1748 boolean HitOnlyAnEdge(int hit_mask)
1750 // check if the laser hit only the edge of an element and, if so, go on
1753 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1757 if ((hit_mask == HIT_MASK_TOPLEFT ||
1758 hit_mask == HIT_MASK_TOPRIGHT ||
1759 hit_mask == HIT_MASK_BOTTOMLEFT ||
1760 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1761 laser.current_angle % 4) // angle is not 90°
1765 if (hit_mask == HIT_MASK_TOPLEFT)
1770 else if (hit_mask == HIT_MASK_TOPRIGHT)
1775 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1780 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1786 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1792 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1799 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1805 boolean HitPolarizer(int element, int hit_mask)
1807 if (HitOnlyAnEdge(hit_mask))
1810 if (IS_DF_GRID(element))
1812 int grid_angle = get_element_angle(element);
1815 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1816 grid_angle, laser.current_angle);
1819 AddLaserEdge(LX, LY);
1820 AddDamagedField(ELX, ELY);
1823 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1825 if (laser.current_angle == grid_angle ||
1826 laser.current_angle == get_opposite_angle(grid_angle))
1828 // skip the whole element before continuing the scan
1834 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1836 if (LX/TILEX > ELX || LY/TILEY > ELY)
1838 /* skipping scan positions to the right and down skips one scan
1839 position too much, because this is only the top left scan position
1840 of totally four scan positions (plus one to the right, one to the
1841 bottom and one to the bottom right) */
1847 AddLaserEdge(LX, LY);
1853 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1855 LX / TILEX, LY / TILEY,
1856 LX % TILEX, LY % TILEY);
1861 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1863 return HitReflectingWalls(element, hit_mask);
1867 return HitAbsorbingWalls(element, hit_mask);
1870 else if (IS_GRID_STEEL(element))
1872 return HitReflectingWalls(element, hit_mask);
1874 else // IS_GRID_WOOD
1876 return HitAbsorbingWalls(element, hit_mask);
1882 boolean HitBlock(int element, int hit_mask)
1884 boolean check = FALSE;
1886 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1887 game_mm.num_keys == 0)
1890 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1893 int ex = ELX * TILEX + 14;
1894 int ey = ELY * TILEY + 14;
1898 for (i = 1; i < 32; i++)
1903 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1908 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1909 return HitAbsorbingWalls(element, hit_mask);
1913 AddLaserEdge(LX - XS, LY - YS);
1914 AddDamagedField(ELX, ELY);
1917 Box[ELX][ELY] = laser.num_edges;
1919 return HitReflectingWalls(element, hit_mask);
1922 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1924 int xs = XS / 2, ys = YS / 2;
1925 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1926 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1928 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1929 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1931 laser.overloaded = (element == EL_GATE_STONE);
1936 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1937 (hit_mask == HIT_MASK_TOP ||
1938 hit_mask == HIT_MASK_LEFT ||
1939 hit_mask == HIT_MASK_RIGHT ||
1940 hit_mask == HIT_MASK_BOTTOM))
1941 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1942 hit_mask == HIT_MASK_BOTTOM),
1943 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1944 hit_mask == HIT_MASK_RIGHT));
1945 AddLaserEdge(LX, LY);
1951 if (element == EL_GATE_STONE && Box[ELX][ELY])
1953 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1965 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1967 int xs = XS / 2, ys = YS / 2;
1968 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1969 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1971 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1972 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1974 laser.overloaded = (element == EL_BLOCK_STONE);
1979 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1980 (hit_mask == HIT_MASK_TOP ||
1981 hit_mask == HIT_MASK_LEFT ||
1982 hit_mask == HIT_MASK_RIGHT ||
1983 hit_mask == HIT_MASK_BOTTOM))
1984 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1985 hit_mask == HIT_MASK_BOTTOM),
1986 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1987 hit_mask == HIT_MASK_RIGHT));
1988 AddDamagedField(ELX, ELY);
1990 LX = ELX * TILEX + 14;
1991 LY = ELY * TILEY + 14;
1993 AddLaserEdge(LX, LY);
1995 laser.stops_inside_element = TRUE;
2003 boolean HitLaserSource(int element, int hit_mask)
2005 if (HitOnlyAnEdge(hit_mask))
2008 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2010 laser.overloaded = TRUE;
2015 boolean HitLaserDestination(int element, int hit_mask)
2017 if (HitOnlyAnEdge(hit_mask))
2020 if (element != EL_EXIT_OPEN &&
2021 !(IS_RECEIVER(element) &&
2022 game_mm.kettles_still_needed == 0 &&
2023 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2025 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2030 if (IS_RECEIVER(element) ||
2031 (IS_22_5_ANGLE(laser.current_angle) &&
2032 (ELX != (LX + 6 * XS) / TILEX ||
2033 ELY != (LY + 6 * YS) / TILEY ||
2042 LX = ELX * TILEX + 14;
2043 LY = ELY * TILEY + 14;
2045 laser.stops_inside_element = TRUE;
2048 AddLaserEdge(LX, LY);
2049 AddDamagedField(ELX, ELY);
2051 if (game_mm.lights_still_needed == 0)
2053 game_mm.level_solved = TRUE;
2055 SetTileCursorActive(FALSE);
2061 boolean HitReflectingWalls(int element, int hit_mask)
2063 // check if laser hits side of a wall with an angle that is not 90°
2064 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2065 hit_mask == HIT_MASK_LEFT ||
2066 hit_mask == HIT_MASK_RIGHT ||
2067 hit_mask == HIT_MASK_BOTTOM))
2069 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2074 if (!IS_DF_GRID(element))
2075 AddLaserEdge(LX, LY);
2077 // check if laser hits wall with an angle of 45°
2078 if (!IS_22_5_ANGLE(laser.current_angle))
2080 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2083 laser.current_angle = get_mirrored_angle(laser.current_angle,
2086 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2089 laser.current_angle = get_mirrored_angle(laser.current_angle,
2093 AddLaserEdge(LX, LY);
2095 XS = 2 * Step[laser.current_angle].x;
2096 YS = 2 * Step[laser.current_angle].y;
2100 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2102 laser.current_angle = get_mirrored_angle(laser.current_angle,
2107 if (!IS_DF_GRID(element))
2108 AddLaserEdge(LX, LY);
2113 if (!IS_DF_GRID(element))
2114 AddLaserEdge(LX, LY + YS / 2);
2117 if (!IS_DF_GRID(element))
2118 AddLaserEdge(LX, LY);
2121 YS = 2 * Step[laser.current_angle].y;
2125 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2127 laser.current_angle = get_mirrored_angle(laser.current_angle,
2132 if (!IS_DF_GRID(element))
2133 AddLaserEdge(LX, LY);
2138 if (!IS_DF_GRID(element))
2139 AddLaserEdge(LX + XS / 2, LY);
2142 if (!IS_DF_GRID(element))
2143 AddLaserEdge(LX, LY);
2146 XS = 2 * Step[laser.current_angle].x;
2152 // reflection at the edge of reflecting DF style wall
2153 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2155 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2156 hit_mask == HIT_MASK_TOPRIGHT) ||
2157 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2158 hit_mask == HIT_MASK_TOPLEFT) ||
2159 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2160 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2161 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2162 hit_mask == HIT_MASK_BOTTOMRIGHT))
2165 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2166 ANG_MIRROR_135 : ANG_MIRROR_45);
2168 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2170 AddDamagedField(ELX, ELY);
2171 AddLaserEdge(LX, LY);
2173 laser.current_angle = get_mirrored_angle(laser.current_angle,
2181 AddLaserEdge(LX, LY);
2187 // reflection inside an edge of reflecting DF style wall
2188 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2190 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2191 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2192 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2193 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2194 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2195 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2196 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2197 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2200 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2201 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2202 ANG_MIRROR_135 : ANG_MIRROR_45);
2204 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2207 AddDamagedField(ELX, ELY);
2210 AddLaserEdge(LX - XS, LY - YS);
2211 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2212 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2214 laser.current_angle = get_mirrored_angle(laser.current_angle,
2222 AddLaserEdge(LX, LY);
2228 // check if laser hits DF style wall with an angle of 90°
2229 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2231 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2232 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2233 (IS_VERT_ANGLE(laser.current_angle) &&
2234 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2236 // laser at last step touched nothing or the same side of the wall
2237 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2239 AddDamagedField(ELX, ELY);
2246 last_hit_mask = hit_mask;
2253 if (!HitOnlyAnEdge(hit_mask))
2255 laser.overloaded = TRUE;
2263 boolean HitAbsorbingWalls(int element, int hit_mask)
2265 if (HitOnlyAnEdge(hit_mask))
2269 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2271 AddLaserEdge(LX - XS, LY - YS);
2278 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2280 AddLaserEdge(LX - XS, LY - YS);
2286 if (IS_WALL_WOOD(element) ||
2287 IS_DF_WALL_WOOD(element) ||
2288 IS_GRID_WOOD(element) ||
2289 IS_GRID_WOOD_FIXED(element) ||
2290 IS_GRID_WOOD_AUTO(element) ||
2291 element == EL_FUSE_ON ||
2292 element == EL_BLOCK_WOOD ||
2293 element == EL_GATE_WOOD)
2295 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2300 if (IS_WALL_ICE(element))
2304 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2305 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2307 // check if laser hits wall with an angle of 90°
2308 if (IS_90_ANGLE(laser.current_angle))
2309 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2311 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2315 for (i = 0; i < 4; i++)
2317 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2318 mask = 15 - (8 >> i);
2319 else if (ABS(XS) == 4 &&
2321 (XS > 0) == (i % 2) &&
2322 (YS < 0) == (i / 2))
2323 mask = 3 + (i / 2) * 9;
2324 else if (ABS(YS) == 4 &&
2326 (XS < 0) == (i % 2) &&
2327 (YS > 0) == (i / 2))
2328 mask = 5 + (i % 2) * 5;
2332 laser.wall_mask = mask;
2334 else if (IS_WALL_AMOEBA(element))
2336 int elx = (LX - 2 * XS) / TILEX;
2337 int ely = (LY - 2 * YS) / TILEY;
2338 int element2 = Tile[elx][ely];
2341 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2343 laser.dest_element = EL_EMPTY;
2351 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2352 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2354 if (IS_90_ANGLE(laser.current_angle))
2355 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2357 laser.dest_element = element2 | EL_WALL_AMOEBA;
2359 laser.wall_mask = mask;
2365 static void OpenExit(int x, int y)
2369 if (!MovDelay[x][y]) // next animation frame
2370 MovDelay[x][y] = 4 * delay;
2372 if (MovDelay[x][y]) // wait some time before next frame
2377 phase = MovDelay[x][y] / delay;
2379 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2380 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2382 if (!MovDelay[x][y])
2384 Tile[x][y] = EL_EXIT_OPEN;
2390 static void OpenSurpriseBall(int x, int y)
2394 if (!MovDelay[x][y]) // next animation frame
2395 MovDelay[x][y] = 50 * delay;
2397 if (MovDelay[x][y]) // wait some time before next frame
2401 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2404 int graphic = el2gfx(Store[x][y]);
2406 int dx = RND(26), dy = RND(26);
2408 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2410 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2411 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2413 MarkTileDirty(x, y);
2416 if (!MovDelay[x][y])
2418 Tile[x][y] = Store[x][y];
2419 Store[x][y] = Store2[x][y] = 0;
2428 static void MeltIce(int x, int y)
2433 if (!MovDelay[x][y]) // next animation frame
2434 MovDelay[x][y] = frames * delay;
2436 if (MovDelay[x][y]) // wait some time before next frame
2439 int wall_mask = Store2[x][y];
2440 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2443 phase = frames - MovDelay[x][y] / delay - 1;
2445 if (!MovDelay[x][y])
2449 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2450 Store[x][y] = Store2[x][y] = 0;
2452 DrawWalls_MM(x, y, Tile[x][y]);
2454 if (Tile[x][y] == EL_WALL_ICE)
2455 Tile[x][y] = EL_EMPTY;
2457 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2458 if (laser.damage[i].is_mirror)
2462 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2464 DrawLaser(0, DL_LASER_DISABLED);
2468 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2470 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2472 laser.redraw = TRUE;
2477 static void GrowAmoeba(int x, int y)
2482 if (!MovDelay[x][y]) // next animation frame
2483 MovDelay[x][y] = frames * delay;
2485 if (MovDelay[x][y]) // wait some time before next frame
2488 int wall_mask = Store2[x][y];
2489 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2492 phase = MovDelay[x][y] / delay;
2494 if (!MovDelay[x][y])
2496 Tile[x][y] = real_element;
2497 Store[x][y] = Store2[x][y] = 0;
2499 DrawWalls_MM(x, y, Tile[x][y]);
2500 DrawLaser(0, DL_LASER_ENABLED);
2502 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2504 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2509 static void DrawFieldAnimated_MM(int x, int y)
2511 int element = Tile[x][y];
2513 if (IS_BLOCKED(x, y))
2518 if (IS_MIRROR(element) ||
2519 IS_MIRROR_FIXED(element) ||
2520 element == EL_PRISM)
2522 if (MovDelay[x][y] != 0) // wait some time before next frame
2526 if (MovDelay[x][y] != 0)
2528 int graphic = IMG_TWINKLE_WHITE;
2529 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2531 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2536 laser.redraw = TRUE;
2539 static void Explode_MM(int x, int y, int phase, int mode)
2541 int num_phase = 9, delay = 2;
2542 int last_phase = num_phase * delay;
2543 int half_phase = (num_phase / 2) * delay;
2545 laser.redraw = TRUE;
2547 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2549 int center_element = Tile[x][y];
2551 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2553 // put moving element to center field (and let it explode there)
2554 center_element = MovingOrBlocked2Element_MM(x, y);
2555 RemoveMovingField_MM(x, y);
2557 Tile[x][y] = center_element;
2560 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2561 Store[x][y] = center_element;
2563 Store[x][y] = EL_EMPTY;
2565 Store2[x][y] = mode;
2567 Tile[x][y] = EL_EXPLODING_OPAQUE;
2568 GfxElement[x][y] = center_element;
2570 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2572 ExplodePhase[x][y] = 1;
2578 GfxFrame[x][y] = 0; // restart explosion animation
2580 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2582 if (phase == half_phase)
2584 Tile[x][y] = EL_EXPLODING_TRANSP;
2586 if (x == ELX && y == ELY)
2590 if (phase == last_phase)
2592 if (Store[x][y] == EL_BOMB_ACTIVE)
2594 DrawLaser(0, DL_LASER_DISABLED);
2597 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2598 Store[x][y] = EL_EMPTY;
2600 GameOver_MM(GAME_OVER_DELAYED);
2602 laser.overloaded = FALSE;
2604 else if (IS_MCDUFFIN(Store[x][y]))
2606 Store[x][y] = EL_EMPTY;
2608 GameOver_MM(GAME_OVER_BOMB);
2611 Tile[x][y] = Store[x][y];
2612 Store[x][y] = Store2[x][y] = 0;
2613 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2618 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2620 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2621 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2623 DrawGraphicAnimation_MM(x, y, graphic, frame);
2625 MarkTileDirty(x, y);
2629 static void Bang_MM(int x, int y)
2631 int element = Tile[x][y];
2634 DrawLaser(0, DL_LASER_ENABLED);
2637 if (IS_PACMAN(element))
2638 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2639 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2640 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2641 else if (element == EL_KEY)
2642 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2644 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2646 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2649 void TurnRound(int x, int y)
2661 { 0, 0 }, { 0, 0 }, { 0, 0 },
2666 int left, right, back;
2670 { MV_DOWN, MV_UP, MV_RIGHT },
2671 { MV_UP, MV_DOWN, MV_LEFT },
2673 { MV_LEFT, MV_RIGHT, MV_DOWN },
2674 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2675 { MV_RIGHT, MV_LEFT, MV_UP }
2678 int element = Tile[x][y];
2679 int old_move_dir = MovDir[x][y];
2680 int right_dir = turn[old_move_dir].right;
2681 int back_dir = turn[old_move_dir].back;
2682 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2683 int right_x = x + right_dx, right_y = y + right_dy;
2685 if (element == EL_PACMAN)
2687 boolean can_turn_right = FALSE;
2689 if (IN_LEV_FIELD(right_x, right_y) &&
2690 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2691 can_turn_right = TRUE;
2694 MovDir[x][y] = right_dir;
2696 MovDir[x][y] = back_dir;
2702 static void StartMoving_MM(int x, int y)
2704 int element = Tile[x][y];
2709 if (CAN_MOVE(element))
2713 if (MovDelay[x][y]) // wait some time before next movement
2721 // now make next step
2723 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2725 if (element == EL_PACMAN &&
2726 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2727 !ObjHit(newx, newy, HIT_POS_CENTER))
2729 Store[newx][newy] = Tile[newx][newy];
2730 Tile[newx][newy] = EL_EMPTY;
2732 DrawField_MM(newx, newy);
2734 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2735 ObjHit(newx, newy, HIT_POS_CENTER))
2737 // object was running against a wall
2744 InitMovingField_MM(x, y, MovDir[x][y]);
2748 ContinueMoving_MM(x, y);
2751 static void ContinueMoving_MM(int x, int y)
2753 int element = Tile[x][y];
2754 int direction = MovDir[x][y];
2755 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2756 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2757 int horiz_move = (dx!=0);
2758 int newx = x + dx, newy = y + dy;
2759 int step = (horiz_move ? dx : dy) * TILEX / 8;
2761 MovPos[x][y] += step;
2763 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2765 Tile[x][y] = EL_EMPTY;
2766 Tile[newx][newy] = element;
2768 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2769 MovDelay[newx][newy] = 0;
2771 if (!CAN_MOVE(element))
2772 MovDir[newx][newy] = 0;
2775 DrawField_MM(newx, newy);
2777 Stop[newx][newy] = TRUE;
2779 if (element == EL_PACMAN)
2781 if (Store[newx][newy] == EL_BOMB)
2782 Bang_MM(newx, newy);
2784 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2785 (LX + 2 * XS) / TILEX == newx &&
2786 (LY + 2 * YS) / TILEY == newy)
2793 else // still moving on
2798 laser.redraw = TRUE;
2801 boolean ClickElement(int x, int y, int button)
2803 static DelayCounter click_delay = { CLICK_DELAY };
2804 static boolean new_button = TRUE;
2805 boolean element_clicked = FALSE;
2810 // initialize static variables
2811 click_delay.count = 0;
2812 click_delay.value = CLICK_DELAY;
2818 // do not rotate objects hit by the laser after the game was solved
2819 if (game_mm.level_solved && Hit[x][y])
2822 if (button == MB_RELEASED)
2825 click_delay.value = CLICK_DELAY;
2827 // release eventually hold auto-rotating mirror
2828 RotateMirror(x, y, MB_RELEASED);
2833 if (!FrameReached(&click_delay) && !new_button)
2836 if (button == MB_MIDDLEBUTTON) // middle button has no function
2839 if (!IN_LEV_FIELD(x, y))
2842 if (Tile[x][y] == EL_EMPTY)
2845 element = Tile[x][y];
2847 if (IS_MIRROR(element) ||
2848 IS_BEAMER(element) ||
2849 IS_POLAR(element) ||
2850 IS_POLAR_CROSS(element) ||
2851 IS_DF_MIRROR(element) ||
2852 IS_DF_MIRROR_AUTO(element))
2854 RotateMirror(x, y, button);
2856 element_clicked = TRUE;
2858 else if (IS_MCDUFFIN(element))
2860 if (!laser.fuse_off)
2862 DrawLaser(0, DL_LASER_DISABLED);
2869 element = get_rotated_element(element, BUTTON_ROTATION(button));
2870 laser.start_angle = get_element_angle(element);
2874 Tile[x][y] = element;
2881 if (!laser.fuse_off)
2884 element_clicked = TRUE;
2886 else if (element == EL_FUSE_ON && laser.fuse_off)
2888 if (x != laser.fuse_x || y != laser.fuse_y)
2891 laser.fuse_off = FALSE;
2892 laser.fuse_x = laser.fuse_y = -1;
2894 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2897 element_clicked = TRUE;
2899 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2901 laser.fuse_off = TRUE;
2904 laser.overloaded = FALSE;
2906 DrawLaser(0, DL_LASER_DISABLED);
2907 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2909 element_clicked = TRUE;
2911 else if (element == EL_LIGHTBALL)
2914 RaiseScoreElement_MM(element);
2915 DrawLaser(0, DL_LASER_ENABLED);
2917 element_clicked = TRUE;
2920 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2923 return element_clicked;
2926 void RotateMirror(int x, int y, int button)
2928 if (button == MB_RELEASED)
2930 // release eventually hold auto-rotating mirror
2937 if (IS_MIRROR(Tile[x][y]) ||
2938 IS_POLAR_CROSS(Tile[x][y]) ||
2939 IS_POLAR(Tile[x][y]) ||
2940 IS_BEAMER(Tile[x][y]) ||
2941 IS_DF_MIRROR(Tile[x][y]) ||
2942 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2943 IS_GRID_WOOD_AUTO(Tile[x][y]))
2945 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2947 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2949 if (button == MB_LEFTBUTTON)
2951 // left mouse button only for manual adjustment, no auto-rotating;
2952 // freeze mirror for until mouse button released
2956 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2958 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2962 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2964 int edge = Hit[x][y];
2970 DrawLaser(edge - 1, DL_LASER_DISABLED);
2974 else if (ObjHit(x, y, HIT_POS_CENTER))
2976 int edge = Hit[x][y];
2980 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2985 DrawLaser(edge - 1, DL_LASER_DISABLED);
2992 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2997 if ((IS_BEAMER(Tile[x][y]) ||
2998 IS_POLAR(Tile[x][y]) ||
2999 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3003 if (IS_BEAMER(Tile[x][y]))
3006 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3007 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3019 DrawLaser(0, DL_LASER_ENABLED);
3023 static void AutoRotateMirrors(void)
3027 if (!FrameReached(&rotate_delay))
3030 for (x = 0; x < lev_fieldx; x++)
3032 for (y = 0; y < lev_fieldy; y++)
3034 int element = Tile[x][y];
3036 // do not rotate objects hit by the laser after the game was solved
3037 if (game_mm.level_solved && Hit[x][y])
3040 if (IS_DF_MIRROR_AUTO(element) ||
3041 IS_GRID_WOOD_AUTO(element) ||
3042 IS_GRID_STEEL_AUTO(element) ||
3043 element == EL_REFRACTOR)
3044 RotateMirror(x, y, MB_RIGHTBUTTON);
3049 boolean ObjHit(int obx, int oby, int bits)
3056 if (bits & HIT_POS_CENTER)
3058 if (CheckLaserPixel(cSX + obx + 15,
3063 if (bits & HIT_POS_EDGE)
3065 for (i = 0; i < 4; i++)
3066 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3067 cSY + oby + 31 * (i / 2)))
3071 if (bits & HIT_POS_BETWEEN)
3073 for (i = 0; i < 4; i++)
3074 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3075 cSY + 4 + oby + 22 * (i / 2)))
3082 void DeletePacMan(int px, int py)
3088 if (game_mm.num_pacman <= 1)
3090 game_mm.num_pacman = 0;
3094 for (i = 0; i < game_mm.num_pacman; i++)
3095 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3098 game_mm.num_pacman--;
3100 for (j = i; j < game_mm.num_pacman; j++)
3102 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3103 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3104 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3108 void ColorCycling(void)
3110 static int CC, Cc = 0;
3112 static int color, old = 0xF00, new = 0x010, mult = 1;
3113 static unsigned short red, green, blue;
3115 if (color_status == STATIC_COLORS)
3120 if (CC < Cc || CC > Cc + 2)
3124 color = old + new * mult;
3130 if (ABS(mult) == 16)
3140 red = 0x0e00 * ((color & 0xF00) >> 8);
3141 green = 0x0e00 * ((color & 0x0F0) >> 4);
3142 blue = 0x0e00 * ((color & 0x00F));
3143 SetRGB(pen_magicolor[0], red, green, blue);
3145 red = 0x1111 * ((color & 0xF00) >> 8);
3146 green = 0x1111 * ((color & 0x0F0) >> 4);
3147 blue = 0x1111 * ((color & 0x00F));
3148 SetRGB(pen_magicolor[1], red, green, blue);
3152 static void GameActions_MM_Ext(void)
3159 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3162 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3164 element = Tile[x][y];
3166 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3167 StartMoving_MM(x, y);
3168 else if (IS_MOVING(x, y))
3169 ContinueMoving_MM(x, y);
3170 else if (IS_EXPLODING(element))
3171 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3172 else if (element == EL_EXIT_OPENING)
3174 else if (element == EL_GRAY_BALL_OPENING)
3175 OpenSurpriseBall(x, y);
3176 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3178 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3181 DrawFieldAnimated_MM(x, y);
3184 AutoRotateMirrors();
3187 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3189 // redraw after Explode_MM() ...
3191 DrawLaser(0, DL_LASER_ENABLED);
3192 laser.redraw = FALSE;
3197 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3201 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3203 DrawLaser(0, DL_LASER_DISABLED);
3208 // skip all following game actions if game is over
3209 if (game_mm.game_over)
3212 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3216 GameOver_MM(GAME_OVER_NO_ENERGY);
3221 if (FrameReached(&energy_delay))
3223 if (game_mm.energy_left > 0)
3224 game_mm.energy_left--;
3226 // when out of energy, wait another frame to play "out of time" sound
3229 element = laser.dest_element;
3232 if (element != Tile[ELX][ELY])
3234 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3235 element, Tile[ELX][ELY]);
3239 if (!laser.overloaded && laser.overload_value == 0 &&
3240 element != EL_BOMB &&
3241 element != EL_BOMB_ACTIVE &&
3242 element != EL_MINE &&
3243 element != EL_MINE_ACTIVE &&
3244 element != EL_BALL_GRAY &&
3245 element != EL_BLOCK_STONE &&
3246 element != EL_BLOCK_WOOD &&
3247 element != EL_FUSE_ON &&
3248 element != EL_FUEL_FULL &&
3249 !IS_WALL_ICE(element) &&
3250 !IS_WALL_AMOEBA(element))
3253 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3255 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3256 (!laser.overloaded && laser.overload_value > 0)) &&
3257 FrameReached(&overload_delay))
3259 if (laser.overloaded)
3260 laser.overload_value++;
3262 laser.overload_value--;
3264 if (game_mm.cheat_no_overload)
3266 laser.overloaded = FALSE;
3267 laser.overload_value = 0;
3270 game_mm.laser_overload_value = laser.overload_value;
3272 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3274 SetLaserColor(0xFF);
3276 DrawLaser(0, DL_LASER_ENABLED);
3279 if (!laser.overloaded)
3280 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3281 else if (setup.sound_loops)
3282 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3284 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3286 if (laser.overloaded)
3289 BlitBitmap(pix[PIX_DOOR], drawto,
3290 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3291 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3292 - laser.overload_value,
3293 OVERLOAD_XSIZE, laser.overload_value,
3294 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3295 - laser.overload_value);
3297 redraw_mask |= REDRAW_DOOR_1;
3302 BlitBitmap(pix[PIX_DOOR], drawto,
3303 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3304 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3305 DX_OVERLOAD, DY_OVERLOAD);
3307 redraw_mask |= REDRAW_DOOR_1;
3310 if (laser.overload_value == MAX_LASER_OVERLOAD)
3312 UpdateAndDisplayGameControlValues();
3316 GameOver_MM(GAME_OVER_OVERLOADED);
3327 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3329 if (game_mm.cheat_no_explosion)
3334 laser.dest_element = EL_EXPLODING_OPAQUE;
3339 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3341 laser.fuse_off = TRUE;
3345 DrawLaser(0, DL_LASER_DISABLED);
3346 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3349 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3351 if (!Store2[ELX][ELY]) // check if content element not yet determined
3353 int last_anim_random_frame = gfx.anim_random_frame;
3356 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3357 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3359 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3360 native_mm_level.ball_choice_mode, 0,
3361 game_mm.ball_choice_pos);
3363 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3364 gfx.anim_random_frame = last_anim_random_frame;
3366 game_mm.ball_choice_pos++;
3368 int new_element = native_mm_level.ball_content[element_pos];
3370 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3371 Store2[ELX][ELY] = TRUE;
3374 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3376 // !!! CHECK AGAIN: Laser on Polarizer !!!
3379 laser.dest_element_last = Tile[ELX][ELY];
3380 laser.dest_element_last_x = ELX;
3381 laser.dest_element_last_y = ELY;
3391 element = EL_MIRROR_START + RND(16);
3397 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3404 element = (rnd == 0 ? EL_FUSE_ON :
3405 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3406 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3407 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3408 EL_MIRROR_FIXED_START + rnd - 25);
3413 graphic = el2gfx(element);
3415 for (i = 0; i < 50; i++)
3421 BlitBitmap(pix[PIX_BACK], drawto,
3422 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3423 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3424 SX + ELX * TILEX + x,
3425 SY + ELY * TILEY + y);
3427 MarkTileDirty(ELX, ELY);
3430 DrawLaser(0, DL_LASER_ENABLED);
3432 Delay_WithScreenUpdates(50);
3435 Tile[ELX][ELY] = element;
3436 DrawField_MM(ELX, ELY);
3439 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3442 // above stuff: GRAY BALL -> PRISM !!!
3444 LX = ELX * TILEX + 14;
3445 LY = ELY * TILEY + 14;
3446 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3453 laser.num_edges -= 2;
3454 laser.num_damages--;
3458 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3459 if (laser.damage[i].is_mirror)
3463 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3465 DrawLaser(0, DL_LASER_DISABLED);
3467 DrawLaser(0, DL_LASER_DISABLED);
3476 if (IS_WALL_ICE(element) && CT > 50)
3478 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3481 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3482 Store[ELX][ELY] = EL_WALL_ICE;
3483 Store2[ELX][ELY] = laser.wall_mask;
3485 laser.dest_element = Tile[ELX][ELY];
3490 for (i = 0; i < 5; i++)
3496 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3500 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3502 Delay_WithScreenUpdates(100);
3505 if (Tile[ELX][ELY] == EL_WALL_ICE)
3506 Tile[ELX][ELY] = EL_EMPTY;
3510 LX = laser.edge[laser.num_edges].x - cSX2;
3511 LY = laser.edge[laser.num_edges].y - cSY2;
3514 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3515 if (laser.damage[i].is_mirror)
3519 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3521 DrawLaser(0, DL_LASER_DISABLED);
3528 if (IS_WALL_AMOEBA(element) && CT > 60)
3530 int k1, k2, k3, dx, dy, de, dm;
3531 int element2 = Tile[ELX][ELY];
3533 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3536 for (i = laser.num_damages - 1; i >= 0; i--)
3537 if (laser.damage[i].is_mirror)
3540 r = laser.num_edges;
3541 d = laser.num_damages;
3548 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3551 DrawLaser(0, DL_LASER_ENABLED);
3554 x = laser.damage[k1].x;
3555 y = laser.damage[k1].y;
3560 for (i = 0; i < 4; i++)
3562 if (laser.wall_mask & (1 << i))
3564 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3565 cSY + ELY * TILEY + 31 * (i / 2)))
3568 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3569 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3576 for (i = 0; i < 4; i++)
3578 if (laser.wall_mask & (1 << i))
3580 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3581 cSY + ELY * TILEY + 31 * (i / 2)))
3588 if (laser.num_beamers > 0 ||
3589 k1 < 1 || k2 < 4 || k3 < 4 ||
3590 CheckLaserPixel(cSX + ELX * TILEX + 14,
3591 cSY + ELY * TILEY + 14))
3593 laser.num_edges = r;
3594 laser.num_damages = d;
3596 DrawLaser(0, DL_LASER_DISABLED);
3599 Tile[ELX][ELY] = element | laser.wall_mask;
3603 de = Tile[ELX][ELY];
3604 dm = laser.wall_mask;
3608 int x = ELX, y = ELY;
3609 int wall_mask = laser.wall_mask;
3612 DrawLaser(0, DL_LASER_ENABLED);
3614 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3616 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3617 Store[x][y] = EL_WALL_AMOEBA;
3618 Store2[x][y] = wall_mask;
3624 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3626 DrawLaser(0, DL_LASER_ENABLED);
3628 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3630 for (i = 4; i >= 0; i--)
3632 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3635 Delay_WithScreenUpdates(20);
3638 DrawLaser(0, DL_LASER_ENABLED);
3643 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3644 laser.stops_inside_element && CT > native_mm_level.time_block)
3649 if (ABS(XS) > ABS(YS))
3656 for (i = 0; i < 4; i++)
3663 x = ELX + Step[k * 4].x;
3664 y = ELY + Step[k * 4].y;
3666 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3669 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3677 laser.overloaded = (element == EL_BLOCK_STONE);
3682 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3685 Tile[x][y] = element;
3687 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3690 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3692 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3693 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3701 if (element == EL_FUEL_FULL && CT > 10)
3703 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3704 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3706 for (i = start; i <= num_init_game_frames; i++)
3708 if (i == num_init_game_frames)
3709 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3710 else if (setup.sound_loops)
3711 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3713 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3715 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3717 UpdateAndDisplayGameControlValues();
3722 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3724 DrawField_MM(ELX, ELY);
3726 DrawLaser(0, DL_LASER_ENABLED);
3734 void GameActions_MM(struct MouseActionInfo action)
3736 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3737 boolean button_released = (action.button == MB_RELEASED);
3739 GameActions_MM_Ext();
3741 CheckSingleStepMode_MM(element_clicked, button_released);
3744 void MovePacMen(void)
3746 int mx, my, ox, oy, nx, ny;
3750 if (++pacman_nr >= game_mm.num_pacman)
3753 game_mm.pacman[pacman_nr].dir--;
3755 for (l = 1; l < 5; l++)
3757 game_mm.pacman[pacman_nr].dir++;
3759 if (game_mm.pacman[pacman_nr].dir > 4)
3760 game_mm.pacman[pacman_nr].dir = 1;
3762 if (game_mm.pacman[pacman_nr].dir % 2)
3765 my = game_mm.pacman[pacman_nr].dir - 2;
3770 mx = 3 - game_mm.pacman[pacman_nr].dir;
3773 ox = game_mm.pacman[pacman_nr].x;
3774 oy = game_mm.pacman[pacman_nr].y;
3777 element = Tile[nx][ny];
3779 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3782 if (!IS_EATABLE4PACMAN(element))
3785 if (ObjHit(nx, ny, HIT_POS_CENTER))
3788 Tile[ox][oy] = EL_EMPTY;
3790 EL_PACMAN_RIGHT - 1 +
3791 (game_mm.pacman[pacman_nr].dir - 1 +
3792 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3794 game_mm.pacman[pacman_nr].x = nx;
3795 game_mm.pacman[pacman_nr].y = ny;
3797 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3799 if (element != EL_EMPTY)
3801 int graphic = el2gfx(Tile[nx][ny]);
3806 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3809 ox = cSX + ox * TILEX;
3810 oy = cSY + oy * TILEY;
3812 for (i = 1; i < 33; i += 2)
3813 BlitBitmap(bitmap, window,
3814 src_x, src_y, TILEX, TILEY,
3815 ox + i * mx, oy + i * my);
3816 Ct = Ct + FrameCounter - CT;
3819 DrawField_MM(nx, ny);
3822 if (!laser.fuse_off)
3824 DrawLaser(0, DL_LASER_ENABLED);
3826 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3828 AddDamagedField(nx, ny);
3830 laser.damage[laser.num_damages - 1].edge = 0;
3834 if (element == EL_BOMB)
3835 DeletePacMan(nx, ny);
3837 if (IS_WALL_AMOEBA(element) &&
3838 (LX + 2 * XS) / TILEX == nx &&
3839 (LY + 2 * YS) / TILEY == ny)
3849 static void InitMovingField_MM(int x, int y, int direction)
3851 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3852 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3854 MovDir[x][y] = direction;
3855 MovDir[newx][newy] = direction;
3857 if (Tile[newx][newy] == EL_EMPTY)
3858 Tile[newx][newy] = EL_BLOCKED;
3861 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3863 int direction = MovDir[x][y];
3864 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3865 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3871 static void Blocked2Moving_MM(int x, int y,
3872 int *comes_from_x, int *comes_from_y)
3874 int oldx = x, oldy = y;
3875 int direction = MovDir[x][y];
3877 if (direction == MV_LEFT)
3879 else if (direction == MV_RIGHT)
3881 else if (direction == MV_UP)
3883 else if (direction == MV_DOWN)
3886 *comes_from_x = oldx;
3887 *comes_from_y = oldy;
3890 static int MovingOrBlocked2Element_MM(int x, int y)
3892 int element = Tile[x][y];
3894 if (element == EL_BLOCKED)
3898 Blocked2Moving_MM(x, y, &oldx, &oldy);
3900 return Tile[oldx][oldy];
3907 static void RemoveField(int x, int y)
3909 Tile[x][y] = EL_EMPTY;
3916 static void RemoveMovingField_MM(int x, int y)
3918 int oldx = x, oldy = y, newx = x, newy = y;
3920 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3923 if (IS_MOVING(x, y))
3925 Moving2Blocked_MM(x, y, &newx, &newy);
3926 if (Tile[newx][newy] != EL_BLOCKED)
3929 else if (Tile[x][y] == EL_BLOCKED)
3931 Blocked2Moving_MM(x, y, &oldx, &oldy);
3932 if (!IS_MOVING(oldx, oldy))
3936 Tile[oldx][oldy] = EL_EMPTY;
3937 Tile[newx][newy] = EL_EMPTY;
3938 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3939 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3941 DrawLevelField_MM(oldx, oldy);
3942 DrawLevelField_MM(newx, newy);
3945 void PlaySoundLevel(int x, int y, int sound_nr)
3947 int sx = SCREENX(x), sy = SCREENY(y);
3949 int silence_distance = 8;
3951 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3952 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3955 if (!IN_LEV_FIELD(x, y) ||
3956 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3957 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3960 volume = SOUND_MAX_VOLUME;
3963 stereo = (sx - SCR_FIELDX/2) * 12;
3965 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3966 if (stereo > SOUND_MAX_RIGHT)
3967 stereo = SOUND_MAX_RIGHT;
3968 if (stereo < SOUND_MAX_LEFT)
3969 stereo = SOUND_MAX_LEFT;
3972 if (!IN_SCR_FIELD(sx, sy))
3974 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3975 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3977 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3980 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3983 static void RaiseScore_MM(int value)
3985 game_mm.score += value;
3988 void RaiseScoreElement_MM(int element)
3993 case EL_PACMAN_RIGHT:
3995 case EL_PACMAN_LEFT:
3996 case EL_PACMAN_DOWN:
3997 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4001 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4006 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4010 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4019 // ----------------------------------------------------------------------------
4020 // Mirror Magic game engine snapshot handling functions
4021 // ----------------------------------------------------------------------------
4023 void SaveEngineSnapshotValues_MM(void)
4027 engine_snapshot_mm.game_mm = game_mm;
4028 engine_snapshot_mm.laser = laser;
4030 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4032 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4034 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4035 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4036 engine_snapshot_mm.Box[x][y] = Box[x][y];
4037 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4041 engine_snapshot_mm.LX = LX;
4042 engine_snapshot_mm.LY = LY;
4043 engine_snapshot_mm.XS = XS;
4044 engine_snapshot_mm.YS = YS;
4045 engine_snapshot_mm.ELX = ELX;
4046 engine_snapshot_mm.ELY = ELY;
4047 engine_snapshot_mm.CT = CT;
4048 engine_snapshot_mm.Ct = Ct;
4050 engine_snapshot_mm.last_LX = last_LX;
4051 engine_snapshot_mm.last_LY = last_LY;
4052 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4053 engine_snapshot_mm.hold_x = hold_x;
4054 engine_snapshot_mm.hold_y = hold_y;
4055 engine_snapshot_mm.pacman_nr = pacman_nr;
4057 engine_snapshot_mm.rotate_delay = rotate_delay;
4058 engine_snapshot_mm.pacman_delay = pacman_delay;
4059 engine_snapshot_mm.energy_delay = energy_delay;
4060 engine_snapshot_mm.overload_delay = overload_delay;
4063 void LoadEngineSnapshotValues_MM(void)
4067 // stored engine snapshot buffers already restored at this point
4069 game_mm = engine_snapshot_mm.game_mm;
4070 laser = engine_snapshot_mm.laser;
4072 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4074 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4076 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4077 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4078 Box[x][y] = engine_snapshot_mm.Box[x][y];
4079 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4083 LX = engine_snapshot_mm.LX;
4084 LY = engine_snapshot_mm.LY;
4085 XS = engine_snapshot_mm.XS;
4086 YS = engine_snapshot_mm.YS;
4087 ELX = engine_snapshot_mm.ELX;
4088 ELY = engine_snapshot_mm.ELY;
4089 CT = engine_snapshot_mm.CT;
4090 Ct = engine_snapshot_mm.Ct;
4092 last_LX = engine_snapshot_mm.last_LX;
4093 last_LY = engine_snapshot_mm.last_LY;
4094 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4095 hold_x = engine_snapshot_mm.hold_x;
4096 hold_y = engine_snapshot_mm.hold_y;
4097 pacman_nr = engine_snapshot_mm.pacman_nr;
4099 rotate_delay = engine_snapshot_mm.rotate_delay;
4100 pacman_delay = engine_snapshot_mm.pacman_delay;
4101 energy_delay = engine_snapshot_mm.energy_delay;
4102 overload_delay = engine_snapshot_mm.overload_delay;
4104 RedrawPlayfield_MM();
4107 static int getAngleFromTouchDelta(int dx, int dy, int base)
4109 double pi = 3.141592653;
4110 double rad = atan2((double)-dy, (double)dx);
4111 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4112 double deg = rad2 * 180.0 / pi;
4114 return (int)(deg * base / 360.0 + 0.5) % base;
4117 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4119 // calculate start (source) position to be at the middle of the tile
4120 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4121 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4122 int dx = dst_mx - src_mx;
4123 int dy = dst_my - src_my;
4132 if (!IN_LEV_FIELD(x, y))
4135 element = Tile[x][y];
4137 if (!IS_MCDUFFIN(element) &&
4138 !IS_MIRROR(element) &&
4139 !IS_BEAMER(element) &&
4140 !IS_POLAR(element) &&
4141 !IS_POLAR_CROSS(element) &&
4142 !IS_DF_MIRROR(element))
4145 angle_old = get_element_angle(element);
4147 if (IS_MCDUFFIN(element))
4149 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4150 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4151 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4152 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4155 else if (IS_MIRROR(element) ||
4156 IS_DF_MIRROR(element))
4158 for (i = 0; i < laser.num_damages; i++)
4160 if (laser.damage[i].x == x &&
4161 laser.damage[i].y == y &&
4162 ObjHit(x, y, HIT_POS_CENTER))
4164 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4165 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4172 if (angle_new == -1)
4174 if (IS_MIRROR(element) ||
4175 IS_DF_MIRROR(element) ||
4179 if (IS_POLAR_CROSS(element))
4182 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4185 button = (angle_new == angle_old ? 0 :
4186 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4187 MB_LEFTBUTTON : MB_RIGHTBUTTON);