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(void)
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);
744 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
747 static void GameOver_MM(int game_over_cause)
749 // do not handle game over if request dialog is already active
750 if (game.request_active)
753 game_mm.game_over = TRUE;
754 game_mm.game_over_cause = game_over_cause;
756 if (setup.ask_on_game_over)
757 game.restart_game_message = (game_over_cause == GAME_OVER_BOMB ?
758 "Bomb killed Mc Duffin! Play it again?" :
759 game_over_cause == GAME_OVER_NO_ENERGY ?
760 "Out of magic energy! Play it again?" :
761 game_over_cause == GAME_OVER_OVERLOADED ?
762 "Magic spell hit Mc Duffin! Play it again?" :
765 SetTileCursorActive(FALSE);
768 void AddLaserEdge(int lx, int ly)
773 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
775 Warn("AddLaserEdge: out of bounds: %d, %d", lx, ly);
780 laser.edge[laser.num_edges].x = cSX2 + lx;
781 laser.edge[laser.num_edges].y = cSY2 + ly;
787 void AddDamagedField(int ex, int ey)
789 laser.damage[laser.num_damages].is_mirror = FALSE;
790 laser.damage[laser.num_damages].angle = laser.current_angle;
791 laser.damage[laser.num_damages].edge = laser.num_edges;
792 laser.damage[laser.num_damages].x = ex;
793 laser.damage[laser.num_damages].y = ey;
797 static boolean StepBehind(void)
803 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
804 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
806 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
812 static int getMaskFromElement(int element)
814 if (IS_GRID(element))
815 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
816 else if (IS_MCDUFFIN(element))
817 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
818 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
819 return IMG_MM_MASK_RECTANGLE;
821 return IMG_MM_MASK_CIRCLE;
824 static int ScanPixel(void)
829 Debug("game:mm:ScanPixel", "start scanning at (%d, %d) [%d, %d] [%d, %d]",
830 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
833 // follow laser beam until it hits something (at least the screen border)
834 while (hit_mask == HIT_MASK_NO_HIT)
840 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
841 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
843 Debug("game:mm:ScanPixel", "touched screen border!");
849 for (i = 0; i < 4; i++)
851 int px = LX + (i % 2) * 2;
852 int py = LY + (i / 2) * 2;
855 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
856 int ly = (py + TILEY) / TILEY - 1; // negative values!
859 if (IN_LEV_FIELD(lx, ly))
861 int element = Tile[lx][ly];
863 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
867 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
869 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
871 pixel = ((element & (1 << pos)) ? 1 : 0);
875 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
877 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
882 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
883 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
886 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
887 hit_mask |= (1 << i);
890 if (hit_mask == HIT_MASK_NO_HIT)
892 // hit nothing -- go on with another step
904 int end = 0, rf = laser.num_edges;
906 // do not scan laser again after the game was lost for whatever reason
907 if (game_mm.game_over)
910 laser.overloaded = FALSE;
911 laser.stops_inside_element = FALSE;
913 DrawLaser(0, DL_LASER_ENABLED);
916 Debug("game:mm:ScanLaser",
917 "Start scanning with LX == %d, LY == %d, XS == %d, YS == %d",
925 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
928 laser.overloaded = TRUE;
933 hit_mask = ScanPixel();
936 Debug("game:mm:ScanLaser",
937 "Hit something at LX == %d, LY == %d, XS == %d, YS == %d",
941 // hit something -- check out what it was
942 ELX = (LX + XS) / TILEX;
943 ELY = (LY + YS) / TILEY;
946 Debug("game:mm:ScanLaser", "hit_mask (1) == '%x' (%d, %d) (%d, %d)",
947 hit_mask, LX, LY, ELX, ELY);
950 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
953 laser.dest_element = element;
958 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
960 /* we have hit the top-right and bottom-left element --
961 choose the bottom-left one */
962 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
963 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
964 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
965 ELX = (LX - 2) / TILEX;
966 ELY = (LY + 2) / TILEY;
969 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
971 /* we have hit the top-left and bottom-right element --
972 choose the top-left one */
974 ELX = (LX - 2) / TILEX;
975 ELY = (LY - 2) / TILEY;
979 Debug("game:mm:ScanLaser", "hit_mask (2) == '%x' (%d, %d) (%d, %d)",
980 hit_mask, LX, LY, ELX, ELY);
983 element = Tile[ELX][ELY];
984 laser.dest_element = element;
987 Debug("game:mm:ScanLaser",
988 "Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]",
991 LX % TILEX, LY % TILEY,
996 if (!IN_LEV_FIELD(ELX, ELY))
997 Debug("game:mm:ScanLaser", "WARNING! (1) %d, %d (%d)",
1001 if (element == EL_EMPTY)
1003 if (!HitOnlyAnEdge(hit_mask))
1006 else if (element == EL_FUSE_ON)
1008 if (HitPolarizer(element, hit_mask))
1011 else if (IS_GRID(element) || IS_DF_GRID(element))
1013 if (HitPolarizer(element, hit_mask))
1016 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
1017 element == EL_GATE_STONE || element == EL_GATE_WOOD)
1019 if (HitBlock(element, hit_mask))
1026 else if (IS_MCDUFFIN(element))
1028 if (HitLaserSource(element, hit_mask))
1031 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1032 IS_RECEIVER(element))
1034 if (HitLaserDestination(element, hit_mask))
1037 else if (IS_WALL(element))
1039 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1041 if (HitReflectingWalls(element, hit_mask))
1046 if (HitAbsorbingWalls(element, hit_mask))
1052 if (HitElement(element, hit_mask))
1057 DrawLaser(rf - 1, DL_LASER_ENABLED);
1058 rf = laser.num_edges;
1062 if (laser.dest_element != Tile[ELX][ELY])
1064 Debug("game:mm:ScanLaser",
1065 "ALARM: laser.dest_element == %d, Tile[ELX][ELY] == %d",
1066 laser.dest_element, Tile[ELX][ELY]);
1070 if (!end && !laser.stops_inside_element && !StepBehind())
1073 Debug("game:mm:ScanLaser", "Go one step back");
1079 AddLaserEdge(LX, LY);
1083 DrawLaser(rf - 1, DL_LASER_ENABLED);
1085 Ct = CT = FrameCounter;
1088 if (!IN_LEV_FIELD(ELX, ELY))
1089 Debug("game:mm:ScanLaser", "WARNING! (2) %d, %d", ELX, ELY);
1093 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1099 Debug("game:mm:DrawLaserExt", "start_edge, num_edges, mode == %d, %d, %d",
1100 start_edge, num_edges, mode);
1105 Warn("DrawLaserExt: start_edge < 0");
1112 Warn("DrawLaserExt: num_edges < 0");
1118 if (mode == DL_LASER_DISABLED)
1120 Debug("game:mm:DrawLaserExt", "Delete laser from edge %d", start_edge);
1124 // now draw the laser to the backbuffer and (if enabled) to the screen
1125 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1127 redraw_mask |= REDRAW_FIELD;
1129 if (mode == DL_LASER_ENABLED)
1132 // after the laser was deleted, the "damaged" graphics must be restored
1133 if (laser.num_damages)
1135 int damage_start = 0;
1138 // determine the starting edge, from which graphics need to be restored
1141 for (i = 0; i < laser.num_damages; i++)
1143 if (laser.damage[i].edge == start_edge + 1)
1152 // restore graphics from this starting edge to the end of damage list
1153 for (i = damage_start; i < laser.num_damages; i++)
1155 int lx = laser.damage[i].x;
1156 int ly = laser.damage[i].y;
1157 int element = Tile[lx][ly];
1159 if (Hit[lx][ly] == laser.damage[i].edge)
1160 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1163 if (Box[lx][ly] == laser.damage[i].edge)
1166 if (IS_DRAWABLE(element))
1167 DrawField_MM(lx, ly);
1170 elx = laser.damage[damage_start].x;
1171 ely = laser.damage[damage_start].y;
1172 element = Tile[elx][ely];
1175 if (IS_BEAMER(element))
1179 for (i = 0; i < laser.num_beamers; i++)
1180 Debug("game:mm:DrawLaserExt", "-> %d", laser.beamer_edge[i]);
1182 Debug("game:mm:DrawLaserExt", "IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]",
1183 mode, elx, ely, Hit[elx][ely], start_edge);
1184 Debug("game:mm:DrawLaserExt", "IS_BEAMER: %d / %d",
1185 get_element_angle(element), laser.damage[damage_start].angle);
1189 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1190 laser.num_beamers > 0 &&
1191 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1193 // element is outgoing beamer
1194 laser.num_damages = damage_start + 1;
1196 if (IS_BEAMER(element))
1197 laser.current_angle = get_element_angle(element);
1201 // element is incoming beamer or other element
1202 laser.num_damages = damage_start;
1203 laser.current_angle = laser.damage[laser.num_damages].angle;
1208 // no damages but McDuffin himself (who needs to be redrawn anyway)
1210 elx = laser.start_edge.x;
1211 ely = laser.start_edge.y;
1212 element = Tile[elx][ely];
1215 laser.num_edges = start_edge + 1;
1216 if (start_edge == 0)
1217 laser.current_angle = laser.start_angle;
1219 LX = laser.edge[start_edge].x - cSX2;
1220 LY = laser.edge[start_edge].y - cSY2;
1221 XS = 2 * Step[laser.current_angle].x;
1222 YS = 2 * Step[laser.current_angle].y;
1225 Debug("game:mm:DrawLaserExt", "Set (LX, LY) to (%d, %d) [%d]",
1231 if (IS_BEAMER(element) ||
1232 IS_FIBRE_OPTIC(element) ||
1233 IS_PACMAN(element) ||
1234 IS_POLAR(element) ||
1235 IS_POLAR_CROSS(element) ||
1236 element == EL_FUSE_ON)
1241 Debug("game:mm:DrawLaserExt", "element == %d", element);
1244 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1245 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1249 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1250 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1251 (laser.num_beamers == 0 ||
1252 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1254 // element is incoming beamer or other element
1255 step_size = -step_size;
1260 if (IS_BEAMER(element))
1261 Debug("game:mm:DrawLaserExt",
1262 "start_edge == %d, laser.beamer_edge == %d",
1263 start_edge, laser.beamer_edge);
1266 LX += step_size * XS;
1267 LY += step_size * YS;
1269 else if (element != EL_EMPTY)
1278 Debug("game:mm:DrawLaserExt", "Finally: (LX, LY) to (%d, %d) [%d]",
1283 void DrawLaser(int start_edge, int mode)
1285 if (laser.num_edges - start_edge < 0)
1287 Warn("DrawLaser: laser.num_edges - start_edge < 0");
1292 // check if laser is interrupted by beamer element
1293 if (laser.num_beamers > 0 &&
1294 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1296 if (mode == DL_LASER_ENABLED)
1299 int tmp_start_edge = start_edge;
1301 // draw laser segments forward from the start to the last beamer
1302 for (i = 0; i < laser.num_beamers; i++)
1304 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1306 if (tmp_num_edges <= 0)
1310 Debug("game:mm:DrawLaser", "DL_LASER_ENABLED: i==%d: %d, %d",
1311 i, laser.beamer_edge[i], tmp_start_edge);
1314 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1316 tmp_start_edge = laser.beamer_edge[i];
1319 // draw last segment from last beamer to the end
1320 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1326 int last_num_edges = laser.num_edges;
1327 int num_beamers = laser.num_beamers;
1329 // delete laser segments backward from the end to the first beamer
1330 for (i = num_beamers - 1; i >= 0; i--)
1332 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1334 if (laser.beamer_edge[i] - start_edge <= 0)
1337 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1339 last_num_edges = laser.beamer_edge[i];
1340 laser.num_beamers--;
1344 if (last_num_edges - start_edge <= 0)
1345 Debug("game:mm:DrawLaser", "DL_LASER_DISABLED: %d, %d",
1346 last_num_edges, start_edge);
1349 // special case when rotating first beamer: delete laser edge on beamer
1350 // (but do not start scanning on previous edge to prevent mirror sound)
1351 if (last_num_edges - start_edge == 1 && start_edge > 0)
1352 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1354 // delete first segment from start to the first beamer
1355 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1360 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1363 game_mm.laser_enabled = mode;
1366 void DrawLaser_MM(void)
1368 DrawLaser(0, game_mm.laser_enabled);
1371 boolean HitElement(int element, int hit_mask)
1373 if (HitOnlyAnEdge(hit_mask))
1376 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1377 element = MovingOrBlocked2Element_MM(ELX, ELY);
1380 Debug("game:mm:HitElement", "(1): element == %d", element);
1384 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1385 Debug("game:mm:HitElement", "(%d): EXACT MATCH @ (%d, %d)",
1388 Debug("game:mm:HitElement", "(%d): FUZZY MATCH @ (%d, %d)",
1392 AddDamagedField(ELX, ELY);
1394 // this is more precise: check if laser would go through the center
1395 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1397 // skip the whole element before continuing the scan
1403 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1405 if (LX/TILEX > ELX || LY/TILEY > ELY)
1407 /* skipping scan positions to the right and down skips one scan
1408 position too much, because this is only the top left scan position
1409 of totally four scan positions (plus one to the right, one to the
1410 bottom and one to the bottom right) */
1420 Debug("game:mm:HitElement", "(2): element == %d", element);
1423 if (LX + 5 * XS < 0 ||
1433 Debug("game:mm:HitElement", "(3): element == %d", element);
1436 if (IS_POLAR(element) &&
1437 ((element - EL_POLAR_START) % 2 ||
1438 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1440 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1442 laser.num_damages--;
1447 if (IS_POLAR_CROSS(element) &&
1448 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1450 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1452 laser.num_damages--;
1457 if (!IS_BEAMER(element) &&
1458 !IS_FIBRE_OPTIC(element) &&
1459 !IS_GRID_WOOD(element) &&
1460 element != EL_FUEL_EMPTY)
1463 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1464 Debug("game:mm:HitElement", "EXACT MATCH @ (%d, %d)", ELX, ELY);
1466 Debug("game:mm:HitElement", "FUZZY MATCH @ (%d, %d)", ELX, ELY);
1469 LX = ELX * TILEX + 14;
1470 LY = ELY * TILEY + 14;
1472 AddLaserEdge(LX, LY);
1475 if (IS_MIRROR(element) ||
1476 IS_MIRROR_FIXED(element) ||
1477 IS_POLAR(element) ||
1478 IS_POLAR_CROSS(element) ||
1479 IS_DF_MIRROR(element) ||
1480 IS_DF_MIRROR_AUTO(element) ||
1481 element == EL_PRISM ||
1482 element == EL_REFRACTOR)
1484 int current_angle = laser.current_angle;
1487 laser.num_damages--;
1489 AddDamagedField(ELX, ELY);
1491 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1494 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1496 if (IS_MIRROR(element) ||
1497 IS_MIRROR_FIXED(element) ||
1498 IS_DF_MIRROR(element) ||
1499 IS_DF_MIRROR_AUTO(element))
1500 laser.current_angle = get_mirrored_angle(laser.current_angle,
1501 get_element_angle(element));
1503 if (element == EL_PRISM || element == EL_REFRACTOR)
1504 laser.current_angle = RND(16);
1506 XS = 2 * Step[laser.current_angle].x;
1507 YS = 2 * Step[laser.current_angle].y;
1509 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1514 LX += step_size * XS;
1515 LY += step_size * YS;
1517 // draw sparkles on mirror
1518 if ((IS_MIRROR(element) ||
1519 IS_MIRROR_FIXED(element) ||
1520 element == EL_PRISM) &&
1521 current_angle != laser.current_angle)
1523 MovDelay[ELX][ELY] = 11; // start animation
1526 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1527 current_angle != laser.current_angle)
1528 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1531 (get_opposite_angle(laser.current_angle) ==
1532 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1534 return (laser.overloaded ? TRUE : FALSE);
1537 if (element == EL_FUEL_FULL)
1539 laser.stops_inside_element = TRUE;
1544 if (element == EL_BOMB || element == EL_MINE)
1546 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1548 if (element == EL_MINE)
1549 laser.overloaded = TRUE;
1552 if (element == EL_KETTLE ||
1553 element == EL_CELL ||
1554 element == EL_KEY ||
1555 element == EL_LIGHTBALL ||
1556 element == EL_PACMAN ||
1559 if (!IS_PACMAN(element))
1562 if (element == EL_PACMAN)
1565 if (element == EL_KETTLE || element == EL_CELL)
1567 if (game_mm.kettles_still_needed > 0)
1568 game_mm.kettles_still_needed--;
1570 game.snapshot.collected_item = TRUE;
1572 if (game_mm.kettles_still_needed == 0)
1576 DrawLaser(0, DL_LASER_ENABLED);
1579 else if (element == EL_KEY)
1583 else if (IS_PACMAN(element))
1585 DeletePacMan(ELX, ELY);
1588 RaiseScoreElement_MM(element);
1593 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1595 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1597 DrawLaser(0, DL_LASER_ENABLED);
1599 if (Tile[ELX][ELY] == EL_LIGHTBULB_OFF)
1601 Tile[ELX][ELY] = EL_LIGHTBULB_ON;
1602 game_mm.lights_still_needed--;
1606 Tile[ELX][ELY] = EL_LIGHTBULB_OFF;
1607 game_mm.lights_still_needed++;
1610 DrawField_MM(ELX, ELY);
1611 DrawLaser(0, DL_LASER_ENABLED);
1616 laser.stops_inside_element = TRUE;
1622 Debug("game:mm:HitElement", "(4): element == %d", element);
1625 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1626 laser.num_beamers < MAX_NUM_BEAMERS &&
1627 laser.beamer[BEAMER_NR(element)][1].num)
1629 int beamer_angle = get_element_angle(element);
1630 int beamer_nr = BEAMER_NR(element);
1634 Debug("game:mm:HitElement", "(BEAMER): element == %d", element);
1637 laser.num_damages--;
1639 if (IS_FIBRE_OPTIC(element) ||
1640 laser.current_angle == get_opposite_angle(beamer_angle))
1644 LX = ELX * TILEX + 14;
1645 LY = ELY * TILEY + 14;
1647 AddLaserEdge(LX, LY);
1648 AddDamagedField(ELX, ELY);
1650 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1653 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1655 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1656 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1657 ELX = laser.beamer[beamer_nr][pos].x;
1658 ELY = laser.beamer[beamer_nr][pos].y;
1659 LX = ELX * TILEX + 14;
1660 LY = ELY * TILEY + 14;
1662 if (IS_BEAMER(element))
1664 laser.current_angle = get_element_angle(Tile[ELX][ELY]);
1665 XS = 2 * Step[laser.current_angle].x;
1666 YS = 2 * Step[laser.current_angle].y;
1669 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1671 AddLaserEdge(LX, LY);
1672 AddDamagedField(ELX, ELY);
1674 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1677 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1679 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1684 LX += step_size * XS;
1685 LY += step_size * YS;
1687 laser.num_beamers++;
1696 boolean HitOnlyAnEdge(int hit_mask)
1698 // check if the laser hit only the edge of an element and, if so, go on
1701 Debug("game:mm:HitOnlyAnEdge", "LX, LY, hit_mask == %d, %d, %d",
1705 if ((hit_mask == HIT_MASK_TOPLEFT ||
1706 hit_mask == HIT_MASK_TOPRIGHT ||
1707 hit_mask == HIT_MASK_BOTTOMLEFT ||
1708 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1709 laser.current_angle % 4) // angle is not 90°
1713 if (hit_mask == HIT_MASK_TOPLEFT)
1718 else if (hit_mask == HIT_MASK_TOPRIGHT)
1723 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1728 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1734 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1740 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == TRUE]");
1747 Debug("game:mm:HitOnlyAnEdge", "[HitOnlyAnEdge() == FALSE]");
1753 boolean HitPolarizer(int element, int hit_mask)
1755 if (HitOnlyAnEdge(hit_mask))
1758 if (IS_DF_GRID(element))
1760 int grid_angle = get_element_angle(element);
1763 Debug("game:mm:HitPolarizer", "angle: grid == %d, laser == %d",
1764 grid_angle, laser.current_angle);
1767 AddLaserEdge(LX, LY);
1768 AddDamagedField(ELX, ELY);
1771 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1773 if (laser.current_angle == grid_angle ||
1774 laser.current_angle == get_opposite_angle(grid_angle))
1776 // skip the whole element before continuing the scan
1782 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1784 if (LX/TILEX > ELX || LY/TILEY > ELY)
1786 /* skipping scan positions to the right and down skips one scan
1787 position too much, because this is only the top left scan position
1788 of totally four scan positions (plus one to the right, one to the
1789 bottom and one to the bottom right) */
1795 AddLaserEdge(LX, LY);
1801 Debug("game:mm:HitPolarizer", "LX, LY == %d, %d [%d, %d] [%d, %d]",
1803 LX / TILEX, LY / TILEY,
1804 LX % TILEX, LY % TILEY);
1809 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1811 return HitReflectingWalls(element, hit_mask);
1815 return HitAbsorbingWalls(element, hit_mask);
1818 else if (IS_GRID_STEEL(element))
1820 return HitReflectingWalls(element, hit_mask);
1822 else // IS_GRID_WOOD
1824 return HitAbsorbingWalls(element, hit_mask);
1830 boolean HitBlock(int element, int hit_mask)
1832 boolean check = FALSE;
1834 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1835 game_mm.num_keys == 0)
1838 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1841 int ex = ELX * TILEX + 14;
1842 int ey = ELY * TILEY + 14;
1846 for (i = 1; i < 32; i++)
1851 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1856 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1857 return HitAbsorbingWalls(element, hit_mask);
1861 AddLaserEdge(LX - XS, LY - YS);
1862 AddDamagedField(ELX, ELY);
1865 Box[ELX][ELY] = laser.num_edges;
1867 return HitReflectingWalls(element, hit_mask);
1870 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1872 int xs = XS / 2, ys = YS / 2;
1873 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1874 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1876 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1877 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1879 laser.overloaded = (element == EL_GATE_STONE);
1884 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1885 (hit_mask == HIT_MASK_TOP ||
1886 hit_mask == HIT_MASK_LEFT ||
1887 hit_mask == HIT_MASK_RIGHT ||
1888 hit_mask == HIT_MASK_BOTTOM))
1889 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1890 hit_mask == HIT_MASK_BOTTOM),
1891 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1892 hit_mask == HIT_MASK_RIGHT));
1893 AddLaserEdge(LX, LY);
1899 if (element == EL_GATE_STONE && Box[ELX][ELY])
1901 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1913 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1915 int xs = XS / 2, ys = YS / 2;
1916 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1917 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1919 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1920 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1922 laser.overloaded = (element == EL_BLOCK_STONE);
1927 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1928 (hit_mask == HIT_MASK_TOP ||
1929 hit_mask == HIT_MASK_LEFT ||
1930 hit_mask == HIT_MASK_RIGHT ||
1931 hit_mask == HIT_MASK_BOTTOM))
1932 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1933 hit_mask == HIT_MASK_BOTTOM),
1934 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1935 hit_mask == HIT_MASK_RIGHT));
1936 AddDamagedField(ELX, ELY);
1938 LX = ELX * TILEX + 14;
1939 LY = ELY * TILEY + 14;
1941 AddLaserEdge(LX, LY);
1943 laser.stops_inside_element = TRUE;
1951 boolean HitLaserSource(int element, int hit_mask)
1953 if (HitOnlyAnEdge(hit_mask))
1956 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1958 laser.overloaded = TRUE;
1963 boolean HitLaserDestination(int element, int hit_mask)
1965 if (HitOnlyAnEdge(hit_mask))
1968 if (element != EL_EXIT_OPEN &&
1969 !(IS_RECEIVER(element) &&
1970 game_mm.kettles_still_needed == 0 &&
1971 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1973 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1978 if (IS_RECEIVER(element) ||
1979 (IS_22_5_ANGLE(laser.current_angle) &&
1980 (ELX != (LX + 6 * XS) / TILEX ||
1981 ELY != (LY + 6 * YS) / TILEY ||
1990 LX = ELX * TILEX + 14;
1991 LY = ELY * TILEY + 14;
1993 laser.stops_inside_element = TRUE;
1996 AddLaserEdge(LX, LY);
1997 AddDamagedField(ELX, ELY);
1999 if (game_mm.lights_still_needed == 0)
2001 game_mm.level_solved = TRUE;
2003 SetTileCursorActive(FALSE);
2009 boolean HitReflectingWalls(int element, int hit_mask)
2011 // check if laser hits side of a wall with an angle that is not 90°
2012 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
2013 hit_mask == HIT_MASK_LEFT ||
2014 hit_mask == HIT_MASK_RIGHT ||
2015 hit_mask == HIT_MASK_BOTTOM))
2017 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2022 if (!IS_DF_GRID(element))
2023 AddLaserEdge(LX, LY);
2025 // check if laser hits wall with an angle of 45°
2026 if (!IS_22_5_ANGLE(laser.current_angle))
2028 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2031 laser.current_angle = get_mirrored_angle(laser.current_angle,
2034 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2037 laser.current_angle = get_mirrored_angle(laser.current_angle,
2041 AddLaserEdge(LX, LY);
2043 XS = 2 * Step[laser.current_angle].x;
2044 YS = 2 * Step[laser.current_angle].y;
2048 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2050 laser.current_angle = get_mirrored_angle(laser.current_angle,
2055 if (!IS_DF_GRID(element))
2056 AddLaserEdge(LX, LY);
2061 if (!IS_DF_GRID(element))
2062 AddLaserEdge(LX, LY + YS / 2);
2065 if (!IS_DF_GRID(element))
2066 AddLaserEdge(LX, LY);
2069 YS = 2 * Step[laser.current_angle].y;
2073 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2075 laser.current_angle = get_mirrored_angle(laser.current_angle,
2080 if (!IS_DF_GRID(element))
2081 AddLaserEdge(LX, LY);
2086 if (!IS_DF_GRID(element))
2087 AddLaserEdge(LX + XS / 2, LY);
2090 if (!IS_DF_GRID(element))
2091 AddLaserEdge(LX, LY);
2094 XS = 2 * Step[laser.current_angle].x;
2100 // reflection at the edge of reflecting DF style wall
2101 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2103 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2104 hit_mask == HIT_MASK_TOPRIGHT) ||
2105 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2106 hit_mask == HIT_MASK_TOPLEFT) ||
2107 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2108 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2109 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2110 hit_mask == HIT_MASK_BOTTOMRIGHT))
2113 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2114 ANG_MIRROR_135 : ANG_MIRROR_45);
2116 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2118 AddDamagedField(ELX, ELY);
2119 AddLaserEdge(LX, LY);
2121 laser.current_angle = get_mirrored_angle(laser.current_angle,
2129 AddLaserEdge(LX, LY);
2135 // reflection inside an edge of reflecting DF style wall
2136 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2138 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2139 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2140 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2141 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2142 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2143 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2144 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2145 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2148 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2149 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2150 ANG_MIRROR_135 : ANG_MIRROR_45);
2152 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2155 AddDamagedField(ELX, ELY);
2158 AddLaserEdge(LX - XS, LY - YS);
2159 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2160 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2162 laser.current_angle = get_mirrored_angle(laser.current_angle,
2170 AddLaserEdge(LX, LY);
2176 // check if laser hits DF style wall with an angle of 90°
2177 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2179 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2180 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2181 (IS_VERT_ANGLE(laser.current_angle) &&
2182 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2184 // laser at last step touched nothing or the same side of the wall
2185 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2187 AddDamagedField(ELX, ELY);
2194 last_hit_mask = hit_mask;
2201 if (!HitOnlyAnEdge(hit_mask))
2203 laser.overloaded = TRUE;
2211 boolean HitAbsorbingWalls(int element, int hit_mask)
2213 if (HitOnlyAnEdge(hit_mask))
2217 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2219 AddLaserEdge(LX - XS, LY - YS);
2226 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2228 AddLaserEdge(LX - XS, LY - YS);
2234 if (IS_WALL_WOOD(element) ||
2235 IS_DF_WALL_WOOD(element) ||
2236 IS_GRID_WOOD(element) ||
2237 IS_GRID_WOOD_FIXED(element) ||
2238 IS_GRID_WOOD_AUTO(element) ||
2239 element == EL_FUSE_ON ||
2240 element == EL_BLOCK_WOOD ||
2241 element == EL_GATE_WOOD)
2243 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2248 if (IS_WALL_ICE(element))
2252 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2253 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2255 // check if laser hits wall with an angle of 90°
2256 if (IS_90_ANGLE(laser.current_angle))
2257 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2259 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2263 for (i = 0; i < 4; i++)
2265 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2266 mask = 15 - (8 >> i);
2267 else if (ABS(XS) == 4 &&
2269 (XS > 0) == (i % 2) &&
2270 (YS < 0) == (i / 2))
2271 mask = 3 + (i / 2) * 9;
2272 else if (ABS(YS) == 4 &&
2274 (XS < 0) == (i % 2) &&
2275 (YS > 0) == (i / 2))
2276 mask = 5 + (i % 2) * 5;
2280 laser.wall_mask = mask;
2282 else if (IS_WALL_AMOEBA(element))
2284 int elx = (LX - 2 * XS) / TILEX;
2285 int ely = (LY - 2 * YS) / TILEY;
2286 int element2 = Tile[elx][ely];
2289 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2291 laser.dest_element = EL_EMPTY;
2299 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2300 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2302 if (IS_90_ANGLE(laser.current_angle))
2303 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2305 laser.dest_element = element2 | EL_WALL_AMOEBA;
2307 laser.wall_mask = mask;
2313 static void OpenExit(int x, int y)
2317 if (!MovDelay[x][y]) // next animation frame
2318 MovDelay[x][y] = 4 * delay;
2320 if (MovDelay[x][y]) // wait some time before next frame
2325 phase = MovDelay[x][y] / delay;
2327 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2328 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2330 if (!MovDelay[x][y])
2332 Tile[x][y] = EL_EXIT_OPEN;
2338 static void OpenSurpriseBall(int x, int y)
2342 if (!MovDelay[x][y]) // next animation frame
2343 MovDelay[x][y] = 50 * delay;
2345 if (MovDelay[x][y]) // wait some time before next frame
2349 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2352 int graphic = el2gfx(Store[x][y]);
2354 int dx = RND(26), dy = RND(26);
2356 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2358 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2359 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2361 MarkTileDirty(x, y);
2364 if (!MovDelay[x][y])
2366 Tile[x][y] = Store[x][y];
2375 static void MeltIce(int x, int y)
2380 if (!MovDelay[x][y]) // next animation frame
2381 MovDelay[x][y] = frames * delay;
2383 if (MovDelay[x][y]) // wait some time before next frame
2386 int wall_mask = Store2[x][y];
2387 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2390 phase = frames - MovDelay[x][y] / delay - 1;
2392 if (!MovDelay[x][y])
2396 Tile[x][y] = real_element & (wall_mask ^ 0xFF);
2397 Store[x][y] = Store2[x][y] = 0;
2399 DrawWalls_MM(x, y, Tile[x][y]);
2401 if (Tile[x][y] == EL_WALL_ICE)
2402 Tile[x][y] = EL_EMPTY;
2404 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2405 if (laser.damage[i].is_mirror)
2409 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2411 DrawLaser(0, DL_LASER_DISABLED);
2415 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2417 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2419 laser.redraw = TRUE;
2424 static void GrowAmoeba(int x, int y)
2429 if (!MovDelay[x][y]) // next animation frame
2430 MovDelay[x][y] = frames * delay;
2432 if (MovDelay[x][y]) // wait some time before next frame
2435 int wall_mask = Store2[x][y];
2436 int real_element = Tile[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2439 phase = MovDelay[x][y] / delay;
2441 if (!MovDelay[x][y])
2443 Tile[x][y] = real_element;
2444 Store[x][y] = Store2[x][y] = 0;
2446 DrawWalls_MM(x, y, Tile[x][y]);
2447 DrawLaser(0, DL_LASER_ENABLED);
2449 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2451 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2456 static void DrawFieldAnimated_MM(int x, int y)
2458 int element = Tile[x][y];
2460 if (IS_BLOCKED(x, y))
2465 if (IS_MIRROR(element) ||
2466 IS_MIRROR_FIXED(element) ||
2467 element == EL_PRISM)
2469 if (MovDelay[x][y] != 0) // wait some time before next frame
2473 if (MovDelay[x][y] != 0)
2475 int graphic = IMG_TWINKLE_WHITE;
2476 int frame = getGraphicAnimationFrame(graphic, 10 - MovDelay[x][y]);
2478 DrawGraphicThruMask_MM(SCREENX(x), SCREENY(y), graphic, frame);
2483 laser.redraw = TRUE;
2486 static void Explode_MM(int x, int y, int phase, int mode)
2488 int num_phase = 9, delay = 2;
2489 int last_phase = num_phase * delay;
2490 int half_phase = (num_phase / 2) * delay;
2492 laser.redraw = TRUE;
2494 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2496 int center_element = Tile[x][y];
2498 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2500 // put moving element to center field (and let it explode there)
2501 center_element = MovingOrBlocked2Element_MM(x, y);
2502 RemoveMovingField_MM(x, y);
2504 Tile[x][y] = center_element;
2507 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2508 Store[x][y] = center_element;
2510 Store[x][y] = EL_EMPTY;
2512 Store2[x][y] = mode;
2513 Tile[x][y] = EL_EXPLODING_OPAQUE;
2514 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2520 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2522 if (phase == half_phase)
2524 Tile[x][y] = EL_EXPLODING_TRANSP;
2526 if (x == ELX && y == ELY)
2530 if (phase == last_phase)
2532 if (Store[x][y] == EL_BOMB)
2534 DrawLaser(0, DL_LASER_DISABLED);
2537 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2538 Store[x][y] = EL_EMPTY;
2540 GameOver_MM(GAME_OVER_DELAYED);
2542 laser.overloaded = FALSE;
2544 else if (IS_MCDUFFIN(Store[x][y]))
2546 Store[x][y] = EL_EMPTY;
2548 GameOver_MM(GAME_OVER_BOMB);
2551 Tile[x][y] = Store[x][y];
2552 Store[x][y] = Store2[x][y] = 0;
2553 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2558 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2560 int graphic = IMG_MM_DEFAULT_EXPLODING;
2561 int graphic_phase = (phase / delay - 1);
2565 if (Store2[x][y] == EX_KETTLE)
2567 if (graphic_phase < 3)
2569 graphic = IMG_MM_KETTLE_EXPLODING;
2571 else if (graphic_phase < 5)
2577 graphic = IMG_EMPTY;
2581 else if (Store2[x][y] == EX_SHORT)
2583 if (graphic_phase < 4)
2589 graphic = IMG_EMPTY;
2594 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2596 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2597 cFX + x * TILEX, cFY + y * TILEY);
2599 MarkTileDirty(x, y);
2603 static void Bang_MM(int x, int y)
2605 int element = Tile[x][y];
2606 int mode = EX_NORMAL;
2609 DrawLaser(0, DL_LASER_ENABLED);
2628 if (IS_PACMAN(element))
2629 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2630 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2631 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2632 else if (element == EL_KEY)
2633 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2635 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2637 Explode_MM(x, y, EX_PHASE_START, mode);
2640 void TurnRound(int x, int y)
2652 { 0, 0 }, { 0, 0 }, { 0, 0 },
2657 int left, right, back;
2661 { MV_DOWN, MV_UP, MV_RIGHT },
2662 { MV_UP, MV_DOWN, MV_LEFT },
2664 { MV_LEFT, MV_RIGHT, MV_DOWN },
2665 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2666 { MV_RIGHT, MV_LEFT, MV_UP }
2669 int element = Tile[x][y];
2670 int old_move_dir = MovDir[x][y];
2671 int right_dir = turn[old_move_dir].right;
2672 int back_dir = turn[old_move_dir].back;
2673 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2674 int right_x = x + right_dx, right_y = y + right_dy;
2676 if (element == EL_PACMAN)
2678 boolean can_turn_right = FALSE;
2680 if (IN_LEV_FIELD(right_x, right_y) &&
2681 IS_EATABLE4PACMAN(Tile[right_x][right_y]))
2682 can_turn_right = TRUE;
2685 MovDir[x][y] = right_dir;
2687 MovDir[x][y] = back_dir;
2693 static void StartMoving_MM(int x, int y)
2695 int element = Tile[x][y];
2700 if (CAN_MOVE(element))
2704 if (MovDelay[x][y]) // wait some time before next movement
2712 // now make next step
2714 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2716 if (element == EL_PACMAN &&
2717 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Tile[newx][newy]) &&
2718 !ObjHit(newx, newy, HIT_POS_CENTER))
2720 Store[newx][newy] = Tile[newx][newy];
2721 Tile[newx][newy] = EL_EMPTY;
2723 DrawField_MM(newx, newy);
2725 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2726 ObjHit(newx, newy, HIT_POS_CENTER))
2728 // object was running against a wall
2735 InitMovingField_MM(x, y, MovDir[x][y]);
2739 ContinueMoving_MM(x, y);
2742 static void ContinueMoving_MM(int x, int y)
2744 int element = Tile[x][y];
2745 int direction = MovDir[x][y];
2746 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2747 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2748 int horiz_move = (dx!=0);
2749 int newx = x + dx, newy = y + dy;
2750 int step = (horiz_move ? dx : dy) * TILEX / 8;
2752 MovPos[x][y] += step;
2754 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2756 Tile[x][y] = EL_EMPTY;
2757 Tile[newx][newy] = element;
2759 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2760 MovDelay[newx][newy] = 0;
2762 if (!CAN_MOVE(element))
2763 MovDir[newx][newy] = 0;
2766 DrawField_MM(newx, newy);
2768 Stop[newx][newy] = TRUE;
2770 if (element == EL_PACMAN)
2772 if (Store[newx][newy] == EL_BOMB)
2773 Bang_MM(newx, newy);
2775 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2776 (LX + 2 * XS) / TILEX == newx &&
2777 (LY + 2 * YS) / TILEY == newy)
2784 else // still moving on
2789 laser.redraw = TRUE;
2792 boolean ClickElement(int x, int y, int button)
2794 static DelayCounter click_delay = { CLICK_DELAY };
2795 static boolean new_button = TRUE;
2796 boolean element_clicked = FALSE;
2801 // initialize static variables
2802 click_delay.count = 0;
2803 click_delay.value = CLICK_DELAY;
2809 // do not rotate objects hit by the laser after the game was solved
2810 if (game_mm.level_solved && Hit[x][y])
2813 if (button == MB_RELEASED)
2816 click_delay.value = CLICK_DELAY;
2818 // release eventually hold auto-rotating mirror
2819 RotateMirror(x, y, MB_RELEASED);
2824 if (!FrameReached(&click_delay) && !new_button)
2827 if (button == MB_MIDDLEBUTTON) // middle button has no function
2830 if (!IN_LEV_FIELD(x, y))
2833 if (Tile[x][y] == EL_EMPTY)
2836 element = Tile[x][y];
2838 if (IS_MIRROR(element) ||
2839 IS_BEAMER(element) ||
2840 IS_POLAR(element) ||
2841 IS_POLAR_CROSS(element) ||
2842 IS_DF_MIRROR(element) ||
2843 IS_DF_MIRROR_AUTO(element))
2845 RotateMirror(x, y, button);
2847 element_clicked = TRUE;
2849 else if (IS_MCDUFFIN(element))
2851 if (!laser.fuse_off)
2853 DrawLaser(0, DL_LASER_DISABLED);
2860 element = get_rotated_element(element, BUTTON_ROTATION(button));
2861 laser.start_angle = get_element_angle(element);
2865 Tile[x][y] = element;
2872 if (!laser.fuse_off)
2875 element_clicked = TRUE;
2877 else if (element == EL_FUSE_ON && laser.fuse_off)
2879 if (x != laser.fuse_x || y != laser.fuse_y)
2882 laser.fuse_off = FALSE;
2883 laser.fuse_x = laser.fuse_y = -1;
2885 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2888 element_clicked = TRUE;
2890 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2892 laser.fuse_off = TRUE;
2895 laser.overloaded = FALSE;
2897 DrawLaser(0, DL_LASER_DISABLED);
2898 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2900 element_clicked = TRUE;
2902 else if (element == EL_LIGHTBALL)
2905 RaiseScoreElement_MM(element);
2906 DrawLaser(0, DL_LASER_ENABLED);
2908 element_clicked = TRUE;
2911 click_delay.value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2914 return element_clicked;
2917 void RotateMirror(int x, int y, int button)
2919 if (button == MB_RELEASED)
2921 // release eventually hold auto-rotating mirror
2928 if (IS_MIRROR(Tile[x][y]) ||
2929 IS_POLAR_CROSS(Tile[x][y]) ||
2930 IS_POLAR(Tile[x][y]) ||
2931 IS_BEAMER(Tile[x][y]) ||
2932 IS_DF_MIRROR(Tile[x][y]) ||
2933 IS_GRID_STEEL_AUTO(Tile[x][y]) ||
2934 IS_GRID_WOOD_AUTO(Tile[x][y]))
2936 Tile[x][y] = get_rotated_element(Tile[x][y], BUTTON_ROTATION(button));
2938 else if (IS_DF_MIRROR_AUTO(Tile[x][y]))
2940 if (button == MB_LEFTBUTTON)
2942 // left mouse button only for manual adjustment, no auto-rotating;
2943 // freeze mirror for until mouse button released
2947 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2949 Tile[x][y] = get_rotated_element(Tile[x][y], ROTATE_RIGHT);
2953 if (IS_GRID_STEEL_AUTO(Tile[x][y]) || IS_GRID_WOOD_AUTO(Tile[x][y]))
2955 int edge = Hit[x][y];
2961 DrawLaser(edge - 1, DL_LASER_DISABLED);
2965 else if (ObjHit(x, y, HIT_POS_CENTER))
2967 int edge = Hit[x][y];
2971 Warn("RotateMirror: inconsistent field Hit[][]!\n");
2976 DrawLaser(edge - 1, DL_LASER_DISABLED);
2983 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2988 if ((IS_BEAMER(Tile[x][y]) ||
2989 IS_POLAR(Tile[x][y]) ||
2990 IS_POLAR_CROSS(Tile[x][y])) && x == ELX && y == ELY)
2994 if (IS_BEAMER(Tile[x][y]))
2997 Debug("game:mm:RotateMirror", "TEST (%d, %d) [%d] [%d]",
2998 LX, LY, laser.beamer_edge, laser.beamer[1].num);
3010 DrawLaser(0, DL_LASER_ENABLED);
3014 static void AutoRotateMirrors(void)
3018 if (!FrameReached(&rotate_delay))
3021 for (x = 0; x < lev_fieldx; x++)
3023 for (y = 0; y < lev_fieldy; y++)
3025 int element = Tile[x][y];
3027 // do not rotate objects hit by the laser after the game was solved
3028 if (game_mm.level_solved && Hit[x][y])
3031 if (IS_DF_MIRROR_AUTO(element) ||
3032 IS_GRID_WOOD_AUTO(element) ||
3033 IS_GRID_STEEL_AUTO(element) ||
3034 element == EL_REFRACTOR)
3035 RotateMirror(x, y, MB_RIGHTBUTTON);
3040 boolean ObjHit(int obx, int oby, int bits)
3047 if (bits & HIT_POS_CENTER)
3049 if (CheckLaserPixel(cSX + obx + 15,
3054 if (bits & HIT_POS_EDGE)
3056 for (i = 0; i < 4; i++)
3057 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3058 cSY + oby + 31 * (i / 2)))
3062 if (bits & HIT_POS_BETWEEN)
3064 for (i = 0; i < 4; i++)
3065 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3066 cSY + 4 + oby + 22 * (i / 2)))
3073 void DeletePacMan(int px, int py)
3079 if (game_mm.num_pacman <= 1)
3081 game_mm.num_pacman = 0;
3085 for (i = 0; i < game_mm.num_pacman; i++)
3086 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3089 game_mm.num_pacman--;
3091 for (j = i; j < game_mm.num_pacman; j++)
3093 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3094 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3095 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3099 void ColorCycling(void)
3101 static int CC, Cc = 0;
3103 static int color, old = 0xF00, new = 0x010, mult = 1;
3104 static unsigned short red, green, blue;
3106 if (color_status == STATIC_COLORS)
3111 if (CC < Cc || CC > Cc + 2)
3115 color = old + new * mult;
3121 if (ABS(mult) == 16)
3131 red = 0x0e00 * ((color & 0xF00) >> 8);
3132 green = 0x0e00 * ((color & 0x0F0) >> 4);
3133 blue = 0x0e00 * ((color & 0x00F));
3134 SetRGB(pen_magicolor[0], red, green, blue);
3136 red = 0x1111 * ((color & 0xF00) >> 8);
3137 green = 0x1111 * ((color & 0x0F0) >> 4);
3138 blue = 0x1111 * ((color & 0x00F));
3139 SetRGB(pen_magicolor[1], red, green, blue);
3143 static void GameActions_MM_Ext(void)
3150 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3153 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3155 element = Tile[x][y];
3157 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3158 StartMoving_MM(x, y);
3159 else if (IS_MOVING(x, y))
3160 ContinueMoving_MM(x, y);
3161 else if (IS_EXPLODING(element))
3162 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3163 else if (element == EL_EXIT_OPENING)
3165 else if (element == EL_GRAY_BALL_OPENING)
3166 OpenSurpriseBall(x, y);
3167 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3169 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3172 DrawFieldAnimated_MM(x, y);
3175 AutoRotateMirrors();
3178 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3180 // redraw after Explode_MM() ...
3182 DrawLaser(0, DL_LASER_ENABLED);
3183 laser.redraw = FALSE;
3188 if (game_mm.num_pacman && FrameReached(&pacman_delay))
3192 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3194 DrawLaser(0, DL_LASER_DISABLED);
3199 // skip all following game actions if game is over
3200 if (game_mm.game_over)
3203 if (FrameReached(&energy_delay))
3205 if (game_mm.energy_left > 0)
3207 game_mm.energy_left--;
3209 redraw_mask |= REDRAW_DOOR_1;
3211 else if (game.time_limit && !game_mm.game_over)
3215 GameOver_MM(GAME_OVER_NO_ENERGY);
3221 element = laser.dest_element;
3224 if (element != Tile[ELX][ELY])
3226 Debug("game:mm:GameActions_MM_Ext", "element == %d, Tile[ELX][ELY] == %d",
3227 element, Tile[ELX][ELY]);
3231 if (!laser.overloaded && laser.overload_value == 0 &&
3232 element != EL_BOMB &&
3233 element != EL_MINE &&
3234 element != EL_BALL_GRAY &&
3235 element != EL_BLOCK_STONE &&
3236 element != EL_BLOCK_WOOD &&
3237 element != EL_FUSE_ON &&
3238 element != EL_FUEL_FULL &&
3239 !IS_WALL_ICE(element) &&
3240 !IS_WALL_AMOEBA(element))
3243 overload_delay.value = HEALTH_DELAY(laser.overloaded);
3245 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3246 (!laser.overloaded && laser.overload_value > 0)) &&
3247 FrameReached(&overload_delay))
3249 if (laser.overloaded)
3250 laser.overload_value++;
3252 laser.overload_value--;
3254 if (game_mm.cheat_no_overload)
3256 laser.overloaded = FALSE;
3257 laser.overload_value = 0;
3260 game_mm.laser_overload_value = laser.overload_value;
3262 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3264 SetLaserColor(0xFF);
3266 DrawLaser(0, DL_LASER_ENABLED);
3269 if (!laser.overloaded)
3270 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3271 else if (setup.sound_loops)
3272 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3274 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3276 if (laser.overloaded)
3279 BlitBitmap(pix[PIX_DOOR], drawto,
3280 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3281 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3282 - laser.overload_value,
3283 OVERLOAD_XSIZE, laser.overload_value,
3284 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3285 - laser.overload_value);
3287 redraw_mask |= REDRAW_DOOR_1;
3292 BlitBitmap(pix[PIX_DOOR], drawto,
3293 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3294 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3295 DX_OVERLOAD, DY_OVERLOAD);
3297 redraw_mask |= REDRAW_DOOR_1;
3300 if (laser.overload_value == MAX_LASER_OVERLOAD)
3302 UpdateAndDisplayGameControlValues();
3306 GameOver_MM(GAME_OVER_OVERLOADED);
3317 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3319 if (game_mm.cheat_no_explosion)
3324 laser.dest_element = EL_EXPLODING_OPAQUE;
3329 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3331 laser.fuse_off = TRUE;
3335 DrawLaser(0, DL_LASER_DISABLED);
3336 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3339 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3341 static int new_elements[] =
3344 EL_MIRROR_FIXED_START,
3346 EL_POLAR_CROSS_START,
3352 int num_new_elements = sizeof(new_elements) / sizeof(int);
3353 int new_element = new_elements[RND(num_new_elements)];
3355 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3356 Tile[ELX][ELY] = EL_GRAY_BALL_OPENING;
3358 // !!! CHECK AGAIN: Laser on Polarizer !!!
3369 element = EL_MIRROR_START + RND(16);
3375 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3382 element = (rnd == 0 ? EL_FUSE_ON :
3383 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3384 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3385 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3386 EL_MIRROR_FIXED_START + rnd - 25);
3391 graphic = el2gfx(element);
3393 for (i = 0; i < 50; i++)
3399 BlitBitmap(pix[PIX_BACK], drawto,
3400 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3401 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3402 SX + ELX * TILEX + x,
3403 SY + ELY * TILEY + y);
3405 MarkTileDirty(ELX, ELY);
3408 DrawLaser(0, DL_LASER_ENABLED);
3410 Delay_WithScreenUpdates(50);
3413 Tile[ELX][ELY] = element;
3414 DrawField_MM(ELX, ELY);
3417 Debug("game:mm:GameActions_MM_Ext", "NEW ELEMENT: (%d, %d)", ELX, ELY);
3420 // above stuff: GRAY BALL -> PRISM !!!
3422 LX = ELX * TILEX + 14;
3423 LY = ELY * TILEY + 14;
3424 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3431 laser.num_edges -= 2;
3432 laser.num_damages--;
3436 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3437 if (laser.damage[i].is_mirror)
3441 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3443 DrawLaser(0, DL_LASER_DISABLED);
3445 DrawLaser(0, DL_LASER_DISABLED);
3454 if (IS_WALL_ICE(element) && CT > 50)
3456 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3459 Tile[ELX][ELY] = Tile[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3460 Store[ELX][ELY] = EL_WALL_ICE;
3461 Store2[ELX][ELY] = laser.wall_mask;
3463 laser.dest_element = Tile[ELX][ELY];
3468 for (i = 0; i < 5; i++)
3474 Tile[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3478 DrawWallsAnimation_MM(ELX, ELY, Tile[ELX][ELY], phase, laser.wall_mask);
3480 Delay_WithScreenUpdates(100);
3483 if (Tile[ELX][ELY] == EL_WALL_ICE)
3484 Tile[ELX][ELY] = EL_EMPTY;
3488 LX = laser.edge[laser.num_edges].x - cSX2;
3489 LY = laser.edge[laser.num_edges].y - cSY2;
3492 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3493 if (laser.damage[i].is_mirror)
3497 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3499 DrawLaser(0, DL_LASER_DISABLED);
3506 if (IS_WALL_AMOEBA(element) && CT > 60)
3508 int k1, k2, k3, dx, dy, de, dm;
3509 int element2 = Tile[ELX][ELY];
3511 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3514 for (i = laser.num_damages - 1; i >= 0; i--)
3515 if (laser.damage[i].is_mirror)
3518 r = laser.num_edges;
3519 d = laser.num_damages;
3526 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3529 DrawLaser(0, DL_LASER_ENABLED);
3532 x = laser.damage[k1].x;
3533 y = laser.damage[k1].y;
3538 for (i = 0; i < 4; i++)
3540 if (laser.wall_mask & (1 << i))
3542 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3543 cSY + ELY * TILEY + 31 * (i / 2)))
3546 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3547 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3554 for (i = 0; i < 4; i++)
3556 if (laser.wall_mask & (1 << i))
3558 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3559 cSY + ELY * TILEY + 31 * (i / 2)))
3566 if (laser.num_beamers > 0 ||
3567 k1 < 1 || k2 < 4 || k3 < 4 ||
3568 CheckLaserPixel(cSX + ELX * TILEX + 14,
3569 cSY + ELY * TILEY + 14))
3571 laser.num_edges = r;
3572 laser.num_damages = d;
3574 DrawLaser(0, DL_LASER_DISABLED);
3577 Tile[ELX][ELY] = element | laser.wall_mask;
3581 de = Tile[ELX][ELY];
3582 dm = laser.wall_mask;
3586 int x = ELX, y = ELY;
3587 int wall_mask = laser.wall_mask;
3590 DrawLaser(0, DL_LASER_ENABLED);
3592 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3594 Tile[x][y] = Tile[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3595 Store[x][y] = EL_WALL_AMOEBA;
3596 Store2[x][y] = wall_mask;
3602 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3604 DrawLaser(0, DL_LASER_ENABLED);
3606 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3608 for (i = 4; i >= 0; i--)
3610 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3613 Delay_WithScreenUpdates(20);
3616 DrawLaser(0, DL_LASER_ENABLED);
3621 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3622 laser.stops_inside_element && CT > native_mm_level.time_block)
3627 if (ABS(XS) > ABS(YS))
3634 for (i = 0; i < 4; i++)
3641 x = ELX + Step[k * 4].x;
3642 y = ELY + Step[k * 4].y;
3644 if (!IN_LEV_FIELD(x, y) || Tile[x][y] != EL_EMPTY)
3647 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3655 laser.overloaded = (element == EL_BLOCK_STONE);
3660 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3663 Tile[x][y] = element;
3665 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3668 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3670 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3671 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3679 if (element == EL_FUEL_FULL && CT > 10)
3681 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3684 BlitBitmap(pix[PIX_DOOR], drawto,
3685 DOOR_GFX_PAGEX4 + XX_ENERGY,
3686 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3687 ENERGY_XSIZE, i, DX_ENERGY,
3688 DY_ENERGY + ENERGY_YSIZE - i);
3691 redraw_mask |= REDRAW_DOOR_1;
3694 Delay_WithScreenUpdates(20);
3697 game_mm.energy_left = MAX_LASER_ENERGY;
3698 Tile[ELX][ELY] = EL_FUEL_EMPTY;
3699 DrawField_MM(ELX, ELY);
3701 DrawLaser(0, DL_LASER_ENABLED);
3709 void GameActions_MM(struct MouseActionInfo action)
3711 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3712 boolean button_released = (action.button == MB_RELEASED);
3714 GameActions_MM_Ext();
3716 CheckSingleStepMode_MM(element_clicked, button_released);
3719 void MovePacMen(void)
3721 int mx, my, ox, oy, nx, ny;
3725 if (++pacman_nr >= game_mm.num_pacman)
3728 game_mm.pacman[pacman_nr].dir--;
3730 for (l = 1; l < 5; l++)
3732 game_mm.pacman[pacman_nr].dir++;
3734 if (game_mm.pacman[pacman_nr].dir > 4)
3735 game_mm.pacman[pacman_nr].dir = 1;
3737 if (game_mm.pacman[pacman_nr].dir % 2)
3740 my = game_mm.pacman[pacman_nr].dir - 2;
3745 mx = 3 - game_mm.pacman[pacman_nr].dir;
3748 ox = game_mm.pacman[pacman_nr].x;
3749 oy = game_mm.pacman[pacman_nr].y;
3752 element = Tile[nx][ny];
3754 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3757 if (!IS_EATABLE4PACMAN(element))
3760 if (ObjHit(nx, ny, HIT_POS_CENTER))
3763 Tile[ox][oy] = EL_EMPTY;
3765 EL_PACMAN_RIGHT - 1 +
3766 (game_mm.pacman[pacman_nr].dir - 1 +
3767 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3769 game_mm.pacman[pacman_nr].x = nx;
3770 game_mm.pacman[pacman_nr].y = ny;
3772 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3774 if (element != EL_EMPTY)
3776 int graphic = el2gfx(Tile[nx][ny]);
3781 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3784 ox = cSX + ox * TILEX;
3785 oy = cSY + oy * TILEY;
3787 for (i = 1; i < 33; i += 2)
3788 BlitBitmap(bitmap, window,
3789 src_x, src_y, TILEX, TILEY,
3790 ox + i * mx, oy + i * my);
3791 Ct = Ct + FrameCounter - CT;
3794 DrawField_MM(nx, ny);
3797 if (!laser.fuse_off)
3799 DrawLaser(0, DL_LASER_ENABLED);
3801 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3803 AddDamagedField(nx, ny);
3805 laser.damage[laser.num_damages - 1].edge = 0;
3809 if (element == EL_BOMB)
3810 DeletePacMan(nx, ny);
3812 if (IS_WALL_AMOEBA(element) &&
3813 (LX + 2 * XS) / TILEX == nx &&
3814 (LY + 2 * YS) / TILEY == ny)
3824 static void InitMovingField_MM(int x, int y, int direction)
3826 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3827 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3829 MovDir[x][y] = direction;
3830 MovDir[newx][newy] = direction;
3832 if (Tile[newx][newy] == EL_EMPTY)
3833 Tile[newx][newy] = EL_BLOCKED;
3836 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3838 int direction = MovDir[x][y];
3839 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3840 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3846 static void Blocked2Moving_MM(int x, int y,
3847 int *comes_from_x, int *comes_from_y)
3849 int oldx = x, oldy = y;
3850 int direction = MovDir[x][y];
3852 if (direction == MV_LEFT)
3854 else if (direction == MV_RIGHT)
3856 else if (direction == MV_UP)
3858 else if (direction == MV_DOWN)
3861 *comes_from_x = oldx;
3862 *comes_from_y = oldy;
3865 static int MovingOrBlocked2Element_MM(int x, int y)
3867 int element = Tile[x][y];
3869 if (element == EL_BLOCKED)
3873 Blocked2Moving_MM(x, y, &oldx, &oldy);
3875 return Tile[oldx][oldy];
3882 static void RemoveField(int x, int y)
3884 Tile[x][y] = EL_EMPTY;
3891 static void RemoveMovingField_MM(int x, int y)
3893 int oldx = x, oldy = y, newx = x, newy = y;
3895 if (Tile[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3898 if (IS_MOVING(x, y))
3900 Moving2Blocked_MM(x, y, &newx, &newy);
3901 if (Tile[newx][newy] != EL_BLOCKED)
3904 else if (Tile[x][y] == EL_BLOCKED)
3906 Blocked2Moving_MM(x, y, &oldx, &oldy);
3907 if (!IS_MOVING(oldx, oldy))
3911 Tile[oldx][oldy] = EL_EMPTY;
3912 Tile[newx][newy] = EL_EMPTY;
3913 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3914 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3916 DrawLevelField_MM(oldx, oldy);
3917 DrawLevelField_MM(newx, newy);
3920 void PlaySoundLevel(int x, int y, int sound_nr)
3922 int sx = SCREENX(x), sy = SCREENY(y);
3924 int silence_distance = 8;
3926 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3927 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3930 if (!IN_LEV_FIELD(x, y) ||
3931 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3932 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3935 volume = SOUND_MAX_VOLUME;
3938 stereo = (sx - SCR_FIELDX/2) * 12;
3940 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3941 if (stereo > SOUND_MAX_RIGHT)
3942 stereo = SOUND_MAX_RIGHT;
3943 if (stereo < SOUND_MAX_LEFT)
3944 stereo = SOUND_MAX_LEFT;
3947 if (!IN_SCR_FIELD(sx, sy))
3949 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3950 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3952 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3955 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3958 static void RaiseScore_MM(int value)
3960 game_mm.score += value;
3963 void RaiseScoreElement_MM(int element)
3968 case EL_PACMAN_RIGHT:
3970 case EL_PACMAN_LEFT:
3971 case EL_PACMAN_DOWN:
3972 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3976 RaiseScore_MM(native_mm_level.score[SC_KEY]);
3981 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
3985 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
3994 // ----------------------------------------------------------------------------
3995 // Mirror Magic game engine snapshot handling functions
3996 // ----------------------------------------------------------------------------
3998 void SaveEngineSnapshotValues_MM(void)
4002 engine_snapshot_mm.game_mm = game_mm;
4003 engine_snapshot_mm.laser = laser;
4005 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4007 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4009 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4010 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4011 engine_snapshot_mm.Box[x][y] = Box[x][y];
4012 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4013 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4017 engine_snapshot_mm.LX = LX;
4018 engine_snapshot_mm.LY = LY;
4019 engine_snapshot_mm.XS = XS;
4020 engine_snapshot_mm.YS = YS;
4021 engine_snapshot_mm.ELX = ELX;
4022 engine_snapshot_mm.ELY = ELY;
4023 engine_snapshot_mm.CT = CT;
4024 engine_snapshot_mm.Ct = Ct;
4026 engine_snapshot_mm.last_LX = last_LX;
4027 engine_snapshot_mm.last_LY = last_LY;
4028 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4029 engine_snapshot_mm.hold_x = hold_x;
4030 engine_snapshot_mm.hold_y = hold_y;
4031 engine_snapshot_mm.pacman_nr = pacman_nr;
4033 engine_snapshot_mm.rotate_delay = rotate_delay;
4034 engine_snapshot_mm.pacman_delay = pacman_delay;
4035 engine_snapshot_mm.energy_delay = energy_delay;
4036 engine_snapshot_mm.overload_delay = overload_delay;
4039 void LoadEngineSnapshotValues_MM(void)
4043 // stored engine snapshot buffers already restored at this point
4045 game_mm = engine_snapshot_mm.game_mm;
4046 laser = engine_snapshot_mm.laser;
4048 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4050 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4052 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4053 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4054 Box[x][y] = engine_snapshot_mm.Box[x][y];
4055 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4056 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4060 LX = engine_snapshot_mm.LX;
4061 LY = engine_snapshot_mm.LY;
4062 XS = engine_snapshot_mm.XS;
4063 YS = engine_snapshot_mm.YS;
4064 ELX = engine_snapshot_mm.ELX;
4065 ELY = engine_snapshot_mm.ELY;
4066 CT = engine_snapshot_mm.CT;
4067 Ct = engine_snapshot_mm.Ct;
4069 last_LX = engine_snapshot_mm.last_LX;
4070 last_LY = engine_snapshot_mm.last_LY;
4071 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4072 hold_x = engine_snapshot_mm.hold_x;
4073 hold_y = engine_snapshot_mm.hold_y;
4074 pacman_nr = engine_snapshot_mm.pacman_nr;
4076 rotate_delay = engine_snapshot_mm.rotate_delay;
4077 pacman_delay = engine_snapshot_mm.pacman_delay;
4078 energy_delay = engine_snapshot_mm.energy_delay;
4079 overload_delay = engine_snapshot_mm.overload_delay;
4081 RedrawPlayfield_MM();
4084 static int getAngleFromTouchDelta(int dx, int dy, int base)
4086 double pi = 3.141592653;
4087 double rad = atan2((double)-dy, (double)dx);
4088 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4089 double deg = rad2 * 180.0 / pi;
4091 return (int)(deg * base / 360.0 + 0.5) % base;
4094 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4096 // calculate start (source) position to be at the middle of the tile
4097 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4098 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4099 int dx = dst_mx - src_mx;
4100 int dy = dst_my - src_my;
4109 if (!IN_LEV_FIELD(x, y))
4112 element = Tile[x][y];
4114 if (!IS_MCDUFFIN(element) &&
4115 !IS_MIRROR(element) &&
4116 !IS_BEAMER(element) &&
4117 !IS_POLAR(element) &&
4118 !IS_POLAR_CROSS(element) &&
4119 !IS_DF_MIRROR(element))
4122 angle_old = get_element_angle(element);
4124 if (IS_MCDUFFIN(element))
4126 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4127 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4128 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4129 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4132 else if (IS_MIRROR(element) ||
4133 IS_DF_MIRROR(element))
4135 for (i = 0; i < laser.num_damages; i++)
4137 if (laser.damage[i].x == x &&
4138 laser.damage[i].y == y &&
4139 ObjHit(x, y, HIT_POS_CENTER))
4141 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4142 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4149 if (angle_new == -1)
4151 if (IS_MIRROR(element) ||
4152 IS_DF_MIRROR(element) ||
4156 if (IS_POLAR_CROSS(element))
4159 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4162 button = (angle_new == angle_old ? 0 :
4163 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4164 MB_LEFTBUTTON : MB_RIGHTBUTTON);