1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // http://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 unsigned int rotate_delay = 0;
115 static unsigned int pacman_delay = 0;
116 static unsigned int energy_delay = 0;
117 static unsigned int 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 (Feld[x][y] == EL_EXIT_CLOSED)
380 // initiate opening animation of exit door
381 Feld[x][y] = EL_EXIT_OPENING;
383 exit_element = EL_EXIT_OPEN;
387 else if (IS_RECEIVER(Feld[x][y]))
389 // remove field that blocks receiver
390 int phase = Feld[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 Feld[blocking_x][blocking_y] = EL_EMPTY;
400 DrawField_MM(blocking_x, blocking_y);
403 exit_element = EL_RECEIVER;
410 if (exit_element != EL_EMPTY)
411 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
414 static void InitMovDir_MM(int x, int y)
416 int element = Feld[x][y];
417 static int direction[3][4] =
419 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
420 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
421 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
426 case EL_PACMAN_RIGHT:
430 Feld[x][y] = EL_PACMAN;
431 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
439 static void InitField(int x, int y, boolean init_game)
441 int element = Feld[x][y];
446 Feld[x][y] = EL_EMPTY;
451 if (native_mm_level.auto_count_kettles)
452 game_mm.kettles_still_needed++;
455 case EL_LIGHTBULB_OFF:
456 game_mm.lights_still_needed++;
460 if (IS_MIRROR(element) ||
461 IS_BEAMER_OLD(element) ||
462 IS_BEAMER(element) ||
464 IS_POLAR_CROSS(element) ||
465 IS_DF_MIRROR(element) ||
466 IS_DF_MIRROR_AUTO(element) ||
467 IS_GRID_STEEL_AUTO(element) ||
468 IS_GRID_WOOD_AUTO(element) ||
469 IS_FIBRE_OPTIC(element))
471 if (IS_BEAMER_OLD(element))
473 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
474 element = Feld[x][y];
477 if (!IS_FIBRE_OPTIC(element))
479 static int steps_grid_auto = 0;
481 if (game_mm.num_cycle == 0) // initialize cycle steps for grids
482 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
484 if (IS_GRID_STEEL_AUTO(element) ||
485 IS_GRID_WOOD_AUTO(element))
486 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
488 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
490 game_mm.cycle[game_mm.num_cycle].x = x;
491 game_mm.cycle[game_mm.num_cycle].y = y;
495 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
497 int beamer_nr = BEAMER_NR(element);
498 int nr = laser.beamer[beamer_nr][0].num;
500 laser.beamer[beamer_nr][nr].x = x;
501 laser.beamer[beamer_nr][nr].y = y;
502 laser.beamer[beamer_nr][nr].num = 1;
505 else if (IS_PACMAN(element))
509 else if (IS_MCDUFFIN(element) || IS_LASER(element))
511 laser.start_edge.x = x;
512 laser.start_edge.y = y;
513 laser.start_angle = get_element_angle(element);
520 static void InitCycleElements_RotateSingleStep(void)
524 if (game_mm.num_cycle == 0) // no elements to cycle
527 for (i = 0; i < game_mm.num_cycle; i++)
529 int x = game_mm.cycle[i].x;
530 int y = game_mm.cycle[i].y;
531 int step = SIGN(game_mm.cycle[i].steps);
532 int last_element = Feld[x][y];
533 int next_element = get_rotated_element(last_element, step);
535 if (!game_mm.cycle[i].steps)
538 Feld[x][y] = next_element;
541 game_mm.cycle[i].steps -= step;
545 static void InitLaser(void)
547 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
548 int step = (IS_LASER(start_element) ? 4 : 0);
550 LX = laser.start_edge.x * TILEX;
551 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
554 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
556 LY = laser.start_edge.y * TILEY;
557 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
558 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
562 XS = 2 * Step[laser.start_angle].x;
563 YS = 2 * Step[laser.start_angle].y;
565 laser.current_angle = laser.start_angle;
567 laser.num_damages = 0;
569 laser.num_beamers = 0;
570 laser.beamer_edge[0] = 0;
572 laser.dest_element = EL_EMPTY;
575 AddLaserEdge(LX, LY); // set laser starting edge
577 pen_ray = GetPixelFromRGB(window,
578 native_mm_level.laser_red * 0xFF,
579 native_mm_level.laser_green * 0xFF,
580 native_mm_level.laser_blue * 0xFF);
583 void InitGameEngine_MM(void)
589 // initialize laser bitmap to current playfield (screen) size
590 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
591 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
595 // set global game control values
596 game_mm.num_cycle = 0;
597 game_mm.num_pacman = 0;
600 game_mm.energy_left = 0; // later set to "native_mm_level.time"
601 game_mm.kettles_still_needed =
602 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
603 game_mm.lights_still_needed = 0;
604 game_mm.num_keys = 0;
606 game_mm.level_solved = FALSE;
607 game_mm.game_over = FALSE;
608 game_mm.game_over_cause = 0;
610 game_mm.laser_overload_value = 0;
611 game_mm.laser_enabled = FALSE;
613 // set global laser control values (must be set before "InitLaser()")
614 laser.start_edge.x = 0;
615 laser.start_edge.y = 0;
616 laser.start_angle = 0;
618 for (i = 0; i < MAX_NUM_BEAMERS; i++)
619 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
621 laser.overloaded = FALSE;
622 laser.overload_value = 0;
623 laser.fuse_off = FALSE;
624 laser.fuse_x = laser.fuse_y = -1;
626 laser.dest_element = EL_EMPTY;
645 ClickElement(-1, -1, -1);
647 for (x = 0; x < lev_fieldx; x++)
649 for (y = 0; y < lev_fieldy; y++)
651 Feld[x][y] = Ur[x][y];
652 Hit[x][y] = Box[x][y] = 0;
654 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
655 Store[x][y] = Store2[x][y] = 0;
659 InitField(x, y, TRUE);
666 void InitGameActions_MM(void)
668 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
669 int cycle_steps_done = 0;
674 for (i = 0; i <= num_init_game_frames; i++)
676 if (i == num_init_game_frames)
677 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
678 else if (setup.sound_loops)
679 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
681 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
683 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
685 UpdateAndDisplayGameControlValues();
687 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
689 InitCycleElements_RotateSingleStep();
699 if (setup.quick_doors)
706 if (game_mm.kettles_still_needed == 0)
709 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
710 SetTileCursorActive(TRUE);
713 void AddLaserEdge(int lx, int ly)
718 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
720 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
725 laser.edge[laser.num_edges].x = cSX2 + lx;
726 laser.edge[laser.num_edges].y = cSY2 + ly;
732 void AddDamagedField(int ex, int ey)
734 laser.damage[laser.num_damages].is_mirror = FALSE;
735 laser.damage[laser.num_damages].angle = laser.current_angle;
736 laser.damage[laser.num_damages].edge = laser.num_edges;
737 laser.damage[laser.num_damages].x = ex;
738 laser.damage[laser.num_damages].y = ey;
742 static boolean StepBehind(void)
748 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
749 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
751 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
757 static int getMaskFromElement(int element)
759 if (IS_GRID(element))
760 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
761 else if (IS_MCDUFFIN(element))
762 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
763 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
764 return IMG_MM_MASK_RECTANGLE;
766 return IMG_MM_MASK_CIRCLE;
769 static int ScanPixel(void)
774 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
775 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
778 // follow laser beam until it hits something (at least the screen border)
779 while (hit_mask == HIT_MASK_NO_HIT)
785 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
786 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
788 printf("ScanPixel: touched screen border!\n");
794 for (i = 0; i < 4; i++)
796 int px = LX + (i % 2) * 2;
797 int py = LY + (i / 2) * 2;
800 int lx = (px + TILEX) / TILEX - 1; // ...+TILEX...-1 to get correct
801 int ly = (py + TILEY) / TILEY - 1; // negative values!
804 if (IN_LEV_FIELD(lx, ly))
806 int element = Feld[lx][ly];
808 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
812 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
814 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
816 pixel = ((element & (1 << pos)) ? 1 : 0);
820 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
822 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
827 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
828 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
831 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
832 hit_mask |= (1 << i);
835 if (hit_mask == HIT_MASK_NO_HIT)
837 // hit nothing -- go on with another step
849 int end = 0, rf = laser.num_edges;
851 // do not scan laser again after the game was lost for whatever reason
852 if (game_mm.game_over)
855 laser.overloaded = FALSE;
856 laser.stops_inside_element = FALSE;
858 DrawLaser(0, DL_LASER_ENABLED);
861 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
869 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
872 laser.overloaded = TRUE;
877 hit_mask = ScanPixel();
880 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
884 // hit something -- check out what it was
885 ELX = (LX + XS) / TILEX;
886 ELY = (LY + YS) / TILEY;
889 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
890 hit_mask, LX, LY, ELX, ELY);
893 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
896 laser.dest_element = element;
901 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
903 /* we have hit the top-right and bottom-left element --
904 choose the bottom-left one */
905 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
906 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
907 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
908 ELX = (LX - 2) / TILEX;
909 ELY = (LY + 2) / TILEY;
912 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
914 /* we have hit the top-left and bottom-right element --
915 choose the top-left one */
917 ELX = (LX - 2) / TILEX;
918 ELY = (LY - 2) / TILEY;
922 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
923 hit_mask, LX, LY, ELX, ELY);
926 element = Feld[ELX][ELY];
927 laser.dest_element = element;
930 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
933 LX % TILEX, LY % TILEY,
938 if (!IN_LEV_FIELD(ELX, ELY))
939 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
942 if (element == EL_EMPTY)
944 if (!HitOnlyAnEdge(element, hit_mask))
947 else if (element == EL_FUSE_ON)
949 if (HitPolarizer(element, hit_mask))
952 else if (IS_GRID(element) || IS_DF_GRID(element))
954 if (HitPolarizer(element, hit_mask))
957 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
958 element == EL_GATE_STONE || element == EL_GATE_WOOD)
960 if (HitBlock(element, hit_mask))
967 else if (IS_MCDUFFIN(element))
969 if (HitLaserSource(element, hit_mask))
972 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
973 IS_RECEIVER(element))
975 if (HitLaserDestination(element, hit_mask))
978 else if (IS_WALL(element))
980 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
982 if (HitReflectingWalls(element, hit_mask))
987 if (HitAbsorbingWalls(element, hit_mask))
993 if (HitElement(element, hit_mask))
998 DrawLaser(rf - 1, DL_LASER_ENABLED);
999 rf = laser.num_edges;
1003 if (laser.dest_element != Feld[ELX][ELY])
1005 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
1006 laser.dest_element, Feld[ELX][ELY]);
1010 if (!end && !laser.stops_inside_element && !StepBehind())
1013 printf("ScanLaser: Go one step back\n");
1019 AddLaserEdge(LX, LY);
1023 DrawLaser(rf - 1, DL_LASER_ENABLED);
1025 Ct = CT = FrameCounter;
1028 if (!IN_LEV_FIELD(ELX, ELY))
1029 printf("WARNING! (2) %d, %d\n", ELX, ELY);
1033 static void DrawLaserExt(int start_edge, int num_edges, int mode)
1039 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
1040 start_edge, num_edges, mode);
1045 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
1052 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
1058 if (mode == DL_LASER_DISABLED)
1060 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
1064 // now draw the laser to the backbuffer and (if enabled) to the screen
1065 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1067 redraw_mask |= REDRAW_FIELD;
1069 if (mode == DL_LASER_ENABLED)
1072 // after the laser was deleted, the "damaged" graphics must be restored
1073 if (laser.num_damages)
1075 int damage_start = 0;
1078 // determine the starting edge, from which graphics need to be restored
1081 for (i = 0; i < laser.num_damages; i++)
1083 if (laser.damage[i].edge == start_edge + 1)
1092 // restore graphics from this starting edge to the end of damage list
1093 for (i = damage_start; i < laser.num_damages; i++)
1095 int lx = laser.damage[i].x;
1096 int ly = laser.damage[i].y;
1097 int element = Feld[lx][ly];
1099 if (Hit[lx][ly] == laser.damage[i].edge)
1100 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1103 if (Box[lx][ly] == laser.damage[i].edge)
1106 if (IS_DRAWABLE(element))
1107 DrawField_MM(lx, ly);
1110 elx = laser.damage[damage_start].x;
1111 ely = laser.damage[damage_start].y;
1112 element = Feld[elx][ely];
1115 if (IS_BEAMER(element))
1119 for (i = 0; i < laser.num_beamers; i++)
1120 printf("-> %d\n", laser.beamer_edge[i]);
1121 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
1122 mode, elx, ely, Hit[elx][ely], start_edge);
1123 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
1124 get_element_angle(element), laser.damage[damage_start].angle);
1128 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1129 laser.num_beamers > 0 &&
1130 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1132 // element is outgoing beamer
1133 laser.num_damages = damage_start + 1;
1135 if (IS_BEAMER(element))
1136 laser.current_angle = get_element_angle(element);
1140 // element is incoming beamer or other element
1141 laser.num_damages = damage_start;
1142 laser.current_angle = laser.damage[laser.num_damages].angle;
1147 // no damages but McDuffin himself (who needs to be redrawn anyway)
1149 elx = laser.start_edge.x;
1150 ely = laser.start_edge.y;
1151 element = Feld[elx][ely];
1154 laser.num_edges = start_edge + 1;
1155 if (start_edge == 0)
1156 laser.current_angle = laser.start_angle;
1158 LX = laser.edge[start_edge].x - cSX2;
1159 LY = laser.edge[start_edge].y - cSY2;
1160 XS = 2 * Step[laser.current_angle].x;
1161 YS = 2 * Step[laser.current_angle].y;
1164 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
1170 if (IS_BEAMER(element) ||
1171 IS_FIBRE_OPTIC(element) ||
1172 IS_PACMAN(element) ||
1173 IS_POLAR(element) ||
1174 IS_POLAR_CROSS(element) ||
1175 element == EL_FUSE_ON)
1180 printf("element == %d\n", element);
1183 if (IS_22_5_ANGLE(laser.current_angle)) // neither 90° nor 45° angle
1184 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1188 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1189 ((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 incoming beamer or other element
1194 step_size = -step_size;
1199 if (IS_BEAMER(element))
1201 printf("start_edge == %d, laser.beamer_edge == %d\n",
1202 start_edge, laser.beamer_edge);
1206 LX += step_size * XS;
1207 LY += step_size * YS;
1209 else if (element != EL_EMPTY)
1218 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
1223 void DrawLaser(int start_edge, int mode)
1225 if (laser.num_edges - start_edge < 0)
1227 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
1232 // check if laser is interrupted by beamer element
1233 if (laser.num_beamers > 0 &&
1234 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1236 if (mode == DL_LASER_ENABLED)
1239 int tmp_start_edge = start_edge;
1241 // draw laser segments forward from the start to the last beamer
1242 for (i = 0; i < laser.num_beamers; i++)
1244 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1246 if (tmp_num_edges <= 0)
1250 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
1251 i, laser.beamer_edge[i], tmp_start_edge);
1254 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1256 tmp_start_edge = laser.beamer_edge[i];
1259 // draw last segment from last beamer to the end
1260 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1266 int last_num_edges = laser.num_edges;
1267 int num_beamers = laser.num_beamers;
1269 // delete laser segments backward from the end to the first beamer
1270 for (i = num_beamers - 1; i >= 0; i--)
1272 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1274 if (laser.beamer_edge[i] - start_edge <= 0)
1277 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1279 last_num_edges = laser.beamer_edge[i];
1280 laser.num_beamers--;
1284 if (last_num_edges - start_edge <= 0)
1285 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1286 last_num_edges, start_edge);
1289 // special case when rotating first beamer: delete laser edge on beamer
1290 // (but do not start scanning on previous edge to prevent mirror sound)
1291 if (last_num_edges - start_edge == 1 && start_edge > 0)
1292 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1294 // delete first segment from start to the first beamer
1295 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1300 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1303 game_mm.laser_enabled = mode;
1306 void DrawLaser_MM(void)
1308 DrawLaser(0, game_mm.laser_enabled);
1311 boolean HitElement(int element, int hit_mask)
1313 if (HitOnlyAnEdge(element, hit_mask))
1316 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1317 element = MovingOrBlocked2Element_MM(ELX, ELY);
1320 printf("HitElement (1): element == %d\n", element);
1324 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1325 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1327 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1330 AddDamagedField(ELX, ELY);
1332 // this is more precise: check if laser would go through the center
1333 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1335 // skip the whole element before continuing the scan
1341 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1343 if (LX/TILEX > ELX || LY/TILEY > ELY)
1345 /* skipping scan positions to the right and down skips one scan
1346 position too much, because this is only the top left scan position
1347 of totally four scan positions (plus one to the right, one to the
1348 bottom and one to the bottom right) */
1358 printf("HitElement (2): element == %d\n", element);
1361 if (LX + 5 * XS < 0 ||
1371 printf("HitElement (3): element == %d\n", element);
1374 if (IS_POLAR(element) &&
1375 ((element - EL_POLAR_START) % 2 ||
1376 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1378 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1380 laser.num_damages--;
1385 if (IS_POLAR_CROSS(element) &&
1386 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1388 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1390 laser.num_damages--;
1395 if (!IS_BEAMER(element) &&
1396 !IS_FIBRE_OPTIC(element) &&
1397 !IS_GRID_WOOD(element) &&
1398 element != EL_FUEL_EMPTY)
1401 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1402 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1404 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1407 LX = ELX * TILEX + 14;
1408 LY = ELY * TILEY + 14;
1410 AddLaserEdge(LX, LY);
1413 if (IS_MIRROR(element) ||
1414 IS_MIRROR_FIXED(element) ||
1415 IS_POLAR(element) ||
1416 IS_POLAR_CROSS(element) ||
1417 IS_DF_MIRROR(element) ||
1418 IS_DF_MIRROR_AUTO(element) ||
1419 element == EL_PRISM ||
1420 element == EL_REFRACTOR)
1422 int current_angle = laser.current_angle;
1425 laser.num_damages--;
1427 AddDamagedField(ELX, ELY);
1429 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1432 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1434 if (IS_MIRROR(element) ||
1435 IS_MIRROR_FIXED(element) ||
1436 IS_DF_MIRROR(element) ||
1437 IS_DF_MIRROR_AUTO(element))
1438 laser.current_angle = get_mirrored_angle(laser.current_angle,
1439 get_element_angle(element));
1441 if (element == EL_PRISM || element == EL_REFRACTOR)
1442 laser.current_angle = RND(16);
1444 XS = 2 * Step[laser.current_angle].x;
1445 YS = 2 * Step[laser.current_angle].y;
1447 if (!IS_22_5_ANGLE(laser.current_angle)) // 90° or 45° angle
1452 LX += step_size * XS;
1453 LY += step_size * YS;
1456 // draw sparkles on mirror
1457 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1458 current_angle != laser.current_angle)
1460 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1464 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1465 current_angle != laser.current_angle)
1466 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1469 (get_opposite_angle(laser.current_angle) ==
1470 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1472 return (laser.overloaded ? TRUE : FALSE);
1475 if (element == EL_FUEL_FULL)
1477 laser.stops_inside_element = TRUE;
1482 if (element == EL_BOMB || element == EL_MINE)
1484 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1486 if (element == EL_MINE)
1487 laser.overloaded = TRUE;
1490 if (element == EL_KETTLE ||
1491 element == EL_CELL ||
1492 element == EL_KEY ||
1493 element == EL_LIGHTBALL ||
1494 element == EL_PACMAN ||
1497 if (!IS_PACMAN(element))
1500 if (element == EL_PACMAN)
1503 if (element == EL_KETTLE || element == EL_CELL)
1505 if (game_mm.kettles_still_needed > 0)
1506 game_mm.kettles_still_needed--;
1508 game.snapshot.collected_item = TRUE;
1510 if (game_mm.kettles_still_needed == 0)
1514 DrawLaser(0, DL_LASER_ENABLED);
1517 else if (element == EL_KEY)
1521 else if (IS_PACMAN(element))
1523 DeletePacMan(ELX, ELY);
1526 RaiseScoreElement_MM(element);
1531 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1533 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1535 DrawLaser(0, DL_LASER_ENABLED);
1537 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1539 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1540 game_mm.lights_still_needed--;
1544 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1545 game_mm.lights_still_needed++;
1548 DrawField_MM(ELX, ELY);
1549 DrawLaser(0, DL_LASER_ENABLED);
1554 laser.stops_inside_element = TRUE;
1560 printf("HitElement (4): element == %d\n", element);
1563 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1564 laser.num_beamers < MAX_NUM_BEAMERS &&
1565 laser.beamer[BEAMER_NR(element)][1].num)
1567 int beamer_angle = get_element_angle(element);
1568 int beamer_nr = BEAMER_NR(element);
1572 printf("HitElement (BEAMER): element == %d\n", element);
1575 laser.num_damages--;
1577 if (IS_FIBRE_OPTIC(element) ||
1578 laser.current_angle == get_opposite_angle(beamer_angle))
1582 LX = ELX * TILEX + 14;
1583 LY = ELY * TILEY + 14;
1585 AddLaserEdge(LX, LY);
1586 AddDamagedField(ELX, ELY);
1588 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1591 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1593 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1594 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1595 ELX = laser.beamer[beamer_nr][pos].x;
1596 ELY = laser.beamer[beamer_nr][pos].y;
1597 LX = ELX * TILEX + 14;
1598 LY = ELY * TILEY + 14;
1600 if (IS_BEAMER(element))
1602 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1603 XS = 2 * Step[laser.current_angle].x;
1604 YS = 2 * Step[laser.current_angle].y;
1607 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1609 AddLaserEdge(LX, LY);
1610 AddDamagedField(ELX, ELY);
1612 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1615 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1617 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1622 LX += step_size * XS;
1623 LY += step_size * YS;
1625 laser.num_beamers++;
1634 boolean HitOnlyAnEdge(int element, int hit_mask)
1636 // check if the laser hit only the edge of an element and, if so, go on
1639 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1642 if ((hit_mask == HIT_MASK_TOPLEFT ||
1643 hit_mask == HIT_MASK_TOPRIGHT ||
1644 hit_mask == HIT_MASK_BOTTOMLEFT ||
1645 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1646 laser.current_angle % 4) // angle is not 90°
1650 if (hit_mask == HIT_MASK_TOPLEFT)
1655 else if (hit_mask == HIT_MASK_TOPRIGHT)
1660 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1665 else // (hit_mask == HIT_MASK_BOTTOMRIGHT)
1671 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1677 printf("[HitOnlyAnEdge() == TRUE]\n");
1684 printf("[HitOnlyAnEdge() == FALSE]\n");
1690 boolean HitPolarizer(int element, int hit_mask)
1692 if (HitOnlyAnEdge(element, hit_mask))
1695 if (IS_DF_GRID(element))
1697 int grid_angle = get_element_angle(element);
1700 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1701 grid_angle, laser.current_angle);
1704 AddLaserEdge(LX, LY);
1705 AddDamagedField(ELX, ELY);
1708 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1710 if (laser.current_angle == grid_angle ||
1711 laser.current_angle == get_opposite_angle(grid_angle))
1713 // skip the whole element before continuing the scan
1719 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1721 if (LX/TILEX > ELX || LY/TILEY > ELY)
1723 /* skipping scan positions to the right and down skips one scan
1724 position too much, because this is only the top left scan position
1725 of totally four scan positions (plus one to the right, one to the
1726 bottom and one to the bottom right) */
1732 AddLaserEdge(LX, LY);
1738 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1740 LX / TILEX, LY / TILEY,
1741 LX % TILEX, LY % TILEY);
1746 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1748 return HitReflectingWalls(element, hit_mask);
1752 return HitAbsorbingWalls(element, hit_mask);
1755 else if (IS_GRID_STEEL(element))
1757 return HitReflectingWalls(element, hit_mask);
1759 else // IS_GRID_WOOD
1761 return HitAbsorbingWalls(element, hit_mask);
1767 boolean HitBlock(int element, int hit_mask)
1769 boolean check = FALSE;
1771 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1772 game_mm.num_keys == 0)
1775 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1778 int ex = ELX * TILEX + 14;
1779 int ey = ELY * TILEY + 14;
1783 for (i = 1; i < 32; i++)
1788 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1793 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1794 return HitAbsorbingWalls(element, hit_mask);
1798 AddLaserEdge(LX - XS, LY - YS);
1799 AddDamagedField(ELX, ELY);
1802 Box[ELX][ELY] = laser.num_edges;
1804 return HitReflectingWalls(element, hit_mask);
1807 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1809 int xs = XS / 2, ys = YS / 2;
1810 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1811 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1813 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1814 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1816 laser.overloaded = (element == EL_GATE_STONE);
1821 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1822 (hit_mask == HIT_MASK_TOP ||
1823 hit_mask == HIT_MASK_LEFT ||
1824 hit_mask == HIT_MASK_RIGHT ||
1825 hit_mask == HIT_MASK_BOTTOM))
1826 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1827 hit_mask == HIT_MASK_BOTTOM),
1828 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1829 hit_mask == HIT_MASK_RIGHT));
1830 AddLaserEdge(LX, LY);
1836 if (element == EL_GATE_STONE && Box[ELX][ELY])
1838 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1850 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1852 int xs = XS / 2, ys = YS / 2;
1853 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1854 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1856 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1857 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1859 laser.overloaded = (element == EL_BLOCK_STONE);
1864 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1865 (hit_mask == HIT_MASK_TOP ||
1866 hit_mask == HIT_MASK_LEFT ||
1867 hit_mask == HIT_MASK_RIGHT ||
1868 hit_mask == HIT_MASK_BOTTOM))
1869 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1870 hit_mask == HIT_MASK_BOTTOM),
1871 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1872 hit_mask == HIT_MASK_RIGHT));
1873 AddDamagedField(ELX, ELY);
1875 LX = ELX * TILEX + 14;
1876 LY = ELY * TILEY + 14;
1878 AddLaserEdge(LX, LY);
1880 laser.stops_inside_element = TRUE;
1888 boolean HitLaserSource(int element, int hit_mask)
1890 if (HitOnlyAnEdge(element, hit_mask))
1893 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1895 laser.overloaded = TRUE;
1900 boolean HitLaserDestination(int element, int hit_mask)
1902 if (HitOnlyAnEdge(element, hit_mask))
1905 if (element != EL_EXIT_OPEN &&
1906 !(IS_RECEIVER(element) &&
1907 game_mm.kettles_still_needed == 0 &&
1908 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1910 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1915 if (IS_RECEIVER(element) ||
1916 (IS_22_5_ANGLE(laser.current_angle) &&
1917 (ELX != (LX + 6 * XS) / TILEX ||
1918 ELY != (LY + 6 * YS) / TILEY ||
1927 LX = ELX * TILEX + 14;
1928 LY = ELY * TILEY + 14;
1930 laser.stops_inside_element = TRUE;
1933 AddLaserEdge(LX, LY);
1934 AddDamagedField(ELX, ELY);
1936 if (game_mm.lights_still_needed == 0)
1938 game_mm.level_solved = TRUE;
1940 SetTileCursorActive(FALSE);
1946 boolean HitReflectingWalls(int element, int hit_mask)
1948 // check if laser hits side of a wall with an angle that is not 90°
1949 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1950 hit_mask == HIT_MASK_LEFT ||
1951 hit_mask == HIT_MASK_RIGHT ||
1952 hit_mask == HIT_MASK_BOTTOM))
1954 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1959 if (!IS_DF_GRID(element))
1960 AddLaserEdge(LX, LY);
1962 // check if laser hits wall with an angle of 45°
1963 if (!IS_22_5_ANGLE(laser.current_angle))
1965 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1968 laser.current_angle = get_mirrored_angle(laser.current_angle,
1971 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
1974 laser.current_angle = get_mirrored_angle(laser.current_angle,
1978 AddLaserEdge(LX, LY);
1980 XS = 2 * Step[laser.current_angle].x;
1981 YS = 2 * Step[laser.current_angle].y;
1985 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1987 laser.current_angle = get_mirrored_angle(laser.current_angle,
1992 if (!IS_DF_GRID(element))
1993 AddLaserEdge(LX, LY);
1998 if (!IS_DF_GRID(element))
1999 AddLaserEdge(LX, LY + YS / 2);
2002 if (!IS_DF_GRID(element))
2003 AddLaserEdge(LX, LY);
2006 YS = 2 * Step[laser.current_angle].y;
2010 else // hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT
2012 laser.current_angle = get_mirrored_angle(laser.current_angle,
2017 if (!IS_DF_GRID(element))
2018 AddLaserEdge(LX, LY);
2023 if (!IS_DF_GRID(element))
2024 AddLaserEdge(LX + XS / 2, LY);
2027 if (!IS_DF_GRID(element))
2028 AddLaserEdge(LX, LY);
2031 XS = 2 * Step[laser.current_angle].x;
2037 // reflection at the edge of reflecting DF style wall
2038 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2040 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2041 hit_mask == HIT_MASK_TOPRIGHT) ||
2042 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2043 hit_mask == HIT_MASK_TOPLEFT) ||
2044 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2045 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2046 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2047 hit_mask == HIT_MASK_BOTTOMRIGHT))
2050 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2051 ANG_MIRROR_135 : ANG_MIRROR_45);
2053 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2055 AddDamagedField(ELX, ELY);
2056 AddLaserEdge(LX, LY);
2058 laser.current_angle = get_mirrored_angle(laser.current_angle,
2066 AddLaserEdge(LX, LY);
2072 // reflection inside an edge of reflecting DF style wall
2073 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2075 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2076 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2077 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2078 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2079 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2080 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2081 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2082 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2085 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2086 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2087 ANG_MIRROR_135 : ANG_MIRROR_45);
2089 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2092 AddDamagedField(ELX, ELY);
2095 AddLaserEdge(LX - XS, LY - YS);
2096 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2097 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2099 laser.current_angle = get_mirrored_angle(laser.current_angle,
2107 AddLaserEdge(LX, LY);
2113 // check if laser hits DF style wall with an angle of 90°
2114 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2116 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2117 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2118 (IS_VERT_ANGLE(laser.current_angle) &&
2119 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2121 // laser at last step touched nothing or the same side of the wall
2122 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2124 AddDamagedField(ELX, ELY);
2131 last_hit_mask = hit_mask;
2138 if (!HitOnlyAnEdge(element, hit_mask))
2140 laser.overloaded = TRUE;
2148 boolean HitAbsorbingWalls(int element, int hit_mask)
2150 if (HitOnlyAnEdge(element, hit_mask))
2154 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2156 AddLaserEdge(LX - XS, LY - YS);
2163 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2165 AddLaserEdge(LX - XS, LY - YS);
2171 if (IS_WALL_WOOD(element) ||
2172 IS_DF_WALL_WOOD(element) ||
2173 IS_GRID_WOOD(element) ||
2174 IS_GRID_WOOD_FIXED(element) ||
2175 IS_GRID_WOOD_AUTO(element) ||
2176 element == EL_FUSE_ON ||
2177 element == EL_BLOCK_WOOD ||
2178 element == EL_GATE_WOOD)
2180 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2185 if (IS_WALL_ICE(element))
2189 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; // Quadrant (horizontal)
2190 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; // || (vertical)
2192 // check if laser hits wall with an angle of 90°
2193 if (IS_90_ANGLE(laser.current_angle))
2194 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2196 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2200 for (i = 0; i < 4; i++)
2202 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2203 mask = 15 - (8 >> i);
2204 else if (ABS(XS) == 4 &&
2206 (XS > 0) == (i % 2) &&
2207 (YS < 0) == (i / 2))
2208 mask = 3 + (i / 2) * 9;
2209 else if (ABS(YS) == 4 &&
2211 (XS < 0) == (i % 2) &&
2212 (YS > 0) == (i / 2))
2213 mask = 5 + (i % 2) * 5;
2217 laser.wall_mask = mask;
2219 else if (IS_WALL_AMOEBA(element))
2221 int elx = (LX - 2 * XS) / TILEX;
2222 int ely = (LY - 2 * YS) / TILEY;
2223 int element2 = Feld[elx][ely];
2226 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2228 laser.dest_element = EL_EMPTY;
2236 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2237 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2239 if (IS_90_ANGLE(laser.current_angle))
2240 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2242 laser.dest_element = element2 | EL_WALL_AMOEBA;
2244 laser.wall_mask = mask;
2250 static void OpenExit(int x, int y)
2254 if (!MovDelay[x][y]) // next animation frame
2255 MovDelay[x][y] = 4 * delay;
2257 if (MovDelay[x][y]) // wait some time before next frame
2262 phase = MovDelay[x][y] / delay;
2264 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2265 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2267 if (!MovDelay[x][y])
2269 Feld[x][y] = EL_EXIT_OPEN;
2275 static void OpenSurpriseBall(int x, int y)
2279 if (!MovDelay[x][y]) // next animation frame
2280 MovDelay[x][y] = 50 * delay;
2282 if (MovDelay[x][y]) // wait some time before next frame
2286 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2289 int graphic = el2gfx(Store[x][y]);
2291 int dx = RND(26), dy = RND(26);
2293 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2295 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2296 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2298 MarkTileDirty(x, y);
2301 if (!MovDelay[x][y])
2303 Feld[x][y] = Store[x][y];
2312 static void MeltIce(int x, int y)
2317 if (!MovDelay[x][y]) // next animation frame
2318 MovDelay[x][y] = frames * delay;
2320 if (MovDelay[x][y]) // wait some time before next frame
2323 int wall_mask = Store2[x][y];
2324 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2327 phase = frames - MovDelay[x][y] / delay - 1;
2329 if (!MovDelay[x][y])
2333 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2334 Store[x][y] = Store2[x][y] = 0;
2336 DrawWalls_MM(x, y, Feld[x][y]);
2338 if (Feld[x][y] == EL_WALL_ICE)
2339 Feld[x][y] = EL_EMPTY;
2341 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2342 if (laser.damage[i].is_mirror)
2346 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2348 DrawLaser(0, DL_LASER_DISABLED);
2352 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2354 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2356 laser.redraw = TRUE;
2361 static void GrowAmoeba(int x, int y)
2366 if (!MovDelay[x][y]) // next animation frame
2367 MovDelay[x][y] = frames * delay;
2369 if (MovDelay[x][y]) // wait some time before next frame
2372 int wall_mask = Store2[x][y];
2373 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2376 phase = MovDelay[x][y] / delay;
2378 if (!MovDelay[x][y])
2380 Feld[x][y] = real_element;
2381 Store[x][y] = Store2[x][y] = 0;
2383 DrawWalls_MM(x, y, Feld[x][y]);
2384 DrawLaser(0, DL_LASER_ENABLED);
2386 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2388 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2393 static void Explode_MM(int x, int y, int phase, int mode)
2395 int num_phase = 9, delay = 2;
2396 int last_phase = num_phase * delay;
2397 int half_phase = (num_phase / 2) * delay;
2399 laser.redraw = TRUE;
2401 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
2403 int center_element = Feld[x][y];
2405 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2407 // put moving element to center field (and let it explode there)
2408 center_element = MovingOrBlocked2Element_MM(x, y);
2409 RemoveMovingField_MM(x, y);
2411 Feld[x][y] = center_element;
2414 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2415 Store[x][y] = center_element;
2417 Store[x][y] = EL_EMPTY;
2419 Store2[x][y] = mode;
2420 Feld[x][y] = EL_EXPLODING_OPAQUE;
2421 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2427 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2429 if (phase == half_phase)
2431 Feld[x][y] = EL_EXPLODING_TRANSP;
2433 if (x == ELX && y == ELY)
2437 if (phase == last_phase)
2439 if (Store[x][y] == EL_BOMB)
2441 DrawLaser(0, DL_LASER_DISABLED);
2444 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2445 Store[x][y] = EL_EMPTY;
2447 game_mm.game_over = TRUE;
2448 game_mm.game_over_cause = GAME_OVER_BOMB;
2450 SetTileCursorActive(FALSE);
2452 laser.overloaded = FALSE;
2454 else if (IS_MCDUFFIN(Store[x][y]))
2456 Store[x][y] = EL_EMPTY;
2458 game.restart_game_message = "Bomb killed Mc Duffin! Play it again?";
2461 Feld[x][y] = Store[x][y];
2462 Store[x][y] = Store2[x][y] = 0;
2463 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2465 InitField(x, y, FALSE);
2468 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2470 int graphic = IMG_MM_DEFAULT_EXPLODING;
2471 int graphic_phase = (phase / delay - 1);
2475 if (Store2[x][y] == EX_KETTLE)
2477 if (graphic_phase < 3)
2479 graphic = IMG_MM_KETTLE_EXPLODING;
2481 else if (graphic_phase < 5)
2487 graphic = IMG_EMPTY;
2491 else if (Store2[x][y] == EX_SHORT)
2493 if (graphic_phase < 4)
2499 graphic = IMG_EMPTY;
2504 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2506 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2507 cFX + x * TILEX, cFY + y * TILEY);
2509 MarkTileDirty(x, y);
2513 static void Bang_MM(int x, int y)
2515 int element = Feld[x][y];
2516 int mode = EX_NORMAL;
2519 DrawLaser(0, DL_LASER_ENABLED);
2538 if (IS_PACMAN(element))
2539 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2540 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2541 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2542 else if (element == EL_KEY)
2543 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2545 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2547 Explode_MM(x, y, EX_PHASE_START, mode);
2550 void TurnRound(int x, int y)
2562 { 0, 0 }, { 0, 0 }, { 0, 0 },
2567 int left, right, back;
2571 { MV_DOWN, MV_UP, MV_RIGHT },
2572 { MV_UP, MV_DOWN, MV_LEFT },
2574 { MV_LEFT, MV_RIGHT, MV_DOWN },
2575 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2576 { MV_RIGHT, MV_LEFT, MV_UP }
2579 int element = Feld[x][y];
2580 int old_move_dir = MovDir[x][y];
2581 int right_dir = turn[old_move_dir].right;
2582 int back_dir = turn[old_move_dir].back;
2583 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2584 int right_x = x + right_dx, right_y = y + right_dy;
2586 if (element == EL_PACMAN)
2588 boolean can_turn_right = FALSE;
2590 if (IN_LEV_FIELD(right_x, right_y) &&
2591 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2592 can_turn_right = TRUE;
2595 MovDir[x][y] = right_dir;
2597 MovDir[x][y] = back_dir;
2603 static void StartMoving_MM(int x, int y)
2605 int element = Feld[x][y];
2610 if (CAN_MOVE(element))
2614 if (MovDelay[x][y]) // wait some time before next movement
2622 // now make next step
2624 Moving2Blocked_MM(x, y, &newx, &newy); // get next screen position
2626 if (element == EL_PACMAN &&
2627 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2628 !ObjHit(newx, newy, HIT_POS_CENTER))
2630 Store[newx][newy] = Feld[newx][newy];
2631 Feld[newx][newy] = EL_EMPTY;
2633 DrawField_MM(newx, newy);
2635 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2636 ObjHit(newx, newy, HIT_POS_CENTER))
2638 // object was running against a wall
2645 InitMovingField_MM(x, y, MovDir[x][y]);
2649 ContinueMoving_MM(x, y);
2652 static void ContinueMoving_MM(int x, int y)
2654 int element = Feld[x][y];
2655 int direction = MovDir[x][y];
2656 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2657 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2658 int horiz_move = (dx!=0);
2659 int newx = x + dx, newy = y + dy;
2660 int step = (horiz_move ? dx : dy) * TILEX / 8;
2662 MovPos[x][y] += step;
2664 if (ABS(MovPos[x][y]) >= TILEX) // object reached its destination
2666 Feld[x][y] = EL_EMPTY;
2667 Feld[newx][newy] = element;
2669 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2670 MovDelay[newx][newy] = 0;
2672 if (!CAN_MOVE(element))
2673 MovDir[newx][newy] = 0;
2676 DrawField_MM(newx, newy);
2678 Stop[newx][newy] = TRUE;
2680 if (element == EL_PACMAN)
2682 if (Store[newx][newy] == EL_BOMB)
2683 Bang_MM(newx, newy);
2685 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2686 (LX + 2 * XS) / TILEX == newx &&
2687 (LY + 2 * YS) / TILEY == newy)
2694 else // still moving on
2699 laser.redraw = TRUE;
2702 boolean ClickElement(int x, int y, int button)
2704 static unsigned int click_delay = 0;
2705 static int click_delay_value = CLICK_DELAY;
2706 static boolean new_button = TRUE;
2707 boolean element_clicked = FALSE;
2712 // initialize static variables
2714 click_delay_value = CLICK_DELAY;
2720 // do not rotate objects hit by the laser after the game was solved
2721 if (game_mm.level_solved && Hit[x][y])
2724 if (button == MB_RELEASED)
2727 click_delay_value = CLICK_DELAY;
2729 // release eventually hold auto-rotating mirror
2730 RotateMirror(x, y, MB_RELEASED);
2735 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2738 if (button == MB_MIDDLEBUTTON) // middle button has no function
2741 if (!IN_LEV_FIELD(x, y))
2744 if (Feld[x][y] == EL_EMPTY)
2747 element = Feld[x][y];
2749 if (IS_MIRROR(element) ||
2750 IS_BEAMER(element) ||
2751 IS_POLAR(element) ||
2752 IS_POLAR_CROSS(element) ||
2753 IS_DF_MIRROR(element) ||
2754 IS_DF_MIRROR_AUTO(element))
2756 RotateMirror(x, y, button);
2758 element_clicked = TRUE;
2760 else if (IS_MCDUFFIN(element))
2762 if (!laser.fuse_off)
2764 DrawLaser(0, DL_LASER_DISABLED);
2771 element = get_rotated_element(element, BUTTON_ROTATION(button));
2772 laser.start_angle = get_element_angle(element);
2776 Feld[x][y] = element;
2783 if (!laser.fuse_off)
2786 element_clicked = TRUE;
2788 else if (element == EL_FUSE_ON && laser.fuse_off)
2790 if (x != laser.fuse_x || y != laser.fuse_y)
2793 laser.fuse_off = FALSE;
2794 laser.fuse_x = laser.fuse_y = -1;
2796 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2799 element_clicked = TRUE;
2801 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2803 laser.fuse_off = TRUE;
2806 laser.overloaded = FALSE;
2808 DrawLaser(0, DL_LASER_DISABLED);
2809 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2811 element_clicked = TRUE;
2813 else if (element == EL_LIGHTBALL)
2816 RaiseScoreElement_MM(element);
2817 DrawLaser(0, DL_LASER_ENABLED);
2819 element_clicked = TRUE;
2822 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2825 return element_clicked;
2828 void RotateMirror(int x, int y, int button)
2830 if (button == MB_RELEASED)
2832 // release eventually hold auto-rotating mirror
2839 if (IS_MIRROR(Feld[x][y]) ||
2840 IS_POLAR_CROSS(Feld[x][y]) ||
2841 IS_POLAR(Feld[x][y]) ||
2842 IS_BEAMER(Feld[x][y]) ||
2843 IS_DF_MIRROR(Feld[x][y]) ||
2844 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2845 IS_GRID_WOOD_AUTO(Feld[x][y]))
2847 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2849 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2851 if (button == MB_LEFTBUTTON)
2853 // left mouse button only for manual adjustment, no auto-rotating;
2854 // freeze mirror for until mouse button released
2858 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2860 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2864 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2866 int edge = Hit[x][y];
2872 DrawLaser(edge - 1, DL_LASER_DISABLED);
2876 else if (ObjHit(x, y, HIT_POS_CENTER))
2878 int edge = Hit[x][y];
2882 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2886 DrawLaser(edge - 1, DL_LASER_DISABLED);
2893 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2898 if ((IS_BEAMER(Feld[x][y]) ||
2899 IS_POLAR(Feld[x][y]) ||
2900 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2904 if (IS_BEAMER(Feld[x][y]))
2907 printf("TEST (%d, %d) [%d] [%d]\n",
2909 laser.beamer_edge, laser.beamer[1].num);
2919 DrawLaser(0, DL_LASER_ENABLED);
2923 static void AutoRotateMirrors(void)
2927 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2930 for (x = 0; x < lev_fieldx; x++)
2932 for (y = 0; y < lev_fieldy; y++)
2934 int element = Feld[x][y];
2936 // do not rotate objects hit by the laser after the game was solved
2937 if (game_mm.level_solved && Hit[x][y])
2940 if (IS_DF_MIRROR_AUTO(element) ||
2941 IS_GRID_WOOD_AUTO(element) ||
2942 IS_GRID_STEEL_AUTO(element) ||
2943 element == EL_REFRACTOR)
2944 RotateMirror(x, y, MB_RIGHTBUTTON);
2949 boolean ObjHit(int obx, int oby, int bits)
2956 if (bits & HIT_POS_CENTER)
2958 if (CheckLaserPixel(cSX + obx + 15,
2963 if (bits & HIT_POS_EDGE)
2965 for (i = 0; i < 4; i++)
2966 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
2967 cSY + oby + 31 * (i / 2)))
2971 if (bits & HIT_POS_BETWEEN)
2973 for (i = 0; i < 4; i++)
2974 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
2975 cSY + 4 + oby + 22 * (i / 2)))
2982 void DeletePacMan(int px, int py)
2988 if (game_mm.num_pacman <= 1)
2990 game_mm.num_pacman = 0;
2994 for (i = 0; i < game_mm.num_pacman; i++)
2995 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2998 game_mm.num_pacman--;
3000 for (j = i; j < game_mm.num_pacman; j++)
3002 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3003 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3004 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3008 void ColorCycling(void)
3010 static int CC, Cc = 0;
3012 static int color, old = 0xF00, new = 0x010, mult = 1;
3013 static unsigned short red, green, blue;
3015 if (color_status == STATIC_COLORS)
3020 if (CC < Cc || CC > Cc + 2)
3024 color = old + new * mult;
3030 if (ABS(mult) == 16)
3040 red = 0x0e00 * ((color & 0xF00) >> 8);
3041 green = 0x0e00 * ((color & 0x0F0) >> 4);
3042 blue = 0x0e00 * ((color & 0x00F));
3043 SetRGB(pen_magicolor[0], red, green, blue);
3045 red = 0x1111 * ((color & 0xF00) >> 8);
3046 green = 0x1111 * ((color & 0x0F0) >> 4);
3047 blue = 0x1111 * ((color & 0x00F));
3048 SetRGB(pen_magicolor[1], red, green, blue);
3052 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3059 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3062 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3064 element = Feld[x][y];
3066 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3067 StartMoving_MM(x, y);
3068 else if (IS_MOVING(x, y))
3069 ContinueMoving_MM(x, y);
3070 else if (IS_EXPLODING(element))
3071 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3072 else if (element == EL_EXIT_OPENING)
3074 else if (element == EL_GRAY_BALL_OPENING)
3075 OpenSurpriseBall(x, y);
3076 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3078 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3082 AutoRotateMirrors();
3085 // !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!!
3087 // redraw after Explode_MM() ...
3089 DrawLaser(0, DL_LASER_ENABLED);
3090 laser.redraw = FALSE;
3095 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3099 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3101 DrawLaser(0, DL_LASER_DISABLED);
3106 if (FrameReached(&energy_delay, ENERGY_DELAY))
3108 if (game_mm.energy_left > 0)
3110 game_mm.energy_left--;
3112 redraw_mask |= REDRAW_DOOR_1;
3114 else if (setup.time_limit && !game_mm.game_over)
3118 for (i = 15; i >= 0; i--)
3121 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3123 pen_ray = GetPixelFromRGB(window,
3124 native_mm_level.laser_red * 0x11 * i,
3125 native_mm_level.laser_green * 0x11 * i,
3126 native_mm_level.laser_blue * 0x11 * i);
3128 DrawLaser(0, DL_LASER_ENABLED);
3130 Delay_WithScreenUpdates(50);
3133 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3138 DrawLaser(0, DL_LASER_DISABLED);
3139 game_mm.game_over = TRUE;
3140 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3142 SetTileCursorActive(FALSE);
3144 game.restart_game_message = "Out of magic energy! Play it again?";
3147 if (Request("Out of magic energy! Play it again?",
3148 REQ_ASK | REQ_STAY_CLOSED))
3154 game_status = MAINMENU;
3163 element = laser.dest_element;
3166 if (element != Feld[ELX][ELY])
3168 printf("element == %d, Feld[ELX][ELY] == %d\n",
3169 element, Feld[ELX][ELY]);
3173 if (!laser.overloaded && laser.overload_value == 0 &&
3174 element != EL_BOMB &&
3175 element != EL_MINE &&
3176 element != EL_BALL_GRAY &&
3177 element != EL_BLOCK_STONE &&
3178 element != EL_BLOCK_WOOD &&
3179 element != EL_FUSE_ON &&
3180 element != EL_FUEL_FULL &&
3181 !IS_WALL_ICE(element) &&
3182 !IS_WALL_AMOEBA(element))
3185 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3186 (!laser.overloaded && laser.overload_value > 0)) &&
3187 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3189 if (laser.overloaded)
3190 laser.overload_value++;
3192 laser.overload_value--;
3194 if (game_mm.cheat_no_overload)
3196 laser.overloaded = FALSE;
3197 laser.overload_value = 0;
3200 game_mm.laser_overload_value = laser.overload_value;
3202 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3204 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3205 int color_down = 0xFF - color_up;
3208 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3209 (15 - (laser.overload_value / 6)) * color_scale);
3212 GetPixelFromRGB(window,
3213 (native_mm_level.laser_red ? 0xFF : color_up),
3214 (native_mm_level.laser_green ? color_down : 0x00),
3215 (native_mm_level.laser_blue ? color_down : 0x00));
3217 DrawLaser(0, DL_LASER_ENABLED);
3220 if (!laser.overloaded)
3221 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3222 else if (setup.sound_loops)
3223 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3225 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3227 if (laser.overloaded)
3230 BlitBitmap(pix[PIX_DOOR], drawto,
3231 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3232 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3233 - laser.overload_value,
3234 OVERLOAD_XSIZE, laser.overload_value,
3235 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3236 - laser.overload_value);
3238 redraw_mask |= REDRAW_DOOR_1;
3243 BlitBitmap(pix[PIX_DOOR], drawto,
3244 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3245 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3246 DX_OVERLOAD, DY_OVERLOAD);
3248 redraw_mask |= REDRAW_DOOR_1;
3251 if (laser.overload_value == MAX_LASER_OVERLOAD)
3255 UpdateAndDisplayGameControlValues();
3257 for (i = 15; i >= 0; i--)
3260 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3263 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3265 DrawLaser(0, DL_LASER_ENABLED);
3267 Delay_WithScreenUpdates(50);
3270 DrawLaser(0, DL_LASER_DISABLED);
3272 game_mm.game_over = TRUE;
3273 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3275 SetTileCursorActive(FALSE);
3277 game.restart_game_message = "Magic spell hit Mc Duffin! Play it again?";
3280 if (Request("Magic spell hit Mc Duffin! Play it again?",
3281 REQ_ASK | REQ_STAY_CLOSED))
3287 game_status = MAINMENU;
3301 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3303 if (game_mm.cheat_no_explosion)
3307 laser.num_damages--;
3308 DrawLaser(0, DL_LASER_DISABLED);
3309 laser.num_edges = 0;
3314 laser.dest_element = EL_EXPLODING_OPAQUE;
3318 laser.num_damages--;
3319 DrawLaser(0, DL_LASER_DISABLED);
3321 laser.num_edges = 0;
3322 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3324 if (Request("Bomb killed Mc Duffin! Play it again?",
3325 REQ_ASK | REQ_STAY_CLOSED))
3331 game_status = MAINMENU;
3339 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3341 laser.fuse_off = TRUE;
3345 DrawLaser(0, DL_LASER_DISABLED);
3346 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3349 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3351 static int new_elements[] =
3354 EL_MIRROR_FIXED_START,
3356 EL_POLAR_CROSS_START,
3362 int num_new_elements = sizeof(new_elements) / sizeof(int);
3363 int new_element = new_elements[RND(num_new_elements)];
3365 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3366 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3368 // !!! CHECK AGAIN: Laser on Polarizer !!!
3379 element = EL_MIRROR_START + RND(16);
3385 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3392 element = (rnd == 0 ? EL_FUSE_ON :
3393 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3394 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3395 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3396 EL_MIRROR_FIXED_START + rnd - 25);
3401 graphic = el2gfx(element);
3403 for (i = 0; i < 50; i++)
3409 BlitBitmap(pix[PIX_BACK], drawto,
3410 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3411 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3412 SX + ELX * TILEX + x,
3413 SY + ELY * TILEY + y);
3415 MarkTileDirty(ELX, ELY);
3418 DrawLaser(0, DL_LASER_ENABLED);
3420 Delay_WithScreenUpdates(50);
3423 Feld[ELX][ELY] = element;
3424 DrawField_MM(ELX, ELY);
3427 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3430 // above stuff: GRAY BALL -> PRISM !!!
3432 LX = ELX * TILEX + 14;
3433 LY = ELY * TILEY + 14;
3434 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3441 laser.num_edges -= 2;
3442 laser.num_damages--;
3446 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3447 if (laser.damage[i].is_mirror)
3451 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3453 DrawLaser(0, DL_LASER_DISABLED);
3455 DrawLaser(0, DL_LASER_DISABLED);
3461 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3468 if (IS_WALL_ICE(element) && CT > 50)
3470 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3473 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3474 Store[ELX][ELY] = EL_WALL_ICE;
3475 Store2[ELX][ELY] = laser.wall_mask;
3477 laser.dest_element = Feld[ELX][ELY];
3482 for (i = 0; i < 5; i++)
3488 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3492 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3494 Delay_WithScreenUpdates(100);
3497 if (Feld[ELX][ELY] == EL_WALL_ICE)
3498 Feld[ELX][ELY] = EL_EMPTY;
3502 LX = laser.edge[laser.num_edges].x - cSX2;
3503 LY = laser.edge[laser.num_edges].y - cSY2;
3506 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3507 if (laser.damage[i].is_mirror)
3511 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3513 DrawLaser(0, DL_LASER_DISABLED);
3520 if (IS_WALL_AMOEBA(element) && CT > 60)
3522 int k1, k2, k3, dx, dy, de, dm;
3523 int element2 = Feld[ELX][ELY];
3525 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3528 for (i = laser.num_damages - 1; i >= 0; i--)
3529 if (laser.damage[i].is_mirror)
3532 r = laser.num_edges;
3533 d = laser.num_damages;
3540 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3543 DrawLaser(0, DL_LASER_ENABLED);
3546 x = laser.damage[k1].x;
3547 y = laser.damage[k1].y;
3552 for (i = 0; i < 4; i++)
3554 if (laser.wall_mask & (1 << i))
3556 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3557 cSY + ELY * TILEY + 31 * (i / 2)))
3560 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3561 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3568 for (i = 0; i < 4; i++)
3570 if (laser.wall_mask & (1 << i))
3572 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3573 cSY + ELY * TILEY + 31 * (i / 2)))
3580 if (laser.num_beamers > 0 ||
3581 k1 < 1 || k2 < 4 || k3 < 4 ||
3582 CheckLaserPixel(cSX + ELX * TILEX + 14,
3583 cSY + ELY * TILEY + 14))
3585 laser.num_edges = r;
3586 laser.num_damages = d;
3588 DrawLaser(0, DL_LASER_DISABLED);
3591 Feld[ELX][ELY] = element | laser.wall_mask;
3595 de = Feld[ELX][ELY];
3596 dm = laser.wall_mask;
3600 int x = ELX, y = ELY;
3601 int wall_mask = laser.wall_mask;
3604 DrawLaser(0, DL_LASER_ENABLED);
3606 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3608 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3609 Store[x][y] = EL_WALL_AMOEBA;
3610 Store2[x][y] = wall_mask;
3616 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3618 DrawLaser(0, DL_LASER_ENABLED);
3620 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3622 for (i = 4; i >= 0; i--)
3624 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3627 Delay_WithScreenUpdates(20);
3630 DrawLaser(0, DL_LASER_ENABLED);
3635 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3636 laser.stops_inside_element && CT > native_mm_level.time_block)
3641 if (ABS(XS) > ABS(YS))
3648 for (i = 0; i < 4; i++)
3655 x = ELX + Step[k * 4].x;
3656 y = ELY + Step[k * 4].y;
3658 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3661 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3669 laser.overloaded = (element == EL_BLOCK_STONE);
3674 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3677 Feld[x][y] = element;
3679 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3682 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3684 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3685 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3693 if (element == EL_FUEL_FULL && CT > 10)
3695 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3698 BlitBitmap(pix[PIX_DOOR], drawto,
3699 DOOR_GFX_PAGEX4 + XX_ENERGY,
3700 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3701 ENERGY_XSIZE, i, DX_ENERGY,
3702 DY_ENERGY + ENERGY_YSIZE - i);
3705 redraw_mask |= REDRAW_DOOR_1;
3708 Delay_WithScreenUpdates(20);
3711 game_mm.energy_left = MAX_LASER_ENERGY;
3712 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3713 DrawField_MM(ELX, ELY);
3715 DrawLaser(0, DL_LASER_ENABLED);
3723 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3725 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3726 boolean button_released = (action.button == MB_RELEASED);
3728 GameActions_MM_Ext(action, warp_mode);
3730 CheckSingleStepMode_MM(element_clicked, button_released);
3733 void MovePacMen(void)
3735 int mx, my, ox, oy, nx, ny;
3739 if (++pacman_nr >= game_mm.num_pacman)
3742 game_mm.pacman[pacman_nr].dir--;
3744 for (l = 1; l < 5; l++)
3746 game_mm.pacman[pacman_nr].dir++;
3748 if (game_mm.pacman[pacman_nr].dir > 4)
3749 game_mm.pacman[pacman_nr].dir = 1;
3751 if (game_mm.pacman[pacman_nr].dir % 2)
3754 my = game_mm.pacman[pacman_nr].dir - 2;
3759 mx = 3 - game_mm.pacman[pacman_nr].dir;
3762 ox = game_mm.pacman[pacman_nr].x;
3763 oy = game_mm.pacman[pacman_nr].y;
3766 element = Feld[nx][ny];
3768 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3771 if (!IS_EATABLE4PACMAN(element))
3774 if (ObjHit(nx, ny, HIT_POS_CENTER))
3777 Feld[ox][oy] = EL_EMPTY;
3779 EL_PACMAN_RIGHT - 1 +
3780 (game_mm.pacman[pacman_nr].dir - 1 +
3781 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3783 game_mm.pacman[pacman_nr].x = nx;
3784 game_mm.pacman[pacman_nr].y = ny;
3786 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3788 if (element != EL_EMPTY)
3790 int graphic = el2gfx(Feld[nx][ny]);
3795 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3798 ox = cSX + ox * TILEX;
3799 oy = cSY + oy * TILEY;
3801 for (i = 1; i < 33; i += 2)
3802 BlitBitmap(bitmap, window,
3803 src_x, src_y, TILEX, TILEY,
3804 ox + i * mx, oy + i * my);
3805 Ct = Ct + FrameCounter - CT;
3808 DrawField_MM(nx, ny);
3811 if (!laser.fuse_off)
3813 DrawLaser(0, DL_LASER_ENABLED);
3815 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3817 AddDamagedField(nx, ny);
3819 laser.damage[laser.num_damages - 1].edge = 0;
3823 if (element == EL_BOMB)
3824 DeletePacMan(nx, ny);
3826 if (IS_WALL_AMOEBA(element) &&
3827 (LX + 2 * XS) / TILEX == nx &&
3828 (LY + 2 * YS) / TILEY == ny)
3838 void GameWon_MM(void)
3841 boolean raise_level = FALSE;
3844 if (local_player->MovPos)
3847 local_player->LevelSolved = FALSE;
3850 if (game_mm.energy_left)
3852 if (setup.sound_loops)
3853 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3854 SND_CTRL_PLAY_LOOP);
3856 while (game_mm.energy_left > 0)
3858 if (!setup.sound_loops)
3859 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3862 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3863 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3868 game_mm.energy_left--;
3869 if (game_mm.energy_left >= 0)
3872 BlitBitmap(pix[PIX_DOOR], drawto,
3873 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3874 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3875 DX_ENERGY, DY_ENERGY);
3877 redraw_mask |= REDRAW_DOOR_1;
3881 Delay_WithScreenUpdates(10);
3884 if (setup.sound_loops)
3885 StopSound(SND_SIRR);
3887 else if (native_mm_level.time == 0) // level without time limit
3889 if (setup.sound_loops)
3890 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3891 SND_CTRL_PLAY_LOOP);
3893 while (TimePlayed < 999)
3895 if (!setup.sound_loops)
3896 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3897 if (TimePlayed < 999 && !(TimePlayed % 10))
3898 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3899 if (TimePlayed < 900 && !(TimePlayed % 10))
3905 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3909 Delay_WithScreenUpdates(10);
3912 if (setup.sound_loops)
3913 StopSound(SND_SIRR);
3916 CloseDoor(DOOR_CLOSE_1);
3918 Request("Level solved!", REQ_CONFIRM);
3920 if (level_nr == leveldir_current->handicap_level)
3922 leveldir_current->handicap_level++;
3923 SaveLevelSetup_SeriesInfo();
3926 if (level_editor_test_game)
3927 game_mm.score = -1; // no highscore when playing from editor
3928 else if (level_nr < leveldir_current->last_level)
3929 raise_level = TRUE; // advance to next level
3931 if ((hi_pos = NewHiScore_MM()) >= 0)
3933 game_status = HALLOFFAME;
3935 // DrawHallOfFame(hi_pos);
3942 game_status = MAINMENU;
3953 int NewHiScore_MM(void)
3958 // LoadScore(level_nr);
3960 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3961 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3964 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3966 if (game_mm.score > highscore[k].Score)
3968 // player has made it to the hall of fame
3970 if (k < MAX_SCORE_ENTRIES - 1)
3972 int m = MAX_SCORE_ENTRIES - 1;
3975 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3976 if (!strcmp(setup.player_name, highscore[l].Name))
3978 if (m == k) // player's new highscore overwrites his old one
3982 for (l = m; l>k; l--)
3984 strcpy(highscore[l].Name, highscore[l - 1].Name);
3985 highscore[l].Score = highscore[l - 1].Score;
3992 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3993 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3994 highscore[k].Score = game_mm.score;
4001 else if (!strncmp(setup.player_name, highscore[k].Name,
4002 MAX_PLAYER_NAME_LEN))
4003 break; // player already there with a higher score
4008 // if (position >= 0)
4009 // SaveScore(level_nr);
4014 static void InitMovingField_MM(int x, int y, int direction)
4016 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4017 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4019 MovDir[x][y] = direction;
4020 MovDir[newx][newy] = direction;
4022 if (Feld[newx][newy] == EL_EMPTY)
4023 Feld[newx][newy] = EL_BLOCKED;
4026 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4028 int direction = MovDir[x][y];
4029 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4030 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4036 static void Blocked2Moving_MM(int x, int y,
4037 int *comes_from_x, int *comes_from_y)
4039 int oldx = x, oldy = y;
4040 int direction = MovDir[x][y];
4042 if (direction == MV_LEFT)
4044 else if (direction == MV_RIGHT)
4046 else if (direction == MV_UP)
4048 else if (direction == MV_DOWN)
4051 *comes_from_x = oldx;
4052 *comes_from_y = oldy;
4055 static int MovingOrBlocked2Element_MM(int x, int y)
4057 int element = Feld[x][y];
4059 if (element == EL_BLOCKED)
4063 Blocked2Moving_MM(x, y, &oldx, &oldy);
4065 return Feld[oldx][oldy];
4072 static void RemoveField(int x, int y)
4074 Feld[x][y] = EL_EMPTY;
4081 static void RemoveMovingField_MM(int x, int y)
4083 int oldx = x, oldy = y, newx = x, newy = y;
4085 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4088 if (IS_MOVING(x, y))
4090 Moving2Blocked_MM(x, y, &newx, &newy);
4091 if (Feld[newx][newy] != EL_BLOCKED)
4094 else if (Feld[x][y] == EL_BLOCKED)
4096 Blocked2Moving_MM(x, y, &oldx, &oldy);
4097 if (!IS_MOVING(oldx, oldy))
4101 Feld[oldx][oldy] = EL_EMPTY;
4102 Feld[newx][newy] = EL_EMPTY;
4103 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4104 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4106 DrawLevelField_MM(oldx, oldy);
4107 DrawLevelField_MM(newx, newy);
4110 void PlaySoundLevel(int x, int y, int sound_nr)
4112 int sx = SCREENX(x), sy = SCREENY(y);
4114 int silence_distance = 8;
4116 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4117 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4120 if (!IN_LEV_FIELD(x, y) ||
4121 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4122 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4125 volume = SOUND_MAX_VOLUME;
4128 stereo = (sx - SCR_FIELDX/2) * 12;
4130 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4131 if (stereo > SOUND_MAX_RIGHT)
4132 stereo = SOUND_MAX_RIGHT;
4133 if (stereo < SOUND_MAX_LEFT)
4134 stereo = SOUND_MAX_LEFT;
4137 if (!IN_SCR_FIELD(sx, sy))
4139 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4140 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4142 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4145 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4148 static void RaiseScore_MM(int value)
4150 game_mm.score += value;
4153 void RaiseScoreElement_MM(int element)
4158 case EL_PACMAN_RIGHT:
4160 case EL_PACMAN_LEFT:
4161 case EL_PACMAN_DOWN:
4162 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4166 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4171 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4175 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4184 // ----------------------------------------------------------------------------
4185 // Mirror Magic game engine snapshot handling functions
4186 // ----------------------------------------------------------------------------
4188 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4192 engine_snapshot_mm.game_mm = game_mm;
4193 engine_snapshot_mm.laser = laser;
4195 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4197 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4199 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4200 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4201 engine_snapshot_mm.Box[x][y] = Box[x][y];
4202 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4203 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4207 engine_snapshot_mm.LX = LX;
4208 engine_snapshot_mm.LY = LY;
4209 engine_snapshot_mm.XS = XS;
4210 engine_snapshot_mm.YS = YS;
4211 engine_snapshot_mm.ELX = ELX;
4212 engine_snapshot_mm.ELY = ELY;
4213 engine_snapshot_mm.CT = CT;
4214 engine_snapshot_mm.Ct = Ct;
4216 engine_snapshot_mm.last_LX = last_LX;
4217 engine_snapshot_mm.last_LY = last_LY;
4218 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4219 engine_snapshot_mm.hold_x = hold_x;
4220 engine_snapshot_mm.hold_y = hold_y;
4221 engine_snapshot_mm.pacman_nr = pacman_nr;
4223 engine_snapshot_mm.rotate_delay = rotate_delay;
4224 engine_snapshot_mm.pacman_delay = pacman_delay;
4225 engine_snapshot_mm.energy_delay = energy_delay;
4226 engine_snapshot_mm.overload_delay = overload_delay;
4229 void LoadEngineSnapshotValues_MM(void)
4233 // stored engine snapshot buffers already restored at this point
4235 game_mm = engine_snapshot_mm.game_mm;
4236 laser = engine_snapshot_mm.laser;
4238 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4240 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4242 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4243 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4244 Box[x][y] = engine_snapshot_mm.Box[x][y];
4245 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4246 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4250 LX = engine_snapshot_mm.LX;
4251 LY = engine_snapshot_mm.LY;
4252 XS = engine_snapshot_mm.XS;
4253 YS = engine_snapshot_mm.YS;
4254 ELX = engine_snapshot_mm.ELX;
4255 ELY = engine_snapshot_mm.ELY;
4256 CT = engine_snapshot_mm.CT;
4257 Ct = engine_snapshot_mm.Ct;
4259 last_LX = engine_snapshot_mm.last_LX;
4260 last_LY = engine_snapshot_mm.last_LY;
4261 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4262 hold_x = engine_snapshot_mm.hold_x;
4263 hold_y = engine_snapshot_mm.hold_y;
4264 pacman_nr = engine_snapshot_mm.pacman_nr;
4266 rotate_delay = engine_snapshot_mm.rotate_delay;
4267 pacman_delay = engine_snapshot_mm.pacman_delay;
4268 energy_delay = engine_snapshot_mm.energy_delay;
4269 overload_delay = engine_snapshot_mm.overload_delay;
4271 RedrawPlayfield_MM();
4274 static int getAngleFromTouchDelta(int dx, int dy, int base)
4276 double pi = 3.141592653;
4277 double rad = atan2((double)-dy, (double)dx);
4278 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4279 double deg = rad2 * 180.0 / pi;
4281 return (int)(deg * base / 360.0 + 0.5) % base;
4284 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4286 // calculate start (source) position to be at the middle of the tile
4287 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4288 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4289 int dx = dst_mx - src_mx;
4290 int dy = dst_my - src_my;
4299 if (!IN_LEV_FIELD(x, y))
4302 element = Feld[x][y];
4304 if (!IS_MCDUFFIN(element) &&
4305 !IS_MIRROR(element) &&
4306 !IS_BEAMER(element) &&
4307 !IS_POLAR(element) &&
4308 !IS_POLAR_CROSS(element) &&
4309 !IS_DF_MIRROR(element))
4312 angle_old = get_element_angle(element);
4314 if (IS_MCDUFFIN(element))
4316 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4317 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4318 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4319 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4322 else if (IS_MIRROR(element) ||
4323 IS_DF_MIRROR(element))
4325 for (i = 0; i < laser.num_damages; i++)
4327 if (laser.damage[i].x == x &&
4328 laser.damage[i].y == y &&
4329 ObjHit(x, y, HIT_POS_CENTER))
4331 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4332 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4339 if (angle_new == -1)
4341 if (IS_MIRROR(element) ||
4342 IS_DF_MIRROR(element) ||
4346 if (IS_POLAR_CROSS(element))
4349 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4352 button = (angle_new == angle_old ? 0 :
4353 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4354 MB_LEFTBUTTON : MB_RIGHTBUTTON);