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 unsigned int rotate_delay = 0;
115 static unsigned int pacman_delay = 0;
116 static unsigned int energy_delay = 0;
117 static unsigned int overload_delay = 0;
119 // element masks for scanning pixels of MM elements
120 static const char mm_masks[10][16][16 + 1] =
304 static int get_element_angle(int element)
306 int element_phase = get_element_phase(element);
308 if (IS_MIRROR_FIXED(element) ||
309 IS_MCDUFFIN(element) ||
311 IS_RECEIVER(element))
312 return 4 * element_phase;
314 return element_phase;
317 static int get_opposite_angle(int angle)
319 int opposite_angle = angle + ANG_RAY_180;
321 // make sure "opposite_angle" is in valid interval [0, 15]
322 return (opposite_angle + 16) % 16;
325 static int get_mirrored_angle(int laser_angle, int mirror_angle)
327 int reflected_angle = 16 - laser_angle + mirror_angle;
329 // make sure "reflected_angle" is in valid interval [0, 15]
330 return (reflected_angle + 16) % 16;
333 static void DrawLaserLines(struct XY *points, int num_points, int mode)
335 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
336 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
338 DrawLines(drawto, points, num_points, pixel_drawto);
342 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
347 static boolean CheckLaserPixel(int x, int y)
353 pixel = ReadPixel(laser_bitmap, x, y);
357 return (pixel == WHITE_PIXEL);
360 static void CheckExitMM(void)
362 int exit_element = EL_EMPTY;
366 static int xy[4][2] =
374 for (y = 0; y < lev_fieldy; y++)
376 for (x = 0; x < lev_fieldx; x++)
378 if (Tile[x][y] == EL_EXIT_CLOSED)
380 // initiate opening animation of exit door
381 Tile[x][y] = EL_EXIT_OPENING;
383 exit_element = EL_EXIT_OPEN;
387 else if (IS_RECEIVER(Tile[x][y]))
389 // remove field that blocks receiver
390 int phase = Tile[x][y] - EL_RECEIVER_START;
391 int blocking_x, blocking_y;
393 blocking_x = x + xy[phase][0];
394 blocking_y = y + xy[phase][1];
396 if (IN_LEV_FIELD(blocking_x, blocking_y))
398 Tile[blocking_x][blocking_y] = EL_EMPTY;
400 DrawField_MM(blocking_x, blocking_y);
403 exit_element = EL_RECEIVER;
410 if (exit_element != EL_EMPTY)
411 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
414 static void InitMovDir_MM(int x, int y)
416 int element = Tile[x][y];
417 static int direction[3][4] =
419 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
420 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
421 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
426 case EL_PACMAN_RIGHT:
430 Tile[x][y] = EL_PACMAN;
431 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
439 static void InitField(int x, int y, boolean init_game)
441 int element = Tile[x][y];
446 Tile[x][y] = EL_EMPTY;
451 if (native_mm_level.auto_count_kettles)
452 game_mm.kettles_still_needed++;
455 case EL_LIGHTBULB_OFF:
456 game_mm.lights_still_needed++;
460 if (IS_MIRROR(element) ||
461 IS_BEAMER_OLD(element) ||
462 IS_BEAMER(element) ||
464 IS_POLAR_CROSS(element) ||
465 IS_DF_MIRROR(element) ||
466 IS_DF_MIRROR_AUTO(element) ||
467 IS_GRID_STEEL_AUTO(element) ||
468 IS_GRID_WOOD_AUTO(element) ||
469 IS_FIBRE_OPTIC(element))
471 if (IS_BEAMER_OLD(element))
473 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
474 element = Tile[x][y];
477 if (!IS_FIBRE_OPTIC(element))
479 static int steps_grid_auto = 0;
481 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
482 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
484 if (IS_GRID_STEEL_AUTO(element) ||
485 IS_GRID_WOOD_AUTO(element))
486 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
488 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
490 game_mm.cycle[game_mm.num_cycle].x = x;
491 game_mm.cycle[game_mm.num_cycle].y = y;
495 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
497 int beamer_nr = BEAMER_NR(element);
498 int nr = laser.beamer[beamer_nr][0].num;
500 laser.beamer[beamer_nr][nr].x = x;
501 laser.beamer[beamer_nr][nr].y = y;
502 laser.beamer[beamer_nr][nr].num = 1;
505 else if (IS_PACMAN(element))
509 else if (IS_MCDUFFIN(element) || IS_LASER(element))
511 laser.start_edge.x = x;
512 laser.start_edge.y = y;
513 laser.start_angle = get_element_angle(element);
520 static void InitCycleElements_RotateSingleStep(void)
524 if (game_mm.num_cycle == 0) // no elements to cycle
527 for (i = 0; i < game_mm.num_cycle; i++)
529 int x = game_mm.cycle[i].x;
530 int y = game_mm.cycle[i].y;
531 int step = SIGN(game_mm.cycle[i].steps);
532 int last_element = Tile[x][y];
533 int next_element = get_rotated_element(last_element, step);
535 if (!game_mm.cycle[i].steps)
538 Tile[x][y] = next_element;
541 game_mm.cycle[i].steps -= step;
545 static void InitLaser(void)
547 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
548 int step = (IS_LASER(start_element) ? 4 : 0);
550 LX = laser.start_edge.x * TILEX;
551 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
554 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
556 LY = laser.start_edge.y * TILEY;
557 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
558 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
562 XS = 2 * Step[laser.start_angle].x;
563 YS = 2 * Step[laser.start_angle].y;
565 laser.current_angle = laser.start_angle;
567 laser.num_damages = 0;
569 laser.num_beamers = 0;
570 laser.beamer_edge[0] = 0;
572 laser.dest_element = EL_EMPTY;
575 AddLaserEdge(LX, LY); // set laser starting edge
577 pen_ray = GetPixelFromRGB(window,
578 native_mm_level.laser_red * 0xFF,
579 native_mm_level.laser_green * 0xFF,
580 native_mm_level.laser_blue * 0xFF);
583 void InitGameEngine_MM(void)
589 // initialize laser bitmap to current playfield (screen) size
590 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
591 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
595 // set global game control values
596 game_mm.num_cycle = 0;
597 game_mm.num_pacman = 0;
600 game_mm.energy_left = 0; // later set to "native_mm_level.time"
601 game_mm.kettles_still_needed =
602 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
603 game_mm.lights_still_needed = 0;
604 game_mm.num_keys = 0;
606 game_mm.level_solved = FALSE;
607 game_mm.game_over = FALSE;
608 game_mm.game_over_cause = 0;
610 game_mm.laser_overload_value = 0;
611 game_mm.laser_enabled = FALSE;
613 // set global laser control values (must be set before "InitLaser()")
614 laser.start_edge.x = 0;
615 laser.start_edge.y = 0;
616 laser.start_angle = 0;
618 for (i = 0; i < MAX_NUM_BEAMERS; i++)
619 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
621 laser.overloaded = FALSE;
622 laser.overload_value = 0;
623 laser.fuse_off = FALSE;
624 laser.fuse_x = laser.fuse_y = -1;
626 laser.dest_element = EL_EMPTY;
645 ClickElement(-1, -1, -1);
647 for (x = 0; x < lev_fieldx; x++)
649 for (y = 0; y < lev_fieldy; y++)
651 Tile[x][y] = Ur[x][y];
652 Hit[x][y] = Box[x][y] = 0;
654 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
655 Store[x][y] = Store2[x][y] = 0;
659 InitField(x, y, TRUE);
666 void InitGameActions_MM(void)
668 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
669 int cycle_steps_done = 0;
674 for (i = 0; i <= num_init_game_frames; i++)
676 if (i == num_init_game_frames)
677 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
678 else if (setup.sound_loops)
679 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
681 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
683 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
685 UpdateAndDisplayGameControlValues();
687 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
689 InitCycleElements_RotateSingleStep();
699 if (setup.quick_doors)
706 if (game_mm.kettles_still_needed == 0)
709 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
710 SetTileCursorActive(TRUE);
713 void AddLaserEdge(int lx, int ly)
718 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
720 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
725 laser.edge[laser.num_edges].x = cSX2 + lx;
726 laser.edge[laser.num_edges].y = cSY2 + ly;
732 void AddDamagedField(int ex, int ey)
734 laser.damage[laser.num_damages].is_mirror = FALSE;
735 laser.damage[laser.num_damages].angle = laser.current_angle;
736 laser.damage[laser.num_damages].edge = laser.num_edges;
737 laser.damage[laser.num_damages].x = ex;
738 laser.damage[laser.num_damages].y = ey;
742 static boolean StepBehind(void)
748 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
749 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
751 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
757 static int getMaskFromElement(int element)
759 if (IS_GRID(element))
760 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
761 else if (IS_MCDUFFIN(element))
762 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
763 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
764 return IMG_MM_MASK_RECTANGLE;
766 return IMG_MM_MASK_CIRCLE;
769 static int ScanPixel(void)
774 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
775 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
778 // follow laser beam until it hits something (at least the screen border)
779 while (hit_mask == HIT_MASK_NO_HIT)
785 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
786 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
788 Debug("game:mm:ScanPixel", "touched screen border!");
794 for (i = 0; i < 4; i++)
796 int px = LX + (i % 2) * 2;
797 int py = LY + (i / 2) * 2;
800 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
801 int ly = (py + TILEY) / TILEY - 1; // negative values!
804 if (IN_LEV_FIELD(lx, ly))
806 int element = Tile[lx][ly];
808 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
812 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
814 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
816 pixel = ((element & (1 << pos)) ? 1 : 0);
820 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
822 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
827 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
828 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
831 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
832 hit_mask |= (1 << i);
835 if (hit_mask == HIT_MASK_NO_HIT)
837 // hit nothing -- go on with another step
849 int end = 0, rf = laser.num_edges;
851 // do not scan laser again after the game was lost for whatever reason
852 if (game_mm.game_over)
855 laser.overloaded = FALSE;
856 laser.stops_inside_element = FALSE;
858 DrawLaser(0, DL_LASER_ENABLED);
861 Debug("game:mm:ScanLaser",
862 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
870 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
873 laser.overloaded = TRUE;
878 hit_mask = ScanPixel();
881 Debug("game:mm:ScanLaser",
882 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
886 // hit something -- check out what it was
887 ELX = (LX + XS) / TILEX;
888 ELY = (LY + YS) / TILEY;
891 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
892 hit_mask, LX, LY, ELX, ELY);
895 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
898 laser.dest_element = element;
903 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
905 /* we have hit the top-right and bottom-left element --
906 choose the bottom-left one */
907 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
908 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
909 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
910 ELX = (LX - 2) / TILEX;
911 ELY = (LY + 2) / TILEY;
914 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
916 /* we have hit the top-left and bottom-right element --
917 choose the top-left one */
919 ELX = (LX - 2) / TILEX;
920 ELY = (LY - 2) / TILEY;
924 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
925 hit_mask, LX, LY, ELX, ELY);
928 element = Tile[ELX][ELY];
929 laser.dest_element = element;
932 Debug("game:mm:ScanLaser",
933 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
936 LX % TILEX, LY % TILEY,
941 if (!IN_LEV_FIELD(ELX, ELY))
942 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
946 if (element == EL_EMPTY)
948 if (!HitOnlyAnEdge(element, hit_mask))
951 else if (element == EL_FUSE_ON)
953 if (HitPolarizer(element, hit_mask))
956 else if (IS_GRID(element) || IS_DF_GRID(element))
958 if (HitPolarizer(element, hit_mask))
961 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
962 element == EL_GATE_STONE || element == EL_GATE_WOOD)
964 if (HitBlock(element, hit_mask))
971 else if (IS_MCDUFFIN(element))
973 if (HitLaserSource(element, hit_mask))
976 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
977 IS_RECEIVER(element))
979 if (HitLaserDestination(element, hit_mask))
982 else if (IS_WALL(element))
984 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
986 if (HitReflectingWalls(element, hit_mask))
991 if (HitAbsorbingWalls(element, hit_mask))
997 if (HitElement(element, hit_mask))
1002 DrawLaser(rf - 1, DL_LASER_ENABLED);
1003 rf = laser.num_edges;
1007 if (laser.dest_element != Tile[ELX][ELY])
1009 Debug("game:mm:ScanLaser",
1010 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1011 laser.dest_element, Tile[ELX][ELY]);
1015 if (!end && !laser.stops_inside_element && !StepBehind())
1018 Debug("game:mm:ScanLaser", "Go one step back");
1024 AddLaserEdge(LX, LY);
1028 DrawLaser(rf - 1, DL_LASER_ENABLED);
1030 Ct = CT = FrameCounter;
1033 if (!IN_LEV_FIELD(ELX, ELY))
1034 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1038 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1044 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1045 start_edge, num_edges, mode);
1050 Warn("DrawLaserExt: start_edge < 0");
1057 Warn("DrawLaserExt: num_edges < 0");
1063 if (mode == DL_LASER_DISABLED)
1065 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1069 // now draw the laser to the backbuffer and (if enabled) to the screen
1070 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1072 redraw_mask |= REDRAW_FIELD;
1074 if (mode == DL_LASER_ENABLED)
1077 // after the laser was deleted, the "damaged" graphics must be restored
1078 if (laser.num_damages)
1080 int damage_start = 0;
1083 // determine the starting edge, from which graphics need to be restored
1086 for (i = 0; i < laser.num_damages; i++)
1088 if (laser.damage[i].edge == start_edge + 1)
1097 // restore graphics from this starting edge to the end of damage list
1098 for (i = damage_start; i < laser.num_damages; i++)
1100 int lx = laser.damage[i].x;
1101 int ly = laser.damage[i].y;
1102 int element = Tile[lx][ly];
1104 if (Hit[lx][ly] == laser.damage[i].edge)
1105 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1108 if (Box[lx][ly] == laser.damage[i].edge)
1111 if (IS_DRAWABLE(element))
1112 DrawField_MM(lx, ly);
1115 elx = laser.damage[damage_start].x;
1116 ely = laser.damage[damage_start].y;
1117 element = Tile[elx][ely];
1120 if (IS_BEAMER(element))
1124 for (i = 0; i < laser.num_beamers; i++)
1125 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1127 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1128 mode, elx, ely, Hit[elx][ely], start_edge);
1129 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1130 get_element_angle(element), laser.damage[damage_start].angle);
1134 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1135 laser.num_beamers > 0 &&
1136 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1138 // element is outgoing beamer
1139 laser.num_damages = damage_start + 1;
1141 if (IS_BEAMER(element))
1142 laser.current_angle = get_element_angle(element);
1146 // element is incoming beamer or other element
1147 laser.num_damages = damage_start;
1148 laser.current_angle = laser.damage[laser.num_damages].angle;
1153 // no damages but McDuffin himself (who needs to be redrawn anyway)
1155 elx = laser.start_edge.x;
1156 ely = laser.start_edge.y;
1157 element = Tile[elx][ely];
1160 laser.num_edges = start_edge + 1;
1161 if (start_edge == 0)
1162 laser.current_angle = laser.start_angle;
1164 LX = laser.edge[start_edge].x - cSX2;
1165 LY = laser.edge[start_edge].y - cSY2;
1166 XS = 2 * Step[laser.current_angle].x;
1167 YS = 2 * Step[laser.current_angle].y;
1170 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1176 if (IS_BEAMER(element) ||
1177 IS_FIBRE_OPTIC(element) ||
1178 IS_PACMAN(element) ||
1179 IS_POLAR(element) ||
1180 IS_POLAR_CROSS(element) ||
1181 element == EL_FUSE_ON)
1186 Debug("game:mm:DrawLaserExt", "element == %d", element);
1189 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1190 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1194 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1195 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1196 (laser.num_beamers == 0 ||
1197 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1199 // element is incoming beamer or other element
1200 step_size = -step_size;
1205 if (IS_BEAMER(element))
1206 Debug("game:mm:DrawLaserExt",
1207 "start_edge == %d, laser.beamer_edge == %d",
1208 start_edge, laser.beamer_edge);
1211 LX += step_size * XS;
1212 LY += step_size * YS;
1214 else if (element != EL_EMPTY)
1223 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1228 void DrawLaser(int start_edge, int mode)
1230 if (laser.num_edges - start_edge < 0)
1232 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1237 // check if laser is interrupted by beamer element
1238 if (laser.num_beamers > 0 &&
1239 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1241 if (mode == DL_LASER_ENABLED)
1244 int tmp_start_edge = start_edge;
1246 // draw laser segments forward from the start to the last beamer
1247 for (i = 0; i < laser.num_beamers; i++)
1249 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1251 if (tmp_num_edges <= 0)
1255 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1256 i, laser.beamer_edge[i], tmp_start_edge);
1259 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1261 tmp_start_edge = laser.beamer_edge[i];
1264 // draw last segment from last beamer to the end
1265 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1271 int last_num_edges = laser.num_edges;
1272 int num_beamers = laser.num_beamers;
1274 // delete laser segments backward from the end to the first beamer
1275 for (i = num_beamers - 1; i >= 0; i--)
1277 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1279 if (laser.beamer_edge[i] - start_edge <= 0)
1282 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1284 last_num_edges = laser.beamer_edge[i];
1285 laser.num_beamers--;
1289 if (last_num_edges - start_edge <= 0)
1290 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1291 last_num_edges, start_edge);
1294 // special case when rotating first beamer: delete laser edge on beamer
1295 // (but do not start scanning on previous edge to prevent mirror sound)
1296 if (last_num_edges - start_edge == 1 && start_edge > 0)
1297 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1299 // delete first segment from start to the first beamer
1300 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1305 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1308 game_mm.laser_enabled = mode;
1311 void DrawLaser_MM(void)
1313 DrawLaser(0, game_mm.laser_enabled);
1316 boolean HitElement(int element, int hit_mask)
1318 if (HitOnlyAnEdge(element, hit_mask))
1321 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1322 element = MovingOrBlocked2Element_MM(ELX, ELY);
1325 Debug("game:mm:HitElement", "(1): element == %d", element);
1329 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1330 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1333 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1337 AddDamagedField(ELX, ELY);
1339 // this is more precise: check if laser would go through the center
1340 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1342 // skip the whole element before continuing the scan
1348 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1350 if (LX/TILEX > ELX || LY/TILEY > ELY)
1352 /* skipping scan positions to the right and down skips one scan
1353 position too much, because this is only the top left scan position
1354 of totally four scan positions (plus one to the right, one to the
1355 bottom and one to the bottom right) */
1365 Debug("game:mm:HitElement", "(2): element == %d", element);
1368 if (LX + 5 * XS < 0 ||
1378 Debug("game:mm:HitElement", "(3): element == %d", element);
1381 if (IS_POLAR(element) &&
1382 ((element - EL_POLAR_START) % 2 ||
1383 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1385 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1387 laser.num_damages--;
1392 if (IS_POLAR_CROSS(element) &&
1393 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1395 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1397 laser.num_damages--;
1402 if (!IS_BEAMER(element) &&
1403 !IS_FIBRE_OPTIC(element) &&
1404 !IS_GRID_WOOD(element) &&
1405 element != EL_FUEL_EMPTY)
1408 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1409 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1411 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1414 LX = ELX * TILEX + 14;
1415 LY = ELY * TILEY + 14;
1417 AddLaserEdge(LX, LY);
1420 if (IS_MIRROR(element) ||
1421 IS_MIRROR_FIXED(element) ||
1422 IS_POLAR(element) ||
1423 IS_POLAR_CROSS(element) ||
1424 IS_DF_MIRROR(element) ||
1425 IS_DF_MIRROR_AUTO(element) ||
1426 element == EL_PRISM ||
1427 element == EL_REFRACTOR)
1429 int current_angle = laser.current_angle;
1432 laser.num_damages--;
1434 AddDamagedField(ELX, ELY);
1436 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1439 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1441 if (IS_MIRROR(element) ||
1442 IS_MIRROR_FIXED(element) ||
1443 IS_DF_MIRROR(element) ||
1444 IS_DF_MIRROR_AUTO(element))
1445 laser.current_angle = get_mirrored_angle(laser.current_angle,
1446 get_element_angle(element));
1448 if (element == EL_PRISM || element == EL_REFRACTOR)
1449 laser.current_angle = RND(16);
1451 XS = 2 * Step[laser.current_angle].x;
1452 YS = 2 * Step[laser.current_angle].y;
1454 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1459 LX += step_size * XS;
1460 LY += step_size * YS;
1463 // draw sparkles on mirror
1464 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1465 current_angle != laser.current_angle)
1467 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1471 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1472 current_angle != laser.current_angle)
1473 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1476 (get_opposite_angle(laser.current_angle) ==
1477 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1479 return (laser.overloaded ? TRUE : FALSE);
1482 if (element == EL_FUEL_FULL)
1484 laser.stops_inside_element = TRUE;
1489 if (element == EL_BOMB || element == EL_MINE)
1491 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1493 if (element == EL_MINE)
1494 laser.overloaded = TRUE;
1497 if (element == EL_KETTLE ||
1498 element == EL_CELL ||
1499 element == EL_KEY ||
1500 element == EL_LIGHTBALL ||
1501 element == EL_PACMAN ||
1504 if (!IS_PACMAN(element))
1507 if (element == EL_PACMAN)
1510 if (element == EL_KETTLE || element == EL_CELL)
1512 if (game_mm.kettles_still_needed > 0)
1513 game_mm.kettles_still_needed--;
1515 game.snapshot.collected_item = TRUE;
1517 if (game_mm.kettles_still_needed == 0)
1521 DrawLaser(0, DL_LASER_ENABLED);
1524 else if (element == EL_KEY)
1528 else if (IS_PACMAN(element))
1530 DeletePacMan(ELX, ELY);
1533 RaiseScoreElement_MM(element);
1538 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1540 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1542 DrawLaser(0, DL_LASER_ENABLED);
1544 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1546 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1547 game_mm.lights_still_needed--;
1551 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1552 game_mm.lights_still_needed++;
1555 DrawField_MM(ELX, ELY);
1556 DrawLaser(0, DL_LASER_ENABLED);
1561 laser.stops_inside_element = TRUE;
1567 Debug("game:mm:HitElement", "(4): element == %d", element);
1570 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1571 laser.num_beamers < MAX_NUM_BEAMERS &&
1572 laser.beamer[BEAMER_NR(element)][1].num)
1574 int beamer_angle = get_element_angle(element);
1575 int beamer_nr = BEAMER_NR(element);
1579 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1582 laser.num_damages--;
1584 if (IS_FIBRE_OPTIC(element) ||
1585 laser.current_angle == get_opposite_angle(beamer_angle))
1589 LX = ELX * TILEX + 14;
1590 LY = ELY * TILEY + 14;
1592 AddLaserEdge(LX, LY);
1593 AddDamagedField(ELX, ELY);
1595 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1598 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1600 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1601 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1602 ELX = laser.beamer[beamer_nr][pos].x;
1603 ELY = laser.beamer[beamer_nr][pos].y;
1604 LX = ELX * TILEX + 14;
1605 LY = ELY * TILEY + 14;
1607 if (IS_BEAMER(element))
1609 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1610 XS = 2 * Step[laser.current_angle].x;
1611 YS = 2 * Step[laser.current_angle].y;
1614 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1616 AddLaserEdge(LX, LY);
1617 AddDamagedField(ELX, ELY);
1619 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1622 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1624 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1629 LX += step_size * XS;
1630 LY += step_size * YS;
1632 laser.num_beamers++;
1641 boolean HitOnlyAnEdge(int element, int hit_mask)
1643 // check if the laser hit only the edge of an element and, if so, go on
1646 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1650 if ((hit_mask == HIT_MASK_TOPLEFT ||
1651 hit_mask == HIT_MASK_TOPRIGHT ||
1652 hit_mask == HIT_MASK_BOTTOMLEFT ||
1653 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1654 laser.current_angle % 4) // angle is not 90°
1658 if (hit_mask == HIT_MASK_TOPLEFT)
1663 else if (hit_mask == HIT_MASK_TOPRIGHT)
1668 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1673 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1679 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1685 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1692 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1698 boolean HitPolarizer(int element, int hit_mask)
1700 if (HitOnlyAnEdge(element, hit_mask))
1703 if (IS_DF_GRID(element))
1705 int grid_angle = get_element_angle(element);
1708 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1709 grid_angle, laser.current_angle);
1712 AddLaserEdge(LX, LY);
1713 AddDamagedField(ELX, ELY);
1716 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1718 if (laser.current_angle == grid_angle ||
1719 laser.current_angle == get_opposite_angle(grid_angle))
1721 // skip the whole element before continuing the scan
1727 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1729 if (LX/TILEX > ELX || LY/TILEY > ELY)
1731 /* skipping scan positions to the right and down skips one scan
1732 position too much, because this is only the top left scan position
1733 of totally four scan positions (plus one to the right, one to the
1734 bottom and one to the bottom right) */
1740 AddLaserEdge(LX, LY);
1746 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1748 LX / TILEX, LY / TILEY,
1749 LX % TILEX, LY % TILEY);
1754 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1756 return HitReflectingWalls(element, hit_mask);
1760 return HitAbsorbingWalls(element, hit_mask);
1763 else if (IS_GRID_STEEL(element))
1765 return HitReflectingWalls(element, hit_mask);
1767 else // IS_GRID_WOOD
1769 return HitAbsorbingWalls(element, hit_mask);
1775 boolean HitBlock(int element, int hit_mask)
1777 boolean check = FALSE;
1779 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1780 game_mm.num_keys == 0)
1783 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1786 int ex = ELX * TILEX + 14;
1787 int ey = ELY * TILEY + 14;
1791 for (i = 1; i < 32; i++)
1796 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1801 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1802 return HitAbsorbingWalls(element, hit_mask);
1806 AddLaserEdge(LX - XS, LY - YS);
1807 AddDamagedField(ELX, ELY);
1810 Box[ELX][ELY] = laser.num_edges;
1812 return HitReflectingWalls(element, hit_mask);
1815 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1817 int xs = XS / 2, ys = YS / 2;
1818 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1819 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1821 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1822 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1824 laser.overloaded = (element == EL_GATE_STONE);
1829 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1830 (hit_mask == HIT_MASK_TOP ||
1831 hit_mask == HIT_MASK_LEFT ||
1832 hit_mask == HIT_MASK_RIGHT ||
1833 hit_mask == HIT_MASK_BOTTOM))
1834 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1835 hit_mask == HIT_MASK_BOTTOM),
1836 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1837 hit_mask == HIT_MASK_RIGHT));
1838 AddLaserEdge(LX, LY);
1844 if (element == EL_GATE_STONE && Box[ELX][ELY])
1846 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1858 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1860 int xs = XS / 2, ys = YS / 2;
1861 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1862 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1864 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1865 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1867 laser.overloaded = (element == EL_BLOCK_STONE);
1872 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1873 (hit_mask == HIT_MASK_TOP ||
1874 hit_mask == HIT_MASK_LEFT ||
1875 hit_mask == HIT_MASK_RIGHT ||
1876 hit_mask == HIT_MASK_BOTTOM))
1877 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1878 hit_mask == HIT_MASK_BOTTOM),
1879 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1880 hit_mask == HIT_MASK_RIGHT));
1881 AddDamagedField(ELX, ELY);
1883 LX = ELX * TILEX + 14;
1884 LY = ELY * TILEY + 14;
1886 AddLaserEdge(LX, LY);
1888 laser.stops_inside_element = TRUE;
1896 boolean HitLaserSource(int element, int hit_mask)
1898 if (HitOnlyAnEdge(element, hit_mask))
1901 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1903 laser.overloaded = TRUE;
1908 boolean HitLaserDestination(int element, int hit_mask)
1910 if (HitOnlyAnEdge(element, hit_mask))
1913 if (element != EL_EXIT_OPEN &&
1914 !(IS_RECEIVER(element) &&
1915 game_mm.kettles_still_needed == 0 &&
1916 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1918 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1923 if (IS_RECEIVER(element) ||
1924 (IS_22_5_ANGLE(laser.current_angle) &&
1925 (ELX != (LX + 6 * XS) / TILEX ||
1926 ELY != (LY + 6 * YS) / TILEY ||
1935 LX = ELX * TILEX + 14;
1936 LY = ELY * TILEY + 14;
1938 laser.stops_inside_element = TRUE;
1941 AddLaserEdge(LX, LY);
1942 AddDamagedField(ELX, ELY);
1944 if (game_mm.lights_still_needed == 0)
1946 game_mm.level_solved = TRUE;
1948 SetTileCursorActive(FALSE);
1954 boolean HitReflectingWalls(int element, int hit_mask)
1956 // check if laser hits side of a wall with an angle that is not 90°
1957 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1958 hit_mask == HIT_MASK_LEFT ||
1959 hit_mask == HIT_MASK_RIGHT ||
1960 hit_mask == HIT_MASK_BOTTOM))
1962 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1967 if (!IS_DF_GRID(element))
1968 AddLaserEdge(LX, LY);
1970 // check if laser hits wall with an angle of 45°
1971 if (!IS_22_5_ANGLE(laser.current_angle))
1973 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1976 laser.current_angle = get_mirrored_angle(laser.current_angle,
1979 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
1982 laser.current_angle = get_mirrored_angle(laser.current_angle,
1986 AddLaserEdge(LX, LY);
1988 XS = 2 * Step[laser.current_angle].x;
1989 YS = 2 * Step[laser.current_angle].y;
1993 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1995 laser.current_angle = get_mirrored_angle(laser.current_angle,
2000 if (!IS_DF_GRID(element))
2001 AddLaserEdge(LX, LY);
2006 if (!IS_DF_GRID(element))
2007 AddLaserEdge(LX, LY + YS / 2);
2010 if (!IS_DF_GRID(element))
2011 AddLaserEdge(LX, LY);
2014 YS = 2 * Step[laser.current_angle].y;
2018 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2020 laser.current_angle = get_mirrored_angle(laser.current_angle,
2025 if (!IS_DF_GRID(element))
2026 AddLaserEdge(LX, LY);
2031 if (!IS_DF_GRID(element))
2032 AddLaserEdge(LX + XS / 2, LY);
2035 if (!IS_DF_GRID(element))
2036 AddLaserEdge(LX, LY);
2039 XS = 2 * Step[laser.current_angle].x;
2045 // reflection at the edge of reflecting DF style wall
2046 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2048 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2049 hit_mask == HIT_MASK_TOPRIGHT) ||
2050 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2051 hit_mask == HIT_MASK_TOPLEFT) ||
2052 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2053 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2054 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2055 hit_mask == HIT_MASK_BOTTOMRIGHT))
2058 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2059 ANG_MIRROR_135 : ANG_MIRROR_45);
2061 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2063 AddDamagedField(ELX, ELY);
2064 AddLaserEdge(LX, LY);
2066 laser.current_angle = get_mirrored_angle(laser.current_angle,
2074 AddLaserEdge(LX, LY);
2080 // reflection inside an edge of reflecting DF style wall
2081 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2083 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2084 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2085 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2086 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2087 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2088 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2089 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2090 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2093 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2094 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2095 ANG_MIRROR_135 : ANG_MIRROR_45);
2097 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2100 AddDamagedField(ELX, ELY);
2103 AddLaserEdge(LX - XS, LY - YS);
2104 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2105 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2107 laser.current_angle = get_mirrored_angle(laser.current_angle,
2115 AddLaserEdge(LX, LY);
2121 // check if laser hits DF style wall with an angle of 90°
2122 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2124 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2125 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2126 (IS_VERT_ANGLE(laser.current_angle) &&
2127 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2129 // laser at last step touched nothing or the same side of the wall
2130 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2132 AddDamagedField(ELX, ELY);
2139 last_hit_mask = hit_mask;
2146 if (!HitOnlyAnEdge(element, hit_mask))
2148 laser.overloaded = TRUE;
2156 boolean HitAbsorbingWalls(int element, int hit_mask)
2158 if (HitOnlyAnEdge(element, hit_mask))
2162 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2164 AddLaserEdge(LX - XS, LY - YS);
2171 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2173 AddLaserEdge(LX - XS, LY - YS);
2179 if (IS_WALL_WOOD(element) ||
2180 IS_DF_WALL_WOOD(element) ||
2181 IS_GRID_WOOD(element) ||
2182 IS_GRID_WOOD_FIXED(element) ||
2183 IS_GRID_WOOD_AUTO(element) ||
2184 element == EL_FUSE_ON ||
2185 element == EL_BLOCK_WOOD ||
2186 element == EL_GATE_WOOD)
2188 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2193 if (IS_WALL_ICE(element))
2197 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2198 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2200 // check if laser hits wall with an angle of 90°
2201 if (IS_90_ANGLE(laser.current_angle))
2202 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2204 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2208 for (i = 0; i < 4; i++)
2210 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2211 mask = 15 - (8 >> i);
2212 else if (ABS(XS) == 4 &&
2214 (XS > 0) == (i % 2) &&
2215 (YS < 0) == (i / 2))
2216 mask = 3 + (i / 2) * 9;
2217 else if (ABS(YS) == 4 &&
2219 (XS < 0) == (i % 2) &&
2220 (YS > 0) == (i / 2))
2221 mask = 5 + (i % 2) * 5;
2225 laser.wall_mask = mask;
2227 else if (IS_WALL_AMOEBA(element))
2229 int elx = (LX - 2 * XS) / TILEX;
2230 int ely = (LY - 2 * YS) / TILEY;
2231 int element2 = Tile[elx][ely];
2234 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2236 laser.dest_element = EL_EMPTY;
2244 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2245 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2247 if (IS_90_ANGLE(laser.current_angle))
2248 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2250 laser.dest_element = element2 | EL_WALL_AMOEBA;
2252 laser.wall_mask = mask;
2258 static void OpenExit(int x, int y)
2262 if (!MovDelay[x][y]) // next animation frame
2263 MovDelay[x][y] = 4 * delay;
2265 if (MovDelay[x][y]) // wait some time before next frame
2270 phase = MovDelay[x][y] / delay;
2272 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2273 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2275 if (!MovDelay[x][y])
2277 Tile[x][y] = EL_EXIT_OPEN;
2283 static void OpenSurpriseBall(int x, int y)
2287 if (!MovDelay[x][y]) // next animation frame
2288 MovDelay[x][y] = 50 * delay;
2290 if (MovDelay[x][y]) // wait some time before next frame
2294 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2297 int graphic = el2gfx(Store[x][y]);
2299 int dx = RND(26), dy = RND(26);
2301 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2303 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2304 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2306 MarkTileDirty(x, y);
2309 if (!MovDelay[x][y])
2311 Tile[x][y] = Store[x][y];
2320 static void MeltIce(int x, int y)
2325 if (!MovDelay[x][y]) // next animation frame
2326 MovDelay[x][y] = frames * delay;
2328 if (MovDelay[x][y]) // wait some time before next frame
2331 int wall_mask = Store2[x][y];
2332 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2335 phase = frames - MovDelay[x][y] / delay - 1;
2337 if (!MovDelay[x][y])
2341 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2342 Store[x][y] = Store2[x][y] = 0;
2344 DrawWalls_MM(x, y, Tile[x][y]);
2346 if (Tile[x][y] == EL_WALL_ICE)
2347 Tile[x][y] = EL_EMPTY;
2349 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2350 if (laser.damage[i].is_mirror)
2354 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2356 DrawLaser(0, DL_LASER_DISABLED);
2360 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2362 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2364 laser.redraw = TRUE;
2369 static void GrowAmoeba(int x, int y)
2374 if (!MovDelay[x][y]) // next animation frame
2375 MovDelay[x][y] = frames * delay;
2377 if (MovDelay[x][y]) // wait some time before next frame
2380 int wall_mask = Store2[x][y];
2381 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2384 phase = MovDelay[x][y] / delay;
2386 if (!MovDelay[x][y])
2388 Tile[x][y] = real_element;
2389 Store[x][y] = Store2[x][y] = 0;
2391 DrawWalls_MM(x, y, Tile[x][y]);
2392 DrawLaser(0, DL_LASER_ENABLED);
2394 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2396 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2401 static void Explode_MM(int x, int y, int phase, int mode)
2403 int num_phase = 9, delay = 2;
2404 int last_phase = num_phase * delay;
2405 int half_phase = (num_phase / 2) * delay;
2407 laser.redraw = TRUE;
2409 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2411 int center_element = Tile[x][y];
2413 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2415 // put moving element to center field (and let it explode there)
2416 center_element = MovingOrBlocked2Element_MM(x, y);
2417 RemoveMovingField_MM(x, y);
2419 Tile[x][y] = center_element;
2422 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2423 Store[x][y] = center_element;
2425 Store[x][y] = EL_EMPTY;
2427 Store2[x][y] = mode;
2428 Tile[x][y] = EL_EXPLODING_OPAQUE;
2429 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2435 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2437 if (phase == half_phase)
2439 Tile[x][y] = EL_EXPLODING_TRANSP;
2441 if (x == ELX && y == ELY)
2445 if (phase == last_phase)
2447 if (Store[x][y] == EL_BOMB)
2449 DrawLaser(0, DL_LASER_DISABLED);
2452 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2453 Store[x][y] = EL_EMPTY;
2455 game_mm.game_over = TRUE;
2456 game_mm.game_over_cause = GAME_OVER_BOMB;
2458 SetTileCursorActive(FALSE);
2460 laser.overloaded = FALSE;
2462 else if (IS_MCDUFFIN(Store[x][y]))
2464 Store[x][y] = EL_EMPTY;
2466 game.restart_game_message = "Bomb killed Mc Duffin! Play it again?";
2469 Tile[x][y] = Store[x][y];
2470 Store[x][y] = Store2[x][y] = 0;
2471 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2473 InitField(x, y, FALSE);
2476 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2478 int graphic = IMG_MM_DEFAULT_EXPLODING;
2479 int graphic_phase = (phase / delay - 1);
2483 if (Store2[x][y] == EX_KETTLE)
2485 if (graphic_phase < 3)
2487 graphic = IMG_MM_KETTLE_EXPLODING;
2489 else if (graphic_phase < 5)
2495 graphic = IMG_EMPTY;
2499 else if (Store2[x][y] == EX_SHORT)
2501 if (graphic_phase < 4)
2507 graphic = IMG_EMPTY;
2512 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2514 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2515 cFX + x * TILEX, cFY + y * TILEY);
2517 MarkTileDirty(x, y);
2521 static void Bang_MM(int x, int y)
2523 int element = Tile[x][y];
2524 int mode = EX_NORMAL;
2527 DrawLaser(0, DL_LASER_ENABLED);
2546 if (IS_PACMAN(element))
2547 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2548 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2549 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2550 else if (element == EL_KEY)
2551 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2553 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2555 Explode_MM(x, y, EX_PHASE_START, mode);
2558 void TurnRound(int x, int y)
2570 { 0, 0 }, { 0, 0 }, { 0, 0 },
2575 int left, right, back;
2579 { MV_DOWN, MV_UP, MV_RIGHT },
2580 { MV_UP, MV_DOWN, MV_LEFT },
2582 { MV_LEFT, MV_RIGHT, MV_DOWN },
2583 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2584 { MV_RIGHT, MV_LEFT, MV_UP }
2587 int element = Tile[x][y];
2588 int old_move_dir = MovDir[x][y];
2589 int right_dir = turn[old_move_dir].right;
2590 int back_dir = turn[old_move_dir].back;
2591 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2592 int right_x = x + right_dx, right_y = y + right_dy;
2594 if (element == EL_PACMAN)
2596 boolean can_turn_right = FALSE;
2598 if (IN_LEV_FIELD(right_x, right_y) &&
2599 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2600 can_turn_right = TRUE;
2603 MovDir[x][y] = right_dir;
2605 MovDir[x][y] = back_dir;
2611 static void StartMoving_MM(int x, int y)
2613 int element = Tile[x][y];
2618 if (CAN_MOVE(element))
2622 if (MovDelay[x][y]) // wait some time before next movement
2630 // now make next step
2632 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2634 if (element == EL_PACMAN &&
2635 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2636 !ObjHit(newx, newy, HIT_POS_CENTER))
2638 Store[newx][newy] = Tile[newx][newy];
2639 Tile[newx][newy] = EL_EMPTY;
2641 DrawField_MM(newx, newy);
2643 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2644 ObjHit(newx, newy, HIT_POS_CENTER))
2646 // object was running against a wall
2653 InitMovingField_MM(x, y, MovDir[x][y]);
2657 ContinueMoving_MM(x, y);
2660 static void ContinueMoving_MM(int x, int y)
2662 int element = Tile[x][y];
2663 int direction = MovDir[x][y];
2664 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2665 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2666 int horiz_move = (dx!=0);
2667 int newx = x + dx, newy = y + dy;
2668 int step = (horiz_move ? dx : dy) * TILEX / 8;
2670 MovPos[x][y] += step;
2672 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2674 Tile[x][y] = EL_EMPTY;
2675 Tile[newx][newy] = element;
2677 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2678 MovDelay[newx][newy] = 0;
2680 if (!CAN_MOVE(element))
2681 MovDir[newx][newy] = 0;
2684 DrawField_MM(newx, newy);
2686 Stop[newx][newy] = TRUE;
2688 if (element == EL_PACMAN)
2690 if (Store[newx][newy] == EL_BOMB)
2691 Bang_MM(newx, newy);
2693 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2694 (LX + 2 * XS) / TILEX == newx &&
2695 (LY + 2 * YS) / TILEY == newy)
2702 else // still moving on
2707 laser.redraw = TRUE;
2710 boolean ClickElement(int x, int y, int button)
2712 static unsigned int click_delay = 0;
2713 static int click_delay_value = CLICK_DELAY;
2714 static boolean new_button = TRUE;
2715 boolean element_clicked = FALSE;
2720 // initialize static variables
2722 click_delay_value = CLICK_DELAY;
2728 // do not rotate objects hit by the laser after the game was solved
2729 if (game_mm.level_solved && Hit[x][y])
2732 if (button == MB_RELEASED)
2735 click_delay_value = CLICK_DELAY;
2737 // release eventually hold auto-rotating mirror
2738 RotateMirror(x, y, MB_RELEASED);
2743 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2746 if (button == MB_MIDDLEBUTTON) // middle button has no function
2749 if (!IN_LEV_FIELD(x, y))
2752 if (Tile[x][y] == EL_EMPTY)
2755 element = Tile[x][y];
2757 if (IS_MIRROR(element) ||
2758 IS_BEAMER(element) ||
2759 IS_POLAR(element) ||
2760 IS_POLAR_CROSS(element) ||
2761 IS_DF_MIRROR(element) ||
2762 IS_DF_MIRROR_AUTO(element))
2764 RotateMirror(x, y, button);
2766 element_clicked = TRUE;
2768 else if (IS_MCDUFFIN(element))
2770 if (!laser.fuse_off)
2772 DrawLaser(0, DL_LASER_DISABLED);
2779 element = get_rotated_element(element, BUTTON_ROTATION(button));
2780 laser.start_angle = get_element_angle(element);
2784 Tile[x][y] = element;
2791 if (!laser.fuse_off)
2794 element_clicked = TRUE;
2796 else if (element == EL_FUSE_ON && laser.fuse_off)
2798 if (x != laser.fuse_x || y != laser.fuse_y)
2801 laser.fuse_off = FALSE;
2802 laser.fuse_x = laser.fuse_y = -1;
2804 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2807 element_clicked = TRUE;
2809 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2811 laser.fuse_off = TRUE;
2814 laser.overloaded = FALSE;
2816 DrawLaser(0, DL_LASER_DISABLED);
2817 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2819 element_clicked = TRUE;
2821 else if (element == EL_LIGHTBALL)
2824 RaiseScoreElement_MM(element);
2825 DrawLaser(0, DL_LASER_ENABLED);
2827 element_clicked = TRUE;
2830 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2833 return element_clicked;
2836 void RotateMirror(int x, int y, int button)
2838 if (button == MB_RELEASED)
2840 // release eventually hold auto-rotating mirror
2847 if (IS_MIRROR(Tile[x][y]) ||
2848 IS_POLAR_CROSS(Tile[x][y]) ||
2849 IS_POLAR(Tile[x][y]) ||
2850 IS_BEAMER(Tile[x][y]) ||
2851 IS_DF_MIRROR(Tile[x][y]) ||
2852 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2853 IS_GRID_WOOD_AUTO(Tile[x][y]))
2855 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2857 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2859 if (button == MB_LEFTBUTTON)
2861 // left mouse button only for manual adjustment, no auto-rotating;
2862 // freeze mirror for until mouse button released
2866 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2868 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2872 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2874 int edge = Hit[x][y];
2880 DrawLaser(edge - 1, DL_LASER_DISABLED);
2884 else if (ObjHit(x, y, HIT_POS_CENTER))
2886 int edge = Hit[x][y];
2890 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2895 DrawLaser(edge - 1, DL_LASER_DISABLED);
2902 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2907 if ((IS_BEAMER(Tile[x][y]) ||
2908 IS_POLAR(Tile[x][y]) ||
2909 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2913 if (IS_BEAMER(Tile[x][y]))
2916 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2917 LX, LY, laser.beamer_edge, laser.beamer[1].num);
2927 DrawLaser(0, DL_LASER_ENABLED);
2931 static void AutoRotateMirrors(void)
2935 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2938 for (x = 0; x < lev_fieldx; x++)
2940 for (y = 0; y < lev_fieldy; y++)
2942 int element = Tile[x][y];
2944 // do not rotate objects hit by the laser after the game was solved
2945 if (game_mm.level_solved && Hit[x][y])
2948 if (IS_DF_MIRROR_AUTO(element) ||
2949 IS_GRID_WOOD_AUTO(element) ||
2950 IS_GRID_STEEL_AUTO(element) ||
2951 element == EL_REFRACTOR)
2952 RotateMirror(x, y, MB_RIGHTBUTTON);
2957 boolean ObjHit(int obx, int oby, int bits)
2964 if (bits & HIT_POS_CENTER)
2966 if (CheckLaserPixel(cSX + obx + 15,
2971 if (bits & HIT_POS_EDGE)
2973 for (i = 0; i < 4; i++)
2974 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
2975 cSY + oby + 31 * (i / 2)))
2979 if (bits & HIT_POS_BETWEEN)
2981 for (i = 0; i < 4; i++)
2982 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
2983 cSY + 4 + oby + 22 * (i / 2)))
2990 void DeletePacMan(int px, int py)
2996 if (game_mm.num_pacman <= 1)
2998 game_mm.num_pacman = 0;
3002 for (i = 0; i < game_mm.num_pacman; i++)
3003 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3006 game_mm.num_pacman--;
3008 for (j = i; j < game_mm.num_pacman; j++)
3010 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3011 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3012 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3016 void ColorCycling(void)
3018 static int CC, Cc = 0;
3020 static int color, old = 0xF00, new = 0x010, mult = 1;
3021 static unsigned short red, green, blue;
3023 if (color_status == STATIC_COLORS)
3028 if (CC < Cc || CC > Cc + 2)
3032 color = old + new * mult;
3038 if (ABS(mult) == 16)
3048 red = 0x0e00 * ((color & 0xF00) >> 8);
3049 green = 0x0e00 * ((color & 0x0F0) >> 4);
3050 blue = 0x0e00 * ((color & 0x00F));
3051 SetRGB(pen_magicolor[0], red, green, blue);
3053 red = 0x1111 * ((color & 0xF00) >> 8);
3054 green = 0x1111 * ((color & 0x0F0) >> 4);
3055 blue = 0x1111 * ((color & 0x00F));
3056 SetRGB(pen_magicolor[1], red, green, blue);
3060 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3067 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3070 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3072 element = Tile[x][y];
3074 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3075 StartMoving_MM(x, y);
3076 else if (IS_MOVING(x, y))
3077 ContinueMoving_MM(x, y);
3078 else if (IS_EXPLODING(element))
3079 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3080 else if (element == EL_EXIT_OPENING)
3082 else if (element == EL_GRAY_BALL_OPENING)
3083 OpenSurpriseBall(x, y);
3084 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3086 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3090 AutoRotateMirrors();
3093 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3095 // redraw after Explode_MM() ...
3097 DrawLaser(0, DL_LASER_ENABLED);
3098 laser.redraw = FALSE;
3103 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3107 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3109 DrawLaser(0, DL_LASER_DISABLED);
3114 if (FrameReached(&energy_delay, ENERGY_DELAY))
3116 if (game_mm.energy_left > 0)
3118 game_mm.energy_left--;
3120 redraw_mask |= REDRAW_DOOR_1;
3122 else if (setup.time_limit && !game_mm.game_over)
3126 for (i = 15; i >= 0; i--)
3129 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3131 pen_ray = GetPixelFromRGB(window,
3132 native_mm_level.laser_red * 0x11 * i,
3133 native_mm_level.laser_green * 0x11 * i,
3134 native_mm_level.laser_blue * 0x11 * i);
3136 DrawLaser(0, DL_LASER_ENABLED);
3138 Delay_WithScreenUpdates(50);
3141 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3146 DrawLaser(0, DL_LASER_DISABLED);
3147 game_mm.game_over = TRUE;
3148 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3150 SetTileCursorActive(FALSE);
3152 game.restart_game_message = "Out of magic energy! Play it again?";
3155 if (Request("Out of magic energy! Play it again?",
3156 REQ_ASK | REQ_STAY_CLOSED))
3162 game_status = MAINMENU;
3171 element = laser.dest_element;
3174 if (element != Tile[ELX][ELY])
3176 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3177 element, Tile[ELX][ELY]);
3181 if (!laser.overloaded && laser.overload_value == 0 &&
3182 element != EL_BOMB &&
3183 element != EL_MINE &&
3184 element != EL_BALL_GRAY &&
3185 element != EL_BLOCK_STONE &&
3186 element != EL_BLOCK_WOOD &&
3187 element != EL_FUSE_ON &&
3188 element != EL_FUEL_FULL &&
3189 !IS_WALL_ICE(element) &&
3190 !IS_WALL_AMOEBA(element))
3193 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3194 (!laser.overloaded && laser.overload_value > 0)) &&
3195 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3197 if (laser.overloaded)
3198 laser.overload_value++;
3200 laser.overload_value--;
3202 if (game_mm.cheat_no_overload)
3204 laser.overloaded = FALSE;
3205 laser.overload_value = 0;
3208 game_mm.laser_overload_value = laser.overload_value;
3210 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3212 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3213 int color_down = 0xFF - color_up;
3216 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3217 (15 - (laser.overload_value / 6)) * color_scale);
3220 GetPixelFromRGB(window,
3221 (native_mm_level.laser_red ? 0xFF : color_up),
3222 (native_mm_level.laser_green ? color_down : 0x00),
3223 (native_mm_level.laser_blue ? color_down : 0x00));
3225 DrawLaser(0, DL_LASER_ENABLED);
3228 if (!laser.overloaded)
3229 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3230 else if (setup.sound_loops)
3231 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3233 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3235 if (laser.overloaded)
3238 BlitBitmap(pix[PIX_DOOR], drawto,
3239 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3240 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3241 - laser.overload_value,
3242 OVERLOAD_XSIZE, laser.overload_value,
3243 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3244 - laser.overload_value);
3246 redraw_mask |= REDRAW_DOOR_1;
3251 BlitBitmap(pix[PIX_DOOR], drawto,
3252 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3253 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3254 DX_OVERLOAD, DY_OVERLOAD);
3256 redraw_mask |= REDRAW_DOOR_1;
3259 if (laser.overload_value == MAX_LASER_OVERLOAD)
3263 UpdateAndDisplayGameControlValues();
3265 for (i = 15; i >= 0; i--)
3268 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3271 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3273 DrawLaser(0, DL_LASER_ENABLED);
3275 Delay_WithScreenUpdates(50);
3278 DrawLaser(0, DL_LASER_DISABLED);
3280 game_mm.game_over = TRUE;
3281 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3283 SetTileCursorActive(FALSE);
3285 game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?";
3288 if (Request("Magic spell hit Mc Duffin! Play it again?",
3289 REQ_ASK | REQ_STAY_CLOSED))
3295 game_status = MAINMENU;
3309 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3311 if (game_mm.cheat_no_explosion)
3315 laser.num_damages--;
3316 DrawLaser(0, DL_LASER_DISABLED);
3317 laser.num_edges = 0;
3322 laser.dest_element = EL_EXPLODING_OPAQUE;
3326 laser.num_damages--;
3327 DrawLaser(0, DL_LASER_DISABLED);
3329 laser.num_edges = 0;
3330 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3332 if (Request("Bomb killed Mc Duffin! Play it again?",
3333 REQ_ASK | REQ_STAY_CLOSED))
3339 game_status = MAINMENU;
3347 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3349 laser.fuse_off = TRUE;
3353 DrawLaser(0, DL_LASER_DISABLED);
3354 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3357 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3359 static int new_elements[] =
3362 EL_MIRROR_FIXED_START,
3364 EL_POLAR_CROSS_START,
3370 int num_new_elements = sizeof(new_elements) / sizeof(int);
3371 int new_element = new_elements[RND(num_new_elements)];
3373 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3374 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3376 // !!! CHECK AGAIN: Laser on Polarizer !!!
3387 element = EL_MIRROR_START + RND(16);
3393 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3400 element = (rnd == 0 ? EL_FUSE_ON :
3401 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3402 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3403 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3404 EL_MIRROR_FIXED_START + rnd - 25);
3409 graphic = el2gfx(element);
3411 for (i = 0; i < 50; i++)
3417 BlitBitmap(pix[PIX_BACK], drawto,
3418 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3419 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3420 SX + ELX * TILEX + x,
3421 SY + ELY * TILEY + y);
3423 MarkTileDirty(ELX, ELY);
3426 DrawLaser(0, DL_LASER_ENABLED);
3428 Delay_WithScreenUpdates(50);
3431 Tile[ELX][ELY] = element;
3432 DrawField_MM(ELX, ELY);
3435 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3438 // above stuff: GRAY BALL -> PRISM !!!
3440 LX = ELX * TILEX + 14;
3441 LY = ELY * TILEY + 14;
3442 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3449 laser.num_edges -= 2;
3450 laser.num_damages--;
3454 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3455 if (laser.damage[i].is_mirror)
3459 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3461 DrawLaser(0, DL_LASER_DISABLED);
3463 DrawLaser(0, DL_LASER_DISABLED);
3472 if (IS_WALL_ICE(element) && CT > 50)
3474 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3477 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3478 Store[ELX][ELY] = EL_WALL_ICE;
3479 Store2[ELX][ELY] = laser.wall_mask;
3481 laser.dest_element = Tile[ELX][ELY];
3486 for (i = 0; i < 5; i++)
3492 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3496 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3498 Delay_WithScreenUpdates(100);
3501 if (Tile[ELX][ELY] == EL_WALL_ICE)
3502 Tile[ELX][ELY] = EL_EMPTY;
3506 LX = laser.edge[laser.num_edges].x - cSX2;
3507 LY = laser.edge[laser.num_edges].y - cSY2;
3510 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3511 if (laser.damage[i].is_mirror)
3515 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3517 DrawLaser(0, DL_LASER_DISABLED);
3524 if (IS_WALL_AMOEBA(element) && CT > 60)
3526 int k1, k2, k3, dx, dy, de, dm;
3527 int element2 = Tile[ELX][ELY];
3529 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3532 for (i = laser.num_damages - 1; i >= 0; i--)
3533 if (laser.damage[i].is_mirror)
3536 r = laser.num_edges;
3537 d = laser.num_damages;
3544 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3547 DrawLaser(0, DL_LASER_ENABLED);
3550 x = laser.damage[k1].x;
3551 y = laser.damage[k1].y;
3556 for (i = 0; i < 4; i++)
3558 if (laser.wall_mask & (1 << i))
3560 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3561 cSY + ELY * TILEY + 31 * (i / 2)))
3564 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3565 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3572 for (i = 0; i < 4; i++)
3574 if (laser.wall_mask & (1 << i))
3576 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3577 cSY + ELY * TILEY + 31 * (i / 2)))
3584 if (laser.num_beamers > 0 ||
3585 k1 < 1 || k2 < 4 || k3 < 4 ||
3586 CheckLaserPixel(cSX + ELX * TILEX + 14,
3587 cSY + ELY * TILEY + 14))
3589 laser.num_edges = r;
3590 laser.num_damages = d;
3592 DrawLaser(0, DL_LASER_DISABLED);
3595 Tile[ELX][ELY] = element | laser.wall_mask;
3599 de = Tile[ELX][ELY];
3600 dm = laser.wall_mask;
3604 int x = ELX, y = ELY;
3605 int wall_mask = laser.wall_mask;
3608 DrawLaser(0, DL_LASER_ENABLED);
3610 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3612 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3613 Store[x][y] = EL_WALL_AMOEBA;
3614 Store2[x][y] = wall_mask;
3620 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3622 DrawLaser(0, DL_LASER_ENABLED);
3624 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3626 for (i = 4; i >= 0; i--)
3628 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3631 Delay_WithScreenUpdates(20);
3634 DrawLaser(0, DL_LASER_ENABLED);
3639 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3640 laser.stops_inside_element && CT > native_mm_level.time_block)
3645 if (ABS(XS) > ABS(YS))
3652 for (i = 0; i < 4; i++)
3659 x = ELX + Step[k * 4].x;
3660 y = ELY + Step[k * 4].y;
3662 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3665 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3673 laser.overloaded = (element == EL_BLOCK_STONE);
3678 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3681 Tile[x][y] = element;
3683 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3686 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3688 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3689 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3697 if (element == EL_FUEL_FULL && CT > 10)
3699 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3702 BlitBitmap(pix[PIX_DOOR], drawto,
3703 DOOR_GFX_PAGEX4 + XX_ENERGY,
3704 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3705 ENERGY_XSIZE, i, DX_ENERGY,
3706 DY_ENERGY + ENERGY_YSIZE - i);
3709 redraw_mask |= REDRAW_DOOR_1;
3712 Delay_WithScreenUpdates(20);
3715 game_mm.energy_left = MAX_LASER_ENERGY;
3716 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3717 DrawField_MM(ELX, ELY);
3719 DrawLaser(0, DL_LASER_ENABLED);
3727 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3729 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3730 boolean button_released = (action.button == MB_RELEASED);
3732 GameActions_MM_Ext(action, warp_mode);
3734 CheckSingleStepMode_MM(element_clicked, button_released);
3737 void MovePacMen(void)
3739 int mx, my, ox, oy, nx, ny;
3743 if (++pacman_nr >= game_mm.num_pacman)
3746 game_mm.pacman[pacman_nr].dir--;
3748 for (l = 1; l < 5; l++)
3750 game_mm.pacman[pacman_nr].dir++;
3752 if (game_mm.pacman[pacman_nr].dir > 4)
3753 game_mm.pacman[pacman_nr].dir = 1;
3755 if (game_mm.pacman[pacman_nr].dir % 2)
3758 my = game_mm.pacman[pacman_nr].dir - 2;
3763 mx = 3 - game_mm.pacman[pacman_nr].dir;
3766 ox = game_mm.pacman[pacman_nr].x;
3767 oy = game_mm.pacman[pacman_nr].y;
3770 element = Tile[nx][ny];
3772 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3775 if (!IS_EATABLE4PACMAN(element))
3778 if (ObjHit(nx, ny, HIT_POS_CENTER))
3781 Tile[ox][oy] = EL_EMPTY;
3783 EL_PACMAN_RIGHT - 1 +
3784 (game_mm.pacman[pacman_nr].dir - 1 +
3785 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3787 game_mm.pacman[pacman_nr].x = nx;
3788 game_mm.pacman[pacman_nr].y = ny;
3790 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3792 if (element != EL_EMPTY)
3794 int graphic = el2gfx(Tile[nx][ny]);
3799 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3802 ox = cSX + ox * TILEX;
3803 oy = cSY + oy * TILEY;
3805 for (i = 1; i < 33; i += 2)
3806 BlitBitmap(bitmap, window,
3807 src_x, src_y, TILEX, TILEY,
3808 ox + i * mx, oy + i * my);
3809 Ct = Ct + FrameCounter - CT;
3812 DrawField_MM(nx, ny);
3815 if (!laser.fuse_off)
3817 DrawLaser(0, DL_LASER_ENABLED);
3819 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3821 AddDamagedField(nx, ny);
3823 laser.damage[laser.num_damages - 1].edge = 0;
3827 if (element == EL_BOMB)
3828 DeletePacMan(nx, ny);
3830 if (IS_WALL_AMOEBA(element) &&
3831 (LX + 2 * XS) / TILEX == nx &&
3832 (LY + 2 * YS) / TILEY == ny)
3842 void GameWon_MM(void)
3845 boolean raise_level = FALSE;
3848 if (local_player->MovPos)
3851 local_player->LevelSolved = FALSE;
3854 if (game_mm.energy_left)
3856 if (setup.sound_loops)
3857 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3858 SND_CTRL_PLAY_LOOP);
3860 while (game_mm.energy_left > 0)
3862 if (!setup.sound_loops)
3863 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3866 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3867 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3872 game_mm.energy_left--;
3873 if (game_mm.energy_left >= 0)
3876 BlitBitmap(pix[PIX_DOOR], drawto,
3877 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3878 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3879 DX_ENERGY, DY_ENERGY);
3881 redraw_mask |= REDRAW_DOOR_1;
3885 Delay_WithScreenUpdates(10);
3888 if (setup.sound_loops)
3889 StopSound(SND_SIRR);
3891 else if (native_mm_level.time == 0) // level without time limit
3893 if (setup.sound_loops)
3894 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3895 SND_CTRL_PLAY_LOOP);
3897 while (TimePlayed < 999)
3899 if (!setup.sound_loops)
3900 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3901 if (TimePlayed < 999 && !(TimePlayed % 10))
3902 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3903 if (TimePlayed < 900 && !(TimePlayed % 10))
3909 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3913 Delay_WithScreenUpdates(10);
3916 if (setup.sound_loops)
3917 StopSound(SND_SIRR);
3920 CloseDoor(DOOR_CLOSE_1);
3922 Request("Level solved!", REQ_CONFIRM);
3924 if (level_nr == leveldir_current->handicap_level)
3926 leveldir_current->handicap_level++;
3927 SaveLevelSetup_SeriesInfo();
3930 if (level_editor_test_game)
3931 game_mm.score = -1; // no highscore when playing from editor
3932 else if (level_nr < leveldir_current->last_level)
3933 raise_level = TRUE; // advance to next level
3935 if ((hi_pos = NewHiScore_MM()) >= 0)
3937 game_status = HALLOFFAME;
3939 // DrawHallOfFame(hi_pos);
3946 game_status = MAINMENU;
3957 int NewHiScore_MM(void)
3962 // LoadScore(level_nr);
3964 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3965 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3968 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3970 if (game_mm.score > highscore[k].Score)
3972 // player has made it to the hall of fame
3974 if (k < MAX_SCORE_ENTRIES - 1)
3976 int m = MAX_SCORE_ENTRIES - 1;
3979 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3980 if (!strcmp(setup.player_name, highscore[l].Name))
3982 if (m == k) // player's new highscore overwrites his old one
3986 for (l = m; l>k; l--)
3988 strcpy(highscore[l].Name, highscore[l - 1].Name);
3989 highscore[l].Score = highscore[l - 1].Score;
3996 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3997 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3998 highscore[k].Score = game_mm.score;
4005 else if (!strncmp(setup.player_name, highscore[k].Name,
4006 MAX_PLAYER_NAME_LEN))
4007 break; // player already there with a higher score
4012 // if (position >= 0)
4013 // SaveScore(level_nr);
4018 static void InitMovingField_MM(int x, int y, int direction)
4020 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4021 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4023 MovDir[x][y] = direction;
4024 MovDir[newx][newy] = direction;
4026 if (Tile[newx][newy] == EL_EMPTY)
4027 Tile[newx][newy] = EL_BLOCKED;
4030 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4032 int direction = MovDir[x][y];
4033 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4034 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4040 static void Blocked2Moving_MM(int x, int y,
4041 int *comes_from_x, int *comes_from_y)
4043 int oldx = x, oldy = y;
4044 int direction = MovDir[x][y];
4046 if (direction == MV_LEFT)
4048 else if (direction == MV_RIGHT)
4050 else if (direction == MV_UP)
4052 else if (direction == MV_DOWN)
4055 *comes_from_x = oldx;
4056 *comes_from_y = oldy;
4059 static int MovingOrBlocked2Element_MM(int x, int y)
4061 int element = Tile[x][y];
4063 if (element == EL_BLOCKED)
4067 Blocked2Moving_MM(x, y, &oldx, &oldy);
4069 return Tile[oldx][oldy];
4076 static void RemoveField(int x, int y)
4078 Tile[x][y] = EL_EMPTY;
4085 static void RemoveMovingField_MM(int x, int y)
4087 int oldx = x, oldy = y, newx = x, newy = y;
4089 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4092 if (IS_MOVING(x, y))
4094 Moving2Blocked_MM(x, y, &newx, &newy);
4095 if (Tile[newx][newy] != EL_BLOCKED)
4098 else if (Tile[x][y] == EL_BLOCKED)
4100 Blocked2Moving_MM(x, y, &oldx, &oldy);
4101 if (!IS_MOVING(oldx, oldy))
4105 Tile[oldx][oldy] = EL_EMPTY;
4106 Tile[newx][newy] = EL_EMPTY;
4107 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4108 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4110 DrawLevelField_MM(oldx, oldy);
4111 DrawLevelField_MM(newx, newy);
4114 void PlaySoundLevel(int x, int y, int sound_nr)
4116 int sx = SCREENX(x), sy = SCREENY(y);
4118 int silence_distance = 8;
4120 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4121 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4124 if (!IN_LEV_FIELD(x, y) ||
4125 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4126 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4129 volume = SOUND_MAX_VOLUME;
4132 stereo = (sx - SCR_FIELDX/2) * 12;
4134 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4135 if (stereo > SOUND_MAX_RIGHT)
4136 stereo = SOUND_MAX_RIGHT;
4137 if (stereo < SOUND_MAX_LEFT)
4138 stereo = SOUND_MAX_LEFT;
4141 if (!IN_SCR_FIELD(sx, sy))
4143 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4144 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4146 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4149 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4152 static void RaiseScore_MM(int value)
4154 game_mm.score += value;
4157 void RaiseScoreElement_MM(int element)
4162 case EL_PACMAN_RIGHT:
4164 case EL_PACMAN_LEFT:
4165 case EL_PACMAN_DOWN:
4166 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4170 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4175 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4179 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4188 // ----------------------------------------------------------------------------
4189 // Mirror Magic game engine snapshot handling functions
4190 // ----------------------------------------------------------------------------
4192 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4196 engine_snapshot_mm.game_mm = game_mm;
4197 engine_snapshot_mm.laser = laser;
4199 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4201 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4203 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4204 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4205 engine_snapshot_mm.Box[x][y] = Box[x][y];
4206 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4207 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4211 engine_snapshot_mm.LX = LX;
4212 engine_snapshot_mm.LY = LY;
4213 engine_snapshot_mm.XS = XS;
4214 engine_snapshot_mm.YS = YS;
4215 engine_snapshot_mm.ELX = ELX;
4216 engine_snapshot_mm.ELY = ELY;
4217 engine_snapshot_mm.CT = CT;
4218 engine_snapshot_mm.Ct = Ct;
4220 engine_snapshot_mm.last_LX = last_LX;
4221 engine_snapshot_mm.last_LY = last_LY;
4222 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4223 engine_snapshot_mm.hold_x = hold_x;
4224 engine_snapshot_mm.hold_y = hold_y;
4225 engine_snapshot_mm.pacman_nr = pacman_nr;
4227 engine_snapshot_mm.rotate_delay = rotate_delay;
4228 engine_snapshot_mm.pacman_delay = pacman_delay;
4229 engine_snapshot_mm.energy_delay = energy_delay;
4230 engine_snapshot_mm.overload_delay = overload_delay;
4233 void LoadEngineSnapshotValues_MM(void)
4237 // stored engine snapshot buffers already restored at this point
4239 game_mm = engine_snapshot_mm.game_mm;
4240 laser = engine_snapshot_mm.laser;
4242 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4244 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4246 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4247 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4248 Box[x][y] = engine_snapshot_mm.Box[x][y];
4249 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4250 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4254 LX = engine_snapshot_mm.LX;
4255 LY = engine_snapshot_mm.LY;
4256 XS = engine_snapshot_mm.XS;
4257 YS = engine_snapshot_mm.YS;
4258 ELX = engine_snapshot_mm.ELX;
4259 ELY = engine_snapshot_mm.ELY;
4260 CT = engine_snapshot_mm.CT;
4261 Ct = engine_snapshot_mm.Ct;
4263 last_LX = engine_snapshot_mm.last_LX;
4264 last_LY = engine_snapshot_mm.last_LY;
4265 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4266 hold_x = engine_snapshot_mm.hold_x;
4267 hold_y = engine_snapshot_mm.hold_y;
4268 pacman_nr = engine_snapshot_mm.pacman_nr;
4270 rotate_delay = engine_snapshot_mm.rotate_delay;
4271 pacman_delay = engine_snapshot_mm.pacman_delay;
4272 energy_delay = engine_snapshot_mm.energy_delay;
4273 overload_delay = engine_snapshot_mm.overload_delay;
4275 RedrawPlayfield_MM();
4278 static int getAngleFromTouchDelta(int dx, int dy, int base)
4280 double pi = 3.141592653;
4281 double rad = atan2((double)-dy, (double)dx);
4282 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4283 double deg = rad2 * 180.0 / pi;
4285 return (int)(deg * base / 360.0 + 0.5) % base;
4288 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4290 // calculate start (source) position to be at the middle of the tile
4291 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4292 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4293 int dx = dst_mx - src_mx;
4294 int dy = dst_my - src_my;
4303 if (!IN_LEV_FIELD(x, y))
4306 element = Tile[x][y];
4308 if (!IS_MCDUFFIN(element) &&
4309 !IS_MIRROR(element) &&
4310 !IS_BEAMER(element) &&
4311 !IS_POLAR(element) &&
4312 !IS_POLAR_CROSS(element) &&
4313 !IS_DF_MIRROR(element))
4316 angle_old = get_element_angle(element);
4318 if (IS_MCDUFFIN(element))
4320 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4321 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4322 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4323 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4326 else if (IS_MIRROR(element) ||
4327 IS_DF_MIRROR(element))
4329 for (i = 0; i < laser.num_damages; i++)
4331 if (laser.damage[i].x == x &&
4332 laser.damage[i].y == y &&
4333 ObjHit(x, y, HIT_POS_CENTER))
4335 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4336 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4343 if (angle_new == -1)
4345 if (IS_MIRROR(element) ||
4346 IS_DF_MIRROR(element) ||
4350 if (IS_POLAR_CROSS(element))
4353 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4356 button = (angle_new == angle_old ? 0 :
4357 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4358 MB_LEFTBUTTON : MB_RIGHTBUTTON);