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 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
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 printf("ScanPixel: touched screen border!\n");
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 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
869 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
872 laser.overloaded = TRUE;
877 hit_mask = ScanPixel();
880 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
884 // hit something -- check out what it was
885 ELX = (LX + XS) / TILEX;
886 ELY = (LY + YS) / TILEY;
889 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
890 hit_mask, LX, LY, ELX, ELY);
893 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
896 laser.dest_element = element;
901 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
903 /* we have hit the top-right and bottom-left element --
904 choose the bottom-left one */
905 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
906 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
907 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
908 ELX = (LX - 2) / TILEX;
909 ELY = (LY + 2) / TILEY;
912 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
914 /* we have hit the top-left and bottom-right element --
915 choose the top-left one */
917 ELX = (LX - 2) / TILEX;
918 ELY = (LY - 2) / TILEY;
922 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
923 hit_mask, LX, LY, ELX, ELY);
926 element = Tile[ELX][ELY];
927 laser.dest_element = element;
930 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
933 LX % TILEX, LY % TILEY,
938 if (!IN_LEV_FIELD(ELX, ELY))
939 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
942 if (element == EL_EMPTY)
944 if (!HitOnlyAnEdge(element, hit_mask))
947 else if (element == EL_FUSE_ON)
949 if (HitPolarizer(element, hit_mask))
952 else if (IS_GRID(element) || IS_DF_GRID(element))
954 if (HitPolarizer(element, hit_mask))
957 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
958 element == EL_GATE_STONE || element == EL_GATE_WOOD)
960 if (HitBlock(element, hit_mask))
967 else if (IS_MCDUFFIN(element))
969 if (HitLaserSource(element, hit_mask))
972 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
973 IS_RECEIVER(element))
975 if (HitLaserDestination(element, hit_mask))
978 else if (IS_WALL(element))
980 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
982 if (HitReflectingWalls(element, hit_mask))
987 if (HitAbsorbingWalls(element, hit_mask))
993 if (HitElement(element, hit_mask))
998 DrawLaser(rf - 1, DL_LASER_ENABLED);
999 rf = laser.num_edges;
1003 if (laser.dest_element != Tile[ELX][ELY])
1005 printf("ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d\n",
1006 laser.dest_element, Tile[ELX][ELY]);
1010 if (!end && !laser.stops_inside_element && !StepBehind())
1013 printf("ScanLaser: Go one step back\n");
1019 AddLaserEdge(LX, LY);
1023 DrawLaser(rf - 1, DL_LASER_ENABLED);
1025 Ct = CT = FrameCounter;
1028 if (!IN_LEV_FIELD(ELX, ELY))
1029 printf("WARNING! (2) %d, %d\n", ELX, ELY);
1033 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1039 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
1040 start_edge, num_edges, mode);
1045 Warn("DrawLaserExt: start_edge < 0");
1052 Warn("DrawLaserExt: num_edges < 0");
1058 if (mode == DL_LASER_DISABLED)
1060 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
1064 // now draw the laser to the backbuffer and (if enabled) to the screen
1065 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1067 redraw_mask |= REDRAW_FIELD;
1069 if (mode == DL_LASER_ENABLED)
1072 // after the laser was deleted, the "damaged" graphics must be restored
1073 if (laser.num_damages)
1075 int damage_start = 0;
1078 // determine the starting edge, from which graphics need to be restored
1081 for (i = 0; i < laser.num_damages; i++)
1083 if (laser.damage[i].edge == start_edge + 1)
1092 // restore graphics from this starting edge to the end of damage list
1093 for (i = damage_start; i < laser.num_damages; i++)
1095 int lx = laser.damage[i].x;
1096 int ly = laser.damage[i].y;
1097 int element = Tile[lx][ly];
1099 if (Hit[lx][ly] == laser.damage[i].edge)
1100 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1103 if (Box[lx][ly] == laser.damage[i].edge)
1106 if (IS_DRAWABLE(element))
1107 DrawField_MM(lx, ly);
1110 elx = laser.damage[damage_start].x;
1111 ely = laser.damage[damage_start].y;
1112 element = Tile[elx][ely];
1115 if (IS_BEAMER(element))
1119 for (i = 0; i < laser.num_beamers; i++)
1120 printf("-> %d\n", laser.beamer_edge[i]);
1121 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
1122 mode, elx, ely, Hit[elx][ely], start_edge);
1123 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
1124 get_element_angle(element), laser.damage[damage_start].angle);
1128 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1129 laser.num_beamers > 0 &&
1130 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1132 // element is outgoing beamer
1133 laser.num_damages = damage_start + 1;
1135 if (IS_BEAMER(element))
1136 laser.current_angle = get_element_angle(element);
1140 // element is incoming beamer or other element
1141 laser.num_damages = damage_start;
1142 laser.current_angle = laser.damage[laser.num_damages].angle;
1147 // no damages but McDuffin himself (who needs to be redrawn anyway)
1149 elx = laser.start_edge.x;
1150 ely = laser.start_edge.y;
1151 element = Tile[elx][ely];
1154 laser.num_edges = start_edge + 1;
1155 if (start_edge == 0)
1156 laser.current_angle = laser.start_angle;
1158 LX = laser.edge[start_edge].x - cSX2;
1159 LY = laser.edge[start_edge].y - cSY2;
1160 XS = 2 * Step[laser.current_angle].x;
1161 YS = 2 * Step[laser.current_angle].y;
1164 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
1170 if (IS_BEAMER(element) ||
1171 IS_FIBRE_OPTIC(element) ||
1172 IS_PACMAN(element) ||
1173 IS_POLAR(element) ||
1174 IS_POLAR_CROSS(element) ||
1175 element == EL_FUSE_ON)
1180 printf("element == %d\n", element);
1183 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1184 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1188 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1189 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1190 (laser.num_beamers == 0 ||
1191 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1193 // element is incoming beamer or other element
1194 step_size = -step_size;
1199 if (IS_BEAMER(element))
1201 printf("start_edge == %d, laser.beamer_edge == %d\n",
1202 start_edge, laser.beamer_edge);
1206 LX += step_size * XS;
1207 LY += step_size * YS;
1209 else if (element != EL_EMPTY)
1218 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
1223 void DrawLaser(int start_edge, int mode)
1225 if (laser.num_edges - start_edge < 0)
1227 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1232 // check if laser is interrupted by beamer element
1233 if (laser.num_beamers > 0 &&
1234 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1236 if (mode == DL_LASER_ENABLED)
1239 int tmp_start_edge = start_edge;
1241 // draw laser segments forward from the start to the last beamer
1242 for (i = 0; i < laser.num_beamers; i++)
1244 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1246 if (tmp_num_edges <= 0)
1250 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
1251 i, laser.beamer_edge[i], tmp_start_edge);
1254 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1256 tmp_start_edge = laser.beamer_edge[i];
1259 // draw last segment from last beamer to the end
1260 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1266 int last_num_edges = laser.num_edges;
1267 int num_beamers = laser.num_beamers;
1269 // delete laser segments backward from the end to the first beamer
1270 for (i = num_beamers - 1; i >= 0; i--)
1272 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1274 if (laser.beamer_edge[i] - start_edge <= 0)
1277 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1279 last_num_edges = laser.beamer_edge[i];
1280 laser.num_beamers--;
1284 if (last_num_edges - start_edge <= 0)
1285 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1286 last_num_edges, start_edge);
1289 // special case when rotating first beamer: delete laser edge on beamer
1290 // (but do not start scanning on previous edge to prevent mirror sound)
1291 if (last_num_edges - start_edge == 1 && start_edge > 0)
1292 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1294 // delete first segment from start to the first beamer
1295 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1300 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1303 game_mm.laser_enabled = mode;
1306 void DrawLaser_MM(void)
1308 DrawLaser(0, game_mm.laser_enabled);
1311 boolean HitElement(int element, int hit_mask)
1313 if (HitOnlyAnEdge(element, hit_mask))
1316 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1317 element = MovingOrBlocked2Element_MM(ELX, ELY);
1320 printf("HitElement (1): element == %d\n", element);
1324 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1325 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1327 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1330 AddDamagedField(ELX, ELY);
1332 // this is more precise: check if laser would go through the center
1333 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1335 // skip the whole element before continuing the scan
1341 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1343 if (LX/TILEX > ELX || LY/TILEY > ELY)
1345 /* skipping scan positions to the right and down skips one scan
1346 position too much, because this is only the top left scan position
1347 of totally four scan positions (plus one to the right, one to the
1348 bottom and one to the bottom right) */
1358 printf("HitElement (2): element == %d\n", element);
1361 if (LX + 5 * XS < 0 ||
1371 printf("HitElement (3): element == %d\n", element);
1374 if (IS_POLAR(element) &&
1375 ((element - EL_POLAR_START) % 2 ||
1376 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1378 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1380 laser.num_damages--;
1385 if (IS_POLAR_CROSS(element) &&
1386 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1388 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1390 laser.num_damages--;
1395 if (!IS_BEAMER(element) &&
1396 !IS_FIBRE_OPTIC(element) &&
1397 !IS_GRID_WOOD(element) &&
1398 element != EL_FUEL_EMPTY)
1401 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1402 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1404 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1407 LX = ELX * TILEX + 14;
1408 LY = ELY * TILEY + 14;
1410 AddLaserEdge(LX, LY);
1413 if (IS_MIRROR(element) ||
1414 IS_MIRROR_FIXED(element) ||
1415 IS_POLAR(element) ||
1416 IS_POLAR_CROSS(element) ||
1417 IS_DF_MIRROR(element) ||
1418 IS_DF_MIRROR_AUTO(element) ||
1419 element == EL_PRISM ||
1420 element == EL_REFRACTOR)
1422 int current_angle = laser.current_angle;
1425 laser.num_damages--;
1427 AddDamagedField(ELX, ELY);
1429 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1432 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1434 if (IS_MIRROR(element) ||
1435 IS_MIRROR_FIXED(element) ||
1436 IS_DF_MIRROR(element) ||
1437 IS_DF_MIRROR_AUTO(element))
1438 laser.current_angle = get_mirrored_angle(laser.current_angle,
1439 get_element_angle(element));
1441 if (element == EL_PRISM || element == EL_REFRACTOR)
1442 laser.current_angle = RND(16);
1444 XS = 2 * Step[laser.current_angle].x;
1445 YS = 2 * Step[laser.current_angle].y;
1447 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1452 LX += step_size * XS;
1453 LY += step_size * YS;
1456 // draw sparkles on mirror
1457 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1458 current_angle != laser.current_angle)
1460 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1464 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1465 current_angle != laser.current_angle)
1466 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1469 (get_opposite_angle(laser.current_angle) ==
1470 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1472 return (laser.overloaded ? TRUE : FALSE);
1475 if (element == EL_FUEL_FULL)
1477 laser.stops_inside_element = TRUE;
1482 if (element == EL_BOMB || element == EL_MINE)
1484 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1486 if (element == EL_MINE)
1487 laser.overloaded = TRUE;
1490 if (element == EL_KETTLE ||
1491 element == EL_CELL ||
1492 element == EL_KEY ||
1493 element == EL_LIGHTBALL ||
1494 element == EL_PACMAN ||
1497 if (!IS_PACMAN(element))
1500 if (element == EL_PACMAN)
1503 if (element == EL_KETTLE || element == EL_CELL)
1505 if (game_mm.kettles_still_needed > 0)
1506 game_mm.kettles_still_needed--;
1508 game.snapshot.collected_item = TRUE;
1510 if (game_mm.kettles_still_needed == 0)
1514 DrawLaser(0, DL_LASER_ENABLED);
1517 else if (element == EL_KEY)
1521 else if (IS_PACMAN(element))
1523 DeletePacMan(ELX, ELY);
1526 RaiseScoreElement_MM(element);
1531 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1533 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1535 DrawLaser(0, DL_LASER_ENABLED);
1537 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1539 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1540 game_mm.lights_still_needed--;
1544 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1545 game_mm.lights_still_needed++;
1548 DrawField_MM(ELX, ELY);
1549 DrawLaser(0, DL_LASER_ENABLED);
1554 laser.stops_inside_element = TRUE;
1560 printf("HitElement (4): element == %d\n", element);
1563 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1564 laser.num_beamers < MAX_NUM_BEAMERS &&
1565 laser.beamer[BEAMER_NR(element)][1].num)
1567 int beamer_angle = get_element_angle(element);
1568 int beamer_nr = BEAMER_NR(element);
1572 printf("HitElement (BEAMER): element == %d\n", element);
1575 laser.num_damages--;
1577 if (IS_FIBRE_OPTIC(element) ||
1578 laser.current_angle == get_opposite_angle(beamer_angle))
1582 LX = ELX * TILEX + 14;
1583 LY = ELY * TILEY + 14;
1585 AddLaserEdge(LX, LY);
1586 AddDamagedField(ELX, ELY);
1588 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1591 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1593 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1594 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1595 ELX = laser.beamer[beamer_nr][pos].x;
1596 ELY = laser.beamer[beamer_nr][pos].y;
1597 LX = ELX * TILEX + 14;
1598 LY = ELY * TILEY + 14;
1600 if (IS_BEAMER(element))
1602 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1603 XS = 2 * Step[laser.current_angle].x;
1604 YS = 2 * Step[laser.current_angle].y;
1607 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1609 AddLaserEdge(LX, LY);
1610 AddDamagedField(ELX, ELY);
1612 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1615 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1617 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1622 LX += step_size * XS;
1623 LY += step_size * YS;
1625 laser.num_beamers++;
1634 boolean HitOnlyAnEdge(int element, int hit_mask)
1636 // check if the laser hit only the edge of an element and, if so, go on
1639 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1642 if ((hit_mask == HIT_MASK_TOPLEFT ||
1643 hit_mask == HIT_MASK_TOPRIGHT ||
1644 hit_mask == HIT_MASK_BOTTOMLEFT ||
1645 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1646 laser.current_angle % 4) // angle is not 90°
1650 if (hit_mask == HIT_MASK_TOPLEFT)
1655 else if (hit_mask == HIT_MASK_TOPRIGHT)
1660 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1665 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1671 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1677 printf("[HitOnlyAnEdge() == TRUE]\n");
1684 printf("[HitOnlyAnEdge() == FALSE]\n");
1690 boolean HitPolarizer(int element, int hit_mask)
1692 if (HitOnlyAnEdge(element, hit_mask))
1695 if (IS_DF_GRID(element))
1697 int grid_angle = get_element_angle(element);
1700 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1701 grid_angle, laser.current_angle);
1704 AddLaserEdge(LX, LY);
1705 AddDamagedField(ELX, ELY);
1708 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1710 if (laser.current_angle == grid_angle ||
1711 laser.current_angle == get_opposite_angle(grid_angle))
1713 // skip the whole element before continuing the scan
1719 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1721 if (LX/TILEX > ELX || LY/TILEY > ELY)
1723 /* skipping scan positions to the right and down skips one scan
1724 position too much, because this is only the top left scan position
1725 of totally four scan positions (plus one to the right, one to the
1726 bottom and one to the bottom right) */
1732 AddLaserEdge(LX, LY);
1738 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1740 LX / TILEX, LY / TILEY,
1741 LX % TILEX, LY % TILEY);
1746 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1748 return HitReflectingWalls(element, hit_mask);
1752 return HitAbsorbingWalls(element, hit_mask);
1755 else if (IS_GRID_STEEL(element))
1757 return HitReflectingWalls(element, hit_mask);
1759 else // IS_GRID_WOOD
1761 return HitAbsorbingWalls(element, hit_mask);
1767 boolean HitBlock(int element, int hit_mask)
1769 boolean check = FALSE;
1771 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1772 game_mm.num_keys == 0)
1775 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1778 int ex = ELX * TILEX + 14;
1779 int ey = ELY * TILEY + 14;
1783 for (i = 1; i < 32; i++)
1788 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1793 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1794 return HitAbsorbingWalls(element, hit_mask);
1798 AddLaserEdge(LX - XS, LY - YS);
1799 AddDamagedField(ELX, ELY);
1802 Box[ELX][ELY] = laser.num_edges;
1804 return HitReflectingWalls(element, hit_mask);
1807 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1809 int xs = XS / 2, ys = YS / 2;
1810 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1811 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1813 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1814 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1816 laser.overloaded = (element == EL_GATE_STONE);
1821 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1822 (hit_mask == HIT_MASK_TOP ||
1823 hit_mask == HIT_MASK_LEFT ||
1824 hit_mask == HIT_MASK_RIGHT ||
1825 hit_mask == HIT_MASK_BOTTOM))
1826 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1827 hit_mask == HIT_MASK_BOTTOM),
1828 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1829 hit_mask == HIT_MASK_RIGHT));
1830 AddLaserEdge(LX, LY);
1836 if (element == EL_GATE_STONE && Box[ELX][ELY])
1838 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1850 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1852 int xs = XS / 2, ys = YS / 2;
1853 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1854 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1856 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1857 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1859 laser.overloaded = (element == EL_BLOCK_STONE);
1864 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1865 (hit_mask == HIT_MASK_TOP ||
1866 hit_mask == HIT_MASK_LEFT ||
1867 hit_mask == HIT_MASK_RIGHT ||
1868 hit_mask == HIT_MASK_BOTTOM))
1869 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1870 hit_mask == HIT_MASK_BOTTOM),
1871 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1872 hit_mask == HIT_MASK_RIGHT));
1873 AddDamagedField(ELX, ELY);
1875 LX = ELX * TILEX + 14;
1876 LY = ELY * TILEY + 14;
1878 AddLaserEdge(LX, LY);
1880 laser.stops_inside_element = TRUE;
1888 boolean HitLaserSource(int element, int hit_mask)
1890 if (HitOnlyAnEdge(element, hit_mask))
1893 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1895 laser.overloaded = TRUE;
1900 boolean HitLaserDestination(int element, int hit_mask)
1902 if (HitOnlyAnEdge(element, hit_mask))
1905 if (element != EL_EXIT_OPEN &&
1906 !(IS_RECEIVER(element) &&
1907 game_mm.kettles_still_needed == 0 &&
1908 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1910 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1915 if (IS_RECEIVER(element) ||
1916 (IS_22_5_ANGLE(laser.current_angle) &&
1917 (ELX != (LX + 6 * XS) / TILEX ||
1918 ELY != (LY + 6 * YS) / TILEY ||
1927 LX = ELX * TILEX + 14;
1928 LY = ELY * TILEY + 14;
1930 laser.stops_inside_element = TRUE;
1933 AddLaserEdge(LX, LY);
1934 AddDamagedField(ELX, ELY);
1936 if (game_mm.lights_still_needed == 0)
1938 game_mm.level_solved = TRUE;
1940 SetTileCursorActive(FALSE);
1946 boolean HitReflectingWalls(int element, int hit_mask)
1948 // check if laser hits side of a wall with an angle that is not 90°
1949 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1950 hit_mask == HIT_MASK_LEFT ||
1951 hit_mask == HIT_MASK_RIGHT ||
1952 hit_mask == HIT_MASK_BOTTOM))
1954 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1959 if (!IS_DF_GRID(element))
1960 AddLaserEdge(LX, LY);
1962 // check if laser hits wall with an angle of 45°
1963 if (!IS_22_5_ANGLE(laser.current_angle))
1965 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1968 laser.current_angle = get_mirrored_angle(laser.current_angle,
1971 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
1974 laser.current_angle = get_mirrored_angle(laser.current_angle,
1978 AddLaserEdge(LX, LY);
1980 XS = 2 * Step[laser.current_angle].x;
1981 YS = 2 * Step[laser.current_angle].y;
1985 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1987 laser.current_angle = get_mirrored_angle(laser.current_angle,
1992 if (!IS_DF_GRID(element))
1993 AddLaserEdge(LX, LY);
1998 if (!IS_DF_GRID(element))
1999 AddLaserEdge(LX, LY + YS / 2);
2002 if (!IS_DF_GRID(element))
2003 AddLaserEdge(LX, LY);
2006 YS = 2 * Step[laser.current_angle].y;
2010 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2012 laser.current_angle = get_mirrored_angle(laser.current_angle,
2017 if (!IS_DF_GRID(element))
2018 AddLaserEdge(LX, LY);
2023 if (!IS_DF_GRID(element))
2024 AddLaserEdge(LX + XS / 2, LY);
2027 if (!IS_DF_GRID(element))
2028 AddLaserEdge(LX, LY);
2031 XS = 2 * Step[laser.current_angle].x;
2037 // reflection at the edge of reflecting DF style wall
2038 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2040 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2041 hit_mask == HIT_MASK_TOPRIGHT) ||
2042 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2043 hit_mask == HIT_MASK_TOPLEFT) ||
2044 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2045 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2046 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2047 hit_mask == HIT_MASK_BOTTOMRIGHT))
2050 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2051 ANG_MIRROR_135 : ANG_MIRROR_45);
2053 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2055 AddDamagedField(ELX, ELY);
2056 AddLaserEdge(LX, LY);
2058 laser.current_angle = get_mirrored_angle(laser.current_angle,
2066 AddLaserEdge(LX, LY);
2072 // reflection inside an edge of reflecting DF style wall
2073 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2075 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2076 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2077 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2078 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2079 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2080 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2081 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2082 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2085 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2086 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2087 ANG_MIRROR_135 : ANG_MIRROR_45);
2089 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2092 AddDamagedField(ELX, ELY);
2095 AddLaserEdge(LX - XS, LY - YS);
2096 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2097 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2099 laser.current_angle = get_mirrored_angle(laser.current_angle,
2107 AddLaserEdge(LX, LY);
2113 // check if laser hits DF style wall with an angle of 90°
2114 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2116 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2117 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2118 (IS_VERT_ANGLE(laser.current_angle) &&
2119 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2121 // laser at last step touched nothing or the same side of the wall
2122 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2124 AddDamagedField(ELX, ELY);
2131 last_hit_mask = hit_mask;
2138 if (!HitOnlyAnEdge(element, hit_mask))
2140 laser.overloaded = TRUE;
2148 boolean HitAbsorbingWalls(int element, int hit_mask)
2150 if (HitOnlyAnEdge(element, hit_mask))
2154 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2156 AddLaserEdge(LX - XS, LY - YS);
2163 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2165 AddLaserEdge(LX - XS, LY - YS);
2171 if (IS_WALL_WOOD(element) ||
2172 IS_DF_WALL_WOOD(element) ||
2173 IS_GRID_WOOD(element) ||
2174 IS_GRID_WOOD_FIXED(element) ||
2175 IS_GRID_WOOD_AUTO(element) ||
2176 element == EL_FUSE_ON ||
2177 element == EL_BLOCK_WOOD ||
2178 element == EL_GATE_WOOD)
2180 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2185 if (IS_WALL_ICE(element))
2189 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2190 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2192 // check if laser hits wall with an angle of 90°
2193 if (IS_90_ANGLE(laser.current_angle))
2194 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2196 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2200 for (i = 0; i < 4; i++)
2202 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2203 mask = 15 - (8 >> i);
2204 else if (ABS(XS) == 4 &&
2206 (XS > 0) == (i % 2) &&
2207 (YS < 0) == (i / 2))
2208 mask = 3 + (i / 2) * 9;
2209 else if (ABS(YS) == 4 &&
2211 (XS < 0) == (i % 2) &&
2212 (YS > 0) == (i / 2))
2213 mask = 5 + (i % 2) * 5;
2217 laser.wall_mask = mask;
2219 else if (IS_WALL_AMOEBA(element))
2221 int elx = (LX - 2 * XS) / TILEX;
2222 int ely = (LY - 2 * YS) / TILEY;
2223 int element2 = Tile[elx][ely];
2226 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2228 laser.dest_element = EL_EMPTY;
2236 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2237 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2239 if (IS_90_ANGLE(laser.current_angle))
2240 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2242 laser.dest_element = element2 | EL_WALL_AMOEBA;
2244 laser.wall_mask = mask;
2250 static void OpenExit(int x, int y)
2254 if (!MovDelay[x][y]) // next animation frame
2255 MovDelay[x][y] = 4 * delay;
2257 if (MovDelay[x][y]) // wait some time before next frame
2262 phase = MovDelay[x][y] / delay;
2264 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2265 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2267 if (!MovDelay[x][y])
2269 Tile[x][y] = EL_EXIT_OPEN;
2275 static void OpenSurpriseBall(int x, int y)
2279 if (!MovDelay[x][y]) // next animation frame
2280 MovDelay[x][y] = 50 * delay;
2282 if (MovDelay[x][y]) // wait some time before next frame
2286 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2289 int graphic = el2gfx(Store[x][y]);
2291 int dx = RND(26), dy = RND(26);
2293 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2295 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2296 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2298 MarkTileDirty(x, y);
2301 if (!MovDelay[x][y])
2303 Tile[x][y] = Store[x][y];
2312 static void MeltIce(int x, int y)
2317 if (!MovDelay[x][y]) // next animation frame
2318 MovDelay[x][y] = frames * delay;
2320 if (MovDelay[x][y]) // wait some time before next frame
2323 int wall_mask = Store2[x][y];
2324 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2327 phase = frames - MovDelay[x][y] / delay - 1;
2329 if (!MovDelay[x][y])
2333 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2334 Store[x][y] = Store2[x][y] = 0;
2336 DrawWalls_MM(x, y, Tile[x][y]);
2338 if (Tile[x][y] == EL_WALL_ICE)
2339 Tile[x][y] = EL_EMPTY;
2341 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2342 if (laser.damage[i].is_mirror)
2346 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2348 DrawLaser(0, DL_LASER_DISABLED);
2352 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2354 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2356 laser.redraw = TRUE;
2361 static void GrowAmoeba(int x, int y)
2366 if (!MovDelay[x][y]) // next animation frame
2367 MovDelay[x][y] = frames * delay;
2369 if (MovDelay[x][y]) // wait some time before next frame
2372 int wall_mask = Store2[x][y];
2373 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2376 phase = MovDelay[x][y] / delay;
2378 if (!MovDelay[x][y])
2380 Tile[x][y] = real_element;
2381 Store[x][y] = Store2[x][y] = 0;
2383 DrawWalls_MM(x, y, Tile[x][y]);
2384 DrawLaser(0, DL_LASER_ENABLED);
2386 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2388 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2393 static void Explode_MM(int x, int y, int phase, int mode)
2395 int num_phase = 9, delay = 2;
2396 int last_phase = num_phase * delay;
2397 int half_phase = (num_phase / 2) * delay;
2399 laser.redraw = TRUE;
2401 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2403 int center_element = Tile[x][y];
2405 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2407 // put moving element to center field (and let it explode there)
2408 center_element = MovingOrBlocked2Element_MM(x, y);
2409 RemoveMovingField_MM(x, y);
2411 Tile[x][y] = center_element;
2414 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2415 Store[x][y] = center_element;
2417 Store[x][y] = EL_EMPTY;
2419 Store2[x][y] = mode;
2420 Tile[x][y] = EL_EXPLODING_OPAQUE;
2421 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2427 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2429 if (phase == half_phase)
2431 Tile[x][y] = EL_EXPLODING_TRANSP;
2433 if (x == ELX && y == ELY)
2437 if (phase == last_phase)
2439 if (Store[x][y] == EL_BOMB)
2441 DrawLaser(0, DL_LASER_DISABLED);
2444 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2445 Store[x][y] = EL_EMPTY;
2447 game_mm.game_over = TRUE;
2448 game_mm.game_over_cause = GAME_OVER_BOMB;
2450 SetTileCursorActive(FALSE);
2452 laser.overloaded = FALSE;
2454 else if (IS_MCDUFFIN(Store[x][y]))
2456 Store[x][y] = EL_EMPTY;
2458 game.restart_game_message = "Bomb killed Mc Duffin! Play it again?";
2461 Tile[x][y] = Store[x][y];
2462 Store[x][y] = Store2[x][y] = 0;
2463 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2465 InitField(x, y, FALSE);
2468 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2470 int graphic = IMG_MM_DEFAULT_EXPLODING;
2471 int graphic_phase = (phase / delay - 1);
2475 if (Store2[x][y] == EX_KETTLE)
2477 if (graphic_phase < 3)
2479 graphic = IMG_MM_KETTLE_EXPLODING;
2481 else if (graphic_phase < 5)
2487 graphic = IMG_EMPTY;
2491 else if (Store2[x][y] == EX_SHORT)
2493 if (graphic_phase < 4)
2499 graphic = IMG_EMPTY;
2504 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2506 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2507 cFX + x * TILEX, cFY + y * TILEY);
2509 MarkTileDirty(x, y);
2513 static void Bang_MM(int x, int y)
2515 int element = Tile[x][y];
2516 int mode = EX_NORMAL;
2519 DrawLaser(0, DL_LASER_ENABLED);
2538 if (IS_PACMAN(element))
2539 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2540 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2541 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2542 else if (element == EL_KEY)
2543 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2545 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2547 Explode_MM(x, y, EX_PHASE_START, mode);
2550 void TurnRound(int x, int y)
2562 { 0, 0 }, { 0, 0 }, { 0, 0 },
2567 int left, right, back;
2571 { MV_DOWN, MV_UP, MV_RIGHT },
2572 { MV_UP, MV_DOWN, MV_LEFT },
2574 { MV_LEFT, MV_RIGHT, MV_DOWN },
2575 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2576 { MV_RIGHT, MV_LEFT, MV_UP }
2579 int element = Tile[x][y];
2580 int old_move_dir = MovDir[x][y];
2581 int right_dir = turn[old_move_dir].right;
2582 int back_dir = turn[old_move_dir].back;
2583 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2584 int right_x = x + right_dx, right_y = y + right_dy;
2586 if (element == EL_PACMAN)
2588 boolean can_turn_right = FALSE;
2590 if (IN_LEV_FIELD(right_x, right_y) &&
2591 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2592 can_turn_right = TRUE;
2595 MovDir[x][y] = right_dir;
2597 MovDir[x][y] = back_dir;
2603 static void StartMoving_MM(int x, int y)
2605 int element = Tile[x][y];
2610 if (CAN_MOVE(element))
2614 if (MovDelay[x][y]) // wait some time before next movement
2622 // now make next step
2624 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2626 if (element == EL_PACMAN &&
2627 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2628 !ObjHit(newx, newy, HIT_POS_CENTER))
2630 Store[newx][newy] = Tile[newx][newy];
2631 Tile[newx][newy] = EL_EMPTY;
2633 DrawField_MM(newx, newy);
2635 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2636 ObjHit(newx, newy, HIT_POS_CENTER))
2638 // object was running against a wall
2645 InitMovingField_MM(x, y, MovDir[x][y]);
2649 ContinueMoving_MM(x, y);
2652 static void ContinueMoving_MM(int x, int y)
2654 int element = Tile[x][y];
2655 int direction = MovDir[x][y];
2656 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2657 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2658 int horiz_move = (dx!=0);
2659 int newx = x + dx, newy = y + dy;
2660 int step = (horiz_move ? dx : dy) * TILEX / 8;
2662 MovPos[x][y] += step;
2664 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2666 Tile[x][y] = EL_EMPTY;
2667 Tile[newx][newy] = element;
2669 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2670 MovDelay[newx][newy] = 0;
2672 if (!CAN_MOVE(element))
2673 MovDir[newx][newy] = 0;
2676 DrawField_MM(newx, newy);
2678 Stop[newx][newy] = TRUE;
2680 if (element == EL_PACMAN)
2682 if (Store[newx][newy] == EL_BOMB)
2683 Bang_MM(newx, newy);
2685 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2686 (LX + 2 * XS) / TILEX == newx &&
2687 (LY + 2 * YS) / TILEY == newy)
2694 else // still moving on
2699 laser.redraw = TRUE;
2702 boolean ClickElement(int x, int y, int button)
2704 static unsigned int click_delay = 0;
2705 static int click_delay_value = CLICK_DELAY;
2706 static boolean new_button = TRUE;
2707 boolean element_clicked = FALSE;
2712 // initialize static variables
2714 click_delay_value = CLICK_DELAY;
2720 // do not rotate objects hit by the laser after the game was solved
2721 if (game_mm.level_solved && Hit[x][y])
2724 if (button == MB_RELEASED)
2727 click_delay_value = CLICK_DELAY;
2729 // release eventually hold auto-rotating mirror
2730 RotateMirror(x, y, MB_RELEASED);
2735 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2738 if (button == MB_MIDDLEBUTTON) // middle button has no function
2741 if (!IN_LEV_FIELD(x, y))
2744 if (Tile[x][y] == EL_EMPTY)
2747 element = Tile[x][y];
2749 if (IS_MIRROR(element) ||
2750 IS_BEAMER(element) ||
2751 IS_POLAR(element) ||
2752 IS_POLAR_CROSS(element) ||
2753 IS_DF_MIRROR(element) ||
2754 IS_DF_MIRROR_AUTO(element))
2756 RotateMirror(x, y, button);
2758 element_clicked = TRUE;
2760 else if (IS_MCDUFFIN(element))
2762 if (!laser.fuse_off)
2764 DrawLaser(0, DL_LASER_DISABLED);
2771 element = get_rotated_element(element, BUTTON_ROTATION(button));
2772 laser.start_angle = get_element_angle(element);
2776 Tile[x][y] = element;
2783 if (!laser.fuse_off)
2786 element_clicked = TRUE;
2788 else if (element == EL_FUSE_ON && laser.fuse_off)
2790 if (x != laser.fuse_x || y != laser.fuse_y)
2793 laser.fuse_off = FALSE;
2794 laser.fuse_x = laser.fuse_y = -1;
2796 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2799 element_clicked = TRUE;
2801 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2803 laser.fuse_off = TRUE;
2806 laser.overloaded = FALSE;
2808 DrawLaser(0, DL_LASER_DISABLED);
2809 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2811 element_clicked = TRUE;
2813 else if (element == EL_LIGHTBALL)
2816 RaiseScoreElement_MM(element);
2817 DrawLaser(0, DL_LASER_ENABLED);
2819 element_clicked = TRUE;
2822 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2825 return element_clicked;
2828 void RotateMirror(int x, int y, int button)
2830 if (button == MB_RELEASED)
2832 // release eventually hold auto-rotating mirror
2839 if (IS_MIRROR(Tile[x][y]) ||
2840 IS_POLAR_CROSS(Tile[x][y]) ||
2841 IS_POLAR(Tile[x][y]) ||
2842 IS_BEAMER(Tile[x][y]) ||
2843 IS_DF_MIRROR(Tile[x][y]) ||
2844 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2845 IS_GRID_WOOD_AUTO(Tile[x][y]))
2847 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2849 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2851 if (button == MB_LEFTBUTTON)
2853 // left mouse button only for manual adjustment, no auto-rotating;
2854 // freeze mirror for until mouse button released
2858 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2860 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2864 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2866 int edge = Hit[x][y];
2872 DrawLaser(edge - 1, DL_LASER_DISABLED);
2876 else if (ObjHit(x, y, HIT_POS_CENTER))
2878 int edge = Hit[x][y];
2882 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2887 DrawLaser(edge - 1, DL_LASER_DISABLED);
2894 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2899 if ((IS_BEAMER(Tile[x][y]) ||
2900 IS_POLAR(Tile[x][y]) ||
2901 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2905 if (IS_BEAMER(Tile[x][y]))
2908 printf("TEST (%d, %d) [%d] [%d]\n",
2910 laser.beamer_edge, laser.beamer[1].num);
2920 DrawLaser(0, DL_LASER_ENABLED);
2924 static void AutoRotateMirrors(void)
2928 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2931 for (x = 0; x < lev_fieldx; x++)
2933 for (y = 0; y < lev_fieldy; y++)
2935 int element = Tile[x][y];
2937 // do not rotate objects hit by the laser after the game was solved
2938 if (game_mm.level_solved && Hit[x][y])
2941 if (IS_DF_MIRROR_AUTO(element) ||
2942 IS_GRID_WOOD_AUTO(element) ||
2943 IS_GRID_STEEL_AUTO(element) ||
2944 element == EL_REFRACTOR)
2945 RotateMirror(x, y, MB_RIGHTBUTTON);
2950 boolean ObjHit(int obx, int oby, int bits)
2957 if (bits & HIT_POS_CENTER)
2959 if (CheckLaserPixel(cSX + obx + 15,
2964 if (bits & HIT_POS_EDGE)
2966 for (i = 0; i < 4; i++)
2967 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
2968 cSY + oby + 31 * (i / 2)))
2972 if (bits & HIT_POS_BETWEEN)
2974 for (i = 0; i < 4; i++)
2975 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
2976 cSY + 4 + oby + 22 * (i / 2)))
2983 void DeletePacMan(int px, int py)
2989 if (game_mm.num_pacman <= 1)
2991 game_mm.num_pacman = 0;
2995 for (i = 0; i < game_mm.num_pacman; i++)
2996 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2999 game_mm.num_pacman--;
3001 for (j = i; j < game_mm.num_pacman; j++)
3003 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3004 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3005 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3009 void ColorCycling(void)
3011 static int CC, Cc = 0;
3013 static int color, old = 0xF00, new = 0x010, mult = 1;
3014 static unsigned short red, green, blue;
3016 if (color_status == STATIC_COLORS)
3021 if (CC < Cc || CC > Cc + 2)
3025 color = old + new * mult;
3031 if (ABS(mult) == 16)
3041 red = 0x0e00 * ((color & 0xF00) >> 8);
3042 green = 0x0e00 * ((color & 0x0F0) >> 4);
3043 blue = 0x0e00 * ((color & 0x00F));
3044 SetRGB(pen_magicolor[0], red, green, blue);
3046 red = 0x1111 * ((color & 0xF00) >> 8);
3047 green = 0x1111 * ((color & 0x0F0) >> 4);
3048 blue = 0x1111 * ((color & 0x00F));
3049 SetRGB(pen_magicolor[1], red, green, blue);
3053 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3060 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3063 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3065 element = Tile[x][y];
3067 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3068 StartMoving_MM(x, y);
3069 else if (IS_MOVING(x, y))
3070 ContinueMoving_MM(x, y);
3071 else if (IS_EXPLODING(element))
3072 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3073 else if (element == EL_EXIT_OPENING)
3075 else if (element == EL_GRAY_BALL_OPENING)
3076 OpenSurpriseBall(x, y);
3077 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3079 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3083 AutoRotateMirrors();
3086 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3088 // redraw after Explode_MM() ...
3090 DrawLaser(0, DL_LASER_ENABLED);
3091 laser.redraw = FALSE;
3096 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3100 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3102 DrawLaser(0, DL_LASER_DISABLED);
3107 if (FrameReached(&energy_delay, ENERGY_DELAY))
3109 if (game_mm.energy_left > 0)
3111 game_mm.energy_left--;
3113 redraw_mask |= REDRAW_DOOR_1;
3115 else if (setup.time_limit && !game_mm.game_over)
3119 for (i = 15; i >= 0; i--)
3122 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3124 pen_ray = GetPixelFromRGB(window,
3125 native_mm_level.laser_red * 0x11 * i,
3126 native_mm_level.laser_green * 0x11 * i,
3127 native_mm_level.laser_blue * 0x11 * i);
3129 DrawLaser(0, DL_LASER_ENABLED);
3131 Delay_WithScreenUpdates(50);
3134 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3139 DrawLaser(0, DL_LASER_DISABLED);
3140 game_mm.game_over = TRUE;
3141 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3143 SetTileCursorActive(FALSE);
3145 game.restart_game_message = "Out of magic energy! Play it again?";
3148 if (Request("Out of magic energy! Play it again?",
3149 REQ_ASK | REQ_STAY_CLOSED))
3155 game_status = MAINMENU;
3164 element = laser.dest_element;
3167 if (element != Tile[ELX][ELY])
3169 printf("element == %d, Tile[ELX][ELY] == %d\n",
3170 element, Tile[ELX][ELY]);
3174 if (!laser.overloaded && laser.overload_value == 0 &&
3175 element != EL_BOMB &&
3176 element != EL_MINE &&
3177 element != EL_BALL_GRAY &&
3178 element != EL_BLOCK_STONE &&
3179 element != EL_BLOCK_WOOD &&
3180 element != EL_FUSE_ON &&
3181 element != EL_FUEL_FULL &&
3182 !IS_WALL_ICE(element) &&
3183 !IS_WALL_AMOEBA(element))
3186 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3187 (!laser.overloaded && laser.overload_value > 0)) &&
3188 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3190 if (laser.overloaded)
3191 laser.overload_value++;
3193 laser.overload_value--;
3195 if (game_mm.cheat_no_overload)
3197 laser.overloaded = FALSE;
3198 laser.overload_value = 0;
3201 game_mm.laser_overload_value = laser.overload_value;
3203 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3205 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3206 int color_down = 0xFF - color_up;
3209 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3210 (15 - (laser.overload_value / 6)) * color_scale);
3213 GetPixelFromRGB(window,
3214 (native_mm_level.laser_red ? 0xFF : color_up),
3215 (native_mm_level.laser_green ? color_down : 0x00),
3216 (native_mm_level.laser_blue ? color_down : 0x00));
3218 DrawLaser(0, DL_LASER_ENABLED);
3221 if (!laser.overloaded)
3222 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3223 else if (setup.sound_loops)
3224 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3226 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3228 if (laser.overloaded)
3231 BlitBitmap(pix[PIX_DOOR], drawto,
3232 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3233 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3234 - laser.overload_value,
3235 OVERLOAD_XSIZE, laser.overload_value,
3236 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3237 - laser.overload_value);
3239 redraw_mask |= REDRAW_DOOR_1;
3244 BlitBitmap(pix[PIX_DOOR], drawto,
3245 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3246 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3247 DX_OVERLOAD, DY_OVERLOAD);
3249 redraw_mask |= REDRAW_DOOR_1;
3252 if (laser.overload_value == MAX_LASER_OVERLOAD)
3256 UpdateAndDisplayGameControlValues();
3258 for (i = 15; i >= 0; i--)
3261 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3264 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3266 DrawLaser(0, DL_LASER_ENABLED);
3268 Delay_WithScreenUpdates(50);
3271 DrawLaser(0, DL_LASER_DISABLED);
3273 game_mm.game_over = TRUE;
3274 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3276 SetTileCursorActive(FALSE);
3278 game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?";
3281 if (Request("Magic spell hit Mc Duffin! Play it again?",
3282 REQ_ASK | REQ_STAY_CLOSED))
3288 game_status = MAINMENU;
3302 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3304 if (game_mm.cheat_no_explosion)
3308 laser.num_damages--;
3309 DrawLaser(0, DL_LASER_DISABLED);
3310 laser.num_edges = 0;
3315 laser.dest_element = EL_EXPLODING_OPAQUE;
3319 laser.num_damages--;
3320 DrawLaser(0, DL_LASER_DISABLED);
3322 laser.num_edges = 0;
3323 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3325 if (Request("Bomb killed Mc Duffin! Play it again?",
3326 REQ_ASK | REQ_STAY_CLOSED))
3332 game_status = MAINMENU;
3340 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3342 laser.fuse_off = TRUE;
3346 DrawLaser(0, DL_LASER_DISABLED);
3347 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3350 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3352 static int new_elements[] =
3355 EL_MIRROR_FIXED_START,
3357 EL_POLAR_CROSS_START,
3363 int num_new_elements = sizeof(new_elements) / sizeof(int);
3364 int new_element = new_elements[RND(num_new_elements)];
3366 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3367 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3369 // !!! CHECK AGAIN: Laser on Polarizer !!!
3380 element = EL_MIRROR_START + RND(16);
3386 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3393 element = (rnd == 0 ? EL_FUSE_ON :
3394 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3395 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3396 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3397 EL_MIRROR_FIXED_START + rnd - 25);
3402 graphic = el2gfx(element);
3404 for (i = 0; i < 50; i++)
3410 BlitBitmap(pix[PIX_BACK], drawto,
3411 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3412 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3413 SX + ELX * TILEX + x,
3414 SY + ELY * TILEY + y);
3416 MarkTileDirty(ELX, ELY);
3419 DrawLaser(0, DL_LASER_ENABLED);
3421 Delay_WithScreenUpdates(50);
3424 Tile[ELX][ELY] = element;
3425 DrawField_MM(ELX, ELY);
3428 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3431 // above stuff: GRAY BALL -> PRISM !!!
3433 LX = ELX * TILEX + 14;
3434 LY = ELY * TILEY + 14;
3435 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3442 laser.num_edges -= 2;
3443 laser.num_damages--;
3447 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3448 if (laser.damage[i].is_mirror)
3452 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3454 DrawLaser(0, DL_LASER_DISABLED);
3456 DrawLaser(0, DL_LASER_DISABLED);
3462 printf("TEST ELEMENT: %d\n", Tile[0][0]);
3469 if (IS_WALL_ICE(element) && CT > 50)
3471 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3474 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3475 Store[ELX][ELY] = EL_WALL_ICE;
3476 Store2[ELX][ELY] = laser.wall_mask;
3478 laser.dest_element = Tile[ELX][ELY];
3483 for (i = 0; i < 5; i++)
3489 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3493 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3495 Delay_WithScreenUpdates(100);
3498 if (Tile[ELX][ELY] == EL_WALL_ICE)
3499 Tile[ELX][ELY] = EL_EMPTY;
3503 LX = laser.edge[laser.num_edges].x - cSX2;
3504 LY = laser.edge[laser.num_edges].y - cSY2;
3507 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3508 if (laser.damage[i].is_mirror)
3512 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3514 DrawLaser(0, DL_LASER_DISABLED);
3521 if (IS_WALL_AMOEBA(element) && CT > 60)
3523 int k1, k2, k3, dx, dy, de, dm;
3524 int element2 = Tile[ELX][ELY];
3526 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3529 for (i = laser.num_damages - 1; i >= 0; i--)
3530 if (laser.damage[i].is_mirror)
3533 r = laser.num_edges;
3534 d = laser.num_damages;
3541 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3544 DrawLaser(0, DL_LASER_ENABLED);
3547 x = laser.damage[k1].x;
3548 y = laser.damage[k1].y;
3553 for (i = 0; i < 4; i++)
3555 if (laser.wall_mask & (1 << i))
3557 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3558 cSY + ELY * TILEY + 31 * (i / 2)))
3561 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3562 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3569 for (i = 0; i < 4; i++)
3571 if (laser.wall_mask & (1 << i))
3573 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3574 cSY + ELY * TILEY + 31 * (i / 2)))
3581 if (laser.num_beamers > 0 ||
3582 k1 < 1 || k2 < 4 || k3 < 4 ||
3583 CheckLaserPixel(cSX + ELX * TILEX + 14,
3584 cSY + ELY * TILEY + 14))
3586 laser.num_edges = r;
3587 laser.num_damages = d;
3589 DrawLaser(0, DL_LASER_DISABLED);
3592 Tile[ELX][ELY] = element | laser.wall_mask;
3596 de = Tile[ELX][ELY];
3597 dm = laser.wall_mask;
3601 int x = ELX, y = ELY;
3602 int wall_mask = laser.wall_mask;
3605 DrawLaser(0, DL_LASER_ENABLED);
3607 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3609 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3610 Store[x][y] = EL_WALL_AMOEBA;
3611 Store2[x][y] = wall_mask;
3617 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3619 DrawLaser(0, DL_LASER_ENABLED);
3621 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3623 for (i = 4; i >= 0; i--)
3625 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3628 Delay_WithScreenUpdates(20);
3631 DrawLaser(0, DL_LASER_ENABLED);
3636 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3637 laser.stops_inside_element && CT > native_mm_level.time_block)
3642 if (ABS(XS) > ABS(YS))
3649 for (i = 0; i < 4; i++)
3656 x = ELX + Step[k * 4].x;
3657 y = ELY + Step[k * 4].y;
3659 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3662 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3670 laser.overloaded = (element == EL_BLOCK_STONE);
3675 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3678 Tile[x][y] = element;
3680 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3683 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3685 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3686 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3694 if (element == EL_FUEL_FULL && CT > 10)
3696 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3699 BlitBitmap(pix[PIX_DOOR], drawto,
3700 DOOR_GFX_PAGEX4 + XX_ENERGY,
3701 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3702 ENERGY_XSIZE, i, DX_ENERGY,
3703 DY_ENERGY + ENERGY_YSIZE - i);
3706 redraw_mask |= REDRAW_DOOR_1;
3709 Delay_WithScreenUpdates(20);
3712 game_mm.energy_left = MAX_LASER_ENERGY;
3713 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3714 DrawField_MM(ELX, ELY);
3716 DrawLaser(0, DL_LASER_ENABLED);
3724 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3726 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3727 boolean button_released = (action.button == MB_RELEASED);
3729 GameActions_MM_Ext(action, warp_mode);
3731 CheckSingleStepMode_MM(element_clicked, button_released);
3734 void MovePacMen(void)
3736 int mx, my, ox, oy, nx, ny;
3740 if (++pacman_nr >= game_mm.num_pacman)
3743 game_mm.pacman[pacman_nr].dir--;
3745 for (l = 1; l < 5; l++)
3747 game_mm.pacman[pacman_nr].dir++;
3749 if (game_mm.pacman[pacman_nr].dir > 4)
3750 game_mm.pacman[pacman_nr].dir = 1;
3752 if (game_mm.pacman[pacman_nr].dir % 2)
3755 my = game_mm.pacman[pacman_nr].dir - 2;
3760 mx = 3 - game_mm.pacman[pacman_nr].dir;
3763 ox = game_mm.pacman[pacman_nr].x;
3764 oy = game_mm.pacman[pacman_nr].y;
3767 element = Tile[nx][ny];
3769 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3772 if (!IS_EATABLE4PACMAN(element))
3775 if (ObjHit(nx, ny, HIT_POS_CENTER))
3778 Tile[ox][oy] = EL_EMPTY;
3780 EL_PACMAN_RIGHT - 1 +
3781 (game_mm.pacman[pacman_nr].dir - 1 +
3782 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3784 game_mm.pacman[pacman_nr].x = nx;
3785 game_mm.pacman[pacman_nr].y = ny;
3787 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3789 if (element != EL_EMPTY)
3791 int graphic = el2gfx(Tile[nx][ny]);
3796 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3799 ox = cSX + ox * TILEX;
3800 oy = cSY + oy * TILEY;
3802 for (i = 1; i < 33; i += 2)
3803 BlitBitmap(bitmap, window,
3804 src_x, src_y, TILEX, TILEY,
3805 ox + i * mx, oy + i * my);
3806 Ct = Ct + FrameCounter - CT;
3809 DrawField_MM(nx, ny);
3812 if (!laser.fuse_off)
3814 DrawLaser(0, DL_LASER_ENABLED);
3816 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3818 AddDamagedField(nx, ny);
3820 laser.damage[laser.num_damages - 1].edge = 0;
3824 if (element == EL_BOMB)
3825 DeletePacMan(nx, ny);
3827 if (IS_WALL_AMOEBA(element) &&
3828 (LX + 2 * XS) / TILEX == nx &&
3829 (LY + 2 * YS) / TILEY == ny)
3839 void GameWon_MM(void)
3842 boolean raise_level = FALSE;
3845 if (local_player->MovPos)
3848 local_player->LevelSolved = FALSE;
3851 if (game_mm.energy_left)
3853 if (setup.sound_loops)
3854 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3855 SND_CTRL_PLAY_LOOP);
3857 while (game_mm.energy_left > 0)
3859 if (!setup.sound_loops)
3860 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3863 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3864 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3869 game_mm.energy_left--;
3870 if (game_mm.energy_left >= 0)
3873 BlitBitmap(pix[PIX_DOOR], drawto,
3874 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3875 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3876 DX_ENERGY, DY_ENERGY);
3878 redraw_mask |= REDRAW_DOOR_1;
3882 Delay_WithScreenUpdates(10);
3885 if (setup.sound_loops)
3886 StopSound(SND_SIRR);
3888 else if (native_mm_level.time == 0) // level without time limit
3890 if (setup.sound_loops)
3891 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3892 SND_CTRL_PLAY_LOOP);
3894 while (TimePlayed < 999)
3896 if (!setup.sound_loops)
3897 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3898 if (TimePlayed < 999 && !(TimePlayed % 10))
3899 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3900 if (TimePlayed < 900 && !(TimePlayed % 10))
3906 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3910 Delay_WithScreenUpdates(10);
3913 if (setup.sound_loops)
3914 StopSound(SND_SIRR);
3917 CloseDoor(DOOR_CLOSE_1);
3919 Request("Level solved!", REQ_CONFIRM);
3921 if (level_nr == leveldir_current->handicap_level)
3923 leveldir_current->handicap_level++;
3924 SaveLevelSetup_SeriesInfo();
3927 if (level_editor_test_game)
3928 game_mm.score = -1; // no highscore when playing from editor
3929 else if (level_nr < leveldir_current->last_level)
3930 raise_level = TRUE; // advance to next level
3932 if ((hi_pos = NewHiScore_MM()) >= 0)
3934 game_status = HALLOFFAME;
3936 // DrawHallOfFame(hi_pos);
3943 game_status = MAINMENU;
3954 int NewHiScore_MM(void)
3959 // LoadScore(level_nr);
3961 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3962 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3965 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3967 if (game_mm.score > highscore[k].Score)
3969 // player has made it to the hall of fame
3971 if (k < MAX_SCORE_ENTRIES - 1)
3973 int m = MAX_SCORE_ENTRIES - 1;
3976 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3977 if (!strcmp(setup.player_name, highscore[l].Name))
3979 if (m == k) // player's new highscore overwrites his old one
3983 for (l = m; l>k; l--)
3985 strcpy(highscore[l].Name, highscore[l - 1].Name);
3986 highscore[l].Score = highscore[l - 1].Score;
3993 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3994 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3995 highscore[k].Score = game_mm.score;
4002 else if (!strncmp(setup.player_name, highscore[k].Name,
4003 MAX_PLAYER_NAME_LEN))
4004 break; // player already there with a higher score
4009 // if (position >= 0)
4010 // SaveScore(level_nr);
4015 static void InitMovingField_MM(int x, int y, int direction)
4017 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4018 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4020 MovDir[x][y] = direction;
4021 MovDir[newx][newy] = direction;
4023 if (Tile[newx][newy] == EL_EMPTY)
4024 Tile[newx][newy] = EL_BLOCKED;
4027 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4029 int direction = MovDir[x][y];
4030 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4031 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4037 static void Blocked2Moving_MM(int x, int y,
4038 int *comes_from_x, int *comes_from_y)
4040 int oldx = x, oldy = y;
4041 int direction = MovDir[x][y];
4043 if (direction == MV_LEFT)
4045 else if (direction == MV_RIGHT)
4047 else if (direction == MV_UP)
4049 else if (direction == MV_DOWN)
4052 *comes_from_x = oldx;
4053 *comes_from_y = oldy;
4056 static int MovingOrBlocked2Element_MM(int x, int y)
4058 int element = Tile[x][y];
4060 if (element == EL_BLOCKED)
4064 Blocked2Moving_MM(x, y, &oldx, &oldy);
4066 return Tile[oldx][oldy];
4073 static void RemoveField(int x, int y)
4075 Tile[x][y] = EL_EMPTY;
4082 static void RemoveMovingField_MM(int x, int y)
4084 int oldx = x, oldy = y, newx = x, newy = y;
4086 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4089 if (IS_MOVING(x, y))
4091 Moving2Blocked_MM(x, y, &newx, &newy);
4092 if (Tile[newx][newy] != EL_BLOCKED)
4095 else if (Tile[x][y] == EL_BLOCKED)
4097 Blocked2Moving_MM(x, y, &oldx, &oldy);
4098 if (!IS_MOVING(oldx, oldy))
4102 Tile[oldx][oldy] = EL_EMPTY;
4103 Tile[newx][newy] = EL_EMPTY;
4104 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4105 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4107 DrawLevelField_MM(oldx, oldy);
4108 DrawLevelField_MM(newx, newy);
4111 void PlaySoundLevel(int x, int y, int sound_nr)
4113 int sx = SCREENX(x), sy = SCREENY(y);
4115 int silence_distance = 8;
4117 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4118 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4121 if (!IN_LEV_FIELD(x, y) ||
4122 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4123 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4126 volume = SOUND_MAX_VOLUME;
4129 stereo = (sx - SCR_FIELDX/2) * 12;
4131 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4132 if (stereo > SOUND_MAX_RIGHT)
4133 stereo = SOUND_MAX_RIGHT;
4134 if (stereo < SOUND_MAX_LEFT)
4135 stereo = SOUND_MAX_LEFT;
4138 if (!IN_SCR_FIELD(sx, sy))
4140 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4141 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4143 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4146 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4149 static void RaiseScore_MM(int value)
4151 game_mm.score += value;
4154 void RaiseScoreElement_MM(int element)
4159 case EL_PACMAN_RIGHT:
4161 case EL_PACMAN_LEFT:
4162 case EL_PACMAN_DOWN:
4163 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4167 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4172 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4176 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4185 // ----------------------------------------------------------------------------
4186 // Mirror Magic game engine snapshot handling functions
4187 // ----------------------------------------------------------------------------
4189 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4193 engine_snapshot_mm.game_mm = game_mm;
4194 engine_snapshot_mm.laser = laser;
4196 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4198 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4200 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4201 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4202 engine_snapshot_mm.Box[x][y] = Box[x][y];
4203 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4204 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4208 engine_snapshot_mm.LX = LX;
4209 engine_snapshot_mm.LY = LY;
4210 engine_snapshot_mm.XS = XS;
4211 engine_snapshot_mm.YS = YS;
4212 engine_snapshot_mm.ELX = ELX;
4213 engine_snapshot_mm.ELY = ELY;
4214 engine_snapshot_mm.CT = CT;
4215 engine_snapshot_mm.Ct = Ct;
4217 engine_snapshot_mm.last_LX = last_LX;
4218 engine_snapshot_mm.last_LY = last_LY;
4219 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4220 engine_snapshot_mm.hold_x = hold_x;
4221 engine_snapshot_mm.hold_y = hold_y;
4222 engine_snapshot_mm.pacman_nr = pacman_nr;
4224 engine_snapshot_mm.rotate_delay = rotate_delay;
4225 engine_snapshot_mm.pacman_delay = pacman_delay;
4226 engine_snapshot_mm.energy_delay = energy_delay;
4227 engine_snapshot_mm.overload_delay = overload_delay;
4230 void LoadEngineSnapshotValues_MM(void)
4234 // stored engine snapshot buffers already restored at this point
4236 game_mm = engine_snapshot_mm.game_mm;
4237 laser = engine_snapshot_mm.laser;
4239 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4241 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4243 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4244 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4245 Box[x][y] = engine_snapshot_mm.Box[x][y];
4246 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4247 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4251 LX = engine_snapshot_mm.LX;
4252 LY = engine_snapshot_mm.LY;
4253 XS = engine_snapshot_mm.XS;
4254 YS = engine_snapshot_mm.YS;
4255 ELX = engine_snapshot_mm.ELX;
4256 ELY = engine_snapshot_mm.ELY;
4257 CT = engine_snapshot_mm.CT;
4258 Ct = engine_snapshot_mm.Ct;
4260 last_LX = engine_snapshot_mm.last_LX;
4261 last_LY = engine_snapshot_mm.last_LY;
4262 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4263 hold_x = engine_snapshot_mm.hold_x;
4264 hold_y = engine_snapshot_mm.hold_y;
4265 pacman_nr = engine_snapshot_mm.pacman_nr;
4267 rotate_delay = engine_snapshot_mm.rotate_delay;
4268 pacman_delay = engine_snapshot_mm.pacman_delay;
4269 energy_delay = engine_snapshot_mm.energy_delay;
4270 overload_delay = engine_snapshot_mm.overload_delay;
4272 RedrawPlayfield_MM();
4275 static int getAngleFromTouchDelta(int dx, int dy, int base)
4277 double pi = 3.141592653;
4278 double rad = atan2((double)-dy, (double)dx);
4279 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4280 double deg = rad2 * 180.0 / pi;
4282 return (int)(deg * base / 360.0 + 0.5) % base;
4285 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4287 // calculate start (source) position to be at the middle of the tile
4288 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4289 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4290 int dx = dst_mx - src_mx;
4291 int dy = dst_my - src_my;
4300 if (!IN_LEV_FIELD(x, y))
4303 element = Tile[x][y];
4305 if (!IS_MCDUFFIN(element) &&
4306 !IS_MIRROR(element) &&
4307 !IS_BEAMER(element) &&
4308 !IS_POLAR(element) &&
4309 !IS_POLAR_CROSS(element) &&
4310 !IS_DF_MIRROR(element))
4313 angle_old = get_element_angle(element);
4315 if (IS_MCDUFFIN(element))
4317 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4318 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4319 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4320 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4323 else if (IS_MIRROR(element) ||
4324 IS_DF_MIRROR(element))
4326 for (i = 0; i < laser.num_damages; i++)
4328 if (laser.damage[i].x == x &&
4329 laser.damage[i].y == y &&
4330 ObjHit(x, y, HIT_POS_CENTER))
4332 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4333 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4340 if (angle_new == -1)
4342 if (IS_MIRROR(element) ||
4343 IS_DF_MIRROR(element) ||
4347 if (IS_POLAR_CROSS(element))
4350 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4353 button = (angle_new == angle_old ? 0 :
4354 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4355 MB_LEFTBUTTON : MB_RIGHTBUTTON);