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;
675 void InitGameActions_MM(void)
677 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
678 int cycle_steps_done = 0;
683 for (i = 0; i <= num_init_game_frames; i++)
685 if (i == num_init_game_frames)
686 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
687 else if (setup.sound_loops)
688 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
690 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
692 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
694 UpdateAndDisplayGameControlValues();
696 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
698 InitCycleElements_RotateSingleStep();
703 AdvanceFrameCounter();
713 if (setup.quick_doors)
720 if (game_mm.kettles_still_needed == 0)
723 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
724 SetTileCursorActive(TRUE);
726 ResetFrameCounter(&energy_delay);
729 static void FadeOutLaser(void)
733 for (i = 15; i >= 0; i--)
735 SetLaserColor(0x11 * i);
737 DrawLaser(0, DL_LASER_ENABLED);
740 Delay_WithScreenUpdates(50);
743 DrawLaser(0, DL_LASER_DISABLED);
745 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
748 static void GameOver_MM(int game_over_cause)
750 // do not handle game over if request dialog is already active
751 if (game.request_active)
754 game_mm.game_over = TRUE;
755 game_mm.game_over_cause = game_over_cause;
757 if (setup.ask_on_game_over)
758 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
759 "Bomb killed Mc Duffin! Play it again?" :
760 game_over_cause == GAME_OVER_NO_ENERGY ?
761 "Out of magic energy! Play it again?" :
762 game_over_cause == GAME_OVER_OVERLOADED ?
763 "Magic spell hit Mc Duffin! Play it again?" :
766 SetTileCursorActive(FALSE);
769 void AddLaserEdge(int lx, int ly)
774 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
776 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
781 laser.edge[laser.num_edges].x = cSX2 + lx;
782 laser.edge[laser.num_edges].y = cSY2 + ly;
788 void AddDamagedField(int ex, int ey)
790 laser.damage[laser.num_damages].is_mirror = FALSE;
791 laser.damage[laser.num_damages].angle = laser.current_angle;
792 laser.damage[laser.num_damages].edge = laser.num_edges;
793 laser.damage[laser.num_damages].x = ex;
794 laser.damage[laser.num_damages].y = ey;
798 static boolean StepBehind(void)
804 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
805 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
807 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
813 static int getMaskFromElement(int element)
815 if (IS_GRID(element))
816 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
817 else if (IS_MCDUFFIN(element))
818 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
819 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
820 return IMG_MM_MASK_RECTANGLE;
822 return IMG_MM_MASK_CIRCLE;
825 static int ScanPixel(void)
830 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
831 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
834 // follow laser beam until it hits something (at least the screen border)
835 while (hit_mask == HIT_MASK_NO_HIT)
841 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
842 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
844 Debug("game:mm:ScanPixel", "touched screen border!");
850 for (i = 0; i < 4; i++)
852 int px = LX + (i % 2) * 2;
853 int py = LY + (i / 2) * 2;
856 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
857 int ly = (py + TILEY) / TILEY - 1; // negative values!
860 if (IN_LEV_FIELD(lx, ly))
862 int element = Tile[lx][ly];
864 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
868 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
870 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
872 pixel = ((element & (1 << pos)) ? 1 : 0);
876 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
878 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
883 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
884 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
887 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
888 hit_mask |= (1 << i);
891 if (hit_mask == HIT_MASK_NO_HIT)
893 // hit nothing -- go on with another step
905 int end = 0, rf = laser.num_edges;
907 // do not scan laser again after the game was lost for whatever reason
908 if (game_mm.game_over)
911 laser.overloaded = FALSE;
912 laser.stops_inside_element = FALSE;
914 DrawLaser(0, DL_LASER_ENABLED);
917 Debug("game:mm:ScanLaser",
918 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
926 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
929 laser.overloaded = TRUE;
934 hit_mask = ScanPixel();
937 Debug("game:mm:ScanLaser",
938 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
942 // hit something -- check out what it was
943 ELX = (LX + XS) / TILEX;
944 ELY = (LY + YS) / TILEY;
947 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
948 hit_mask, LX, LY, ELX, ELY);
951 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
954 laser.dest_element = element;
959 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
961 /* we have hit the top-right and bottom-left element --
962 choose the bottom-left one */
963 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
964 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
965 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
966 ELX = (LX - 2) / TILEX;
967 ELY = (LY + 2) / TILEY;
970 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
972 /* we have hit the top-left and bottom-right element --
973 choose the top-left one */
975 ELX = (LX - 2) / TILEX;
976 ELY = (LY - 2) / TILEY;
980 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
981 hit_mask, LX, LY, ELX, ELY);
984 element = Tile[ELX][ELY];
985 laser.dest_element = element;
988 Debug("game:mm:ScanLaser",
989 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
992 LX % TILEX, LY % TILEY,
997 if (!IN_LEV_FIELD(ELX, ELY))
998 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1002 if (element == EL_EMPTY)
1004 if (!HitOnlyAnEdge(hit_mask))
1007 else if (element == EL_FUSE_ON)
1009 if (HitPolarizer(element, hit_mask))
1012 else if (IS_GRID(element) || IS_DF_GRID(element))
1014 if (HitPolarizer(element, hit_mask))
1017 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1018 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1020 if (HitBlock(element, hit_mask))
1027 else if (IS_MCDUFFIN(element))
1029 if (HitLaserSource(element, hit_mask))
1032 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1033 IS_RECEIVER(element))
1035 if (HitLaserDestination(element, hit_mask))
1038 else if (IS_WALL(element))
1040 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1042 if (HitReflectingWalls(element, hit_mask))
1047 if (HitAbsorbingWalls(element, hit_mask))
1053 if (HitElement(element, hit_mask))
1058 DrawLaser(rf - 1, DL_LASER_ENABLED);
1059 rf = laser.num_edges;
1063 if (laser.dest_element != Tile[ELX][ELY])
1065 Debug("game:mm:ScanLaser",
1066 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1067 laser.dest_element, Tile[ELX][ELY]);
1071 if (!end && !laser.stops_inside_element && !StepBehind())
1074 Debug("game:mm:ScanLaser", "Go one step back");
1080 AddLaserEdge(LX, LY);
1084 DrawLaser(rf - 1, DL_LASER_ENABLED);
1086 Ct = CT = FrameCounter;
1089 if (!IN_LEV_FIELD(ELX, ELY))
1090 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1094 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1100 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1101 start_edge, num_edges, mode);
1106 Warn("DrawLaserExt: start_edge < 0");
1113 Warn("DrawLaserExt: num_edges < 0");
1119 if (mode == DL_LASER_DISABLED)
1121 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1125 // now draw the laser to the backbuffer and (if enabled) to the screen
1126 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1128 redraw_mask |= REDRAW_FIELD;
1130 if (mode == DL_LASER_ENABLED)
1133 // after the laser was deleted, the "damaged" graphics must be restored
1134 if (laser.num_damages)
1136 int damage_start = 0;
1139 // determine the starting edge, from which graphics need to be restored
1142 for (i = 0; i < laser.num_damages; i++)
1144 if (laser.damage[i].edge == start_edge + 1)
1153 // restore graphics from this starting edge to the end of damage list
1154 for (i = damage_start; i < laser.num_damages; i++)
1156 int lx = laser.damage[i].x;
1157 int ly = laser.damage[i].y;
1158 int element = Tile[lx][ly];
1160 if (Hit[lx][ly] == laser.damage[i].edge)
1161 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1164 if (Box[lx][ly] == laser.damage[i].edge)
1167 if (IS_DRAWABLE(element))
1168 DrawField_MM(lx, ly);
1171 elx = laser.damage[damage_start].x;
1172 ely = laser.damage[damage_start].y;
1173 element = Tile[elx][ely];
1176 if (IS_BEAMER(element))
1180 for (i = 0; i < laser.num_beamers; i++)
1181 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1183 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1184 mode, elx, ely, Hit[elx][ely], start_edge);
1185 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1186 get_element_angle(element), laser.damage[damage_start].angle);
1190 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1191 laser.num_beamers > 0 &&
1192 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1194 // element is outgoing beamer
1195 laser.num_damages = damage_start + 1;
1197 if (IS_BEAMER(element))
1198 laser.current_angle = get_element_angle(element);
1202 // element is incoming beamer or other element
1203 laser.num_damages = damage_start;
1204 laser.current_angle = laser.damage[laser.num_damages].angle;
1209 // no damages but McDuffin himself (who needs to be redrawn anyway)
1211 elx = laser.start_edge.x;
1212 ely = laser.start_edge.y;
1213 element = Tile[elx][ely];
1216 laser.num_edges = start_edge + 1;
1217 if (start_edge == 0)
1218 laser.current_angle = laser.start_angle;
1220 LX = laser.edge[start_edge].x - cSX2;
1221 LY = laser.edge[start_edge].y - cSY2;
1222 XS = 2 * Step[laser.current_angle].x;
1223 YS = 2 * Step[laser.current_angle].y;
1226 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1232 if (IS_BEAMER(element) ||
1233 IS_FIBRE_OPTIC(element) ||
1234 IS_PACMAN(element) ||
1235 IS_POLAR(element) ||
1236 IS_POLAR_CROSS(element) ||
1237 element == EL_FUSE_ON)
1242 Debug("game:mm:DrawLaserExt", "element == %d", element);
1245 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1246 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1250 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1251 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1252 (laser.num_beamers == 0 ||
1253 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1255 // element is incoming beamer or other element
1256 step_size = -step_size;
1261 if (IS_BEAMER(element))
1262 Debug("game:mm:DrawLaserExt",
1263 "start_edge == %d, laser.beamer_edge == %d",
1264 start_edge, laser.beamer_edge);
1267 LX += step_size * XS;
1268 LY += step_size * YS;
1270 else if (element != EL_EMPTY)
1279 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1284 void DrawLaser(int start_edge, int mode)
1286 if (laser.num_edges - start_edge < 0)
1288 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1293 // check if laser is interrupted by beamer element
1294 if (laser.num_beamers > 0 &&
1295 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1297 if (mode == DL_LASER_ENABLED)
1300 int tmp_start_edge = start_edge;
1302 // draw laser segments forward from the start to the last beamer
1303 for (i = 0; i < laser.num_beamers; i++)
1305 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1307 if (tmp_num_edges <= 0)
1311 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1312 i, laser.beamer_edge[i], tmp_start_edge);
1315 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1317 tmp_start_edge = laser.beamer_edge[i];
1320 // draw last segment from last beamer to the end
1321 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1327 int last_num_edges = laser.num_edges;
1328 int num_beamers = laser.num_beamers;
1330 // delete laser segments backward from the end to the first beamer
1331 for (i = num_beamers - 1; i >= 0; i--)
1333 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1335 if (laser.beamer_edge[i] - start_edge <= 0)
1338 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1340 last_num_edges = laser.beamer_edge[i];
1341 laser.num_beamers--;
1345 if (last_num_edges - start_edge <= 0)
1346 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1347 last_num_edges, start_edge);
1350 // special case when rotating first beamer: delete laser edge on beamer
1351 // (but do not start scanning on previous edge to prevent mirror sound)
1352 if (last_num_edges - start_edge == 1 && start_edge > 0)
1353 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1355 // delete first segment from start to the first beamer
1356 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1361 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1364 game_mm.laser_enabled = mode;
1367 void DrawLaser_MM(void)
1369 DrawLaser(0, game_mm.laser_enabled);
1372 boolean HitElement(int element, int hit_mask)
1374 if (HitOnlyAnEdge(hit_mask))
1377 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1378 element = MovingOrBlocked2Element_MM(ELX, ELY);
1381 Debug("game:mm:HitElement", "(1): element == %d", element);
1385 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1386 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1389 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1393 AddDamagedField(ELX, ELY);
1395 // this is more precise: check if laser would go through the center
1396 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1398 // skip the whole element before continuing the scan
1404 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1406 if (LX/TILEX > ELX || LY/TILEY > ELY)
1408 /* skipping scan positions to the right and down skips one scan
1409 position too much, because this is only the top left scan position
1410 of totally four scan positions (plus one to the right, one to the
1411 bottom and one to the bottom right) */
1421 Debug("game:mm:HitElement", "(2): element == %d", element);
1424 if (LX + 5 * XS < 0 ||
1434 Debug("game:mm:HitElement", "(3): element == %d", element);
1437 if (IS_POLAR(element) &&
1438 ((element - EL_POLAR_START) % 2 ||
1439 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1441 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1443 laser.num_damages--;
1448 if (IS_POLAR_CROSS(element) &&
1449 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1451 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1453 laser.num_damages--;
1458 if (!IS_BEAMER(element) &&
1459 !IS_FIBRE_OPTIC(element) &&
1460 !IS_GRID_WOOD(element) &&
1461 element != EL_FUEL_EMPTY)
1464 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1465 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1467 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1470 LX = ELX * TILEX + 14;
1471 LY = ELY * TILEY + 14;
1473 AddLaserEdge(LX, LY);
1476 if (IS_MIRROR(element) ||
1477 IS_MIRROR_FIXED(element) ||
1478 IS_POLAR(element) ||
1479 IS_POLAR_CROSS(element) ||
1480 IS_DF_MIRROR(element) ||
1481 IS_DF_MIRROR_AUTO(element) ||
1482 element == EL_PRISM ||
1483 element == EL_REFRACTOR)
1485 int current_angle = laser.current_angle;
1488 laser.num_damages--;
1490 AddDamagedField(ELX, ELY);
1492 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1495 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1497 if (IS_MIRROR(element) ||
1498 IS_MIRROR_FIXED(element) ||
1499 IS_DF_MIRROR(element) ||
1500 IS_DF_MIRROR_AUTO(element))
1501 laser.current_angle = get_mirrored_angle(laser.current_angle,
1502 get_element_angle(element));
1504 if (element == EL_PRISM || element == EL_REFRACTOR)
1505 laser.current_angle = RND(16);
1507 XS = 2 * Step[laser.current_angle].x;
1508 YS = 2 * Step[laser.current_angle].y;
1510 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1515 LX += step_size * XS;
1516 LY += step_size * YS;
1518 // draw sparkles on mirror
1519 if ((IS_MIRROR(element) ||
1520 IS_MIRROR_FIXED(element) ||
1521 element == EL_PRISM) &&
1522 current_angle != laser.current_angle)
1524 MovDelay[ELX][ELY] = 11; // start animation
1527 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1528 current_angle != laser.current_angle)
1529 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1532 (get_opposite_angle(laser.current_angle) ==
1533 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1535 return (laser.overloaded ? TRUE : FALSE);
1538 if (element == EL_FUEL_FULL)
1540 laser.stops_inside_element = TRUE;
1545 if (element == EL_BOMB || element == EL_MINE)
1547 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1549 if (element == EL_MINE)
1550 laser.overloaded = TRUE;
1553 if (element == EL_KETTLE ||
1554 element == EL_CELL ||
1555 element == EL_KEY ||
1556 element == EL_LIGHTBALL ||
1557 element == EL_PACMAN ||
1560 if (!IS_PACMAN(element))
1563 if (element == EL_PACMAN)
1566 if (element == EL_KETTLE || element == EL_CELL)
1568 if (game_mm.kettles_still_needed > 0)
1569 game_mm.kettles_still_needed--;
1571 game.snapshot.collected_item = TRUE;
1573 if (game_mm.kettles_still_needed == 0)
1577 DrawLaser(0, DL_LASER_ENABLED);
1580 else if (element == EL_KEY)
1584 else if (IS_PACMAN(element))
1586 DeletePacMan(ELX, ELY);
1589 RaiseScoreElement_MM(element);
1594 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1596 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1598 DrawLaser(0, DL_LASER_ENABLED);
1600 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1602 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1603 game_mm.lights_still_needed--;
1607 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1608 game_mm.lights_still_needed++;
1611 DrawField_MM(ELX, ELY);
1612 DrawLaser(0, DL_LASER_ENABLED);
1617 laser.stops_inside_element = TRUE;
1623 Debug("game:mm:HitElement", "(4): element == %d", element);
1626 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1627 laser.num_beamers < MAX_NUM_BEAMERS &&
1628 laser.beamer[BEAMER_NR(element)][1].num)
1630 int beamer_angle = get_element_angle(element);
1631 int beamer_nr = BEAMER_NR(element);
1635 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1638 laser.num_damages--;
1640 if (IS_FIBRE_OPTIC(element) ||
1641 laser.current_angle == get_opposite_angle(beamer_angle))
1645 LX = ELX * TILEX + 14;
1646 LY = ELY * TILEY + 14;
1648 AddLaserEdge(LX, LY);
1649 AddDamagedField(ELX, ELY);
1651 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1654 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1656 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1657 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1658 ELX = laser.beamer[beamer_nr][pos].x;
1659 ELY = laser.beamer[beamer_nr][pos].y;
1660 LX = ELX * TILEX + 14;
1661 LY = ELY * TILEY + 14;
1663 if (IS_BEAMER(element))
1665 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1666 XS = 2 * Step[laser.current_angle].x;
1667 YS = 2 * Step[laser.current_angle].y;
1670 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1672 AddLaserEdge(LX, LY);
1673 AddDamagedField(ELX, ELY);
1675 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1678 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1680 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1685 LX += step_size * XS;
1686 LY += step_size * YS;
1688 laser.num_beamers++;
1697 boolean HitOnlyAnEdge(int hit_mask)
1699 // check if the laser hit only the edge of an element and, if so, go on
1702 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1706 if ((hit_mask == HIT_MASK_TOPLEFT ||
1707 hit_mask == HIT_MASK_TOPRIGHT ||
1708 hit_mask == HIT_MASK_BOTTOMLEFT ||
1709 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1710 laser.current_angle % 4) // angle is not 90°
1714 if (hit_mask == HIT_MASK_TOPLEFT)
1719 else if (hit_mask == HIT_MASK_TOPRIGHT)
1724 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1729 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1735 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1741 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1748 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1754 boolean HitPolarizer(int element, int hit_mask)
1756 if (HitOnlyAnEdge(hit_mask))
1759 if (IS_DF_GRID(element))
1761 int grid_angle = get_element_angle(element);
1764 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1765 grid_angle, laser.current_angle);
1768 AddLaserEdge(LX, LY);
1769 AddDamagedField(ELX, ELY);
1772 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1774 if (laser.current_angle == grid_angle ||
1775 laser.current_angle == get_opposite_angle(grid_angle))
1777 // skip the whole element before continuing the scan
1783 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1785 if (LX/TILEX > ELX || LY/TILEY > ELY)
1787 /* skipping scan positions to the right and down skips one scan
1788 position too much, because this is only the top left scan position
1789 of totally four scan positions (plus one to the right, one to the
1790 bottom and one to the bottom right) */
1796 AddLaserEdge(LX, LY);
1802 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1804 LX / TILEX, LY / TILEY,
1805 LX % TILEX, LY % TILEY);
1810 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1812 return HitReflectingWalls(element, hit_mask);
1816 return HitAbsorbingWalls(element, hit_mask);
1819 else if (IS_GRID_STEEL(element))
1821 return HitReflectingWalls(element, hit_mask);
1823 else // IS_GRID_WOOD
1825 return HitAbsorbingWalls(element, hit_mask);
1831 boolean HitBlock(int element, int hit_mask)
1833 boolean check = FALSE;
1835 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1836 game_mm.num_keys == 0)
1839 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1842 int ex = ELX * TILEX + 14;
1843 int ey = ELY * TILEY + 14;
1847 for (i = 1; i < 32; i++)
1852 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1857 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1858 return HitAbsorbingWalls(element, hit_mask);
1862 AddLaserEdge(LX - XS, LY - YS);
1863 AddDamagedField(ELX, ELY);
1866 Box[ELX][ELY] = laser.num_edges;
1868 return HitReflectingWalls(element, hit_mask);
1871 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1873 int xs = XS / 2, ys = YS / 2;
1874 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1875 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1877 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1878 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1880 laser.overloaded = (element == EL_GATE_STONE);
1885 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1886 (hit_mask == HIT_MASK_TOP ||
1887 hit_mask == HIT_MASK_LEFT ||
1888 hit_mask == HIT_MASK_RIGHT ||
1889 hit_mask == HIT_MASK_BOTTOM))
1890 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1891 hit_mask == HIT_MASK_BOTTOM),
1892 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1893 hit_mask == HIT_MASK_RIGHT));
1894 AddLaserEdge(LX, LY);
1900 if (element == EL_GATE_STONE && Box[ELX][ELY])
1902 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1914 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1916 int xs = XS / 2, ys = YS / 2;
1917 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1918 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1920 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1921 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1923 laser.overloaded = (element == EL_BLOCK_STONE);
1928 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1929 (hit_mask == HIT_MASK_TOP ||
1930 hit_mask == HIT_MASK_LEFT ||
1931 hit_mask == HIT_MASK_RIGHT ||
1932 hit_mask == HIT_MASK_BOTTOM))
1933 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1934 hit_mask == HIT_MASK_BOTTOM),
1935 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1936 hit_mask == HIT_MASK_RIGHT));
1937 AddDamagedField(ELX, ELY);
1939 LX = ELX * TILEX + 14;
1940 LY = ELY * TILEY + 14;
1942 AddLaserEdge(LX, LY);
1944 laser.stops_inside_element = TRUE;
1952 boolean HitLaserSource(int element, int hit_mask)
1954 if (HitOnlyAnEdge(hit_mask))
1957 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1959 laser.overloaded = TRUE;
1964 boolean HitLaserDestination(int element, int hit_mask)
1966 if (HitOnlyAnEdge(hit_mask))
1969 if (element != EL_EXIT_OPEN &&
1970 !(IS_RECEIVER(element) &&
1971 game_mm.kettles_still_needed == 0 &&
1972 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1974 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1979 if (IS_RECEIVER(element) ||
1980 (IS_22_5_ANGLE(laser.current_angle) &&
1981 (ELX != (LX + 6 * XS) / TILEX ||
1982 ELY != (LY + 6 * YS) / TILEY ||
1991 LX = ELX * TILEX + 14;
1992 LY = ELY * TILEY + 14;
1994 laser.stops_inside_element = TRUE;
1997 AddLaserEdge(LX, LY);
1998 AddDamagedField(ELX, ELY);
2000 if (game_mm.lights_still_needed == 0)
2002 game_mm.level_solved = TRUE;
2004 SetTileCursorActive(FALSE);
2010 boolean HitReflectingWalls(int element, int hit_mask)
2012 // check if laser hits side of a wall with an angle that is not 90°
2013 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2014 hit_mask == HIT_MASK_LEFT ||
2015 hit_mask == HIT_MASK_RIGHT ||
2016 hit_mask == HIT_MASK_BOTTOM))
2018 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2023 if (!IS_DF_GRID(element))
2024 AddLaserEdge(LX, LY);
2026 // check if laser hits wall with an angle of 45°
2027 if (!IS_22_5_ANGLE(laser.current_angle))
2029 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2032 laser.current_angle = get_mirrored_angle(laser.current_angle,
2035 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2038 laser.current_angle = get_mirrored_angle(laser.current_angle,
2042 AddLaserEdge(LX, LY);
2044 XS = 2 * Step[laser.current_angle].x;
2045 YS = 2 * Step[laser.current_angle].y;
2049 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2051 laser.current_angle = get_mirrored_angle(laser.current_angle,
2056 if (!IS_DF_GRID(element))
2057 AddLaserEdge(LX, LY);
2062 if (!IS_DF_GRID(element))
2063 AddLaserEdge(LX, LY + YS / 2);
2066 if (!IS_DF_GRID(element))
2067 AddLaserEdge(LX, LY);
2070 YS = 2 * Step[laser.current_angle].y;
2074 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2076 laser.current_angle = get_mirrored_angle(laser.current_angle,
2081 if (!IS_DF_GRID(element))
2082 AddLaserEdge(LX, LY);
2087 if (!IS_DF_GRID(element))
2088 AddLaserEdge(LX + XS / 2, LY);
2091 if (!IS_DF_GRID(element))
2092 AddLaserEdge(LX, LY);
2095 XS = 2 * Step[laser.current_angle].x;
2101 // reflection at the edge of reflecting DF style wall
2102 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2104 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2105 hit_mask == HIT_MASK_TOPRIGHT) ||
2106 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2107 hit_mask == HIT_MASK_TOPLEFT) ||
2108 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2109 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2110 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2111 hit_mask == HIT_MASK_BOTTOMRIGHT))
2114 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2115 ANG_MIRROR_135 : ANG_MIRROR_45);
2117 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2119 AddDamagedField(ELX, ELY);
2120 AddLaserEdge(LX, LY);
2122 laser.current_angle = get_mirrored_angle(laser.current_angle,
2130 AddLaserEdge(LX, LY);
2136 // reflection inside an edge of reflecting DF style wall
2137 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2139 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2140 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2141 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2142 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2143 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2144 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2145 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2146 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2149 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2150 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2151 ANG_MIRROR_135 : ANG_MIRROR_45);
2153 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2156 AddDamagedField(ELX, ELY);
2159 AddLaserEdge(LX - XS, LY - YS);
2160 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2161 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2163 laser.current_angle = get_mirrored_angle(laser.current_angle,
2171 AddLaserEdge(LX, LY);
2177 // check if laser hits DF style wall with an angle of 90°
2178 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2180 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2181 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2182 (IS_VERT_ANGLE(laser.current_angle) &&
2183 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2185 // laser at last step touched nothing or the same side of the wall
2186 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2188 AddDamagedField(ELX, ELY);
2195 last_hit_mask = hit_mask;
2202 if (!HitOnlyAnEdge(hit_mask))
2204 laser.overloaded = TRUE;
2212 boolean HitAbsorbingWalls(int element, int hit_mask)
2214 if (HitOnlyAnEdge(hit_mask))
2218 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2220 AddLaserEdge(LX - XS, LY - YS);
2227 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2229 AddLaserEdge(LX - XS, LY - YS);
2235 if (IS_WALL_WOOD(element) ||
2236 IS_DF_WALL_WOOD(element) ||
2237 IS_GRID_WOOD(element) ||
2238 IS_GRID_WOOD_FIXED(element) ||
2239 IS_GRID_WOOD_AUTO(element) ||
2240 element == EL_FUSE_ON ||
2241 element == EL_BLOCK_WOOD ||
2242 element == EL_GATE_WOOD)
2244 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2249 if (IS_WALL_ICE(element))
2253 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2254 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2256 // check if laser hits wall with an angle of 90°
2257 if (IS_90_ANGLE(laser.current_angle))
2258 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2260 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2264 for (i = 0; i < 4; i++)
2266 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2267 mask = 15 - (8 >> i);
2268 else if (ABS(XS) == 4 &&
2270 (XS > 0) == (i % 2) &&
2271 (YS < 0) == (i / 2))
2272 mask = 3 + (i / 2) * 9;
2273 else if (ABS(YS) == 4 &&
2275 (XS < 0) == (i % 2) &&
2276 (YS > 0) == (i / 2))
2277 mask = 5 + (i % 2) * 5;
2281 laser.wall_mask = mask;
2283 else if (IS_WALL_AMOEBA(element))
2285 int elx = (LX - 2 * XS) / TILEX;
2286 int ely = (LY - 2 * YS) / TILEY;
2287 int element2 = Tile[elx][ely];
2290 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2292 laser.dest_element = EL_EMPTY;
2300 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2301 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2303 if (IS_90_ANGLE(laser.current_angle))
2304 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2306 laser.dest_element = element2 | EL_WALL_AMOEBA;
2308 laser.wall_mask = mask;
2314 static void OpenExit(int x, int y)
2318 if (!MovDelay[x][y]) // next animation frame
2319 MovDelay[x][y] = 4 * delay;
2321 if (MovDelay[x][y]) // wait some time before next frame
2326 phase = MovDelay[x][y] / delay;
2328 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2329 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2331 if (!MovDelay[x][y])
2333 Tile[x][y] = EL_EXIT_OPEN;
2339 static void OpenSurpriseBall(int x, int y)
2343 if (!MovDelay[x][y]) // next animation frame
2344 MovDelay[x][y] = 50 * delay;
2346 if (MovDelay[x][y]) // wait some time before next frame
2350 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2353 int graphic = el2gfx(Store[x][y]);
2355 int dx = RND(26), dy = RND(26);
2357 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2359 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2360 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2362 MarkTileDirty(x, y);
2365 if (!MovDelay[x][y])
2367 Tile[x][y] = Store[x][y];
2376 static void MeltIce(int x, int y)
2381 if (!MovDelay[x][y]) // next animation frame
2382 MovDelay[x][y] = frames * delay;
2384 if (MovDelay[x][y]) // wait some time before next frame
2387 int wall_mask = Store2[x][y];
2388 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2391 phase = frames - MovDelay[x][y] / delay - 1;
2393 if (!MovDelay[x][y])
2397 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2398 Store[x][y] = Store2[x][y] = 0;
2400 DrawWalls_MM(x, y, Tile[x][y]);
2402 if (Tile[x][y] == EL_WALL_ICE)
2403 Tile[x][y] = EL_EMPTY;
2405 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2406 if (laser.damage[i].is_mirror)
2410 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2412 DrawLaser(0, DL_LASER_DISABLED);
2416 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2418 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2420 laser.redraw = TRUE;
2425 static void GrowAmoeba(int x, int y)
2430 if (!MovDelay[x][y]) // next animation frame
2431 MovDelay[x][y] = frames * delay;
2433 if (MovDelay[x][y]) // wait some time before next frame
2436 int wall_mask = Store2[x][y];
2437 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2440 phase = MovDelay[x][y] / delay;
2442 if (!MovDelay[x][y])
2444 Tile[x][y] = real_element;
2445 Store[x][y] = Store2[x][y] = 0;
2447 DrawWalls_MM(x, y, Tile[x][y]);
2448 DrawLaser(0, DL_LASER_ENABLED);
2450 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2452 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2457 static void DrawFieldAnimated_MM(int x, int y)
2459 int element = Tile[x][y];
2461 if (IS_BLOCKED(x, y))
2466 if (IS_MIRROR(element) ||
2467 IS_MIRROR_FIXED(element) ||
2468 element == EL_PRISM)
2470 if (MovDelay[x][y] != 0) // wait some time before next frame
2474 if (MovDelay[x][y] != 0)
2476 int graphic = IMG_TWINKLE_WHITE;
2477 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2479 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2484 laser.redraw = TRUE;
2487 static void Explode_MM(int x, int y, int phase, int mode)
2489 int num_phase = 9, delay = 2;
2490 int last_phase = num_phase * delay;
2491 int half_phase = (num_phase / 2) * delay;
2493 laser.redraw = TRUE;
2495 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2497 int center_element = Tile[x][y];
2499 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2501 // put moving element to center field (and let it explode there)
2502 center_element = MovingOrBlocked2Element_MM(x, y);
2503 RemoveMovingField_MM(x, y);
2505 Tile[x][y] = center_element;
2508 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2509 Store[x][y] = center_element;
2511 Store[x][y] = EL_EMPTY;
2513 Store2[x][y] = mode;
2514 Tile[x][y] = EL_EXPLODING_OPAQUE;
2515 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2516 ExplodePhase[x][y] = 1;
2521 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
2523 if (phase == half_phase)
2525 Tile[x][y] = EL_EXPLODING_TRANSP;
2527 if (x == ELX && y == ELY)
2531 if (phase == last_phase)
2533 if (Store[x][y] == EL_BOMB)
2535 DrawLaser(0, DL_LASER_DISABLED);
2538 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2539 Store[x][y] = EL_EMPTY;
2541 GameOver_MM(GAME_OVER_DELAYED);
2543 laser.overloaded = FALSE;
2545 else if (IS_MCDUFFIN(Store[x][y]))
2547 Store[x][y] = EL_EMPTY;
2549 GameOver_MM(GAME_OVER_BOMB);
2552 Tile[x][y] = Store[x][y];
2553 Store[x][y] = Store2[x][y] = 0;
2554 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2559 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2561 int graphic = IMG_MM_DEFAULT_EXPLODING;
2562 int graphic_phase = (phase / delay - 1);
2566 if (Store2[x][y] == EX_KETTLE)
2568 if (graphic_phase < 3)
2570 graphic = IMG_MM_KETTLE_EXPLODING;
2572 else if (graphic_phase < 5)
2578 graphic = IMG_EMPTY;
2582 else if (Store2[x][y] == EX_SHORT)
2584 if (graphic_phase < 4)
2590 graphic = IMG_EMPTY;
2595 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2597 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2598 cFX + x * TILEX, cFY + y * TILEY);
2600 MarkTileDirty(x, y);
2604 static void Bang_MM(int x, int y)
2606 int element = Tile[x][y];
2607 int mode = EX_NORMAL;
2610 DrawLaser(0, DL_LASER_ENABLED);
2629 if (IS_PACMAN(element))
2630 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2631 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2632 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2633 else if (element == EL_KEY)
2634 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2636 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2638 Explode_MM(x, y, EX_PHASE_START, mode);
2641 void TurnRound(int x, int y)
2653 { 0, 0 }, { 0, 0 }, { 0, 0 },
2658 int left, right, back;
2662 { MV_DOWN, MV_UP, MV_RIGHT },
2663 { MV_UP, MV_DOWN, MV_LEFT },
2665 { MV_LEFT, MV_RIGHT, MV_DOWN },
2666 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2667 { MV_RIGHT, MV_LEFT, MV_UP }
2670 int element = Tile[x][y];
2671 int old_move_dir = MovDir[x][y];
2672 int right_dir = turn[old_move_dir].right;
2673 int back_dir = turn[old_move_dir].back;
2674 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2675 int right_x = x + right_dx, right_y = y + right_dy;
2677 if (element == EL_PACMAN)
2679 boolean can_turn_right = FALSE;
2681 if (IN_LEV_FIELD(right_x, right_y) &&
2682 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2683 can_turn_right = TRUE;
2686 MovDir[x][y] = right_dir;
2688 MovDir[x][y] = back_dir;
2694 static void StartMoving_MM(int x, int y)
2696 int element = Tile[x][y];
2701 if (CAN_MOVE(element))
2705 if (MovDelay[x][y]) // wait some time before next movement
2713 // now make next step
2715 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2717 if (element == EL_PACMAN &&
2718 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2719 !ObjHit(newx, newy, HIT_POS_CENTER))
2721 Store[newx][newy] = Tile[newx][newy];
2722 Tile[newx][newy] = EL_EMPTY;
2724 DrawField_MM(newx, newy);
2726 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2727 ObjHit(newx, newy, HIT_POS_CENTER))
2729 // object was running against a wall
2736 InitMovingField_MM(x, y, MovDir[x][y]);
2740 ContinueMoving_MM(x, y);
2743 static void ContinueMoving_MM(int x, int y)
2745 int element = Tile[x][y];
2746 int direction = MovDir[x][y];
2747 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2748 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2749 int horiz_move = (dx!=0);
2750 int newx = x + dx, newy = y + dy;
2751 int step = (horiz_move ? dx : dy) * TILEX / 8;
2753 MovPos[x][y] += step;
2755 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2757 Tile[x][y] = EL_EMPTY;
2758 Tile[newx][newy] = element;
2760 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2761 MovDelay[newx][newy] = 0;
2763 if (!CAN_MOVE(element))
2764 MovDir[newx][newy] = 0;
2767 DrawField_MM(newx, newy);
2769 Stop[newx][newy] = TRUE;
2771 if (element == EL_PACMAN)
2773 if (Store[newx][newy] == EL_BOMB)
2774 Bang_MM(newx, newy);
2776 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2777 (LX + 2 * XS) / TILEX == newx &&
2778 (LY + 2 * YS) / TILEY == newy)
2785 else // still moving on
2790 laser.redraw = TRUE;
2793 boolean ClickElement(int x, int y, int button)
2795 static DelayCounter click_delay = { CLICK_DELAY };
2796 static boolean new_button = TRUE;
2797 boolean element_clicked = FALSE;
2802 // initialize static variables
2803 click_delay.count = 0;
2804 click_delay.value = CLICK_DELAY;
2810 // do not rotate objects hit by the laser after the game was solved
2811 if (game_mm.level_solved && Hit[x][y])
2814 if (button == MB_RELEASED)
2817 click_delay.value = CLICK_DELAY;
2819 // release eventually hold auto-rotating mirror
2820 RotateMirror(x, y, MB_RELEASED);
2825 if (!FrameReached(&click_delay) && !new_button)
2828 if (button == MB_MIDDLEBUTTON) // middle button has no function
2831 if (!IN_LEV_FIELD(x, y))
2834 if (Tile[x][y] == EL_EMPTY)
2837 element = Tile[x][y];
2839 if (IS_MIRROR(element) ||
2840 IS_BEAMER(element) ||
2841 IS_POLAR(element) ||
2842 IS_POLAR_CROSS(element) ||
2843 IS_DF_MIRROR(element) ||
2844 IS_DF_MIRROR_AUTO(element))
2846 RotateMirror(x, y, button);
2848 element_clicked = TRUE;
2850 else if (IS_MCDUFFIN(element))
2852 if (!laser.fuse_off)
2854 DrawLaser(0, DL_LASER_DISABLED);
2861 element = get_rotated_element(element, BUTTON_ROTATION(button));
2862 laser.start_angle = get_element_angle(element);
2866 Tile[x][y] = element;
2873 if (!laser.fuse_off)
2876 element_clicked = TRUE;
2878 else if (element == EL_FUSE_ON && laser.fuse_off)
2880 if (x != laser.fuse_x || y != laser.fuse_y)
2883 laser.fuse_off = FALSE;
2884 laser.fuse_x = laser.fuse_y = -1;
2886 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2889 element_clicked = TRUE;
2891 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2893 laser.fuse_off = TRUE;
2896 laser.overloaded = FALSE;
2898 DrawLaser(0, DL_LASER_DISABLED);
2899 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2901 element_clicked = TRUE;
2903 else if (element == EL_LIGHTBALL)
2906 RaiseScoreElement_MM(element);
2907 DrawLaser(0, DL_LASER_ENABLED);
2909 element_clicked = TRUE;
2912 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2915 return element_clicked;
2918 void RotateMirror(int x, int y, int button)
2920 if (button == MB_RELEASED)
2922 // release eventually hold auto-rotating mirror
2929 if (IS_MIRROR(Tile[x][y]) ||
2930 IS_POLAR_CROSS(Tile[x][y]) ||
2931 IS_POLAR(Tile[x][y]) ||
2932 IS_BEAMER(Tile[x][y]) ||
2933 IS_DF_MIRROR(Tile[x][y]) ||
2934 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2935 IS_GRID_WOOD_AUTO(Tile[x][y]))
2937 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2939 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2941 if (button == MB_LEFTBUTTON)
2943 // left mouse button only for manual adjustment, no auto-rotating;
2944 // freeze mirror for until mouse button released
2948 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2950 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2954 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2956 int edge = Hit[x][y];
2962 DrawLaser(edge - 1, DL_LASER_DISABLED);
2966 else if (ObjHit(x, y, HIT_POS_CENTER))
2968 int edge = Hit[x][y];
2972 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2977 DrawLaser(edge - 1, DL_LASER_DISABLED);
2984 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2989 if ((IS_BEAMER(Tile[x][y]) ||
2990 IS_POLAR(Tile[x][y]) ||
2991 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2995 if (IS_BEAMER(Tile[x][y]))
2998 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2999 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3011 DrawLaser(0, DL_LASER_ENABLED);
3015 static void AutoRotateMirrors(void)
3019 if (!FrameReached(&rotate_delay))
3022 for (x = 0; x < lev_fieldx; x++)
3024 for (y = 0; y < lev_fieldy; y++)
3026 int element = Tile[x][y];
3028 // do not rotate objects hit by the laser after the game was solved
3029 if (game_mm.level_solved && Hit[x][y])
3032 if (IS_DF_MIRROR_AUTO(element) ||
3033 IS_GRID_WOOD_AUTO(element) ||
3034 IS_GRID_STEEL_AUTO(element) ||
3035 element == EL_REFRACTOR)
3036 RotateMirror(x, y, MB_RIGHTBUTTON);
3041 boolean ObjHit(int obx, int oby, int bits)
3048 if (bits & HIT_POS_CENTER)
3050 if (CheckLaserPixel(cSX + obx + 15,
3055 if (bits & HIT_POS_EDGE)
3057 for (i = 0; i < 4; i++)
3058 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3059 cSY + oby + 31 * (i / 2)))
3063 if (bits & HIT_POS_BETWEEN)
3065 for (i = 0; i < 4; i++)
3066 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3067 cSY + 4 + oby + 22 * (i / 2)))
3074 void DeletePacMan(int px, int py)
3080 if (game_mm.num_pacman <= 1)
3082 game_mm.num_pacman = 0;
3086 for (i = 0; i < game_mm.num_pacman; i++)
3087 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3090 game_mm.num_pacman--;
3092 for (j = i; j < game_mm.num_pacman; j++)
3094 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3095 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3096 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3100 void ColorCycling(void)
3102 static int CC, Cc = 0;
3104 static int color, old = 0xF00, new = 0x010, mult = 1;
3105 static unsigned short red, green, blue;
3107 if (color_status == STATIC_COLORS)
3112 if (CC < Cc || CC > Cc + 2)
3116 color = old + new * mult;
3122 if (ABS(mult) == 16)
3132 red = 0x0e00 * ((color & 0xF00) >> 8);
3133 green = 0x0e00 * ((color & 0x0F0) >> 4);
3134 blue = 0x0e00 * ((color & 0x00F));
3135 SetRGB(pen_magicolor[0], red, green, blue);
3137 red = 0x1111 * ((color & 0xF00) >> 8);
3138 green = 0x1111 * ((color & 0x0F0) >> 4);
3139 blue = 0x1111 * ((color & 0x00F));
3140 SetRGB(pen_magicolor[1], red, green, blue);
3144 static void GameActions_MM_Ext(void)
3151 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3154 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3156 element = Tile[x][y];
3158 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3159 StartMoving_MM(x, y);
3160 else if (IS_MOVING(x, y))
3161 ContinueMoving_MM(x, y);
3162 else if (IS_EXPLODING(element))
3163 Explode_MM(x, y, ExplodePhase[x][y], EX_NORMAL);
3164 else if (element == EL_EXIT_OPENING)
3166 else if (element == EL_GRAY_BALL_OPENING)
3167 OpenSurpriseBall(x, y);
3168 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3170 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3173 DrawFieldAnimated_MM(x, y);
3176 AutoRotateMirrors();
3179 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3181 // redraw after Explode_MM() ...
3183 DrawLaser(0, DL_LASER_ENABLED);
3184 laser.redraw = FALSE;
3189 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3193 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3195 DrawLaser(0, DL_LASER_DISABLED);
3200 // skip all following game actions if game is over
3201 if (game_mm.game_over)
3204 if (game_mm.energy_left == 0 && !game.no_level_time_limit && game.time_limit)
3208 GameOver_MM(GAME_OVER_NO_ENERGY);
3213 if (FrameReached(&energy_delay))
3215 if (game_mm.energy_left > 0)
3216 game_mm.energy_left--;
3218 // when out of energy, wait another frame to play "out of time" sound
3221 element = laser.dest_element;
3224 if (element != Tile[ELX][ELY])
3226 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3227 element, Tile[ELX][ELY]);
3231 if (!laser.overloaded && laser.overload_value == 0 &&
3232 element != EL_BOMB &&
3233 element != EL_MINE &&
3234 element != EL_BALL_GRAY &&
3235 element != EL_BLOCK_STONE &&
3236 element != EL_BLOCK_WOOD &&
3237 element != EL_FUSE_ON &&
3238 element != EL_FUEL_FULL &&
3239 !IS_WALL_ICE(element) &&
3240 !IS_WALL_AMOEBA(element))
3243 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3245 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3246 (!laser.overloaded && laser.overload_value > 0)) &&
3247 FrameReached(&overload_delay))
3249 if (laser.overloaded)
3250 laser.overload_value++;
3252 laser.overload_value--;
3254 if (game_mm.cheat_no_overload)
3256 laser.overloaded = FALSE;
3257 laser.overload_value = 0;
3260 game_mm.laser_overload_value = laser.overload_value;
3262 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3264 SetLaserColor(0xFF);
3266 DrawLaser(0, DL_LASER_ENABLED);
3269 if (!laser.overloaded)
3270 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3271 else if (setup.sound_loops)
3272 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3274 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3276 if (laser.overloaded)
3279 BlitBitmap(pix[PIX_DOOR], drawto,
3280 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3281 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3282 - laser.overload_value,
3283 OVERLOAD_XSIZE, laser.overload_value,
3284 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3285 - laser.overload_value);
3287 redraw_mask |= REDRAW_DOOR_1;
3292 BlitBitmap(pix[PIX_DOOR], drawto,
3293 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3294 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3295 DX_OVERLOAD, DY_OVERLOAD);
3297 redraw_mask |= REDRAW_DOOR_1;
3300 if (laser.overload_value == MAX_LASER_OVERLOAD)
3302 UpdateAndDisplayGameControlValues();
3306 GameOver_MM(GAME_OVER_OVERLOADED);
3317 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3319 if (game_mm.cheat_no_explosion)
3324 laser.dest_element = EL_EXPLODING_OPAQUE;
3329 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3331 laser.fuse_off = TRUE;
3335 DrawLaser(0, DL_LASER_DISABLED);
3336 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3339 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3341 static int new_elements[] =
3344 EL_MIRROR_FIXED_START,
3346 EL_POLAR_CROSS_START,
3352 int num_new_elements = sizeof(new_elements) / sizeof(int);
3353 int new_element = new_elements[RND(num_new_elements)];
3355 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3356 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3358 // !!! CHECK AGAIN: Laser on Polarizer !!!
3369 element = EL_MIRROR_START + RND(16);
3375 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3382 element = (rnd == 0 ? EL_FUSE_ON :
3383 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3384 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3385 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3386 EL_MIRROR_FIXED_START + rnd - 25);
3391 graphic = el2gfx(element);
3393 for (i = 0; i < 50; i++)
3399 BlitBitmap(pix[PIX_BACK], drawto,
3400 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3401 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3402 SX + ELX * TILEX + x,
3403 SY + ELY * TILEY + y);
3405 MarkTileDirty(ELX, ELY);
3408 DrawLaser(0, DL_LASER_ENABLED);
3410 Delay_WithScreenUpdates(50);
3413 Tile[ELX][ELY] = element;
3414 DrawField_MM(ELX, ELY);
3417 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3420 // above stuff: GRAY BALL -> PRISM !!!
3422 LX = ELX * TILEX + 14;
3423 LY = ELY * TILEY + 14;
3424 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3431 laser.num_edges -= 2;
3432 laser.num_damages--;
3436 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3437 if (laser.damage[i].is_mirror)
3441 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3443 DrawLaser(0, DL_LASER_DISABLED);
3445 DrawLaser(0, DL_LASER_DISABLED);
3454 if (IS_WALL_ICE(element) && CT > 50)
3456 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3459 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3460 Store[ELX][ELY] = EL_WALL_ICE;
3461 Store2[ELX][ELY] = laser.wall_mask;
3463 laser.dest_element = Tile[ELX][ELY];
3468 for (i = 0; i < 5; i++)
3474 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3478 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3480 Delay_WithScreenUpdates(100);
3483 if (Tile[ELX][ELY] == EL_WALL_ICE)
3484 Tile[ELX][ELY] = EL_EMPTY;
3488 LX = laser.edge[laser.num_edges].x - cSX2;
3489 LY = laser.edge[laser.num_edges].y - cSY2;
3492 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3493 if (laser.damage[i].is_mirror)
3497 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3499 DrawLaser(0, DL_LASER_DISABLED);
3506 if (IS_WALL_AMOEBA(element) && CT > 60)
3508 int k1, k2, k3, dx, dy, de, dm;
3509 int element2 = Tile[ELX][ELY];
3511 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3514 for (i = laser.num_damages - 1; i >= 0; i--)
3515 if (laser.damage[i].is_mirror)
3518 r = laser.num_edges;
3519 d = laser.num_damages;
3526 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3529 DrawLaser(0, DL_LASER_ENABLED);
3532 x = laser.damage[k1].x;
3533 y = laser.damage[k1].y;
3538 for (i = 0; i < 4; i++)
3540 if (laser.wall_mask & (1 << i))
3542 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3543 cSY + ELY * TILEY + 31 * (i / 2)))
3546 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3547 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3554 for (i = 0; i < 4; i++)
3556 if (laser.wall_mask & (1 << i))
3558 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3559 cSY + ELY * TILEY + 31 * (i / 2)))
3566 if (laser.num_beamers > 0 ||
3567 k1 < 1 || k2 < 4 || k3 < 4 ||
3568 CheckLaserPixel(cSX + ELX * TILEX + 14,
3569 cSY + ELY * TILEY + 14))
3571 laser.num_edges = r;
3572 laser.num_damages = d;
3574 DrawLaser(0, DL_LASER_DISABLED);
3577 Tile[ELX][ELY] = element | laser.wall_mask;
3581 de = Tile[ELX][ELY];
3582 dm = laser.wall_mask;
3586 int x = ELX, y = ELY;
3587 int wall_mask = laser.wall_mask;
3590 DrawLaser(0, DL_LASER_ENABLED);
3592 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3594 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3595 Store[x][y] = EL_WALL_AMOEBA;
3596 Store2[x][y] = wall_mask;
3602 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3604 DrawLaser(0, DL_LASER_ENABLED);
3606 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3608 for (i = 4; i >= 0; i--)
3610 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3613 Delay_WithScreenUpdates(20);
3616 DrawLaser(0, DL_LASER_ENABLED);
3621 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3622 laser.stops_inside_element && CT > native_mm_level.time_block)
3627 if (ABS(XS) > ABS(YS))
3634 for (i = 0; i < 4; i++)
3641 x = ELX + Step[k * 4].x;
3642 y = ELY + Step[k * 4].y;
3644 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3647 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3655 laser.overloaded = (element == EL_BLOCK_STONE);
3660 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3663 Tile[x][y] = element;
3665 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3668 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3670 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3671 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3679 if (element == EL_FUEL_FULL && CT > 10)
3681 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
3682 int start = num_init_game_frames * game_mm.energy_left / native_mm_level.time;
3684 for (i = start; i <= num_init_game_frames; i++)
3686 if (i == num_init_game_frames)
3687 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3688 else if (setup.sound_loops)
3689 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3691 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
3693 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
3695 UpdateAndDisplayGameControlValues();
3700 Tile[ELX][ELY] = laser.dest_element = EL_FUEL_EMPTY;
3702 DrawField_MM(ELX, ELY);
3704 DrawLaser(0, DL_LASER_ENABLED);
3712 void GameActions_MM(struct MouseActionInfo action)
3714 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3715 boolean button_released = (action.button == MB_RELEASED);
3717 GameActions_MM_Ext();
3719 CheckSingleStepMode_MM(element_clicked, button_released);
3722 void MovePacMen(void)
3724 int mx, my, ox, oy, nx, ny;
3728 if (++pacman_nr >= game_mm.num_pacman)
3731 game_mm.pacman[pacman_nr].dir--;
3733 for (l = 1; l < 5; l++)
3735 game_mm.pacman[pacman_nr].dir++;
3737 if (game_mm.pacman[pacman_nr].dir > 4)
3738 game_mm.pacman[pacman_nr].dir = 1;
3740 if (game_mm.pacman[pacman_nr].dir % 2)
3743 my = game_mm.pacman[pacman_nr].dir - 2;
3748 mx = 3 - game_mm.pacman[pacman_nr].dir;
3751 ox = game_mm.pacman[pacman_nr].x;
3752 oy = game_mm.pacman[pacman_nr].y;
3755 element = Tile[nx][ny];
3757 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3760 if (!IS_EATABLE4PACMAN(element))
3763 if (ObjHit(nx, ny, HIT_POS_CENTER))
3766 Tile[ox][oy] = EL_EMPTY;
3768 EL_PACMAN_RIGHT - 1 +
3769 (game_mm.pacman[pacman_nr].dir - 1 +
3770 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3772 game_mm.pacman[pacman_nr].x = nx;
3773 game_mm.pacman[pacman_nr].y = ny;
3775 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3777 if (element != EL_EMPTY)
3779 int graphic = el2gfx(Tile[nx][ny]);
3784 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3787 ox = cSX + ox * TILEX;
3788 oy = cSY + oy * TILEY;
3790 for (i = 1; i < 33; i += 2)
3791 BlitBitmap(bitmap, window,
3792 src_x, src_y, TILEX, TILEY,
3793 ox + i * mx, oy + i * my);
3794 Ct = Ct + FrameCounter - CT;
3797 DrawField_MM(nx, ny);
3800 if (!laser.fuse_off)
3802 DrawLaser(0, DL_LASER_ENABLED);
3804 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3806 AddDamagedField(nx, ny);
3808 laser.damage[laser.num_damages - 1].edge = 0;
3812 if (element == EL_BOMB)
3813 DeletePacMan(nx, ny);
3815 if (IS_WALL_AMOEBA(element) &&
3816 (LX + 2 * XS) / TILEX == nx &&
3817 (LY + 2 * YS) / TILEY == ny)
3827 static void InitMovingField_MM(int x, int y, int direction)
3829 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3830 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3832 MovDir[x][y] = direction;
3833 MovDir[newx][newy] = direction;
3835 if (Tile[newx][newy] == EL_EMPTY)
3836 Tile[newx][newy] = EL_BLOCKED;
3839 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3841 int direction = MovDir[x][y];
3842 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3843 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3849 static void Blocked2Moving_MM(int x, int y,
3850 int *comes_from_x, int *comes_from_y)
3852 int oldx = x, oldy = y;
3853 int direction = MovDir[x][y];
3855 if (direction == MV_LEFT)
3857 else if (direction == MV_RIGHT)
3859 else if (direction == MV_UP)
3861 else if (direction == MV_DOWN)
3864 *comes_from_x = oldx;
3865 *comes_from_y = oldy;
3868 static int MovingOrBlocked2Element_MM(int x, int y)
3870 int element = Tile[x][y];
3872 if (element == EL_BLOCKED)
3876 Blocked2Moving_MM(x, y, &oldx, &oldy);
3878 return Tile[oldx][oldy];
3885 static void RemoveField(int x, int y)
3887 Tile[x][y] = EL_EMPTY;
3894 static void RemoveMovingField_MM(int x, int y)
3896 int oldx = x, oldy = y, newx = x, newy = y;
3898 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3901 if (IS_MOVING(x, y))
3903 Moving2Blocked_MM(x, y, &newx, &newy);
3904 if (Tile[newx][newy] != EL_BLOCKED)
3907 else if (Tile[x][y] == EL_BLOCKED)
3909 Blocked2Moving_MM(x, y, &oldx, &oldy);
3910 if (!IS_MOVING(oldx, oldy))
3914 Tile[oldx][oldy] = EL_EMPTY;
3915 Tile[newx][newy] = EL_EMPTY;
3916 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3917 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3919 DrawLevelField_MM(oldx, oldy);
3920 DrawLevelField_MM(newx, newy);
3923 void PlaySoundLevel(int x, int y, int sound_nr)
3925 int sx = SCREENX(x), sy = SCREENY(y);
3927 int silence_distance = 8;
3929 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3930 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3933 if (!IN_LEV_FIELD(x, y) ||
3934 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3935 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3938 volume = SOUND_MAX_VOLUME;
3941 stereo = (sx - SCR_FIELDX/2) * 12;
3943 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3944 if (stereo > SOUND_MAX_RIGHT)
3945 stereo = SOUND_MAX_RIGHT;
3946 if (stereo < SOUND_MAX_LEFT)
3947 stereo = SOUND_MAX_LEFT;
3950 if (!IN_SCR_FIELD(sx, sy))
3952 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3953 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3955 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3958 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3961 static void RaiseScore_MM(int value)
3963 game_mm.score += value;
3966 void RaiseScoreElement_MM(int element)
3971 case EL_PACMAN_RIGHT:
3973 case EL_PACMAN_LEFT:
3974 case EL_PACMAN_DOWN:
3975 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3979 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3984 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3988 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3997 // ----------------------------------------------------------------------------
3998 // Mirror Magic game engine snapshot handling functions
3999 // ----------------------------------------------------------------------------
4001 void SaveEngineSnapshotValues_MM(void)
4005 engine_snapshot_mm.game_mm = game_mm;
4006 engine_snapshot_mm.laser = laser;
4008 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4010 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4012 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4013 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4014 engine_snapshot_mm.Box[x][y] = Box[x][y];
4015 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4019 engine_snapshot_mm.LX = LX;
4020 engine_snapshot_mm.LY = LY;
4021 engine_snapshot_mm.XS = XS;
4022 engine_snapshot_mm.YS = YS;
4023 engine_snapshot_mm.ELX = ELX;
4024 engine_snapshot_mm.ELY = ELY;
4025 engine_snapshot_mm.CT = CT;
4026 engine_snapshot_mm.Ct = Ct;
4028 engine_snapshot_mm.last_LX = last_LX;
4029 engine_snapshot_mm.last_LY = last_LY;
4030 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4031 engine_snapshot_mm.hold_x = hold_x;
4032 engine_snapshot_mm.hold_y = hold_y;
4033 engine_snapshot_mm.pacman_nr = pacman_nr;
4035 engine_snapshot_mm.rotate_delay = rotate_delay;
4036 engine_snapshot_mm.pacman_delay = pacman_delay;
4037 engine_snapshot_mm.energy_delay = energy_delay;
4038 engine_snapshot_mm.overload_delay = overload_delay;
4041 void LoadEngineSnapshotValues_MM(void)
4045 // stored engine snapshot buffers already restored at this point
4047 game_mm = engine_snapshot_mm.game_mm;
4048 laser = engine_snapshot_mm.laser;
4050 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4052 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4054 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4055 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4056 Box[x][y] = engine_snapshot_mm.Box[x][y];
4057 Angle[x][y] = engine_snapshot_mm.Angle[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);