1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
32 // special positions in the game control window (relative to control window)
41 #define XX_OVERLOAD 60
42 #define YY_OVERLOAD YY_ENERGY
44 // special positions in the game control window (relative to main window)
45 #define DX_LEVEL (DX + XX_LEVEL)
46 #define DY_LEVEL (DY + YY_LEVEL)
47 #define DX_KETTLES (DX + XX_KETTLES)
48 #define DY_KETTLES (DY + YY_KETTLES)
49 #define DX_SCORE (DX + XX_SCORE)
50 #define DY_SCORE (DY + YY_SCORE)
51 #define DX_ENERGY (DX + XX_ENERGY)
52 #define DY_ENERGY (DY + YY_ENERGY)
53 #define DX_OVERLOAD (DX + XX_OVERLOAD)
54 #define DY_OVERLOAD (DY + YY_OVERLOAD)
56 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
57 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
59 // game button identifiers
60 #define GAME_CTRL_ID_LEFT 0
61 #define GAME_CTRL_ID_MIDDLE 1
62 #define GAME_CTRL_ID_RIGHT 2
64 #define NUM_GAME_BUTTONS 3
66 // values for DrawLaser()
67 #define DL_LASER_DISABLED 0
68 #define DL_LASER_ENABLED 1
70 // values for 'click_delay_value' in ClickElement()
71 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
72 #define CLICK_DELAY 6 // delay (frames) for pressed butten
74 #define AUTO_ROTATE_DELAY CLICK_DELAY
75 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
76 #define NUM_INIT_CYCLE_STEPS 16
77 #define PACMAN_MOVE_DELAY 12
78 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
79 #define HEALTH_DEC_DELAY 3
80 #define HEALTH_INC_DELAY 9
81 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
83 #define BEGIN_NO_HEADLESS \
85 boolean last_headless = program.headless; \
87 program.headless = FALSE; \
89 #define END_NO_HEADLESS \
90 program.headless = last_headless; \
93 // forward declaration for internal use
94 static int MovingOrBlocked2Element_MM(int, int);
95 static void Bang_MM(int, int);
96 static void RaiseScore_MM(int);
97 static void RaiseScoreElement_MM(int);
98 static void RemoveMovingField_MM(int, int);
99 static void InitMovingField_MM(int, int, int);
100 static void ContinueMoving_MM(int, int);
101 static void Moving2Blocked_MM(int, int, int *, int *);
103 // bitmap for laser beam detection
104 static Bitmap *laser_bitmap = NULL;
106 // variables for laser control
107 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
108 static int hold_x = -1, hold_y = -1;
110 // variables for pacman control
111 static int pacman_nr = -1;
113 // various game engine delay counters
114 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
115 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
116 static DelayCounter energy_delay = { ENERGY_DELAY };
117 static DelayCounter overload_delay = { 0 };
119 // element 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;
640 rotate_delay.count = 0;
641 pacman_delay.count = 0;
642 energy_delay.count = 0;
643 overload_delay.count = 0;
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 DelayCounter click_delay = { CLICK_DELAY };
2713 static boolean new_button = TRUE;
2714 boolean element_clicked = FALSE;
2719 // initialize static variables
2720 click_delay.count = 0;
2721 click_delay.value = CLICK_DELAY;
2727 // do not rotate objects hit by the laser after the game was solved
2728 if (game_mm.level_solved && Hit[x][y])
2731 if (button == MB_RELEASED)
2734 click_delay.value = CLICK_DELAY;
2736 // release eventually hold auto-rotating mirror
2737 RotateMirror(x, y, MB_RELEASED);
2742 if (!FrameReached(&click_delay) && !new_button)
2745 if (button == MB_MIDDLEBUTTON) // middle button has no function
2748 if (!IN_LEV_FIELD(x, y))
2751 if (Tile[x][y] == EL_EMPTY)
2754 element = Tile[x][y];
2756 if (IS_MIRROR(element) ||
2757 IS_BEAMER(element) ||
2758 IS_POLAR(element) ||
2759 IS_POLAR_CROSS(element) ||
2760 IS_DF_MIRROR(element) ||
2761 IS_DF_MIRROR_AUTO(element))
2763 RotateMirror(x, y, button);
2765 element_clicked = TRUE;
2767 else if (IS_MCDUFFIN(element))
2769 if (!laser.fuse_off)
2771 DrawLaser(0, DL_LASER_DISABLED);
2778 element = get_rotated_element(element, BUTTON_ROTATION(button));
2779 laser.start_angle = get_element_angle(element);
2783 Tile[x][y] = element;
2790 if (!laser.fuse_off)
2793 element_clicked = TRUE;
2795 else if (element == EL_FUSE_ON && laser.fuse_off)
2797 if (x != laser.fuse_x || y != laser.fuse_y)
2800 laser.fuse_off = FALSE;
2801 laser.fuse_x = laser.fuse_y = -1;
2803 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2806 element_clicked = TRUE;
2808 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2810 laser.fuse_off = TRUE;
2813 laser.overloaded = FALSE;
2815 DrawLaser(0, DL_LASER_DISABLED);
2816 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2818 element_clicked = TRUE;
2820 else if (element == EL_LIGHTBALL)
2823 RaiseScoreElement_MM(element);
2824 DrawLaser(0, DL_LASER_ENABLED);
2826 element_clicked = TRUE;
2829 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2832 return element_clicked;
2835 void RotateMirror(int x, int y, int button)
2837 if (button == MB_RELEASED)
2839 // release eventually hold auto-rotating mirror
2846 if (IS_MIRROR(Tile[x][y]) ||
2847 IS_POLAR_CROSS(Tile[x][y]) ||
2848 IS_POLAR(Tile[x][y]) ||
2849 IS_BEAMER(Tile[x][y]) ||
2850 IS_DF_MIRROR(Tile[x][y]) ||
2851 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2852 IS_GRID_WOOD_AUTO(Tile[x][y]))
2854 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2856 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2858 if (button == MB_LEFTBUTTON)
2860 // left mouse button only for manual adjustment, no auto-rotating;
2861 // freeze mirror for until mouse button released
2865 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2867 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2871 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2873 int edge = Hit[x][y];
2879 DrawLaser(edge - 1, DL_LASER_DISABLED);
2883 else if (ObjHit(x, y, HIT_POS_CENTER))
2885 int edge = Hit[x][y];
2889 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2894 DrawLaser(edge - 1, DL_LASER_DISABLED);
2901 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2906 if ((IS_BEAMER(Tile[x][y]) ||
2907 IS_POLAR(Tile[x][y]) ||
2908 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2912 if (IS_BEAMER(Tile[x][y]))
2915 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2916 LX, LY, laser.beamer_edge, laser.beamer[1].num);
2926 DrawLaser(0, DL_LASER_ENABLED);
2930 static void AutoRotateMirrors(void)
2934 if (!FrameReached(&rotate_delay))
2937 for (x = 0; x < lev_fieldx; x++)
2939 for (y = 0; y < lev_fieldy; y++)
2941 int element = Tile[x][y];
2943 // do not rotate objects hit by the laser after the game was solved
2944 if (game_mm.level_solved && Hit[x][y])
2947 if (IS_DF_MIRROR_AUTO(element) ||
2948 IS_GRID_WOOD_AUTO(element) ||
2949 IS_GRID_STEEL_AUTO(element) ||
2950 element == EL_REFRACTOR)
2951 RotateMirror(x, y, MB_RIGHTBUTTON);
2956 boolean ObjHit(int obx, int oby, int bits)
2963 if (bits & HIT_POS_CENTER)
2965 if (CheckLaserPixel(cSX + obx + 15,
2970 if (bits & HIT_POS_EDGE)
2972 for (i = 0; i < 4; i++)
2973 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
2974 cSY + oby + 31 * (i / 2)))
2978 if (bits & HIT_POS_BETWEEN)
2980 for (i = 0; i < 4; i++)
2981 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
2982 cSY + 4 + oby + 22 * (i / 2)))
2989 void DeletePacMan(int px, int py)
2995 if (game_mm.num_pacman <= 1)
2997 game_mm.num_pacman = 0;
3001 for (i = 0; i < game_mm.num_pacman; i++)
3002 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3005 game_mm.num_pacman--;
3007 for (j = i; j < game_mm.num_pacman; j++)
3009 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3010 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3011 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3015 void ColorCycling(void)
3017 static int CC, Cc = 0;
3019 static int color, old = 0xF00, new = 0x010, mult = 1;
3020 static unsigned short red, green, blue;
3022 if (color_status == STATIC_COLORS)
3027 if (CC < Cc || CC > Cc + 2)
3031 color = old + new * mult;
3037 if (ABS(mult) == 16)
3047 red = 0x0e00 * ((color & 0xF00) >> 8);
3048 green = 0x0e00 * ((color & 0x0F0) >> 4);
3049 blue = 0x0e00 * ((color & 0x00F));
3050 SetRGB(pen_magicolor[0], red, green, blue);
3052 red = 0x1111 * ((color & 0xF00) >> 8);
3053 green = 0x1111 * ((color & 0x0F0) >> 4);
3054 blue = 0x1111 * ((color & 0x00F));
3055 SetRGB(pen_magicolor[1], red, green, blue);
3059 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3066 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3069 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3071 element = Tile[x][y];
3073 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3074 StartMoving_MM(x, y);
3075 else if (IS_MOVING(x, y))
3076 ContinueMoving_MM(x, y);
3077 else if (IS_EXPLODING(element))
3078 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3079 else if (element == EL_EXIT_OPENING)
3081 else if (element == EL_GRAY_BALL_OPENING)
3082 OpenSurpriseBall(x, y);
3083 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3085 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3089 AutoRotateMirrors();
3092 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3094 // redraw after Explode_MM() ...
3096 DrawLaser(0, DL_LASER_ENABLED);
3097 laser.redraw = FALSE;
3102 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3106 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3108 DrawLaser(0, DL_LASER_DISABLED);
3113 if (FrameReached(&energy_delay))
3115 if (game_mm.energy_left > 0)
3117 game_mm.energy_left--;
3119 redraw_mask |= REDRAW_DOOR_1;
3121 else if (game.time_limit && !game_mm.game_over)
3125 for (i = 15; i >= 0; i--)
3128 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3130 pen_ray = GetPixelFromRGB(window,
3131 native_mm_level.laser_red * 0x11 * i,
3132 native_mm_level.laser_green * 0x11 * i,
3133 native_mm_level.laser_blue * 0x11 * i);
3135 DrawLaser(0, DL_LASER_ENABLED);
3137 Delay_WithScreenUpdates(50);
3140 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3145 DrawLaser(0, DL_LASER_DISABLED);
3146 game_mm.game_over = TRUE;
3147 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3149 SetTileCursorActive(FALSE);
3151 game.restart_game_message = "Out of magic energy! Play it again?";
3157 element = laser.dest_element;
3160 if (element != Tile[ELX][ELY])
3162 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3163 element, Tile[ELX][ELY]);
3167 if (!laser.overloaded && laser.overload_value == 0 &&
3168 element != EL_BOMB &&
3169 element != EL_MINE &&
3170 element != EL_BALL_GRAY &&
3171 element != EL_BLOCK_STONE &&
3172 element != EL_BLOCK_WOOD &&
3173 element != EL_FUSE_ON &&
3174 element != EL_FUEL_FULL &&
3175 !IS_WALL_ICE(element) &&
3176 !IS_WALL_AMOEBA(element))
3179 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3181 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3182 (!laser.overloaded && laser.overload_value > 0)) &&
3183 FrameReached(&overload_delay))
3185 if (laser.overloaded)
3186 laser.overload_value++;
3188 laser.overload_value--;
3190 if (game_mm.cheat_no_overload)
3192 laser.overloaded = FALSE;
3193 laser.overload_value = 0;
3196 game_mm.laser_overload_value = laser.overload_value;
3198 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3200 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3201 int color_down = 0xFF - color_up;
3204 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3205 (15 - (laser.overload_value / 6)) * color_scale);
3208 GetPixelFromRGB(window,
3209 (native_mm_level.laser_red ? 0xFF : color_up),
3210 (native_mm_level.laser_green ? color_down : 0x00),
3211 (native_mm_level.laser_blue ? color_down : 0x00));
3213 DrawLaser(0, DL_LASER_ENABLED);
3216 if (!laser.overloaded)
3217 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3218 else if (setup.sound_loops)
3219 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3221 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3223 if (laser.overloaded)
3226 BlitBitmap(pix[PIX_DOOR], drawto,
3227 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3228 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3229 - laser.overload_value,
3230 OVERLOAD_XSIZE, laser.overload_value,
3231 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3232 - laser.overload_value);
3234 redraw_mask |= REDRAW_DOOR_1;
3239 BlitBitmap(pix[PIX_DOOR], drawto,
3240 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3241 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3242 DX_OVERLOAD, DY_OVERLOAD);
3244 redraw_mask |= REDRAW_DOOR_1;
3247 if (laser.overload_value == MAX_LASER_OVERLOAD)
3251 UpdateAndDisplayGameControlValues();
3253 for (i = 15; i >= 0; i--)
3256 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3259 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3261 DrawLaser(0, DL_LASER_ENABLED);
3263 Delay_WithScreenUpdates(50);
3266 DrawLaser(0, DL_LASER_DISABLED);
3268 game_mm.game_over = TRUE;
3269 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3271 SetTileCursorActive(FALSE);
3273 game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?";
3284 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3286 if (game_mm.cheat_no_explosion)
3291 laser.dest_element = EL_EXPLODING_OPAQUE;
3296 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3298 laser.fuse_off = TRUE;
3302 DrawLaser(0, DL_LASER_DISABLED);
3303 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3306 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3308 static int new_elements[] =
3311 EL_MIRROR_FIXED_START,
3313 EL_POLAR_CROSS_START,
3319 int num_new_elements = sizeof(new_elements) / sizeof(int);
3320 int new_element = new_elements[RND(num_new_elements)];
3322 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3323 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3325 // !!! CHECK AGAIN: Laser on Polarizer !!!
3336 element = EL_MIRROR_START + RND(16);
3342 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3349 element = (rnd == 0 ? EL_FUSE_ON :
3350 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3351 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3352 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3353 EL_MIRROR_FIXED_START + rnd - 25);
3358 graphic = el2gfx(element);
3360 for (i = 0; i < 50; i++)
3366 BlitBitmap(pix[PIX_BACK], drawto,
3367 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3368 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3369 SX + ELX * TILEX + x,
3370 SY + ELY * TILEY + y);
3372 MarkTileDirty(ELX, ELY);
3375 DrawLaser(0, DL_LASER_ENABLED);
3377 Delay_WithScreenUpdates(50);
3380 Tile[ELX][ELY] = element;
3381 DrawField_MM(ELX, ELY);
3384 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3387 // above stuff: GRAY BALL -> PRISM !!!
3389 LX = ELX * TILEX + 14;
3390 LY = ELY * TILEY + 14;
3391 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3398 laser.num_edges -= 2;
3399 laser.num_damages--;
3403 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3404 if (laser.damage[i].is_mirror)
3408 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3410 DrawLaser(0, DL_LASER_DISABLED);
3412 DrawLaser(0, DL_LASER_DISABLED);
3421 if (IS_WALL_ICE(element) && CT > 50)
3423 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3426 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3427 Store[ELX][ELY] = EL_WALL_ICE;
3428 Store2[ELX][ELY] = laser.wall_mask;
3430 laser.dest_element = Tile[ELX][ELY];
3435 for (i = 0; i < 5; i++)
3441 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3445 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3447 Delay_WithScreenUpdates(100);
3450 if (Tile[ELX][ELY] == EL_WALL_ICE)
3451 Tile[ELX][ELY] = EL_EMPTY;
3455 LX = laser.edge[laser.num_edges].x - cSX2;
3456 LY = laser.edge[laser.num_edges].y - cSY2;
3459 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3460 if (laser.damage[i].is_mirror)
3464 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3466 DrawLaser(0, DL_LASER_DISABLED);
3473 if (IS_WALL_AMOEBA(element) && CT > 60)
3475 int k1, k2, k3, dx, dy, de, dm;
3476 int element2 = Tile[ELX][ELY];
3478 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3481 for (i = laser.num_damages - 1; i >= 0; i--)
3482 if (laser.damage[i].is_mirror)
3485 r = laser.num_edges;
3486 d = laser.num_damages;
3493 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3496 DrawLaser(0, DL_LASER_ENABLED);
3499 x = laser.damage[k1].x;
3500 y = laser.damage[k1].y;
3505 for (i = 0; i < 4; i++)
3507 if (laser.wall_mask & (1 << i))
3509 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3510 cSY + ELY * TILEY + 31 * (i / 2)))
3513 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3514 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3521 for (i = 0; i < 4; i++)
3523 if (laser.wall_mask & (1 << i))
3525 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3526 cSY + ELY * TILEY + 31 * (i / 2)))
3533 if (laser.num_beamers > 0 ||
3534 k1 < 1 || k2 < 4 || k3 < 4 ||
3535 CheckLaserPixel(cSX + ELX * TILEX + 14,
3536 cSY + ELY * TILEY + 14))
3538 laser.num_edges = r;
3539 laser.num_damages = d;
3541 DrawLaser(0, DL_LASER_DISABLED);
3544 Tile[ELX][ELY] = element | laser.wall_mask;
3548 de = Tile[ELX][ELY];
3549 dm = laser.wall_mask;
3553 int x = ELX, y = ELY;
3554 int wall_mask = laser.wall_mask;
3557 DrawLaser(0, DL_LASER_ENABLED);
3559 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3561 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3562 Store[x][y] = EL_WALL_AMOEBA;
3563 Store2[x][y] = wall_mask;
3569 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3571 DrawLaser(0, DL_LASER_ENABLED);
3573 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3575 for (i = 4; i >= 0; i--)
3577 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3580 Delay_WithScreenUpdates(20);
3583 DrawLaser(0, DL_LASER_ENABLED);
3588 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3589 laser.stops_inside_element && CT > native_mm_level.time_block)
3594 if (ABS(XS) > ABS(YS))
3601 for (i = 0; i < 4; i++)
3608 x = ELX + Step[k * 4].x;
3609 y = ELY + Step[k * 4].y;
3611 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3614 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3622 laser.overloaded = (element == EL_BLOCK_STONE);
3627 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3630 Tile[x][y] = element;
3632 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3635 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3637 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3638 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3646 if (element == EL_FUEL_FULL && CT > 10)
3648 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3651 BlitBitmap(pix[PIX_DOOR], drawto,
3652 DOOR_GFX_PAGEX4 + XX_ENERGY,
3653 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3654 ENERGY_XSIZE, i, DX_ENERGY,
3655 DY_ENERGY + ENERGY_YSIZE - i);
3658 redraw_mask |= REDRAW_DOOR_1;
3661 Delay_WithScreenUpdates(20);
3664 game_mm.energy_left = MAX_LASER_ENERGY;
3665 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3666 DrawField_MM(ELX, ELY);
3668 DrawLaser(0, DL_LASER_ENABLED);
3676 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3678 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3679 boolean button_released = (action.button == MB_RELEASED);
3681 GameActions_MM_Ext(action, warp_mode);
3683 CheckSingleStepMode_MM(element_clicked, button_released);
3686 void MovePacMen(void)
3688 int mx, my, ox, oy, nx, ny;
3692 if (++pacman_nr >= game_mm.num_pacman)
3695 game_mm.pacman[pacman_nr].dir--;
3697 for (l = 1; l < 5; l++)
3699 game_mm.pacman[pacman_nr].dir++;
3701 if (game_mm.pacman[pacman_nr].dir > 4)
3702 game_mm.pacman[pacman_nr].dir = 1;
3704 if (game_mm.pacman[pacman_nr].dir % 2)
3707 my = game_mm.pacman[pacman_nr].dir - 2;
3712 mx = 3 - game_mm.pacman[pacman_nr].dir;
3715 ox = game_mm.pacman[pacman_nr].x;
3716 oy = game_mm.pacman[pacman_nr].y;
3719 element = Tile[nx][ny];
3721 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3724 if (!IS_EATABLE4PACMAN(element))
3727 if (ObjHit(nx, ny, HIT_POS_CENTER))
3730 Tile[ox][oy] = EL_EMPTY;
3732 EL_PACMAN_RIGHT - 1 +
3733 (game_mm.pacman[pacman_nr].dir - 1 +
3734 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3736 game_mm.pacman[pacman_nr].x = nx;
3737 game_mm.pacman[pacman_nr].y = ny;
3739 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3741 if (element != EL_EMPTY)
3743 int graphic = el2gfx(Tile[nx][ny]);
3748 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3751 ox = cSX + ox * TILEX;
3752 oy = cSY + oy * TILEY;
3754 for (i = 1; i < 33; i += 2)
3755 BlitBitmap(bitmap, window,
3756 src_x, src_y, TILEX, TILEY,
3757 ox + i * mx, oy + i * my);
3758 Ct = Ct + FrameCounter - CT;
3761 DrawField_MM(nx, ny);
3764 if (!laser.fuse_off)
3766 DrawLaser(0, DL_LASER_ENABLED);
3768 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3770 AddDamagedField(nx, ny);
3772 laser.damage[laser.num_damages - 1].edge = 0;
3776 if (element == EL_BOMB)
3777 DeletePacMan(nx, ny);
3779 if (IS_WALL_AMOEBA(element) &&
3780 (LX + 2 * XS) / TILEX == nx &&
3781 (LY + 2 * YS) / TILEY == ny)
3791 static void InitMovingField_MM(int x, int y, int direction)
3793 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3794 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3796 MovDir[x][y] = direction;
3797 MovDir[newx][newy] = direction;
3799 if (Tile[newx][newy] == EL_EMPTY)
3800 Tile[newx][newy] = EL_BLOCKED;
3803 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3805 int direction = MovDir[x][y];
3806 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3807 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3813 static void Blocked2Moving_MM(int x, int y,
3814 int *comes_from_x, int *comes_from_y)
3816 int oldx = x, oldy = y;
3817 int direction = MovDir[x][y];
3819 if (direction == MV_LEFT)
3821 else if (direction == MV_RIGHT)
3823 else if (direction == MV_UP)
3825 else if (direction == MV_DOWN)
3828 *comes_from_x = oldx;
3829 *comes_from_y = oldy;
3832 static int MovingOrBlocked2Element_MM(int x, int y)
3834 int element = Tile[x][y];
3836 if (element == EL_BLOCKED)
3840 Blocked2Moving_MM(x, y, &oldx, &oldy);
3842 return Tile[oldx][oldy];
3849 static void RemoveField(int x, int y)
3851 Tile[x][y] = EL_EMPTY;
3858 static void RemoveMovingField_MM(int x, int y)
3860 int oldx = x, oldy = y, newx = x, newy = y;
3862 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3865 if (IS_MOVING(x, y))
3867 Moving2Blocked_MM(x, y, &newx, &newy);
3868 if (Tile[newx][newy] != EL_BLOCKED)
3871 else if (Tile[x][y] == EL_BLOCKED)
3873 Blocked2Moving_MM(x, y, &oldx, &oldy);
3874 if (!IS_MOVING(oldx, oldy))
3878 Tile[oldx][oldy] = EL_EMPTY;
3879 Tile[newx][newy] = EL_EMPTY;
3880 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3881 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3883 DrawLevelField_MM(oldx, oldy);
3884 DrawLevelField_MM(newx, newy);
3887 void PlaySoundLevel(int x, int y, int sound_nr)
3889 int sx = SCREENX(x), sy = SCREENY(y);
3891 int silence_distance = 8;
3893 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3894 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3897 if (!IN_LEV_FIELD(x, y) ||
3898 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3899 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3902 volume = SOUND_MAX_VOLUME;
3905 stereo = (sx - SCR_FIELDX/2) * 12;
3907 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3908 if (stereo > SOUND_MAX_RIGHT)
3909 stereo = SOUND_MAX_RIGHT;
3910 if (stereo < SOUND_MAX_LEFT)
3911 stereo = SOUND_MAX_LEFT;
3914 if (!IN_SCR_FIELD(sx, sy))
3916 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3917 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3919 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3922 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3925 static void RaiseScore_MM(int value)
3927 game_mm.score += value;
3930 void RaiseScoreElement_MM(int element)
3935 case EL_PACMAN_RIGHT:
3937 case EL_PACMAN_LEFT:
3938 case EL_PACMAN_DOWN:
3939 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3943 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3948 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3952 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3961 // ----------------------------------------------------------------------------
3962 // Mirror Magic game engine snapshot handling functions
3963 // ----------------------------------------------------------------------------
3965 void SaveEngineSnapshotValues_MM(ListNode **buffers)
3969 engine_snapshot_mm.game_mm = game_mm;
3970 engine_snapshot_mm.laser = laser;
3972 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
3974 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
3976 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
3977 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
3978 engine_snapshot_mm.Box[x][y] = Box[x][y];
3979 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
3980 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
3984 engine_snapshot_mm.LX = LX;
3985 engine_snapshot_mm.LY = LY;
3986 engine_snapshot_mm.XS = XS;
3987 engine_snapshot_mm.YS = YS;
3988 engine_snapshot_mm.ELX = ELX;
3989 engine_snapshot_mm.ELY = ELY;
3990 engine_snapshot_mm.CT = CT;
3991 engine_snapshot_mm.Ct = Ct;
3993 engine_snapshot_mm.last_LX = last_LX;
3994 engine_snapshot_mm.last_LY = last_LY;
3995 engine_snapshot_mm.last_hit_mask = last_hit_mask;
3996 engine_snapshot_mm.hold_x = hold_x;
3997 engine_snapshot_mm.hold_y = hold_y;
3998 engine_snapshot_mm.pacman_nr = pacman_nr;
4000 engine_snapshot_mm.rotate_delay = rotate_delay;
4001 engine_snapshot_mm.pacman_delay = pacman_delay;
4002 engine_snapshot_mm.energy_delay = energy_delay;
4003 engine_snapshot_mm.overload_delay = overload_delay;
4006 void LoadEngineSnapshotValues_MM(void)
4010 // stored engine snapshot buffers already restored at this point
4012 game_mm = engine_snapshot_mm.game_mm;
4013 laser = engine_snapshot_mm.laser;
4015 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4017 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4019 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4020 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4021 Box[x][y] = engine_snapshot_mm.Box[x][y];
4022 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4023 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4027 LX = engine_snapshot_mm.LX;
4028 LY = engine_snapshot_mm.LY;
4029 XS = engine_snapshot_mm.XS;
4030 YS = engine_snapshot_mm.YS;
4031 ELX = engine_snapshot_mm.ELX;
4032 ELY = engine_snapshot_mm.ELY;
4033 CT = engine_snapshot_mm.CT;
4034 Ct = engine_snapshot_mm.Ct;
4036 last_LX = engine_snapshot_mm.last_LX;
4037 last_LY = engine_snapshot_mm.last_LY;
4038 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4039 hold_x = engine_snapshot_mm.hold_x;
4040 hold_y = engine_snapshot_mm.hold_y;
4041 pacman_nr = engine_snapshot_mm.pacman_nr;
4043 rotate_delay = engine_snapshot_mm.rotate_delay;
4044 pacman_delay = engine_snapshot_mm.pacman_delay;
4045 energy_delay = engine_snapshot_mm.energy_delay;
4046 overload_delay = engine_snapshot_mm.overload_delay;
4048 RedrawPlayfield_MM();
4051 static int getAngleFromTouchDelta(int dx, int dy, int base)
4053 double pi = 3.141592653;
4054 double rad = atan2((double)-dy, (double)dx);
4055 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4056 double deg = rad2 * 180.0 / pi;
4058 return (int)(deg * base / 360.0 + 0.5) % base;
4061 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4063 // calculate start (source) position to be at the middle of the tile
4064 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4065 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4066 int dx = dst_mx - src_mx;
4067 int dy = dst_my - src_my;
4076 if (!IN_LEV_FIELD(x, y))
4079 element = Tile[x][y];
4081 if (!IS_MCDUFFIN(element) &&
4082 !IS_MIRROR(element) &&
4083 !IS_BEAMER(element) &&
4084 !IS_POLAR(element) &&
4085 !IS_POLAR_CROSS(element) &&
4086 !IS_DF_MIRROR(element))
4089 angle_old = get_element_angle(element);
4091 if (IS_MCDUFFIN(element))
4093 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4094 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4095 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4096 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4099 else if (IS_MIRROR(element) ||
4100 IS_DF_MIRROR(element))
4102 for (i = 0; i < laser.num_damages; i++)
4104 if (laser.damage[i].x == x &&
4105 laser.damage[i].y == y &&
4106 ObjHit(x, y, HIT_POS_CENTER))
4108 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4109 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4116 if (angle_new == -1)
4118 if (IS_MIRROR(element) ||
4119 IS_DF_MIRROR(element) ||
4123 if (IS_POLAR_CROSS(element))
4126 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4129 button = (angle_new == angle_old ? 0 :
4130 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4131 MB_LEFTBUTTON : MB_RIGHTBUTTON);