1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 // graphic position values for game controls
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 // values for Explode_MM()
27 #define EX_PHASE_START 0
32 // special positions in the game control window (relative to control window)
41 #define XX_OVERLOAD 60
42 #define YY_OVERLOAD YY_ENERGY
44 // special positions in the game control window (relative to main window)
45 #define DX_LEVEL (DX + XX_LEVEL)
46 #define DY_LEVEL (DY + YY_LEVEL)
47 #define DX_KETTLES (DX + XX_KETTLES)
48 #define DY_KETTLES (DY + YY_KETTLES)
49 #define DX_SCORE (DX + XX_SCORE)
50 #define DY_SCORE (DY + YY_SCORE)
51 #define DX_ENERGY (DX + XX_ENERGY)
52 #define DY_ENERGY (DY + YY_ENERGY)
53 #define DX_OVERLOAD (DX + XX_OVERLOAD)
54 #define DY_OVERLOAD (DY + YY_OVERLOAD)
56 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
57 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
59 // game button identifiers
60 #define GAME_CTRL_ID_LEFT 0
61 #define GAME_CTRL_ID_MIDDLE 1
62 #define GAME_CTRL_ID_RIGHT 2
64 #define NUM_GAME_BUTTONS 3
66 // values for DrawLaser()
67 #define DL_LASER_DISABLED 0
68 #define DL_LASER_ENABLED 1
70 // values for 'click_delay_value' in ClickElement()
71 #define CLICK_DELAY_FIRST 12 // delay (frames) after first click
72 #define CLICK_DELAY 6 // delay (frames) for pressed butten
74 #define AUTO_ROTATE_DELAY CLICK_DELAY
75 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
76 #define NUM_INIT_CYCLE_STEPS 16
77 #define PACMAN_MOVE_DELAY 12
78 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
79 #define HEALTH_DEC_DELAY 3
80 #define HEALTH_INC_DELAY 9
81 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
83 #define BEGIN_NO_HEADLESS \
85 boolean last_headless = program.headless; \
87 program.headless = FALSE; \
89 #define END_NO_HEADLESS \
90 program.headless = last_headless; \
93 // forward declaration for internal use
94 static int MovingOrBlocked2Element_MM(int, int);
95 static void Bang_MM(int, int);
96 static void RaiseScore_MM(int);
97 static void RaiseScoreElement_MM(int);
98 static void RemoveMovingField_MM(int, int);
99 static void InitMovingField_MM(int, int, int);
100 static void ContinueMoving_MM(int, int);
101 static void Moving2Blocked_MM(int, int, int *, int *);
103 // bitmap for laser beam detection
104 static Bitmap *laser_bitmap = NULL;
106 // variables for laser control
107 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
108 static int hold_x = -1, hold_y = -1;
110 // variables for pacman control
111 static int pacman_nr = -1;
113 // various game engine delay counters
114 static DelayCounter rotate_delay = { AUTO_ROTATE_DELAY };
115 static DelayCounter pacman_delay = { PACMAN_MOVE_DELAY };
116 static DelayCounter energy_delay = { ENERGY_DELAY };
117 static DelayCounter overload_delay = { 0 };
119 // element masks for scanning pixels of MM elements
120 static const char mm_masks[10][16][16 + 1] =
304 static int get_element_angle(int element)
306 int element_phase = get_element_phase(element);
308 if (IS_MIRROR_FIXED(element) ||
309 IS_MCDUFFIN(element) ||
311 IS_RECEIVER(element))
312 return 4 * element_phase;
314 return element_phase;
317 static int get_opposite_angle(int angle)
319 int opposite_angle = angle + ANG_RAY_180;
321 // make sure "opposite_angle" is in valid interval [0, 15]
322 return (opposite_angle + 16) % 16;
325 static int get_mirrored_angle(int laser_angle, int mirror_angle)
327 int reflected_angle = 16 - laser_angle + mirror_angle;
329 // make sure "reflected_angle" is in valid interval [0, 15]
330 return (reflected_angle + 16) % 16;
333 static void DrawLaserLines(struct XY *points, int num_points, int mode)
335 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
336 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
338 DrawLines(drawto, points, num_points, pixel_drawto);
342 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
347 static boolean CheckLaserPixel(int x, int y)
353 pixel = ReadPixel(laser_bitmap, x, y);
357 return (pixel == WHITE_PIXEL);
360 static void CheckExitMM(void)
362 int exit_element = EL_EMPTY;
366 static int xy[4][2] =
374 for (y = 0; y < lev_fieldy; y++)
376 for (x = 0; x < lev_fieldx; x++)
378 if (Tile[x][y] == EL_EXIT_CLOSED)
380 // initiate opening animation of exit door
381 Tile[x][y] = EL_EXIT_OPENING;
383 exit_element = EL_EXIT_OPEN;
387 else if (IS_RECEIVER(Tile[x][y]))
389 // remove field that blocks receiver
390 int phase = Tile[x][y] - EL_RECEIVER_START;
391 int blocking_x, blocking_y;
393 blocking_x = x + xy[phase][0];
394 blocking_y = y + xy[phase][1];
396 if (IN_LEV_FIELD(blocking_x, blocking_y))
398 Tile[blocking_x][blocking_y] = EL_EMPTY;
400 DrawField_MM(blocking_x, blocking_y);
403 exit_element = EL_RECEIVER;
410 if (exit_element != EL_EMPTY)
411 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
414 static void SetLaserColor(int brightness)
416 int color_min = 0x00;
417 int color_max = brightness; // (0x00 <= brightness <= 0xFF)
418 int color_up = color_max * laser.overload_value / MAX_LASER_OVERLOAD;
419 int color_down = color_max - color_up;
422 GetPixelFromRGB(window,
423 (native_mm_level.laser_red ? color_max : color_up),
424 (native_mm_level.laser_green ? color_down : color_min),
425 (native_mm_level.laser_blue ? color_down : color_min));
428 static void InitMovDir_MM(int x, int y)
430 int element = Tile[x][y];
431 static int direction[3][4] =
433 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
434 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
435 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
440 case EL_PACMAN_RIGHT:
444 Tile[x][y] = EL_PACMAN;
445 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
453 static void InitField(int x, int y)
455 int element = Tile[x][y];
460 Tile[x][y] = EL_EMPTY;
465 if (native_mm_level.auto_count_kettles)
466 game_mm.kettles_still_needed++;
469 case EL_LIGHTBULB_OFF:
470 game_mm.lights_still_needed++;
474 if (IS_MIRROR(element) ||
475 IS_BEAMER_OLD(element) ||
476 IS_BEAMER(element) ||
478 IS_POLAR_CROSS(element) ||
479 IS_DF_MIRROR(element) ||
480 IS_DF_MIRROR_AUTO(element) ||
481 IS_GRID_STEEL_AUTO(element) ||
482 IS_GRID_WOOD_AUTO(element) ||
483 IS_FIBRE_OPTIC(element))
485 if (IS_BEAMER_OLD(element))
487 Tile[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
488 element = Tile[x][y];
491 if (!IS_FIBRE_OPTIC(element))
493 static int steps_grid_auto = 0;
495 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
496 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
498 if (IS_GRID_STEEL_AUTO(element) ||
499 IS_GRID_WOOD_AUTO(element))
500 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
502 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
504 game_mm.cycle[game_mm.num_cycle].x = x;
505 game_mm.cycle[game_mm.num_cycle].y = y;
509 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
511 int beamer_nr = BEAMER_NR(element);
512 int nr = laser.beamer[beamer_nr][0].num;
514 laser.beamer[beamer_nr][nr].x = x;
515 laser.beamer[beamer_nr][nr].y = y;
516 laser.beamer[beamer_nr][nr].num = 1;
519 else if (IS_PACMAN(element))
523 else if (IS_MCDUFFIN(element) || IS_LASER(element))
525 laser.start_edge.x = x;
526 laser.start_edge.y = y;
527 laser.start_angle = get_element_angle(element);
534 static void InitCycleElements_RotateSingleStep(void)
538 if (game_mm.num_cycle == 0) // no elements to cycle
541 for (i = 0; i < game_mm.num_cycle; i++)
543 int x = game_mm.cycle[i].x;
544 int y = game_mm.cycle[i].y;
545 int step = SIGN(game_mm.cycle[i].steps);
546 int last_element = Tile[x][y];
547 int next_element = get_rotated_element(last_element, step);
549 if (!game_mm.cycle[i].steps)
552 Tile[x][y] = next_element;
554 game_mm.cycle[i].steps -= step;
558 static void InitLaser(void)
560 int start_element = Tile[laser.start_edge.x][laser.start_edge.y];
561 int step = (IS_LASER(start_element) ? 4 : 0);
563 LX = laser.start_edge.x * TILEX;
564 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
567 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
569 LY = laser.start_edge.y * TILEY;
570 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
571 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
575 XS = 2 * Step[laser.start_angle].x;
576 YS = 2 * Step[laser.start_angle].y;
578 laser.current_angle = laser.start_angle;
580 laser.num_damages = 0;
582 laser.num_beamers = 0;
583 laser.beamer_edge[0] = 0;
585 laser.dest_element = EL_EMPTY;
588 AddLaserEdge(LX, LY); // set laser starting edge
593 void InitGameEngine_MM(void)
599 // initialize laser bitmap to current playfield (screen) size
600 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
601 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
605 // set global game control values
606 game_mm.num_cycle = 0;
607 game_mm.num_pacman = 0;
610 game_mm.energy_left = 0; // later set to "native_mm_level.time"
611 game_mm.kettles_still_needed =
612 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
613 game_mm.lights_still_needed = 0;
614 game_mm.num_keys = 0;
616 game_mm.level_solved = FALSE;
617 game_mm.game_over = FALSE;
618 game_mm.game_over_cause = 0;
620 game_mm.laser_overload_value = 0;
621 game_mm.laser_enabled = FALSE;
623 // set global laser control values (must be set before "InitLaser()")
624 laser.start_edge.x = 0;
625 laser.start_edge.y = 0;
626 laser.start_angle = 0;
628 for (i = 0; i < MAX_NUM_BEAMERS; i++)
629 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
631 laser.overloaded = FALSE;
632 laser.overload_value = 0;
633 laser.fuse_off = FALSE;
634 laser.fuse_x = laser.fuse_y = -1;
636 laser.dest_element = EL_EMPTY;
650 rotate_delay.count = 0;
651 pacman_delay.count = 0;
652 energy_delay.count = 0;
653 overload_delay.count = 0;
655 ClickElement(-1, -1, -1);
657 for (x = 0; x < lev_fieldx; x++)
659 for (y = 0; y < lev_fieldy; y++)
661 Tile[x][y] = Ur[x][y];
662 Hit[x][y] = Box[x][y] = 0;
664 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
665 Store[x][y] = Store2[x][y] = 0;
676 void InitGameActions_MM(void)
678 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
679 int cycle_steps_done = 0;
684 for (i = 0; i <= num_init_game_frames; i++)
686 if (i == num_init_game_frames)
687 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
688 else if (setup.sound_loops)
689 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
691 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
693 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
695 UpdateAndDisplayGameControlValues();
697 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
699 InitCycleElements_RotateSingleStep();
704 AdvanceFrameCounter();
714 if (setup.quick_doors)
721 if (game_mm.kettles_still_needed == 0)
724 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
725 SetTileCursorActive(TRUE);
728 static void FadeOutLaser(boolean overloaded)
732 for (i = 15; i >= 0; i--)
734 SetLaserColor(0x11 * i);
736 DrawLaser(0, DL_LASER_ENABLED);
739 Delay_WithScreenUpdates(50);
742 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;
2521 Frame[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, Frame[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 (FrameReached(&energy_delay))
3206 if (game_mm.energy_left > 0)
3208 game_mm.energy_left--;
3210 redraw_mask |= REDRAW_DOOR_1;
3212 else if (game.time_limit && !game_mm.game_over)
3214 FadeOutLaser(FALSE);
3216 GameOver_MM(GAME_OVER_NO_ENERGY);
3222 element = laser.dest_element;
3225 if (element != Tile[ELX][ELY])
3227 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3228 element, Tile[ELX][ELY]);
3232 if (!laser.overloaded && laser.overload_value == 0 &&
3233 element != EL_BOMB &&
3234 element != EL_MINE &&
3235 element != EL_BALL_GRAY &&
3236 element != EL_BLOCK_STONE &&
3237 element != EL_BLOCK_WOOD &&
3238 element != EL_FUSE_ON &&
3239 element != EL_FUEL_FULL &&
3240 !IS_WALL_ICE(element) &&
3241 !IS_WALL_AMOEBA(element))
3244 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3246 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3247 (!laser.overloaded && laser.overload_value > 0)) &&
3248 FrameReached(&overload_delay))
3250 if (laser.overloaded)
3251 laser.overload_value++;
3253 laser.overload_value--;
3255 if (game_mm.cheat_no_overload)
3257 laser.overloaded = FALSE;
3258 laser.overload_value = 0;
3261 game_mm.laser_overload_value = laser.overload_value;
3263 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3265 SetLaserColor(0xFF);
3267 DrawLaser(0, DL_LASER_ENABLED);
3270 if (!laser.overloaded)
3271 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3272 else if (setup.sound_loops)
3273 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3275 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3277 if (laser.overloaded)
3280 BlitBitmap(pix[PIX_DOOR], drawto,
3281 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3282 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3283 - laser.overload_value,
3284 OVERLOAD_XSIZE, laser.overload_value,
3285 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3286 - laser.overload_value);
3288 redraw_mask |= REDRAW_DOOR_1;
3293 BlitBitmap(pix[PIX_DOOR], drawto,
3294 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3295 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3296 DX_OVERLOAD, DY_OVERLOAD);
3298 redraw_mask |= REDRAW_DOOR_1;
3301 if (laser.overload_value == MAX_LASER_OVERLOAD)
3303 UpdateAndDisplayGameControlValues();
3307 GameOver_MM(GAME_OVER_OVERLOADED);
3318 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3320 if (game_mm.cheat_no_explosion)
3325 laser.dest_element = EL_EXPLODING_OPAQUE;
3330 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3332 laser.fuse_off = TRUE;
3336 DrawLaser(0, DL_LASER_DISABLED);
3337 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3340 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3342 static int new_elements[] =
3345 EL_MIRROR_FIXED_START,
3347 EL_POLAR_CROSS_START,
3353 int num_new_elements = sizeof(new_elements) / sizeof(int);
3354 int new_element = new_elements[RND(num_new_elements)];
3356 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3357 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3359 // !!! CHECK AGAIN: Laser on Polarizer !!!
3370 element = EL_MIRROR_START + RND(16);
3376 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3383 element = (rnd == 0 ? EL_FUSE_ON :
3384 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3385 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3386 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3387 EL_MIRROR_FIXED_START + rnd - 25);
3392 graphic = el2gfx(element);
3394 for (i = 0; i < 50; i++)
3400 BlitBitmap(pix[PIX_BACK], drawto,
3401 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3402 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3403 SX + ELX * TILEX + x,
3404 SY + ELY * TILEY + y);
3406 MarkTileDirty(ELX, ELY);
3409 DrawLaser(0, DL_LASER_ENABLED);
3411 Delay_WithScreenUpdates(50);
3414 Tile[ELX][ELY] = element;
3415 DrawField_MM(ELX, ELY);
3418 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3421 // above stuff: GRAY BALL -> PRISM !!!
3423 LX = ELX * TILEX + 14;
3424 LY = ELY * TILEY + 14;
3425 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3432 laser.num_edges -= 2;
3433 laser.num_damages--;
3437 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3438 if (laser.damage[i].is_mirror)
3442 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3444 DrawLaser(0, DL_LASER_DISABLED);
3446 DrawLaser(0, DL_LASER_DISABLED);
3455 if (IS_WALL_ICE(element) && CT > 50)
3457 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3460 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3461 Store[ELX][ELY] = EL_WALL_ICE;
3462 Store2[ELX][ELY] = laser.wall_mask;
3464 laser.dest_element = Tile[ELX][ELY];
3469 for (i = 0; i < 5; i++)
3475 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3479 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3481 Delay_WithScreenUpdates(100);
3484 if (Tile[ELX][ELY] == EL_WALL_ICE)
3485 Tile[ELX][ELY] = EL_EMPTY;
3489 LX = laser.edge[laser.num_edges].x - cSX2;
3490 LY = laser.edge[laser.num_edges].y - cSY2;
3493 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3494 if (laser.damage[i].is_mirror)
3498 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3500 DrawLaser(0, DL_LASER_DISABLED);
3507 if (IS_WALL_AMOEBA(element) && CT > 60)
3509 int k1, k2, k3, dx, dy, de, dm;
3510 int element2 = Tile[ELX][ELY];
3512 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3515 for (i = laser.num_damages - 1; i >= 0; i--)
3516 if (laser.damage[i].is_mirror)
3519 r = laser.num_edges;
3520 d = laser.num_damages;
3527 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3530 DrawLaser(0, DL_LASER_ENABLED);
3533 x = laser.damage[k1].x;
3534 y = laser.damage[k1].y;
3539 for (i = 0; i < 4; i++)
3541 if (laser.wall_mask & (1 << i))
3543 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3544 cSY + ELY * TILEY + 31 * (i / 2)))
3547 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3548 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3555 for (i = 0; i < 4; i++)
3557 if (laser.wall_mask & (1 << i))
3559 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3560 cSY + ELY * TILEY + 31 * (i / 2)))
3567 if (laser.num_beamers > 0 ||
3568 k1 < 1 || k2 < 4 || k3 < 4 ||
3569 CheckLaserPixel(cSX + ELX * TILEX + 14,
3570 cSY + ELY * TILEY + 14))
3572 laser.num_edges = r;
3573 laser.num_damages = d;
3575 DrawLaser(0, DL_LASER_DISABLED);
3578 Tile[ELX][ELY] = element | laser.wall_mask;
3582 de = Tile[ELX][ELY];
3583 dm = laser.wall_mask;
3587 int x = ELX, y = ELY;
3588 int wall_mask = laser.wall_mask;
3591 DrawLaser(0, DL_LASER_ENABLED);
3593 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3595 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3596 Store[x][y] = EL_WALL_AMOEBA;
3597 Store2[x][y] = wall_mask;
3603 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3605 DrawLaser(0, DL_LASER_ENABLED);
3607 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3609 for (i = 4; i >= 0; i--)
3611 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3614 Delay_WithScreenUpdates(20);
3617 DrawLaser(0, DL_LASER_ENABLED);
3622 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3623 laser.stops_inside_element && CT > native_mm_level.time_block)
3628 if (ABS(XS) > ABS(YS))
3635 for (i = 0; i < 4; i++)
3642 x = ELX + Step[k * 4].x;
3643 y = ELY + Step[k * 4].y;
3645 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3648 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3656 laser.overloaded = (element == EL_BLOCK_STONE);
3661 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3664 Tile[x][y] = element;
3666 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3669 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3671 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3672 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3680 if (element == EL_FUEL_FULL && CT > 10)
3682 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3685 BlitBitmap(pix[PIX_DOOR], drawto,
3686 DOOR_GFX_PAGEX4 + XX_ENERGY,
3687 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3688 ENERGY_XSIZE, i, DX_ENERGY,
3689 DY_ENERGY + ENERGY_YSIZE - i);
3692 redraw_mask |= REDRAW_DOOR_1;
3695 Delay_WithScreenUpdates(20);
3698 game_mm.energy_left = MAX_LASER_ENERGY;
3699 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3700 DrawField_MM(ELX, ELY);
3702 DrawLaser(0, DL_LASER_ENABLED);
3710 void GameActions_MM(struct MouseActionInfo action)
3712 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3713 boolean button_released = (action.button == MB_RELEASED);
3715 GameActions_MM_Ext();
3717 CheckSingleStepMode_MM(element_clicked, button_released);
3720 void MovePacMen(void)
3722 int mx, my, ox, oy, nx, ny;
3726 if (++pacman_nr >= game_mm.num_pacman)
3729 game_mm.pacman[pacman_nr].dir--;
3731 for (l = 1; l < 5; l++)
3733 game_mm.pacman[pacman_nr].dir++;
3735 if (game_mm.pacman[pacman_nr].dir > 4)
3736 game_mm.pacman[pacman_nr].dir = 1;
3738 if (game_mm.pacman[pacman_nr].dir % 2)
3741 my = game_mm.pacman[pacman_nr].dir - 2;
3746 mx = 3 - game_mm.pacman[pacman_nr].dir;
3749 ox = game_mm.pacman[pacman_nr].x;
3750 oy = game_mm.pacman[pacman_nr].y;
3753 element = Tile[nx][ny];
3755 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3758 if (!IS_EATABLE4PACMAN(element))
3761 if (ObjHit(nx, ny, HIT_POS_CENTER))
3764 Tile[ox][oy] = EL_EMPTY;
3766 EL_PACMAN_RIGHT - 1 +
3767 (game_mm.pacman[pacman_nr].dir - 1 +
3768 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3770 game_mm.pacman[pacman_nr].x = nx;
3771 game_mm.pacman[pacman_nr].y = ny;
3773 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3775 if (element != EL_EMPTY)
3777 int graphic = el2gfx(Tile[nx][ny]);
3782 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3785 ox = cSX + ox * TILEX;
3786 oy = cSY + oy * TILEY;
3788 for (i = 1; i < 33; i += 2)
3789 BlitBitmap(bitmap, window,
3790 src_x, src_y, TILEX, TILEY,
3791 ox + i * mx, oy + i * my);
3792 Ct = Ct + FrameCounter - CT;
3795 DrawField_MM(nx, ny);
3798 if (!laser.fuse_off)
3800 DrawLaser(0, DL_LASER_ENABLED);
3802 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3804 AddDamagedField(nx, ny);
3806 laser.damage[laser.num_damages - 1].edge = 0;
3810 if (element == EL_BOMB)
3811 DeletePacMan(nx, ny);
3813 if (IS_WALL_AMOEBA(element) &&
3814 (LX + 2 * XS) / TILEX == nx &&
3815 (LY + 2 * YS) / TILEY == ny)
3825 static void InitMovingField_MM(int x, int y, int direction)
3827 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3828 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3830 MovDir[x][y] = direction;
3831 MovDir[newx][newy] = direction;
3833 if (Tile[newx][newy] == EL_EMPTY)
3834 Tile[newx][newy] = EL_BLOCKED;
3837 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3839 int direction = MovDir[x][y];
3840 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3841 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3847 static void Blocked2Moving_MM(int x, int y,
3848 int *comes_from_x, int *comes_from_y)
3850 int oldx = x, oldy = y;
3851 int direction = MovDir[x][y];
3853 if (direction == MV_LEFT)
3855 else if (direction == MV_RIGHT)
3857 else if (direction == MV_UP)
3859 else if (direction == MV_DOWN)
3862 *comes_from_x = oldx;
3863 *comes_from_y = oldy;
3866 static int MovingOrBlocked2Element_MM(int x, int y)
3868 int element = Tile[x][y];
3870 if (element == EL_BLOCKED)
3874 Blocked2Moving_MM(x, y, &oldx, &oldy);
3876 return Tile[oldx][oldy];
3883 static void RemoveField(int x, int y)
3885 Tile[x][y] = EL_EMPTY;
3892 static void RemoveMovingField_MM(int x, int y)
3894 int oldx = x, oldy = y, newx = x, newy = y;
3896 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3899 if (IS_MOVING(x, y))
3901 Moving2Blocked_MM(x, y, &newx, &newy);
3902 if (Tile[newx][newy] != EL_BLOCKED)
3905 else if (Tile[x][y] == EL_BLOCKED)
3907 Blocked2Moving_MM(x, y, &oldx, &oldy);
3908 if (!IS_MOVING(oldx, oldy))
3912 Tile[oldx][oldy] = EL_EMPTY;
3913 Tile[newx][newy] = EL_EMPTY;
3914 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3915 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3917 DrawLevelField_MM(oldx, oldy);
3918 DrawLevelField_MM(newx, newy);
3921 void PlaySoundLevel(int x, int y, int sound_nr)
3923 int sx = SCREENX(x), sy = SCREENY(y);
3925 int silence_distance = 8;
3927 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3928 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3931 if (!IN_LEV_FIELD(x, y) ||
3932 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3933 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3936 volume = SOUND_MAX_VOLUME;
3939 stereo = (sx - SCR_FIELDX/2) * 12;
3941 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3942 if (stereo > SOUND_MAX_RIGHT)
3943 stereo = SOUND_MAX_RIGHT;
3944 if (stereo < SOUND_MAX_LEFT)
3945 stereo = SOUND_MAX_LEFT;
3948 if (!IN_SCR_FIELD(sx, sy))
3950 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3951 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3953 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3956 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3959 static void RaiseScore_MM(int value)
3961 game_mm.score += value;
3964 void RaiseScoreElement_MM(int element)
3969 case EL_PACMAN_RIGHT:
3971 case EL_PACMAN_LEFT:
3972 case EL_PACMAN_DOWN:
3973 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3977 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3982 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3986 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3995 // ----------------------------------------------------------------------------
3996 // Mirror Magic game engine snapshot handling functions
3997 // ----------------------------------------------------------------------------
3999 void SaveEngineSnapshotValues_MM(void)
4003 engine_snapshot_mm.game_mm = game_mm;
4004 engine_snapshot_mm.laser = laser;
4006 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4008 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4010 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4011 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4012 engine_snapshot_mm.Box[x][y] = Box[x][y];
4013 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4014 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4018 engine_snapshot_mm.LX = LX;
4019 engine_snapshot_mm.LY = LY;
4020 engine_snapshot_mm.XS = XS;
4021 engine_snapshot_mm.YS = YS;
4022 engine_snapshot_mm.ELX = ELX;
4023 engine_snapshot_mm.ELY = ELY;
4024 engine_snapshot_mm.CT = CT;
4025 engine_snapshot_mm.Ct = Ct;
4027 engine_snapshot_mm.last_LX = last_LX;
4028 engine_snapshot_mm.last_LY = last_LY;
4029 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4030 engine_snapshot_mm.hold_x = hold_x;
4031 engine_snapshot_mm.hold_y = hold_y;
4032 engine_snapshot_mm.pacman_nr = pacman_nr;
4034 engine_snapshot_mm.rotate_delay = rotate_delay;
4035 engine_snapshot_mm.pacman_delay = pacman_delay;
4036 engine_snapshot_mm.energy_delay = energy_delay;
4037 engine_snapshot_mm.overload_delay = overload_delay;
4040 void LoadEngineSnapshotValues_MM(void)
4044 // stored engine snapshot buffers already restored at this point
4046 game_mm = engine_snapshot_mm.game_mm;
4047 laser = engine_snapshot_mm.laser;
4049 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4051 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4053 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4054 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4055 Box[x][y] = engine_snapshot_mm.Box[x][y];
4056 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4057 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4061 LX = engine_snapshot_mm.LX;
4062 LY = engine_snapshot_mm.LY;
4063 XS = engine_snapshot_mm.XS;
4064 YS = engine_snapshot_mm.YS;
4065 ELX = engine_snapshot_mm.ELX;
4066 ELY = engine_snapshot_mm.ELY;
4067 CT = engine_snapshot_mm.CT;
4068 Ct = engine_snapshot_mm.Ct;
4070 last_LX = engine_snapshot_mm.last_LX;
4071 last_LY = engine_snapshot_mm.last_LY;
4072 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4073 hold_x = engine_snapshot_mm.hold_x;
4074 hold_y = engine_snapshot_mm.hold_y;
4075 pacman_nr = engine_snapshot_mm.pacman_nr;
4077 rotate_delay = engine_snapshot_mm.rotate_delay;
4078 pacman_delay = engine_snapshot_mm.pacman_delay;
4079 energy_delay = engine_snapshot_mm.energy_delay;
4080 overload_delay = engine_snapshot_mm.overload_delay;
4082 RedrawPlayfield_MM();
4085 static int getAngleFromTouchDelta(int dx, int dy, int base)
4087 double pi = 3.141592653;
4088 double rad = atan2((double)-dy, (double)dx);
4089 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4090 double deg = rad2 * 180.0 / pi;
4092 return (int)(deg * base / 360.0 + 0.5) % base;
4095 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4097 // calculate start (source) position to be at the middle of the tile
4098 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4099 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4100 int dx = dst_mx - src_mx;
4101 int dy = dst_my - src_my;
4110 if (!IN_LEV_FIELD(x, y))
4113 element = Tile[x][y];
4115 if (!IS_MCDUFFIN(element) &&
4116 !IS_MIRROR(element) &&
4117 !IS_BEAMER(element) &&
4118 !IS_POLAR(element) &&
4119 !IS_POLAR_CROSS(element) &&
4120 !IS_DF_MIRROR(element))
4123 angle_old = get_element_angle(element);
4125 if (IS_MCDUFFIN(element))
4127 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4128 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4129 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4130 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4133 else if (IS_MIRROR(element) ||
4134 IS_DF_MIRROR(element))
4136 for (i = 0; i < laser.num_damages; i++)
4138 if (laser.damage[i].x == x &&
4139 laser.damage[i].y == y &&
4140 ObjHit(x, y, HIT_POS_CENTER))
4142 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4143 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4150 if (angle_new == -1)
4152 if (IS_MIRROR(element) ||
4153 IS_DF_MIRROR(element) ||
4157 if (IS_POLAR_CROSS(element))
4160 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4163 button = (angle_new == angle_old ? 0 :
4164 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4165 MB_LEFTBUTTON : MB_RIGHTBUTTON);