1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
32 // special positions in the game control window (relative to control window)
41 #define XX_OVERLOAD 60
42 #define YY_OVERLOAD YY_ENERGY
44 // special positions in the game control window (relative to main window)
45 #define DX_LEVEL (DX + XX_LEVEL)
46 #define DY_LEVEL (DY + YY_LEVEL)
47 #define DX_KETTLES (DX + XX_KETTLES)
48 #define DY_KETTLES (DY + YY_KETTLES)
49 #define DX_SCORE (DX + XX_SCORE)
50 #define DY_SCORE (DY + YY_SCORE)
51 #define DX_ENERGY (DX + XX_ENERGY)
52 #define DY_ENERGY (DY + YY_ENERGY)
53 #define DX_OVERLOAD (DX + XX_OVERLOAD)
54 #define DY_OVERLOAD (DY + YY_OVERLOAD)
56 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
57 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
59 // game button identifiers
60 #define GAME_CTRL_ID_LEFT 0
61 #define GAME_CTRL_ID_MIDDLE 1
62 #define GAME_CTRL_ID_RIGHT 2
64 #define NUM_GAME_BUTTONS 3
66 // values for DrawLaser()
67 #define DL_LASER_DISABLED 0
68 #define DL_LASER_ENABLED 1
70 // values for 'click_delay_value' in ClickElement()
71 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
72 #define CLICK_DELAY 6 // delay (frames) for pressed butten
74 #define AUTO_ROTATE_DELAY CLICK_DELAY
75 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
76 #define NUM_INIT_CYCLE_STEPS 16
77 #define PACMAN_MOVE_DELAY 12
78 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
79 #define HEALTH_DEC_DELAY 3
80 #define HEALTH_INC_DELAY 9
81 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
83 #define BEGIN_NO_HEADLESS \
85 boolean last_headless = program.headless; \
87 program.headless = FALSE; \
89 #define END_NO_HEADLESS \
90 program.headless = last_headless; \
93 // forward declaration for internal use
94 static int MovingOrBlocked2Element_MM(int, int);
95 static void Bang_MM(int, int);
96 static void RaiseScore_MM(int);
97 static void RaiseScoreElement_MM(int);
98 static void RemoveMovingField_MM(int, int);
99 static void InitMovingField_MM(int, int, int);
100 static void ContinueMoving_MM(int, int);
101 static void Moving2Blocked_MM(int, int, int *, int *);
103 // bitmap for laser beam detection
104 static Bitmap *laser_bitmap = NULL;
106 // variables for laser control
107 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
108 static int hold_x = -1, hold_y = -1;
110 // variables for pacman control
111 static int pacman_nr = -1;
113 // various game engine delay counters
114 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
115 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
116 static DelayCounter energy_delay = { ENERGY_DELAY };
117 static DelayCounter overload_delay = { 0 };
119 // element masks for scanning pixels of MM elements
120 static const char mm_masks[10][16][16 + 1] =
304 static int get_element_angle(int element)
306 int element_phase = get_element_phase(element);
308 if (IS_MIRROR_FIXED(element) ||
309 IS_MCDUFFIN(element) ||
311 IS_RECEIVER(element))
312 return 4 * element_phase;
314 return element_phase;
317 static int get_opposite_angle(int angle)
319 int opposite_angle = angle + ANG_RAY_180;
321 // make sure "opposite_angle" is in valid interval [0, 15]
322 return (opposite_angle + 16) % 16;
325 static int get_mirrored_angle(int laser_angle, int mirror_angle)
327 int reflected_angle = 16 - laser_angle + mirror_angle;
329 // make sure "reflected_angle" is in valid interval [0, 15]
330 return (reflected_angle + 16) % 16;
333 static void DrawLaserLines(struct XY *points, int num_points, int mode)
335 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
336 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
338 DrawLines(drawto, points, num_points, pixel_drawto);
342 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
347 static boolean CheckLaserPixel(int x, int y)
353 pixel = ReadPixel(laser_bitmap, x, y);
357 return (pixel == WHITE_PIXEL);
360 static void CheckExitMM(void)
362 int exit_element = EL_EMPTY;
366 static int xy[4][2] =
374 for (y = 0; y < lev_fieldy; y++)
376 for (x = 0; x < lev_fieldx; x++)
378 if (Tile[x][y] == EL_EXIT_CLOSED)
380 // initiate opening animation of exit door
381 Tile[x][y] = EL_EXIT_OPENING;
383 exit_element = EL_EXIT_OPEN;
387 else if (IS_RECEIVER(Tile[x][y]))
389 // remove field that blocks receiver
390 int phase = Tile[x][y] - EL_RECEIVER_START;
391 int blocking_x, blocking_y;
393 blocking_x = x + xy[phase][0];
394 blocking_y = y + xy[phase][1];
396 if (IN_LEV_FIELD(blocking_x, blocking_y))
398 Tile[blocking_x][blocking_y] = EL_EMPTY;
400 DrawField_MM(blocking_x, blocking_y);
403 exit_element = EL_RECEIVER;
410 if (exit_element != EL_EMPTY)
411 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
414 static void InitMovDir_MM(int x, int y)
416 int element = Tile[x][y];
417 static int direction[3][4] =
419 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
420 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
421 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
426 case EL_PACMAN_RIGHT:
430 Tile[x][y] = EL_PACMAN;
431 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
439 static void InitField(int x, int y)
441 int element = Tile[x][y];
446 Tile[x][y] = EL_EMPTY;
451 if (native_mm_level.auto_count_kettles)
452 game_mm.kettles_still_needed++;
455 case EL_LIGHTBULB_OFF:
456 game_mm.lights_still_needed++;
460 if (IS_MIRROR(element) ||
461 IS_BEAMER_OLD(element) ||
462 IS_BEAMER(element) ||
464 IS_POLAR_CROSS(element) ||
465 IS_DF_MIRROR(element) ||
466 IS_DF_MIRROR_AUTO(element) ||
467 IS_GRID_STEEL_AUTO(element) ||
468 IS_GRID_WOOD_AUTO(element) ||
469 IS_FIBRE_OPTIC(element))
471 if (IS_BEAMER_OLD(element))
473 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
474 element = Tile[x][y];
477 if (!IS_FIBRE_OPTIC(element))
479 static int steps_grid_auto = 0;
481 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
482 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
484 if (IS_GRID_STEEL_AUTO(element) ||
485 IS_GRID_WOOD_AUTO(element))
486 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
488 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
490 game_mm.cycle[game_mm.num_cycle].x = x;
491 game_mm.cycle[game_mm.num_cycle].y = y;
495 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
497 int beamer_nr = BEAMER_NR(element);
498 int nr = laser.beamer[beamer_nr][0].num;
500 laser.beamer[beamer_nr][nr].x = x;
501 laser.beamer[beamer_nr][nr].y = y;
502 laser.beamer[beamer_nr][nr].num = 1;
505 else if (IS_PACMAN(element))
509 else if (IS_MCDUFFIN(element) || IS_LASER(element))
511 laser.start_edge.x = x;
512 laser.start_edge.y = y;
513 laser.start_angle = get_element_angle(element);
520 static void InitCycleElements_RotateSingleStep(void)
524 if (game_mm.num_cycle == 0) // no elements to cycle
527 for (i = 0; i < game_mm.num_cycle; i++)
529 int x = game_mm.cycle[i].x;
530 int y = game_mm.cycle[i].y;
531 int step = SIGN(game_mm.cycle[i].steps);
532 int last_element = Tile[x][y];
533 int next_element = get_rotated_element(last_element, step);
535 if (!game_mm.cycle[i].steps)
538 Tile[x][y] = next_element;
540 game_mm.cycle[i].steps -= step;
544 static void InitLaser(void)
546 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
547 int step = (IS_LASER(start_element) ? 4 : 0);
549 LX = laser.start_edge.x * TILEX;
550 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
553 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
555 LY = laser.start_edge.y * TILEY;
556 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
557 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
561 XS = 2 * Step[laser.start_angle].x;
562 YS = 2 * Step[laser.start_angle].y;
564 laser.current_angle = laser.start_angle;
566 laser.num_damages = 0;
568 laser.num_beamers = 0;
569 laser.beamer_edge[0] = 0;
571 laser.dest_element = EL_EMPTY;
574 AddLaserEdge(LX, LY); // set laser starting edge
576 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
577 int color_down = 0xFF - color_up;
579 pen_ray = GetPixelFromRGB(window,
580 (native_mm_level.laser_red ? 0xFF : color_up),
581 (native_mm_level.laser_green ? color_down : 0x00),
582 (native_mm_level.laser_blue ? color_down : 0x00));
585 void InitGameEngine_MM(void)
591 // initialize laser bitmap to current playfield (screen) size
592 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
593 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
597 // set global game control values
598 game_mm.num_cycle = 0;
599 game_mm.num_pacman = 0;
602 game_mm.energy_left = 0; // later set to "native_mm_level.time"
603 game_mm.kettles_still_needed =
604 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
605 game_mm.lights_still_needed = 0;
606 game_mm.num_keys = 0;
608 game_mm.level_solved = FALSE;
609 game_mm.game_over = FALSE;
610 game_mm.game_over_cause = 0;
612 game_mm.laser_overload_value = 0;
613 game_mm.laser_enabled = FALSE;
615 // set global laser control values (must be set before "InitLaser()")
616 laser.start_edge.x = 0;
617 laser.start_edge.y = 0;
618 laser.start_angle = 0;
620 for (i = 0; i < MAX_NUM_BEAMERS; i++)
621 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
623 laser.overloaded = FALSE;
624 laser.overload_value = 0;
625 laser.fuse_off = FALSE;
626 laser.fuse_x = laser.fuse_y = -1;
628 laser.dest_element = EL_EMPTY;
642 rotate_delay.count = 0;
643 pacman_delay.count = 0;
644 energy_delay.count = 0;
645 overload_delay.count = 0;
647 ClickElement(-1, -1, -1);
649 for (x = 0; x < lev_fieldx; x++)
651 for (y = 0; y < lev_fieldy; y++)
653 Tile[x][y] = Ur[x][y];
654 Hit[x][y] = Box[x][y] = 0;
656 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
657 Store[x][y] = Store2[x][y] = 0;
668 void InitGameActions_MM(void)
670 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
671 int cycle_steps_done = 0;
676 for (i = 0; i <= num_init_game_frames; i++)
678 if (i == num_init_game_frames)
679 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
680 else if (setup.sound_loops)
681 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
683 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
685 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
687 UpdateAndDisplayGameControlValues();
689 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
691 InitCycleElements_RotateSingleStep();
696 AdvanceFrameCounter();
706 if (setup.quick_doors)
713 if (game_mm.kettles_still_needed == 0)
716 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
717 SetTileCursorActive(TRUE);
720 static void FadeOutLaser(boolean overloaded)
724 for (i = 15; i >= 0; i--)
727 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
729 pen_ray = GetPixelFromRGB(window,
730 native_mm_level.laser_red * 0x11 * i,
731 native_mm_level.laser_green * 0x11 * i,
732 native_mm_level.laser_blue * 0x11 * i);
734 DrawLaser(0, DL_LASER_ENABLED);
737 Delay_WithScreenUpdates(50);
740 DrawLaser(0, DL_LASER_DISABLED);
743 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
746 static void GameOver_MM(int game_over_cause)
748 // do not handle game over if request dialog is already active
749 if (game.request_active)
752 game_mm.game_over = TRUE;
753 game_mm.game_over_cause = game_over_cause;
755 if (setup.ask_on_game_over)
756 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
757 "Bomb killed Mc Duffin! Play it again?" :
758 game_over_cause == GAME_OVER_NO_ENERGY ?
759 "Out of magic energy! Play it again?" :
760 game_over_cause == GAME_OVER_OVERLOADED ?
761 "Magic spell hit Mc Duffin! Play it again?" :
764 SetTileCursorActive(FALSE);
767 void AddLaserEdge(int lx, int ly)
772 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
774 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
779 laser.edge[laser.num_edges].x = cSX2 + lx;
780 laser.edge[laser.num_edges].y = cSY2 + ly;
786 void AddDamagedField(int ex, int ey)
788 laser.damage[laser.num_damages].is_mirror = FALSE;
789 laser.damage[laser.num_damages].angle = laser.current_angle;
790 laser.damage[laser.num_damages].edge = laser.num_edges;
791 laser.damage[laser.num_damages].x = ex;
792 laser.damage[laser.num_damages].y = ey;
796 static boolean StepBehind(void)
802 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
803 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
805 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
811 static int getMaskFromElement(int element)
813 if (IS_GRID(element))
814 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
815 else if (IS_MCDUFFIN(element))
816 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
817 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
818 return IMG_MM_MASK_RECTANGLE;
820 return IMG_MM_MASK_CIRCLE;
823 static int ScanPixel(void)
828 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
829 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
832 // follow laser beam until it hits something (at least the screen border)
833 while (hit_mask == HIT_MASK_NO_HIT)
839 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
840 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
842 Debug("game:mm:ScanPixel", "touched screen border!");
848 for (i = 0; i < 4; i++)
850 int px = LX + (i % 2) * 2;
851 int py = LY + (i / 2) * 2;
854 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
855 int ly = (py + TILEY) / TILEY - 1; // negative values!
858 if (IN_LEV_FIELD(lx, ly))
860 int element = Tile[lx][ly];
862 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
866 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
868 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
870 pixel = ((element & (1 << pos)) ? 1 : 0);
874 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
876 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
881 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
882 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
885 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
886 hit_mask |= (1 << i);
889 if (hit_mask == HIT_MASK_NO_HIT)
891 // hit nothing -- go on with another step
903 int end = 0, rf = laser.num_edges;
905 // do not scan laser again after the game was lost for whatever reason
906 if (game_mm.game_over)
909 laser.overloaded = FALSE;
910 laser.stops_inside_element = FALSE;
912 DrawLaser(0, DL_LASER_ENABLED);
915 Debug("game:mm:ScanLaser",
916 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
924 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
927 laser.overloaded = TRUE;
932 hit_mask = ScanPixel();
935 Debug("game:mm:ScanLaser",
936 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
940 // hit something -- check out what it was
941 ELX = (LX + XS) / TILEX;
942 ELY = (LY + YS) / TILEY;
945 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
946 hit_mask, LX, LY, ELX, ELY);
949 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
952 laser.dest_element = element;
957 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
959 /* we have hit the top-right and bottom-left element --
960 choose the bottom-left one */
961 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
962 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
963 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
964 ELX = (LX - 2) / TILEX;
965 ELY = (LY + 2) / TILEY;
968 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
970 /* we have hit the top-left and bottom-right element --
971 choose the top-left one */
973 ELX = (LX - 2) / TILEX;
974 ELY = (LY - 2) / TILEY;
978 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
979 hit_mask, LX, LY, ELX, ELY);
982 element = Tile[ELX][ELY];
983 laser.dest_element = element;
986 Debug("game:mm:ScanLaser",
987 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
990 LX % TILEX, LY % TILEY,
995 if (!IN_LEV_FIELD(ELX, ELY))
996 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1000 if (element == EL_EMPTY)
1002 if (!HitOnlyAnEdge(hit_mask))
1005 else if (element == EL_FUSE_ON)
1007 if (HitPolarizer(element, hit_mask))
1010 else if (IS_GRID(element) || IS_DF_GRID(element))
1012 if (HitPolarizer(element, hit_mask))
1015 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1016 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1018 if (HitBlock(element, hit_mask))
1025 else if (IS_MCDUFFIN(element))
1027 if (HitLaserSource(element, hit_mask))
1030 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1031 IS_RECEIVER(element))
1033 if (HitLaserDestination(element, hit_mask))
1036 else if (IS_WALL(element))
1038 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1040 if (HitReflectingWalls(element, hit_mask))
1045 if (HitAbsorbingWalls(element, hit_mask))
1051 if (HitElement(element, hit_mask))
1056 DrawLaser(rf - 1, DL_LASER_ENABLED);
1057 rf = laser.num_edges;
1061 if (laser.dest_element != Tile[ELX][ELY])
1063 Debug("game:mm:ScanLaser",
1064 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1065 laser.dest_element, Tile[ELX][ELY]);
1069 if (!end && !laser.stops_inside_element && !StepBehind())
1072 Debug("game:mm:ScanLaser", "Go one step back");
1078 AddLaserEdge(LX, LY);
1082 DrawLaser(rf - 1, DL_LASER_ENABLED);
1084 Ct = CT = FrameCounter;
1087 if (!IN_LEV_FIELD(ELX, ELY))
1088 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1092 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1098 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1099 start_edge, num_edges, mode);
1104 Warn("DrawLaserExt: start_edge < 0");
1111 Warn("DrawLaserExt: num_edges < 0");
1117 if (mode == DL_LASER_DISABLED)
1119 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1123 // now draw the laser to the backbuffer and (if enabled) to the screen
1124 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1126 redraw_mask |= REDRAW_FIELD;
1128 if (mode == DL_LASER_ENABLED)
1131 // after the laser was deleted, the "damaged" graphics must be restored
1132 if (laser.num_damages)
1134 int damage_start = 0;
1137 // determine the starting edge, from which graphics need to be restored
1140 for (i = 0; i < laser.num_damages; i++)
1142 if (laser.damage[i].edge == start_edge + 1)
1151 // restore graphics from this starting edge to the end of damage list
1152 for (i = damage_start; i < laser.num_damages; i++)
1154 int lx = laser.damage[i].x;
1155 int ly = laser.damage[i].y;
1156 int element = Tile[lx][ly];
1158 if (Hit[lx][ly] == laser.damage[i].edge)
1159 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1162 if (Box[lx][ly] == laser.damage[i].edge)
1165 if (IS_DRAWABLE(element))
1166 DrawField_MM(lx, ly);
1169 elx = laser.damage[damage_start].x;
1170 ely = laser.damage[damage_start].y;
1171 element = Tile[elx][ely];
1174 if (IS_BEAMER(element))
1178 for (i = 0; i < laser.num_beamers; i++)
1179 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1181 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1182 mode, elx, ely, Hit[elx][ely], start_edge);
1183 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1184 get_element_angle(element), laser.damage[damage_start].angle);
1188 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1189 laser.num_beamers > 0 &&
1190 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1192 // element is outgoing beamer
1193 laser.num_damages = damage_start + 1;
1195 if (IS_BEAMER(element))
1196 laser.current_angle = get_element_angle(element);
1200 // element is incoming beamer or other element
1201 laser.num_damages = damage_start;
1202 laser.current_angle = laser.damage[laser.num_damages].angle;
1207 // no damages but McDuffin himself (who needs to be redrawn anyway)
1209 elx = laser.start_edge.x;
1210 ely = laser.start_edge.y;
1211 element = Tile[elx][ely];
1214 laser.num_edges = start_edge + 1;
1215 if (start_edge == 0)
1216 laser.current_angle = laser.start_angle;
1218 LX = laser.edge[start_edge].x - cSX2;
1219 LY = laser.edge[start_edge].y - cSY2;
1220 XS = 2 * Step[laser.current_angle].x;
1221 YS = 2 * Step[laser.current_angle].y;
1224 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1230 if (IS_BEAMER(element) ||
1231 IS_FIBRE_OPTIC(element) ||
1232 IS_PACMAN(element) ||
1233 IS_POLAR(element) ||
1234 IS_POLAR_CROSS(element) ||
1235 element == EL_FUSE_ON)
1240 Debug("game:mm:DrawLaserExt", "element == %d", element);
1243 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1244 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1248 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1249 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1250 (laser.num_beamers == 0 ||
1251 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1253 // element is incoming beamer or other element
1254 step_size = -step_size;
1259 if (IS_BEAMER(element))
1260 Debug("game:mm:DrawLaserExt",
1261 "start_edge == %d, laser.beamer_edge == %d",
1262 start_edge, laser.beamer_edge);
1265 LX += step_size * XS;
1266 LY += step_size * YS;
1268 else if (element != EL_EMPTY)
1277 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1282 void DrawLaser(int start_edge, int mode)
1284 if (laser.num_edges - start_edge < 0)
1286 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1291 // check if laser is interrupted by beamer element
1292 if (laser.num_beamers > 0 &&
1293 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1295 if (mode == DL_LASER_ENABLED)
1298 int tmp_start_edge = start_edge;
1300 // draw laser segments forward from the start to the last beamer
1301 for (i = 0; i < laser.num_beamers; i++)
1303 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1305 if (tmp_num_edges <= 0)
1309 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1310 i, laser.beamer_edge[i], tmp_start_edge);
1313 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1315 tmp_start_edge = laser.beamer_edge[i];
1318 // draw last segment from last beamer to the end
1319 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1325 int last_num_edges = laser.num_edges;
1326 int num_beamers = laser.num_beamers;
1328 // delete laser segments backward from the end to the first beamer
1329 for (i = num_beamers - 1; i >= 0; i--)
1331 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1333 if (laser.beamer_edge[i] - start_edge <= 0)
1336 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1338 last_num_edges = laser.beamer_edge[i];
1339 laser.num_beamers--;
1343 if (last_num_edges - start_edge <= 0)
1344 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1345 last_num_edges, start_edge);
1348 // special case when rotating first beamer: delete laser edge on beamer
1349 // (but do not start scanning on previous edge to prevent mirror sound)
1350 if (last_num_edges - start_edge == 1 && start_edge > 0)
1351 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1353 // delete first segment from start to the first beamer
1354 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1359 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1362 game_mm.laser_enabled = mode;
1365 void DrawLaser_MM(void)
1367 DrawLaser(0, game_mm.laser_enabled);
1370 boolean HitElement(int element, int hit_mask)
1372 if (HitOnlyAnEdge(hit_mask))
1375 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1376 element = MovingOrBlocked2Element_MM(ELX, ELY);
1379 Debug("game:mm:HitElement", "(1): element == %d", element);
1383 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1384 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1387 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1391 AddDamagedField(ELX, ELY);
1393 // this is more precise: check if laser would go through the center
1394 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1396 // skip the whole element before continuing the scan
1402 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1404 if (LX/TILEX > ELX || LY/TILEY > ELY)
1406 /* skipping scan positions to the right and down skips one scan
1407 position too much, because this is only the top left scan position
1408 of totally four scan positions (plus one to the right, one to the
1409 bottom and one to the bottom right) */
1419 Debug("game:mm:HitElement", "(2): element == %d", element);
1422 if (LX + 5 * XS < 0 ||
1432 Debug("game:mm:HitElement", "(3): element == %d", element);
1435 if (IS_POLAR(element) &&
1436 ((element - EL_POLAR_START) % 2 ||
1437 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1439 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1441 laser.num_damages--;
1446 if (IS_POLAR_CROSS(element) &&
1447 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1449 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1451 laser.num_damages--;
1456 if (!IS_BEAMER(element) &&
1457 !IS_FIBRE_OPTIC(element) &&
1458 !IS_GRID_WOOD(element) &&
1459 element != EL_FUEL_EMPTY)
1462 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1463 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1465 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1468 LX = ELX * TILEX + 14;
1469 LY = ELY * TILEY + 14;
1471 AddLaserEdge(LX, LY);
1474 if (IS_MIRROR(element) ||
1475 IS_MIRROR_FIXED(element) ||
1476 IS_POLAR(element) ||
1477 IS_POLAR_CROSS(element) ||
1478 IS_DF_MIRROR(element) ||
1479 IS_DF_MIRROR_AUTO(element) ||
1480 element == EL_PRISM ||
1481 element == EL_REFRACTOR)
1483 int current_angle = laser.current_angle;
1486 laser.num_damages--;
1488 AddDamagedField(ELX, ELY);
1490 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1493 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1495 if (IS_MIRROR(element) ||
1496 IS_MIRROR_FIXED(element) ||
1497 IS_DF_MIRROR(element) ||
1498 IS_DF_MIRROR_AUTO(element))
1499 laser.current_angle = get_mirrored_angle(laser.current_angle,
1500 get_element_angle(element));
1502 if (element == EL_PRISM || element == EL_REFRACTOR)
1503 laser.current_angle = RND(16);
1505 XS = 2 * Step[laser.current_angle].x;
1506 YS = 2 * Step[laser.current_angle].y;
1508 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1513 LX += step_size * XS;
1514 LY += step_size * YS;
1516 // draw sparkles on mirror
1517 if ((IS_MIRROR(element) ||
1518 IS_MIRROR_FIXED(element) ||
1519 element == EL_PRISM) &&
1520 current_angle != laser.current_angle)
1522 MovDelay[ELX][ELY] = 11; // start animation
1525 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1526 current_angle != laser.current_angle)
1527 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1530 (get_opposite_angle(laser.current_angle) ==
1531 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1533 return (laser.overloaded ? TRUE : FALSE);
1536 if (element == EL_FUEL_FULL)
1538 laser.stops_inside_element = TRUE;
1543 if (element == EL_BOMB || element == EL_MINE)
1545 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1547 if (element == EL_MINE)
1548 laser.overloaded = TRUE;
1551 if (element == EL_KETTLE ||
1552 element == EL_CELL ||
1553 element == EL_KEY ||
1554 element == EL_LIGHTBALL ||
1555 element == EL_PACMAN ||
1558 if (!IS_PACMAN(element))
1561 if (element == EL_PACMAN)
1564 if (element == EL_KETTLE || element == EL_CELL)
1566 if (game_mm.kettles_still_needed > 0)
1567 game_mm.kettles_still_needed--;
1569 game.snapshot.collected_item = TRUE;
1571 if (game_mm.kettles_still_needed == 0)
1575 DrawLaser(0, DL_LASER_ENABLED);
1578 else if (element == EL_KEY)
1582 else if (IS_PACMAN(element))
1584 DeletePacMan(ELX, ELY);
1587 RaiseScoreElement_MM(element);
1592 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1594 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1596 DrawLaser(0, DL_LASER_ENABLED);
1598 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1600 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1601 game_mm.lights_still_needed--;
1605 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1606 game_mm.lights_still_needed++;
1609 DrawField_MM(ELX, ELY);
1610 DrawLaser(0, DL_LASER_ENABLED);
1615 laser.stops_inside_element = TRUE;
1621 Debug("game:mm:HitElement", "(4): element == %d", element);
1624 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1625 laser.num_beamers < MAX_NUM_BEAMERS &&
1626 laser.beamer[BEAMER_NR(element)][1].num)
1628 int beamer_angle = get_element_angle(element);
1629 int beamer_nr = BEAMER_NR(element);
1633 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1636 laser.num_damages--;
1638 if (IS_FIBRE_OPTIC(element) ||
1639 laser.current_angle == get_opposite_angle(beamer_angle))
1643 LX = ELX * TILEX + 14;
1644 LY = ELY * TILEY + 14;
1646 AddLaserEdge(LX, LY);
1647 AddDamagedField(ELX, ELY);
1649 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1652 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1654 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1655 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1656 ELX = laser.beamer[beamer_nr][pos].x;
1657 ELY = laser.beamer[beamer_nr][pos].y;
1658 LX = ELX * TILEX + 14;
1659 LY = ELY * TILEY + 14;
1661 if (IS_BEAMER(element))
1663 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1664 XS = 2 * Step[laser.current_angle].x;
1665 YS = 2 * Step[laser.current_angle].y;
1668 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1670 AddLaserEdge(LX, LY);
1671 AddDamagedField(ELX, ELY);
1673 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1676 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1678 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1683 LX += step_size * XS;
1684 LY += step_size * YS;
1686 laser.num_beamers++;
1695 boolean HitOnlyAnEdge(int hit_mask)
1697 // check if the laser hit only the edge of an element and, if so, go on
1700 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1704 if ((hit_mask == HIT_MASK_TOPLEFT ||
1705 hit_mask == HIT_MASK_TOPRIGHT ||
1706 hit_mask == HIT_MASK_BOTTOMLEFT ||
1707 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1708 laser.current_angle % 4) // angle is not 90°
1712 if (hit_mask == HIT_MASK_TOPLEFT)
1717 else if (hit_mask == HIT_MASK_TOPRIGHT)
1722 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1727 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1733 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1739 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1746 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1752 boolean HitPolarizer(int element, int hit_mask)
1754 if (HitOnlyAnEdge(hit_mask))
1757 if (IS_DF_GRID(element))
1759 int grid_angle = get_element_angle(element);
1762 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1763 grid_angle, laser.current_angle);
1766 AddLaserEdge(LX, LY);
1767 AddDamagedField(ELX, ELY);
1770 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1772 if (laser.current_angle == grid_angle ||
1773 laser.current_angle == get_opposite_angle(grid_angle))
1775 // skip the whole element before continuing the scan
1781 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1783 if (LX/TILEX > ELX || LY/TILEY > ELY)
1785 /* skipping scan positions to the right and down skips one scan
1786 position too much, because this is only the top left scan position
1787 of totally four scan positions (plus one to the right, one to the
1788 bottom and one to the bottom right) */
1794 AddLaserEdge(LX, LY);
1800 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1802 LX / TILEX, LY / TILEY,
1803 LX % TILEX, LY % TILEY);
1808 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1810 return HitReflectingWalls(element, hit_mask);
1814 return HitAbsorbingWalls(element, hit_mask);
1817 else if (IS_GRID_STEEL(element))
1819 return HitReflectingWalls(element, hit_mask);
1821 else // IS_GRID_WOOD
1823 return HitAbsorbingWalls(element, hit_mask);
1829 boolean HitBlock(int element, int hit_mask)
1831 boolean check = FALSE;
1833 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1834 game_mm.num_keys == 0)
1837 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1840 int ex = ELX * TILEX + 14;
1841 int ey = ELY * TILEY + 14;
1845 for (i = 1; i < 32; i++)
1850 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1855 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1856 return HitAbsorbingWalls(element, hit_mask);
1860 AddLaserEdge(LX - XS, LY - YS);
1861 AddDamagedField(ELX, ELY);
1864 Box[ELX][ELY] = laser.num_edges;
1866 return HitReflectingWalls(element, hit_mask);
1869 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1871 int xs = XS / 2, ys = YS / 2;
1872 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1873 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1875 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1876 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1878 laser.overloaded = (element == EL_GATE_STONE);
1883 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1884 (hit_mask == HIT_MASK_TOP ||
1885 hit_mask == HIT_MASK_LEFT ||
1886 hit_mask == HIT_MASK_RIGHT ||
1887 hit_mask == HIT_MASK_BOTTOM))
1888 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1889 hit_mask == HIT_MASK_BOTTOM),
1890 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1891 hit_mask == HIT_MASK_RIGHT));
1892 AddLaserEdge(LX, LY);
1898 if (element == EL_GATE_STONE && Box[ELX][ELY])
1900 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1912 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1914 int xs = XS / 2, ys = YS / 2;
1915 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1916 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1918 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1919 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1921 laser.overloaded = (element == EL_BLOCK_STONE);
1926 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1927 (hit_mask == HIT_MASK_TOP ||
1928 hit_mask == HIT_MASK_LEFT ||
1929 hit_mask == HIT_MASK_RIGHT ||
1930 hit_mask == HIT_MASK_BOTTOM))
1931 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1932 hit_mask == HIT_MASK_BOTTOM),
1933 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1934 hit_mask == HIT_MASK_RIGHT));
1935 AddDamagedField(ELX, ELY);
1937 LX = ELX * TILEX + 14;
1938 LY = ELY * TILEY + 14;
1940 AddLaserEdge(LX, LY);
1942 laser.stops_inside_element = TRUE;
1950 boolean HitLaserSource(int element, int hit_mask)
1952 if (HitOnlyAnEdge(hit_mask))
1955 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1957 laser.overloaded = TRUE;
1962 boolean HitLaserDestination(int element, int hit_mask)
1964 if (HitOnlyAnEdge(hit_mask))
1967 if (element != EL_EXIT_OPEN &&
1968 !(IS_RECEIVER(element) &&
1969 game_mm.kettles_still_needed == 0 &&
1970 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1972 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1977 if (IS_RECEIVER(element) ||
1978 (IS_22_5_ANGLE(laser.current_angle) &&
1979 (ELX != (LX + 6 * XS) / TILEX ||
1980 ELY != (LY + 6 * YS) / TILEY ||
1989 LX = ELX * TILEX + 14;
1990 LY = ELY * TILEY + 14;
1992 laser.stops_inside_element = TRUE;
1995 AddLaserEdge(LX, LY);
1996 AddDamagedField(ELX, ELY);
1998 if (game_mm.lights_still_needed == 0)
2000 game_mm.level_solved = TRUE;
2002 SetTileCursorActive(FALSE);
2008 boolean HitReflectingWalls(int element, int hit_mask)
2010 // check if laser hits side of a wall with an angle that is not 90°
2011 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2012 hit_mask == HIT_MASK_LEFT ||
2013 hit_mask == HIT_MASK_RIGHT ||
2014 hit_mask == HIT_MASK_BOTTOM))
2016 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2021 if (!IS_DF_GRID(element))
2022 AddLaserEdge(LX, LY);
2024 // check if laser hits wall with an angle of 45°
2025 if (!IS_22_5_ANGLE(laser.current_angle))
2027 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2030 laser.current_angle = get_mirrored_angle(laser.current_angle,
2033 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2036 laser.current_angle = get_mirrored_angle(laser.current_angle,
2040 AddLaserEdge(LX, LY);
2042 XS = 2 * Step[laser.current_angle].x;
2043 YS = 2 * Step[laser.current_angle].y;
2047 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2049 laser.current_angle = get_mirrored_angle(laser.current_angle,
2054 if (!IS_DF_GRID(element))
2055 AddLaserEdge(LX, LY);
2060 if (!IS_DF_GRID(element))
2061 AddLaserEdge(LX, LY + YS / 2);
2064 if (!IS_DF_GRID(element))
2065 AddLaserEdge(LX, LY);
2068 YS = 2 * Step[laser.current_angle].y;
2072 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2074 laser.current_angle = get_mirrored_angle(laser.current_angle,
2079 if (!IS_DF_GRID(element))
2080 AddLaserEdge(LX, LY);
2085 if (!IS_DF_GRID(element))
2086 AddLaserEdge(LX + XS / 2, LY);
2089 if (!IS_DF_GRID(element))
2090 AddLaserEdge(LX, LY);
2093 XS = 2 * Step[laser.current_angle].x;
2099 // reflection at the edge of reflecting DF style wall
2100 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2102 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2103 hit_mask == HIT_MASK_TOPRIGHT) ||
2104 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2105 hit_mask == HIT_MASK_TOPLEFT) ||
2106 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2107 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2108 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2109 hit_mask == HIT_MASK_BOTTOMRIGHT))
2112 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2113 ANG_MIRROR_135 : ANG_MIRROR_45);
2115 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2117 AddDamagedField(ELX, ELY);
2118 AddLaserEdge(LX, LY);
2120 laser.current_angle = get_mirrored_angle(laser.current_angle,
2128 AddLaserEdge(LX, LY);
2134 // reflection inside an edge of reflecting DF style wall
2135 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2137 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2138 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2139 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2140 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2141 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2142 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2143 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2144 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2147 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2148 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2149 ANG_MIRROR_135 : ANG_MIRROR_45);
2151 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2154 AddDamagedField(ELX, ELY);
2157 AddLaserEdge(LX - XS, LY - YS);
2158 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2159 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2161 laser.current_angle = get_mirrored_angle(laser.current_angle,
2169 AddLaserEdge(LX, LY);
2175 // check if laser hits DF style wall with an angle of 90°
2176 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2178 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2179 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2180 (IS_VERT_ANGLE(laser.current_angle) &&
2181 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2183 // laser at last step touched nothing or the same side of the wall
2184 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2186 AddDamagedField(ELX, ELY);
2193 last_hit_mask = hit_mask;
2200 if (!HitOnlyAnEdge(hit_mask))
2202 laser.overloaded = TRUE;
2210 boolean HitAbsorbingWalls(int element, int hit_mask)
2212 if (HitOnlyAnEdge(hit_mask))
2216 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2218 AddLaserEdge(LX - XS, LY - YS);
2225 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2227 AddLaserEdge(LX - XS, LY - YS);
2233 if (IS_WALL_WOOD(element) ||
2234 IS_DF_WALL_WOOD(element) ||
2235 IS_GRID_WOOD(element) ||
2236 IS_GRID_WOOD_FIXED(element) ||
2237 IS_GRID_WOOD_AUTO(element) ||
2238 element == EL_FUSE_ON ||
2239 element == EL_BLOCK_WOOD ||
2240 element == EL_GATE_WOOD)
2242 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2247 if (IS_WALL_ICE(element))
2251 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2252 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2254 // check if laser hits wall with an angle of 90°
2255 if (IS_90_ANGLE(laser.current_angle))
2256 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2258 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2262 for (i = 0; i < 4; i++)
2264 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2265 mask = 15 - (8 >> i);
2266 else if (ABS(XS) == 4 &&
2268 (XS > 0) == (i % 2) &&
2269 (YS < 0) == (i / 2))
2270 mask = 3 + (i / 2) * 9;
2271 else if (ABS(YS) == 4 &&
2273 (XS < 0) == (i % 2) &&
2274 (YS > 0) == (i / 2))
2275 mask = 5 + (i % 2) * 5;
2279 laser.wall_mask = mask;
2281 else if (IS_WALL_AMOEBA(element))
2283 int elx = (LX - 2 * XS) / TILEX;
2284 int ely = (LY - 2 * YS) / TILEY;
2285 int element2 = Tile[elx][ely];
2288 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2290 laser.dest_element = EL_EMPTY;
2298 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2299 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2301 if (IS_90_ANGLE(laser.current_angle))
2302 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2304 laser.dest_element = element2 | EL_WALL_AMOEBA;
2306 laser.wall_mask = mask;
2312 static void OpenExit(int x, int y)
2316 if (!MovDelay[x][y]) // next animation frame
2317 MovDelay[x][y] = 4 * delay;
2319 if (MovDelay[x][y]) // wait some time before next frame
2324 phase = MovDelay[x][y] / delay;
2326 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2327 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2329 if (!MovDelay[x][y])
2331 Tile[x][y] = EL_EXIT_OPEN;
2337 static void OpenSurpriseBall(int x, int y)
2341 if (!MovDelay[x][y]) // next animation frame
2342 MovDelay[x][y] = 50 * delay;
2344 if (MovDelay[x][y]) // wait some time before next frame
2348 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2351 int graphic = el2gfx(Store[x][y]);
2353 int dx = RND(26), dy = RND(26);
2355 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2357 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2358 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2360 MarkTileDirty(x, y);
2363 if (!MovDelay[x][y])
2365 Tile[x][y] = Store[x][y];
2374 static void MeltIce(int x, int y)
2379 if (!MovDelay[x][y]) // next animation frame
2380 MovDelay[x][y] = frames * delay;
2382 if (MovDelay[x][y]) // wait some time before next frame
2385 int wall_mask = Store2[x][y];
2386 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2389 phase = frames - MovDelay[x][y] / delay - 1;
2391 if (!MovDelay[x][y])
2395 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2396 Store[x][y] = Store2[x][y] = 0;
2398 DrawWalls_MM(x, y, Tile[x][y]);
2400 if (Tile[x][y] == EL_WALL_ICE)
2401 Tile[x][y] = EL_EMPTY;
2403 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2404 if (laser.damage[i].is_mirror)
2408 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2410 DrawLaser(0, DL_LASER_DISABLED);
2414 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2416 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2418 laser.redraw = TRUE;
2423 static void GrowAmoeba(int x, int y)
2428 if (!MovDelay[x][y]) // next animation frame
2429 MovDelay[x][y] = frames * delay;
2431 if (MovDelay[x][y]) // wait some time before next frame
2434 int wall_mask = Store2[x][y];
2435 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2438 phase = MovDelay[x][y] / delay;
2440 if (!MovDelay[x][y])
2442 Tile[x][y] = real_element;
2443 Store[x][y] = Store2[x][y] = 0;
2445 DrawWalls_MM(x, y, Tile[x][y]);
2446 DrawLaser(0, DL_LASER_ENABLED);
2448 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2450 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2455 static void DrawFieldAnimated_MM(int x, int y)
2457 int element = Tile[x][y];
2459 if (IS_BLOCKED(x, y))
2464 if (IS_MIRROR(element) ||
2465 IS_MIRROR_FIXED(element) ||
2466 element == EL_PRISM)
2468 if (MovDelay[x][y] != 0) // wait some time before next frame
2472 if (MovDelay[x][y] != 0)
2474 int graphic = IMG_TWINKLE_WHITE;
2475 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2477 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2482 laser.redraw = TRUE;
2485 static void Explode_MM(int x, int y, int phase, int mode)
2487 int num_phase = 9, delay = 2;
2488 int last_phase = num_phase * delay;
2489 int half_phase = (num_phase / 2) * delay;
2491 laser.redraw = TRUE;
2493 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2495 int center_element = Tile[x][y];
2497 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2499 // put moving element to center field (and let it explode there)
2500 center_element = MovingOrBlocked2Element_MM(x, y);
2501 RemoveMovingField_MM(x, y);
2503 Tile[x][y] = center_element;
2506 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2507 Store[x][y] = center_element;
2509 Store[x][y] = EL_EMPTY;
2511 Store2[x][y] = mode;
2512 Tile[x][y] = EL_EXPLODING_OPAQUE;
2513 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2519 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2521 if (phase == half_phase)
2523 Tile[x][y] = EL_EXPLODING_TRANSP;
2525 if (x == ELX && y == ELY)
2529 if (phase == last_phase)
2531 if (Store[x][y] == EL_BOMB)
2533 DrawLaser(0, DL_LASER_DISABLED);
2536 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2537 Store[x][y] = EL_EMPTY;
2539 GameOver_MM(GAME_OVER_DELAYED);
2541 laser.overloaded = FALSE;
2543 else if (IS_MCDUFFIN(Store[x][y]))
2545 Store[x][y] = EL_EMPTY;
2547 GameOver_MM(GAME_OVER_BOMB);
2550 Tile[x][y] = Store[x][y];
2551 Store[x][y] = Store2[x][y] = 0;
2552 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2557 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2559 int graphic = IMG_MM_DEFAULT_EXPLODING;
2560 int graphic_phase = (phase / delay - 1);
2564 if (Store2[x][y] == EX_KETTLE)
2566 if (graphic_phase < 3)
2568 graphic = IMG_MM_KETTLE_EXPLODING;
2570 else if (graphic_phase < 5)
2576 graphic = IMG_EMPTY;
2580 else if (Store2[x][y] == EX_SHORT)
2582 if (graphic_phase < 4)
2588 graphic = IMG_EMPTY;
2593 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2595 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2596 cFX + x * TILEX, cFY + y * TILEY);
2598 MarkTileDirty(x, y);
2602 static void Bang_MM(int x, int y)
2604 int element = Tile[x][y];
2605 int mode = EX_NORMAL;
2608 DrawLaser(0, DL_LASER_ENABLED);
2627 if (IS_PACMAN(element))
2628 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2629 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2630 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2631 else if (element == EL_KEY)
2632 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2634 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2636 Explode_MM(x, y, EX_PHASE_START, mode);
2639 void TurnRound(int x, int y)
2651 { 0, 0 }, { 0, 0 }, { 0, 0 },
2656 int left, right, back;
2660 { MV_DOWN, MV_UP, MV_RIGHT },
2661 { MV_UP, MV_DOWN, MV_LEFT },
2663 { MV_LEFT, MV_RIGHT, MV_DOWN },
2664 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2665 { MV_RIGHT, MV_LEFT, MV_UP }
2668 int element = Tile[x][y];
2669 int old_move_dir = MovDir[x][y];
2670 int right_dir = turn[old_move_dir].right;
2671 int back_dir = turn[old_move_dir].back;
2672 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2673 int right_x = x + right_dx, right_y = y + right_dy;
2675 if (element == EL_PACMAN)
2677 boolean can_turn_right = FALSE;
2679 if (IN_LEV_FIELD(right_x, right_y) &&
2680 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2681 can_turn_right = TRUE;
2684 MovDir[x][y] = right_dir;
2686 MovDir[x][y] = back_dir;
2692 static void StartMoving_MM(int x, int y)
2694 int element = Tile[x][y];
2699 if (CAN_MOVE(element))
2703 if (MovDelay[x][y]) // wait some time before next movement
2711 // now make next step
2713 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2715 if (element == EL_PACMAN &&
2716 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2717 !ObjHit(newx, newy, HIT_POS_CENTER))
2719 Store[newx][newy] = Tile[newx][newy];
2720 Tile[newx][newy] = EL_EMPTY;
2722 DrawField_MM(newx, newy);
2724 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2725 ObjHit(newx, newy, HIT_POS_CENTER))
2727 // object was running against a wall
2734 InitMovingField_MM(x, y, MovDir[x][y]);
2738 ContinueMoving_MM(x, y);
2741 static void ContinueMoving_MM(int x, int y)
2743 int element = Tile[x][y];
2744 int direction = MovDir[x][y];
2745 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2746 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2747 int horiz_move = (dx!=0);
2748 int newx = x + dx, newy = y + dy;
2749 int step = (horiz_move ? dx : dy) * TILEX / 8;
2751 MovPos[x][y] += step;
2753 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2755 Tile[x][y] = EL_EMPTY;
2756 Tile[newx][newy] = element;
2758 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2759 MovDelay[newx][newy] = 0;
2761 if (!CAN_MOVE(element))
2762 MovDir[newx][newy] = 0;
2765 DrawField_MM(newx, newy);
2767 Stop[newx][newy] = TRUE;
2769 if (element == EL_PACMAN)
2771 if (Store[newx][newy] == EL_BOMB)
2772 Bang_MM(newx, newy);
2774 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2775 (LX + 2 * XS) / TILEX == newx &&
2776 (LY + 2 * YS) / TILEY == newy)
2783 else // still moving on
2788 laser.redraw = TRUE;
2791 boolean ClickElement(int x, int y, int button)
2793 static DelayCounter click_delay = { CLICK_DELAY };
2794 static boolean new_button = TRUE;
2795 boolean element_clicked = FALSE;
2800 // initialize static variables
2801 click_delay.count = 0;
2802 click_delay.value = CLICK_DELAY;
2808 // do not rotate objects hit by the laser after the game was solved
2809 if (game_mm.level_solved && Hit[x][y])
2812 if (button == MB_RELEASED)
2815 click_delay.value = CLICK_DELAY;
2817 // release eventually hold auto-rotating mirror
2818 RotateMirror(x, y, MB_RELEASED);
2823 if (!FrameReached(&click_delay) && !new_button)
2826 if (button == MB_MIDDLEBUTTON) // middle button has no function
2829 if (!IN_LEV_FIELD(x, y))
2832 if (Tile[x][y] == EL_EMPTY)
2835 element = Tile[x][y];
2837 if (IS_MIRROR(element) ||
2838 IS_BEAMER(element) ||
2839 IS_POLAR(element) ||
2840 IS_POLAR_CROSS(element) ||
2841 IS_DF_MIRROR(element) ||
2842 IS_DF_MIRROR_AUTO(element))
2844 RotateMirror(x, y, button);
2846 element_clicked = TRUE;
2848 else if (IS_MCDUFFIN(element))
2850 if (!laser.fuse_off)
2852 DrawLaser(0, DL_LASER_DISABLED);
2859 element = get_rotated_element(element, BUTTON_ROTATION(button));
2860 laser.start_angle = get_element_angle(element);
2864 Tile[x][y] = element;
2871 if (!laser.fuse_off)
2874 element_clicked = TRUE;
2876 else if (element == EL_FUSE_ON && laser.fuse_off)
2878 if (x != laser.fuse_x || y != laser.fuse_y)
2881 laser.fuse_off = FALSE;
2882 laser.fuse_x = laser.fuse_y = -1;
2884 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2887 element_clicked = TRUE;
2889 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2891 laser.fuse_off = TRUE;
2894 laser.overloaded = FALSE;
2896 DrawLaser(0, DL_LASER_DISABLED);
2897 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2899 element_clicked = TRUE;
2901 else if (element == EL_LIGHTBALL)
2904 RaiseScoreElement_MM(element);
2905 DrawLaser(0, DL_LASER_ENABLED);
2907 element_clicked = TRUE;
2910 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2913 return element_clicked;
2916 void RotateMirror(int x, int y, int button)
2918 if (button == MB_RELEASED)
2920 // release eventually hold auto-rotating mirror
2927 if (IS_MIRROR(Tile[x][y]) ||
2928 IS_POLAR_CROSS(Tile[x][y]) ||
2929 IS_POLAR(Tile[x][y]) ||
2930 IS_BEAMER(Tile[x][y]) ||
2931 IS_DF_MIRROR(Tile[x][y]) ||
2932 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2933 IS_GRID_WOOD_AUTO(Tile[x][y]))
2935 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2937 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2939 if (button == MB_LEFTBUTTON)
2941 // left mouse button only for manual adjustment, no auto-rotating;
2942 // freeze mirror for until mouse button released
2946 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2948 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2952 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2954 int edge = Hit[x][y];
2960 DrawLaser(edge - 1, DL_LASER_DISABLED);
2964 else if (ObjHit(x, y, HIT_POS_CENTER))
2966 int edge = Hit[x][y];
2970 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2975 DrawLaser(edge - 1, DL_LASER_DISABLED);
2982 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2987 if ((IS_BEAMER(Tile[x][y]) ||
2988 IS_POLAR(Tile[x][y]) ||
2989 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2993 if (IS_BEAMER(Tile[x][y]))
2996 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2997 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3009 DrawLaser(0, DL_LASER_ENABLED);
3013 static void AutoRotateMirrors(void)
3017 if (!FrameReached(&rotate_delay))
3020 for (x = 0; x < lev_fieldx; x++)
3022 for (y = 0; y < lev_fieldy; y++)
3024 int element = Tile[x][y];
3026 // do not rotate objects hit by the laser after the game was solved
3027 if (game_mm.level_solved && Hit[x][y])
3030 if (IS_DF_MIRROR_AUTO(element) ||
3031 IS_GRID_WOOD_AUTO(element) ||
3032 IS_GRID_STEEL_AUTO(element) ||
3033 element == EL_REFRACTOR)
3034 RotateMirror(x, y, MB_RIGHTBUTTON);
3039 boolean ObjHit(int obx, int oby, int bits)
3046 if (bits & HIT_POS_CENTER)
3048 if (CheckLaserPixel(cSX + obx + 15,
3053 if (bits & HIT_POS_EDGE)
3055 for (i = 0; i < 4; i++)
3056 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3057 cSY + oby + 31 * (i / 2)))
3061 if (bits & HIT_POS_BETWEEN)
3063 for (i = 0; i < 4; i++)
3064 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3065 cSY + 4 + oby + 22 * (i / 2)))
3072 void DeletePacMan(int px, int py)
3078 if (game_mm.num_pacman <= 1)
3080 game_mm.num_pacman = 0;
3084 for (i = 0; i < game_mm.num_pacman; i++)
3085 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3088 game_mm.num_pacman--;
3090 for (j = i; j < game_mm.num_pacman; j++)
3092 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3093 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3094 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3098 void ColorCycling(void)
3100 static int CC, Cc = 0;
3102 static int color, old = 0xF00, new = 0x010, mult = 1;
3103 static unsigned short red, green, blue;
3105 if (color_status == STATIC_COLORS)
3110 if (CC < Cc || CC > Cc + 2)
3114 color = old + new * mult;
3120 if (ABS(mult) == 16)
3130 red = 0x0e00 * ((color & 0xF00) >> 8);
3131 green = 0x0e00 * ((color & 0x0F0) >> 4);
3132 blue = 0x0e00 * ((color & 0x00F));
3133 SetRGB(pen_magicolor[0], red, green, blue);
3135 red = 0x1111 * ((color & 0xF00) >> 8);
3136 green = 0x1111 * ((color & 0x0F0) >> 4);
3137 blue = 0x1111 * ((color & 0x00F));
3138 SetRGB(pen_magicolor[1], red, green, blue);
3142 static void GameActions_MM_Ext(void)
3149 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3152 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3154 element = Tile[x][y];
3156 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3157 StartMoving_MM(x, y);
3158 else if (IS_MOVING(x, y))
3159 ContinueMoving_MM(x, y);
3160 else if (IS_EXPLODING(element))
3161 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3162 else if (element == EL_EXIT_OPENING)
3164 else if (element == EL_GRAY_BALL_OPENING)
3165 OpenSurpriseBall(x, y);
3166 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3168 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3171 DrawFieldAnimated_MM(x, y);
3174 AutoRotateMirrors();
3177 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3179 // redraw after Explode_MM() ...
3181 DrawLaser(0, DL_LASER_ENABLED);
3182 laser.redraw = FALSE;
3187 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3191 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3193 DrawLaser(0, DL_LASER_DISABLED);
3198 // skip all following game actions if game is over
3199 if (game_mm.game_over)
3202 if (FrameReached(&energy_delay))
3204 if (game_mm.energy_left > 0)
3206 game_mm.energy_left--;
3208 redraw_mask |= REDRAW_DOOR_1;
3210 else if (game.time_limit && !game_mm.game_over)
3212 FadeOutLaser(FALSE);
3214 GameOver_MM(GAME_OVER_NO_ENERGY);
3220 element = laser.dest_element;
3223 if (element != Tile[ELX][ELY])
3225 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3226 element, Tile[ELX][ELY]);
3230 if (!laser.overloaded && laser.overload_value == 0 &&
3231 element != EL_BOMB &&
3232 element != EL_MINE &&
3233 element != EL_BALL_GRAY &&
3234 element != EL_BLOCK_STONE &&
3235 element != EL_BLOCK_WOOD &&
3236 element != EL_FUSE_ON &&
3237 element != EL_FUEL_FULL &&
3238 !IS_WALL_ICE(element) &&
3239 !IS_WALL_AMOEBA(element))
3242 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3244 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3245 (!laser.overloaded && laser.overload_value > 0)) &&
3246 FrameReached(&overload_delay))
3248 if (laser.overloaded)
3249 laser.overload_value++;
3251 laser.overload_value--;
3253 if (game_mm.cheat_no_overload)
3255 laser.overloaded = FALSE;
3256 laser.overload_value = 0;
3259 game_mm.laser_overload_value = laser.overload_value;
3261 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3263 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3264 int color_down = 0xFF - color_up;
3267 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3268 (15 - (laser.overload_value / 6)) * color_scale);
3271 GetPixelFromRGB(window,
3272 (native_mm_level.laser_red ? 0xFF : color_up),
3273 (native_mm_level.laser_green ? color_down : 0x00),
3274 (native_mm_level.laser_blue ? color_down : 0x00));
3276 DrawLaser(0, DL_LASER_ENABLED);
3279 if (!laser.overloaded)
3280 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3281 else if (setup.sound_loops)
3282 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3284 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3286 if (laser.overloaded)
3289 BlitBitmap(pix[PIX_DOOR], drawto,
3290 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3291 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3292 - laser.overload_value,
3293 OVERLOAD_XSIZE, laser.overload_value,
3294 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3295 - laser.overload_value);
3297 redraw_mask |= REDRAW_DOOR_1;
3302 BlitBitmap(pix[PIX_DOOR], drawto,
3303 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3304 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3305 DX_OVERLOAD, DY_OVERLOAD);
3307 redraw_mask |= REDRAW_DOOR_1;
3310 if (laser.overload_value == MAX_LASER_OVERLOAD)
3312 UpdateAndDisplayGameControlValues();
3316 GameOver_MM(GAME_OVER_OVERLOADED);
3327 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3329 if (game_mm.cheat_no_explosion)
3334 laser.dest_element = EL_EXPLODING_OPAQUE;
3339 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3341 laser.fuse_off = TRUE;
3345 DrawLaser(0, DL_LASER_DISABLED);
3346 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3349 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3351 static int new_elements[] =
3354 EL_MIRROR_FIXED_START,
3356 EL_POLAR_CROSS_START,
3362 int num_new_elements = sizeof(new_elements) / sizeof(int);
3363 int new_element = new_elements[RND(num_new_elements)];
3365 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3366 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3368 // !!! CHECK AGAIN: Laser on Polarizer !!!
3379 element = EL_MIRROR_START + RND(16);
3385 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3392 element = (rnd == 0 ? EL_FUSE_ON :
3393 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3394 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3395 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3396 EL_MIRROR_FIXED_START + rnd - 25);
3401 graphic = el2gfx(element);
3403 for (i = 0; i < 50; i++)
3409 BlitBitmap(pix[PIX_BACK], drawto,
3410 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3411 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3412 SX + ELX * TILEX + x,
3413 SY + ELY * TILEY + y);
3415 MarkTileDirty(ELX, ELY);
3418 DrawLaser(0, DL_LASER_ENABLED);
3420 Delay_WithScreenUpdates(50);
3423 Tile[ELX][ELY] = element;
3424 DrawField_MM(ELX, ELY);
3427 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3430 // above stuff: GRAY BALL -> PRISM !!!
3432 LX = ELX * TILEX + 14;
3433 LY = ELY * TILEY + 14;
3434 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3441 laser.num_edges -= 2;
3442 laser.num_damages--;
3446 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3447 if (laser.damage[i].is_mirror)
3451 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3453 DrawLaser(0, DL_LASER_DISABLED);
3455 DrawLaser(0, DL_LASER_DISABLED);
3464 if (IS_WALL_ICE(element) && CT > 50)
3466 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3469 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3470 Store[ELX][ELY] = EL_WALL_ICE;
3471 Store2[ELX][ELY] = laser.wall_mask;
3473 laser.dest_element = Tile[ELX][ELY];
3478 for (i = 0; i < 5; i++)
3484 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3488 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3490 Delay_WithScreenUpdates(100);
3493 if (Tile[ELX][ELY] == EL_WALL_ICE)
3494 Tile[ELX][ELY] = EL_EMPTY;
3498 LX = laser.edge[laser.num_edges].x - cSX2;
3499 LY = laser.edge[laser.num_edges].y - cSY2;
3502 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3503 if (laser.damage[i].is_mirror)
3507 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3509 DrawLaser(0, DL_LASER_DISABLED);
3516 if (IS_WALL_AMOEBA(element) && CT > 60)
3518 int k1, k2, k3, dx, dy, de, dm;
3519 int element2 = Tile[ELX][ELY];
3521 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3524 for (i = laser.num_damages - 1; i >= 0; i--)
3525 if (laser.damage[i].is_mirror)
3528 r = laser.num_edges;
3529 d = laser.num_damages;
3536 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3539 DrawLaser(0, DL_LASER_ENABLED);
3542 x = laser.damage[k1].x;
3543 y = laser.damage[k1].y;
3548 for (i = 0; i < 4; i++)
3550 if (laser.wall_mask & (1 << i))
3552 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3553 cSY + ELY * TILEY + 31 * (i / 2)))
3556 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3557 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3564 for (i = 0; i < 4; i++)
3566 if (laser.wall_mask & (1 << i))
3568 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3569 cSY + ELY * TILEY + 31 * (i / 2)))
3576 if (laser.num_beamers > 0 ||
3577 k1 < 1 || k2 < 4 || k3 < 4 ||
3578 CheckLaserPixel(cSX + ELX * TILEX + 14,
3579 cSY + ELY * TILEY + 14))
3581 laser.num_edges = r;
3582 laser.num_damages = d;
3584 DrawLaser(0, DL_LASER_DISABLED);
3587 Tile[ELX][ELY] = element | laser.wall_mask;
3591 de = Tile[ELX][ELY];
3592 dm = laser.wall_mask;
3596 int x = ELX, y = ELY;
3597 int wall_mask = laser.wall_mask;
3600 DrawLaser(0, DL_LASER_ENABLED);
3602 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3604 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3605 Store[x][y] = EL_WALL_AMOEBA;
3606 Store2[x][y] = wall_mask;
3612 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3614 DrawLaser(0, DL_LASER_ENABLED);
3616 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3618 for (i = 4; i >= 0; i--)
3620 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3623 Delay_WithScreenUpdates(20);
3626 DrawLaser(0, DL_LASER_ENABLED);
3631 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3632 laser.stops_inside_element && CT > native_mm_level.time_block)
3637 if (ABS(XS) > ABS(YS))
3644 for (i = 0; i < 4; i++)
3651 x = ELX + Step[k * 4].x;
3652 y = ELY + Step[k * 4].y;
3654 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3657 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3665 laser.overloaded = (element == EL_BLOCK_STONE);
3670 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3673 Tile[x][y] = element;
3675 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3678 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3680 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3681 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3689 if (element == EL_FUEL_FULL && CT > 10)
3691 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3694 BlitBitmap(pix[PIX_DOOR], drawto,
3695 DOOR_GFX_PAGEX4 + XX_ENERGY,
3696 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3697 ENERGY_XSIZE, i, DX_ENERGY,
3698 DY_ENERGY + ENERGY_YSIZE - i);
3701 redraw_mask |= REDRAW_DOOR_1;
3704 Delay_WithScreenUpdates(20);
3707 game_mm.energy_left = MAX_LASER_ENERGY;
3708 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3709 DrawField_MM(ELX, ELY);
3711 DrawLaser(0, DL_LASER_ENABLED);
3719 void GameActions_MM(struct MouseActionInfo action)
3721 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3722 boolean button_released = (action.button == MB_RELEASED);
3724 GameActions_MM_Ext();
3726 CheckSingleStepMode_MM(element_clicked, button_released);
3729 void MovePacMen(void)
3731 int mx, my, ox, oy, nx, ny;
3735 if (++pacman_nr >= game_mm.num_pacman)
3738 game_mm.pacman[pacman_nr].dir--;
3740 for (l = 1; l < 5; l++)
3742 game_mm.pacman[pacman_nr].dir++;
3744 if (game_mm.pacman[pacman_nr].dir > 4)
3745 game_mm.pacman[pacman_nr].dir = 1;
3747 if (game_mm.pacman[pacman_nr].dir % 2)
3750 my = game_mm.pacman[pacman_nr].dir - 2;
3755 mx = 3 - game_mm.pacman[pacman_nr].dir;
3758 ox = game_mm.pacman[pacman_nr].x;
3759 oy = game_mm.pacman[pacman_nr].y;
3762 element = Tile[nx][ny];
3764 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3767 if (!IS_EATABLE4PACMAN(element))
3770 if (ObjHit(nx, ny, HIT_POS_CENTER))
3773 Tile[ox][oy] = EL_EMPTY;
3775 EL_PACMAN_RIGHT - 1 +
3776 (game_mm.pacman[pacman_nr].dir - 1 +
3777 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3779 game_mm.pacman[pacman_nr].x = nx;
3780 game_mm.pacman[pacman_nr].y = ny;
3782 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3784 if (element != EL_EMPTY)
3786 int graphic = el2gfx(Tile[nx][ny]);
3791 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3794 ox = cSX + ox * TILEX;
3795 oy = cSY + oy * TILEY;
3797 for (i = 1; i < 33; i += 2)
3798 BlitBitmap(bitmap, window,
3799 src_x, src_y, TILEX, TILEY,
3800 ox + i * mx, oy + i * my);
3801 Ct = Ct + FrameCounter - CT;
3804 DrawField_MM(nx, ny);
3807 if (!laser.fuse_off)
3809 DrawLaser(0, DL_LASER_ENABLED);
3811 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3813 AddDamagedField(nx, ny);
3815 laser.damage[laser.num_damages - 1].edge = 0;
3819 if (element == EL_BOMB)
3820 DeletePacMan(nx, ny);
3822 if (IS_WALL_AMOEBA(element) &&
3823 (LX + 2 * XS) / TILEX == nx &&
3824 (LY + 2 * YS) / TILEY == ny)
3834 static void InitMovingField_MM(int x, int y, int direction)
3836 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3837 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3839 MovDir[x][y] = direction;
3840 MovDir[newx][newy] = direction;
3842 if (Tile[newx][newy] == EL_EMPTY)
3843 Tile[newx][newy] = EL_BLOCKED;
3846 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3848 int direction = MovDir[x][y];
3849 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3850 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3856 static void Blocked2Moving_MM(int x, int y,
3857 int *comes_from_x, int *comes_from_y)
3859 int oldx = x, oldy = y;
3860 int direction = MovDir[x][y];
3862 if (direction == MV_LEFT)
3864 else if (direction == MV_RIGHT)
3866 else if (direction == MV_UP)
3868 else if (direction == MV_DOWN)
3871 *comes_from_x = oldx;
3872 *comes_from_y = oldy;
3875 static int MovingOrBlocked2Element_MM(int x, int y)
3877 int element = Tile[x][y];
3879 if (element == EL_BLOCKED)
3883 Blocked2Moving_MM(x, y, &oldx, &oldy);
3885 return Tile[oldx][oldy];
3892 static void RemoveField(int x, int y)
3894 Tile[x][y] = EL_EMPTY;
3901 static void RemoveMovingField_MM(int x, int y)
3903 int oldx = x, oldy = y, newx = x, newy = y;
3905 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3908 if (IS_MOVING(x, y))
3910 Moving2Blocked_MM(x, y, &newx, &newy);
3911 if (Tile[newx][newy] != EL_BLOCKED)
3914 else if (Tile[x][y] == EL_BLOCKED)
3916 Blocked2Moving_MM(x, y, &oldx, &oldy);
3917 if (!IS_MOVING(oldx, oldy))
3921 Tile[oldx][oldy] = EL_EMPTY;
3922 Tile[newx][newy] = EL_EMPTY;
3923 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3924 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3926 DrawLevelField_MM(oldx, oldy);
3927 DrawLevelField_MM(newx, newy);
3930 void PlaySoundLevel(int x, int y, int sound_nr)
3932 int sx = SCREENX(x), sy = SCREENY(y);
3934 int silence_distance = 8;
3936 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3937 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3940 if (!IN_LEV_FIELD(x, y) ||
3941 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3942 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3945 volume = SOUND_MAX_VOLUME;
3948 stereo = (sx - SCR_FIELDX/2) * 12;
3950 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3951 if (stereo > SOUND_MAX_RIGHT)
3952 stereo = SOUND_MAX_RIGHT;
3953 if (stereo < SOUND_MAX_LEFT)
3954 stereo = SOUND_MAX_LEFT;
3957 if (!IN_SCR_FIELD(sx, sy))
3959 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3960 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3962 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3965 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3968 static void RaiseScore_MM(int value)
3970 game_mm.score += value;
3973 void RaiseScoreElement_MM(int element)
3978 case EL_PACMAN_RIGHT:
3980 case EL_PACMAN_LEFT:
3981 case EL_PACMAN_DOWN:
3982 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3986 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3991 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3995 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4004 // ----------------------------------------------------------------------------
4005 // Mirror Magic game engine snapshot handling functions
4006 // ----------------------------------------------------------------------------
4008 void SaveEngineSnapshotValues_MM(void)
4012 engine_snapshot_mm.game_mm = game_mm;
4013 engine_snapshot_mm.laser = laser;
4015 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4017 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4019 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4020 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4021 engine_snapshot_mm.Box[x][y] = Box[x][y];
4022 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4023 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4027 engine_snapshot_mm.LX = LX;
4028 engine_snapshot_mm.LY = LY;
4029 engine_snapshot_mm.XS = XS;
4030 engine_snapshot_mm.YS = YS;
4031 engine_snapshot_mm.ELX = ELX;
4032 engine_snapshot_mm.ELY = ELY;
4033 engine_snapshot_mm.CT = CT;
4034 engine_snapshot_mm.Ct = Ct;
4036 engine_snapshot_mm.last_LX = last_LX;
4037 engine_snapshot_mm.last_LY = last_LY;
4038 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4039 engine_snapshot_mm.hold_x = hold_x;
4040 engine_snapshot_mm.hold_y = hold_y;
4041 engine_snapshot_mm.pacman_nr = pacman_nr;
4043 engine_snapshot_mm.rotate_delay = rotate_delay;
4044 engine_snapshot_mm.pacman_delay = pacman_delay;
4045 engine_snapshot_mm.energy_delay = energy_delay;
4046 engine_snapshot_mm.overload_delay = overload_delay;
4049 void LoadEngineSnapshotValues_MM(void)
4053 // stored engine snapshot buffers already restored at this point
4055 game_mm = engine_snapshot_mm.game_mm;
4056 laser = engine_snapshot_mm.laser;
4058 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4060 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4062 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4063 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4064 Box[x][y] = engine_snapshot_mm.Box[x][y];
4065 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4066 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4070 LX = engine_snapshot_mm.LX;
4071 LY = engine_snapshot_mm.LY;
4072 XS = engine_snapshot_mm.XS;
4073 YS = engine_snapshot_mm.YS;
4074 ELX = engine_snapshot_mm.ELX;
4075 ELY = engine_snapshot_mm.ELY;
4076 CT = engine_snapshot_mm.CT;
4077 Ct = engine_snapshot_mm.Ct;
4079 last_LX = engine_snapshot_mm.last_LX;
4080 last_LY = engine_snapshot_mm.last_LY;
4081 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4082 hold_x = engine_snapshot_mm.hold_x;
4083 hold_y = engine_snapshot_mm.hold_y;
4084 pacman_nr = engine_snapshot_mm.pacman_nr;
4086 rotate_delay = engine_snapshot_mm.rotate_delay;
4087 pacman_delay = engine_snapshot_mm.pacman_delay;
4088 energy_delay = engine_snapshot_mm.energy_delay;
4089 overload_delay = engine_snapshot_mm.overload_delay;
4091 RedrawPlayfield_MM();
4094 static int getAngleFromTouchDelta(int dx, int dy, int base)
4096 double pi = 3.141592653;
4097 double rad = atan2((double)-dy, (double)dx);
4098 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4099 double deg = rad2 * 180.0 / pi;
4101 return (int)(deg * base / 360.0 + 0.5) % base;
4104 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4106 // calculate start (source) position to be at the middle of the tile
4107 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4108 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4109 int dx = dst_mx - src_mx;
4110 int dy = dst_my - src_my;
4119 if (!IN_LEV_FIELD(x, y))
4122 element = Tile[x][y];
4124 if (!IS_MCDUFFIN(element) &&
4125 !IS_MIRROR(element) &&
4126 !IS_BEAMER(element) &&
4127 !IS_POLAR(element) &&
4128 !IS_POLAR_CROSS(element) &&
4129 !IS_DF_MIRROR(element))
4132 angle_old = get_element_angle(element);
4134 if (IS_MCDUFFIN(element))
4136 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4137 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4138 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4139 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4142 else if (IS_MIRROR(element) ||
4143 IS_DF_MIRROR(element))
4145 for (i = 0; i < laser.num_damages; i++)
4147 if (laser.damage[i].x == x &&
4148 laser.damage[i].y == y &&
4149 ObjHit(x, y, HIT_POS_CENTER))
4151 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4152 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4159 if (angle_new == -1)
4161 if (IS_MIRROR(element) ||
4162 IS_DF_MIRROR(element) ||
4166 if (IS_POLAR_CROSS(element))
4169 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4172 button = (angle_new == angle_old ? 0 :
4173 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4174 MB_LEFTBUTTON : MB_RIGHTBUTTON);