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
32 // special positions in the game control window (relative to control window)
41 #define XX_OVERLOAD 60
42 #define YY_OVERLOAD YY_ENERGY
44 // special positions in the game control window (relative to main window)
45 #define DX_LEVEL (DX + XX_LEVEL)
46 #define DY_LEVEL (DY + YY_LEVEL)
47 #define DX_KETTLES (DX + XX_KETTLES)
48 #define DY_KETTLES (DY + YY_KETTLES)
49 #define DX_SCORE (DX + XX_SCORE)
50 #define DY_SCORE (DY + YY_SCORE)
51 #define DX_ENERGY (DX + XX_ENERGY)
52 #define DY_ENERGY (DY + YY_ENERGY)
53 #define DX_OVERLOAD (DX + XX_OVERLOAD)
54 #define DY_OVERLOAD (DY + YY_OVERLOAD)
56 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
57 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
59 // game button identifiers
60 #define GAME_CTRL_ID_LEFT 0
61 #define GAME_CTRL_ID_MIDDLE 1
62 #define GAME_CTRL_ID_RIGHT 2
64 #define NUM_GAME_BUTTONS 3
66 // values for DrawLaser()
67 #define DL_LASER_DISABLED 0
68 #define DL_LASER_ENABLED 1
70 // values for 'click_delay_value' in ClickElement()
71 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
72 #define CLICK_DELAY 6 // delay (frames) for pressed butten
74 #define AUTO_ROTATE_DELAY CLICK_DELAY
75 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
76 #define NUM_INIT_CYCLE_STEPS 16
77 #define PACMAN_MOVE_DELAY 12
78 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
79 #define HEALTH_DEC_DELAY 3
80 #define HEALTH_INC_DELAY 9
81 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
83 #define BEGIN_NO_HEADLESS \
85 boolean last_headless = program.headless; \
87 program.headless = FALSE; \
89 #define END_NO_HEADLESS \
90 program.headless = last_headless; \
93 // forward declaration for internal use
94 static int MovingOrBlocked2Element_MM(int, int);
95 static void Bang_MM(int, int);
96 static void RaiseScore_MM(int);
97 static void RaiseScoreElement_MM(int);
98 static void RemoveMovingField_MM(int, int);
99 static void InitMovingField_MM(int, int, int);
100 static void ContinueMoving_MM(int, int);
101 static void Moving2Blocked_MM(int, int, int *, int *);
103 // bitmap for laser beam detection
104 static Bitmap *laser_bitmap = NULL;
106 // variables for laser control
107 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
108 static int hold_x = -1, hold_y = -1;
110 // variables for pacman control
111 static int pacman_nr = -1;
113 // various game engine delay counters
114 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
115 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
116 static DelayCounter energy_delay = { ENERGY_DELAY };
117 static DelayCounter overload_delay = { 0 };
119 // element mask positions for scanning pixels of MM elements
120 #define MM_MASK_MCDUFFIN_RIGHT 0
121 #define MM_MASK_MCDUFFIN_UP 1
122 #define MM_MASK_MCDUFFIN_LEFT 2
123 #define MM_MASK_MCDUFFIN_DOWN 3
124 #define MM_MASK_GRID_1 4
125 #define MM_MASK_GRID_2 5
126 #define MM_MASK_GRID_3 6
127 #define MM_MASK_GRID_4 7
128 #define MM_MASK_RECTANGLE 8
129 #define MM_MASK_CIRCLE 9
131 #define NUM_MM_MASKS 10
133 // element masks for scanning pixels of MM elements
134 static const char mm_masks[NUM_MM_MASKS][16][16 + 1] =
318 static int get_element_angle(int element)
320 int element_phase = get_element_phase(element);
322 if (IS_MIRROR_FIXED(element) ||
323 IS_MCDUFFIN(element) ||
325 IS_RECEIVER(element))
326 return 4 * element_phase;
328 return element_phase;
331 static int get_opposite_angle(int angle)
333 int opposite_angle = angle + ANG_RAY_180;
335 // make sure "opposite_angle" is in valid interval [0, 15]
336 return (opposite_angle + 16) % 16;
339 static int get_mirrored_angle(int laser_angle, int mirror_angle)
341 int reflected_angle = 16 - laser_angle + mirror_angle;
343 // make sure "reflected_angle" is in valid interval [0, 15]
344 return (reflected_angle + 16) % 16;
347 static void DrawLaserLines(struct XY *points, int num_points, int mode)
349 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
350 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
352 DrawLines(drawto, points, num_points, pixel_drawto);
356 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
361 static boolean CheckLaserPixel(int x, int y)
367 pixel = ReadPixel(laser_bitmap, x, y);
371 return (pixel == WHITE_PIXEL);
374 static void CheckExitMM(void)
376 int exit_element = EL_EMPTY;
380 static int xy[4][2] =
388 for (y = 0; y < lev_fieldy; y++)
390 for (x = 0; x < lev_fieldx; x++)
392 if (Tile[x][y] == EL_EXIT_CLOSED)
394 // initiate opening animation of exit door
395 Tile[x][y] = EL_EXIT_OPENING;
397 exit_element = EL_EXIT_OPEN;
401 else if (IS_RECEIVER(Tile[x][y]))
403 // remove field that blocks receiver
404 int phase = Tile[x][y] - EL_RECEIVER_START;
405 int blocking_x, blocking_y;
407 blocking_x = x + xy[phase][0];
408 blocking_y = y + xy[phase][1];
410 if (IN_LEV_FIELD(blocking_x, blocking_y))
412 Tile[blocking_x][blocking_y] = EL_EMPTY;
414 DrawField_MM(blocking_x, blocking_y);
417 exit_element = EL_RECEIVER;
424 if (exit_element != EL_EMPTY)
425 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
428 static void SetLaserColor(int brightness)
430 int color_min = 0x00;
431 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
432 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
433 int color_down = color_max - color_up;
436 GetPixelFromRGB(window,
437 (native_mm_level.laser_red ? color_max : color_up),
438 (native_mm_level.laser_green ? color_down : color_min),
439 (native_mm_level.laser_blue ? color_down : color_min));
442 static void InitMovDir_MM(int x, int y)
444 int element = Tile[x][y];
445 static int direction[3][4] =
447 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
448 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
449 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
454 case EL_PACMAN_RIGHT:
458 Tile[x][y] = EL_PACMAN;
459 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
467 static void InitField(int x, int y)
469 int element = Tile[x][y];
474 Tile[x][y] = EL_EMPTY;
479 if (native_mm_level.auto_count_kettles)
480 game_mm.kettles_still_needed++;
483 case EL_LIGHTBULB_OFF:
484 game_mm.lights_still_needed++;
488 if (IS_MIRROR(element) ||
489 IS_BEAMER_OLD(element) ||
490 IS_BEAMER(element) ||
492 IS_POLAR_CROSS(element) ||
493 IS_DF_MIRROR(element) ||
494 IS_DF_MIRROR_AUTO(element) ||
495 IS_GRID_STEEL_AUTO(element) ||
496 IS_GRID_WOOD_AUTO(element) ||
497 IS_FIBRE_OPTIC(element))
499 if (IS_BEAMER_OLD(element))
501 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
502 element = Tile[x][y];
505 if (!IS_FIBRE_OPTIC(element))
507 static int steps_grid_auto = 0;
509 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
510 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
512 if (IS_GRID_STEEL_AUTO(element) ||
513 IS_GRID_WOOD_AUTO(element))
514 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
516 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
518 game_mm.cycle[game_mm.num_cycle].x = x;
519 game_mm.cycle[game_mm.num_cycle].y = y;
523 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
525 int beamer_nr = BEAMER_NR(element);
526 int nr = laser.beamer[beamer_nr][0].num;
528 laser.beamer[beamer_nr][nr].x = x;
529 laser.beamer[beamer_nr][nr].y = y;
530 laser.beamer[beamer_nr][nr].num = 1;
533 else if (IS_PACMAN(element))
537 else if (IS_MCDUFFIN(element) || IS_LASER(element))
539 laser.start_edge.x = x;
540 laser.start_edge.y = y;
541 laser.start_angle = get_element_angle(element);
548 static void InitCycleElements_RotateSingleStep(void)
552 if (game_mm.num_cycle == 0) // no elements to cycle
555 for (i = 0; i < game_mm.num_cycle; i++)
557 int x = game_mm.cycle[i].x;
558 int y = game_mm.cycle[i].y;
559 int step = SIGN(game_mm.cycle[i].steps);
560 int last_element = Tile[x][y];
561 int next_element = get_rotated_element(last_element, step);
563 if (!game_mm.cycle[i].steps)
566 Tile[x][y] = next_element;
568 game_mm.cycle[i].steps -= step;
572 static void InitLaser(void)
574 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
575 int step = (IS_LASER(start_element) ? 4 : 0);
577 LX = laser.start_edge.x * TILEX;
578 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
581 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
583 LY = laser.start_edge.y * TILEY;
584 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
585 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
589 XS = 2 * Step[laser.start_angle].x;
590 YS = 2 * Step[laser.start_angle].y;
592 laser.current_angle = laser.start_angle;
594 laser.num_damages = 0;
596 laser.num_beamers = 0;
597 laser.beamer_edge[0] = 0;
599 laser.dest_element = EL_EMPTY;
602 AddLaserEdge(LX, LY); // set laser starting edge
607 void InitGameEngine_MM(void)
613 // initialize laser bitmap to current playfield (screen) size
614 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
615 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
619 // set global game control values
620 game_mm.num_cycle = 0;
621 game_mm.num_pacman = 0;
624 game_mm.energy_left = 0; // later set to "native_mm_level.time"
625 game_mm.kettles_still_needed =
626 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
627 game_mm.lights_still_needed = 0;
628 game_mm.num_keys = 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;
664 rotate_delay.count = 0;
665 pacman_delay.count = 0;
666 energy_delay.count = 0;
667 overload_delay.count = 0;
669 ClickElement(-1, -1, -1);
671 for (x = 0; x < lev_fieldx; x++)
673 for (y = 0; y < lev_fieldy; y++)
675 Tile[x][y] = Ur[x][y];
676 Hit[x][y] = Box[x][y] = 0;
678 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
679 Store[x][y] = Store2[x][y] = 0;
689 void InitGameActions_MM(void)
691 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
692 int cycle_steps_done = 0;
697 for (i = 0; i <= num_init_game_frames; i++)
699 if (i == num_init_game_frames)
700 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
701 else if (setup.sound_loops)
702 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
704 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
706 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
708 UpdateAndDisplayGameControlValues();
710 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
712 InitCycleElements_RotateSingleStep();
717 AdvanceFrameCounter();
727 if (setup.quick_doors)
734 if (game_mm.kettles_still_needed == 0)
737 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
738 SetTileCursorActive(TRUE);
740 ResetFrameCounter(&energy_delay);
743 static void FadeOutLaser(void)
747 for (i = 15; i >= 0; i--)
749 SetLaserColor(0x11 * i);
751 DrawLaser(0, DL_LASER_ENABLED);
754 Delay_WithScreenUpdates(50);
757 DrawLaser(0, DL_LASER_DISABLED);
759 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
762 static void GameOver_MM(int game_over_cause)
764 // do not handle game over if request dialog is already active
765 if (game.request_active)
768 game_mm.game_over = TRUE;
769 game_mm.game_over_cause = game_over_cause;
771 if (setup.ask_on_game_over)
772 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
773 "Bomb killed Mc Duffin! Play it again?" :
774 game_over_cause == GAME_OVER_NO_ENERGY ?
775 "Out of magic energy! Play it again?" :
776 game_over_cause == GAME_OVER_OVERLOADED ?
777 "Magic spell hit Mc Duffin! Play it again?" :
780 SetTileCursorActive(FALSE);
783 void AddLaserEdge(int lx, int ly)
788 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
790 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
795 laser.edge[laser.num_edges].x = cSX2 + lx;
796 laser.edge[laser.num_edges].y = cSY2 + ly;
802 void AddDamagedField(int ex, int ey)
804 laser.damage[laser.num_damages].is_mirror = FALSE;
805 laser.damage[laser.num_damages].angle = laser.current_angle;
806 laser.damage[laser.num_damages].edge = laser.num_edges;
807 laser.damage[laser.num_damages].x = ex;
808 laser.damage[laser.num_damages].y = ey;
812 static boolean StepBehind(void)
818 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
819 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
821 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
827 static int getMaskFromElement(int element)
829 if (IS_GRID(element))
830 return MM_MASK_GRID_1 + get_element_phase(element);
831 else if (IS_MCDUFFIN(element))
832 return MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
833 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
834 return MM_MASK_RECTANGLE;
836 return MM_MASK_CIRCLE;
839 static int ScanPixel(void)
844 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
845 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
848 // follow laser beam until it hits something (at least the screen border)
849 while (hit_mask == HIT_MASK_NO_HIT)
855 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
856 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
858 Debug("game:mm:ScanPixel", "touched screen border!");
864 for (i = 0; i < 4; i++)
866 int px = LX + (i % 2) * 2;
867 int py = LY + (i / 2) * 2;
870 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
871 int ly = (py + TILEY) / TILEY - 1; // negative values!
874 if (IN_LEV_FIELD(lx, ly))
876 int element = Tile[lx][ly];
878 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
882 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
884 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
886 pixel = ((element & (1 << pos)) ? 1 : 0);
890 int pos = getMaskFromElement(element);
892 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
897 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
898 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
901 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
902 hit_mask |= (1 << i);
905 if (hit_mask == HIT_MASK_NO_HIT)
907 // hit nothing -- go on with another step
919 int end = 0, rf = laser.num_edges;
921 // do not scan laser again after the game was lost for whatever reason
922 if (game_mm.game_over)
925 laser.overloaded = FALSE;
926 laser.stops_inside_element = FALSE;
928 DrawLaser(0, DL_LASER_ENABLED);
931 Debug("game:mm:ScanLaser",
932 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
940 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
943 laser.overloaded = TRUE;
948 hit_mask = ScanPixel();
951 Debug("game:mm:ScanLaser",
952 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
956 // hit something -- check out what it was
957 ELX = (LX + XS) / TILEX;
958 ELY = (LY + YS) / TILEY;
961 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
962 hit_mask, LX, LY, ELX, ELY);
965 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
968 laser.dest_element = element;
973 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
975 /* we have hit the top-right and bottom-left element --
976 choose the bottom-left one */
977 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
978 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
979 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
980 ELX = (LX - 2) / TILEX;
981 ELY = (LY + 2) / TILEY;
984 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
986 /* we have hit the top-left and bottom-right element --
987 choose the top-left one */
989 ELX = (LX - 2) / TILEX;
990 ELY = (LY - 2) / TILEY;
994 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
995 hit_mask, LX, LY, ELX, ELY);
998 element = Tile[ELX][ELY];
999 laser.dest_element = element;
1002 Debug("game:mm:ScanLaser",
1003 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
1006 LX % TILEX, LY % TILEY,
1011 if (!IN_LEV_FIELD(ELX, ELY))
1012 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1016 if (element == EL_EMPTY)
1018 if (!HitOnlyAnEdge(hit_mask))
1021 else if (element == EL_FUSE_ON)
1023 if (HitPolarizer(element, hit_mask))
1026 else if (IS_GRID(element) || IS_DF_GRID(element))
1028 if (HitPolarizer(element, hit_mask))
1031 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1032 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1034 if (HitBlock(element, hit_mask))
1041 else if (IS_MCDUFFIN(element))
1043 if (HitLaserSource(element, hit_mask))
1046 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1047 IS_RECEIVER(element))
1049 if (HitLaserDestination(element, hit_mask))
1052 else if (IS_WALL(element))
1054 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1056 if (HitReflectingWalls(element, hit_mask))
1061 if (HitAbsorbingWalls(element, hit_mask))
1067 if (HitElement(element, hit_mask))
1072 DrawLaser(rf - 1, DL_LASER_ENABLED);
1073 rf = laser.num_edges;
1077 if (laser.dest_element != Tile[ELX][ELY])
1079 Debug("game:mm:ScanLaser",
1080 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1081 laser.dest_element, Tile[ELX][ELY]);
1085 if (!end && !laser.stops_inside_element && !StepBehind())
1088 Debug("game:mm:ScanLaser", "Go one step back");
1094 AddLaserEdge(LX, LY);
1098 DrawLaser(rf - 1, DL_LASER_ENABLED);
1100 Ct = CT = FrameCounter;
1103 if (!IN_LEV_FIELD(ELX, ELY))
1104 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1108 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1114 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1115 start_edge, num_edges, mode);
1120 Warn("DrawLaserExt: start_edge < 0");
1127 Warn("DrawLaserExt: num_edges < 0");
1133 if (mode == DL_LASER_DISABLED)
1135 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1139 // now draw the laser to the backbuffer and (if enabled) to the screen
1140 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1142 redraw_mask |= REDRAW_FIELD;
1144 if (mode == DL_LASER_ENABLED)
1147 // after the laser was deleted, the "damaged" graphics must be restored
1148 if (laser.num_damages)
1150 int damage_start = 0;
1153 // determine the starting edge, from which graphics need to be restored
1156 for (i = 0; i < laser.num_damages; i++)
1158 if (laser.damage[i].edge == start_edge + 1)
1167 // restore graphics from this starting edge to the end of damage list
1168 for (i = damage_start; i < laser.num_damages; i++)
1170 int lx = laser.damage[i].x;
1171 int ly = laser.damage[i].y;
1172 int element = Tile[lx][ly];
1174 if (Hit[lx][ly] == laser.damage[i].edge)
1175 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1178 if (Box[lx][ly] == laser.damage[i].edge)
1181 if (IS_DRAWABLE(element))
1182 DrawField_MM(lx, ly);
1185 elx = laser.damage[damage_start].x;
1186 ely = laser.damage[damage_start].y;
1187 element = Tile[elx][ely];
1190 if (IS_BEAMER(element))
1194 for (i = 0; i < laser.num_beamers; i++)
1195 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1197 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1198 mode, elx, ely, Hit[elx][ely], start_edge);
1199 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1200 get_element_angle(element), laser.damage[damage_start].angle);
1204 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1205 laser.num_beamers > 0 &&
1206 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1208 // element is outgoing beamer
1209 laser.num_damages = damage_start + 1;
1211 if (IS_BEAMER(element))
1212 laser.current_angle = get_element_angle(element);
1216 // element is incoming beamer or other element
1217 laser.num_damages = damage_start;
1218 laser.current_angle = laser.damage[laser.num_damages].angle;
1223 // no damages but McDuffin himself (who needs to be redrawn anyway)
1225 elx = laser.start_edge.x;
1226 ely = laser.start_edge.y;
1227 element = Tile[elx][ely];
1230 laser.num_edges = start_edge + 1;
1231 if (start_edge == 0)
1232 laser.current_angle = laser.start_angle;
1234 LX = laser.edge[start_edge].x - cSX2;
1235 LY = laser.edge[start_edge].y - cSY2;
1236 XS = 2 * Step[laser.current_angle].x;
1237 YS = 2 * Step[laser.current_angle].y;
1240 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1246 if (IS_BEAMER(element) ||
1247 IS_FIBRE_OPTIC(element) ||
1248 IS_PACMAN(element) ||
1249 IS_POLAR(element) ||
1250 IS_POLAR_CROSS(element) ||
1251 element == EL_FUSE_ON)
1256 Debug("game:mm:DrawLaserExt", "element == %d", element);
1259 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1260 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1264 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1265 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1266 (laser.num_beamers == 0 ||
1267 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1269 // element is incoming beamer or other element
1270 step_size = -step_size;
1275 if (IS_BEAMER(element))
1276 Debug("game:mm:DrawLaserExt",
1277 "start_edge == %d, laser.beamer_edge == %d",
1278 start_edge, laser.beamer_edge);
1281 LX += step_size * XS;
1282 LY += step_size * YS;
1284 else if (element != EL_EMPTY)
1293 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1298 void DrawLaser(int start_edge, int mode)
1300 if (laser.num_edges - start_edge < 0)
1302 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1307 // check if laser is interrupted by beamer element
1308 if (laser.num_beamers > 0 &&
1309 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1311 if (mode == DL_LASER_ENABLED)
1314 int tmp_start_edge = start_edge;
1316 // draw laser segments forward from the start to the last beamer
1317 for (i = 0; i < laser.num_beamers; i++)
1319 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1321 if (tmp_num_edges <= 0)
1325 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1326 i, laser.beamer_edge[i], tmp_start_edge);
1329 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1331 tmp_start_edge = laser.beamer_edge[i];
1334 // draw last segment from last beamer to the end
1335 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1341 int last_num_edges = laser.num_edges;
1342 int num_beamers = laser.num_beamers;
1344 // delete laser segments backward from the end to the first beamer
1345 for (i = num_beamers - 1; i >= 0; i--)
1347 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1349 if (laser.beamer_edge[i] - start_edge <= 0)
1352 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1354 last_num_edges = laser.beamer_edge[i];
1355 laser.num_beamers--;
1359 if (last_num_edges - start_edge <= 0)
1360 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1361 last_num_edges, start_edge);
1364 // special case when rotating first beamer: delete laser edge on beamer
1365 // (but do not start scanning on previous edge to prevent mirror sound)
1366 if (last_num_edges - start_edge == 1 && start_edge > 0)
1367 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1369 // delete first segment from start to the first beamer
1370 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1375 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1378 game_mm.laser_enabled = mode;
1381 void DrawLaser_MM(void)
1383 DrawLaser(0, game_mm.laser_enabled);
1386 boolean HitElement(int element, int hit_mask)
1388 if (HitOnlyAnEdge(hit_mask))
1391 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1392 element = MovingOrBlocked2Element_MM(ELX, ELY);
1395 Debug("game:mm:HitElement", "(1): element == %d", element);
1399 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1400 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1403 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1407 AddDamagedField(ELX, ELY);
1409 // this is more precise: check if laser would go through the center
1410 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1412 // skip the whole element before continuing the scan
1418 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1420 if (LX/TILEX > ELX || LY/TILEY > ELY)
1422 /* skipping scan positions to the right and down skips one scan
1423 position too much, because this is only the top left scan position
1424 of totally four scan positions (plus one to the right, one to the
1425 bottom and one to the bottom right) */
1435 Debug("game:mm:HitElement", "(2): element == %d", element);
1438 if (LX + 5 * XS < 0 ||
1448 Debug("game:mm:HitElement", "(3): element == %d", element);
1451 if (IS_POLAR(element) &&
1452 ((element - EL_POLAR_START) % 2 ||
1453 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1455 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1457 laser.num_damages--;
1462 if (IS_POLAR_CROSS(element) &&
1463 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1465 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1467 laser.num_damages--;
1472 if (!IS_BEAMER(element) &&
1473 !IS_FIBRE_OPTIC(element) &&
1474 !IS_GRID_WOOD(element) &&
1475 element != EL_FUEL_EMPTY)
1478 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1479 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1481 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1484 LX = ELX * TILEX + 14;
1485 LY = ELY * TILEY + 14;
1487 AddLaserEdge(LX, LY);
1490 if (IS_MIRROR(element) ||
1491 IS_MIRROR_FIXED(element) ||
1492 IS_POLAR(element) ||
1493 IS_POLAR_CROSS(element) ||
1494 IS_DF_MIRROR(element) ||
1495 IS_DF_MIRROR_AUTO(element) ||
1496 element == EL_PRISM ||
1497 element == EL_REFRACTOR)
1499 int current_angle = laser.current_angle;
1502 laser.num_damages--;
1504 AddDamagedField(ELX, ELY);
1506 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1509 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1511 if (IS_MIRROR(element) ||
1512 IS_MIRROR_FIXED(element) ||
1513 IS_DF_MIRROR(element) ||
1514 IS_DF_MIRROR_AUTO(element))
1515 laser.current_angle = get_mirrored_angle(laser.current_angle,
1516 get_element_angle(element));
1518 if (element == EL_PRISM || element == EL_REFRACTOR)
1519 laser.current_angle = RND(16);
1521 XS = 2 * Step[laser.current_angle].x;
1522 YS = 2 * Step[laser.current_angle].y;
1524 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1529 LX += step_size * XS;
1530 LY += step_size * YS;
1532 // draw sparkles on mirror
1533 if ((IS_MIRROR(element) ||
1534 IS_MIRROR_FIXED(element) ||
1535 element == EL_PRISM) &&
1536 current_angle != laser.current_angle)
1538 MovDelay[ELX][ELY] = 11; // start animation
1541 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1542 current_angle != laser.current_angle)
1543 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1546 (get_opposite_angle(laser.current_angle) ==
1547 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1549 return (laser.overloaded ? TRUE : FALSE);
1552 if (element == EL_FUEL_FULL)
1554 laser.stops_inside_element = TRUE;
1559 if (element == EL_BOMB || element == EL_MINE)
1561 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1563 if (element == EL_MINE)
1564 laser.overloaded = TRUE;
1567 if (element == EL_KETTLE ||
1568 element == EL_CELL ||
1569 element == EL_KEY ||
1570 element == EL_LIGHTBALL ||
1571 element == EL_PACMAN ||
1574 if (!IS_PACMAN(element))
1577 if (element == EL_PACMAN)
1580 if (element == EL_KETTLE || element == EL_CELL)
1582 if (game_mm.kettles_still_needed > 0)
1583 game_mm.kettles_still_needed--;
1585 game.snapshot.collected_item = TRUE;
1587 if (game_mm.kettles_still_needed == 0)
1591 DrawLaser(0, DL_LASER_ENABLED);
1594 else if (element == EL_KEY)
1598 else if (IS_PACMAN(element))
1600 DeletePacMan(ELX, ELY);
1603 RaiseScoreElement_MM(element);
1608 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1610 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1612 DrawLaser(0, DL_LASER_ENABLED);
1614 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1616 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1617 game_mm.lights_still_needed--;
1621 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1622 game_mm.lights_still_needed++;
1625 DrawField_MM(ELX, ELY);
1626 DrawLaser(0, DL_LASER_ENABLED);
1631 laser.stops_inside_element = TRUE;
1637 Debug("game:mm:HitElement", "(4): element == %d", element);
1640 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1641 laser.num_beamers < MAX_NUM_BEAMERS &&
1642 laser.beamer[BEAMER_NR(element)][1].num)
1644 int beamer_angle = get_element_angle(element);
1645 int beamer_nr = BEAMER_NR(element);
1649 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1652 laser.num_damages--;
1654 if (IS_FIBRE_OPTIC(element) ||
1655 laser.current_angle == get_opposite_angle(beamer_angle))
1659 LX = ELX * TILEX + 14;
1660 LY = ELY * TILEY + 14;
1662 AddLaserEdge(LX, LY);
1663 AddDamagedField(ELX, ELY);
1665 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1668 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1670 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1671 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1672 ELX = laser.beamer[beamer_nr][pos].x;
1673 ELY = laser.beamer[beamer_nr][pos].y;
1674 LX = ELX * TILEX + 14;
1675 LY = ELY * TILEY + 14;
1677 if (IS_BEAMER(element))
1679 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1680 XS = 2 * Step[laser.current_angle].x;
1681 YS = 2 * Step[laser.current_angle].y;
1684 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
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 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1699 LX += step_size * XS;
1700 LY += step_size * YS;
1702 laser.num_beamers++;
1711 boolean HitOnlyAnEdge(int hit_mask)
1713 // check if the laser hit only the edge of an element and, if so, go on
1716 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1720 if ((hit_mask == HIT_MASK_TOPLEFT ||
1721 hit_mask == HIT_MASK_TOPRIGHT ||
1722 hit_mask == HIT_MASK_BOTTOMLEFT ||
1723 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1724 laser.current_angle % 4) // angle is not 90°
1728 if (hit_mask == HIT_MASK_TOPLEFT)
1733 else if (hit_mask == HIT_MASK_TOPRIGHT)
1738 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1743 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1749 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1755 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1762 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1768 boolean HitPolarizer(int element, int hit_mask)
1770 if (HitOnlyAnEdge(hit_mask))
1773 if (IS_DF_GRID(element))
1775 int grid_angle = get_element_angle(element);
1778 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1779 grid_angle, laser.current_angle);
1782 AddLaserEdge(LX, LY);
1783 AddDamagedField(ELX, ELY);
1786 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1788 if (laser.current_angle == grid_angle ||
1789 laser.current_angle == get_opposite_angle(grid_angle))
1791 // skip the whole element before continuing the scan
1797 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1799 if (LX/TILEX > ELX || LY/TILEY > ELY)
1801 /* skipping scan positions to the right and down skips one scan
1802 position too much, because this is only the top left scan position
1803 of totally four scan positions (plus one to the right, one to the
1804 bottom and one to the bottom right) */
1810 AddLaserEdge(LX, LY);
1816 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1818 LX / TILEX, LY / TILEY,
1819 LX % TILEX, LY % TILEY);
1824 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1826 return HitReflectingWalls(element, hit_mask);
1830 return HitAbsorbingWalls(element, hit_mask);
1833 else if (IS_GRID_STEEL(element))
1835 return HitReflectingWalls(element, hit_mask);
1837 else // IS_GRID_WOOD
1839 return HitAbsorbingWalls(element, hit_mask);
1845 boolean HitBlock(int element, int hit_mask)
1847 boolean check = FALSE;
1849 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1850 game_mm.num_keys == 0)
1853 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1856 int ex = ELX * TILEX + 14;
1857 int ey = ELY * TILEY + 14;
1861 for (i = 1; i < 32; i++)
1866 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1871 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1872 return HitAbsorbingWalls(element, hit_mask);
1876 AddLaserEdge(LX - XS, LY - YS);
1877 AddDamagedField(ELX, ELY);
1880 Box[ELX][ELY] = laser.num_edges;
1882 return HitReflectingWalls(element, hit_mask);
1885 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1887 int xs = XS / 2, ys = YS / 2;
1888 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1889 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1891 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1892 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1894 laser.overloaded = (element == EL_GATE_STONE);
1899 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1900 (hit_mask == HIT_MASK_TOP ||
1901 hit_mask == HIT_MASK_LEFT ||
1902 hit_mask == HIT_MASK_RIGHT ||
1903 hit_mask == HIT_MASK_BOTTOM))
1904 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1905 hit_mask == HIT_MASK_BOTTOM),
1906 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1907 hit_mask == HIT_MASK_RIGHT));
1908 AddLaserEdge(LX, LY);
1914 if (element == EL_GATE_STONE && Box[ELX][ELY])
1916 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1928 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1930 int xs = XS / 2, ys = YS / 2;
1931 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1932 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1934 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1935 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1937 laser.overloaded = (element == EL_BLOCK_STONE);
1942 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1943 (hit_mask == HIT_MASK_TOP ||
1944 hit_mask == HIT_MASK_LEFT ||
1945 hit_mask == HIT_MASK_RIGHT ||
1946 hit_mask == HIT_MASK_BOTTOM))
1947 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1948 hit_mask == HIT_MASK_BOTTOM),
1949 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1950 hit_mask == HIT_MASK_RIGHT));
1951 AddDamagedField(ELX, ELY);
1953 LX = ELX * TILEX + 14;
1954 LY = ELY * TILEY + 14;
1956 AddLaserEdge(LX, LY);
1958 laser.stops_inside_element = TRUE;
1966 boolean HitLaserSource(int element, int hit_mask)
1968 if (HitOnlyAnEdge(hit_mask))
1971 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1973 laser.overloaded = TRUE;
1978 boolean HitLaserDestination(int element, int hit_mask)
1980 if (HitOnlyAnEdge(hit_mask))
1983 if (element != EL_EXIT_OPEN &&
1984 !(IS_RECEIVER(element) &&
1985 game_mm.kettles_still_needed == 0 &&
1986 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1988 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1993 if (IS_RECEIVER(element) ||
1994 (IS_22_5_ANGLE(laser.current_angle) &&
1995 (ELX != (LX + 6 * XS) / TILEX ||
1996 ELY != (LY + 6 * YS) / TILEY ||
2005 LX = ELX * TILEX + 14;
2006 LY = ELY * TILEY + 14;
2008 laser.stops_inside_element = TRUE;
2011 AddLaserEdge(LX, LY);
2012 AddDamagedField(ELX, ELY);
2014 if (game_mm.lights_still_needed == 0)
2016 game_mm.level_solved = TRUE;
2018 SetTileCursorActive(FALSE);
2024 boolean HitReflectingWalls(int element, int hit_mask)
2026 // check if laser hits side of a wall with an angle that is not 90°
2027 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2028 hit_mask == HIT_MASK_LEFT ||
2029 hit_mask == HIT_MASK_RIGHT ||
2030 hit_mask == HIT_MASK_BOTTOM))
2032 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2037 if (!IS_DF_GRID(element))
2038 AddLaserEdge(LX, LY);
2040 // check if laser hits wall with an angle of 45°
2041 if (!IS_22_5_ANGLE(laser.current_angle))
2043 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2046 laser.current_angle = get_mirrored_angle(laser.current_angle,
2049 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2052 laser.current_angle = get_mirrored_angle(laser.current_angle,
2056 AddLaserEdge(LX, LY);
2058 XS = 2 * Step[laser.current_angle].x;
2059 YS = 2 * Step[laser.current_angle].y;
2063 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2065 laser.current_angle = get_mirrored_angle(laser.current_angle,
2070 if (!IS_DF_GRID(element))
2071 AddLaserEdge(LX, LY);
2076 if (!IS_DF_GRID(element))
2077 AddLaserEdge(LX, LY + YS / 2);
2080 if (!IS_DF_GRID(element))
2081 AddLaserEdge(LX, LY);
2084 YS = 2 * Step[laser.current_angle].y;
2088 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2090 laser.current_angle = get_mirrored_angle(laser.current_angle,
2095 if (!IS_DF_GRID(element))
2096 AddLaserEdge(LX, LY);
2101 if (!IS_DF_GRID(element))
2102 AddLaserEdge(LX + XS / 2, LY);
2105 if (!IS_DF_GRID(element))
2106 AddLaserEdge(LX, LY);
2109 XS = 2 * Step[laser.current_angle].x;
2115 // reflection at the edge of reflecting DF style wall
2116 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2118 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2119 hit_mask == HIT_MASK_TOPRIGHT) ||
2120 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2121 hit_mask == HIT_MASK_TOPLEFT) ||
2122 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2123 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2124 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2125 hit_mask == HIT_MASK_BOTTOMRIGHT))
2128 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2129 ANG_MIRROR_135 : ANG_MIRROR_45);
2131 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2133 AddDamagedField(ELX, ELY);
2134 AddLaserEdge(LX, LY);
2136 laser.current_angle = get_mirrored_angle(laser.current_angle,
2144 AddLaserEdge(LX, LY);
2150 // reflection inside an edge of reflecting DF style wall
2151 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2153 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2154 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2155 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2156 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2157 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2158 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2159 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2160 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2163 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2164 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2165 ANG_MIRROR_135 : ANG_MIRROR_45);
2167 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2170 AddDamagedField(ELX, ELY);
2173 AddLaserEdge(LX - XS, LY - YS);
2174 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2175 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2177 laser.current_angle = get_mirrored_angle(laser.current_angle,
2185 AddLaserEdge(LX, LY);
2191 // check if laser hits DF style wall with an angle of 90°
2192 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2194 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2195 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2196 (IS_VERT_ANGLE(laser.current_angle) &&
2197 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2199 // laser at last step touched nothing or the same side of the wall
2200 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2202 AddDamagedField(ELX, ELY);
2209 last_hit_mask = hit_mask;
2216 if (!HitOnlyAnEdge(hit_mask))
2218 laser.overloaded = TRUE;
2226 boolean HitAbsorbingWalls(int element, int hit_mask)
2228 if (HitOnlyAnEdge(hit_mask))
2232 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2234 AddLaserEdge(LX - XS, LY - YS);
2241 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2243 AddLaserEdge(LX - XS, LY - YS);
2249 if (IS_WALL_WOOD(element) ||
2250 IS_DF_WALL_WOOD(element) ||
2251 IS_GRID_WOOD(element) ||
2252 IS_GRID_WOOD_FIXED(element) ||
2253 IS_GRID_WOOD_AUTO(element) ||
2254 element == EL_FUSE_ON ||
2255 element == EL_BLOCK_WOOD ||
2256 element == EL_GATE_WOOD)
2258 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2263 if (IS_WALL_ICE(element))
2267 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2268 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2270 // check if laser hits wall with an angle of 90°
2271 if (IS_90_ANGLE(laser.current_angle))
2272 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2274 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2278 for (i = 0; i < 4; i++)
2280 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2281 mask = 15 - (8 >> i);
2282 else if (ABS(XS) == 4 &&
2284 (XS > 0) == (i % 2) &&
2285 (YS < 0) == (i / 2))
2286 mask = 3 + (i / 2) * 9;
2287 else if (ABS(YS) == 4 &&
2289 (XS < 0) == (i % 2) &&
2290 (YS > 0) == (i / 2))
2291 mask = 5 + (i % 2) * 5;
2295 laser.wall_mask = mask;
2297 else if (IS_WALL_AMOEBA(element))
2299 int elx = (LX - 2 * XS) / TILEX;
2300 int ely = (LY - 2 * YS) / TILEY;
2301 int element2 = Tile[elx][ely];
2304 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2306 laser.dest_element = EL_EMPTY;
2314 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2315 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2317 if (IS_90_ANGLE(laser.current_angle))
2318 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2320 laser.dest_element = element2 | EL_WALL_AMOEBA;
2322 laser.wall_mask = mask;
2328 static void OpenExit(int x, int y)
2332 if (!MovDelay[x][y]) // next animation frame
2333 MovDelay[x][y] = 4 * delay;
2335 if (MovDelay[x][y]) // wait some time before next frame
2340 phase = MovDelay[x][y] / delay;
2342 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2343 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2345 if (!MovDelay[x][y])
2347 Tile[x][y] = EL_EXIT_OPEN;
2353 static void OpenSurpriseBall(int x, int y)
2357 if (!MovDelay[x][y]) // next animation frame
2358 MovDelay[x][y] = 50 * delay;
2360 if (MovDelay[x][y]) // wait some time before next frame
2364 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2367 int graphic = el2gfx(Store[x][y]);
2369 int dx = RND(26), dy = RND(26);
2371 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2373 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2374 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2376 MarkTileDirty(x, y);
2379 if (!MovDelay[x][y])
2381 Tile[x][y] = Store[x][y];
2390 static void MeltIce(int x, int y)
2395 if (!MovDelay[x][y]) // next animation frame
2396 MovDelay[x][y] = frames * delay;
2398 if (MovDelay[x][y]) // wait some time before next frame
2401 int wall_mask = Store2[x][y];
2402 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2405 phase = frames - MovDelay[x][y] / delay - 1;
2407 if (!MovDelay[x][y])
2411 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2412 Store[x][y] = Store2[x][y] = 0;
2414 DrawWalls_MM(x, y, Tile[x][y]);
2416 if (Tile[x][y] == EL_WALL_ICE)
2417 Tile[x][y] = EL_EMPTY;
2419 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2420 if (laser.damage[i].is_mirror)
2424 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2426 DrawLaser(0, DL_LASER_DISABLED);
2430 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2432 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2434 laser.redraw = TRUE;
2439 static void GrowAmoeba(int x, int y)
2444 if (!MovDelay[x][y]) // next animation frame
2445 MovDelay[x][y] = frames * delay;
2447 if (MovDelay[x][y]) // wait some time before next frame
2450 int wall_mask = Store2[x][y];
2451 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2454 phase = MovDelay[x][y] / delay;
2456 if (!MovDelay[x][y])
2458 Tile[x][y] = real_element;
2459 Store[x][y] = Store2[x][y] = 0;
2461 DrawWalls_MM(x, y, Tile[x][y]);
2462 DrawLaser(0, DL_LASER_ENABLED);
2464 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2466 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2471 static void DrawFieldAnimated_MM(int x, int y)
2473 int element = Tile[x][y];
2475 if (IS_BLOCKED(x, y))
2480 if (IS_MIRROR(element) ||
2481 IS_MIRROR_FIXED(element) ||
2482 element == EL_PRISM)
2484 if (MovDelay[x][y] != 0) // wait some time before next frame
2488 if (MovDelay[x][y] != 0)
2490 int graphic = IMG_TWINKLE_WHITE;
2491 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2493 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2498 laser.redraw = TRUE;
2501 static void Explode_MM(int x, int y, int phase, int mode)
2503 int num_phase = 9, delay = 2;
2504 int last_phase = num_phase * delay;
2505 int half_phase = (num_phase / 2) * delay;
2507 laser.redraw = TRUE;
2509 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2511 int center_element = Tile[x][y];
2513 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2515 // put moving element to center field (and let it explode there)
2516 center_element = MovingOrBlocked2Element_MM(x, y);
2517 RemoveMovingField_MM(x, y);
2519 Tile[x][y] = center_element;
2522 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2523 Store[x][y] = center_element;
2525 Store[x][y] = EL_EMPTY;
2527 Store2[x][y] = mode;
2528 Tile[x][y] = EL_EXPLODING_OPAQUE;
2529 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2530 ExplodePhase[x][y] = 1;
2535 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2537 if (phase == half_phase)
2539 Tile[x][y] = EL_EXPLODING_TRANSP;
2541 if (x == ELX && y == ELY)
2545 if (phase == last_phase)
2547 if (Store[x][y] == EL_BOMB)
2549 DrawLaser(0, DL_LASER_DISABLED);
2552 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2553 Store[x][y] = EL_EMPTY;
2555 GameOver_MM(GAME_OVER_DELAYED);
2557 laser.overloaded = FALSE;
2559 else if (IS_MCDUFFIN(Store[x][y]))
2561 Store[x][y] = EL_EMPTY;
2563 GameOver_MM(GAME_OVER_BOMB);
2566 Tile[x][y] = Store[x][y];
2567 Store[x][y] = Store2[x][y] = 0;
2568 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2573 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2575 int graphic = IMG_MM_DEFAULT_EXPLODING;
2576 int graphic_phase = (phase / delay - 1);
2580 if (Store2[x][y] == EX_KETTLE)
2582 if (graphic_phase < 3)
2584 graphic = IMG_MM_KETTLE_EXPLODING;
2586 else if (graphic_phase < 5)
2592 graphic = IMG_EMPTY;
2596 else if (Store2[x][y] == EX_SHORT)
2598 if (graphic_phase < 4)
2604 graphic = IMG_EMPTY;
2609 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2611 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2612 cFX + x * TILEX, cFY + y * TILEY);
2614 MarkTileDirty(x, y);
2618 static void Bang_MM(int x, int y)
2620 int element = Tile[x][y];
2621 int mode = EX_NORMAL;
2624 DrawLaser(0, DL_LASER_ENABLED);
2643 if (IS_PACMAN(element))
2644 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2645 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2646 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2647 else if (element == EL_KEY)
2648 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2650 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2652 Explode_MM(x, y, EX_PHASE_START, mode);
2655 void TurnRound(int x, int y)
2667 { 0, 0 }, { 0, 0 }, { 0, 0 },
2672 int left, right, back;
2676 { MV_DOWN, MV_UP, MV_RIGHT },
2677 { MV_UP, MV_DOWN, MV_LEFT },
2679 { MV_LEFT, MV_RIGHT, MV_DOWN },
2680 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2681 { MV_RIGHT, MV_LEFT, MV_UP }
2684 int element = Tile[x][y];
2685 int old_move_dir = MovDir[x][y];
2686 int right_dir = turn[old_move_dir].right;
2687 int back_dir = turn[old_move_dir].back;
2688 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2689 int right_x = x + right_dx, right_y = y + right_dy;
2691 if (element == EL_PACMAN)
2693 boolean can_turn_right = FALSE;
2695 if (IN_LEV_FIELD(right_x, right_y) &&
2696 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2697 can_turn_right = TRUE;
2700 MovDir[x][y] = right_dir;
2702 MovDir[x][y] = back_dir;
2708 static void StartMoving_MM(int x, int y)
2710 int element = Tile[x][y];
2715 if (CAN_MOVE(element))
2719 if (MovDelay[x][y]) // wait some time before next movement
2727 // now make next step
2729 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2731 if (element == EL_PACMAN &&
2732 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2733 !ObjHit(newx, newy, HIT_POS_CENTER))
2735 Store[newx][newy] = Tile[newx][newy];
2736 Tile[newx][newy] = EL_EMPTY;
2738 DrawField_MM(newx, newy);
2740 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2741 ObjHit(newx, newy, HIT_POS_CENTER))
2743 // object was running against a wall
2750 InitMovingField_MM(x, y, MovDir[x][y]);
2754 ContinueMoving_MM(x, y);
2757 static void ContinueMoving_MM(int x, int y)
2759 int element = Tile[x][y];
2760 int direction = MovDir[x][y];
2761 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2762 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2763 int horiz_move = (dx!=0);
2764 int newx = x + dx, newy = y + dy;
2765 int step = (horiz_move ? dx : dy) * TILEX / 8;
2767 MovPos[x][y] += step;
2769 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2771 Tile[x][y] = EL_EMPTY;
2772 Tile[newx][newy] = element;
2774 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2775 MovDelay[newx][newy] = 0;
2777 if (!CAN_MOVE(element))
2778 MovDir[newx][newy] = 0;
2781 DrawField_MM(newx, newy);
2783 Stop[newx][newy] = TRUE;
2785 if (element == EL_PACMAN)
2787 if (Store[newx][newy] == EL_BOMB)
2788 Bang_MM(newx, newy);
2790 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2791 (LX + 2 * XS) / TILEX == newx &&
2792 (LY + 2 * YS) / TILEY == newy)
2799 else // still moving on
2804 laser.redraw = TRUE;
2807 boolean ClickElement(int x, int y, int button)
2809 static DelayCounter click_delay = { CLICK_DELAY };
2810 static boolean new_button = TRUE;
2811 boolean element_clicked = FALSE;
2816 // initialize static variables
2817 click_delay.count = 0;
2818 click_delay.value = CLICK_DELAY;
2824 // do not rotate objects hit by the laser after the game was solved
2825 if (game_mm.level_solved && Hit[x][y])
2828 if (button == MB_RELEASED)
2831 click_delay.value = CLICK_DELAY;
2833 // release eventually hold auto-rotating mirror
2834 RotateMirror(x, y, MB_RELEASED);
2839 if (!FrameReached(&click_delay) && !new_button)
2842 if (button == MB_MIDDLEBUTTON) // middle button has no function
2845 if (!IN_LEV_FIELD(x, y))
2848 if (Tile[x][y] == EL_EMPTY)
2851 element = Tile[x][y];
2853 if (IS_MIRROR(element) ||
2854 IS_BEAMER(element) ||
2855 IS_POLAR(element) ||
2856 IS_POLAR_CROSS(element) ||
2857 IS_DF_MIRROR(element) ||
2858 IS_DF_MIRROR_AUTO(element))
2860 RotateMirror(x, y, button);
2862 element_clicked = TRUE;
2864 else if (IS_MCDUFFIN(element))
2866 if (!laser.fuse_off)
2868 DrawLaser(0, DL_LASER_DISABLED);
2875 element = get_rotated_element(element, BUTTON_ROTATION(button));
2876 laser.start_angle = get_element_angle(element);
2880 Tile[x][y] = element;
2887 if (!laser.fuse_off)
2890 element_clicked = TRUE;
2892 else if (element == EL_FUSE_ON && laser.fuse_off)
2894 if (x != laser.fuse_x || y != laser.fuse_y)
2897 laser.fuse_off = FALSE;
2898 laser.fuse_x = laser.fuse_y = -1;
2900 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2903 element_clicked = TRUE;
2905 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2907 laser.fuse_off = TRUE;
2910 laser.overloaded = FALSE;
2912 DrawLaser(0, DL_LASER_DISABLED);
2913 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2915 element_clicked = TRUE;
2917 else if (element == EL_LIGHTBALL)
2920 RaiseScoreElement_MM(element);
2921 DrawLaser(0, DL_LASER_ENABLED);
2923 element_clicked = TRUE;
2926 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2929 return element_clicked;
2932 void RotateMirror(int x, int y, int button)
2934 if (button == MB_RELEASED)
2936 // release eventually hold auto-rotating mirror
2943 if (IS_MIRROR(Tile[x][y]) ||
2944 IS_POLAR_CROSS(Tile[x][y]) ||
2945 IS_POLAR(Tile[x][y]) ||
2946 IS_BEAMER(Tile[x][y]) ||
2947 IS_DF_MIRROR(Tile[x][y]) ||
2948 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2949 IS_GRID_WOOD_AUTO(Tile[x][y]))
2951 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2953 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2955 if (button == MB_LEFTBUTTON)
2957 // left mouse button only for manual adjustment, no auto-rotating;
2958 // freeze mirror for until mouse button released
2962 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2964 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2968 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2970 int edge = Hit[x][y];
2976 DrawLaser(edge - 1, DL_LASER_DISABLED);
2980 else if (ObjHit(x, y, HIT_POS_CENTER))
2982 int edge = Hit[x][y];
2986 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2991 DrawLaser(edge - 1, DL_LASER_DISABLED);
2998 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
3003 if ((IS_BEAMER(Tile[x][y]) ||
3004 IS_POLAR(Tile[x][y]) ||
3005 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
3009 if (IS_BEAMER(Tile[x][y]))
3012 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3013 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3025 DrawLaser(0, DL_LASER_ENABLED);
3029 static void AutoRotateMirrors(void)
3033 if (!FrameReached(&rotate_delay))
3036 for (x = 0; x < lev_fieldx; x++)
3038 for (y = 0; y < lev_fieldy; y++)
3040 int element = Tile[x][y];
3042 // do not rotate objects hit by the laser after the game was solved
3043 if (game_mm.level_solved && Hit[x][y])
3046 if (IS_DF_MIRROR_AUTO(element) ||
3047 IS_GRID_WOOD_AUTO(element) ||
3048 IS_GRID_STEEL_AUTO(element) ||
3049 element == EL_REFRACTOR)
3050 RotateMirror(x, y, MB_RIGHTBUTTON);
3055 boolean ObjHit(int obx, int oby, int bits)
3062 if (bits & HIT_POS_CENTER)
3064 if (CheckLaserPixel(cSX + obx + 15,
3069 if (bits & HIT_POS_EDGE)
3071 for (i = 0; i < 4; i++)
3072 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3073 cSY + oby + 31 * (i / 2)))
3077 if (bits & HIT_POS_BETWEEN)
3079 for (i = 0; i < 4; i++)
3080 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3081 cSY + 4 + oby + 22 * (i / 2)))
3088 void DeletePacMan(int px, int py)
3094 if (game_mm.num_pacman <= 1)
3096 game_mm.num_pacman = 0;
3100 for (i = 0; i < game_mm.num_pacman; i++)
3101 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3104 game_mm.num_pacman--;
3106 for (j = i; j < game_mm.num_pacman; j++)
3108 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3109 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3110 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3114 void ColorCycling(void)
3116 static int CC, Cc = 0;
3118 static int color, old = 0xF00, new = 0x010, mult = 1;
3119 static unsigned short red, green, blue;
3121 if (color_status == STATIC_COLORS)
3126 if (CC < Cc || CC > Cc + 2)
3130 color = old + new * mult;
3136 if (ABS(mult) == 16)
3146 red = 0x0e00 * ((color & 0xF00) >> 8);
3147 green = 0x0e00 * ((color & 0x0F0) >> 4);
3148 blue = 0x0e00 * ((color & 0x00F));
3149 SetRGB(pen_magicolor[0], red, green, blue);
3151 red = 0x1111 * ((color & 0xF00) >> 8);
3152 green = 0x1111 * ((color & 0x0F0) >> 4);
3153 blue = 0x1111 * ((color & 0x00F));
3154 SetRGB(pen_magicolor[1], red, green, blue);
3158 static void GameActions_MM_Ext(void)
3165 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3168 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3170 element = Tile[x][y];
3172 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3173 StartMoving_MM(x, y);
3174 else if (IS_MOVING(x, y))
3175 ContinueMoving_MM(x, y);
3176 else if (IS_EXPLODING(element))
3177 Explode_MM(x, y, ExplodePhase[x][y], EX_NORMAL);
3178 else if (element == EL_EXIT_OPENING)
3180 else if (element == EL_GRAY_BALL_OPENING)
3181 OpenSurpriseBall(x, y);
3182 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3184 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3187 DrawFieldAnimated_MM(x, y);
3190 AutoRotateMirrors();
3193 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3195 // redraw after Explode_MM() ...
3197 DrawLaser(0, DL_LASER_ENABLED);
3198 laser.redraw = FALSE;
3203 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3207 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3209 DrawLaser(0, DL_LASER_DISABLED);
3214 // skip all following game actions if game is over
3215 if (game_mm.game_over)
3218 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3222 GameOver_MM(GAME_OVER_NO_ENERGY);
3227 if (FrameReached(&energy_delay))
3229 if (game_mm.energy_left > 0)
3230 game_mm.energy_left--;
3232 // when out of energy, wait another frame to play "out of time" sound
3235 element = laser.dest_element;
3238 if (element != Tile[ELX][ELY])
3240 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3241 element, Tile[ELX][ELY]);
3245 if (!laser.overloaded && laser.overload_value == 0 &&
3246 element != EL_BOMB &&
3247 element != EL_MINE &&
3248 element != EL_BALL_GRAY &&
3249 element != EL_BLOCK_STONE &&
3250 element != EL_BLOCK_WOOD &&
3251 element != EL_FUSE_ON &&
3252 element != EL_FUEL_FULL &&
3253 !IS_WALL_ICE(element) &&
3254 !IS_WALL_AMOEBA(element))
3257 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3259 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3260 (!laser.overloaded && laser.overload_value > 0)) &&
3261 FrameReached(&overload_delay))
3263 if (laser.overloaded)
3264 laser.overload_value++;
3266 laser.overload_value--;
3268 if (game_mm.cheat_no_overload)
3270 laser.overloaded = FALSE;
3271 laser.overload_value = 0;
3274 game_mm.laser_overload_value = laser.overload_value;
3276 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3278 SetLaserColor(0xFF);
3280 DrawLaser(0, DL_LASER_ENABLED);
3283 if (!laser.overloaded)
3284 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3285 else if (setup.sound_loops)
3286 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3288 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3290 if (laser.overloaded)
3293 BlitBitmap(pix[PIX_DOOR], drawto,
3294 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3295 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3296 - laser.overload_value,
3297 OVERLOAD_XSIZE, laser.overload_value,
3298 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3299 - laser.overload_value);
3301 redraw_mask |= REDRAW_DOOR_1;
3306 BlitBitmap(pix[PIX_DOOR], drawto,
3307 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3308 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3309 DX_OVERLOAD, DY_OVERLOAD);
3311 redraw_mask |= REDRAW_DOOR_1;
3314 if (laser.overload_value == MAX_LASER_OVERLOAD)
3316 UpdateAndDisplayGameControlValues();
3320 GameOver_MM(GAME_OVER_OVERLOADED);
3331 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3333 if (game_mm.cheat_no_explosion)
3338 laser.dest_element = EL_EXPLODING_OPAQUE;
3343 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3345 laser.fuse_off = TRUE;
3349 DrawLaser(0, DL_LASER_DISABLED);
3350 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3353 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3355 static int new_elements[] =
3358 EL_MIRROR_FIXED_START,
3360 EL_POLAR_CROSS_START,
3366 int num_new_elements = sizeof(new_elements) / sizeof(int);
3367 int new_element = new_elements[RND(num_new_elements)];
3369 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3370 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3372 // !!! CHECK AGAIN: Laser on Polarizer !!!
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);