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)
931 int x = laser.dest_element_last_x;
932 int y = laser.dest_element_last_y;
933 int element = laser.dest_element_last;
935 if (Tile[x][y] == element)
936 Tile[x][y] = (element == EL_BOMB_ACTIVE ? EL_BOMB : EL_MINE);
938 laser.dest_element_last = EL_EMPTY;
939 laser.dest_element_last_x = -1;
940 laser.dest_element_last_y = -1;
943 laser.overloaded = FALSE;
944 laser.stops_inside_element = FALSE;
946 DrawLaser(0, DL_LASER_ENABLED);
949 Debug("game:mm:ScanLaser",
950 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
958 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
961 laser.overloaded = TRUE;
966 hit_mask = ScanPixel();
969 Debug("game:mm:ScanLaser",
970 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
974 // hit something -- check out what it was
975 ELX = (LX + XS) / TILEX;
976 ELY = (LY + YS) / TILEY;
979 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
980 hit_mask, LX, LY, ELX, ELY);
983 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
986 laser.dest_element = element;
991 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
993 /* we have hit the top-right and bottom-left element --
994 choose the bottom-left one */
995 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
996 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
997 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
998 ELX = (LX - 2) / TILEX;
999 ELY = (LY + 2) / TILEY;
1002 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
1004 /* we have hit the top-left and bottom-right element --
1005 choose the top-left one */
1006 // !!! SEE ABOVE !!!
1007 ELX = (LX - 2) / TILEX;
1008 ELY = (LY - 2) / TILEY;
1012 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
1013 hit_mask, LX, LY, ELX, ELY);
1016 element = Tile[ELX][ELY];
1017 laser.dest_element = element;
1020 Debug("game:mm:ScanLaser",
1021 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1024 LX % TILEX, LY % TILEY,
1029 if (!IN_LEV_FIELD(ELX, ELY))
1030 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1034 if (element == EL_EMPTY)
1036 if (!HitOnlyAnEdge(hit_mask))
1039 else if (element == EL_FUSE_ON)
1041 if (HitPolarizer(element, hit_mask))
1044 else if (IS_GRID(element) || IS_DF_GRID(element))
1046 if (HitPolarizer(element, hit_mask))
1049 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1050 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1052 if (HitBlock(element, hit_mask))
1059 else if (IS_MCDUFFIN(element))
1061 if (HitLaserSource(element, hit_mask))
1064 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1065 IS_RECEIVER(element))
1067 if (HitLaserDestination(element, hit_mask))
1070 else if (IS_WALL(element))
1072 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1074 if (HitReflectingWalls(element, hit_mask))
1079 if (HitAbsorbingWalls(element, hit_mask))
1085 if (HitElement(element, hit_mask))
1090 DrawLaser(rf - 1, DL_LASER_ENABLED);
1091 rf = laser.num_edges;
1095 if (laser.dest_element != Tile[ELX][ELY])
1097 Debug("game:mm:ScanLaser",
1098 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1099 laser.dest_element, Tile[ELX][ELY]);
1103 if (!end && !laser.stops_inside_element && !StepBehind())
1106 Debug("game:mm:ScanLaser", "Go one step back");
1112 AddLaserEdge(LX, LY);
1116 DrawLaser(rf - 1, DL_LASER_ENABLED);
1118 Ct = CT = FrameCounter;
1121 if (!IN_LEV_FIELD(ELX, ELY))
1122 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1126 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1132 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1133 start_edge, num_edges, mode);
1138 Warn("DrawLaserExt: start_edge < 0");
1145 Warn("DrawLaserExt: num_edges < 0");
1151 if (mode == DL_LASER_DISABLED)
1153 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1157 // now draw the laser to the backbuffer and (if enabled) to the screen
1158 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1160 redraw_mask |= REDRAW_FIELD;
1162 if (mode == DL_LASER_ENABLED)
1165 // after the laser was deleted, the "damaged" graphics must be restored
1166 if (laser.num_damages)
1168 int damage_start = 0;
1171 // determine the starting edge, from which graphics need to be restored
1174 for (i = 0; i < laser.num_damages; i++)
1176 if (laser.damage[i].edge == start_edge + 1)
1185 // restore graphics from this starting edge to the end of damage list
1186 for (i = damage_start; i < laser.num_damages; i++)
1188 int lx = laser.damage[i].x;
1189 int ly = laser.damage[i].y;
1190 int element = Tile[lx][ly];
1192 if (Hit[lx][ly] == laser.damage[i].edge)
1193 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1196 if (Box[lx][ly] == laser.damage[i].edge)
1199 if (IS_DRAWABLE(element))
1200 DrawField_MM(lx, ly);
1203 elx = laser.damage[damage_start].x;
1204 ely = laser.damage[damage_start].y;
1205 element = Tile[elx][ely];
1208 if (IS_BEAMER(element))
1212 for (i = 0; i < laser.num_beamers; i++)
1213 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1215 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1216 mode, elx, ely, Hit[elx][ely], start_edge);
1217 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1218 get_element_angle(element), laser.damage[damage_start].angle);
1222 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1223 laser.num_beamers > 0 &&
1224 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1226 // element is outgoing beamer
1227 laser.num_damages = damage_start + 1;
1229 if (IS_BEAMER(element))
1230 laser.current_angle = get_element_angle(element);
1234 // element is incoming beamer or other element
1235 laser.num_damages = damage_start;
1236 laser.current_angle = laser.damage[laser.num_damages].angle;
1241 // no damages but McDuffin himself (who needs to be redrawn anyway)
1243 elx = laser.start_edge.x;
1244 ely = laser.start_edge.y;
1245 element = Tile[elx][ely];
1248 laser.num_edges = start_edge + 1;
1249 if (start_edge == 0)
1250 laser.current_angle = laser.start_angle;
1252 LX = laser.edge[start_edge].x - cSX2;
1253 LY = laser.edge[start_edge].y - cSY2;
1254 XS = 2 * Step[laser.current_angle].x;
1255 YS = 2 * Step[laser.current_angle].y;
1258 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1264 if (IS_BEAMER(element) ||
1265 IS_FIBRE_OPTIC(element) ||
1266 IS_PACMAN(element) ||
1267 IS_POLAR(element) ||
1268 IS_POLAR_CROSS(element) ||
1269 element == EL_FUSE_ON)
1274 Debug("game:mm:DrawLaserExt", "element == %d", element);
1277 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1278 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1282 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1283 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1284 (laser.num_beamers == 0 ||
1285 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1287 // element is incoming beamer or other element
1288 step_size = -step_size;
1293 if (IS_BEAMER(element))
1294 Debug("game:mm:DrawLaserExt",
1295 "start_edge == %d, laser.beamer_edge == %d",
1296 start_edge, laser.beamer_edge);
1299 LX += step_size * XS;
1300 LY += step_size * YS;
1302 else if (element != EL_EMPTY)
1311 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1316 void DrawLaser(int start_edge, int mode)
1318 if (laser.num_edges - start_edge < 0)
1320 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1325 // check if laser is interrupted by beamer element
1326 if (laser.num_beamers > 0 &&
1327 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1329 if (mode == DL_LASER_ENABLED)
1332 int tmp_start_edge = start_edge;
1334 // draw laser segments forward from the start to the last beamer
1335 for (i = 0; i < laser.num_beamers; i++)
1337 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1339 if (tmp_num_edges <= 0)
1343 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1344 i, laser.beamer_edge[i], tmp_start_edge);
1347 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1349 tmp_start_edge = laser.beamer_edge[i];
1352 // draw last segment from last beamer to the end
1353 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1359 int last_num_edges = laser.num_edges;
1360 int num_beamers = laser.num_beamers;
1362 // delete laser segments backward from the end to the first beamer
1363 for (i = num_beamers - 1; i >= 0; i--)
1365 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1367 if (laser.beamer_edge[i] - start_edge <= 0)
1370 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1372 last_num_edges = laser.beamer_edge[i];
1373 laser.num_beamers--;
1377 if (last_num_edges - start_edge <= 0)
1378 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1379 last_num_edges, start_edge);
1382 // special case when rotating first beamer: delete laser edge on beamer
1383 // (but do not start scanning on previous edge to prevent mirror sound)
1384 if (last_num_edges - start_edge == 1 && start_edge > 0)
1385 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1387 // delete first segment from start to the first beamer
1388 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1393 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1396 game_mm.laser_enabled = mode;
1399 void DrawLaser_MM(void)
1401 DrawLaser(0, game_mm.laser_enabled);
1404 boolean HitElement(int element, int hit_mask)
1406 if (HitOnlyAnEdge(hit_mask))
1409 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1410 element = MovingOrBlocked2Element_MM(ELX, ELY);
1413 Debug("game:mm:HitElement", "(1): element == %d", element);
1417 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1418 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1421 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1425 AddDamagedField(ELX, ELY);
1427 // this is more precise: check if laser would go through the center
1428 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1430 // skip the whole element before continuing the scan
1436 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1438 if (LX/TILEX > ELX || LY/TILEY > ELY)
1440 /* skipping scan positions to the right and down skips one scan
1441 position too much, because this is only the top left scan position
1442 of totally four scan positions (plus one to the right, one to the
1443 bottom and one to the bottom right) */
1453 Debug("game:mm:HitElement", "(2): element == %d", element);
1456 if (LX + 5 * XS < 0 ||
1466 Debug("game:mm:HitElement", "(3): element == %d", element);
1469 if (IS_POLAR(element) &&
1470 ((element - EL_POLAR_START) % 2 ||
1471 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1473 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1475 laser.num_damages--;
1480 if (IS_POLAR_CROSS(element) &&
1481 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1483 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1485 laser.num_damages--;
1490 if (!IS_BEAMER(element) &&
1491 !IS_FIBRE_OPTIC(element) &&
1492 !IS_GRID_WOOD(element) &&
1493 element != EL_FUEL_EMPTY)
1496 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1497 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1499 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1502 LX = ELX * TILEX + 14;
1503 LY = ELY * TILEY + 14;
1505 AddLaserEdge(LX, LY);
1508 if (IS_MIRROR(element) ||
1509 IS_MIRROR_FIXED(element) ||
1510 IS_POLAR(element) ||
1511 IS_POLAR_CROSS(element) ||
1512 IS_DF_MIRROR(element) ||
1513 IS_DF_MIRROR_AUTO(element) ||
1514 element == EL_PRISM ||
1515 element == EL_REFRACTOR)
1517 int current_angle = laser.current_angle;
1520 laser.num_damages--;
1522 AddDamagedField(ELX, ELY);
1524 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1527 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1529 if (IS_MIRROR(element) ||
1530 IS_MIRROR_FIXED(element) ||
1531 IS_DF_MIRROR(element) ||
1532 IS_DF_MIRROR_AUTO(element))
1533 laser.current_angle = get_mirrored_angle(laser.current_angle,
1534 get_element_angle(element));
1536 if (element == EL_PRISM || element == EL_REFRACTOR)
1537 laser.current_angle = RND(16);
1539 XS = 2 * Step[laser.current_angle].x;
1540 YS = 2 * Step[laser.current_angle].y;
1542 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1547 LX += step_size * XS;
1548 LY += step_size * YS;
1550 // draw sparkles on mirror
1551 if ((IS_MIRROR(element) ||
1552 IS_MIRROR_FIXED(element) ||
1553 element == EL_PRISM) &&
1554 current_angle != laser.current_angle)
1556 MovDelay[ELX][ELY] = 11; // start animation
1559 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1560 current_angle != laser.current_angle)
1561 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1564 (get_opposite_angle(laser.current_angle) ==
1565 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1567 return (laser.overloaded ? TRUE : FALSE);
1570 if (element == EL_FUEL_FULL)
1572 laser.stops_inside_element = TRUE;
1577 if (element == EL_BOMB || element == EL_MINE)
1579 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1581 Tile[ELX][ELY] = (element == EL_BOMB ? EL_BOMB_ACTIVE : EL_MINE_ACTIVE);
1583 laser.dest_element_last = Tile[ELX][ELY];
1584 laser.dest_element_last_x = ELX;
1585 laser.dest_element_last_y = ELY;
1587 if (element == EL_MINE)
1588 laser.overloaded = TRUE;
1591 if (element == EL_KETTLE ||
1592 element == EL_CELL ||
1593 element == EL_KEY ||
1594 element == EL_LIGHTBALL ||
1595 element == EL_PACMAN ||
1598 if (!IS_PACMAN(element))
1601 if (element == EL_PACMAN)
1604 if (element == EL_KETTLE || element == EL_CELL)
1606 if (game_mm.kettles_still_needed > 0)
1607 game_mm.kettles_still_needed--;
1609 game.snapshot.collected_item = TRUE;
1611 if (game_mm.kettles_still_needed == 0)
1615 DrawLaser(0, DL_LASER_ENABLED);
1618 else if (element == EL_KEY)
1622 else if (IS_PACMAN(element))
1624 DeletePacMan(ELX, ELY);
1627 RaiseScoreElement_MM(element);
1632 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1634 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1636 DrawLaser(0, DL_LASER_ENABLED);
1638 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1640 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1641 game_mm.lights_still_needed--;
1645 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1646 game_mm.lights_still_needed++;
1649 DrawField_MM(ELX, ELY);
1650 DrawLaser(0, DL_LASER_ENABLED);
1655 laser.stops_inside_element = TRUE;
1661 Debug("game:mm:HitElement", "(4): element == %d", element);
1664 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1665 laser.num_beamers < MAX_NUM_BEAMERS &&
1666 laser.beamer[BEAMER_NR(element)][1].num)
1668 int beamer_angle = get_element_angle(element);
1669 int beamer_nr = BEAMER_NR(element);
1673 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1676 laser.num_damages--;
1678 if (IS_FIBRE_OPTIC(element) ||
1679 laser.current_angle == get_opposite_angle(beamer_angle))
1683 LX = ELX * TILEX + 14;
1684 LY = ELY * TILEY + 14;
1686 AddLaserEdge(LX, LY);
1687 AddDamagedField(ELX, ELY);
1689 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1692 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1694 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1695 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1696 ELX = laser.beamer[beamer_nr][pos].x;
1697 ELY = laser.beamer[beamer_nr][pos].y;
1698 LX = ELX * TILEX + 14;
1699 LY = ELY * TILEY + 14;
1701 if (IS_BEAMER(element))
1703 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1704 XS = 2 * Step[laser.current_angle].x;
1705 YS = 2 * Step[laser.current_angle].y;
1708 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1710 AddLaserEdge(LX, LY);
1711 AddDamagedField(ELX, ELY);
1713 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1716 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1718 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1723 LX += step_size * XS;
1724 LY += step_size * YS;
1726 laser.num_beamers++;
1735 boolean HitOnlyAnEdge(int hit_mask)
1737 // check if the laser hit only the edge of an element and, if so, go on
1740 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1744 if ((hit_mask == HIT_MASK_TOPLEFT ||
1745 hit_mask == HIT_MASK_TOPRIGHT ||
1746 hit_mask == HIT_MASK_BOTTOMLEFT ||
1747 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1748 laser.current_angle % 4) // angle is not 90°
1752 if (hit_mask == HIT_MASK_TOPLEFT)
1757 else if (hit_mask == HIT_MASK_TOPRIGHT)
1762 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1767 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1773 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1779 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1786 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1792 boolean HitPolarizer(int element, int hit_mask)
1794 if (HitOnlyAnEdge(hit_mask))
1797 if (IS_DF_GRID(element))
1799 int grid_angle = get_element_angle(element);
1802 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1803 grid_angle, laser.current_angle);
1806 AddLaserEdge(LX, LY);
1807 AddDamagedField(ELX, ELY);
1810 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1812 if (laser.current_angle == grid_angle ||
1813 laser.current_angle == get_opposite_angle(grid_angle))
1815 // skip the whole element before continuing the scan
1821 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1823 if (LX/TILEX > ELX || LY/TILEY > ELY)
1825 /* skipping scan positions to the right and down skips one scan
1826 position too much, because this is only the top left scan position
1827 of totally four scan positions (plus one to the right, one to the
1828 bottom and one to the bottom right) */
1834 AddLaserEdge(LX, LY);
1840 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1842 LX / TILEX, LY / TILEY,
1843 LX % TILEX, LY % TILEY);
1848 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1850 return HitReflectingWalls(element, hit_mask);
1854 return HitAbsorbingWalls(element, hit_mask);
1857 else if (IS_GRID_STEEL(element))
1859 return HitReflectingWalls(element, hit_mask);
1861 else // IS_GRID_WOOD
1863 return HitAbsorbingWalls(element, hit_mask);
1869 boolean HitBlock(int element, int hit_mask)
1871 boolean check = FALSE;
1873 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1874 game_mm.num_keys == 0)
1877 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1880 int ex = ELX * TILEX + 14;
1881 int ey = ELY * TILEY + 14;
1885 for (i = 1; i < 32; i++)
1890 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1895 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1896 return HitAbsorbingWalls(element, hit_mask);
1900 AddLaserEdge(LX - XS, LY - YS);
1901 AddDamagedField(ELX, ELY);
1904 Box[ELX][ELY] = laser.num_edges;
1906 return HitReflectingWalls(element, hit_mask);
1909 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1911 int xs = XS / 2, ys = YS / 2;
1912 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1913 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1915 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1916 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1918 laser.overloaded = (element == EL_GATE_STONE);
1923 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1924 (hit_mask == HIT_MASK_TOP ||
1925 hit_mask == HIT_MASK_LEFT ||
1926 hit_mask == HIT_MASK_RIGHT ||
1927 hit_mask == HIT_MASK_BOTTOM))
1928 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1929 hit_mask == HIT_MASK_BOTTOM),
1930 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1931 hit_mask == HIT_MASK_RIGHT));
1932 AddLaserEdge(LX, LY);
1938 if (element == EL_GATE_STONE && Box[ELX][ELY])
1940 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1952 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1954 int xs = XS / 2, ys = YS / 2;
1955 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1956 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1958 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1959 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1961 laser.overloaded = (element == EL_BLOCK_STONE);
1966 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1967 (hit_mask == HIT_MASK_TOP ||
1968 hit_mask == HIT_MASK_LEFT ||
1969 hit_mask == HIT_MASK_RIGHT ||
1970 hit_mask == HIT_MASK_BOTTOM))
1971 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1972 hit_mask == HIT_MASK_BOTTOM),
1973 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1974 hit_mask == HIT_MASK_RIGHT));
1975 AddDamagedField(ELX, ELY);
1977 LX = ELX * TILEX + 14;
1978 LY = ELY * TILEY + 14;
1980 AddLaserEdge(LX, LY);
1982 laser.stops_inside_element = TRUE;
1990 boolean HitLaserSource(int element, int hit_mask)
1992 if (HitOnlyAnEdge(hit_mask))
1995 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1997 laser.overloaded = TRUE;
2002 boolean HitLaserDestination(int element, int hit_mask)
2004 if (HitOnlyAnEdge(hit_mask))
2007 if (element != EL_EXIT_OPEN &&
2008 !(IS_RECEIVER(element) &&
2009 game_mm.kettles_still_needed == 0 &&
2010 laser.current_angle == get_opposite_angle(get_element_angle(element))))
2012 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2017 if (IS_RECEIVER(element) ||
2018 (IS_22_5_ANGLE(laser.current_angle) &&
2019 (ELX != (LX + 6 * XS) / TILEX ||
2020 ELY != (LY + 6 * YS) / TILEY ||
2029 LX = ELX * TILEX + 14;
2030 LY = ELY * TILEY + 14;
2032 laser.stops_inside_element = TRUE;
2035 AddLaserEdge(LX, LY);
2036 AddDamagedField(ELX, ELY);
2038 if (game_mm.lights_still_needed == 0)
2040 game_mm.level_solved = TRUE;
2042 SetTileCursorActive(FALSE);
2048 boolean HitReflectingWalls(int element, int hit_mask)
2050 // check if laser hits side of a wall with an angle that is not 90°
2051 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2052 hit_mask == HIT_MASK_LEFT ||
2053 hit_mask == HIT_MASK_RIGHT ||
2054 hit_mask == HIT_MASK_BOTTOM))
2056 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2061 if (!IS_DF_GRID(element))
2062 AddLaserEdge(LX, LY);
2064 // check if laser hits wall with an angle of 45°
2065 if (!IS_22_5_ANGLE(laser.current_angle))
2067 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2070 laser.current_angle = get_mirrored_angle(laser.current_angle,
2073 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2076 laser.current_angle = get_mirrored_angle(laser.current_angle,
2080 AddLaserEdge(LX, LY);
2082 XS = 2 * Step[laser.current_angle].x;
2083 YS = 2 * Step[laser.current_angle].y;
2087 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2089 laser.current_angle = get_mirrored_angle(laser.current_angle,
2094 if (!IS_DF_GRID(element))
2095 AddLaserEdge(LX, LY);
2100 if (!IS_DF_GRID(element))
2101 AddLaserEdge(LX, LY + YS / 2);
2104 if (!IS_DF_GRID(element))
2105 AddLaserEdge(LX, LY);
2108 YS = 2 * Step[laser.current_angle].y;
2112 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2114 laser.current_angle = get_mirrored_angle(laser.current_angle,
2119 if (!IS_DF_GRID(element))
2120 AddLaserEdge(LX, LY);
2125 if (!IS_DF_GRID(element))
2126 AddLaserEdge(LX + XS / 2, LY);
2129 if (!IS_DF_GRID(element))
2130 AddLaserEdge(LX, LY);
2133 XS = 2 * Step[laser.current_angle].x;
2139 // reflection at the edge of reflecting DF style wall
2140 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2142 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2143 hit_mask == HIT_MASK_TOPRIGHT) ||
2144 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2145 hit_mask == HIT_MASK_TOPLEFT) ||
2146 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2147 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2148 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2149 hit_mask == HIT_MASK_BOTTOMRIGHT))
2152 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2153 ANG_MIRROR_135 : ANG_MIRROR_45);
2155 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2157 AddDamagedField(ELX, ELY);
2158 AddLaserEdge(LX, LY);
2160 laser.current_angle = get_mirrored_angle(laser.current_angle,
2168 AddLaserEdge(LX, LY);
2174 // reflection inside an edge of reflecting DF style wall
2175 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2177 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2178 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2179 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2180 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2181 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2182 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2183 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2184 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2187 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2188 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2189 ANG_MIRROR_135 : ANG_MIRROR_45);
2191 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2194 AddDamagedField(ELX, ELY);
2197 AddLaserEdge(LX - XS, LY - YS);
2198 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2199 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2201 laser.current_angle = get_mirrored_angle(laser.current_angle,
2209 AddLaserEdge(LX, LY);
2215 // check if laser hits DF style wall with an angle of 90°
2216 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2218 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2219 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2220 (IS_VERT_ANGLE(laser.current_angle) &&
2221 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2223 // laser at last step touched nothing or the same side of the wall
2224 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2226 AddDamagedField(ELX, ELY);
2233 last_hit_mask = hit_mask;
2240 if (!HitOnlyAnEdge(hit_mask))
2242 laser.overloaded = TRUE;
2250 boolean HitAbsorbingWalls(int element, int hit_mask)
2252 if (HitOnlyAnEdge(hit_mask))
2256 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2258 AddLaserEdge(LX - XS, LY - YS);
2265 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2267 AddLaserEdge(LX - XS, LY - YS);
2273 if (IS_WALL_WOOD(element) ||
2274 IS_DF_WALL_WOOD(element) ||
2275 IS_GRID_WOOD(element) ||
2276 IS_GRID_WOOD_FIXED(element) ||
2277 IS_GRID_WOOD_AUTO(element) ||
2278 element == EL_FUSE_ON ||
2279 element == EL_BLOCK_WOOD ||
2280 element == EL_GATE_WOOD)
2282 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2287 if (IS_WALL_ICE(element))
2291 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2292 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2294 // check if laser hits wall with an angle of 90°
2295 if (IS_90_ANGLE(laser.current_angle))
2296 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2298 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2302 for (i = 0; i < 4; i++)
2304 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2305 mask = 15 - (8 >> i);
2306 else if (ABS(XS) == 4 &&
2308 (XS > 0) == (i % 2) &&
2309 (YS < 0) == (i / 2))
2310 mask = 3 + (i / 2) * 9;
2311 else if (ABS(YS) == 4 &&
2313 (XS < 0) == (i % 2) &&
2314 (YS > 0) == (i / 2))
2315 mask = 5 + (i % 2) * 5;
2319 laser.wall_mask = mask;
2321 else if (IS_WALL_AMOEBA(element))
2323 int elx = (LX - 2 * XS) / TILEX;
2324 int ely = (LY - 2 * YS) / TILEY;
2325 int element2 = Tile[elx][ely];
2328 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2330 laser.dest_element = EL_EMPTY;
2338 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2339 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2341 if (IS_90_ANGLE(laser.current_angle))
2342 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2344 laser.dest_element = element2 | EL_WALL_AMOEBA;
2346 laser.wall_mask = mask;
2352 static void OpenExit(int x, int y)
2356 if (!MovDelay[x][y]) // next animation frame
2357 MovDelay[x][y] = 4 * delay;
2359 if (MovDelay[x][y]) // wait some time before next frame
2364 phase = MovDelay[x][y] / delay;
2366 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2367 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2369 if (!MovDelay[x][y])
2371 Tile[x][y] = EL_EXIT_OPEN;
2377 static void OpenSurpriseBall(int x, int y)
2381 if (!MovDelay[x][y]) // next animation frame
2382 MovDelay[x][y] = 50 * delay;
2384 if (MovDelay[x][y]) // wait some time before next frame
2388 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2391 int graphic = el2gfx(Store[x][y]);
2393 int dx = RND(26), dy = RND(26);
2395 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2397 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2398 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2400 MarkTileDirty(x, y);
2403 if (!MovDelay[x][y])
2405 Tile[x][y] = Store[x][y];
2414 static void MeltIce(int x, int y)
2419 if (!MovDelay[x][y]) // next animation frame
2420 MovDelay[x][y] = frames * delay;
2422 if (MovDelay[x][y]) // wait some time before next frame
2425 int wall_mask = Store2[x][y];
2426 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2429 phase = frames - MovDelay[x][y] / delay - 1;
2431 if (!MovDelay[x][y])
2435 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2436 Store[x][y] = Store2[x][y] = 0;
2438 DrawWalls_MM(x, y, Tile[x][y]);
2440 if (Tile[x][y] == EL_WALL_ICE)
2441 Tile[x][y] = EL_EMPTY;
2443 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2444 if (laser.damage[i].is_mirror)
2448 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2450 DrawLaser(0, DL_LASER_DISABLED);
2454 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2456 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2458 laser.redraw = TRUE;
2463 static void GrowAmoeba(int x, int y)
2468 if (!MovDelay[x][y]) // next animation frame
2469 MovDelay[x][y] = frames * delay;
2471 if (MovDelay[x][y]) // wait some time before next frame
2474 int wall_mask = Store2[x][y];
2475 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2478 phase = MovDelay[x][y] / delay;
2480 if (!MovDelay[x][y])
2482 Tile[x][y] = real_element;
2483 Store[x][y] = Store2[x][y] = 0;
2485 DrawWalls_MM(x, y, Tile[x][y]);
2486 DrawLaser(0, DL_LASER_ENABLED);
2488 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2490 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2495 static void DrawFieldAnimated_MM(int x, int y)
2497 int element = Tile[x][y];
2499 if (IS_BLOCKED(x, y))
2504 if (IS_MIRROR(element) ||
2505 IS_MIRROR_FIXED(element) ||
2506 element == EL_PRISM)
2508 if (MovDelay[x][y] != 0) // wait some time before next frame
2512 if (MovDelay[x][y] != 0)
2514 int graphic = IMG_TWINKLE_WHITE;
2515 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2517 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2522 laser.redraw = TRUE;
2525 static void Explode_MM(int x, int y, int phase, int mode)
2527 int num_phase = 9, delay = 2;
2528 int last_phase = num_phase * delay;
2529 int half_phase = (num_phase / 2) * delay;
2531 laser.redraw = TRUE;
2533 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2535 int center_element = Tile[x][y];
2537 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2539 // put moving element to center field (and let it explode there)
2540 center_element = MovingOrBlocked2Element_MM(x, y);
2541 RemoveMovingField_MM(x, y);
2543 Tile[x][y] = center_element;
2546 if (center_element == EL_BOMB_ACTIVE || IS_MCDUFFIN(center_element))
2547 Store[x][y] = center_element;
2549 Store[x][y] = EL_EMPTY;
2551 Store2[x][y] = mode;
2553 Tile[x][y] = EL_EXPLODING_OPAQUE;
2554 GfxElement[x][y] = center_element;
2556 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2558 ExplodePhase[x][y] = 1;
2564 GfxFrame[x][y] = 0; // restart explosion animation
2566 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2568 if (phase == half_phase)
2570 Tile[x][y] = EL_EXPLODING_TRANSP;
2572 if (x == ELX && y == ELY)
2576 if (phase == last_phase)
2578 if (Store[x][y] == EL_BOMB_ACTIVE)
2580 DrawLaser(0, DL_LASER_DISABLED);
2583 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2584 Store[x][y] = EL_EMPTY;
2586 GameOver_MM(GAME_OVER_DELAYED);
2588 laser.overloaded = FALSE;
2590 else if (IS_MCDUFFIN(Store[x][y]))
2592 Store[x][y] = EL_EMPTY;
2594 GameOver_MM(GAME_OVER_BOMB);
2597 Tile[x][y] = Store[x][y];
2598 Store[x][y] = Store2[x][y] = 0;
2599 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2604 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2606 int graphic = el_act2gfx(GfxElement[x][y], MM_ACTION_EXPLODING);
2607 int frame = getGraphicAnimationFrameXY(graphic, x, y);
2609 DrawGraphicAnimation_MM(x, y, graphic, frame);
2611 MarkTileDirty(x, y);
2615 static void Bang_MM(int x, int y)
2617 int element = Tile[x][y];
2620 DrawLaser(0, DL_LASER_ENABLED);
2623 if (IS_PACMAN(element))
2624 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2625 else if (element == EL_BOMB_ACTIVE || IS_MCDUFFIN(element))
2626 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2627 else if (element == EL_KEY)
2628 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2630 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2632 Explode_MM(x, y, EX_PHASE_START, EX_TYPE_NORMAL);
2635 void TurnRound(int x, int y)
2647 { 0, 0 }, { 0, 0 }, { 0, 0 },
2652 int left, right, back;
2656 { MV_DOWN, MV_UP, MV_RIGHT },
2657 { MV_UP, MV_DOWN, MV_LEFT },
2659 { MV_LEFT, MV_RIGHT, MV_DOWN },
2660 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2661 { MV_RIGHT, MV_LEFT, MV_UP }
2664 int element = Tile[x][y];
2665 int old_move_dir = MovDir[x][y];
2666 int right_dir = turn[old_move_dir].right;
2667 int back_dir = turn[old_move_dir].back;
2668 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2669 int right_x = x + right_dx, right_y = y + right_dy;
2671 if (element == EL_PACMAN)
2673 boolean can_turn_right = FALSE;
2675 if (IN_LEV_FIELD(right_x, right_y) &&
2676 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2677 can_turn_right = TRUE;
2680 MovDir[x][y] = right_dir;
2682 MovDir[x][y] = back_dir;
2688 static void StartMoving_MM(int x, int y)
2690 int element = Tile[x][y];
2695 if (CAN_MOVE(element))
2699 if (MovDelay[x][y]) // wait some time before next movement
2707 // now make next step
2709 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2711 if (element == EL_PACMAN &&
2712 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2713 !ObjHit(newx, newy, HIT_POS_CENTER))
2715 Store[newx][newy] = Tile[newx][newy];
2716 Tile[newx][newy] = EL_EMPTY;
2718 DrawField_MM(newx, newy);
2720 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2721 ObjHit(newx, newy, HIT_POS_CENTER))
2723 // object was running against a wall
2730 InitMovingField_MM(x, y, MovDir[x][y]);
2734 ContinueMoving_MM(x, y);
2737 static void ContinueMoving_MM(int x, int y)
2739 int element = Tile[x][y];
2740 int direction = MovDir[x][y];
2741 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2742 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2743 int horiz_move = (dx!=0);
2744 int newx = x + dx, newy = y + dy;
2745 int step = (horiz_move ? dx : dy) * TILEX / 8;
2747 MovPos[x][y] += step;
2749 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2751 Tile[x][y] = EL_EMPTY;
2752 Tile[newx][newy] = element;
2754 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2755 MovDelay[newx][newy] = 0;
2757 if (!CAN_MOVE(element))
2758 MovDir[newx][newy] = 0;
2761 DrawField_MM(newx, newy);
2763 Stop[newx][newy] = TRUE;
2765 if (element == EL_PACMAN)
2767 if (Store[newx][newy] == EL_BOMB)
2768 Bang_MM(newx, newy);
2770 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2771 (LX + 2 * XS) / TILEX == newx &&
2772 (LY + 2 * YS) / TILEY == newy)
2779 else // still moving on
2784 laser.redraw = TRUE;
2787 boolean ClickElement(int x, int y, int button)
2789 static DelayCounter click_delay = { CLICK_DELAY };
2790 static boolean new_button = TRUE;
2791 boolean element_clicked = FALSE;
2796 // initialize static variables
2797 click_delay.count = 0;
2798 click_delay.value = CLICK_DELAY;
2804 // do not rotate objects hit by the laser after the game was solved
2805 if (game_mm.level_solved && Hit[x][y])
2808 if (button == MB_RELEASED)
2811 click_delay.value = CLICK_DELAY;
2813 // release eventually hold auto-rotating mirror
2814 RotateMirror(x, y, MB_RELEASED);
2819 if (!FrameReached(&click_delay) && !new_button)
2822 if (button == MB_MIDDLEBUTTON) // middle button has no function
2825 if (!IN_LEV_FIELD(x, y))
2828 if (Tile[x][y] == EL_EMPTY)
2831 element = Tile[x][y];
2833 if (IS_MIRROR(element) ||
2834 IS_BEAMER(element) ||
2835 IS_POLAR(element) ||
2836 IS_POLAR_CROSS(element) ||
2837 IS_DF_MIRROR(element) ||
2838 IS_DF_MIRROR_AUTO(element))
2840 RotateMirror(x, y, button);
2842 element_clicked = TRUE;
2844 else if (IS_MCDUFFIN(element))
2846 if (!laser.fuse_off)
2848 DrawLaser(0, DL_LASER_DISABLED);
2855 element = get_rotated_element(element, BUTTON_ROTATION(button));
2856 laser.start_angle = get_element_angle(element);
2860 Tile[x][y] = element;
2867 if (!laser.fuse_off)
2870 element_clicked = TRUE;
2872 else if (element == EL_FUSE_ON && laser.fuse_off)
2874 if (x != laser.fuse_x || y != laser.fuse_y)
2877 laser.fuse_off = FALSE;
2878 laser.fuse_x = laser.fuse_y = -1;
2880 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2883 element_clicked = TRUE;
2885 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2887 laser.fuse_off = TRUE;
2890 laser.overloaded = FALSE;
2892 DrawLaser(0, DL_LASER_DISABLED);
2893 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2895 element_clicked = TRUE;
2897 else if (element == EL_LIGHTBALL)
2900 RaiseScoreElement_MM(element);
2901 DrawLaser(0, DL_LASER_ENABLED);
2903 element_clicked = TRUE;
2906 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2909 return element_clicked;
2912 void RotateMirror(int x, int y, int button)
2914 if (button == MB_RELEASED)
2916 // release eventually hold auto-rotating mirror
2923 if (IS_MIRROR(Tile[x][y]) ||
2924 IS_POLAR_CROSS(Tile[x][y]) ||
2925 IS_POLAR(Tile[x][y]) ||
2926 IS_BEAMER(Tile[x][y]) ||
2927 IS_DF_MIRROR(Tile[x][y]) ||
2928 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2929 IS_GRID_WOOD_AUTO(Tile[x][y]))
2931 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2933 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2935 if (button == MB_LEFTBUTTON)
2937 // left mouse button only for manual adjustment, no auto-rotating;
2938 // freeze mirror for until mouse button released
2942 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2944 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2948 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2950 int edge = Hit[x][y];
2956 DrawLaser(edge - 1, DL_LASER_DISABLED);
2960 else if (ObjHit(x, y, HIT_POS_CENTER))
2962 int edge = Hit[x][y];
2966 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2971 DrawLaser(edge - 1, DL_LASER_DISABLED);
2978 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2983 if ((IS_BEAMER(Tile[x][y]) ||
2984 IS_POLAR(Tile[x][y]) ||
2985 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2989 if (IS_BEAMER(Tile[x][y]))
2992 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2993 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3005 DrawLaser(0, DL_LASER_ENABLED);
3009 static void AutoRotateMirrors(void)
3013 if (!FrameReached(&rotate_delay))
3016 for (x = 0; x < lev_fieldx; x++)
3018 for (y = 0; y < lev_fieldy; y++)
3020 int element = Tile[x][y];
3022 // do not rotate objects hit by the laser after the game was solved
3023 if (game_mm.level_solved && Hit[x][y])
3026 if (IS_DF_MIRROR_AUTO(element) ||
3027 IS_GRID_WOOD_AUTO(element) ||
3028 IS_GRID_STEEL_AUTO(element) ||
3029 element == EL_REFRACTOR)
3030 RotateMirror(x, y, MB_RIGHTBUTTON);
3035 boolean ObjHit(int obx, int oby, int bits)
3042 if (bits & HIT_POS_CENTER)
3044 if (CheckLaserPixel(cSX + obx + 15,
3049 if (bits & HIT_POS_EDGE)
3051 for (i = 0; i < 4; i++)
3052 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3053 cSY + oby + 31 * (i / 2)))
3057 if (bits & HIT_POS_BETWEEN)
3059 for (i = 0; i < 4; i++)
3060 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3061 cSY + 4 + oby + 22 * (i / 2)))
3068 void DeletePacMan(int px, int py)
3074 if (game_mm.num_pacman <= 1)
3076 game_mm.num_pacman = 0;
3080 for (i = 0; i < game_mm.num_pacman; i++)
3081 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3084 game_mm.num_pacman--;
3086 for (j = i; j < game_mm.num_pacman; j++)
3088 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3089 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3090 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3094 void ColorCycling(void)
3096 static int CC, Cc = 0;
3098 static int color, old = 0xF00, new = 0x010, mult = 1;
3099 static unsigned short red, green, blue;
3101 if (color_status == STATIC_COLORS)
3106 if (CC < Cc || CC > Cc + 2)
3110 color = old + new * mult;
3116 if (ABS(mult) == 16)
3126 red = 0x0e00 * ((color & 0xF00) >> 8);
3127 green = 0x0e00 * ((color & 0x0F0) >> 4);
3128 blue = 0x0e00 * ((color & 0x00F));
3129 SetRGB(pen_magicolor[0], red, green, blue);
3131 red = 0x1111 * ((color & 0xF00) >> 8);
3132 green = 0x1111 * ((color & 0x0F0) >> 4);
3133 blue = 0x1111 * ((color & 0x00F));
3134 SetRGB(pen_magicolor[1], red, green, blue);
3138 static void GameActions_MM_Ext(void)
3145 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3148 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3150 element = Tile[x][y];
3152 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3153 StartMoving_MM(x, y);
3154 else if (IS_MOVING(x, y))
3155 ContinueMoving_MM(x, y);
3156 else if (IS_EXPLODING(element))
3157 Explode_MM(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
3158 else if (element == EL_EXIT_OPENING)
3160 else if (element == EL_GRAY_BALL_OPENING)
3161 OpenSurpriseBall(x, y);
3162 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3164 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3167 DrawFieldAnimated_MM(x, y);
3170 AutoRotateMirrors();
3173 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3175 // redraw after Explode_MM() ...
3177 DrawLaser(0, DL_LASER_ENABLED);
3178 laser.redraw = FALSE;
3183 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3187 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3189 DrawLaser(0, DL_LASER_DISABLED);
3194 // skip all following game actions if game is over
3195 if (game_mm.game_over)
3198 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3202 GameOver_MM(GAME_OVER_NO_ENERGY);
3207 if (FrameReached(&energy_delay))
3209 if (game_mm.energy_left > 0)
3210 game_mm.energy_left--;
3212 // when out of energy, wait another frame to play "out of time" sound
3215 element = laser.dest_element;
3218 if (element != Tile[ELX][ELY])
3220 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3221 element, Tile[ELX][ELY]);
3225 if (!laser.overloaded && laser.overload_value == 0 &&
3226 element != EL_BOMB &&
3227 element != EL_BOMB_ACTIVE &&
3228 element != EL_MINE &&
3229 element != EL_MINE_ACTIVE &&
3230 element != EL_BALL_GRAY &&
3231 element != EL_BLOCK_STONE &&
3232 element != EL_BLOCK_WOOD &&
3233 element != EL_FUSE_ON &&
3234 element != EL_FUEL_FULL &&
3235 !IS_WALL_ICE(element) &&
3236 !IS_WALL_AMOEBA(element))
3239 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3241 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3242 (!laser.overloaded && laser.overload_value > 0)) &&
3243 FrameReached(&overload_delay))
3245 if (laser.overloaded)
3246 laser.overload_value++;
3248 laser.overload_value--;
3250 if (game_mm.cheat_no_overload)
3252 laser.overloaded = FALSE;
3253 laser.overload_value = 0;
3256 game_mm.laser_overload_value = laser.overload_value;
3258 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3260 SetLaserColor(0xFF);
3262 DrawLaser(0, DL_LASER_ENABLED);
3265 if (!laser.overloaded)
3266 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3267 else if (setup.sound_loops)
3268 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3270 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3272 if (laser.overloaded)
3275 BlitBitmap(pix[PIX_DOOR], drawto,
3276 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3277 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3278 - laser.overload_value,
3279 OVERLOAD_XSIZE, laser.overload_value,
3280 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3281 - laser.overload_value);
3283 redraw_mask |= REDRAW_DOOR_1;
3288 BlitBitmap(pix[PIX_DOOR], drawto,
3289 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3290 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3291 DX_OVERLOAD, DY_OVERLOAD);
3293 redraw_mask |= REDRAW_DOOR_1;
3296 if (laser.overload_value == MAX_LASER_OVERLOAD)
3298 UpdateAndDisplayGameControlValues();
3302 GameOver_MM(GAME_OVER_OVERLOADED);
3313 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3315 if (game_mm.cheat_no_explosion)
3320 laser.dest_element = EL_EXPLODING_OPAQUE;
3325 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3327 laser.fuse_off = TRUE;
3331 DrawLaser(0, DL_LASER_DISABLED);
3332 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3335 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3337 int last_anim_random_frame = gfx.anim_random_frame;
3340 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3341 gfx.anim_random_frame = RND(native_mm_level.num_ball_contents);
3343 element_pos = getAnimationFrame(native_mm_level.num_ball_contents, 1,
3344 native_mm_level.ball_choice_mode, 0,
3345 game_mm.ball_choice_pos);
3347 if (native_mm_level.ball_choice_mode == ANIM_RANDOM)
3348 gfx.anim_random_frame = last_anim_random_frame;
3350 game_mm.ball_choice_pos++;
3352 int new_element = native_mm_level.ball_content[element_pos];
3354 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3355 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3357 // !!! CHECK AGAIN: Laser on Polarizer !!!
3368 element = EL_MIRROR_START + RND(16);
3374 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3381 element = (rnd == 0 ? EL_FUSE_ON :
3382 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3383 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3384 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3385 EL_MIRROR_FIXED_START + rnd - 25);
3390 graphic = el2gfx(element);
3392 for (i = 0; i < 50; i++)
3398 BlitBitmap(pix[PIX_BACK], drawto,
3399 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3400 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3401 SX + ELX * TILEX + x,
3402 SY + ELY * TILEY + y);
3404 MarkTileDirty(ELX, ELY);
3407 DrawLaser(0, DL_LASER_ENABLED);
3409 Delay_WithScreenUpdates(50);
3412 Tile[ELX][ELY] = element;
3413 DrawField_MM(ELX, ELY);
3416 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3419 // above stuff: GRAY BALL -> PRISM !!!
3421 LX = ELX * TILEX + 14;
3422 LY = ELY * TILEY + 14;
3423 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3430 laser.num_edges -= 2;
3431 laser.num_damages--;
3435 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3436 if (laser.damage[i].is_mirror)
3440 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3442 DrawLaser(0, DL_LASER_DISABLED);
3444 DrawLaser(0, DL_LASER_DISABLED);
3453 if (IS_WALL_ICE(element) && CT > 50)
3455 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3458 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3459 Store[ELX][ELY] = EL_WALL_ICE;
3460 Store2[ELX][ELY] = laser.wall_mask;
3462 laser.dest_element = Tile[ELX][ELY];
3467 for (i = 0; i < 5; i++)
3473 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3477 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3479 Delay_WithScreenUpdates(100);
3482 if (Tile[ELX][ELY] == EL_WALL_ICE)
3483 Tile[ELX][ELY] = EL_EMPTY;
3487 LX = laser.edge[laser.num_edges].x - cSX2;
3488 LY = laser.edge[laser.num_edges].y - cSY2;
3491 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3492 if (laser.damage[i].is_mirror)
3496 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3498 DrawLaser(0, DL_LASER_DISABLED);
3505 if (IS_WALL_AMOEBA(element) && CT > 60)
3507 int k1, k2, k3, dx, dy, de, dm;
3508 int element2 = Tile[ELX][ELY];
3510 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3513 for (i = laser.num_damages - 1; i >= 0; i--)
3514 if (laser.damage[i].is_mirror)
3517 r = laser.num_edges;
3518 d = laser.num_damages;
3525 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3528 DrawLaser(0, DL_LASER_ENABLED);
3531 x = laser.damage[k1].x;
3532 y = laser.damage[k1].y;
3537 for (i = 0; i < 4; i++)
3539 if (laser.wall_mask & (1 << i))
3541 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3542 cSY + ELY * TILEY + 31 * (i / 2)))
3545 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3546 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3553 for (i = 0; i < 4; i++)
3555 if (laser.wall_mask & (1 << i))
3557 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3558 cSY + ELY * TILEY + 31 * (i / 2)))
3565 if (laser.num_beamers > 0 ||
3566 k1 < 1 || k2 < 4 || k3 < 4 ||
3567 CheckLaserPixel(cSX + ELX * TILEX + 14,
3568 cSY + ELY * TILEY + 14))
3570 laser.num_edges = r;
3571 laser.num_damages = d;
3573 DrawLaser(0, DL_LASER_DISABLED);
3576 Tile[ELX][ELY] = element | laser.wall_mask;
3580 de = Tile[ELX][ELY];
3581 dm = laser.wall_mask;
3585 int x = ELX, y = ELY;
3586 int wall_mask = laser.wall_mask;
3589 DrawLaser(0, DL_LASER_ENABLED);
3591 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3593 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3594 Store[x][y] = EL_WALL_AMOEBA;
3595 Store2[x][y] = wall_mask;
3601 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3603 DrawLaser(0, DL_LASER_ENABLED);
3605 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3607 for (i = 4; i >= 0; i--)
3609 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3612 Delay_WithScreenUpdates(20);
3615 DrawLaser(0, DL_LASER_ENABLED);
3620 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3621 laser.stops_inside_element && CT > native_mm_level.time_block)
3626 if (ABS(XS) > ABS(YS))
3633 for (i = 0; i < 4; i++)
3640 x = ELX + Step[k * 4].x;
3641 y = ELY + Step[k * 4].y;
3643 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3646 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3654 laser.overloaded = (element == EL_BLOCK_STONE);
3659 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3662 Tile[x][y] = element;
3664 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3667 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3669 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3670 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3678 if (element == EL_FUEL_FULL && CT > 10)
3680 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3681 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3683 for (i = start; i <= num_init_game_frames; i++)
3685 if (i == num_init_game_frames)
3686 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3687 else if (setup.sound_loops)
3688 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3690 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3692 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3694 UpdateAndDisplayGameControlValues();
3699 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3701 DrawField_MM(ELX, ELY);
3703 DrawLaser(0, DL_LASER_ENABLED);
3711 void GameActions_MM(struct MouseActionInfo action)
3713 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3714 boolean button_released = (action.button == MB_RELEASED);
3716 GameActions_MM_Ext();
3718 CheckSingleStepMode_MM(element_clicked, button_released);
3721 void MovePacMen(void)
3723 int mx, my, ox, oy, nx, ny;
3727 if (++pacman_nr >= game_mm.num_pacman)
3730 game_mm.pacman[pacman_nr].dir--;
3732 for (l = 1; l < 5; l++)
3734 game_mm.pacman[pacman_nr].dir++;
3736 if (game_mm.pacman[pacman_nr].dir > 4)
3737 game_mm.pacman[pacman_nr].dir = 1;
3739 if (game_mm.pacman[pacman_nr].dir % 2)
3742 my = game_mm.pacman[pacman_nr].dir - 2;
3747 mx = 3 - game_mm.pacman[pacman_nr].dir;
3750 ox = game_mm.pacman[pacman_nr].x;
3751 oy = game_mm.pacman[pacman_nr].y;
3754 element = Tile[nx][ny];
3756 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3759 if (!IS_EATABLE4PACMAN(element))
3762 if (ObjHit(nx, ny, HIT_POS_CENTER))
3765 Tile[ox][oy] = EL_EMPTY;
3767 EL_PACMAN_RIGHT - 1 +
3768 (game_mm.pacman[pacman_nr].dir - 1 +
3769 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3771 game_mm.pacman[pacman_nr].x = nx;
3772 game_mm.pacman[pacman_nr].y = ny;
3774 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3776 if (element != EL_EMPTY)
3778 int graphic = el2gfx(Tile[nx][ny]);
3783 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3786 ox = cSX + ox * TILEX;
3787 oy = cSY + oy * TILEY;
3789 for (i = 1; i < 33; i += 2)
3790 BlitBitmap(bitmap, window,
3791 src_x, src_y, TILEX, TILEY,
3792 ox + i * mx, oy + i * my);
3793 Ct = Ct + FrameCounter - CT;
3796 DrawField_MM(nx, ny);
3799 if (!laser.fuse_off)
3801 DrawLaser(0, DL_LASER_ENABLED);
3803 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3805 AddDamagedField(nx, ny);
3807 laser.damage[laser.num_damages - 1].edge = 0;
3811 if (element == EL_BOMB)
3812 DeletePacMan(nx, ny);
3814 if (IS_WALL_AMOEBA(element) &&
3815 (LX + 2 * XS) / TILEX == nx &&
3816 (LY + 2 * YS) / TILEY == ny)
3826 static void InitMovingField_MM(int x, int y, int direction)
3828 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3829 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3831 MovDir[x][y] = direction;
3832 MovDir[newx][newy] = direction;
3834 if (Tile[newx][newy] == EL_EMPTY)
3835 Tile[newx][newy] = EL_BLOCKED;
3838 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3840 int direction = MovDir[x][y];
3841 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3842 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3848 static void Blocked2Moving_MM(int x, int y,
3849 int *comes_from_x, int *comes_from_y)
3851 int oldx = x, oldy = y;
3852 int direction = MovDir[x][y];
3854 if (direction == MV_LEFT)
3856 else if (direction == MV_RIGHT)
3858 else if (direction == MV_UP)
3860 else if (direction == MV_DOWN)
3863 *comes_from_x = oldx;
3864 *comes_from_y = oldy;
3867 static int MovingOrBlocked2Element_MM(int x, int y)
3869 int element = Tile[x][y];
3871 if (element == EL_BLOCKED)
3875 Blocked2Moving_MM(x, y, &oldx, &oldy);
3877 return Tile[oldx][oldy];
3884 static void RemoveField(int x, int y)
3886 Tile[x][y] = EL_EMPTY;
3893 static void RemoveMovingField_MM(int x, int y)
3895 int oldx = x, oldy = y, newx = x, newy = y;
3897 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3900 if (IS_MOVING(x, y))
3902 Moving2Blocked_MM(x, y, &newx, &newy);
3903 if (Tile[newx][newy] != EL_BLOCKED)
3906 else if (Tile[x][y] == EL_BLOCKED)
3908 Blocked2Moving_MM(x, y, &oldx, &oldy);
3909 if (!IS_MOVING(oldx, oldy))
3913 Tile[oldx][oldy] = EL_EMPTY;
3914 Tile[newx][newy] = EL_EMPTY;
3915 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3916 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3918 DrawLevelField_MM(oldx, oldy);
3919 DrawLevelField_MM(newx, newy);
3922 void PlaySoundLevel(int x, int y, int sound_nr)
3924 int sx = SCREENX(x), sy = SCREENY(y);
3926 int silence_distance = 8;
3928 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3929 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3932 if (!IN_LEV_FIELD(x, y) ||
3933 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3934 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3937 volume = SOUND_MAX_VOLUME;
3940 stereo = (sx - SCR_FIELDX/2) * 12;
3942 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3943 if (stereo > SOUND_MAX_RIGHT)
3944 stereo = SOUND_MAX_RIGHT;
3945 if (stereo < SOUND_MAX_LEFT)
3946 stereo = SOUND_MAX_LEFT;
3949 if (!IN_SCR_FIELD(sx, sy))
3951 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3952 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3954 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3957 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3960 static void RaiseScore_MM(int value)
3962 game_mm.score += value;
3965 void RaiseScoreElement_MM(int element)
3970 case EL_PACMAN_RIGHT:
3972 case EL_PACMAN_LEFT:
3973 case EL_PACMAN_DOWN:
3974 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3978 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3983 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3987 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3996 // ----------------------------------------------------------------------------
3997 // Mirror Magic game engine snapshot handling functions
3998 // ----------------------------------------------------------------------------
4000 void SaveEngineSnapshotValues_MM(void)
4004 engine_snapshot_mm.game_mm = game_mm;
4005 engine_snapshot_mm.laser = laser;
4007 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4009 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4011 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4012 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4013 engine_snapshot_mm.Box[x][y] = Box[x][y];
4014 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4018 engine_snapshot_mm.LX = LX;
4019 engine_snapshot_mm.LY = LY;
4020 engine_snapshot_mm.XS = XS;
4021 engine_snapshot_mm.YS = YS;
4022 engine_snapshot_mm.ELX = ELX;
4023 engine_snapshot_mm.ELY = ELY;
4024 engine_snapshot_mm.CT = CT;
4025 engine_snapshot_mm.Ct = Ct;
4027 engine_snapshot_mm.last_LX = last_LX;
4028 engine_snapshot_mm.last_LY = last_LY;
4029 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4030 engine_snapshot_mm.hold_x = hold_x;
4031 engine_snapshot_mm.hold_y = hold_y;
4032 engine_snapshot_mm.pacman_nr = pacman_nr;
4034 engine_snapshot_mm.rotate_delay = rotate_delay;
4035 engine_snapshot_mm.pacman_delay = pacman_delay;
4036 engine_snapshot_mm.energy_delay = energy_delay;
4037 engine_snapshot_mm.overload_delay = overload_delay;
4040 void LoadEngineSnapshotValues_MM(void)
4044 // stored engine snapshot buffers already restored at this point
4046 game_mm = engine_snapshot_mm.game_mm;
4047 laser = engine_snapshot_mm.laser;
4049 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4051 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4053 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4054 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4055 Box[x][y] = engine_snapshot_mm.Box[x][y];
4056 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4060 LX = engine_snapshot_mm.LX;
4061 LY = engine_snapshot_mm.LY;
4062 XS = engine_snapshot_mm.XS;
4063 YS = engine_snapshot_mm.YS;
4064 ELX = engine_snapshot_mm.ELX;
4065 ELY = engine_snapshot_mm.ELY;
4066 CT = engine_snapshot_mm.CT;
4067 Ct = engine_snapshot_mm.Ct;
4069 last_LX = engine_snapshot_mm.last_LX;
4070 last_LY = engine_snapshot_mm.last_LY;
4071 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4072 hold_x = engine_snapshot_mm.hold_x;
4073 hold_y = engine_snapshot_mm.hold_y;
4074 pacman_nr = engine_snapshot_mm.pacman_nr;
4076 rotate_delay = engine_snapshot_mm.rotate_delay;
4077 pacman_delay = engine_snapshot_mm.pacman_delay;
4078 energy_delay = engine_snapshot_mm.energy_delay;
4079 overload_delay = engine_snapshot_mm.overload_delay;
4081 RedrawPlayfield_MM();
4084 static int getAngleFromTouchDelta(int dx, int dy, int base)
4086 double pi = 3.141592653;
4087 double rad = atan2((double)-dy, (double)dx);
4088 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4089 double deg = rad2 * 180.0 / pi;
4091 return (int)(deg * base / 360.0 + 0.5) % base;
4094 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4096 // calculate start (source) position to be at the middle of the tile
4097 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4098 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4099 int dx = dst_mx - src_mx;
4100 int dy = dst_my - src_my;
4109 if (!IN_LEV_FIELD(x, y))
4112 element = Tile[x][y];
4114 if (!IS_MCDUFFIN(element) &&
4115 !IS_MIRROR(element) &&
4116 !IS_BEAMER(element) &&
4117 !IS_POLAR(element) &&
4118 !IS_POLAR_CROSS(element) &&
4119 !IS_DF_MIRROR(element))
4122 angle_old = get_element_angle(element);
4124 if (IS_MCDUFFIN(element))
4126 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4127 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4128 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4129 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4132 else if (IS_MIRROR(element) ||
4133 IS_DF_MIRROR(element))
4135 for (i = 0; i < laser.num_damages; i++)
4137 if (laser.damage[i].x == x &&
4138 laser.damage[i].y == y &&
4139 ObjHit(x, y, HIT_POS_CENTER))
4141 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4142 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4149 if (angle_new == -1)
4151 if (IS_MIRROR(element) ||
4152 IS_DF_MIRROR(element) ||
4156 if (IS_POLAR_CROSS(element))
4159 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4162 button = (angle_new == angle_old ? 0 :
4163 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4164 MB_LEFTBUTTON : MB_RIGHTBUTTON);