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()
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()
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()
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()
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);
664 CloseDoor(DOOR_CLOSE_1);
670 void InitGameActions_MM()
672 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
673 int cycle_steps_done = 0;
679 /* copy default game door content to main double buffer */
680 BlitBitmap(pix[PIX_DOOR], drawto,
681 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
685 DrawText(DX_LEVEL, DY_LEVEL,
686 int2str(level_nr, 2), FONT_TEXT_2);
687 DrawText(DX_KETTLES, DY_KETTLES,
688 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
689 DrawText(DX_SCORE, DY_SCORE,
690 int2str(game_mm.score, 4), FONT_TEXT_2);
699 /* copy actual game door content to door double buffer for OpenDoor() */
700 BlitBitmap(drawto, pix[PIX_DB_DOOR],
701 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
705 OpenDoor(DOOR_OPEN_ALL);
708 for (i = 0; i <= num_init_game_frames; i++)
710 if (i == num_init_game_frames)
711 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
712 else if (setup.sound_loops)
713 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
715 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
717 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
719 UpdateAndDisplayGameControlValues();
721 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
723 InitCycleElements_RotateSingleStep();
733 if (setup.quick_doors)
739 if (setup.sound_music && num_bg_loops)
740 PlayMusic(level_nr % num_bg_loops);
745 if (game_mm.kettles_still_needed == 0)
748 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
749 SetTileCursorActive(TRUE);
752 void AddLaserEdge(int lx, int ly)
757 if (clx < -2 || cly < -2 || clx >= SXSIZE + 2 || cly >= SYSIZE + 2)
759 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
764 laser.edge[laser.num_edges].x = cSX2 + lx;
765 laser.edge[laser.num_edges].y = cSY2 + ly;
771 void AddDamagedField(int ex, int ey)
773 laser.damage[laser.num_damages].is_mirror = FALSE;
774 laser.damage[laser.num_damages].angle = laser.current_angle;
775 laser.damage[laser.num_damages].edge = laser.num_edges;
776 laser.damage[laser.num_damages].x = ex;
777 laser.damage[laser.num_damages].y = ey;
787 int last_x = laser.edge[laser.num_edges - 1].x - cSX2;
788 int last_y = laser.edge[laser.num_edges - 1].y - cSY2;
790 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
796 static int getMaskFromElement(int element)
798 if (IS_GRID(element))
799 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
800 else if (IS_MCDUFFIN(element))
801 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
802 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
803 return IMG_MM_MASK_RECTANGLE;
805 return IMG_MM_MASK_CIRCLE;
813 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
814 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
817 /* follow laser beam until it hits something (at least the screen border) */
818 while (hit_mask == HIT_MASK_NO_HIT)
824 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
825 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
827 printf("ScanPixel: touched screen border!\n");
833 for (i = 0; i < 4; i++)
835 int px = LX + (i % 2) * 2;
836 int py = LY + (i / 2) * 2;
839 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
840 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
843 if (IN_LEV_FIELD(lx, ly))
845 int element = Feld[lx][ly];
847 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
851 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
853 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
855 pixel = ((element & (1 << pos)) ? 1 : 0);
859 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
861 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
866 pixel = (cSX + px < REAL_SX || cSX + px >= REAL_SX + FULL_SXSIZE ||
867 cSY + py < REAL_SY || cSY + py >= REAL_SY + FULL_SYSIZE);
870 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
871 hit_mask |= (1 << i);
874 if (hit_mask == HIT_MASK_NO_HIT)
876 /* hit nothing -- go on with another step */
888 int end = 0, rf = laser.num_edges;
890 /* do not scan laser again after the game was lost for whatever reason */
891 if (game_mm.game_over)
894 laser.overloaded = FALSE;
895 laser.stops_inside_element = FALSE;
897 DrawLaser(0, DL_LASER_ENABLED);
900 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
908 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
911 laser.overloaded = TRUE;
916 hit_mask = ScanPixel();
919 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
923 /* hit something -- check out what it was */
924 ELX = (LX + XS) / TILEX;
925 ELY = (LY + YS) / TILEY;
928 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
929 hit_mask, LX, LY, ELX, ELY);
932 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
935 laser.dest_element = element;
940 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
942 /* we have hit the top-right and bottom-left element --
943 choose the bottom-left one */
944 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
945 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
946 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
947 ELX = (LX - 2) / TILEX;
948 ELY = (LY + 2) / TILEY;
951 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
953 /* we have hit the top-left and bottom-right element --
954 choose the top-left one */
955 /* !!! SEE ABOVE !!! */
956 ELX = (LX - 2) / TILEX;
957 ELY = (LY - 2) / TILEY;
961 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
962 hit_mask, LX, LY, ELX, ELY);
965 element = Feld[ELX][ELY];
966 laser.dest_element = element;
969 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
972 LX % TILEX, LY % TILEY,
977 if (!IN_LEV_FIELD(ELX, ELY))
978 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
981 if (element == EL_EMPTY)
983 if (!HitOnlyAnEdge(element, hit_mask))
986 else if (element == EL_FUSE_ON)
988 if (HitPolarizer(element, hit_mask))
991 else if (IS_GRID(element) || IS_DF_GRID(element))
993 if (HitPolarizer(element, hit_mask))
996 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
997 element == EL_GATE_STONE || element == EL_GATE_WOOD)
999 if (HitBlock(element, hit_mask))
1006 else if (IS_MCDUFFIN(element))
1008 if (HitLaserSource(element, hit_mask))
1011 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1012 IS_RECEIVER(element))
1014 if (HitLaserDestination(element, hit_mask))
1017 else if (IS_WALL(element))
1019 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1021 if (HitReflectingWalls(element, hit_mask))
1026 if (HitAbsorbingWalls(element, hit_mask))
1032 if (HitElement(element, hit_mask))
1037 DrawLaser(rf - 1, DL_LASER_ENABLED);
1038 rf = laser.num_edges;
1042 if (laser.dest_element != Feld[ELX][ELY])
1044 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
1045 laser.dest_element, Feld[ELX][ELY]);
1049 if (!end && !laser.stops_inside_element && !StepBehind())
1052 printf("ScanLaser: Go one step back\n");
1058 AddLaserEdge(LX, LY);
1062 DrawLaser(rf - 1, DL_LASER_ENABLED);
1064 Ct = CT = FrameCounter;
1067 if (!IN_LEV_FIELD(ELX, ELY))
1068 printf("WARNING! (2) %d, %d\n", ELX, ELY);
1072 void DrawLaserExt(int start_edge, int num_edges, int mode)
1078 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
1079 start_edge, num_edges, mode);
1084 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
1091 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
1097 if (mode == DL_LASER_DISABLED)
1099 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
1103 /* now draw the laser to the backbuffer and (if enabled) to the screen */
1104 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1106 redraw_mask |= REDRAW_FIELD;
1108 if (mode == DL_LASER_ENABLED)
1111 /* after the laser was deleted, the "damaged" graphics must be restored */
1112 if (laser.num_damages)
1114 int damage_start = 0;
1117 /* determine the starting edge, from which graphics need to be restored */
1120 for (i = 0; i < laser.num_damages; i++)
1122 if (laser.damage[i].edge == start_edge + 1)
1131 /* restore graphics from this starting edge to the end of damage list */
1132 for (i = damage_start; i < laser.num_damages; i++)
1134 int lx = laser.damage[i].x;
1135 int ly = laser.damage[i].y;
1136 int element = Feld[lx][ly];
1138 if (Hit[lx][ly] == laser.damage[i].edge)
1139 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1142 if (Box[lx][ly] == laser.damage[i].edge)
1145 if (IS_DRAWABLE(element))
1146 DrawField_MM(lx, ly);
1149 elx = laser.damage[damage_start].x;
1150 ely = laser.damage[damage_start].y;
1151 element = Feld[elx][ely];
1154 if (IS_BEAMER(element))
1158 for (i = 0; i < laser.num_beamers; i++)
1159 printf("-> %d\n", laser.beamer_edge[i]);
1160 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
1161 mode, elx, ely, Hit[elx][ely], start_edge);
1162 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
1163 get_element_angle(element), laser.damage[damage_start].angle);
1167 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1168 laser.num_beamers > 0 &&
1169 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1171 /* element is outgoing beamer */
1172 laser.num_damages = damage_start + 1;
1174 if (IS_BEAMER(element))
1175 laser.current_angle = get_element_angle(element);
1179 /* element is incoming beamer or other element */
1180 laser.num_damages = damage_start;
1181 laser.current_angle = laser.damage[laser.num_damages].angle;
1186 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
1188 elx = laser.start_edge.x;
1189 ely = laser.start_edge.y;
1190 element = Feld[elx][ely];
1193 laser.num_edges = start_edge + 1;
1194 if (start_edge == 0)
1195 laser.current_angle = laser.start_angle;
1197 LX = laser.edge[start_edge].x - cSX2;
1198 LY = laser.edge[start_edge].y - cSY2;
1199 XS = 2 * Step[laser.current_angle].x;
1200 YS = 2 * Step[laser.current_angle].y;
1203 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
1209 if (IS_BEAMER(element) ||
1210 IS_FIBRE_OPTIC(element) ||
1211 IS_PACMAN(element) ||
1212 IS_POLAR(element) ||
1213 IS_POLAR_CROSS(element) ||
1214 element == EL_FUSE_ON)
1219 printf("element == %d\n", element);
1222 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
1223 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1227 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1228 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1229 (laser.num_beamers == 0 ||
1230 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1232 /* element is incoming beamer or other element */
1233 step_size = -step_size;
1238 if (IS_BEAMER(element))
1240 printf("start_edge == %d, laser.beamer_edge == %d\n",
1241 start_edge, laser.beamer_edge);
1245 LX += step_size * XS;
1246 LY += step_size * YS;
1248 else if (element != EL_EMPTY)
1257 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
1262 void DrawLaser(int start_edge, int mode)
1264 if (laser.num_edges - start_edge < 0)
1266 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
1271 /* check if laser is interrupted by beamer element */
1272 if (laser.num_beamers > 0 &&
1273 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1275 if (mode == DL_LASER_ENABLED)
1278 int tmp_start_edge = start_edge;
1280 /* draw laser segments forward from the start to the last beamer */
1281 for (i = 0; i < laser.num_beamers; i++)
1283 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1285 if (tmp_num_edges <= 0)
1289 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
1290 i, laser.beamer_edge[i], tmp_start_edge);
1293 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1295 tmp_start_edge = laser.beamer_edge[i];
1298 /* draw last segment from last beamer to the end */
1299 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1305 int last_num_edges = laser.num_edges;
1306 int num_beamers = laser.num_beamers;
1308 /* delete laser segments backward from the end to the first beamer */
1309 for (i = num_beamers - 1; i >= 0; i--)
1311 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1313 if (laser.beamer_edge[i] - start_edge <= 0)
1316 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1318 last_num_edges = laser.beamer_edge[i];
1319 laser.num_beamers--;
1323 if (last_num_edges - start_edge <= 0)
1324 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1325 last_num_edges, start_edge);
1328 // special case when rotating first beamer: delete laser edge on beamer
1329 // (but do not start scanning on previous edge to prevent mirror sound)
1330 if (last_num_edges - start_edge == 1 && start_edge > 0)
1331 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1333 /* delete first segment from start to the first beamer */
1334 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1339 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1342 game_mm.laser_enabled = mode;
1347 DrawLaser(0, game_mm.laser_enabled);
1350 boolean HitElement(int element, int hit_mask)
1352 if (HitOnlyAnEdge(element, hit_mask))
1355 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1356 element = MovingOrBlocked2Element_MM(ELX, ELY);
1359 printf("HitElement (1): element == %d\n", element);
1363 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1364 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1366 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1369 AddDamagedField(ELX, ELY);
1371 /* this is more precise: check if laser would go through the center */
1372 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1374 /* skip the whole element before continuing the scan */
1380 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1382 if (LX/TILEX > ELX || LY/TILEY > ELY)
1384 /* skipping scan positions to the right and down skips one scan
1385 position too much, because this is only the top left scan position
1386 of totally four scan positions (plus one to the right, one to the
1387 bottom and one to the bottom right) */
1397 printf("HitElement (2): element == %d\n", element);
1400 if (LX + 5 * XS < 0 ||
1410 printf("HitElement (3): element == %d\n", element);
1413 if (IS_POLAR(element) &&
1414 ((element - EL_POLAR_START) % 2 ||
1415 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1417 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1419 laser.num_damages--;
1424 if (IS_POLAR_CROSS(element) &&
1425 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1427 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1429 laser.num_damages--;
1434 if (!IS_BEAMER(element) &&
1435 !IS_FIBRE_OPTIC(element) &&
1436 !IS_GRID_WOOD(element) &&
1437 element != EL_FUEL_EMPTY)
1440 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1441 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1443 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1446 LX = ELX * TILEX + 14;
1447 LY = ELY * TILEY + 14;
1449 AddLaserEdge(LX, LY);
1452 if (IS_MIRROR(element) ||
1453 IS_MIRROR_FIXED(element) ||
1454 IS_POLAR(element) ||
1455 IS_POLAR_CROSS(element) ||
1456 IS_DF_MIRROR(element) ||
1457 IS_DF_MIRROR_AUTO(element) ||
1458 element == EL_PRISM ||
1459 element == EL_REFRACTOR)
1461 int current_angle = laser.current_angle;
1464 laser.num_damages--;
1466 AddDamagedField(ELX, ELY);
1468 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1471 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1473 if (IS_MIRROR(element) ||
1474 IS_MIRROR_FIXED(element) ||
1475 IS_DF_MIRROR(element) ||
1476 IS_DF_MIRROR_AUTO(element))
1477 laser.current_angle = get_mirrored_angle(laser.current_angle,
1478 get_element_angle(element));
1480 if (element == EL_PRISM || element == EL_REFRACTOR)
1481 laser.current_angle = RND(16);
1483 XS = 2 * Step[laser.current_angle].x;
1484 YS = 2 * Step[laser.current_angle].y;
1486 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1491 LX += step_size * XS;
1492 LY += step_size * YS;
1495 /* draw sparkles on mirror */
1496 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1497 current_angle != laser.current_angle)
1499 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1503 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1504 current_angle != laser.current_angle)
1505 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1508 (get_opposite_angle(laser.current_angle) ==
1509 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1511 return (laser.overloaded ? TRUE : FALSE);
1514 if (element == EL_FUEL_FULL)
1516 laser.stops_inside_element = TRUE;
1521 if (element == EL_BOMB || element == EL_MINE)
1523 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1525 if (element == EL_MINE)
1526 laser.overloaded = TRUE;
1529 if (element == EL_KETTLE ||
1530 element == EL_CELL ||
1531 element == EL_KEY ||
1532 element == EL_LIGHTBALL ||
1533 element == EL_PACMAN ||
1536 if (!IS_PACMAN(element))
1539 if (element == EL_PACMAN)
1542 if (element == EL_KETTLE || element == EL_CELL)
1544 if (game_mm.kettles_still_needed > 0)
1545 game_mm.kettles_still_needed--;
1547 game.snapshot.collected_item = TRUE;
1549 if (game_mm.kettles_still_needed == 0)
1553 DrawLaser(0, DL_LASER_ENABLED);
1556 else if (element == EL_KEY)
1560 else if (IS_PACMAN(element))
1562 DeletePacMan(ELX, ELY);
1565 RaiseScoreElement_MM(element);
1570 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1572 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1574 DrawLaser(0, DL_LASER_ENABLED);
1576 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1578 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1579 game_mm.lights_still_needed--;
1583 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1584 game_mm.lights_still_needed++;
1587 DrawField_MM(ELX, ELY);
1588 DrawLaser(0, DL_LASER_ENABLED);
1593 laser.stops_inside_element = TRUE;
1599 printf("HitElement (4): element == %d\n", element);
1602 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1603 laser.num_beamers < MAX_NUM_BEAMERS &&
1604 laser.beamer[BEAMER_NR(element)][1].num)
1606 int beamer_angle = get_element_angle(element);
1607 int beamer_nr = BEAMER_NR(element);
1611 printf("HitElement (BEAMER): element == %d\n", element);
1614 laser.num_damages--;
1616 if (IS_FIBRE_OPTIC(element) ||
1617 laser.current_angle == get_opposite_angle(beamer_angle))
1621 LX = ELX * TILEX + 14;
1622 LY = ELY * TILEY + 14;
1624 AddLaserEdge(LX, LY);
1625 AddDamagedField(ELX, ELY);
1627 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1630 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1632 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1633 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1634 ELX = laser.beamer[beamer_nr][pos].x;
1635 ELY = laser.beamer[beamer_nr][pos].y;
1636 LX = ELX * TILEX + 14;
1637 LY = ELY * TILEY + 14;
1639 if (IS_BEAMER(element))
1641 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1642 XS = 2 * Step[laser.current_angle].x;
1643 YS = 2 * Step[laser.current_angle].y;
1646 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1648 AddLaserEdge(LX, LY);
1649 AddDamagedField(ELX, ELY);
1651 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1654 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1656 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1661 LX += step_size * XS;
1662 LY += step_size * YS;
1664 laser.num_beamers++;
1673 boolean HitOnlyAnEdge(int element, int hit_mask)
1675 /* check if the laser hit only the edge of an element and, if so, go on */
1678 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1681 if ((hit_mask == HIT_MASK_TOPLEFT ||
1682 hit_mask == HIT_MASK_TOPRIGHT ||
1683 hit_mask == HIT_MASK_BOTTOMLEFT ||
1684 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1685 laser.current_angle % 4) /* angle is not 90° */
1689 if (hit_mask == HIT_MASK_TOPLEFT)
1694 else if (hit_mask == HIT_MASK_TOPRIGHT)
1699 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1704 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1710 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1716 printf("[HitOnlyAnEdge() == TRUE]\n");
1723 printf("[HitOnlyAnEdge() == FALSE]\n");
1729 boolean HitPolarizer(int element, int hit_mask)
1731 if (HitOnlyAnEdge(element, hit_mask))
1734 if (IS_DF_GRID(element))
1736 int grid_angle = get_element_angle(element);
1739 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1740 grid_angle, laser.current_angle);
1743 AddLaserEdge(LX, LY);
1744 AddDamagedField(ELX, ELY);
1747 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1749 if (laser.current_angle == grid_angle ||
1750 laser.current_angle == get_opposite_angle(grid_angle))
1752 /* skip the whole element before continuing the scan */
1758 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1760 if (LX/TILEX > ELX || LY/TILEY > ELY)
1762 /* skipping scan positions to the right and down skips one scan
1763 position too much, because this is only the top left scan position
1764 of totally four scan positions (plus one to the right, one to the
1765 bottom and one to the bottom right) */
1771 AddLaserEdge(LX, LY);
1777 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1779 LX / TILEX, LY / TILEY,
1780 LX % TILEX, LY % TILEY);
1785 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1787 return HitReflectingWalls(element, hit_mask);
1791 return HitAbsorbingWalls(element, hit_mask);
1794 else if (IS_GRID_STEEL(element))
1796 return HitReflectingWalls(element, hit_mask);
1798 else /* IS_GRID_WOOD */
1800 return HitAbsorbingWalls(element, hit_mask);
1806 boolean HitBlock(int element, int hit_mask)
1808 boolean check = FALSE;
1810 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1811 game_mm.num_keys == 0)
1814 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1817 int ex = ELX * TILEX + 14;
1818 int ey = ELY * TILEY + 14;
1822 for (i = 1; i < 32; i++)
1827 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1832 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1833 return HitAbsorbingWalls(element, hit_mask);
1837 AddLaserEdge(LX - XS, LY - YS);
1838 AddDamagedField(ELX, ELY);
1841 Box[ELX][ELY] = laser.num_edges;
1843 return HitReflectingWalls(element, hit_mask);
1846 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1848 int xs = XS / 2, ys = YS / 2;
1849 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1850 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1852 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1853 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1855 laser.overloaded = (element == EL_GATE_STONE);
1860 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1861 (hit_mask == HIT_MASK_TOP ||
1862 hit_mask == HIT_MASK_LEFT ||
1863 hit_mask == HIT_MASK_RIGHT ||
1864 hit_mask == HIT_MASK_BOTTOM))
1865 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1866 hit_mask == HIT_MASK_BOTTOM),
1867 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1868 hit_mask == HIT_MASK_RIGHT));
1869 AddLaserEdge(LX, LY);
1875 if (element == EL_GATE_STONE && Box[ELX][ELY])
1877 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1889 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1891 int xs = XS / 2, ys = YS / 2;
1892 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1893 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1895 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1896 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1898 laser.overloaded = (element == EL_BLOCK_STONE);
1903 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1904 (hit_mask == HIT_MASK_TOP ||
1905 hit_mask == HIT_MASK_LEFT ||
1906 hit_mask == HIT_MASK_RIGHT ||
1907 hit_mask == HIT_MASK_BOTTOM))
1908 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1909 hit_mask == HIT_MASK_BOTTOM),
1910 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1911 hit_mask == HIT_MASK_RIGHT));
1912 AddDamagedField(ELX, ELY);
1914 LX = ELX * TILEX + 14;
1915 LY = ELY * TILEY + 14;
1917 AddLaserEdge(LX, LY);
1919 laser.stops_inside_element = TRUE;
1927 boolean HitLaserSource(int element, int hit_mask)
1929 if (HitOnlyAnEdge(element, hit_mask))
1932 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1934 laser.overloaded = TRUE;
1939 boolean HitLaserDestination(int element, int hit_mask)
1941 if (HitOnlyAnEdge(element, hit_mask))
1944 if (element != EL_EXIT_OPEN &&
1945 !(IS_RECEIVER(element) &&
1946 game_mm.kettles_still_needed == 0 &&
1947 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1949 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1954 if (IS_RECEIVER(element) ||
1955 (IS_22_5_ANGLE(laser.current_angle) &&
1956 (ELX != (LX + 6 * XS) / TILEX ||
1957 ELY != (LY + 6 * YS) / TILEY ||
1966 LX = ELX * TILEX + 14;
1967 LY = ELY * TILEY + 14;
1969 laser.stops_inside_element = TRUE;
1972 AddLaserEdge(LX, LY);
1973 AddDamagedField(ELX, ELY);
1975 if (game_mm.lights_still_needed == 0)
1977 game_mm.level_solved = TRUE;
1979 SetTileCursorActive(FALSE);
1985 boolean HitReflectingWalls(int element, int hit_mask)
1987 /* check if laser hits side of a wall with an angle that is not 90° */
1988 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1989 hit_mask == HIT_MASK_LEFT ||
1990 hit_mask == HIT_MASK_RIGHT ||
1991 hit_mask == HIT_MASK_BOTTOM))
1993 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1998 if (!IS_DF_GRID(element))
1999 AddLaserEdge(LX, LY);
2001 /* check if laser hits wall with an angle of 45° */
2002 if (!IS_22_5_ANGLE(laser.current_angle))
2004 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2007 laser.current_angle = get_mirrored_angle(laser.current_angle,
2010 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2013 laser.current_angle = get_mirrored_angle(laser.current_angle,
2017 AddLaserEdge(LX, LY);
2019 XS = 2 * Step[laser.current_angle].x;
2020 YS = 2 * Step[laser.current_angle].y;
2024 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2026 laser.current_angle = get_mirrored_angle(laser.current_angle,
2031 if (!IS_DF_GRID(element))
2032 AddLaserEdge(LX, LY);
2037 if (!IS_DF_GRID(element))
2038 AddLaserEdge(LX, LY + YS / 2);
2041 if (!IS_DF_GRID(element))
2042 AddLaserEdge(LX, LY);
2045 YS = 2 * Step[laser.current_angle].y;
2049 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2051 laser.current_angle = get_mirrored_angle(laser.current_angle,
2056 if (!IS_DF_GRID(element))
2057 AddLaserEdge(LX, LY);
2062 if (!IS_DF_GRID(element))
2063 AddLaserEdge(LX + XS / 2, LY);
2066 if (!IS_DF_GRID(element))
2067 AddLaserEdge(LX, LY);
2070 XS = 2 * Step[laser.current_angle].x;
2076 /* reflection at the edge of reflecting DF style wall */
2077 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2079 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2080 hit_mask == HIT_MASK_TOPRIGHT) ||
2081 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2082 hit_mask == HIT_MASK_TOPLEFT) ||
2083 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2084 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2085 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2086 hit_mask == HIT_MASK_BOTTOMRIGHT))
2089 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2090 ANG_MIRROR_135 : ANG_MIRROR_45);
2092 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2094 AddDamagedField(ELX, ELY);
2095 AddLaserEdge(LX, LY);
2097 laser.current_angle = get_mirrored_angle(laser.current_angle,
2105 AddLaserEdge(LX, LY);
2111 /* reflection inside an edge of reflecting DF style wall */
2112 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2114 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2115 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2116 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2117 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2118 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2119 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2120 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2121 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2124 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2125 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2126 ANG_MIRROR_135 : ANG_MIRROR_45);
2128 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2131 AddDamagedField(ELX, ELY);
2134 AddLaserEdge(LX - XS, LY - YS);
2135 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2136 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2138 laser.current_angle = get_mirrored_angle(laser.current_angle,
2146 AddLaserEdge(LX, LY);
2152 /* check if laser hits DF style wall with an angle of 90° */
2153 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2155 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2156 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2157 (IS_VERT_ANGLE(laser.current_angle) &&
2158 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2160 /* laser at last step touched nothing or the same side of the wall */
2161 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2163 AddDamagedField(ELX, ELY);
2170 last_hit_mask = hit_mask;
2177 if (!HitOnlyAnEdge(element, hit_mask))
2179 laser.overloaded = TRUE;
2187 boolean HitAbsorbingWalls(int element, int hit_mask)
2189 if (HitOnlyAnEdge(element, hit_mask))
2193 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2195 AddLaserEdge(LX - XS, LY - YS);
2202 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2204 AddLaserEdge(LX - XS, LY - YS);
2210 if (IS_WALL_WOOD(element) ||
2211 IS_DF_WALL_WOOD(element) ||
2212 IS_GRID_WOOD(element) ||
2213 IS_GRID_WOOD_FIXED(element) ||
2214 IS_GRID_WOOD_AUTO(element) ||
2215 element == EL_FUSE_ON ||
2216 element == EL_BLOCK_WOOD ||
2217 element == EL_GATE_WOOD)
2219 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2224 if (IS_WALL_ICE(element))
2228 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
2229 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
2231 /* check if laser hits wall with an angle of 90° */
2232 if (IS_90_ANGLE(laser.current_angle))
2233 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2235 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2239 for (i = 0; i < 4; i++)
2241 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2242 mask = 15 - (8 >> i);
2243 else if (ABS(XS) == 4 &&
2245 (XS > 0) == (i % 2) &&
2246 (YS < 0) == (i / 2))
2247 mask = 3 + (i / 2) * 9;
2248 else if (ABS(YS) == 4 &&
2250 (XS < 0) == (i % 2) &&
2251 (YS > 0) == (i / 2))
2252 mask = 5 + (i % 2) * 5;
2256 laser.wall_mask = mask;
2258 else if (IS_WALL_AMOEBA(element))
2260 int elx = (LX - 2 * XS) / TILEX;
2261 int ely = (LY - 2 * YS) / TILEY;
2262 int element2 = Feld[elx][ely];
2265 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2267 laser.dest_element = EL_EMPTY;
2275 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2276 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2278 if (IS_90_ANGLE(laser.current_angle))
2279 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2281 laser.dest_element = element2 | EL_WALL_AMOEBA;
2283 laser.wall_mask = mask;
2289 void OpenExit(int x, int y)
2293 if (!MovDelay[x][y]) /* next animation frame */
2294 MovDelay[x][y] = 4 * delay;
2296 if (MovDelay[x][y]) /* wait some time before next frame */
2301 phase = MovDelay[x][y] / delay;
2303 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2304 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2306 if (!MovDelay[x][y])
2308 Feld[x][y] = EL_EXIT_OPEN;
2314 void OpenSurpriseBall(int x, int y)
2318 if (!MovDelay[x][y]) /* next animation frame */
2319 MovDelay[x][y] = 50 * delay;
2321 if (MovDelay[x][y]) /* wait some time before next frame */
2325 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2328 int graphic = el2gfx(Store[x][y]);
2330 int dx = RND(26), dy = RND(26);
2332 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2334 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2335 cSX + x * TILEX + dx, cSY + y * TILEY + dy);
2337 MarkTileDirty(x, y);
2340 if (!MovDelay[x][y])
2342 Feld[x][y] = Store[x][y];
2351 void MeltIce(int x, int y)
2356 if (!MovDelay[x][y]) /* next animation frame */
2357 MovDelay[x][y] = frames * delay;
2359 if (MovDelay[x][y]) /* wait some time before next frame */
2362 int wall_mask = Store2[x][y];
2363 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2366 phase = frames - MovDelay[x][y] / delay - 1;
2368 if (!MovDelay[x][y])
2372 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2373 Store[x][y] = Store2[x][y] = 0;
2375 DrawWalls_MM(x, y, Feld[x][y]);
2377 if (Feld[x][y] == EL_WALL_ICE)
2378 Feld[x][y] = EL_EMPTY;
2380 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2381 if (laser.damage[i].is_mirror)
2385 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2387 DrawLaser(0, DL_LASER_DISABLED);
2391 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2393 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2395 laser.redraw = TRUE;
2400 void GrowAmoeba(int x, int y)
2405 if (!MovDelay[x][y]) /* next animation frame */
2406 MovDelay[x][y] = frames * delay;
2408 if (MovDelay[x][y]) /* wait some time before next frame */
2411 int wall_mask = Store2[x][y];
2412 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2415 phase = MovDelay[x][y] / delay;
2417 if (!MovDelay[x][y])
2419 Feld[x][y] = real_element;
2420 Store[x][y] = Store2[x][y] = 0;
2422 DrawWalls_MM(x, y, Feld[x][y]);
2423 DrawLaser(0, DL_LASER_ENABLED);
2425 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2427 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2432 static void Explode_MM(int x, int y, int phase, int mode)
2434 int num_phase = 9, delay = 2;
2435 int last_phase = num_phase * delay;
2436 int half_phase = (num_phase / 2) * delay;
2438 laser.redraw = TRUE;
2440 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2442 int center_element = Feld[x][y];
2444 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2446 /* put moving element to center field (and let it explode there) */
2447 center_element = MovingOrBlocked2Element_MM(x, y);
2448 RemoveMovingField_MM(x, y);
2450 Feld[x][y] = center_element;
2453 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2454 Store[x][y] = center_element;
2456 Store[x][y] = EL_EMPTY;
2458 Store2[x][y] = mode;
2459 Feld[x][y] = EL_EXPLODING_OPAQUE;
2460 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2466 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2468 if (phase == half_phase)
2470 Feld[x][y] = EL_EXPLODING_TRANSP;
2472 if (x == ELX && y == ELY)
2476 if (phase == last_phase)
2478 if (Store[x][y] == EL_BOMB)
2480 DrawLaser(0, DL_LASER_DISABLED);
2483 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2484 Store[x][y] = EL_EMPTY;
2486 game_mm.game_over = TRUE;
2487 game_mm.game_over_cause = GAME_OVER_BOMB;
2489 SetTileCursorActive(FALSE);
2491 laser.overloaded = FALSE;
2493 else if (IS_MCDUFFIN(Store[x][y]))
2495 Store[x][y] = EL_EMPTY;
2497 game.restart_game_message = "Bomb killed Mc Duffin ! Play it again ?";
2500 Feld[x][y] = Store[x][y];
2501 Store[x][y] = Store2[x][y] = 0;
2502 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2504 InitField(x, y, FALSE);
2507 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2509 int graphic = IMG_MM_DEFAULT_EXPLODING;
2510 int graphic_phase = (phase / delay - 1);
2514 if (Store2[x][y] == EX_KETTLE)
2516 if (graphic_phase < 3)
2518 graphic = IMG_MM_KETTLE_EXPLODING;
2520 else if (graphic_phase < 5)
2526 graphic = IMG_EMPTY;
2530 else if (Store2[x][y] == EX_SHORT)
2532 if (graphic_phase < 4)
2538 graphic = IMG_EMPTY;
2543 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2545 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2546 cFX + x * TILEX, cFY + y * TILEY);
2548 MarkTileDirty(x, y);
2552 static void Bang_MM(int x, int y)
2554 int element = Feld[x][y];
2555 int mode = EX_NORMAL;
2558 DrawLaser(0, DL_LASER_ENABLED);
2577 if (IS_PACMAN(element))
2578 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2579 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2580 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2581 else if (element == EL_KEY)
2582 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2584 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2586 Explode_MM(x, y, EX_PHASE_START, mode);
2589 void TurnRound(int x, int y)
2601 { 0, 0 }, { 0, 0 }, { 0, 0 },
2606 int left, right, back;
2610 { MV_DOWN, MV_UP, MV_RIGHT },
2611 { MV_UP, MV_DOWN, MV_LEFT },
2613 { MV_LEFT, MV_RIGHT, MV_DOWN },
2614 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2615 { MV_RIGHT, MV_LEFT, MV_UP }
2618 int element = Feld[x][y];
2619 int old_move_dir = MovDir[x][y];
2620 int right_dir = turn[old_move_dir].right;
2621 int back_dir = turn[old_move_dir].back;
2622 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2623 int right_x = x + right_dx, right_y = y + right_dy;
2625 if (element == EL_PACMAN)
2627 boolean can_turn_right = FALSE;
2629 if (IN_LEV_FIELD(right_x, right_y) &&
2630 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2631 can_turn_right = TRUE;
2634 MovDir[x][y] = right_dir;
2636 MovDir[x][y] = back_dir;
2642 static void StartMoving_MM(int x, int y)
2644 int element = Feld[x][y];
2649 if (CAN_MOVE(element))
2653 if (MovDelay[x][y]) /* wait some time before next movement */
2661 /* now make next step */
2663 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2665 if (element == EL_PACMAN &&
2666 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2667 !ObjHit(newx, newy, HIT_POS_CENTER))
2669 Store[newx][newy] = Feld[newx][newy];
2670 Feld[newx][newy] = EL_EMPTY;
2672 DrawField_MM(newx, newy);
2674 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2675 ObjHit(newx, newy, HIT_POS_CENTER))
2677 /* object was running against a wall */
2684 InitMovingField_MM(x, y, MovDir[x][y]);
2688 ContinueMoving_MM(x, y);
2691 static void ContinueMoving_MM(int x, int y)
2693 int element = Feld[x][y];
2694 int direction = MovDir[x][y];
2695 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2696 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2697 int horiz_move = (dx!=0);
2698 int newx = x + dx, newy = y + dy;
2699 int step = (horiz_move ? dx : dy) * TILEX / 8;
2701 MovPos[x][y] += step;
2703 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2705 Feld[x][y] = EL_EMPTY;
2706 Feld[newx][newy] = element;
2708 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2709 MovDelay[newx][newy] = 0;
2711 if (!CAN_MOVE(element))
2712 MovDir[newx][newy] = 0;
2715 DrawField_MM(newx, newy);
2717 Stop[newx][newy] = TRUE;
2719 if (element == EL_PACMAN)
2721 if (Store[newx][newy] == EL_BOMB)
2722 Bang_MM(newx, newy);
2724 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2725 (LX + 2 * XS) / TILEX == newx &&
2726 (LY + 2 * YS) / TILEY == newy)
2733 else /* still moving on */
2738 laser.redraw = TRUE;
2741 boolean ClickElement(int x, int y, int button)
2743 static unsigned int click_delay = 0;
2744 static int click_delay_value = CLICK_DELAY;
2745 static boolean new_button = TRUE;
2746 boolean element_clicked = FALSE;
2751 /* initialize static variables */
2753 click_delay_value = CLICK_DELAY;
2759 /* do not rotate objects hit by the laser after the game was solved */
2760 if (game_mm.level_solved && Hit[x][y])
2763 if (button == MB_RELEASED)
2766 click_delay_value = CLICK_DELAY;
2768 /* release eventually hold auto-rotating mirror */
2769 RotateMirror(x, y, MB_RELEASED);
2774 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2777 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2780 if (!IN_LEV_FIELD(x, y))
2783 if (Feld[x][y] == EL_EMPTY)
2786 element = Feld[x][y];
2788 if (IS_MIRROR(element) ||
2789 IS_BEAMER(element) ||
2790 IS_POLAR(element) ||
2791 IS_POLAR_CROSS(element) ||
2792 IS_DF_MIRROR(element) ||
2793 IS_DF_MIRROR_AUTO(element))
2795 RotateMirror(x, y, button);
2797 element_clicked = TRUE;
2799 else if (IS_MCDUFFIN(element))
2801 if (!laser.fuse_off)
2803 DrawLaser(0, DL_LASER_DISABLED);
2810 element = get_rotated_element(element, BUTTON_ROTATION(button));
2811 laser.start_angle = get_element_angle(element);
2815 Feld[x][y] = element;
2822 if (!laser.fuse_off)
2825 element_clicked = TRUE;
2827 else if (element == EL_FUSE_ON && laser.fuse_off)
2829 if (x != laser.fuse_x || y != laser.fuse_y)
2832 laser.fuse_off = FALSE;
2833 laser.fuse_x = laser.fuse_y = -1;
2835 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2838 element_clicked = TRUE;
2840 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2842 laser.fuse_off = TRUE;
2845 laser.overloaded = FALSE;
2847 DrawLaser(0, DL_LASER_DISABLED);
2848 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2850 element_clicked = TRUE;
2852 else if (element == EL_LIGHTBALL)
2855 RaiseScoreElement_MM(element);
2856 DrawLaser(0, DL_LASER_ENABLED);
2858 element_clicked = TRUE;
2861 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2864 return element_clicked;
2867 void RotateMirror(int x, int y, int button)
2869 if (button == MB_RELEASED)
2871 /* release eventually hold auto-rotating mirror */
2878 if (IS_MIRROR(Feld[x][y]) ||
2879 IS_POLAR_CROSS(Feld[x][y]) ||
2880 IS_POLAR(Feld[x][y]) ||
2881 IS_BEAMER(Feld[x][y]) ||
2882 IS_DF_MIRROR(Feld[x][y]) ||
2883 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2884 IS_GRID_WOOD_AUTO(Feld[x][y]))
2886 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2888 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2890 if (button == MB_LEFTBUTTON)
2892 /* left mouse button only for manual adjustment, no auto-rotating;
2893 freeze mirror for until mouse button released */
2897 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2899 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2903 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2905 int edge = Hit[x][y];
2911 DrawLaser(edge - 1, DL_LASER_DISABLED);
2915 else if (ObjHit(x, y, HIT_POS_CENTER))
2917 int edge = Hit[x][y];
2921 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2925 DrawLaser(edge - 1, DL_LASER_DISABLED);
2932 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2937 if ((IS_BEAMER(Feld[x][y]) ||
2938 IS_POLAR(Feld[x][y]) ||
2939 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2943 if (IS_BEAMER(Feld[x][y]))
2946 printf("TEST (%d, %d) [%d] [%d]\n",
2948 laser.beamer_edge, laser.beamer[1].num);
2958 DrawLaser(0, DL_LASER_ENABLED);
2962 void AutoRotateMirrors()
2966 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2969 for (x = 0; x < lev_fieldx; x++)
2971 for (y = 0; y < lev_fieldy; y++)
2973 int element = Feld[x][y];
2975 /* do not rotate objects hit by the laser after the game was solved */
2976 if (game_mm.level_solved && Hit[x][y])
2979 if (IS_DF_MIRROR_AUTO(element) ||
2980 IS_GRID_WOOD_AUTO(element) ||
2981 IS_GRID_STEEL_AUTO(element) ||
2982 element == EL_REFRACTOR)
2983 RotateMirror(x, y, MB_RIGHTBUTTON);
2988 boolean ObjHit(int obx, int oby, int bits)
2995 if (bits & HIT_POS_CENTER)
2997 if (CheckLaserPixel(cSX + obx + 15,
3002 if (bits & HIT_POS_EDGE)
3004 for (i = 0; i < 4; i++)
3005 if (CheckLaserPixel(cSX + obx + 31 * (i % 2),
3006 cSY + oby + 31 * (i / 2)))
3010 if (bits & HIT_POS_BETWEEN)
3012 for (i = 0; i < 4; i++)
3013 if (CheckLaserPixel(cSX + 4 + obx + 22 * (i % 2),
3014 cSY + 4 + oby + 22 * (i / 2)))
3021 void DeletePacMan(int px, int py)
3027 if (game_mm.num_pacman <= 1)
3029 game_mm.num_pacman = 0;
3033 for (i = 0; i < game_mm.num_pacman; i++)
3034 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3037 game_mm.num_pacman--;
3039 for (j = i; j < game_mm.num_pacman; j++)
3041 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3042 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3043 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3047 void ColorCycling(void)
3049 static int CC, Cc = 0;
3051 static int color, old = 0xF00, new = 0x010, mult = 1;
3052 static unsigned short red, green, blue;
3054 if (color_status == STATIC_COLORS)
3059 if (CC < Cc || CC > Cc + 2)
3063 color = old + new * mult;
3069 if (ABS(mult) == 16)
3079 red = 0x0e00 * ((color & 0xF00) >> 8);
3080 green = 0x0e00 * ((color & 0x0F0) >> 4);
3081 blue = 0x0e00 * ((color & 0x00F));
3082 SetRGB(pen_magicolor[0], red, green, blue);
3084 red = 0x1111 * ((color & 0xF00) >> 8);
3085 green = 0x1111 * ((color & 0x0F0) >> 4);
3086 blue = 0x1111 * ((color & 0x00F));
3087 SetRGB(pen_magicolor[1], red, green, blue);
3091 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3098 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3101 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3103 element = Feld[x][y];
3105 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3106 StartMoving_MM(x, y);
3107 else if (IS_MOVING(x, y))
3108 ContinueMoving_MM(x, y);
3109 else if (IS_EXPLODING(element))
3110 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3111 else if (element == EL_EXIT_OPENING)
3113 else if (element == EL_GRAY_BALL_OPENING)
3114 OpenSurpriseBall(x, y);
3115 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3117 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3121 AutoRotateMirrors();
3124 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3126 /* redraw after Explode_MM() ... */
3128 DrawLaser(0, DL_LASER_ENABLED);
3129 laser.redraw = FALSE;
3134 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3138 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3140 DrawLaser(0, DL_LASER_DISABLED);
3145 if (FrameReached(&energy_delay, ENERGY_DELAY))
3147 if (game_mm.energy_left > 0)
3149 game_mm.energy_left--;
3152 BlitBitmap(pix[PIX_DOOR], drawto,
3153 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3154 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3155 DX_ENERGY, DY_ENERGY);
3157 redraw_mask |= REDRAW_DOOR_1;
3159 else if (setup.time_limit && !game_mm.game_over)
3163 for (i = 15; i >= 0; i--)
3166 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3168 pen_ray = GetPixelFromRGB(window,
3169 native_mm_level.laser_red * 0x11 * i,
3170 native_mm_level.laser_green * 0x11 * i,
3171 native_mm_level.laser_blue * 0x11 * i);
3173 DrawLaser(0, DL_LASER_ENABLED);
3178 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3183 DrawLaser(0, DL_LASER_DISABLED);
3184 game_mm.game_over = TRUE;
3185 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3187 SetTileCursorActive(FALSE);
3189 game.restart_game_message = "Out of magic energy ! Play it again ?";
3192 if (Request("Out of magic energy ! Play it again ?",
3193 REQ_ASK | REQ_STAY_CLOSED))
3199 game_status = MAINMENU;
3208 element = laser.dest_element;
3211 if (element != Feld[ELX][ELY])
3213 printf("element == %d, Feld[ELX][ELY] == %d\n",
3214 element, Feld[ELX][ELY]);
3218 if (!laser.overloaded && laser.overload_value == 0 &&
3219 element != EL_BOMB &&
3220 element != EL_MINE &&
3221 element != EL_BALL_GRAY &&
3222 element != EL_BLOCK_STONE &&
3223 element != EL_BLOCK_WOOD &&
3224 element != EL_FUSE_ON &&
3225 element != EL_FUEL_FULL &&
3226 !IS_WALL_ICE(element) &&
3227 !IS_WALL_AMOEBA(element))
3230 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3231 (!laser.overloaded && laser.overload_value > 0)) &&
3232 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3234 if (laser.overloaded)
3235 laser.overload_value++;
3237 laser.overload_value--;
3239 if (game_mm.cheat_no_overload)
3241 laser.overloaded = FALSE;
3242 laser.overload_value = 0;
3245 game_mm.laser_overload_value = laser.overload_value;
3247 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3249 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3250 int color_down = 0xFF - color_up;
3253 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3254 (15 - (laser.overload_value / 6)) * color_scale);
3257 GetPixelFromRGB(window,
3258 (native_mm_level.laser_red ? 0xFF : color_up),
3259 (native_mm_level.laser_green ? color_down : 0x00),
3260 (native_mm_level.laser_blue ? color_down : 0x00));
3262 DrawLaser(0, DL_LASER_ENABLED);
3268 if (!laser.overloaded)
3269 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3270 else if (setup.sound_loops)
3271 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3273 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3275 if (laser.overloaded)
3278 BlitBitmap(pix[PIX_DOOR], drawto,
3279 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3280 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3281 - laser.overload_value,
3282 OVERLOAD_XSIZE, laser.overload_value,
3283 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3284 - laser.overload_value);
3286 redraw_mask |= REDRAW_DOOR_1;
3291 BlitBitmap(pix[PIX_DOOR], drawto,
3292 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3293 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3294 DX_OVERLOAD, DY_OVERLOAD);
3296 redraw_mask |= REDRAW_DOOR_1;
3299 if (laser.overload_value == MAX_LASER_OVERLOAD)
3303 for (i = 15; i >= 0; i--)
3306 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3309 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3311 DrawLaser(0, DL_LASER_ENABLED);
3316 DrawLaser(0, DL_LASER_DISABLED);
3318 game_mm.game_over = TRUE;
3319 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3321 SetTileCursorActive(FALSE);
3323 game.restart_game_message = "Magic spell hit Mc Duffin ! Play it again ?";
3326 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3327 REQ_ASK | REQ_STAY_CLOSED))
3333 game_status = MAINMENU;
3347 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3349 if (game_mm.cheat_no_explosion)
3353 laser.num_damages--;
3354 DrawLaser(0, DL_LASER_DISABLED);
3355 laser.num_edges = 0;
3360 laser.dest_element = EL_EXPLODING_OPAQUE;
3364 laser.num_damages--;
3365 DrawLaser(0, DL_LASER_DISABLED);
3367 laser.num_edges = 0;
3368 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3370 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3371 REQ_ASK | REQ_STAY_CLOSED))
3377 game_status = MAINMENU;
3385 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3387 laser.fuse_off = TRUE;
3391 DrawLaser(0, DL_LASER_DISABLED);
3392 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3395 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3397 static int new_elements[] =
3400 EL_MIRROR_FIXED_START,
3402 EL_POLAR_CROSS_START,
3408 int num_new_elements = sizeof(new_elements) / sizeof(int);
3409 int new_element = new_elements[RND(num_new_elements)];
3411 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3412 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3414 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3425 element = EL_MIRROR_START + RND(16);
3431 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3438 element = (rnd == 0 ? EL_FUSE_ON :
3439 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3440 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3441 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3442 EL_MIRROR_FIXED_START + rnd - 25);
3447 graphic = el2gfx(element);
3449 for (i = 0; i < 50; i++)
3455 BlitBitmap(pix[PIX_BACK], drawto,
3456 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3457 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3458 SX + ELX * TILEX + x,
3459 SY + ELY * TILEY + y);
3461 MarkTileDirty(ELX, ELY);
3464 DrawLaser(0, DL_LASER_ENABLED);
3469 Feld[ELX][ELY] = element;
3470 DrawField_MM(ELX, ELY);
3473 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3476 /* above stuff: GRAY BALL -> PRISM !!! */
3478 LX = ELX * TILEX + 14;
3479 LY = ELY * TILEY + 14;
3480 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3487 laser.num_edges -= 2;
3488 laser.num_damages--;
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);
3501 DrawLaser(0, DL_LASER_DISABLED);
3507 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3514 if (IS_WALL_ICE(element) && CT > 50)
3516 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3519 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3520 Store[ELX][ELY] = EL_WALL_ICE;
3521 Store2[ELX][ELY] = laser.wall_mask;
3523 laser.dest_element = Feld[ELX][ELY];
3528 for (i = 0; i < 5; i++)
3534 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3538 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3543 if (Feld[ELX][ELY] == EL_WALL_ICE)
3544 Feld[ELX][ELY] = EL_EMPTY;
3548 LX = laser.edge[laser.num_edges].x - cSX2;
3549 LY = laser.edge[laser.num_edges].y - cSY2;
3552 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3553 if (laser.damage[i].is_mirror)
3557 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3559 DrawLaser(0, DL_LASER_DISABLED);
3566 if (IS_WALL_AMOEBA(element) && CT > 60)
3568 int k1, k2, k3, dx, dy, de, dm;
3569 int element2 = Feld[ELX][ELY];
3571 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3574 for (i = laser.num_damages - 1; i >= 0; i--)
3575 if (laser.damage[i].is_mirror)
3578 r = laser.num_edges;
3579 d = laser.num_damages;
3586 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3589 DrawLaser(0, DL_LASER_ENABLED);
3592 x = laser.damage[k1].x;
3593 y = laser.damage[k1].y;
3598 for (i = 0; i < 4; i++)
3600 if (laser.wall_mask & (1 << i))
3602 if (CheckLaserPixel(cSX + ELX * TILEX + 14 + (i % 2) * 2,
3603 cSY + ELY * TILEY + 31 * (i / 2)))
3606 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3607 cSY + ELY * TILEY + 14 + (i / 2) * 2))
3614 for (i = 0; i < 4; i++)
3616 if (laser.wall_mask & (1 << i))
3618 if (CheckLaserPixel(cSX + ELX * TILEX + 31 * (i % 2),
3619 cSY + ELY * TILEY + 31 * (i / 2)))
3626 if (laser.num_beamers > 0 ||
3627 k1 < 1 || k2 < 4 || k3 < 4 ||
3628 CheckLaserPixel(cSX + ELX * TILEX + 14,
3629 cSY + ELY * TILEY + 14))
3631 laser.num_edges = r;
3632 laser.num_damages = d;
3634 DrawLaser(0, DL_LASER_DISABLED);
3637 Feld[ELX][ELY] = element | laser.wall_mask;
3641 de = Feld[ELX][ELY];
3642 dm = laser.wall_mask;
3646 int x = ELX, y = ELY;
3647 int wall_mask = laser.wall_mask;
3650 DrawLaser(0, DL_LASER_ENABLED);
3652 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3654 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3655 Store[x][y] = EL_WALL_AMOEBA;
3656 Store2[x][y] = wall_mask;
3662 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3664 DrawLaser(0, DL_LASER_ENABLED);
3666 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3668 for (i = 4; i >= 0; i--)
3670 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3676 DrawLaser(0, DL_LASER_ENABLED);
3681 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3682 laser.stops_inside_element && CT > native_mm_level.time_block)
3687 if (ABS(XS) > ABS(YS))
3694 for (i = 0; i < 4; i++)
3701 x = ELX + Step[k * 4].x;
3702 y = ELY + Step[k * 4].y;
3704 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3707 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3715 laser.overloaded = (element == EL_BLOCK_STONE);
3720 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3723 Feld[x][y] = element;
3725 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3728 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3730 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3731 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3739 if (element == EL_FUEL_FULL && CT > 10)
3741 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3744 BlitBitmap(pix[PIX_DOOR], drawto,
3745 DOOR_GFX_PAGEX4 + XX_ENERGY,
3746 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3747 ENERGY_XSIZE, i, DX_ENERGY,
3748 DY_ENERGY + ENERGY_YSIZE - i);
3751 redraw_mask |= REDRAW_DOOR_1;
3757 game_mm.energy_left = MAX_LASER_ENERGY;
3758 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3759 DrawField_MM(ELX, ELY);
3761 DrawLaser(0, DL_LASER_ENABLED);
3769 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3771 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3772 boolean button_released = (action.button == MB_RELEASED);
3774 GameActions_MM_Ext(action, warp_mode);
3776 CheckSingleStepMode_MM(element_clicked, button_released);
3781 int mx, my, ox, oy, nx, ny;
3785 if (++pacman_nr >= game_mm.num_pacman)
3788 game_mm.pacman[pacman_nr].dir--;
3790 for (l = 1; l < 5; l++)
3792 game_mm.pacman[pacman_nr].dir++;
3794 if (game_mm.pacman[pacman_nr].dir > 4)
3795 game_mm.pacman[pacman_nr].dir = 1;
3797 if (game_mm.pacman[pacman_nr].dir % 2)
3800 my = game_mm.pacman[pacman_nr].dir - 2;
3805 mx = 3 - game_mm.pacman[pacman_nr].dir;
3808 ox = game_mm.pacman[pacman_nr].x;
3809 oy = game_mm.pacman[pacman_nr].y;
3812 element = Feld[nx][ny];
3814 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3817 if (!IS_EATABLE4PACMAN(element))
3820 if (ObjHit(nx, ny, HIT_POS_CENTER))
3823 Feld[ox][oy] = EL_EMPTY;
3825 EL_PACMAN_RIGHT - 1 +
3826 (game_mm.pacman[pacman_nr].dir - 1 +
3827 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3829 game_mm.pacman[pacman_nr].x = nx;
3830 game_mm.pacman[pacman_nr].y = ny;
3832 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3834 if (element != EL_EMPTY)
3836 int graphic = el2gfx(Feld[nx][ny]);
3841 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3844 ox = cSX + ox * TILEX;
3845 oy = cSY + oy * TILEY;
3847 for (i = 1; i < 33; i += 2)
3848 BlitBitmap(bitmap, window,
3849 src_x, src_y, TILEX, TILEY,
3850 ox + i * mx, oy + i * my);
3851 Ct = Ct + FrameCounter - CT;
3854 DrawField_MM(nx, ny);
3857 if (!laser.fuse_off)
3859 DrawLaser(0, DL_LASER_ENABLED);
3861 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3863 AddDamagedField(nx, ny);
3865 laser.damage[laser.num_damages - 1].edge = 0;
3869 if (element == EL_BOMB)
3870 DeletePacMan(nx, ny);
3872 if (IS_WALL_AMOEBA(element) &&
3873 (LX + 2 * XS) / TILEX == nx &&
3874 (LY + 2 * YS) / TILEY == ny)
3887 boolean raise_level = FALSE;
3890 if (local_player->MovPos)
3893 local_player->LevelSolved = FALSE;
3896 if (game_mm.energy_left)
3898 if (setup.sound_loops)
3899 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3900 SND_CTRL_PLAY_LOOP);
3902 while (game_mm.energy_left > 0)
3904 if (!setup.sound_loops)
3905 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3908 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3909 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3914 game_mm.energy_left--;
3915 if (game_mm.energy_left >= 0)
3918 BlitBitmap(pix[PIX_DOOR], drawto,
3919 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3920 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3921 DX_ENERGY, DY_ENERGY);
3923 redraw_mask |= REDRAW_DOOR_1;
3930 if (setup.sound_loops)
3931 StopSound(SND_SIRR);
3933 else if (native_mm_level.time == 0) /* level without time limit */
3935 if (setup.sound_loops)
3936 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3937 SND_CTRL_PLAY_LOOP);
3939 while (TimePlayed < 999)
3941 if (!setup.sound_loops)
3942 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3943 if (TimePlayed < 999 && !(TimePlayed % 10))
3944 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3945 if (TimePlayed < 900 && !(TimePlayed % 10))
3951 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3958 if (setup.sound_loops)
3959 StopSound(SND_SIRR);
3966 CloseDoor(DOOR_CLOSE_1);
3968 Request("Level solved !", REQ_CONFIRM);
3970 if (level_nr == leveldir_current->handicap_level)
3972 leveldir_current->handicap_level++;
3973 SaveLevelSetup_SeriesInfo();
3976 if (level_editor_test_game)
3977 game_mm.score = -1; /* no highscore when playing from editor */
3978 else if (level_nr < leveldir_current->last_level)
3979 raise_level = TRUE; /* advance to next level */
3981 if ((hi_pos = NewHiScore_MM()) >= 0)
3983 game_status = HALLOFFAME;
3985 // DrawHallOfFame(hi_pos);
3992 game_status = MAINMENU;
4008 // LoadScore(level_nr);
4010 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
4011 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
4014 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4016 if (game_mm.score > highscore[k].Score)
4018 /* player has made it to the hall of fame */
4020 if (k < MAX_SCORE_ENTRIES - 1)
4022 int m = MAX_SCORE_ENTRIES - 1;
4025 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4026 if (!strcmp(setup.player_name, highscore[l].Name))
4028 if (m == k) /* player's new highscore overwrites his old one */
4032 for (l = m; l>k; l--)
4034 strcpy(highscore[l].Name, highscore[l - 1].Name);
4035 highscore[l].Score = highscore[l - 1].Score;
4042 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4043 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4044 highscore[k].Score = game_mm.score;
4051 else if (!strncmp(setup.player_name, highscore[k].Name,
4052 MAX_PLAYER_NAME_LEN))
4053 break; /* player already there with a higher score */
4058 // if (position >= 0)
4059 // SaveScore(level_nr);
4064 static void InitMovingField_MM(int x, int y, int direction)
4066 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4067 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4069 MovDir[x][y] = direction;
4070 MovDir[newx][newy] = direction;
4072 if (Feld[newx][newy] == EL_EMPTY)
4073 Feld[newx][newy] = EL_BLOCKED;
4076 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4078 int direction = MovDir[x][y];
4079 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4080 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4086 static void Blocked2Moving_MM(int x, int y,
4087 int *comes_from_x, int *comes_from_y)
4089 int oldx = x, oldy = y;
4090 int direction = MovDir[x][y];
4092 if (direction == MV_LEFT)
4094 else if (direction == MV_RIGHT)
4096 else if (direction == MV_UP)
4098 else if (direction == MV_DOWN)
4101 *comes_from_x = oldx;
4102 *comes_from_y = oldy;
4105 static int MovingOrBlocked2Element_MM(int x, int y)
4107 int element = Feld[x][y];
4109 if (element == EL_BLOCKED)
4113 Blocked2Moving_MM(x, y, &oldx, &oldy);
4115 return Feld[oldx][oldy];
4122 static void RemoveField(int x, int y)
4124 Feld[x][y] = EL_EMPTY;
4131 static void RemoveMovingField_MM(int x, int y)
4133 int oldx = x, oldy = y, newx = x, newy = y;
4135 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4138 if (IS_MOVING(x, y))
4140 Moving2Blocked_MM(x, y, &newx, &newy);
4141 if (Feld[newx][newy] != EL_BLOCKED)
4144 else if (Feld[x][y] == EL_BLOCKED)
4146 Blocked2Moving_MM(x, y, &oldx, &oldy);
4147 if (!IS_MOVING(oldx, oldy))
4151 Feld[oldx][oldy] = EL_EMPTY;
4152 Feld[newx][newy] = EL_EMPTY;
4153 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4154 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4156 DrawLevelField_MM(oldx, oldy);
4157 DrawLevelField_MM(newx, newy);
4160 void PlaySoundLevel(int x, int y, int sound_nr)
4162 int sx = SCREENX(x), sy = SCREENY(y);
4164 int silence_distance = 8;
4166 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4167 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4170 if (!IN_LEV_FIELD(x, y) ||
4171 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4172 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4175 volume = SOUND_MAX_VOLUME;
4178 stereo = (sx - SCR_FIELDX/2) * 12;
4180 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4181 if (stereo > SOUND_MAX_RIGHT)
4182 stereo = SOUND_MAX_RIGHT;
4183 if (stereo < SOUND_MAX_LEFT)
4184 stereo = SOUND_MAX_LEFT;
4187 if (!IN_SCR_FIELD(sx, sy))
4189 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4190 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4192 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4195 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4198 static void RaiseScore_MM(int value)
4200 game_mm.score += value;
4203 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4208 void RaiseScoreElement_MM(int element)
4213 case EL_PACMAN_RIGHT:
4215 case EL_PACMAN_LEFT:
4216 case EL_PACMAN_DOWN:
4217 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4221 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4226 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4230 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4239 /* ------------------------------------------------------------------------- */
4240 /* Mirror Magic game engine snapshot handling functions */
4241 /* ------------------------------------------------------------------------- */
4243 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4247 engine_snapshot_mm.game_mm = game_mm;
4248 engine_snapshot_mm.laser = laser;
4250 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4252 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4254 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4255 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4256 engine_snapshot_mm.Box[x][y] = Box[x][y];
4257 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4258 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4262 engine_snapshot_mm.LX = LX;
4263 engine_snapshot_mm.LY = LY;
4264 engine_snapshot_mm.XS = XS;
4265 engine_snapshot_mm.YS = YS;
4266 engine_snapshot_mm.ELX = ELX;
4267 engine_snapshot_mm.ELY = ELY;
4268 engine_snapshot_mm.CT = CT;
4269 engine_snapshot_mm.Ct = Ct;
4271 engine_snapshot_mm.last_LX = last_LX;
4272 engine_snapshot_mm.last_LY = last_LY;
4273 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4274 engine_snapshot_mm.hold_x = hold_x;
4275 engine_snapshot_mm.hold_y = hold_y;
4276 engine_snapshot_mm.pacman_nr = pacman_nr;
4278 engine_snapshot_mm.rotate_delay = rotate_delay;
4279 engine_snapshot_mm.pacman_delay = pacman_delay;
4280 engine_snapshot_mm.energy_delay = energy_delay;
4281 engine_snapshot_mm.overload_delay = overload_delay;
4284 void LoadEngineSnapshotValues_MM()
4288 /* stored engine snapshot buffers already restored at this point */
4290 game_mm = engine_snapshot_mm.game_mm;
4291 laser = engine_snapshot_mm.laser;
4293 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4295 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4297 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4298 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4299 Box[x][y] = engine_snapshot_mm.Box[x][y];
4300 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4301 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4305 LX = engine_snapshot_mm.LX;
4306 LY = engine_snapshot_mm.LY;
4307 XS = engine_snapshot_mm.XS;
4308 YS = engine_snapshot_mm.YS;
4309 ELX = engine_snapshot_mm.ELX;
4310 ELY = engine_snapshot_mm.ELY;
4311 CT = engine_snapshot_mm.CT;
4312 Ct = engine_snapshot_mm.Ct;
4314 last_LX = engine_snapshot_mm.last_LX;
4315 last_LY = engine_snapshot_mm.last_LY;
4316 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4317 hold_x = engine_snapshot_mm.hold_x;
4318 hold_y = engine_snapshot_mm.hold_y;
4319 pacman_nr = engine_snapshot_mm.pacman_nr;
4321 rotate_delay = engine_snapshot_mm.rotate_delay;
4322 pacman_delay = engine_snapshot_mm.pacman_delay;
4323 energy_delay = engine_snapshot_mm.energy_delay;
4324 overload_delay = engine_snapshot_mm.overload_delay;
4326 RedrawPlayfield_MM(TRUE);
4329 static int getAngleFromTouchDelta(int dx, int dy, int base)
4331 double pi = 3.141592653;
4332 double rad = atan2((double)-dy, (double)dx);
4333 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4334 double deg = rad2 * 180.0 / pi;
4336 return (int)(deg * base / 360.0 + 0.5) % base;
4339 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4341 // calculate start (source) position to be at the middle of the tile
4342 int src_mx = cSX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4343 int src_my = cSY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4344 int dx = dst_mx - src_mx;
4345 int dy = dst_my - src_my;
4354 if (!IN_LEV_FIELD(x, y))
4357 element = Feld[x][y];
4359 if (!IS_MCDUFFIN(element) &&
4360 !IS_MIRROR(element) &&
4361 !IS_BEAMER(element) &&
4362 !IS_POLAR(element) &&
4363 !IS_POLAR_CROSS(element) &&
4364 !IS_DF_MIRROR(element))
4367 angle_old = get_element_angle(element);
4369 if (IS_MCDUFFIN(element))
4371 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4372 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4373 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4374 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4377 else if (IS_MIRROR(element) ||
4378 IS_DF_MIRROR(element))
4380 for (i = 0; i < laser.num_damages; i++)
4382 if (laser.damage[i].x == x &&
4383 laser.damage[i].y == y &&
4384 ObjHit(x, y, HIT_POS_CENTER))
4386 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4387 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4394 if (angle_new == -1)
4396 if (IS_MIRROR(element) ||
4397 IS_DF_MIRROR(element) ||
4401 if (IS_POLAR_CROSS(element))
4404 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4407 button = (angle_new == angle_old ? 0 :
4408 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4409 MB_LEFTBUTTON : MB_RIGHTBUTTON);