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
922 int end = 0, rf = laser.num_edges;
924 // do not scan laser again after the game was lost for whatever reason
925 if (game_mm.game_over)
928 if (laser.dest_element_last == EL_BOMB_ACTIVE ||
929 laser.dest_element_last == EL_MINE_ACTIVE ||
930 laser.dest_element_last == EL_GRAY_BALL_OPENING)
932 int x = laser.dest_element_last_x;
933 int y = laser.dest_element_last_y;
934 int element = laser.dest_element_last;
936 if (Tile[x][y] == element)
937 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB :
938 element == EL_MINE_ACTIVE ? EL_MINE : EL_BALL_GRAY);
940 if (Tile[x][y] == EL_BALL_GRAY)
943 laser.dest_element_last = EL_EMPTY;
944 laser.dest_element_last_x = -1;
945 laser.dest_element_last_y = -1;
948 laser.overloaded = FALSE;
949 laser.stops_inside_element = FALSE;
951 DrawLaser(0, DL_LASER_ENABLED);
954 Debug("game:mm:ScanLaser",
955 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
963 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
966 laser.overloaded = TRUE;
971 hit_mask = ScanPixel();
974 Debug("game:mm:ScanLaser",
975 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
979 // hit something -- check out what it was
980 ELX = (LX + XS) / TILEX;
981 ELY = (LY + YS) / TILEY;
984 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
985 hit_mask, LX, LY, ELX, ELY);
988 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
991 laser.dest_element = element;
996 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
998 /* we have hit the top-right and bottom-left element --
999 choose the bottom-left one */
1000 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
1001 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
1002 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
1003 ELX = (LX - 2) / TILEX;
1004 ELY = (LY + 2) / TILEY;
1007 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1009 /* we have hit the top-left and bottom-right element --
1010 choose the top-left one */
1011 // !!! SEE ABOVE !!!
1012 ELX = (LX - 2) / TILEX;
1013 ELY = (LY - 2) / TILEY;
1017 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1018 hit_mask, LX, LY, ELX, ELY);
1021 element = Tile[ELX][ELY];
1022 laser.dest_element = element;
1025 Debug("game:mm:ScanLaser",
1026 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1029 LX % TILEX, LY % TILEY,
1034 if (!IN_LEV_FIELD(ELX, ELY))
1035 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1039 if (element == EL_EMPTY)
1041 if (!HitOnlyAnEdge(hit_mask))
1044 else if (element == EL_FUSE_ON)
1046 if (HitPolarizer(element, hit_mask))
1049 else if (IS_GRID(element) || IS_DF_GRID(element))
1051 if (HitPolarizer(element, hit_mask))
1054 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1055 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1057 if (HitBlock(element, hit_mask))
1064 else if (IS_MCDUFFIN(element))
1066 if (HitLaserSource(element, hit_mask))
1069 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1070 IS_RECEIVER(element))
1072 if (HitLaserDestination(element, hit_mask))
1075 else if (IS_WALL(element))
1077 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1079 if (HitReflectingWalls(element, hit_mask))
1084 if (HitAbsorbingWalls(element, hit_mask))
1090 if (HitElement(element, hit_mask))
1095 DrawLaser(rf - 1, DL_LASER_ENABLED);
1096 rf = laser.num_edges;
1100 if (laser.dest_element != Tile[ELX][ELY])
1102 Debug("game:mm:ScanLaser",
1103 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1104 laser.dest_element, Tile[ELX][ELY]);
1108 if (!end && !laser.stops_inside_element && !StepBehind())
1111 Debug("game:mm:ScanLaser", "Go one step back");
1117 AddLaserEdge(LX, LY);
1121 DrawLaser(rf - 1, DL_LASER_ENABLED);
1123 Ct = CT = FrameCounter;
1126 if (!IN_LEV_FIELD(ELX, ELY))
1127 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1131 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1137 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1138 start_edge, num_edges, mode);
1143 Warn("DrawLaserExt: start_edge < 0");
1150 Warn("DrawLaserExt: num_edges < 0");
1156 if (mode == DL_LASER_DISABLED)
1158 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1162 // now draw the laser to the backbuffer and (if enabled) to the screen
1163 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1165 redraw_mask |= REDRAW_FIELD;
1167 if (mode == DL_LASER_ENABLED)
1170 // after the laser was deleted, the "damaged" graphics must be restored
1171 if (laser.num_damages)
1173 int damage_start = 0;
1176 // determine the starting edge, from which graphics need to be restored
1179 for (i = 0; i < laser.num_damages; i++)
1181 if (laser.damage[i].edge == start_edge + 1)
1190 // restore graphics from this starting edge to the end of damage list
1191 for (i = damage_start; i < laser.num_damages; i++)
1193 int lx = laser.damage[i].x;
1194 int ly = laser.damage[i].y;
1195 int element = Tile[lx][ly];
1197 if (Hit[lx][ly] == laser.damage[i].edge)
1198 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1201 if (Box[lx][ly] == laser.damage[i].edge)
1204 if (IS_DRAWABLE(element))
1205 DrawField_MM(lx, ly);
1208 elx = laser.damage[damage_start].x;
1209 ely = laser.damage[damage_start].y;
1210 element = Tile[elx][ely];
1213 if (IS_BEAMER(element))
1217 for (i = 0; i < laser.num_beamers; i++)
1218 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1220 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1221 mode, elx, ely, Hit[elx][ely], start_edge);
1222 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1223 get_element_angle(element), laser.damage[damage_start].angle);
1227 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1228 laser.num_beamers > 0 &&
1229 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1231 // element is outgoing beamer
1232 laser.num_damages = damage_start + 1;
1234 if (IS_BEAMER(element))
1235 laser.current_angle = get_element_angle(element);
1239 // element is incoming beamer or other element
1240 laser.num_damages = damage_start;
1241 laser.current_angle = laser.damage[laser.num_damages].angle;
1246 // no damages but McDuffin himself (who needs to be redrawn anyway)
1248 elx = laser.start_edge.x;
1249 ely = laser.start_edge.y;
1250 element = Tile[elx][ely];
1253 laser.num_edges = start_edge + 1;
1254 if (start_edge == 0)
1255 laser.current_angle = laser.start_angle;
1257 LX = laser.edge[start_edge].x - cSX2;
1258 LY = laser.edge[start_edge].y - cSY2;
1259 XS = 2 * Step[laser.current_angle].x;
1260 YS = 2 * Step[laser.current_angle].y;
1263 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1269 if (IS_BEAMER(element) ||
1270 IS_FIBRE_OPTIC(element) ||
1271 IS_PACMAN(element) ||
1272 IS_POLAR(element) ||
1273 IS_POLAR_CROSS(element) ||
1274 element == EL_FUSE_ON)
1279 Debug("game:mm:DrawLaserExt", "element == %d", element);
1282 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1283 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1287 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1288 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1289 (laser.num_beamers == 0 ||
1290 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1292 // element is incoming beamer or other element
1293 step_size = -step_size;
1298 if (IS_BEAMER(element))
1299 Debug("game:mm:DrawLaserExt",
1300 "start_edge == %d, laser.beamer_edge == %d",
1301 start_edge, laser.beamer_edge);
1304 LX += step_size * XS;
1305 LY += step_size * YS;
1307 else if (element != EL_EMPTY)
1316 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1321 void DrawLaser(int start_edge, int mode)
1323 if (laser.num_edges - start_edge < 0)
1325 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1330 // check if laser is interrupted by beamer element
1331 if (laser.num_beamers > 0 &&
1332 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1334 if (mode == DL_LASER_ENABLED)
1337 int tmp_start_edge = start_edge;
1339 // draw laser segments forward from the start to the last beamer
1340 for (i = 0; i < laser.num_beamers; i++)
1342 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1344 if (tmp_num_edges <= 0)
1348 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1349 i, laser.beamer_edge[i], tmp_start_edge);
1352 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1354 tmp_start_edge = laser.beamer_edge[i];
1357 // draw last segment from last beamer to the end
1358 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1364 int last_num_edges = laser.num_edges;
1365 int num_beamers = laser.num_beamers;
1367 // delete laser segments backward from the end to the first beamer
1368 for (i = num_beamers - 1; i >= 0; i--)
1370 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1372 if (laser.beamer_edge[i] - start_edge <= 0)
1375 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1377 last_num_edges = laser.beamer_edge[i];
1378 laser.num_beamers--;
1382 if (last_num_edges - start_edge <= 0)
1383 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1384 last_num_edges, start_edge);
1387 // special case when rotating first beamer: delete laser edge on beamer
1388 // (but do not start scanning on previous edge to prevent mirror sound)
1389 if (last_num_edges - start_edge == 1 && start_edge > 0)
1390 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1392 // delete first segment from start to the first beamer
1393 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1398 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1401 game_mm.laser_enabled = mode;
1404 void DrawLaser_MM(void)
1406 DrawLaser(0, game_mm.laser_enabled);
1409 boolean HitElement(int element, int hit_mask)
1411 if (HitOnlyAnEdge(hit_mask))
1414 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1415 element = MovingOrBlocked2Element_MM(ELX, ELY);
1418 Debug("game:mm:HitElement", "(1): element == %d", element);
1422 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1423 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1426 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1430 AddDamagedField(ELX, ELY);
1432 // this is more precise: check if laser would go through the center
1433 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1435 // skip the whole element before continuing the scan
1441 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1443 if (LX/TILEX > ELX || LY/TILEY > ELY)
1445 /* skipping scan positions to the right and down skips one scan
1446 position too much, because this is only the top left scan position
1447 of totally four scan positions (plus one to the right, one to the
1448 bottom and one to the bottom right) */
1458 Debug("game:mm:HitElement", "(2): element == %d", element);
1461 if (LX + 5 * XS < 0 ||
1471 Debug("game:mm:HitElement", "(3): element == %d", element);
1474 if (IS_POLAR(element) &&
1475 ((element - EL_POLAR_START) % 2 ||
1476 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1478 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1480 laser.num_damages--;
1485 if (IS_POLAR_CROSS(element) &&
1486 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1488 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1490 laser.num_damages--;
1495 if (!IS_BEAMER(element) &&
1496 !IS_FIBRE_OPTIC(element) &&
1497 !IS_GRID_WOOD(element) &&
1498 element != EL_FUEL_EMPTY)
1501 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1502 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1504 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1507 LX = ELX * TILEX + 14;
1508 LY = ELY * TILEY + 14;
1510 AddLaserEdge(LX, LY);
1513 if (IS_MIRROR(element) ||
1514 IS_MIRROR_FIXED(element) ||
1515 IS_POLAR(element) ||
1516 IS_POLAR_CROSS(element) ||
1517 IS_DF_MIRROR(element) ||
1518 IS_DF_MIRROR_AUTO(element) ||
1519 element == EL_PRISM ||
1520 element == EL_REFRACTOR)
1522 int current_angle = laser.current_angle;
1525 laser.num_damages--;
1527 AddDamagedField(ELX, ELY);
1529 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1532 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1534 if (IS_MIRROR(element) ||
1535 IS_MIRROR_FIXED(element) ||
1536 IS_DF_MIRROR(element) ||
1537 IS_DF_MIRROR_AUTO(element))
1538 laser.current_angle = get_mirrored_angle(laser.current_angle,
1539 get_element_angle(element));
1541 if (element == EL_PRISM || element == EL_REFRACTOR)
1542 laser.current_angle = RND(16);
1544 XS = 2 * Step[laser.current_angle].x;
1545 YS = 2 * Step[laser.current_angle].y;
1547 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1552 LX += step_size * XS;
1553 LY += step_size * YS;
1555 // draw sparkles on mirror
1556 if ((IS_MIRROR(element) ||
1557 IS_MIRROR_FIXED(element) ||
1558 element == EL_PRISM) &&
1559 current_angle != laser.current_angle)
1561 MovDelay[ELX][ELY] = 11; // start animation
1564 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1565 current_angle != laser.current_angle)
1566 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1569 (get_opposite_angle(laser.current_angle) ==
1570 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1572 return (laser.overloaded ? TRUE : FALSE);
1575 if (element == EL_FUEL_FULL)
1577 laser.stops_inside_element = TRUE;
1582 if (element == EL_BOMB || element == EL_MINE)
1584 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1586 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1588 laser.dest_element_last = Tile[ELX][ELY];
1589 laser.dest_element_last_x = ELX;
1590 laser.dest_element_last_y = ELY;
1592 if (element == EL_MINE)
1593 laser.overloaded = TRUE;
1596 if (element == EL_KETTLE ||
1597 element == EL_CELL ||
1598 element == EL_KEY ||
1599 element == EL_LIGHTBALL ||
1600 element == EL_PACMAN ||
1603 if (!IS_PACMAN(element))
1606 if (element == EL_PACMAN)
1609 if (element == EL_KETTLE || element == EL_CELL)
1611 if (game_mm.kettles_still_needed > 0)
1612 game_mm.kettles_still_needed--;
1614 game.snapshot.collected_item = TRUE;
1616 if (game_mm.kettles_still_needed == 0)
1620 DrawLaser(0, DL_LASER_ENABLED);
1623 else if (element == EL_KEY)
1627 else if (IS_PACMAN(element))
1629 DeletePacMan(ELX, ELY);
1632 RaiseScoreElement_MM(element);
1637 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1639 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1641 DrawLaser(0, DL_LASER_ENABLED);
1643 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1645 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1646 game_mm.lights_still_needed--;
1650 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1651 game_mm.lights_still_needed++;
1654 DrawField_MM(ELX, ELY);
1655 DrawLaser(0, DL_LASER_ENABLED);
1660 laser.stops_inside_element = TRUE;
1666 Debug("game:mm:HitElement", "(4): element == %d", element);
1669 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1670 laser.num_beamers < MAX_NUM_BEAMERS &&
1671 laser.beamer[BEAMER_NR(element)][1].num)
1673 int beamer_angle = get_element_angle(element);
1674 int beamer_nr = BEAMER_NR(element);
1678 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1681 laser.num_damages--;
1683 if (IS_FIBRE_OPTIC(element) ||
1684 laser.current_angle == get_opposite_angle(beamer_angle))
1688 LX = ELX * TILEX + 14;
1689 LY = ELY * TILEY + 14;
1691 AddLaserEdge(LX, LY);
1692 AddDamagedField(ELX, ELY);
1694 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1697 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1699 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1700 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1701 ELX = laser.beamer[beamer_nr][pos].x;
1702 ELY = laser.beamer[beamer_nr][pos].y;
1703 LX = ELX * TILEX + 14;
1704 LY = ELY * TILEY + 14;
1706 if (IS_BEAMER(element))
1708 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1709 XS = 2 * Step[laser.current_angle].x;
1710 YS = 2 * Step[laser.current_angle].y;
1713 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1715 AddLaserEdge(LX, LY);
1716 AddDamagedField(ELX, ELY);
1718 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1721 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1723 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1728 LX += step_size * XS;
1729 LY += step_size * YS;
1731 laser.num_beamers++;
1740 boolean HitOnlyAnEdge(int hit_mask)
1742 // check if the laser hit only the edge of an element and, if so, go on
1745 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1749 if ((hit_mask == HIT_MASK_TOPLEFT ||
1750 hit_mask == HIT_MASK_TOPRIGHT ||
1751 hit_mask == HIT_MASK_BOTTOMLEFT ||
1752 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1753 laser.current_angle % 4) // angle is not 90°
1757 if (hit_mask == HIT_MASK_TOPLEFT)
1762 else if (hit_mask == HIT_MASK_TOPRIGHT)
1767 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1772 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1778 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1784 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1791 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1797 boolean HitPolarizer(int element, int hit_mask)
1799 if (HitOnlyAnEdge(hit_mask))
1802 if (IS_DF_GRID(element))
1804 int grid_angle = get_element_angle(element);
1807 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1808 grid_angle, laser.current_angle);
1811 AddLaserEdge(LX, LY);
1812 AddDamagedField(ELX, ELY);
1815 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1817 if (laser.current_angle == grid_angle ||
1818 laser.current_angle == get_opposite_angle(grid_angle))
1820 // skip the whole element before continuing the scan
1826 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1828 if (LX/TILEX > ELX || LY/TILEY > ELY)
1830 /* skipping scan positions to the right and down skips one scan
1831 position too much, because this is only the top left scan position
1832 of totally four scan positions (plus one to the right, one to the
1833 bottom and one to the bottom right) */
1839 AddLaserEdge(LX, LY);
1845 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1847 LX / TILEX, LY / TILEY,
1848 LX % TILEX, LY % TILEY);
1853 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1855 return HitReflectingWalls(element, hit_mask);
1859 return HitAbsorbingWalls(element, hit_mask);
1862 else if (IS_GRID_STEEL(element))
1864 return HitReflectingWalls(element, hit_mask);
1866 else // IS_GRID_WOOD
1868 return HitAbsorbingWalls(element, hit_mask);
1874 boolean HitBlock(int element, int hit_mask)
1876 boolean check = FALSE;
1878 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1879 game_mm.num_keys == 0)
1882 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1885 int ex = ELX * TILEX + 14;
1886 int ey = ELY * TILEY + 14;
1890 for (i = 1; i < 32; i++)
1895 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1900 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1901 return HitAbsorbingWalls(element, hit_mask);
1905 AddLaserEdge(LX - XS, LY - YS);
1906 AddDamagedField(ELX, ELY);
1909 Box[ELX][ELY] = laser.num_edges;
1911 return HitReflectingWalls(element, hit_mask);
1914 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1916 int xs = XS / 2, ys = YS / 2;
1917 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1918 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1920 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1921 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1923 laser.overloaded = (element == EL_GATE_STONE);
1928 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1929 (hit_mask == HIT_MASK_TOP ||
1930 hit_mask == HIT_MASK_LEFT ||
1931 hit_mask == HIT_MASK_RIGHT ||
1932 hit_mask == HIT_MASK_BOTTOM))
1933 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1934 hit_mask == HIT_MASK_BOTTOM),
1935 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1936 hit_mask == HIT_MASK_RIGHT));
1937 AddLaserEdge(LX, LY);
1943 if (element == EL_GATE_STONE && Box[ELX][ELY])
1945 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1957 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1959 int xs = XS / 2, ys = YS / 2;
1960 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1961 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1963 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1964 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1966 laser.overloaded = (element == EL_BLOCK_STONE);
1971 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1972 (hit_mask == HIT_MASK_TOP ||
1973 hit_mask == HIT_MASK_LEFT ||
1974 hit_mask == HIT_MASK_RIGHT ||
1975 hit_mask == HIT_MASK_BOTTOM))
1976 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1977 hit_mask == HIT_MASK_BOTTOM),
1978 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1979 hit_mask == HIT_MASK_RIGHT));
1980 AddDamagedField(ELX, ELY);
1982 LX = ELX * TILEX + 14;
1983 LY = ELY * TILEY + 14;
1985 AddLaserEdge(LX, LY);
1987 laser.stops_inside_element = TRUE;
1995 boolean HitLaserSource(int element, int hit_mask)
1997 if (HitOnlyAnEdge(hit_mask))
2000 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2002 laser.overloaded = TRUE;
2007 boolean HitLaserDestination(int element, int hit_mask)
2009 if (HitOnlyAnEdge(hit_mask))
2012 if (element != EL_EXIT_OPEN &&
2013 !(IS_RECEIVER(element) &&
2014 game_mm.kettles_still_needed == 0 &&
2015 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2017 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2022 if (IS_RECEIVER(element) ||
2023 (IS_22_5_ANGLE(laser.current_angle) &&
2024 (ELX != (LX + 6 * XS) / TILEX ||
2025 ELY != (LY + 6 * YS) / TILEY ||
2034 LX = ELX * TILEX + 14;
2035 LY = ELY * TILEY + 14;
2037 laser.stops_inside_element = TRUE;
2040 AddLaserEdge(LX, LY);
2041 AddDamagedField(ELX, ELY);
2043 if (game_mm.lights_still_needed == 0)
2045 game_mm.level_solved = TRUE;
2047 SetTileCursorActive(FALSE);
2053 boolean HitReflectingWalls(int element, int hit_mask)
2055 // check if laser hits side of a wall with an angle that is not 90°
2056 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2057 hit_mask == HIT_MASK_LEFT ||
2058 hit_mask == HIT_MASK_RIGHT ||
2059 hit_mask == HIT_MASK_BOTTOM))
2061 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2066 if (!IS_DF_GRID(element))
2067 AddLaserEdge(LX, LY);
2069 // check if laser hits wall with an angle of 45°
2070 if (!IS_22_5_ANGLE(laser.current_angle))
2072 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2075 laser.current_angle = get_mirrored_angle(laser.current_angle,
2078 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2081 laser.current_angle = get_mirrored_angle(laser.current_angle,
2085 AddLaserEdge(LX, LY);
2087 XS = 2 * Step[laser.current_angle].x;
2088 YS = 2 * Step[laser.current_angle].y;
2092 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2094 laser.current_angle = get_mirrored_angle(laser.current_angle,
2099 if (!IS_DF_GRID(element))
2100 AddLaserEdge(LX, LY);
2105 if (!IS_DF_GRID(element))
2106 AddLaserEdge(LX, LY + YS / 2);
2109 if (!IS_DF_GRID(element))
2110 AddLaserEdge(LX, LY);
2113 YS = 2 * Step[laser.current_angle].y;
2117 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2119 laser.current_angle = get_mirrored_angle(laser.current_angle,
2124 if (!IS_DF_GRID(element))
2125 AddLaserEdge(LX, LY);
2130 if (!IS_DF_GRID(element))
2131 AddLaserEdge(LX + XS / 2, LY);
2134 if (!IS_DF_GRID(element))
2135 AddLaserEdge(LX, LY);
2138 XS = 2 * Step[laser.current_angle].x;
2144 // reflection at the edge of reflecting DF style wall
2145 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2147 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2148 hit_mask == HIT_MASK_TOPRIGHT) ||
2149 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2150 hit_mask == HIT_MASK_TOPLEFT) ||
2151 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2152 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2153 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2154 hit_mask == HIT_MASK_BOTTOMRIGHT))
2157 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2158 ANG_MIRROR_135 : ANG_MIRROR_45);
2160 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2162 AddDamagedField(ELX, ELY);
2163 AddLaserEdge(LX, LY);
2165 laser.current_angle = get_mirrored_angle(laser.current_angle,
2173 AddLaserEdge(LX, LY);
2179 // reflection inside an edge of reflecting DF style wall
2180 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2182 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2183 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2184 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2185 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2186 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2187 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2188 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2189 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2192 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2193 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2194 ANG_MIRROR_135 : ANG_MIRROR_45);
2196 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2199 AddDamagedField(ELX, ELY);
2202 AddLaserEdge(LX - XS, LY - YS);
2203 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2204 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2206 laser.current_angle = get_mirrored_angle(laser.current_angle,
2214 AddLaserEdge(LX, LY);
2220 // check if laser hits DF style wall with an angle of 90°
2221 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2223 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2224 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2225 (IS_VERT_ANGLE(laser.current_angle) &&
2226 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2228 // laser at last step touched nothing or the same side of the wall
2229 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2231 AddDamagedField(ELX, ELY);
2238 last_hit_mask = hit_mask;
2245 if (!HitOnlyAnEdge(hit_mask))
2247 laser.overloaded = TRUE;
2255 boolean HitAbsorbingWalls(int element, int hit_mask)
2257 if (HitOnlyAnEdge(hit_mask))
2261 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2263 AddLaserEdge(LX - XS, LY - YS);
2270 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2272 AddLaserEdge(LX - XS, LY - YS);
2278 if (IS_WALL_WOOD(element) ||
2279 IS_DF_WALL_WOOD(element) ||
2280 IS_GRID_WOOD(element) ||
2281 IS_GRID_WOOD_FIXED(element) ||
2282 IS_GRID_WOOD_AUTO(element) ||
2283 element == EL_FUSE_ON ||
2284 element == EL_BLOCK_WOOD ||
2285 element == EL_GATE_WOOD)
2287 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2292 if (IS_WALL_ICE(element))
2296 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2297 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2299 // check if laser hits wall with an angle of 90°
2300 if (IS_90_ANGLE(laser.current_angle))
2301 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2303 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2307 for (i = 0; i < 4; i++)
2309 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2310 mask = 15 - (8 >> i);
2311 else if (ABS(XS) == 4 &&
2313 (XS > 0) == (i % 2) &&
2314 (YS < 0) == (i / 2))
2315 mask = 3 + (i / 2) * 9;
2316 else if (ABS(YS) == 4 &&
2318 (XS < 0) == (i % 2) &&
2319 (YS > 0) == (i / 2))
2320 mask = 5 + (i % 2) * 5;
2324 laser.wall_mask = mask;
2326 else if (IS_WALL_AMOEBA(element))
2328 int elx = (LX - 2 * XS) / TILEX;
2329 int ely = (LY - 2 * YS) / TILEY;
2330 int element2 = Tile[elx][ely];
2333 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2335 laser.dest_element = EL_EMPTY;
2343 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2344 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2346 if (IS_90_ANGLE(laser.current_angle))
2347 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2349 laser.dest_element = element2 | EL_WALL_AMOEBA;
2351 laser.wall_mask = mask;
2357 static void OpenExit(int x, int y)
2361 if (!MovDelay[x][y]) // next animation frame
2362 MovDelay[x][y] = 4 * delay;
2364 if (MovDelay[x][y]) // wait some time before next frame
2369 phase = MovDelay[x][y] / delay;
2371 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2372 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2374 if (!MovDelay[x][y])
2376 Tile[x][y] = EL_EXIT_OPEN;
2382 static void OpenSurpriseBall(int x, int y)
2386 if (!MovDelay[x][y]) // next animation frame
2387 MovDelay[x][y] = 50 * delay;
2389 if (MovDelay[x][y]) // wait some time before next frame
2393 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2396 int graphic = el2gfx(Store[x][y]);
2398 int dx = RND(26), dy = RND(26);
2400 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2402 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2403 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2405 MarkTileDirty(x, y);
2408 if (!MovDelay[x][y])
2410 Tile[x][y] = Store[x][y];
2411 Store[x][y] = Store2[x][y] = 0;
2420 static void MeltIce(int x, int y)
2425 if (!MovDelay[x][y]) // next animation frame
2426 MovDelay[x][y] = frames * delay;
2428 if (MovDelay[x][y]) // wait some time before next frame
2431 int wall_mask = Store2[x][y];
2432 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2435 phase = frames - MovDelay[x][y] / delay - 1;
2437 if (!MovDelay[x][y])
2441 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2442 Store[x][y] = Store2[x][y] = 0;
2444 DrawWalls_MM(x, y, Tile[x][y]);
2446 if (Tile[x][y] == EL_WALL_ICE)
2447 Tile[x][y] = EL_EMPTY;
2449 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2450 if (laser.damage[i].is_mirror)
2454 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2456 DrawLaser(0, DL_LASER_DISABLED);
2460 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2462 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2464 laser.redraw = TRUE;
2469 static void GrowAmoeba(int x, int y)
2474 if (!MovDelay[x][y]) // next animation frame
2475 MovDelay[x][y] = frames * delay;
2477 if (MovDelay[x][y]) // wait some time before next frame
2480 int wall_mask = Store2[x][y];
2481 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2484 phase = MovDelay[x][y] / delay;
2486 if (!MovDelay[x][y])
2488 Tile[x][y] = real_element;
2489 Store[x][y] = Store2[x][y] = 0;
2491 DrawWalls_MM(x, y, Tile[x][y]);
2492 DrawLaser(0, DL_LASER_ENABLED);
2494 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2496 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2501 static void DrawFieldAnimated_MM(int x, int y)
2503 int element = Tile[x][y];
2505 if (IS_BLOCKED(x, y))
2510 if (IS_MIRROR(element) ||
2511 IS_MIRROR_FIXED(element) ||
2512 element == EL_PRISM)
2514 if (MovDelay[x][y] != 0) // wait some time before next frame
2518 if (MovDelay[x][y] != 0)
2520 int graphic = IMG_TWINKLE_WHITE;
2521 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2523 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2528 laser.redraw = TRUE;
2531 static void Explode_MM(int x, int y, int phase, int mode)
2533 int num_phase = 9, delay = 2;
2534 int last_phase = num_phase * delay;
2535 int half_phase = (num_phase / 2) * delay;
2537 laser.redraw = TRUE;
2539 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2541 int center_element = Tile[x][y];
2543 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2545 // put moving element to center field (and let it explode there)
2546 center_element = MovingOrBlocked2Element_MM(x, y);
2547 RemoveMovingField_MM(x, y);
2549 Tile[x][y] = center_element;
2552 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2553 Store[x][y] = center_element;
2555 Store[x][y] = EL_EMPTY;
2557 Store2[x][y] = mode;
2559 Tile[x][y] = EL_EXPLODING_OPAQUE;
2560 GfxElement[x][y] = center_element;
2562 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2564 ExplodePhase[x][y] = 1;
2570 GfxFrame[x][y] = 0; // restart explosion animation
2572 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2574 if (phase == half_phase)
2576 Tile[x][y] = EL_EXPLODING_TRANSP;
2578 if (x == ELX && y == ELY)
2582 if (phase == last_phase)
2584 if (Store[x][y] == EL_BOMB_ACTIVE)
2586 DrawLaser(0, DL_LASER_DISABLED);
2589 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2590 Store[x][y] = EL_EMPTY;
2592 GameOver_MM(GAME_OVER_DELAYED);
2594 laser.overloaded = FALSE;
2596 else if (IS_MCDUFFIN(Store[x][y]))
2598 Store[x][y] = EL_EMPTY;
2600 GameOver_MM(GAME_OVER_BOMB);
2603 Tile[x][y] = Store[x][y];
2604 Store[x][y] = Store2[x][y] = 0;
2605 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2610 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2612 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2613 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2615 DrawGraphicAnimation_MM(x, y, graphic, frame);
2617 MarkTileDirty(x, y);
2621 static void Bang_MM(int x, int y)
2623 int element = Tile[x][y];
2626 DrawLaser(0, DL_LASER_ENABLED);
2629 if (IS_PACMAN(element))
2630 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2631 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2632 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2633 else if (element == EL_KEY)
2634 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2636 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2638 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2641 void TurnRound(int x, int y)
2653 { 0, 0 }, { 0, 0 }, { 0, 0 },
2658 int left, right, back;
2662 { MV_DOWN, MV_UP, MV_RIGHT },
2663 { MV_UP, MV_DOWN, MV_LEFT },
2665 { MV_LEFT, MV_RIGHT, MV_DOWN },
2666 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2667 { MV_RIGHT, MV_LEFT, MV_UP }
2670 int element = Tile[x][y];
2671 int old_move_dir = MovDir[x][y];
2672 int right_dir = turn[old_move_dir].right;
2673 int back_dir = turn[old_move_dir].back;
2674 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2675 int right_x = x + right_dx, right_y = y + right_dy;
2677 if (element == EL_PACMAN)
2679 boolean can_turn_right = FALSE;
2681 if (IN_LEV_FIELD(right_x, right_y) &&
2682 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2683 can_turn_right = TRUE;
2686 MovDir[x][y] = right_dir;
2688 MovDir[x][y] = back_dir;
2694 static void StartMoving_MM(int x, int y)
2696 int element = Tile[x][y];
2701 if (CAN_MOVE(element))
2705 if (MovDelay[x][y]) // wait some time before next movement
2713 // now make next step
2715 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2717 if (element == EL_PACMAN &&
2718 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2719 !ObjHit(newx, newy, HIT_POS_CENTER))
2721 Store[newx][newy] = Tile[newx][newy];
2722 Tile[newx][newy] = EL_EMPTY;
2724 DrawField_MM(newx, newy);
2726 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2727 ObjHit(newx, newy, HIT_POS_CENTER))
2729 // object was running against a wall
2736 InitMovingField_MM(x, y, MovDir[x][y]);
2740 ContinueMoving_MM(x, y);
2743 static void ContinueMoving_MM(int x, int y)
2745 int element = Tile[x][y];
2746 int direction = MovDir[x][y];
2747 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2748 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2749 int horiz_move = (dx!=0);
2750 int newx = x + dx, newy = y + dy;
2751 int step = (horiz_move ? dx : dy) * TILEX / 8;
2753 MovPos[x][y] += step;
2755 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2757 Tile[x][y] = EL_EMPTY;
2758 Tile[newx][newy] = element;
2760 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2761 MovDelay[newx][newy] = 0;
2763 if (!CAN_MOVE(element))
2764 MovDir[newx][newy] = 0;
2767 DrawField_MM(newx, newy);
2769 Stop[newx][newy] = TRUE;
2771 if (element == EL_PACMAN)
2773 if (Store[newx][newy] == EL_BOMB)
2774 Bang_MM(newx, newy);
2776 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2777 (LX + 2 * XS) / TILEX == newx &&
2778 (LY + 2 * YS) / TILEY == newy)
2785 else // still moving on
2790 laser.redraw = TRUE;
2793 boolean ClickElement(int x, int y, int button)
2795 static DelayCounter click_delay = { CLICK_DELAY };
2796 static boolean new_button = TRUE;
2797 boolean element_clicked = FALSE;
2802 // initialize static variables
2803 click_delay.count = 0;
2804 click_delay.value = CLICK_DELAY;
2810 // do not rotate objects hit by the laser after the game was solved
2811 if (game_mm.level_solved && Hit[x][y])
2814 if (button == MB_RELEASED)
2817 click_delay.value = CLICK_DELAY;
2819 // release eventually hold auto-rotating mirror
2820 RotateMirror(x, y, MB_RELEASED);
2825 if (!FrameReached(&click_delay) && !new_button)
2828 if (button == MB_MIDDLEBUTTON) // middle button has no function
2831 if (!IN_LEV_FIELD(x, y))
2834 if (Tile[x][y] == EL_EMPTY)
2837 element = Tile[x][y];
2839 if (IS_MIRROR(element) ||
2840 IS_BEAMER(element) ||
2841 IS_POLAR(element) ||
2842 IS_POLAR_CROSS(element) ||
2843 IS_DF_MIRROR(element) ||
2844 IS_DF_MIRROR_AUTO(element))
2846 RotateMirror(x, y, button);
2848 element_clicked = TRUE;
2850 else if (IS_MCDUFFIN(element))
2852 if (!laser.fuse_off)
2854 DrawLaser(0, DL_LASER_DISABLED);
2861 element = get_rotated_element(element, BUTTON_ROTATION(button));
2862 laser.start_angle = get_element_angle(element);
2866 Tile[x][y] = element;
2873 if (!laser.fuse_off)
2876 element_clicked = TRUE;
2878 else if (element == EL_FUSE_ON && laser.fuse_off)
2880 if (x != laser.fuse_x || y != laser.fuse_y)
2883 laser.fuse_off = FALSE;
2884 laser.fuse_x = laser.fuse_y = -1;
2886 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2889 element_clicked = TRUE;
2891 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2893 laser.fuse_off = TRUE;
2896 laser.overloaded = FALSE;
2898 DrawLaser(0, DL_LASER_DISABLED);
2899 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2901 element_clicked = TRUE;
2903 else if (element == EL_LIGHTBALL)
2906 RaiseScoreElement_MM(element);
2907 DrawLaser(0, DL_LASER_ENABLED);
2909 element_clicked = TRUE;
2912 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2915 return element_clicked;
2918 void RotateMirror(int x, int y, int button)
2920 if (button == MB_RELEASED)
2922 // release eventually hold auto-rotating mirror
2929 if (IS_MIRROR(Tile[x][y]) ||
2930 IS_POLAR_CROSS(Tile[x][y]) ||
2931 IS_POLAR(Tile[x][y]) ||
2932 IS_BEAMER(Tile[x][y]) ||
2933 IS_DF_MIRROR(Tile[x][y]) ||
2934 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2935 IS_GRID_WOOD_AUTO(Tile[x][y]))
2937 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2939 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2941 if (button == MB_LEFTBUTTON)
2943 // left mouse button only for manual adjustment, no auto-rotating;
2944 // freeze mirror for until mouse button released
2948 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2950 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2954 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2956 int edge = Hit[x][y];
2962 DrawLaser(edge - 1, DL_LASER_DISABLED);
2966 else if (ObjHit(x, y, HIT_POS_CENTER))
2968 int edge = Hit[x][y];
2972 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2977 DrawLaser(edge - 1, DL_LASER_DISABLED);
2984 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2989 if ((IS_BEAMER(Tile[x][y]) ||
2990 IS_POLAR(Tile[x][y]) ||
2991 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2995 if (IS_BEAMER(Tile[x][y]))
2998 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2999 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3011 DrawLaser(0, DL_LASER_ENABLED);
3015 static void AutoRotateMirrors(void)
3019 if (!FrameReached(&rotate_delay))
3022 for (x = 0; x < lev_fieldx; x++)
3024 for (y = 0; y < lev_fieldy; y++)
3026 int element = Tile[x][y];
3028 // do not rotate objects hit by the laser after the game was solved
3029 if (game_mm.level_solved && Hit[x][y])
3032 if (IS_DF_MIRROR_AUTO(element) ||
3033 IS_GRID_WOOD_AUTO(element) ||
3034 IS_GRID_STEEL_AUTO(element) ||
3035 element == EL_REFRACTOR)
3036 RotateMirror(x, y, MB_RIGHTBUTTON);
3041 boolean ObjHit(int obx, int oby, int bits)
3048 if (bits & HIT_POS_CENTER)
3050 if (CheckLaserPixel(cSX + obx + 15,
3055 if (bits & HIT_POS_EDGE)
3057 for (i = 0; i < 4; i++)
3058 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3059 cSY + oby + 31 * (i / 2)))
3063 if (bits & HIT_POS_BETWEEN)
3065 for (i = 0; i < 4; i++)
3066 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3067 cSY + 4 + oby + 22 * (i / 2)))
3074 void DeletePacMan(int px, int py)
3080 if (game_mm.num_pacman <= 1)
3082 game_mm.num_pacman = 0;
3086 for (i = 0; i < game_mm.num_pacman; i++)
3087 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3090 game_mm.num_pacman--;
3092 for (j = i; j < game_mm.num_pacman; j++)
3094 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3095 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3096 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3100 void ColorCycling(void)
3102 static int CC, Cc = 0;
3104 static int color, old = 0xF00, new = 0x010, mult = 1;
3105 static unsigned short red, green, blue;
3107 if (color_status == STATIC_COLORS)
3112 if (CC < Cc || CC > Cc + 2)
3116 color = old + new * mult;
3122 if (ABS(mult) == 16)
3132 red = 0x0e00 * ((color & 0xF00) >> 8);
3133 green = 0x0e00 * ((color & 0x0F0) >> 4);
3134 blue = 0x0e00 * ((color & 0x00F));
3135 SetRGB(pen_magicolor[0], red, green, blue);
3137 red = 0x1111 * ((color & 0xF00) >> 8);
3138 green = 0x1111 * ((color & 0x0F0) >> 4);
3139 blue = 0x1111 * ((color & 0x00F));
3140 SetRGB(pen_magicolor[1], red, green, blue);
3144 static void GameActions_MM_Ext(void)
3151 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3154 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3156 element = Tile[x][y];
3158 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3159 StartMoving_MM(x, y);
3160 else if (IS_MOVING(x, y))
3161 ContinueMoving_MM(x, y);
3162 else if (IS_EXPLODING(element))
3163 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3164 else if (element == EL_EXIT_OPENING)
3166 else if (element == EL_GRAY_BALL_OPENING)
3167 OpenSurpriseBall(x, y);
3168 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3170 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3173 DrawFieldAnimated_MM(x, y);
3176 AutoRotateMirrors();
3179 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3181 // redraw after Explode_MM() ...
3183 DrawLaser(0, DL_LASER_ENABLED);
3184 laser.redraw = FALSE;
3189 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3193 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3195 DrawLaser(0, DL_LASER_DISABLED);
3200 // skip all following game actions if game is over
3201 if (game_mm.game_over)
3204 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3208 GameOver_MM(GAME_OVER_NO_ENERGY);
3213 if (FrameReached(&energy_delay))
3215 if (game_mm.energy_left > 0)
3216 game_mm.energy_left--;
3218 // when out of energy, wait another frame to play "out of time" sound
3221 element = laser.dest_element;
3224 if (element != Tile[ELX][ELY])
3226 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3227 element, Tile[ELX][ELY]);
3231 if (!laser.overloaded && laser.overload_value == 0 &&
3232 element != EL_BOMB &&
3233 element != EL_BOMB_ACTIVE &&
3234 element != EL_MINE &&
3235 element != EL_MINE_ACTIVE &&
3236 element != EL_BALL_GRAY &&
3237 element != EL_BLOCK_STONE &&
3238 element != EL_BLOCK_WOOD &&
3239 element != EL_FUSE_ON &&
3240 element != EL_FUEL_FULL &&
3241 !IS_WALL_ICE(element) &&
3242 !IS_WALL_AMOEBA(element))
3245 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3247 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3248 (!laser.overloaded && laser.overload_value > 0)) &&
3249 FrameReached(&overload_delay))
3251 if (laser.overloaded)
3252 laser.overload_value++;
3254 laser.overload_value--;
3256 if (game_mm.cheat_no_overload)
3258 laser.overloaded = FALSE;
3259 laser.overload_value = 0;
3262 game_mm.laser_overload_value = laser.overload_value;
3264 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3266 SetLaserColor(0xFF);
3268 DrawLaser(0, DL_LASER_ENABLED);
3271 if (!laser.overloaded)
3272 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3273 else if (setup.sound_loops)
3274 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3276 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3278 if (laser.overloaded)
3281 BlitBitmap(pix[PIX_DOOR], drawto,
3282 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3283 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3284 - laser.overload_value,
3285 OVERLOAD_XSIZE, laser.overload_value,
3286 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3287 - laser.overload_value);
3289 redraw_mask |= REDRAW_DOOR_1;
3294 BlitBitmap(pix[PIX_DOOR], drawto,
3295 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3296 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3297 DX_OVERLOAD, DY_OVERLOAD);
3299 redraw_mask |= REDRAW_DOOR_1;
3302 if (laser.overload_value == MAX_LASER_OVERLOAD)
3304 UpdateAndDisplayGameControlValues();
3308 GameOver_MM(GAME_OVER_OVERLOADED);
3319 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3321 if (game_mm.cheat_no_explosion)
3326 laser.dest_element = EL_EXPLODING_OPAQUE;
3331 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3333 laser.fuse_off = TRUE;
3337 DrawLaser(0, DL_LASER_DISABLED);
3338 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3341 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3343 if (!Store2[ELX][ELY]) // check if content element not yet determined
3345 int last_anim_random_frame = gfx.anim_random_frame;
3348 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3349 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3351 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3352 native_mm_level.ball_choice_mode, 0,
3353 game_mm.ball_choice_pos);
3355 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3356 gfx.anim_random_frame = last_anim_random_frame;
3358 game_mm.ball_choice_pos++;
3360 int new_element = native_mm_level.ball_content[element_pos];
3362 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3363 Store2[ELX][ELY] = TRUE;
3366 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3368 // !!! CHECK AGAIN: Laser on Polarizer !!!
3371 laser.dest_element_last = Tile[ELX][ELY];
3372 laser.dest_element_last_x = ELX;
3373 laser.dest_element_last_y = ELY;
3383 element = EL_MIRROR_START + RND(16);
3389 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3396 element = (rnd == 0 ? EL_FUSE_ON :
3397 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3398 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3399 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3400 EL_MIRROR_FIXED_START + rnd - 25);
3405 graphic = el2gfx(element);
3407 for (i = 0; i < 50; i++)
3413 BlitBitmap(pix[PIX_BACK], drawto,
3414 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3415 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3416 SX + ELX * TILEX + x,
3417 SY + ELY * TILEY + y);
3419 MarkTileDirty(ELX, ELY);
3422 DrawLaser(0, DL_LASER_ENABLED);
3424 Delay_WithScreenUpdates(50);
3427 Tile[ELX][ELY] = element;
3428 DrawField_MM(ELX, ELY);
3431 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3434 // above stuff: GRAY BALL -> PRISM !!!
3436 LX = ELX * TILEX + 14;
3437 LY = ELY * TILEY + 14;
3438 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3445 laser.num_edges -= 2;
3446 laser.num_damages--;
3450 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3451 if (laser.damage[i].is_mirror)
3455 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3457 DrawLaser(0, DL_LASER_DISABLED);
3459 DrawLaser(0, DL_LASER_DISABLED);
3468 if (IS_WALL_ICE(element) && CT > 50)
3470 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3473 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3474 Store[ELX][ELY] = EL_WALL_ICE;
3475 Store2[ELX][ELY] = laser.wall_mask;
3477 laser.dest_element = Tile[ELX][ELY];
3482 for (i = 0; i < 5; i++)
3488 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3492 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3494 Delay_WithScreenUpdates(100);
3497 if (Tile[ELX][ELY] == EL_WALL_ICE)
3498 Tile[ELX][ELY] = EL_EMPTY;
3502 LX = laser.edge[laser.num_edges].x - cSX2;
3503 LY = laser.edge[laser.num_edges].y - cSY2;
3506 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3507 if (laser.damage[i].is_mirror)
3511 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3513 DrawLaser(0, DL_LASER_DISABLED);
3520 if (IS_WALL_AMOEBA(element) && CT > 60)
3522 int k1, k2, k3, dx, dy, de, dm;
3523 int element2 = Tile[ELX][ELY];
3525 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3528 for (i = laser.num_damages - 1; i >= 0; i--)
3529 if (laser.damage[i].is_mirror)
3532 r = laser.num_edges;
3533 d = laser.num_damages;
3540 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3543 DrawLaser(0, DL_LASER_ENABLED);
3546 x = laser.damage[k1].x;
3547 y = laser.damage[k1].y;
3552 for (i = 0; i < 4; i++)
3554 if (laser.wall_mask & (1 << i))
3556 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3557 cSY + ELY * TILEY + 31 * (i / 2)))
3560 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3561 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3568 for (i = 0; i < 4; i++)
3570 if (laser.wall_mask & (1 << i))
3572 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3573 cSY + ELY * TILEY + 31 * (i / 2)))
3580 if (laser.num_beamers > 0 ||
3581 k1 < 1 || k2 < 4 || k3 < 4 ||
3582 CheckLaserPixel(cSX + ELX * TILEX + 14,
3583 cSY + ELY * TILEY + 14))
3585 laser.num_edges = r;
3586 laser.num_damages = d;
3588 DrawLaser(0, DL_LASER_DISABLED);
3591 Tile[ELX][ELY] = element | laser.wall_mask;
3595 de = Tile[ELX][ELY];
3596 dm = laser.wall_mask;
3600 int x = ELX, y = ELY;
3601 int wall_mask = laser.wall_mask;
3604 DrawLaser(0, DL_LASER_ENABLED);
3606 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3608 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3609 Store[x][y] = EL_WALL_AMOEBA;
3610 Store2[x][y] = wall_mask;
3616 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3618 DrawLaser(0, DL_LASER_ENABLED);
3620 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3622 for (i = 4; i >= 0; i--)
3624 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3627 Delay_WithScreenUpdates(20);
3630 DrawLaser(0, DL_LASER_ENABLED);
3635 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3636 laser.stops_inside_element && CT > native_mm_level.time_block)
3641 if (ABS(XS) > ABS(YS))
3648 for (i = 0; i < 4; i++)
3655 x = ELX + Step[k * 4].x;
3656 y = ELY + Step[k * 4].y;
3658 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3661 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3669 laser.overloaded = (element == EL_BLOCK_STONE);
3674 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3677 Tile[x][y] = element;
3679 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3682 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3684 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3685 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3693 if (element == EL_FUEL_FULL && CT > 10)
3695 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3696 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3698 for (i = start; i <= num_init_game_frames; i++)
3700 if (i == num_init_game_frames)
3701 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3702 else if (setup.sound_loops)
3703 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3705 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3707 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3709 UpdateAndDisplayGameControlValues();
3714 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3716 DrawField_MM(ELX, ELY);
3718 DrawLaser(0, DL_LASER_ENABLED);
3726 void GameActions_MM(struct MouseActionInfo action)
3728 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3729 boolean button_released = (action.button == MB_RELEASED);
3731 GameActions_MM_Ext();
3733 CheckSingleStepMode_MM(element_clicked, button_released);
3736 void MovePacMen(void)
3738 int mx, my, ox, oy, nx, ny;
3742 if (++pacman_nr >= game_mm.num_pacman)
3745 game_mm.pacman[pacman_nr].dir--;
3747 for (l = 1; l < 5; l++)
3749 game_mm.pacman[pacman_nr].dir++;
3751 if (game_mm.pacman[pacman_nr].dir > 4)
3752 game_mm.pacman[pacman_nr].dir = 1;
3754 if (game_mm.pacman[pacman_nr].dir % 2)
3757 my = game_mm.pacman[pacman_nr].dir - 2;
3762 mx = 3 - game_mm.pacman[pacman_nr].dir;
3765 ox = game_mm.pacman[pacman_nr].x;
3766 oy = game_mm.pacman[pacman_nr].y;
3769 element = Tile[nx][ny];
3771 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3774 if (!IS_EATABLE4PACMAN(element))
3777 if (ObjHit(nx, ny, HIT_POS_CENTER))
3780 Tile[ox][oy] = EL_EMPTY;
3782 EL_PACMAN_RIGHT - 1 +
3783 (game_mm.pacman[pacman_nr].dir - 1 +
3784 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3786 game_mm.pacman[pacman_nr].x = nx;
3787 game_mm.pacman[pacman_nr].y = ny;
3789 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3791 if (element != EL_EMPTY)
3793 int graphic = el2gfx(Tile[nx][ny]);
3798 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3801 ox = cSX + ox * TILEX;
3802 oy = cSY + oy * TILEY;
3804 for (i = 1; i < 33; i += 2)
3805 BlitBitmap(bitmap, window,
3806 src_x, src_y, TILEX, TILEY,
3807 ox + i * mx, oy + i * my);
3808 Ct = Ct + FrameCounter - CT;
3811 DrawField_MM(nx, ny);
3814 if (!laser.fuse_off)
3816 DrawLaser(0, DL_LASER_ENABLED);
3818 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3820 AddDamagedField(nx, ny);
3822 laser.damage[laser.num_damages - 1].edge = 0;
3826 if (element == EL_BOMB)
3827 DeletePacMan(nx, ny);
3829 if (IS_WALL_AMOEBA(element) &&
3830 (LX + 2 * XS) / TILEX == nx &&
3831 (LY + 2 * YS) / TILEY == ny)
3841 static void InitMovingField_MM(int x, int y, int direction)
3843 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3844 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3846 MovDir[x][y] = direction;
3847 MovDir[newx][newy] = direction;
3849 if (Tile[newx][newy] == EL_EMPTY)
3850 Tile[newx][newy] = EL_BLOCKED;
3853 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3855 int direction = MovDir[x][y];
3856 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3857 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3863 static void Blocked2Moving_MM(int x, int y,
3864 int *comes_from_x, int *comes_from_y)
3866 int oldx = x, oldy = y;
3867 int direction = MovDir[x][y];
3869 if (direction == MV_LEFT)
3871 else if (direction == MV_RIGHT)
3873 else if (direction == MV_UP)
3875 else if (direction == MV_DOWN)
3878 *comes_from_x = oldx;
3879 *comes_from_y = oldy;
3882 static int MovingOrBlocked2Element_MM(int x, int y)
3884 int element = Tile[x][y];
3886 if (element == EL_BLOCKED)
3890 Blocked2Moving_MM(x, y, &oldx, &oldy);
3892 return Tile[oldx][oldy];
3899 static void RemoveField(int x, int y)
3901 Tile[x][y] = EL_EMPTY;
3908 static void RemoveMovingField_MM(int x, int y)
3910 int oldx = x, oldy = y, newx = x, newy = y;
3912 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3915 if (IS_MOVING(x, y))
3917 Moving2Blocked_MM(x, y, &newx, &newy);
3918 if (Tile[newx][newy] != EL_BLOCKED)
3921 else if (Tile[x][y] == EL_BLOCKED)
3923 Blocked2Moving_MM(x, y, &oldx, &oldy);
3924 if (!IS_MOVING(oldx, oldy))
3928 Tile[oldx][oldy] = EL_EMPTY;
3929 Tile[newx][newy] = EL_EMPTY;
3930 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3931 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3933 DrawLevelField_MM(oldx, oldy);
3934 DrawLevelField_MM(newx, newy);
3937 void PlaySoundLevel(int x, int y, int sound_nr)
3939 int sx = SCREENX(x), sy = SCREENY(y);
3941 int silence_distance = 8;
3943 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3944 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3947 if (!IN_LEV_FIELD(x, y) ||
3948 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3949 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3952 volume = SOUND_MAX_VOLUME;
3955 stereo = (sx - SCR_FIELDX/2) * 12;
3957 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3958 if (stereo > SOUND_MAX_RIGHT)
3959 stereo = SOUND_MAX_RIGHT;
3960 if (stereo < SOUND_MAX_LEFT)
3961 stereo = SOUND_MAX_LEFT;
3964 if (!IN_SCR_FIELD(sx, sy))
3966 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3967 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3969 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3972 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3975 static void RaiseScore_MM(int value)
3977 game_mm.score += value;
3980 void RaiseScoreElement_MM(int element)
3985 case EL_PACMAN_RIGHT:
3987 case EL_PACMAN_LEFT:
3988 case EL_PACMAN_DOWN:
3989 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3993 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3998 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4002 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4011 // ----------------------------------------------------------------------------
4012 // Mirror Magic game engine snapshot handling functions
4013 // ----------------------------------------------------------------------------
4015 void SaveEngineSnapshotValues_MM(void)
4019 engine_snapshot_mm.game_mm = game_mm;
4020 engine_snapshot_mm.laser = laser;
4022 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4024 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4026 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4027 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4028 engine_snapshot_mm.Box[x][y] = Box[x][y];
4029 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4033 engine_snapshot_mm.LX = LX;
4034 engine_snapshot_mm.LY = LY;
4035 engine_snapshot_mm.XS = XS;
4036 engine_snapshot_mm.YS = YS;
4037 engine_snapshot_mm.ELX = ELX;
4038 engine_snapshot_mm.ELY = ELY;
4039 engine_snapshot_mm.CT = CT;
4040 engine_snapshot_mm.Ct = Ct;
4042 engine_snapshot_mm.last_LX = last_LX;
4043 engine_snapshot_mm.last_LY = last_LY;
4044 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4045 engine_snapshot_mm.hold_x = hold_x;
4046 engine_snapshot_mm.hold_y = hold_y;
4047 engine_snapshot_mm.pacman_nr = pacman_nr;
4049 engine_snapshot_mm.rotate_delay = rotate_delay;
4050 engine_snapshot_mm.pacman_delay = pacman_delay;
4051 engine_snapshot_mm.energy_delay = energy_delay;
4052 engine_snapshot_mm.overload_delay = overload_delay;
4055 void LoadEngineSnapshotValues_MM(void)
4059 // stored engine snapshot buffers already restored at this point
4061 game_mm = engine_snapshot_mm.game_mm;
4062 laser = engine_snapshot_mm.laser;
4064 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4066 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4068 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4069 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4070 Box[x][y] = engine_snapshot_mm.Box[x][y];
4071 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4075 LX = engine_snapshot_mm.LX;
4076 LY = engine_snapshot_mm.LY;
4077 XS = engine_snapshot_mm.XS;
4078 YS = engine_snapshot_mm.YS;
4079 ELX = engine_snapshot_mm.ELX;
4080 ELY = engine_snapshot_mm.ELY;
4081 CT = engine_snapshot_mm.CT;
4082 Ct = engine_snapshot_mm.Ct;
4084 last_LX = engine_snapshot_mm.last_LX;
4085 last_LY = engine_snapshot_mm.last_LY;
4086 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4087 hold_x = engine_snapshot_mm.hold_x;
4088 hold_y = engine_snapshot_mm.hold_y;
4089 pacman_nr = engine_snapshot_mm.pacman_nr;
4091 rotate_delay = engine_snapshot_mm.rotate_delay;
4092 pacman_delay = engine_snapshot_mm.pacman_delay;
4093 energy_delay = engine_snapshot_mm.energy_delay;
4094 overload_delay = engine_snapshot_mm.overload_delay;
4096 RedrawPlayfield_MM();
4099 static int getAngleFromTouchDelta(int dx, int dy, int base)
4101 double pi = 3.141592653;
4102 double rad = atan2((double)-dy, (double)dx);
4103 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4104 double deg = rad2 * 180.0 / pi;
4106 return (int)(deg * base / 360.0 + 0.5) % base;
4109 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4111 // calculate start (source) position to be at the middle of the tile
4112 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4113 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4114 int dx = dst_mx - src_mx;
4115 int dy = dst_my - src_my;
4124 if (!IN_LEV_FIELD(x, y))
4127 element = Tile[x][y];
4129 if (!IS_MCDUFFIN(element) &&
4130 !IS_MIRROR(element) &&
4131 !IS_BEAMER(element) &&
4132 !IS_POLAR(element) &&
4133 !IS_POLAR_CROSS(element) &&
4134 !IS_DF_MIRROR(element))
4137 angle_old = get_element_angle(element);
4139 if (IS_MCDUFFIN(element))
4141 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4142 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4143 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4144 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4147 else if (IS_MIRROR(element) ||
4148 IS_DF_MIRROR(element))
4150 for (i = 0; i < laser.num_damages; i++)
4152 if (laser.damage[i].x == x &&
4153 laser.damage[i].y == y &&
4154 ObjHit(x, y, HIT_POS_CENTER))
4156 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4157 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4164 if (angle_new == -1)
4166 if (IS_MIRROR(element) ||
4167 IS_DF_MIRROR(element) ||
4171 if (IS_POLAR_CROSS(element))
4174 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4177 button = (angle_new == angle_old ? 0 :
4178 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4179 MB_LEFTBUTTON : MB_RIGHTBUTTON);