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 SetLaserColor(int brightness)
416 int color_min = 0x00;
417 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
418 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
419 int color_down = color_max - color_up;
422 GetPixelFromRGB(window,
423 (native_mm_level.laser_red ? color_max : color_up),
424 (native_mm_level.laser_green ? color_down : color_min),
425 (native_mm_level.laser_blue ? color_down : color_min));
428 static void InitMovDir_MM(int x, int y)
430 int element = Tile[x][y];
431 static int direction[3][4] =
433 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
434 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
435 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
440 case EL_PACMAN_RIGHT:
444 Tile[x][y] = EL_PACMAN;
445 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
453 static void InitField(int x, int y)
455 int element = Tile[x][y];
460 Tile[x][y] = EL_EMPTY;
465 if (native_mm_level.auto_count_kettles)
466 game_mm.kettles_still_needed++;
469 case EL_LIGHTBULB_OFF:
470 game_mm.lights_still_needed++;
474 if (IS_MIRROR(element) ||
475 IS_BEAMER_OLD(element) ||
476 IS_BEAMER(element) ||
478 IS_POLAR_CROSS(element) ||
479 IS_DF_MIRROR(element) ||
480 IS_DF_MIRROR_AUTO(element) ||
481 IS_GRID_STEEL_AUTO(element) ||
482 IS_GRID_WOOD_AUTO(element) ||
483 IS_FIBRE_OPTIC(element))
485 if (IS_BEAMER_OLD(element))
487 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
488 element = Tile[x][y];
491 if (!IS_FIBRE_OPTIC(element))
493 static int steps_grid_auto = 0;
495 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
496 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
498 if (IS_GRID_STEEL_AUTO(element) ||
499 IS_GRID_WOOD_AUTO(element))
500 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
502 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
504 game_mm.cycle[game_mm.num_cycle].x = x;
505 game_mm.cycle[game_mm.num_cycle].y = y;
509 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
511 int beamer_nr = BEAMER_NR(element);
512 int nr = laser.beamer[beamer_nr][0].num;
514 laser.beamer[beamer_nr][nr].x = x;
515 laser.beamer[beamer_nr][nr].y = y;
516 laser.beamer[beamer_nr][nr].num = 1;
519 else if (IS_PACMAN(element))
523 else if (IS_MCDUFFIN(element) || IS_LASER(element))
525 laser.start_edge.x = x;
526 laser.start_edge.y = y;
527 laser.start_angle = get_element_angle(element);
534 static void InitCycleElements_RotateSingleStep(void)
538 if (game_mm.num_cycle == 0) // no elements to cycle
541 for (i = 0; i < game_mm.num_cycle; i++)
543 int x = game_mm.cycle[i].x;
544 int y = game_mm.cycle[i].y;
545 int step = SIGN(game_mm.cycle[i].steps);
546 int last_element = Tile[x][y];
547 int next_element = get_rotated_element(last_element, step);
549 if (!game_mm.cycle[i].steps)
552 Tile[x][y] = next_element;
554 game_mm.cycle[i].steps -= step;
558 static void InitLaser(void)
560 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
561 int step = (IS_LASER(start_element) ? 4 : 0);
563 LX = laser.start_edge.x * TILEX;
564 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
567 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
569 LY = laser.start_edge.y * TILEY;
570 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
571 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
575 XS = 2 * Step[laser.start_angle].x;
576 YS = 2 * Step[laser.start_angle].y;
578 laser.current_angle = laser.start_angle;
580 laser.num_damages = 0;
582 laser.num_beamers = 0;
583 laser.beamer_edge[0] = 0;
585 laser.dest_element = EL_EMPTY;
588 AddLaserEdge(LX, LY); // set laser starting edge
593 void InitGameEngine_MM(void)
599 // initialize laser bitmap to current playfield (screen) size
600 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
601 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
605 // set global game control values
606 game_mm.num_cycle = 0;
607 game_mm.num_pacman = 0;
610 game_mm.energy_left = 0; // later set to "native_mm_level.time"
611 game_mm.kettles_still_needed =
612 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
613 game_mm.lights_still_needed = 0;
614 game_mm.num_keys = 0;
616 game_mm.level_solved = FALSE;
617 game_mm.game_over = FALSE;
618 game_mm.game_over_cause = 0;
620 game_mm.laser_overload_value = 0;
621 game_mm.laser_enabled = FALSE;
623 // set global laser control values (must be set before "InitLaser()")
624 laser.start_edge.x = 0;
625 laser.start_edge.y = 0;
626 laser.start_angle = 0;
628 for (i = 0; i < MAX_NUM_BEAMERS; i++)
629 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
631 laser.overloaded = FALSE;
632 laser.overload_value = 0;
633 laser.fuse_off = FALSE;
634 laser.fuse_x = laser.fuse_y = -1;
636 laser.dest_element = EL_EMPTY;
650 rotate_delay.count = 0;
651 pacman_delay.count = 0;
652 energy_delay.count = 0;
653 overload_delay.count = 0;
655 ClickElement(-1, -1, -1);
657 for (x = 0; x < lev_fieldx; x++)
659 for (y = 0; y < lev_fieldy; y++)
661 Tile[x][y] = Ur[x][y];
662 Hit[x][y] = Box[x][y] = 0;
664 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
665 Store[x][y] = Store2[x][y] = 0;
676 void InitGameActions_MM(void)
678 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
679 int cycle_steps_done = 0;
684 for (i = 0; i <= num_init_game_frames; i++)
686 if (i == num_init_game_frames)
687 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
688 else if (setup.sound_loops)
689 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
691 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
693 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
695 UpdateAndDisplayGameControlValues();
697 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
699 InitCycleElements_RotateSingleStep();
704 AdvanceFrameCounter();
714 if (setup.quick_doors)
721 if (game_mm.kettles_still_needed == 0)
724 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
725 SetTileCursorActive(TRUE);
727 ResetFrameCounter(&energy_delay);
730 static void FadeOutLaser(void)
734 for (i = 15; i >= 0; i--)
736 SetLaserColor(0x11 * i);
738 DrawLaser(0, DL_LASER_ENABLED);
741 Delay_WithScreenUpdates(50);
744 DrawLaser(0, DL_LASER_DISABLED);
746 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
749 static void GameOver_MM(int game_over_cause)
751 // do not handle game over if request dialog is already active
752 if (game.request_active)
755 game_mm.game_over = TRUE;
756 game_mm.game_over_cause = game_over_cause;
758 if (setup.ask_on_game_over)
759 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
760 "Bomb killed Mc Duffin! Play it again?" :
761 game_over_cause == GAME_OVER_NO_ENERGY ?
762 "Out of magic energy! Play it again?" :
763 game_over_cause == GAME_OVER_OVERLOADED ?
764 "Magic spell hit Mc Duffin! Play it again?" :
767 SetTileCursorActive(FALSE);
770 void AddLaserEdge(int lx, int ly)
775 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
777 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
782 laser.edge[laser.num_edges].x = cSX2 + lx;
783 laser.edge[laser.num_edges].y = cSY2 + ly;
789 void AddDamagedField(int ex, int ey)
791 laser.damage[laser.num_damages].is_mirror = FALSE;
792 laser.damage[laser.num_damages].angle = laser.current_angle;
793 laser.damage[laser.num_damages].edge = laser.num_edges;
794 laser.damage[laser.num_damages].x = ex;
795 laser.damage[laser.num_damages].y = ey;
799 static boolean StepBehind(void)
805 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
806 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
808 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
814 static int getMaskFromElement(int element)
816 if (IS_GRID(element))
817 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
818 else if (IS_MCDUFFIN(element))
819 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
820 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
821 return IMG_MM_MASK_RECTANGLE;
823 return IMG_MM_MASK_CIRCLE;
826 static int ScanPixel(void)
831 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
832 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
835 // follow laser beam until it hits something (at least the screen border)
836 while (hit_mask == HIT_MASK_NO_HIT)
842 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
843 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
845 Debug("game:mm:ScanPixel", "touched screen border!");
851 for (i = 0; i < 4; i++)
853 int px = LX + (i % 2) * 2;
854 int py = LY + (i / 2) * 2;
857 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
858 int ly = (py + TILEY) / TILEY - 1; // negative values!
861 if (IN_LEV_FIELD(lx, ly))
863 int element = Tile[lx][ly];
865 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
869 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
871 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
873 pixel = ((element & (1 << pos)) ? 1 : 0);
877 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
879 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
884 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
885 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
888 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
889 hit_mask |= (1 << i);
892 if (hit_mask == HIT_MASK_NO_HIT)
894 // hit nothing -- go on with another step
906 int end = 0, rf = laser.num_edges;
908 // do not scan laser again after the game was lost for whatever reason
909 if (game_mm.game_over)
912 laser.overloaded = FALSE;
913 laser.stops_inside_element = FALSE;
915 DrawLaser(0, DL_LASER_ENABLED);
918 Debug("game:mm:ScanLaser",
919 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
927 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
930 laser.overloaded = TRUE;
935 hit_mask = ScanPixel();
938 Debug("game:mm:ScanLaser",
939 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
943 // hit something -- check out what it was
944 ELX = (LX + XS) / TILEX;
945 ELY = (LY + YS) / TILEY;
948 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
949 hit_mask, LX, LY, ELX, ELY);
952 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
955 laser.dest_element = element;
960 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
962 /* we have hit the top-right and bottom-left element --
963 choose the bottom-left one */
964 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
965 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
966 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
967 ELX = (LX - 2) / TILEX;
968 ELY = (LY + 2) / TILEY;
971 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
973 /* we have hit the top-left and bottom-right element --
974 choose the top-left one */
976 ELX = (LX - 2) / TILEX;
977 ELY = (LY - 2) / TILEY;
981 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
982 hit_mask, LX, LY, ELX, ELY);
985 element = Tile[ELX][ELY];
986 laser.dest_element = element;
989 Debug("game:mm:ScanLaser",
990 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
993 LX % TILEX, LY % TILEY,
998 if (!IN_LEV_FIELD(ELX, ELY))
999 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1003 if (element == EL_EMPTY)
1005 if (!HitOnlyAnEdge(hit_mask))
1008 else if (element == EL_FUSE_ON)
1010 if (HitPolarizer(element, hit_mask))
1013 else if (IS_GRID(element) || IS_DF_GRID(element))
1015 if (HitPolarizer(element, hit_mask))
1018 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1019 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1021 if (HitBlock(element, hit_mask))
1028 else if (IS_MCDUFFIN(element))
1030 if (HitLaserSource(element, hit_mask))
1033 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1034 IS_RECEIVER(element))
1036 if (HitLaserDestination(element, hit_mask))
1039 else if (IS_WALL(element))
1041 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1043 if (HitReflectingWalls(element, hit_mask))
1048 if (HitAbsorbingWalls(element, hit_mask))
1054 if (HitElement(element, hit_mask))
1059 DrawLaser(rf - 1, DL_LASER_ENABLED);
1060 rf = laser.num_edges;
1064 if (laser.dest_element != Tile[ELX][ELY])
1066 Debug("game:mm:ScanLaser",
1067 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1068 laser.dest_element, Tile[ELX][ELY]);
1072 if (!end && !laser.stops_inside_element && !StepBehind())
1075 Debug("game:mm:ScanLaser", "Go one step back");
1081 AddLaserEdge(LX, LY);
1085 DrawLaser(rf - 1, DL_LASER_ENABLED);
1087 Ct = CT = FrameCounter;
1090 if (!IN_LEV_FIELD(ELX, ELY))
1091 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1095 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1101 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1102 start_edge, num_edges, mode);
1107 Warn("DrawLaserExt: start_edge < 0");
1114 Warn("DrawLaserExt: num_edges < 0");
1120 if (mode == DL_LASER_DISABLED)
1122 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1126 // now draw the laser to the backbuffer and (if enabled) to the screen
1127 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1129 redraw_mask |= REDRAW_FIELD;
1131 if (mode == DL_LASER_ENABLED)
1134 // after the laser was deleted, the "damaged" graphics must be restored
1135 if (laser.num_damages)
1137 int damage_start = 0;
1140 // determine the starting edge, from which graphics need to be restored
1143 for (i = 0; i < laser.num_damages; i++)
1145 if (laser.damage[i].edge == start_edge + 1)
1154 // restore graphics from this starting edge to the end of damage list
1155 for (i = damage_start; i < laser.num_damages; i++)
1157 int lx = laser.damage[i].x;
1158 int ly = laser.damage[i].y;
1159 int element = Tile[lx][ly];
1161 if (Hit[lx][ly] == laser.damage[i].edge)
1162 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1165 if (Box[lx][ly] == laser.damage[i].edge)
1168 if (IS_DRAWABLE(element))
1169 DrawField_MM(lx, ly);
1172 elx = laser.damage[damage_start].x;
1173 ely = laser.damage[damage_start].y;
1174 element = Tile[elx][ely];
1177 if (IS_BEAMER(element))
1181 for (i = 0; i < laser.num_beamers; i++)
1182 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1184 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1185 mode, elx, ely, Hit[elx][ely], start_edge);
1186 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1187 get_element_angle(element), laser.damage[damage_start].angle);
1191 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1192 laser.num_beamers > 0 &&
1193 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1195 // element is outgoing beamer
1196 laser.num_damages = damage_start + 1;
1198 if (IS_BEAMER(element))
1199 laser.current_angle = get_element_angle(element);
1203 // element is incoming beamer or other element
1204 laser.num_damages = damage_start;
1205 laser.current_angle = laser.damage[laser.num_damages].angle;
1210 // no damages but McDuffin himself (who needs to be redrawn anyway)
1212 elx = laser.start_edge.x;
1213 ely = laser.start_edge.y;
1214 element = Tile[elx][ely];
1217 laser.num_edges = start_edge + 1;
1218 if (start_edge == 0)
1219 laser.current_angle = laser.start_angle;
1221 LX = laser.edge[start_edge].x - cSX2;
1222 LY = laser.edge[start_edge].y - cSY2;
1223 XS = 2 * Step[laser.current_angle].x;
1224 YS = 2 * Step[laser.current_angle].y;
1227 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1233 if (IS_BEAMER(element) ||
1234 IS_FIBRE_OPTIC(element) ||
1235 IS_PACMAN(element) ||
1236 IS_POLAR(element) ||
1237 IS_POLAR_CROSS(element) ||
1238 element == EL_FUSE_ON)
1243 Debug("game:mm:DrawLaserExt", "element == %d", element);
1246 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1247 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1251 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1252 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1253 (laser.num_beamers == 0 ||
1254 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1256 // element is incoming beamer or other element
1257 step_size = -step_size;
1262 if (IS_BEAMER(element))
1263 Debug("game:mm:DrawLaserExt",
1264 "start_edge == %d, laser.beamer_edge == %d",
1265 start_edge, laser.beamer_edge);
1268 LX += step_size * XS;
1269 LY += step_size * YS;
1271 else if (element != EL_EMPTY)
1280 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1285 void DrawLaser(int start_edge, int mode)
1287 if (laser.num_edges - start_edge < 0)
1289 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1294 // check if laser is interrupted by beamer element
1295 if (laser.num_beamers > 0 &&
1296 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1298 if (mode == DL_LASER_ENABLED)
1301 int tmp_start_edge = start_edge;
1303 // draw laser segments forward from the start to the last beamer
1304 for (i = 0; i < laser.num_beamers; i++)
1306 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1308 if (tmp_num_edges <= 0)
1312 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1313 i, laser.beamer_edge[i], tmp_start_edge);
1316 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1318 tmp_start_edge = laser.beamer_edge[i];
1321 // draw last segment from last beamer to the end
1322 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1328 int last_num_edges = laser.num_edges;
1329 int num_beamers = laser.num_beamers;
1331 // delete laser segments backward from the end to the first beamer
1332 for (i = num_beamers - 1; i >= 0; i--)
1334 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1336 if (laser.beamer_edge[i] - start_edge <= 0)
1339 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1341 last_num_edges = laser.beamer_edge[i];
1342 laser.num_beamers--;
1346 if (last_num_edges - start_edge <= 0)
1347 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1348 last_num_edges, start_edge);
1351 // special case when rotating first beamer: delete laser edge on beamer
1352 // (but do not start scanning on previous edge to prevent mirror sound)
1353 if (last_num_edges - start_edge == 1 && start_edge > 0)
1354 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1356 // delete first segment from start to the first beamer
1357 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1362 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1365 game_mm.laser_enabled = mode;
1368 void DrawLaser_MM(void)
1370 DrawLaser(0, game_mm.laser_enabled);
1373 boolean HitElement(int element, int hit_mask)
1375 if (HitOnlyAnEdge(hit_mask))
1378 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1379 element = MovingOrBlocked2Element_MM(ELX, ELY);
1382 Debug("game:mm:HitElement", "(1): element == %d", element);
1386 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1387 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1390 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1394 AddDamagedField(ELX, ELY);
1396 // this is more precise: check if laser would go through the center
1397 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1399 // skip the whole element before continuing the scan
1405 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1407 if (LX/TILEX > ELX || LY/TILEY > ELY)
1409 /* skipping scan positions to the right and down skips one scan
1410 position too much, because this is only the top left scan position
1411 of totally four scan positions (plus one to the right, one to the
1412 bottom and one to the bottom right) */
1422 Debug("game:mm:HitElement", "(2): element == %d", element);
1425 if (LX + 5 * XS < 0 ||
1435 Debug("game:mm:HitElement", "(3): element == %d", element);
1438 if (IS_POLAR(element) &&
1439 ((element - EL_POLAR_START) % 2 ||
1440 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1442 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1444 laser.num_damages--;
1449 if (IS_POLAR_CROSS(element) &&
1450 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1452 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1454 laser.num_damages--;
1459 if (!IS_BEAMER(element) &&
1460 !IS_FIBRE_OPTIC(element) &&
1461 !IS_GRID_WOOD(element) &&
1462 element != EL_FUEL_EMPTY)
1465 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1466 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1468 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1471 LX = ELX * TILEX + 14;
1472 LY = ELY * TILEY + 14;
1474 AddLaserEdge(LX, LY);
1477 if (IS_MIRROR(element) ||
1478 IS_MIRROR_FIXED(element) ||
1479 IS_POLAR(element) ||
1480 IS_POLAR_CROSS(element) ||
1481 IS_DF_MIRROR(element) ||
1482 IS_DF_MIRROR_AUTO(element) ||
1483 element == EL_PRISM ||
1484 element == EL_REFRACTOR)
1486 int current_angle = laser.current_angle;
1489 laser.num_damages--;
1491 AddDamagedField(ELX, ELY);
1493 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1496 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1498 if (IS_MIRROR(element) ||
1499 IS_MIRROR_FIXED(element) ||
1500 IS_DF_MIRROR(element) ||
1501 IS_DF_MIRROR_AUTO(element))
1502 laser.current_angle = get_mirrored_angle(laser.current_angle,
1503 get_element_angle(element));
1505 if (element == EL_PRISM || element == EL_REFRACTOR)
1506 laser.current_angle = RND(16);
1508 XS = 2 * Step[laser.current_angle].x;
1509 YS = 2 * Step[laser.current_angle].y;
1511 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1516 LX += step_size * XS;
1517 LY += step_size * YS;
1519 // draw sparkles on mirror
1520 if ((IS_MIRROR(element) ||
1521 IS_MIRROR_FIXED(element) ||
1522 element == EL_PRISM) &&
1523 current_angle != laser.current_angle)
1525 MovDelay[ELX][ELY] = 11; // start animation
1528 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1529 current_angle != laser.current_angle)
1530 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1533 (get_opposite_angle(laser.current_angle) ==
1534 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1536 return (laser.overloaded ? TRUE : FALSE);
1539 if (element == EL_FUEL_FULL)
1541 laser.stops_inside_element = TRUE;
1546 if (element == EL_BOMB || element == EL_MINE)
1548 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1550 if (element == EL_MINE)
1551 laser.overloaded = TRUE;
1554 if (element == EL_KETTLE ||
1555 element == EL_CELL ||
1556 element == EL_KEY ||
1557 element == EL_LIGHTBALL ||
1558 element == EL_PACMAN ||
1561 if (!IS_PACMAN(element))
1564 if (element == EL_PACMAN)
1567 if (element == EL_KETTLE || element == EL_CELL)
1569 if (game_mm.kettles_still_needed > 0)
1570 game_mm.kettles_still_needed--;
1572 game.snapshot.collected_item = TRUE;
1574 if (game_mm.kettles_still_needed == 0)
1578 DrawLaser(0, DL_LASER_ENABLED);
1581 else if (element == EL_KEY)
1585 else if (IS_PACMAN(element))
1587 DeletePacMan(ELX, ELY);
1590 RaiseScoreElement_MM(element);
1595 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1597 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1599 DrawLaser(0, DL_LASER_ENABLED);
1601 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1603 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1604 game_mm.lights_still_needed--;
1608 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1609 game_mm.lights_still_needed++;
1612 DrawField_MM(ELX, ELY);
1613 DrawLaser(0, DL_LASER_ENABLED);
1618 laser.stops_inside_element = TRUE;
1624 Debug("game:mm:HitElement", "(4): element == %d", element);
1627 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1628 laser.num_beamers < MAX_NUM_BEAMERS &&
1629 laser.beamer[BEAMER_NR(element)][1].num)
1631 int beamer_angle = get_element_angle(element);
1632 int beamer_nr = BEAMER_NR(element);
1636 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1639 laser.num_damages--;
1641 if (IS_FIBRE_OPTIC(element) ||
1642 laser.current_angle == get_opposite_angle(beamer_angle))
1646 LX = ELX * TILEX + 14;
1647 LY = ELY * TILEY + 14;
1649 AddLaserEdge(LX, LY);
1650 AddDamagedField(ELX, ELY);
1652 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1655 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1657 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1658 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1659 ELX = laser.beamer[beamer_nr][pos].x;
1660 ELY = laser.beamer[beamer_nr][pos].y;
1661 LX = ELX * TILEX + 14;
1662 LY = ELY * TILEY + 14;
1664 if (IS_BEAMER(element))
1666 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1667 XS = 2 * Step[laser.current_angle].x;
1668 YS = 2 * Step[laser.current_angle].y;
1671 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1673 AddLaserEdge(LX, LY);
1674 AddDamagedField(ELX, ELY);
1676 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1679 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1681 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1686 LX += step_size * XS;
1687 LY += step_size * YS;
1689 laser.num_beamers++;
1698 boolean HitOnlyAnEdge(int hit_mask)
1700 // check if the laser hit only the edge of an element and, if so, go on
1703 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1707 if ((hit_mask == HIT_MASK_TOPLEFT ||
1708 hit_mask == HIT_MASK_TOPRIGHT ||
1709 hit_mask == HIT_MASK_BOTTOMLEFT ||
1710 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1711 laser.current_angle % 4) // angle is not 90°
1715 if (hit_mask == HIT_MASK_TOPLEFT)
1720 else if (hit_mask == HIT_MASK_TOPRIGHT)
1725 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1730 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1736 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1742 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1749 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1755 boolean HitPolarizer(int element, int hit_mask)
1757 if (HitOnlyAnEdge(hit_mask))
1760 if (IS_DF_GRID(element))
1762 int grid_angle = get_element_angle(element);
1765 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1766 grid_angle, laser.current_angle);
1769 AddLaserEdge(LX, LY);
1770 AddDamagedField(ELX, ELY);
1773 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1775 if (laser.current_angle == grid_angle ||
1776 laser.current_angle == get_opposite_angle(grid_angle))
1778 // skip the whole element before continuing the scan
1784 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1786 if (LX/TILEX > ELX || LY/TILEY > ELY)
1788 /* skipping scan positions to the right and down skips one scan
1789 position too much, because this is only the top left scan position
1790 of totally four scan positions (plus one to the right, one to the
1791 bottom and one to the bottom right) */
1797 AddLaserEdge(LX, LY);
1803 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1805 LX / TILEX, LY / TILEY,
1806 LX % TILEX, LY % TILEY);
1811 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1813 return HitReflectingWalls(element, hit_mask);
1817 return HitAbsorbingWalls(element, hit_mask);
1820 else if (IS_GRID_STEEL(element))
1822 return HitReflectingWalls(element, hit_mask);
1824 else // IS_GRID_WOOD
1826 return HitAbsorbingWalls(element, hit_mask);
1832 boolean HitBlock(int element, int hit_mask)
1834 boolean check = FALSE;
1836 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1837 game_mm.num_keys == 0)
1840 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1843 int ex = ELX * TILEX + 14;
1844 int ey = ELY * TILEY + 14;
1848 for (i = 1; i < 32; i++)
1853 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1858 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1859 return HitAbsorbingWalls(element, hit_mask);
1863 AddLaserEdge(LX - XS, LY - YS);
1864 AddDamagedField(ELX, ELY);
1867 Box[ELX][ELY] = laser.num_edges;
1869 return HitReflectingWalls(element, hit_mask);
1872 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1874 int xs = XS / 2, ys = YS / 2;
1875 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1876 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1878 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1879 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1881 laser.overloaded = (element == EL_GATE_STONE);
1886 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1887 (hit_mask == HIT_MASK_TOP ||
1888 hit_mask == HIT_MASK_LEFT ||
1889 hit_mask == HIT_MASK_RIGHT ||
1890 hit_mask == HIT_MASK_BOTTOM))
1891 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1892 hit_mask == HIT_MASK_BOTTOM),
1893 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1894 hit_mask == HIT_MASK_RIGHT));
1895 AddLaserEdge(LX, LY);
1901 if (element == EL_GATE_STONE && Box[ELX][ELY])
1903 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1915 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1917 int xs = XS / 2, ys = YS / 2;
1918 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1919 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1921 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1922 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1924 laser.overloaded = (element == EL_BLOCK_STONE);
1929 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1930 (hit_mask == HIT_MASK_TOP ||
1931 hit_mask == HIT_MASK_LEFT ||
1932 hit_mask == HIT_MASK_RIGHT ||
1933 hit_mask == HIT_MASK_BOTTOM))
1934 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1935 hit_mask == HIT_MASK_BOTTOM),
1936 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1937 hit_mask == HIT_MASK_RIGHT));
1938 AddDamagedField(ELX, ELY);
1940 LX = ELX * TILEX + 14;
1941 LY = ELY * TILEY + 14;
1943 AddLaserEdge(LX, LY);
1945 laser.stops_inside_element = TRUE;
1953 boolean HitLaserSource(int element, int hit_mask)
1955 if (HitOnlyAnEdge(hit_mask))
1958 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1960 laser.overloaded = TRUE;
1965 boolean HitLaserDestination(int element, int hit_mask)
1967 if (HitOnlyAnEdge(hit_mask))
1970 if (element != EL_EXIT_OPEN &&
1971 !(IS_RECEIVER(element) &&
1972 game_mm.kettles_still_needed == 0 &&
1973 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1975 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1980 if (IS_RECEIVER(element) ||
1981 (IS_22_5_ANGLE(laser.current_angle) &&
1982 (ELX != (LX + 6 * XS) / TILEX ||
1983 ELY != (LY + 6 * YS) / TILEY ||
1992 LX = ELX * TILEX + 14;
1993 LY = ELY * TILEY + 14;
1995 laser.stops_inside_element = TRUE;
1998 AddLaserEdge(LX, LY);
1999 AddDamagedField(ELX, ELY);
2001 if (game_mm.lights_still_needed == 0)
2003 game_mm.level_solved = TRUE;
2005 SetTileCursorActive(FALSE);
2011 boolean HitReflectingWalls(int element, int hit_mask)
2013 // check if laser hits side of a wall with an angle that is not 90°
2014 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2015 hit_mask == HIT_MASK_LEFT ||
2016 hit_mask == HIT_MASK_RIGHT ||
2017 hit_mask == HIT_MASK_BOTTOM))
2019 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2024 if (!IS_DF_GRID(element))
2025 AddLaserEdge(LX, LY);
2027 // check if laser hits wall with an angle of 45°
2028 if (!IS_22_5_ANGLE(laser.current_angle))
2030 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2033 laser.current_angle = get_mirrored_angle(laser.current_angle,
2036 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2039 laser.current_angle = get_mirrored_angle(laser.current_angle,
2043 AddLaserEdge(LX, LY);
2045 XS = 2 * Step[laser.current_angle].x;
2046 YS = 2 * Step[laser.current_angle].y;
2050 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2052 laser.current_angle = get_mirrored_angle(laser.current_angle,
2057 if (!IS_DF_GRID(element))
2058 AddLaserEdge(LX, LY);
2063 if (!IS_DF_GRID(element))
2064 AddLaserEdge(LX, LY + YS / 2);
2067 if (!IS_DF_GRID(element))
2068 AddLaserEdge(LX, LY);
2071 YS = 2 * Step[laser.current_angle].y;
2075 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2077 laser.current_angle = get_mirrored_angle(laser.current_angle,
2082 if (!IS_DF_GRID(element))
2083 AddLaserEdge(LX, LY);
2088 if (!IS_DF_GRID(element))
2089 AddLaserEdge(LX + XS / 2, LY);
2092 if (!IS_DF_GRID(element))
2093 AddLaserEdge(LX, LY);
2096 XS = 2 * Step[laser.current_angle].x;
2102 // reflection at the edge of reflecting DF style wall
2103 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2105 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2106 hit_mask == HIT_MASK_TOPRIGHT) ||
2107 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2108 hit_mask == HIT_MASK_TOPLEFT) ||
2109 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2110 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2111 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2112 hit_mask == HIT_MASK_BOTTOMRIGHT))
2115 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2116 ANG_MIRROR_135 : ANG_MIRROR_45);
2118 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2120 AddDamagedField(ELX, ELY);
2121 AddLaserEdge(LX, LY);
2123 laser.current_angle = get_mirrored_angle(laser.current_angle,
2131 AddLaserEdge(LX, LY);
2137 // reflection inside an edge of reflecting DF style wall
2138 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2140 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2141 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2142 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2143 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2144 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2145 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2146 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2147 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2150 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2151 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2152 ANG_MIRROR_135 : ANG_MIRROR_45);
2154 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2157 AddDamagedField(ELX, ELY);
2160 AddLaserEdge(LX - XS, LY - YS);
2161 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2162 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2164 laser.current_angle = get_mirrored_angle(laser.current_angle,
2172 AddLaserEdge(LX, LY);
2178 // check if laser hits DF style wall with an angle of 90°
2179 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2181 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2182 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2183 (IS_VERT_ANGLE(laser.current_angle) &&
2184 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2186 // laser at last step touched nothing or the same side of the wall
2187 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2189 AddDamagedField(ELX, ELY);
2196 last_hit_mask = hit_mask;
2203 if (!HitOnlyAnEdge(hit_mask))
2205 laser.overloaded = TRUE;
2213 boolean HitAbsorbingWalls(int element, int hit_mask)
2215 if (HitOnlyAnEdge(hit_mask))
2219 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2221 AddLaserEdge(LX - XS, LY - YS);
2228 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2230 AddLaserEdge(LX - XS, LY - YS);
2236 if (IS_WALL_WOOD(element) ||
2237 IS_DF_WALL_WOOD(element) ||
2238 IS_GRID_WOOD(element) ||
2239 IS_GRID_WOOD_FIXED(element) ||
2240 IS_GRID_WOOD_AUTO(element) ||
2241 element == EL_FUSE_ON ||
2242 element == EL_BLOCK_WOOD ||
2243 element == EL_GATE_WOOD)
2245 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2250 if (IS_WALL_ICE(element))
2254 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2255 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2257 // check if laser hits wall with an angle of 90°
2258 if (IS_90_ANGLE(laser.current_angle))
2259 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2261 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2265 for (i = 0; i < 4; i++)
2267 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2268 mask = 15 - (8 >> i);
2269 else if (ABS(XS) == 4 &&
2271 (XS > 0) == (i % 2) &&
2272 (YS < 0) == (i / 2))
2273 mask = 3 + (i / 2) * 9;
2274 else if (ABS(YS) == 4 &&
2276 (XS < 0) == (i % 2) &&
2277 (YS > 0) == (i / 2))
2278 mask = 5 + (i % 2) * 5;
2282 laser.wall_mask = mask;
2284 else if (IS_WALL_AMOEBA(element))
2286 int elx = (LX - 2 * XS) / TILEX;
2287 int ely = (LY - 2 * YS) / TILEY;
2288 int element2 = Tile[elx][ely];
2291 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2293 laser.dest_element = EL_EMPTY;
2301 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2302 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2304 if (IS_90_ANGLE(laser.current_angle))
2305 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2307 laser.dest_element = element2 | EL_WALL_AMOEBA;
2309 laser.wall_mask = mask;
2315 static void OpenExit(int x, int y)
2319 if (!MovDelay[x][y]) // next animation frame
2320 MovDelay[x][y] = 4 * delay;
2322 if (MovDelay[x][y]) // wait some time before next frame
2327 phase = MovDelay[x][y] / delay;
2329 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2330 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2332 if (!MovDelay[x][y])
2334 Tile[x][y] = EL_EXIT_OPEN;
2340 static void OpenSurpriseBall(int x, int y)
2344 if (!MovDelay[x][y]) // next animation frame
2345 MovDelay[x][y] = 50 * delay;
2347 if (MovDelay[x][y]) // wait some time before next frame
2351 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2354 int graphic = el2gfx(Store[x][y]);
2356 int dx = RND(26), dy = RND(26);
2358 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2360 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2361 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2363 MarkTileDirty(x, y);
2366 if (!MovDelay[x][y])
2368 Tile[x][y] = Store[x][y];
2377 static void MeltIce(int x, int y)
2382 if (!MovDelay[x][y]) // next animation frame
2383 MovDelay[x][y] = frames * delay;
2385 if (MovDelay[x][y]) // wait some time before next frame
2388 int wall_mask = Store2[x][y];
2389 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2392 phase = frames - MovDelay[x][y] / delay - 1;
2394 if (!MovDelay[x][y])
2398 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2399 Store[x][y] = Store2[x][y] = 0;
2401 DrawWalls_MM(x, y, Tile[x][y]);
2403 if (Tile[x][y] == EL_WALL_ICE)
2404 Tile[x][y] = EL_EMPTY;
2406 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2407 if (laser.damage[i].is_mirror)
2411 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2413 DrawLaser(0, DL_LASER_DISABLED);
2417 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2419 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2421 laser.redraw = TRUE;
2426 static void GrowAmoeba(int x, int y)
2431 if (!MovDelay[x][y]) // next animation frame
2432 MovDelay[x][y] = frames * delay;
2434 if (MovDelay[x][y]) // wait some time before next frame
2437 int wall_mask = Store2[x][y];
2438 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2441 phase = MovDelay[x][y] / delay;
2443 if (!MovDelay[x][y])
2445 Tile[x][y] = real_element;
2446 Store[x][y] = Store2[x][y] = 0;
2448 DrawWalls_MM(x, y, Tile[x][y]);
2449 DrawLaser(0, DL_LASER_ENABLED);
2451 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2453 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2458 static void DrawFieldAnimated_MM(int x, int y)
2460 int element = Tile[x][y];
2462 if (IS_BLOCKED(x, y))
2467 if (IS_MIRROR(element) ||
2468 IS_MIRROR_FIXED(element) ||
2469 element == EL_PRISM)
2471 if (MovDelay[x][y] != 0) // wait some time before next frame
2475 if (MovDelay[x][y] != 0)
2477 int graphic = IMG_TWINKLE_WHITE;
2478 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2480 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2485 laser.redraw = TRUE;
2488 static void Explode_MM(int x, int y, int phase, int mode)
2490 int num_phase = 9, delay = 2;
2491 int last_phase = num_phase * delay;
2492 int half_phase = (num_phase / 2) * delay;
2494 laser.redraw = TRUE;
2496 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2498 int center_element = Tile[x][y];
2500 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2502 // put moving element to center field (and let it explode there)
2503 center_element = MovingOrBlocked2Element_MM(x, y);
2504 RemoveMovingField_MM(x, y);
2506 Tile[x][y] = center_element;
2509 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2510 Store[x][y] = center_element;
2512 Store[x][y] = EL_EMPTY;
2514 Store2[x][y] = mode;
2515 Tile[x][y] = EL_EXPLODING_OPAQUE;
2516 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2522 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2524 if (phase == half_phase)
2526 Tile[x][y] = EL_EXPLODING_TRANSP;
2528 if (x == ELX && y == ELY)
2532 if (phase == last_phase)
2534 if (Store[x][y] == EL_BOMB)
2536 DrawLaser(0, DL_LASER_DISABLED);
2539 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2540 Store[x][y] = EL_EMPTY;
2542 GameOver_MM(GAME_OVER_DELAYED);
2544 laser.overloaded = FALSE;
2546 else if (IS_MCDUFFIN(Store[x][y]))
2548 Store[x][y] = EL_EMPTY;
2550 GameOver_MM(GAME_OVER_BOMB);
2553 Tile[x][y] = Store[x][y];
2554 Store[x][y] = Store2[x][y] = 0;
2555 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2560 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2562 int graphic = IMG_MM_DEFAULT_EXPLODING;
2563 int graphic_phase = (phase / delay - 1);
2567 if (Store2[x][y] == EX_KETTLE)
2569 if (graphic_phase < 3)
2571 graphic = IMG_MM_KETTLE_EXPLODING;
2573 else if (graphic_phase < 5)
2579 graphic = IMG_EMPTY;
2583 else if (Store2[x][y] == EX_SHORT)
2585 if (graphic_phase < 4)
2591 graphic = IMG_EMPTY;
2596 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2598 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2599 cFX + x * TILEX, cFY + y * TILEY);
2601 MarkTileDirty(x, y);
2605 static void Bang_MM(int x, int y)
2607 int element = Tile[x][y];
2608 int mode = EX_NORMAL;
2611 DrawLaser(0, DL_LASER_ENABLED);
2630 if (IS_PACMAN(element))
2631 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2632 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2633 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2634 else if (element == EL_KEY)
2635 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2637 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2639 Explode_MM(x, y, EX_PHASE_START, mode);
2642 void TurnRound(int x, int y)
2654 { 0, 0 }, { 0, 0 }, { 0, 0 },
2659 int left, right, back;
2663 { MV_DOWN, MV_UP, MV_RIGHT },
2664 { MV_UP, MV_DOWN, MV_LEFT },
2666 { MV_LEFT, MV_RIGHT, MV_DOWN },
2667 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2668 { MV_RIGHT, MV_LEFT, MV_UP }
2671 int element = Tile[x][y];
2672 int old_move_dir = MovDir[x][y];
2673 int right_dir = turn[old_move_dir].right;
2674 int back_dir = turn[old_move_dir].back;
2675 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2676 int right_x = x + right_dx, right_y = y + right_dy;
2678 if (element == EL_PACMAN)
2680 boolean can_turn_right = FALSE;
2682 if (IN_LEV_FIELD(right_x, right_y) &&
2683 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2684 can_turn_right = TRUE;
2687 MovDir[x][y] = right_dir;
2689 MovDir[x][y] = back_dir;
2695 static void StartMoving_MM(int x, int y)
2697 int element = Tile[x][y];
2702 if (CAN_MOVE(element))
2706 if (MovDelay[x][y]) // wait some time before next movement
2714 // now make next step
2716 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2718 if (element == EL_PACMAN &&
2719 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2720 !ObjHit(newx, newy, HIT_POS_CENTER))
2722 Store[newx][newy] = Tile[newx][newy];
2723 Tile[newx][newy] = EL_EMPTY;
2725 DrawField_MM(newx, newy);
2727 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2728 ObjHit(newx, newy, HIT_POS_CENTER))
2730 // object was running against a wall
2737 InitMovingField_MM(x, y, MovDir[x][y]);
2741 ContinueMoving_MM(x, y);
2744 static void ContinueMoving_MM(int x, int y)
2746 int element = Tile[x][y];
2747 int direction = MovDir[x][y];
2748 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2749 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2750 int horiz_move = (dx!=0);
2751 int newx = x + dx, newy = y + dy;
2752 int step = (horiz_move ? dx : dy) * TILEX / 8;
2754 MovPos[x][y] += step;
2756 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2758 Tile[x][y] = EL_EMPTY;
2759 Tile[newx][newy] = element;
2761 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2762 MovDelay[newx][newy] = 0;
2764 if (!CAN_MOVE(element))
2765 MovDir[newx][newy] = 0;
2768 DrawField_MM(newx, newy);
2770 Stop[newx][newy] = TRUE;
2772 if (element == EL_PACMAN)
2774 if (Store[newx][newy] == EL_BOMB)
2775 Bang_MM(newx, newy);
2777 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2778 (LX + 2 * XS) / TILEX == newx &&
2779 (LY + 2 * YS) / TILEY == newy)
2786 else // still moving on
2791 laser.redraw = TRUE;
2794 boolean ClickElement(int x, int y, int button)
2796 static DelayCounter click_delay = { CLICK_DELAY };
2797 static boolean new_button = TRUE;
2798 boolean element_clicked = FALSE;
2803 // initialize static variables
2804 click_delay.count = 0;
2805 click_delay.value = CLICK_DELAY;
2811 // do not rotate objects hit by the laser after the game was solved
2812 if (game_mm.level_solved && Hit[x][y])
2815 if (button == MB_RELEASED)
2818 click_delay.value = CLICK_DELAY;
2820 // release eventually hold auto-rotating mirror
2821 RotateMirror(x, y, MB_RELEASED);
2826 if (!FrameReached(&click_delay) && !new_button)
2829 if (button == MB_MIDDLEBUTTON) // middle button has no function
2832 if (!IN_LEV_FIELD(x, y))
2835 if (Tile[x][y] == EL_EMPTY)
2838 element = Tile[x][y];
2840 if (IS_MIRROR(element) ||
2841 IS_BEAMER(element) ||
2842 IS_POLAR(element) ||
2843 IS_POLAR_CROSS(element) ||
2844 IS_DF_MIRROR(element) ||
2845 IS_DF_MIRROR_AUTO(element))
2847 RotateMirror(x, y, button);
2849 element_clicked = TRUE;
2851 else if (IS_MCDUFFIN(element))
2853 if (!laser.fuse_off)
2855 DrawLaser(0, DL_LASER_DISABLED);
2862 element = get_rotated_element(element, BUTTON_ROTATION(button));
2863 laser.start_angle = get_element_angle(element);
2867 Tile[x][y] = element;
2874 if (!laser.fuse_off)
2877 element_clicked = TRUE;
2879 else if (element == EL_FUSE_ON && laser.fuse_off)
2881 if (x != laser.fuse_x || y != laser.fuse_y)
2884 laser.fuse_off = FALSE;
2885 laser.fuse_x = laser.fuse_y = -1;
2887 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2890 element_clicked = TRUE;
2892 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2894 laser.fuse_off = TRUE;
2897 laser.overloaded = FALSE;
2899 DrawLaser(0, DL_LASER_DISABLED);
2900 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2902 element_clicked = TRUE;
2904 else if (element == EL_LIGHTBALL)
2907 RaiseScoreElement_MM(element);
2908 DrawLaser(0, DL_LASER_ENABLED);
2910 element_clicked = TRUE;
2913 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2916 return element_clicked;
2919 void RotateMirror(int x, int y, int button)
2921 if (button == MB_RELEASED)
2923 // release eventually hold auto-rotating mirror
2930 if (IS_MIRROR(Tile[x][y]) ||
2931 IS_POLAR_CROSS(Tile[x][y]) ||
2932 IS_POLAR(Tile[x][y]) ||
2933 IS_BEAMER(Tile[x][y]) ||
2934 IS_DF_MIRROR(Tile[x][y]) ||
2935 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2936 IS_GRID_WOOD_AUTO(Tile[x][y]))
2938 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2940 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2942 if (button == MB_LEFTBUTTON)
2944 // left mouse button only for manual adjustment, no auto-rotating;
2945 // freeze mirror for until mouse button released
2949 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2951 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2955 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2957 int edge = Hit[x][y];
2963 DrawLaser(edge - 1, DL_LASER_DISABLED);
2967 else if (ObjHit(x, y, HIT_POS_CENTER))
2969 int edge = Hit[x][y];
2973 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2978 DrawLaser(edge - 1, DL_LASER_DISABLED);
2985 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2990 if ((IS_BEAMER(Tile[x][y]) ||
2991 IS_POLAR(Tile[x][y]) ||
2992 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2996 if (IS_BEAMER(Tile[x][y]))
2999 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
3000 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3012 DrawLaser(0, DL_LASER_ENABLED);
3016 static void AutoRotateMirrors(void)
3020 if (!FrameReached(&rotate_delay))
3023 for (x = 0; x < lev_fieldx; x++)
3025 for (y = 0; y < lev_fieldy; y++)
3027 int element = Tile[x][y];
3029 // do not rotate objects hit by the laser after the game was solved
3030 if (game_mm.level_solved && Hit[x][y])
3033 if (IS_DF_MIRROR_AUTO(element) ||
3034 IS_GRID_WOOD_AUTO(element) ||
3035 IS_GRID_STEEL_AUTO(element) ||
3036 element == EL_REFRACTOR)
3037 RotateMirror(x, y, MB_RIGHTBUTTON);
3042 boolean ObjHit(int obx, int oby, int bits)
3049 if (bits & HIT_POS_CENTER)
3051 if (CheckLaserPixel(cSX + obx + 15,
3056 if (bits & HIT_POS_EDGE)
3058 for (i = 0; i < 4; i++)
3059 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3060 cSY + oby + 31 * (i / 2)))
3064 if (bits & HIT_POS_BETWEEN)
3066 for (i = 0; i < 4; i++)
3067 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3068 cSY + 4 + oby + 22 * (i / 2)))
3075 void DeletePacMan(int px, int py)
3081 if (game_mm.num_pacman <= 1)
3083 game_mm.num_pacman = 0;
3087 for (i = 0; i < game_mm.num_pacman; i++)
3088 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3091 game_mm.num_pacman--;
3093 for (j = i; j < game_mm.num_pacman; j++)
3095 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3096 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3097 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3101 void ColorCycling(void)
3103 static int CC, Cc = 0;
3105 static int color, old = 0xF00, new = 0x010, mult = 1;
3106 static unsigned short red, green, blue;
3108 if (color_status == STATIC_COLORS)
3113 if (CC < Cc || CC > Cc + 2)
3117 color = old + new * mult;
3123 if (ABS(mult) == 16)
3133 red = 0x0e00 * ((color & 0xF00) >> 8);
3134 green = 0x0e00 * ((color & 0x0F0) >> 4);
3135 blue = 0x0e00 * ((color & 0x00F));
3136 SetRGB(pen_magicolor[0], red, green, blue);
3138 red = 0x1111 * ((color & 0xF00) >> 8);
3139 green = 0x1111 * ((color & 0x0F0) >> 4);
3140 blue = 0x1111 * ((color & 0x00F));
3141 SetRGB(pen_magicolor[1], red, green, blue);
3145 static void GameActions_MM_Ext(void)
3152 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3155 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3157 element = Tile[x][y];
3159 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3160 StartMoving_MM(x, y);
3161 else if (IS_MOVING(x, y))
3162 ContinueMoving_MM(x, y);
3163 else if (IS_EXPLODING(element))
3164 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3165 else if (element == EL_EXIT_OPENING)
3167 else if (element == EL_GRAY_BALL_OPENING)
3168 OpenSurpriseBall(x, y);
3169 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3171 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3174 DrawFieldAnimated_MM(x, y);
3177 AutoRotateMirrors();
3180 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3182 // redraw after Explode_MM() ...
3184 DrawLaser(0, DL_LASER_ENABLED);
3185 laser.redraw = FALSE;
3190 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3194 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3196 DrawLaser(0, DL_LASER_DISABLED);
3201 // skip all following game actions if game is over
3202 if (game_mm.game_over)
3205 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3209 GameOver_MM(GAME_OVER_NO_ENERGY);
3214 if (FrameReached(&energy_delay))
3216 if (game_mm.energy_left > 0)
3217 game_mm.energy_left--;
3219 // when out of energy, wait another frame to play "out of time" sound
3222 element = laser.dest_element;
3225 if (element != Tile[ELX][ELY])
3227 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3228 element, Tile[ELX][ELY]);
3232 if (!laser.overloaded && laser.overload_value == 0 &&
3233 element != EL_BOMB &&
3234 element != EL_MINE &&
3235 element != EL_BALL_GRAY &&
3236 element != EL_BLOCK_STONE &&
3237 element != EL_BLOCK_WOOD &&
3238 element != EL_FUSE_ON &&
3239 element != EL_FUEL_FULL &&
3240 !IS_WALL_ICE(element) &&
3241 !IS_WALL_AMOEBA(element))
3244 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3246 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3247 (!laser.overloaded && laser.overload_value > 0)) &&
3248 FrameReached(&overload_delay))
3250 if (laser.overloaded)
3251 laser.overload_value++;
3253 laser.overload_value--;
3255 if (game_mm.cheat_no_overload)
3257 laser.overloaded = FALSE;
3258 laser.overload_value = 0;
3261 game_mm.laser_overload_value = laser.overload_value;
3263 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3265 SetLaserColor(0xFF);
3267 DrawLaser(0, DL_LASER_ENABLED);
3270 if (!laser.overloaded)
3271 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3272 else if (setup.sound_loops)
3273 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3275 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3277 if (laser.overloaded)
3280 BlitBitmap(pix[PIX_DOOR], drawto,
3281 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3282 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3283 - laser.overload_value,
3284 OVERLOAD_XSIZE, laser.overload_value,
3285 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3286 - laser.overload_value);
3288 redraw_mask |= REDRAW_DOOR_1;
3293 BlitBitmap(pix[PIX_DOOR], drawto,
3294 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3295 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3296 DX_OVERLOAD, DY_OVERLOAD);
3298 redraw_mask |= REDRAW_DOOR_1;
3301 if (laser.overload_value == MAX_LASER_OVERLOAD)
3303 UpdateAndDisplayGameControlValues();
3307 GameOver_MM(GAME_OVER_OVERLOADED);
3318 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3320 if (game_mm.cheat_no_explosion)
3325 laser.dest_element = EL_EXPLODING_OPAQUE;
3330 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3332 laser.fuse_off = TRUE;
3336 DrawLaser(0, DL_LASER_DISABLED);
3337 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3340 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3342 static int new_elements[] =
3345 EL_MIRROR_FIXED_START,
3347 EL_POLAR_CROSS_START,
3353 int num_new_elements = sizeof(new_elements) / sizeof(int);
3354 int new_element = new_elements[RND(num_new_elements)];
3356 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3357 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3359 // !!! CHECK AGAIN: Laser on Polarizer !!!
3370 element = EL_MIRROR_START + RND(16);
3376 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3383 element = (rnd == 0 ? EL_FUSE_ON :
3384 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3385 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3386 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3387 EL_MIRROR_FIXED_START + rnd - 25);
3392 graphic = el2gfx(element);
3394 for (i = 0; i < 50; i++)
3400 BlitBitmap(pix[PIX_BACK], drawto,
3401 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3402 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3403 SX + ELX * TILEX + x,
3404 SY + ELY * TILEY + y);
3406 MarkTileDirty(ELX, ELY);
3409 DrawLaser(0, DL_LASER_ENABLED);
3411 Delay_WithScreenUpdates(50);
3414 Tile[ELX][ELY] = element;
3415 DrawField_MM(ELX, ELY);
3418 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3421 // above stuff: GRAY BALL -> PRISM !!!
3423 LX = ELX * TILEX + 14;
3424 LY = ELY * TILEY + 14;
3425 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3432 laser.num_edges -= 2;
3433 laser.num_damages--;
3437 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3438 if (laser.damage[i].is_mirror)
3442 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3444 DrawLaser(0, DL_LASER_DISABLED);
3446 DrawLaser(0, DL_LASER_DISABLED);
3455 if (IS_WALL_ICE(element) && CT > 50)
3457 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3460 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3461 Store[ELX][ELY] = EL_WALL_ICE;
3462 Store2[ELX][ELY] = laser.wall_mask;
3464 laser.dest_element = Tile[ELX][ELY];
3469 for (i = 0; i < 5; i++)
3475 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3479 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3481 Delay_WithScreenUpdates(100);
3484 if (Tile[ELX][ELY] == EL_WALL_ICE)
3485 Tile[ELX][ELY] = EL_EMPTY;
3489 LX = laser.edge[laser.num_edges].x - cSX2;
3490 LY = laser.edge[laser.num_edges].y - cSY2;
3493 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3494 if (laser.damage[i].is_mirror)
3498 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3500 DrawLaser(0, DL_LASER_DISABLED);
3507 if (IS_WALL_AMOEBA(element) && CT > 60)
3509 int k1, k2, k3, dx, dy, de, dm;
3510 int element2 = Tile[ELX][ELY];
3512 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3515 for (i = laser.num_damages - 1; i >= 0; i--)
3516 if (laser.damage[i].is_mirror)
3519 r = laser.num_edges;
3520 d = laser.num_damages;
3527 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3530 DrawLaser(0, DL_LASER_ENABLED);
3533 x = laser.damage[k1].x;
3534 y = laser.damage[k1].y;
3539 for (i = 0; i < 4; i++)
3541 if (laser.wall_mask & (1 << i))
3543 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3544 cSY + ELY * TILEY + 31 * (i / 2)))
3547 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3548 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3555 for (i = 0; i < 4; i++)
3557 if (laser.wall_mask & (1 << i))
3559 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3560 cSY + ELY * TILEY + 31 * (i / 2)))
3567 if (laser.num_beamers > 0 ||
3568 k1 < 1 || k2 < 4 || k3 < 4 ||
3569 CheckLaserPixel(cSX + ELX * TILEX + 14,
3570 cSY + ELY * TILEY + 14))
3572 laser.num_edges = r;
3573 laser.num_damages = d;
3575 DrawLaser(0, DL_LASER_DISABLED);
3578 Tile[ELX][ELY] = element | laser.wall_mask;
3582 de = Tile[ELX][ELY];
3583 dm = laser.wall_mask;
3587 int x = ELX, y = ELY;
3588 int wall_mask = laser.wall_mask;
3591 DrawLaser(0, DL_LASER_ENABLED);
3593 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3595 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3596 Store[x][y] = EL_WALL_AMOEBA;
3597 Store2[x][y] = wall_mask;
3603 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3605 DrawLaser(0, DL_LASER_ENABLED);
3607 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3609 for (i = 4; i >= 0; i--)
3611 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3614 Delay_WithScreenUpdates(20);
3617 DrawLaser(0, DL_LASER_ENABLED);
3622 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3623 laser.stops_inside_element && CT > native_mm_level.time_block)
3628 if (ABS(XS) > ABS(YS))
3635 for (i = 0; i < 4; i++)
3642 x = ELX + Step[k * 4].x;
3643 y = ELY + Step[k * 4].y;
3645 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3648 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3656 laser.overloaded = (element == EL_BLOCK_STONE);
3661 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3664 Tile[x][y] = element;
3666 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3669 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3671 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3672 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3680 if (element == EL_FUEL_FULL && CT > 10)
3682 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3685 BlitBitmap(pix[PIX_DOOR], drawto,
3686 DOOR_GFX_PAGEX4 + XX_ENERGY,
3687 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3688 ENERGY_XSIZE, i, DX_ENERGY,
3689 DY_ENERGY + ENERGY_YSIZE - i);
3692 redraw_mask |= REDRAW_DOOR_1;
3695 Delay_WithScreenUpdates(20);
3698 game_mm.energy_left = MAX_LASER_ENERGY;
3699 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3700 DrawField_MM(ELX, ELY);
3702 DrawLaser(0, DL_LASER_ENABLED);
3710 void GameActions_MM(struct MouseActionInfo action)
3712 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3713 boolean button_released = (action.button == MB_RELEASED);
3715 GameActions_MM_Ext();
3717 CheckSingleStepMode_MM(element_clicked, button_released);
3720 void MovePacMen(void)
3722 int mx, my, ox, oy, nx, ny;
3726 if (++pacman_nr >= game_mm.num_pacman)
3729 game_mm.pacman[pacman_nr].dir--;
3731 for (l = 1; l < 5; l++)
3733 game_mm.pacman[pacman_nr].dir++;
3735 if (game_mm.pacman[pacman_nr].dir > 4)
3736 game_mm.pacman[pacman_nr].dir = 1;
3738 if (game_mm.pacman[pacman_nr].dir % 2)
3741 my = game_mm.pacman[pacman_nr].dir - 2;
3746 mx = 3 - game_mm.pacman[pacman_nr].dir;
3749 ox = game_mm.pacman[pacman_nr].x;
3750 oy = game_mm.pacman[pacman_nr].y;
3753 element = Tile[nx][ny];
3755 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3758 if (!IS_EATABLE4PACMAN(element))
3761 if (ObjHit(nx, ny, HIT_POS_CENTER))
3764 Tile[ox][oy] = EL_EMPTY;
3766 EL_PACMAN_RIGHT - 1 +
3767 (game_mm.pacman[pacman_nr].dir - 1 +
3768 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3770 game_mm.pacman[pacman_nr].x = nx;
3771 game_mm.pacman[pacman_nr].y = ny;
3773 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3775 if (element != EL_EMPTY)
3777 int graphic = el2gfx(Tile[nx][ny]);
3782 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3785 ox = cSX + ox * TILEX;
3786 oy = cSY + oy * TILEY;
3788 for (i = 1; i < 33; i += 2)
3789 BlitBitmap(bitmap, window,
3790 src_x, src_y, TILEX, TILEY,
3791 ox + i * mx, oy + i * my);
3792 Ct = Ct + FrameCounter - CT;
3795 DrawField_MM(nx, ny);
3798 if (!laser.fuse_off)
3800 DrawLaser(0, DL_LASER_ENABLED);
3802 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3804 AddDamagedField(nx, ny);
3806 laser.damage[laser.num_damages - 1].edge = 0;
3810 if (element == EL_BOMB)
3811 DeletePacMan(nx, ny);
3813 if (IS_WALL_AMOEBA(element) &&
3814 (LX + 2 * XS) / TILEX == nx &&
3815 (LY + 2 * YS) / TILEY == ny)
3825 static void InitMovingField_MM(int x, int y, int direction)
3827 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3828 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3830 MovDir[x][y] = direction;
3831 MovDir[newx][newy] = direction;
3833 if (Tile[newx][newy] == EL_EMPTY)
3834 Tile[newx][newy] = EL_BLOCKED;
3837 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3839 int direction = MovDir[x][y];
3840 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3841 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3847 static void Blocked2Moving_MM(int x, int y,
3848 int *comes_from_x, int *comes_from_y)
3850 int oldx = x, oldy = y;
3851 int direction = MovDir[x][y];
3853 if (direction == MV_LEFT)
3855 else if (direction == MV_RIGHT)
3857 else if (direction == MV_UP)
3859 else if (direction == MV_DOWN)
3862 *comes_from_x = oldx;
3863 *comes_from_y = oldy;
3866 static int MovingOrBlocked2Element_MM(int x, int y)
3868 int element = Tile[x][y];
3870 if (element == EL_BLOCKED)
3874 Blocked2Moving_MM(x, y, &oldx, &oldy);
3876 return Tile[oldx][oldy];
3883 static void RemoveField(int x, int y)
3885 Tile[x][y] = EL_EMPTY;
3892 static void RemoveMovingField_MM(int x, int y)
3894 int oldx = x, oldy = y, newx = x, newy = y;
3896 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3899 if (IS_MOVING(x, y))
3901 Moving2Blocked_MM(x, y, &newx, &newy);
3902 if (Tile[newx][newy] != EL_BLOCKED)
3905 else if (Tile[x][y] == EL_BLOCKED)
3907 Blocked2Moving_MM(x, y, &oldx, &oldy);
3908 if (!IS_MOVING(oldx, oldy))
3912 Tile[oldx][oldy] = EL_EMPTY;
3913 Tile[newx][newy] = EL_EMPTY;
3914 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3915 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3917 DrawLevelField_MM(oldx, oldy);
3918 DrawLevelField_MM(newx, newy);
3921 void PlaySoundLevel(int x, int y, int sound_nr)
3923 int sx = SCREENX(x), sy = SCREENY(y);
3925 int silence_distance = 8;
3927 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3928 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3931 if (!IN_LEV_FIELD(x, y) ||
3932 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3933 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3936 volume = SOUND_MAX_VOLUME;
3939 stereo = (sx - SCR_FIELDX/2) * 12;
3941 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3942 if (stereo > SOUND_MAX_RIGHT)
3943 stereo = SOUND_MAX_RIGHT;
3944 if (stereo < SOUND_MAX_LEFT)
3945 stereo = SOUND_MAX_LEFT;
3948 if (!IN_SCR_FIELD(sx, sy))
3950 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3951 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3953 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3956 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3959 static void RaiseScore_MM(int value)
3961 game_mm.score += value;
3964 void RaiseScoreElement_MM(int element)
3969 case EL_PACMAN_RIGHT:
3971 case EL_PACMAN_LEFT:
3972 case EL_PACMAN_DOWN:
3973 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3977 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3982 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3986 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3995 // ----------------------------------------------------------------------------
3996 // Mirror Magic game engine snapshot handling functions
3997 // ----------------------------------------------------------------------------
3999 void SaveEngineSnapshotValues_MM(void)
4003 engine_snapshot_mm.game_mm = game_mm;
4004 engine_snapshot_mm.laser = laser;
4006 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4008 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4010 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4011 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4012 engine_snapshot_mm.Box[x][y] = Box[x][y];
4013 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4014 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4018 engine_snapshot_mm.LX = LX;
4019 engine_snapshot_mm.LY = LY;
4020 engine_snapshot_mm.XS = XS;
4021 engine_snapshot_mm.YS = YS;
4022 engine_snapshot_mm.ELX = ELX;
4023 engine_snapshot_mm.ELY = ELY;
4024 engine_snapshot_mm.CT = CT;
4025 engine_snapshot_mm.Ct = Ct;
4027 engine_snapshot_mm.last_LX = last_LX;
4028 engine_snapshot_mm.last_LY = last_LY;
4029 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4030 engine_snapshot_mm.hold_x = hold_x;
4031 engine_snapshot_mm.hold_y = hold_y;
4032 engine_snapshot_mm.pacman_nr = pacman_nr;
4034 engine_snapshot_mm.rotate_delay = rotate_delay;
4035 engine_snapshot_mm.pacman_delay = pacman_delay;
4036 engine_snapshot_mm.energy_delay = energy_delay;
4037 engine_snapshot_mm.overload_delay = overload_delay;
4040 void LoadEngineSnapshotValues_MM(void)
4044 // stored engine snapshot buffers already restored at this point
4046 game_mm = engine_snapshot_mm.game_mm;
4047 laser = engine_snapshot_mm.laser;
4049 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4051 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4053 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4054 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4055 Box[x][y] = engine_snapshot_mm.Box[x][y];
4056 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4057 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4061 LX = engine_snapshot_mm.LX;
4062 LY = engine_snapshot_mm.LY;
4063 XS = engine_snapshot_mm.XS;
4064 YS = engine_snapshot_mm.YS;
4065 ELX = engine_snapshot_mm.ELX;
4066 ELY = engine_snapshot_mm.ELY;
4067 CT = engine_snapshot_mm.CT;
4068 Ct = engine_snapshot_mm.Ct;
4070 last_LX = engine_snapshot_mm.last_LX;
4071 last_LY = engine_snapshot_mm.last_LY;
4072 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4073 hold_x = engine_snapshot_mm.hold_x;
4074 hold_y = engine_snapshot_mm.hold_y;
4075 pacman_nr = engine_snapshot_mm.pacman_nr;
4077 rotate_delay = engine_snapshot_mm.rotate_delay;
4078 pacman_delay = engine_snapshot_mm.pacman_delay;
4079 energy_delay = engine_snapshot_mm.energy_delay;
4080 overload_delay = engine_snapshot_mm.overload_delay;
4082 RedrawPlayfield_MM();
4085 static int getAngleFromTouchDelta(int dx, int dy, int base)
4087 double pi = 3.141592653;
4088 double rad = atan2((double)-dy, (double)dx);
4089 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4090 double deg = rad2 * 180.0 / pi;
4092 return (int)(deg * base / 360.0 + 0.5) % base;
4095 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4097 // calculate start (source) position to be at the middle of the tile
4098 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4099 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4100 int dx = dst_mx - src_mx;
4101 int dy = dst_my - src_my;
4110 if (!IN_LEV_FIELD(x, y))
4113 element = Tile[x][y];
4115 if (!IS_MCDUFFIN(element) &&
4116 !IS_MIRROR(element) &&
4117 !IS_BEAMER(element) &&
4118 !IS_POLAR(element) &&
4119 !IS_POLAR_CROSS(element) &&
4120 !IS_DF_MIRROR(element))
4123 angle_old = get_element_angle(element);
4125 if (IS_MCDUFFIN(element))
4127 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4128 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4129 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4130 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4133 else if (IS_MIRROR(element) ||
4134 IS_DF_MIRROR(element))
4136 for (i = 0; i < laser.num_damages; i++)
4138 if (laser.damage[i].x == x &&
4139 laser.damage[i].y == y &&
4140 ObjHit(x, y, HIT_POS_CENTER))
4142 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4143 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4150 if (angle_new == -1)
4152 if (IS_MIRROR(element) ||
4153 IS_DF_MIRROR(element) ||
4157 if (IS_POLAR_CROSS(element))
4160 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4163 button = (angle_new == angle_old ? 0 :
4164 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4165 MB_LEFTBUTTON : MB_RIGHTBUTTON);