1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
18 /* graphic position values for game controls */
19 #define ENERGY_XSIZE 32
20 #define ENERGY_YSIZE MAX_LASER_ENERGY
21 #define OVERLOAD_XSIZE ENERGY_XSIZE
22 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
24 /* values for Explode_MM() */
25 #define EX_PHASE_START 0
30 /* special positions in the game control window (relative to control window) */
39 #define XX_OVERLOAD 60
40 #define YY_OVERLOAD YY_ENERGY
42 /* special positions in the game control window (relative to main window) */
43 #define DX_LEVEL (DX + XX_LEVEL)
44 #define DY_LEVEL (DY + YY_LEVEL)
45 #define DX_KETTLES (DX + XX_KETTLES)
46 #define DY_KETTLES (DY + YY_KETTLES)
47 #define DX_SCORE (DX + XX_SCORE)
48 #define DY_SCORE (DY + YY_SCORE)
49 #define DX_ENERGY (DX + XX_ENERGY)
50 #define DY_ENERGY (DY + YY_ENERGY)
51 #define DX_OVERLOAD (DX + XX_OVERLOAD)
52 #define DY_OVERLOAD (DY + YY_OVERLOAD)
54 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
55 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
57 /* game button identifiers */
58 #define GAME_CTRL_ID_LEFT 0
59 #define GAME_CTRL_ID_MIDDLE 1
60 #define GAME_CTRL_ID_RIGHT 2
62 #define NUM_GAME_BUTTONS 3
64 /* values for DrawLaser() */
65 #define DL_LASER_DISABLED 0
66 #define DL_LASER_ENABLED 1
68 /* values for 'click_delay_value' in ClickElement() */
69 #define CLICK_DELAY_FIRST 12 /* delay (frames) after first click */
70 #define CLICK_DELAY 6 /* delay (frames) for pressed butten */
72 #define AUTO_ROTATE_DELAY CLICK_DELAY
73 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
74 #define NUM_INIT_CYCLE_STEPS 16
75 #define PACMAN_MOVE_DELAY 12
76 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
77 #define HEALTH_DEC_DELAY 3
78 #define HEALTH_INC_DELAY 9
79 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
81 #define BEGIN_NO_HEADLESS \
83 boolean last_headless = program.headless; \
85 program.headless = FALSE; \
87 #define END_NO_HEADLESS \
88 program.headless = last_headless; \
91 /* forward declaration for internal use */
92 static int MovingOrBlocked2Element_MM(int, int);
93 static void Bang_MM(int, int);
94 static void RaiseScore_MM(int);
95 static void RemoveMovingField_MM(int, int);
96 static void InitMovingField_MM(int, int, int);
97 static void ContinueMoving_MM(int, int);
98 static void Moving2Blocked_MM(int, int, int *, int *);
100 /* bitmap for laser beam detection */
101 static Bitmap *laser_bitmap = NULL;
103 /* variables for laser control */
104 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
105 static int hold_x = -1, hold_y = -1;
107 /* variables for pacman control */
108 static int pacman_nr = -1;
110 /* various game engine delay counters */
111 static unsigned int rotate_delay = 0;
112 static unsigned int pacman_delay = 0;
113 static unsigned int energy_delay = 0;
114 static unsigned int overload_delay = 0;
116 /* element masks for scanning pixels of MM elements */
117 static const char mm_masks[10][16][16 + 1] =
301 static int get_element_angle(int element)
303 int element_phase = get_element_phase(element);
305 if (IS_MIRROR_FIXED(element) ||
306 IS_MCDUFFIN(element) ||
308 IS_RECEIVER(element))
309 return 4 * element_phase;
311 return element_phase;
314 static int get_opposite_angle(int angle)
316 int opposite_angle = angle + ANG_RAY_180;
318 /* make sure "opposite_angle" is in valid interval [0, 15] */
319 return (opposite_angle + 16) % 16;
322 static int get_mirrored_angle(int laser_angle, int mirror_angle)
324 int reflected_angle = 16 - laser_angle + mirror_angle;
326 /* make sure "reflected_angle" is in valid interval [0, 15] */
327 return (reflected_angle + 16) % 16;
330 static void DrawLaserLines(struct XY *points, int num_points, int mode)
332 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
333 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
335 DrawLines(drawto, points, num_points, pixel_drawto);
339 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
344 static boolean CheckLaserPixel(int x, int y)
350 pixel = ReadPixel(laser_bitmap, x, y);
354 return (pixel == WHITE_PIXEL);
357 static void InitMovDir_MM(int x, int y)
359 int element = Feld[x][y];
360 static int direction[3][4] =
362 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
363 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
364 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
369 case EL_PACMAN_RIGHT:
373 Feld[x][y] = EL_PACMAN;
374 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
382 static void InitField(int x, int y, boolean init_game)
384 int element = Feld[x][y];
389 Feld[x][y] = EL_EMPTY;
394 if (native_mm_level.auto_count_kettles)
395 game_mm.kettles_still_needed++;
398 case EL_LIGHTBULB_OFF:
399 game_mm.lights_still_needed++;
403 if (IS_MIRROR(element) ||
404 IS_BEAMER_OLD(element) ||
405 IS_BEAMER(element) ||
407 IS_POLAR_CROSS(element) ||
408 IS_DF_MIRROR(element) ||
409 IS_DF_MIRROR_AUTO(element) ||
410 IS_GRID_STEEL_AUTO(element) ||
411 IS_GRID_WOOD_AUTO(element) ||
412 IS_FIBRE_OPTIC(element))
414 if (IS_BEAMER_OLD(element))
416 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
417 element = Feld[x][y];
420 if (!IS_FIBRE_OPTIC(element))
422 static int steps_grid_auto = 0;
424 if (game_mm.num_cycle == 0) /* initialize cycle steps for grids */
425 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
427 if (IS_GRID_STEEL_AUTO(element) ||
428 IS_GRID_WOOD_AUTO(element))
429 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
431 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
433 game_mm.cycle[game_mm.num_cycle].x = x;
434 game_mm.cycle[game_mm.num_cycle].y = y;
438 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
440 int beamer_nr = BEAMER_NR(element);
441 int nr = laser.beamer[beamer_nr][0].num;
443 laser.beamer[beamer_nr][nr].x = x;
444 laser.beamer[beamer_nr][nr].y = y;
445 laser.beamer[beamer_nr][nr].num = 1;
448 else if (IS_PACMAN(element))
452 else if (IS_MCDUFFIN(element) || IS_LASER(element))
454 laser.start_edge.x = x;
455 laser.start_edge.y = y;
456 laser.start_angle = get_element_angle(element);
463 static void InitCycleElements_RotateSingleStep()
467 if (game_mm.num_cycle == 0) /* no elements to cycle */
470 for (i = 0; i < game_mm.num_cycle; i++)
472 int x = game_mm.cycle[i].x;
473 int y = game_mm.cycle[i].y;
474 int step = SIGN(game_mm.cycle[i].steps);
475 int last_element = Feld[x][y];
476 int next_element = get_rotated_element(last_element, step);
478 if (!game_mm.cycle[i].steps)
481 Feld[x][y] = next_element;
484 game_mm.cycle[i].steps -= step;
488 static void InitLaser()
490 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
491 int step = (IS_LASER(start_element) ? 4 : 0);
493 LX = laser.start_edge.x * TILEX;
494 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
497 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
499 LY = laser.start_edge.y * TILEY;
500 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
501 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
505 XS = 2 * Step[laser.start_angle].x;
506 YS = 2 * Step[laser.start_angle].y;
508 laser.current_angle = laser.start_angle;
510 laser.num_damages = 0;
512 laser.num_beamers = 0;
513 laser.beamer_edge[0] = 0;
515 laser.dest_element = EL_EMPTY;
518 AddLaserEdge(LX, LY); /* set laser starting edge */
520 pen_ray = GetPixelFromRGB(window,
521 native_mm_level.laser_red * 0xFF,
522 native_mm_level.laser_green * 0xFF,
523 native_mm_level.laser_blue * 0xFF);
526 void InitGameEngine_MM()
532 /* initialize laser bitmap to current playfield (screen) size */
533 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
534 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
538 /* set global game control values */
539 game_mm.num_cycle = 0;
540 game_mm.num_pacman = 0;
543 game_mm.energy_left = 0; // later set to "native_mm_level.time"
544 game_mm.kettles_still_needed =
545 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
546 game_mm.lights_still_needed = 0;
547 game_mm.num_keys = 0;
549 game_mm.level_solved = FALSE;
550 game_mm.game_over = FALSE;
551 game_mm.game_over_cause = 0;
553 game_mm.laser_overload_value = 0;
555 /* set global laser control values (must be set before "InitLaser()") */
556 laser.start_edge.x = 0;
557 laser.start_edge.y = 0;
558 laser.start_angle = 0;
560 for (i = 0; i < MAX_NUM_BEAMERS; i++)
561 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
563 laser.overloaded = FALSE;
564 laser.overload_value = 0;
565 laser.fuse_off = FALSE;
566 laser.fuse_x = laser.fuse_y = -1;
568 laser.dest_element = EL_EMPTY;
587 ClickElement(-1, -1, -1);
589 for (x = 0; x < lev_fieldx; x++)
591 for (y = 0; y < lev_fieldy; y++)
593 Feld[x][y] = Ur[x][y];
594 Hit[x][y] = Box[x][y] = 0;
596 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
597 Store[x][y] = Store2[x][y] = 0;
601 InitField(x, y, TRUE);
606 CloseDoor(DOOR_CLOSE_1);
612 void InitGameActions_MM()
614 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
615 int cycle_steps_done = 0;
621 /* copy default game door content to main double buffer */
622 BlitBitmap(pix[PIX_DOOR], drawto,
623 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
627 DrawText(DX_LEVEL, DY_LEVEL,
628 int2str(level_nr, 2), FONT_TEXT_2);
629 DrawText(DX_KETTLES, DY_KETTLES,
630 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
631 DrawText(DX_SCORE, DY_SCORE,
632 int2str(game_mm.score, 4), FONT_TEXT_2);
641 /* copy actual game door content to door double buffer for OpenDoor() */
642 BlitBitmap(drawto, pix[PIX_DB_DOOR],
643 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
647 OpenDoor(DOOR_OPEN_ALL);
650 for (i = 0; i <= num_init_game_frames; i++)
652 if (i == num_init_game_frames)
653 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
654 else if (setup.sound_loops)
655 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
657 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
659 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
661 UpdateAndDisplayGameControlValues();
663 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
665 InitCycleElements_RotateSingleStep();
675 if (setup.quick_doors)
681 if (setup.sound_music && num_bg_loops)
682 PlayMusic(level_nr % num_bg_loops);
688 void AddLaserEdge(int lx, int ly)
690 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
692 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
697 laser.edge[laser.num_edges].x = SX + 2 + lx;
698 laser.edge[laser.num_edges].y = SY + 2 + ly;
704 void AddDamagedField(int ex, int ey)
706 laser.damage[laser.num_damages].is_mirror = FALSE;
707 laser.damage[laser.num_damages].angle = laser.current_angle;
708 laser.damage[laser.num_damages].edge = laser.num_edges;
709 laser.damage[laser.num_damages].x = ex;
710 laser.damage[laser.num_damages].y = ey;
720 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
721 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
723 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
729 static int getMaskFromElement(int element)
731 if (IS_GRID(element))
732 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
733 else if (IS_MCDUFFIN(element))
734 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
735 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
736 return IMG_MM_MASK_RECTANGLE;
738 return IMG_MM_MASK_CIRCLE;
746 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
747 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
750 /* follow laser beam until it hits something (at least the screen border) */
751 while (hit_mask == HIT_MASK_NO_HIT)
757 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
758 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
760 printf("ScanPixel: touched screen border!\n");
766 for (i = 0; i < 4; i++)
768 int px = LX + (i % 2) * 2;
769 int py = LY + (i / 2) * 2;
772 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
773 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
776 if (IN_LEV_FIELD(lx, ly))
778 int element = Feld[lx][ly];
780 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
784 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
786 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
788 pixel = ((element & (1 << pos)) ? 1 : 0);
792 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
794 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
799 pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
800 SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
803 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
804 hit_mask |= (1 << i);
807 if (hit_mask == HIT_MASK_NO_HIT)
809 /* hit nothing -- go on with another step */
821 int end = 0, rf = laser.num_edges;
823 /* do not scan laser again after the game was lost for whatever reason */
824 if (game_mm.game_over)
827 laser.overloaded = FALSE;
828 laser.stops_inside_element = FALSE;
830 DrawLaser(0, DL_LASER_ENABLED);
833 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
841 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
844 laser.overloaded = TRUE;
849 hit_mask = ScanPixel();
852 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
856 /* hit something -- check out what it was */
857 ELX = (LX + XS) / TILEX;
858 ELY = (LY + YS) / TILEY;
861 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
862 hit_mask, LX, LY, ELX, ELY);
865 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
868 laser.dest_element = element;
873 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
875 /* we have hit the top-right and bottom-left element --
876 choose the bottom-left one */
877 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
878 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
879 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
880 ELX = (LX - 2) / TILEX;
881 ELY = (LY + 2) / TILEY;
884 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
886 /* we have hit the top-left and bottom-right element --
887 choose the top-left one */
888 /* !!! SEE ABOVE !!! */
889 ELX = (LX - 2) / TILEX;
890 ELY = (LY - 2) / TILEY;
894 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
895 hit_mask, LX, LY, ELX, ELY);
898 element = Feld[ELX][ELY];
899 laser.dest_element = element;
902 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
905 LX % TILEX, LY % TILEY,
910 if (!IN_LEV_FIELD(ELX, ELY))
911 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
914 if (element == EL_EMPTY)
916 if (!HitOnlyAnEdge(element, hit_mask))
919 else if (element == EL_FUSE_ON)
921 if (HitPolarizer(element, hit_mask))
924 else if (IS_GRID(element) || IS_DF_GRID(element))
926 if (HitPolarizer(element, hit_mask))
929 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
930 element == EL_GATE_STONE || element == EL_GATE_WOOD)
932 if (HitBlock(element, hit_mask))
939 else if (IS_MCDUFFIN(element))
941 if (HitLaserSource(element, hit_mask))
944 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
945 IS_RECEIVER(element))
947 if (HitLaserDestination(element, hit_mask))
950 else if (IS_WALL(element))
952 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
954 if (HitReflectingWalls(element, hit_mask))
959 if (HitAbsorbingWalls(element, hit_mask))
965 if (HitElement(element, hit_mask))
970 DrawLaser(rf - 1, DL_LASER_ENABLED);
971 rf = laser.num_edges;
975 if (laser.dest_element != Feld[ELX][ELY])
977 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
978 laser.dest_element, Feld[ELX][ELY]);
982 if (!end && !laser.stops_inside_element && !StepBehind())
985 printf("ScanLaser: Go one step back\n");
991 AddLaserEdge(LX, LY);
995 DrawLaser(rf - 1, DL_LASER_ENABLED);
997 Ct = CT = FrameCounter;
1000 if (!IN_LEV_FIELD(ELX, ELY))
1001 printf("WARNING! (2) %d, %d\n", ELX, ELY);
1005 void DrawLaserExt(int start_edge, int num_edges, int mode)
1011 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
1012 start_edge, num_edges, mode);
1017 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
1024 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
1030 if (mode == DL_LASER_DISABLED)
1032 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
1036 /* now draw the laser to the backbuffer and (if enabled) to the screen */
1037 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1039 redraw_mask |= REDRAW_FIELD;
1041 if (mode == DL_LASER_ENABLED)
1044 /* after the laser was deleted, the "damaged" graphics must be restored */
1045 if (laser.num_damages)
1047 int damage_start = 0;
1050 /* determine the starting edge, from which graphics need to be restored */
1053 for (i = 0; i < laser.num_damages; i++)
1055 if (laser.damage[i].edge == start_edge + 1)
1064 /* restore graphics from this starting edge to the end of damage list */
1065 for (i = damage_start; i < laser.num_damages; i++)
1067 int lx = laser.damage[i].x;
1068 int ly = laser.damage[i].y;
1069 int element = Feld[lx][ly];
1071 if (Hit[lx][ly] == laser.damage[i].edge)
1072 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1075 if (Box[lx][ly] == laser.damage[i].edge)
1078 if (IS_DRAWABLE(element))
1079 DrawField_MM(lx, ly);
1082 elx = laser.damage[damage_start].x;
1083 ely = laser.damage[damage_start].y;
1084 element = Feld[elx][ely];
1087 if (IS_BEAMER(element))
1091 for (i = 0; i < laser.num_beamers; i++)
1092 printf("-> %d\n", laser.beamer_edge[i]);
1093 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
1094 mode, elx, ely, Hit[elx][ely], start_edge);
1095 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
1096 get_element_angle(element), laser.damage[damage_start].angle);
1100 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1101 laser.num_beamers > 0 &&
1102 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1104 /* element is outgoing beamer */
1105 laser.num_damages = damage_start + 1;
1107 if (IS_BEAMER(element))
1108 laser.current_angle = get_element_angle(element);
1112 /* element is incoming beamer or other element */
1113 laser.num_damages = damage_start;
1114 laser.current_angle = laser.damage[laser.num_damages].angle;
1119 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
1121 elx = laser.start_edge.x;
1122 ely = laser.start_edge.y;
1123 element = Feld[elx][ely];
1126 laser.num_edges = start_edge + 1;
1127 if (start_edge == 0)
1128 laser.current_angle = laser.start_angle;
1130 LX = laser.edge[start_edge].x - (SX + 2);
1131 LY = laser.edge[start_edge].y - (SY + 2);
1132 XS = 2 * Step[laser.current_angle].x;
1133 YS = 2 * Step[laser.current_angle].y;
1136 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
1142 if (IS_BEAMER(element) ||
1143 IS_FIBRE_OPTIC(element) ||
1144 IS_PACMAN(element) ||
1145 IS_POLAR(element) ||
1146 IS_POLAR_CROSS(element) ||
1147 element == EL_FUSE_ON)
1152 printf("element == %d\n", element);
1155 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
1156 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1160 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1161 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1162 (laser.num_beamers == 0 ||
1163 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1165 /* element is incoming beamer or other element */
1166 step_size = -step_size;
1171 if (IS_BEAMER(element))
1173 printf("start_edge == %d, laser.beamer_edge == %d\n",
1174 start_edge, laser.beamer_edge);
1178 LX += step_size * XS;
1179 LY += step_size * YS;
1181 else if (element != EL_EMPTY)
1190 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
1195 void DrawLaser(int start_edge, int mode)
1197 if (laser.num_edges - start_edge < 0)
1199 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
1204 /* check if laser is interrupted by beamer element */
1205 if (laser.num_beamers > 0 &&
1206 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1208 if (mode == DL_LASER_ENABLED)
1211 int tmp_start_edge = start_edge;
1213 /* draw laser segments forward from the start to the last beamer */
1214 for (i = 0; i < laser.num_beamers; i++)
1216 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1218 if (tmp_num_edges <= 0)
1222 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
1223 i, laser.beamer_edge[i], tmp_start_edge);
1226 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1228 tmp_start_edge = laser.beamer_edge[i];
1231 /* draw last segment from last beamer to the end */
1232 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1238 int last_num_edges = laser.num_edges;
1239 int num_beamers = laser.num_beamers;
1241 /* delete laser segments backward from the end to the first beamer */
1242 for (i = num_beamers-1; i >= 0; i--)
1244 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1246 if (laser.beamer_edge[i] - start_edge <= 0)
1249 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1251 last_num_edges = laser.beamer_edge[i];
1252 laser.num_beamers--;
1256 if (last_num_edges - start_edge <= 0)
1257 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1258 last_num_edges, start_edge);
1261 // special case when rotating first beamer: delete laser edge on beamer
1262 // (but do not start scanning on previous edge to prevent mirror sound)
1263 if (last_num_edges - start_edge == 1 && start_edge > 0)
1264 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1266 /* delete first segment from start to the first beamer */
1267 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1272 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1276 boolean HitElement(int element, int hit_mask)
1278 if (HitOnlyAnEdge(element, hit_mask))
1281 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1282 element = MovingOrBlocked2Element_MM(ELX, ELY);
1285 printf("HitElement (1): element == %d\n", element);
1289 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1290 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1292 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1295 AddDamagedField(ELX, ELY);
1297 /* this is more precise: check if laser would go through the center */
1298 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1300 /* skip the whole element before continuing the scan */
1306 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1308 if (LX/TILEX > ELX || LY/TILEY > ELY)
1310 /* skipping scan positions to the right and down skips one scan
1311 position too much, because this is only the top left scan position
1312 of totally four scan positions (plus one to the right, one to the
1313 bottom and one to the bottom right) */
1323 printf("HitElement (2): element == %d\n", element);
1326 if (LX + 5 * XS < 0 ||
1336 printf("HitElement (3): element == %d\n", element);
1339 if (IS_POLAR(element) &&
1340 ((element - EL_POLAR_START) % 2 ||
1341 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1343 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1345 laser.num_damages--;
1350 if (IS_POLAR_CROSS(element) &&
1351 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1353 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1355 laser.num_damages--;
1360 if (!IS_BEAMER(element) &&
1361 !IS_FIBRE_OPTIC(element) &&
1362 !IS_GRID_WOOD(element) &&
1363 element != EL_FUEL_EMPTY)
1366 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1367 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1369 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1372 LX = ELX * TILEX + 14;
1373 LY = ELY * TILEY + 14;
1375 AddLaserEdge(LX, LY);
1378 if (IS_MIRROR(element) ||
1379 IS_MIRROR_FIXED(element) ||
1380 IS_POLAR(element) ||
1381 IS_POLAR_CROSS(element) ||
1382 IS_DF_MIRROR(element) ||
1383 IS_DF_MIRROR_AUTO(element) ||
1384 element == EL_PRISM ||
1385 element == EL_REFRACTOR)
1387 int current_angle = laser.current_angle;
1390 laser.num_damages--;
1392 AddDamagedField(ELX, ELY);
1394 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1397 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1399 if (IS_MIRROR(element) ||
1400 IS_MIRROR_FIXED(element) ||
1401 IS_DF_MIRROR(element) ||
1402 IS_DF_MIRROR_AUTO(element))
1403 laser.current_angle = get_mirrored_angle(laser.current_angle,
1404 get_element_angle(element));
1406 if (element == EL_PRISM || element == EL_REFRACTOR)
1407 laser.current_angle = RND(16);
1409 XS = 2 * Step[laser.current_angle].x;
1410 YS = 2 * Step[laser.current_angle].y;
1412 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1417 LX += step_size * XS;
1418 LY += step_size * YS;
1421 /* draw sparkles on mirror */
1422 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1423 current_angle != laser.current_angle)
1425 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1429 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1430 current_angle != laser.current_angle)
1431 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1434 (get_opposite_angle(laser.current_angle) ==
1435 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1437 return (laser.overloaded ? TRUE : FALSE);
1440 if (element == EL_FUEL_FULL)
1442 laser.stops_inside_element = TRUE;
1447 if (element == EL_BOMB || element == EL_MINE)
1449 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1451 if (element == EL_MINE)
1452 laser.overloaded = TRUE;
1455 if (element == EL_KETTLE ||
1456 element == EL_CELL ||
1457 element == EL_KEY ||
1458 element == EL_LIGHTBALL ||
1459 element == EL_PACMAN ||
1462 if (!IS_PACMAN(element))
1465 if (element == EL_PACMAN)
1468 if (element == EL_KETTLE || element == EL_CELL)
1470 if (game_mm.kettles_still_needed > 0)
1471 game_mm.kettles_still_needed--;
1475 if (game_mm.kettles_still_needed == 0)
1477 int exit_element = (element == EL_KETTLE ? EL_EXIT_OPEN : EL_RECEIVER);
1479 static int xy[4][2] =
1487 PlayLevelSound_MM(ELX, ELY, exit_element, MM_ACTION_OPENING);
1489 for (y = 0; y < lev_fieldy; y++)
1491 for (x = 0; x < lev_fieldx; x++)
1493 /* initiate opening animation of exit door */
1494 if (Feld[x][y] == EL_EXIT_CLOSED)
1495 Feld[x][y] = EL_EXIT_OPENING;
1497 /* remove field that blocks receiver */
1498 if (IS_RECEIVER(Feld[x][y]))
1500 int phase = Feld[x][y] - EL_RECEIVER_START;
1501 int blocking_x, blocking_y;
1503 blocking_x = x + xy[phase][0];
1504 blocking_y = y + xy[phase][1];
1506 if (IN_LEV_FIELD(blocking_x, blocking_y))
1508 Feld[blocking_x][blocking_y] = EL_EMPTY;
1510 DrawField_MM(blocking_x, blocking_y);
1516 DrawLaser(0, DL_LASER_ENABLED);
1519 else if (element == EL_KEY)
1523 else if (element == EL_LIGHTBALL)
1527 else if (IS_PACMAN(element))
1529 DeletePacMan(ELX, ELY);
1536 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1538 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1540 DrawLaser(0, DL_LASER_ENABLED);
1542 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1544 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1545 game_mm.lights_still_needed--;
1549 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1550 game_mm.lights_still_needed++;
1553 DrawField_MM(ELX, ELY);
1554 DrawLaser(0, DL_LASER_ENABLED);
1559 laser.stops_inside_element = TRUE;
1565 printf("HitElement (4): element == %d\n", element);
1568 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1569 laser.num_beamers < MAX_NUM_BEAMERS &&
1570 laser.beamer[BEAMER_NR(element)][1].num)
1572 int beamer_angle = get_element_angle(element);
1573 int beamer_nr = BEAMER_NR(element);
1577 printf("HitElement (BEAMER): element == %d\n", element);
1580 laser.num_damages--;
1582 if (IS_FIBRE_OPTIC(element) ||
1583 laser.current_angle == get_opposite_angle(beamer_angle))
1587 LX = ELX * TILEX + 14;
1588 LY = ELY * TILEY + 14;
1590 AddLaserEdge(LX, LY);
1591 AddDamagedField(ELX, ELY);
1593 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1596 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1598 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1599 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1600 ELX = laser.beamer[beamer_nr][pos].x;
1601 ELY = laser.beamer[beamer_nr][pos].y;
1602 LX = ELX * TILEX + 14;
1603 LY = ELY * TILEY + 14;
1605 if (IS_BEAMER(element))
1607 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1608 XS = 2 * Step[laser.current_angle].x;
1609 YS = 2 * Step[laser.current_angle].y;
1612 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1614 AddLaserEdge(LX, LY);
1615 AddDamagedField(ELX, ELY);
1617 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1620 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1622 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1627 LX += step_size * XS;
1628 LY += step_size * YS;
1630 laser.num_beamers++;
1639 boolean HitOnlyAnEdge(int element, int hit_mask)
1641 /* check if the laser hit only the edge of an element and, if so, go on */
1644 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1647 if ((hit_mask == HIT_MASK_TOPLEFT ||
1648 hit_mask == HIT_MASK_TOPRIGHT ||
1649 hit_mask == HIT_MASK_BOTTOMLEFT ||
1650 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1651 laser.current_angle % 4) /* angle is not 90° */
1655 if (hit_mask == HIT_MASK_TOPLEFT)
1660 else if (hit_mask == HIT_MASK_TOPRIGHT)
1665 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1670 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1676 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1682 printf("[HitOnlyAnEdge() == TRUE]\n");
1689 printf("[HitOnlyAnEdge() == FALSE]\n");
1695 boolean HitPolarizer(int element, int hit_mask)
1697 if (HitOnlyAnEdge(element, hit_mask))
1700 if (IS_DF_GRID(element))
1702 int grid_angle = get_element_angle(element);
1705 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1706 grid_angle, laser.current_angle);
1709 AddLaserEdge(LX, LY);
1710 AddDamagedField(ELX, ELY);
1713 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1715 if (laser.current_angle == grid_angle ||
1716 laser.current_angle == get_opposite_angle(grid_angle))
1718 /* skip the whole element before continuing the scan */
1724 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1726 if (LX/TILEX > ELX || LY/TILEY > ELY)
1728 /* skipping scan positions to the right and down skips one scan
1729 position too much, because this is only the top left scan position
1730 of totally four scan positions (plus one to the right, one to the
1731 bottom and one to the bottom right) */
1737 AddLaserEdge(LX, LY);
1743 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1745 LX / TILEX, LY / TILEY,
1746 LX % TILEX, LY % TILEY);
1751 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1753 return HitReflectingWalls(element, hit_mask);
1757 return HitAbsorbingWalls(element, hit_mask);
1760 else if (IS_GRID_STEEL(element))
1762 return HitReflectingWalls(element, hit_mask);
1764 else /* IS_GRID_WOOD */
1766 return HitAbsorbingWalls(element, hit_mask);
1772 boolean HitBlock(int element, int hit_mask)
1774 boolean check = FALSE;
1776 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1777 game_mm.num_keys == 0)
1780 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1783 int ex = ELX * TILEX + 14;
1784 int ey = ELY * TILEY + 14;
1788 for (i = 1; i < 32; i++)
1793 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1798 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1799 return HitAbsorbingWalls(element, hit_mask);
1803 AddLaserEdge(LX - XS, LY - YS);
1804 AddDamagedField(ELX, ELY);
1807 Box[ELX][ELY] = laser.num_edges;
1809 return HitReflectingWalls(element, hit_mask);
1812 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1814 int xs = XS / 2, ys = YS / 2;
1815 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1816 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1818 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1819 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1821 laser.overloaded = (element == EL_GATE_STONE);
1826 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1827 (hit_mask == HIT_MASK_TOP ||
1828 hit_mask == HIT_MASK_LEFT ||
1829 hit_mask == HIT_MASK_RIGHT ||
1830 hit_mask == HIT_MASK_BOTTOM))
1831 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1832 hit_mask == HIT_MASK_BOTTOM),
1833 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1834 hit_mask == HIT_MASK_RIGHT));
1835 AddLaserEdge(LX, LY);
1841 if (element == EL_GATE_STONE && Box[ELX][ELY])
1843 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1855 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1857 int xs = XS / 2, ys = YS / 2;
1858 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1859 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1861 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1862 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1864 laser.overloaded = (element == EL_BLOCK_STONE);
1869 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1870 (hit_mask == HIT_MASK_TOP ||
1871 hit_mask == HIT_MASK_LEFT ||
1872 hit_mask == HIT_MASK_RIGHT ||
1873 hit_mask == HIT_MASK_BOTTOM))
1874 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1875 hit_mask == HIT_MASK_BOTTOM),
1876 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1877 hit_mask == HIT_MASK_RIGHT));
1878 AddDamagedField(ELX, ELY);
1880 LX = ELX * TILEX + 14;
1881 LY = ELY * TILEY + 14;
1883 AddLaserEdge(LX, LY);
1885 laser.stops_inside_element = TRUE;
1893 boolean HitLaserSource(int element, int hit_mask)
1895 if (HitOnlyAnEdge(element, hit_mask))
1898 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1900 laser.overloaded = TRUE;
1905 boolean HitLaserDestination(int element, int hit_mask)
1907 if (HitOnlyAnEdge(element, hit_mask))
1910 if (element != EL_EXIT_OPEN &&
1911 !(IS_RECEIVER(element) &&
1912 game_mm.kettles_still_needed == 0 &&
1913 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1915 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1920 if (IS_RECEIVER(element) ||
1921 (IS_22_5_ANGLE(laser.current_angle) &&
1922 (ELX != (LX + 6 * XS) / TILEX ||
1923 ELY != (LY + 6 * YS) / TILEY ||
1932 LX = ELX * TILEX + 14;
1933 LY = ELY * TILEY + 14;
1935 laser.stops_inside_element = TRUE;
1938 AddLaserEdge(LX, LY);
1939 AddDamagedField(ELX, ELY);
1941 if (game_mm.lights_still_needed == 0)
1942 game_mm.level_solved = TRUE;
1947 boolean HitReflectingWalls(int element, int hit_mask)
1949 /* check if laser hits side of a wall with an angle that is not 90° */
1950 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1951 hit_mask == HIT_MASK_LEFT ||
1952 hit_mask == HIT_MASK_RIGHT ||
1953 hit_mask == HIT_MASK_BOTTOM))
1955 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1960 if (!IS_DF_GRID(element))
1961 AddLaserEdge(LX, LY);
1963 /* check if laser hits wall with an angle of 45° */
1964 if (!IS_22_5_ANGLE(laser.current_angle))
1966 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1969 laser.current_angle = get_mirrored_angle(laser.current_angle,
1972 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1975 laser.current_angle = get_mirrored_angle(laser.current_angle,
1979 AddLaserEdge(LX, LY);
1981 XS = 2 * Step[laser.current_angle].x;
1982 YS = 2 * Step[laser.current_angle].y;
1986 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1988 laser.current_angle = get_mirrored_angle(laser.current_angle,
1993 if (!IS_DF_GRID(element))
1994 AddLaserEdge(LX, LY);
1999 if (!IS_DF_GRID(element))
2000 AddLaserEdge(LX, LY + YS / 2);
2003 if (!IS_DF_GRID(element))
2004 AddLaserEdge(LX, LY);
2007 YS = 2 * Step[laser.current_angle].y;
2011 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2013 laser.current_angle = get_mirrored_angle(laser.current_angle,
2018 if (!IS_DF_GRID(element))
2019 AddLaserEdge(LX, LY);
2024 if (!IS_DF_GRID(element))
2025 AddLaserEdge(LX + XS / 2, LY);
2028 if (!IS_DF_GRID(element))
2029 AddLaserEdge(LX, LY);
2032 XS = 2 * Step[laser.current_angle].x;
2038 /* reflection at the edge of reflecting DF style wall */
2039 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2041 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2042 hit_mask == HIT_MASK_TOPRIGHT) ||
2043 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2044 hit_mask == HIT_MASK_TOPLEFT) ||
2045 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2046 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2047 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2048 hit_mask == HIT_MASK_BOTTOMRIGHT))
2051 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2052 ANG_MIRROR_135 : ANG_MIRROR_45);
2054 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2056 AddDamagedField(ELX, ELY);
2057 AddLaserEdge(LX, LY);
2059 laser.current_angle = get_mirrored_angle(laser.current_angle,
2067 AddLaserEdge(LX, LY);
2073 /* reflection inside an edge of reflecting DF style wall */
2074 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2076 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2077 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2078 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2079 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2080 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2081 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2082 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2083 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2086 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2087 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2088 ANG_MIRROR_135 : ANG_MIRROR_45);
2090 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2093 AddDamagedField(ELX, ELY);
2096 AddLaserEdge(LX - XS, LY - YS);
2097 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2098 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2100 laser.current_angle = get_mirrored_angle(laser.current_angle,
2108 AddLaserEdge(LX, LY);
2114 /* check if laser hits DF style wall with an angle of 90° */
2115 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2117 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2118 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2119 (IS_VERT_ANGLE(laser.current_angle) &&
2120 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2122 /* laser at last step touched nothing or the same side of the wall */
2123 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2125 AddDamagedField(ELX, ELY);
2132 last_hit_mask = hit_mask;
2139 if (!HitOnlyAnEdge(element, hit_mask))
2141 laser.overloaded = TRUE;
2149 boolean HitAbsorbingWalls(int element, int hit_mask)
2151 if (HitOnlyAnEdge(element, hit_mask))
2155 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2157 AddLaserEdge(LX - XS, LY - YS);
2164 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2166 AddLaserEdge(LX - XS, LY - YS);
2172 if (IS_WALL_WOOD(element) ||
2173 IS_DF_WALL_WOOD(element) ||
2174 IS_GRID_WOOD(element) ||
2175 IS_GRID_WOOD_FIXED(element) ||
2176 IS_GRID_WOOD_AUTO(element) ||
2177 element == EL_FUSE_ON ||
2178 element == EL_BLOCK_WOOD ||
2179 element == EL_GATE_WOOD)
2181 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2186 if (IS_WALL_ICE(element))
2190 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
2191 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
2193 /* check if laser hits wall with an angle of 90° */
2194 if (IS_90_ANGLE(laser.current_angle))
2195 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2197 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2201 for (i = 0; i < 4; i++)
2203 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2204 mask = 15 - (8 >> i);
2205 else if (ABS(XS) == 4 &&
2207 (XS > 0) == (i % 2) &&
2208 (YS < 0) == (i / 2))
2209 mask = 3 + (i / 2) * 9;
2210 else if (ABS(YS) == 4 &&
2212 (XS < 0) == (i % 2) &&
2213 (YS > 0) == (i / 2))
2214 mask = 5 + (i % 2) * 5;
2218 laser.wall_mask = mask;
2220 else if (IS_WALL_AMOEBA(element))
2222 int elx = (LX - 2 * XS) / TILEX;
2223 int ely = (LY - 2 * YS) / TILEY;
2224 int element2 = Feld[elx][ely];
2227 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2229 laser.dest_element = EL_EMPTY;
2237 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2238 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2240 if (IS_90_ANGLE(laser.current_angle))
2241 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2243 laser.dest_element = element2 | EL_WALL_AMOEBA;
2245 laser.wall_mask = mask;
2251 void OpenExit(int x, int y)
2255 if (!MovDelay[x][y]) /* next animation frame */
2256 MovDelay[x][y] = 4 * delay;
2258 if (MovDelay[x][y]) /* wait some time before next frame */
2263 phase = MovDelay[x][y] / delay;
2265 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2266 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2268 if (!MovDelay[x][y])
2270 Feld[x][y] = EL_EXIT_OPEN;
2276 void OpenSurpriseBall(int x, int y)
2280 if (!MovDelay[x][y]) /* next animation frame */
2281 MovDelay[x][y] = 50 * delay;
2283 if (MovDelay[x][y]) /* wait some time before next frame */
2287 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2290 int graphic = el2gfx(Store[x][y]);
2292 int dx = RND(26), dy = RND(26);
2294 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2296 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2297 SX + x * TILEX + dx, SY + y * TILEY + dy);
2299 MarkTileDirty(x, y);
2302 if (!MovDelay[x][y])
2304 Feld[x][y] = Store[x][y];
2313 void MeltIce(int x, int y)
2318 if (!MovDelay[x][y]) /* next animation frame */
2319 MovDelay[x][y] = frames * delay;
2321 if (MovDelay[x][y]) /* wait some time before next frame */
2324 int wall_mask = Store2[x][y];
2325 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2328 phase = frames - MovDelay[x][y] / delay - 1;
2330 if (!MovDelay[x][y])
2334 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2335 Store[x][y] = Store2[x][y] = 0;
2337 DrawWalls_MM(x, y, Feld[x][y]);
2339 if (Feld[x][y] == EL_WALL_ICE)
2340 Feld[x][y] = EL_EMPTY;
2342 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2343 if (laser.damage[i].is_mirror)
2347 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2349 DrawLaser(0, DL_LASER_DISABLED);
2353 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2355 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2357 laser.redraw = TRUE;
2362 void GrowAmoeba(int x, int y)
2367 if (!MovDelay[x][y]) /* next animation frame */
2368 MovDelay[x][y] = frames * delay;
2370 if (MovDelay[x][y]) /* wait some time before next frame */
2373 int wall_mask = Store2[x][y];
2374 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2377 phase = MovDelay[x][y] / delay;
2379 if (!MovDelay[x][y])
2381 Feld[x][y] = real_element;
2382 Store[x][y] = Store2[x][y] = 0;
2384 DrawWalls_MM(x, y, Feld[x][y]);
2385 DrawLaser(0, DL_LASER_ENABLED);
2387 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2389 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2394 static void Explode_MM(int x, int y, int phase, int mode)
2396 int num_phase = 9, delay = 2;
2397 int last_phase = num_phase * delay;
2398 int half_phase = (num_phase / 2) * delay;
2400 laser.redraw = TRUE;
2402 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2404 int center_element = Feld[x][y];
2406 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2408 /* put moving element to center field (and let it explode there) */
2409 center_element = MovingOrBlocked2Element_MM(x, y);
2410 RemoveMovingField_MM(x, y);
2412 Feld[x][y] = center_element;
2415 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2416 Store[x][y] = center_element;
2418 Store[x][y] = EL_EMPTY;
2420 Store2[x][y] = mode;
2421 Feld[x][y] = EL_EXPLODING_OPAQUE;
2422 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2428 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2430 if (phase == half_phase)
2432 Feld[x][y] = EL_EXPLODING_TRANSP;
2434 if (x == ELX && y == ELY)
2438 if (phase == last_phase)
2440 if (Store[x][y] == EL_BOMB)
2442 DrawLaser(0, DL_LASER_DISABLED);
2445 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2446 Store[x][y] = EL_EMPTY;
2448 game_mm.game_over = TRUE;
2449 game_mm.game_over_cause = GAME_OVER_BOMB;
2451 laser.overloaded = FALSE;
2453 else if (IS_MCDUFFIN(Store[x][y]))
2455 Store[x][y] = EL_EMPTY;
2458 Feld[x][y] = Store[x][y];
2459 Store[x][y] = Store2[x][y] = 0;
2460 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2462 InitField(x, y, FALSE);
2465 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2467 int graphic = IMG_MM_DEFAULT_EXPLODING;
2468 int graphic_phase = (phase / delay - 1);
2472 if (Store2[x][y] == EX_KETTLE)
2474 if (graphic_phase < 3)
2476 graphic = IMG_MM_KETTLE_EXPLODING;
2478 else if (graphic_phase < 5)
2484 graphic = IMG_EMPTY;
2488 else if (Store2[x][y] == EX_SHORT)
2490 if (graphic_phase < 4)
2496 graphic = IMG_EMPTY;
2501 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2503 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2504 FX + x * TILEX, FY + y * TILEY);
2506 MarkTileDirty(x, y);
2510 static void Bang_MM(int x, int y)
2512 int element = Feld[x][y];
2513 int mode = EX_NORMAL;
2516 DrawLaser(0, DL_LASER_ENABLED);
2535 if (IS_PACMAN(element))
2536 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2537 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2538 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2539 else if (element == EL_KEY)
2540 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2542 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2544 Explode_MM(x, y, EX_PHASE_START, mode);
2547 void TurnRound(int x, int y)
2559 { 0, 0 }, { 0, 0 }, { 0, 0 },
2564 int left, right, back;
2568 { MV_DOWN, MV_UP, MV_RIGHT },
2569 { MV_UP, MV_DOWN, MV_LEFT },
2571 { MV_LEFT, MV_RIGHT, MV_DOWN },
2572 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2573 { MV_RIGHT, MV_LEFT, MV_UP }
2576 int element = Feld[x][y];
2577 int old_move_dir = MovDir[x][y];
2578 int right_dir = turn[old_move_dir].right;
2579 int back_dir = turn[old_move_dir].back;
2580 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2581 int right_x = x + right_dx, right_y = y + right_dy;
2583 if (element == EL_PACMAN)
2585 boolean can_turn_right = FALSE;
2587 if (IN_LEV_FIELD(right_x, right_y) &&
2588 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2589 can_turn_right = TRUE;
2592 MovDir[x][y] = right_dir;
2594 MovDir[x][y] = back_dir;
2600 static void StartMoving_MM(int x, int y)
2602 int element = Feld[x][y];
2607 if (CAN_MOVE(element))
2611 if (MovDelay[x][y]) /* wait some time before next movement */
2619 /* now make next step */
2621 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2623 if (element == EL_PACMAN &&
2624 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2625 !ObjHit(newx, newy, HIT_POS_CENTER))
2627 Store[newx][newy] = Feld[newx][newy];
2628 Feld[newx][newy] = EL_EMPTY;
2630 DrawField_MM(newx, newy);
2632 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2633 ObjHit(newx, newy, HIT_POS_CENTER))
2635 /* object was running against a wall */
2642 InitMovingField_MM(x, y, MovDir[x][y]);
2646 ContinueMoving_MM(x, y);
2649 static void ContinueMoving_MM(int x, int y)
2651 int element = Feld[x][y];
2652 int direction = MovDir[x][y];
2653 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2654 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2655 int horiz_move = (dx!=0);
2656 int newx = x + dx, newy = y + dy;
2657 int step = (horiz_move ? dx : dy) * TILEX / 8;
2659 MovPos[x][y] += step;
2661 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2663 Feld[x][y] = EL_EMPTY;
2664 Feld[newx][newy] = element;
2666 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2667 MovDelay[newx][newy] = 0;
2669 if (!CAN_MOVE(element))
2670 MovDir[newx][newy] = 0;
2673 DrawField_MM(newx, newy);
2675 Stop[newx][newy] = TRUE;
2677 if (element == EL_PACMAN)
2679 if (Store[newx][newy] == EL_BOMB)
2680 Bang_MM(newx, newy);
2682 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2683 (LX + 2 * XS) / TILEX == newx &&
2684 (LY + 2 * YS) / TILEY == newy)
2691 else /* still moving on */
2696 laser.redraw = TRUE;
2699 void ClickElement(int x, int y, int button)
2701 static unsigned int click_delay = 0;
2702 static int click_delay_value = CLICK_DELAY;
2703 static boolean new_button = TRUE;
2708 /* initialize static variables */
2710 click_delay_value = CLICK_DELAY;
2716 /* do not rotate objects hit by the laser after the game was solved */
2717 if (game_mm.level_solved && Hit[x][y])
2720 if (button == MB_RELEASED)
2723 click_delay_value = CLICK_DELAY;
2725 /* release eventually hold auto-rotating mirror */
2726 RotateMirror(x, y, MB_RELEASED);
2731 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2734 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2737 if (!IN_LEV_FIELD(x, y))
2740 if (Feld[x][y] == EL_EMPTY)
2743 element = Feld[x][y];
2745 if (IS_MIRROR(element) ||
2746 IS_BEAMER(element) ||
2747 IS_POLAR(element) ||
2748 IS_POLAR_CROSS(element) ||
2749 IS_DF_MIRROR(element) ||
2750 IS_DF_MIRROR_AUTO(element))
2752 RotateMirror(x, y, button);
2754 else if (IS_MCDUFFIN(element))
2756 if (!laser.fuse_off)
2758 DrawLaser(0, DL_LASER_DISABLED);
2765 element = get_rotated_element(element, BUTTON_ROTATION(button));
2766 laser.start_angle = get_element_angle(element);
2770 Feld[x][y] = element;
2777 if (!laser.fuse_off)
2780 else if (element == EL_FUSE_ON && laser.fuse_off)
2782 if (x != laser.fuse_x || y != laser.fuse_y)
2785 laser.fuse_off = FALSE;
2786 laser.fuse_x = laser.fuse_y = -1;
2788 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2791 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2793 laser.fuse_off = TRUE;
2796 laser.overloaded = FALSE;
2798 DrawLaser(0, DL_LASER_DISABLED);
2799 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2801 else if (element == EL_LIGHTBALL)
2805 DrawLaser(0, DL_LASER_ENABLED);
2808 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2812 void RotateMirror(int x, int y, int button)
2814 if (button == MB_RELEASED)
2816 /* release eventually hold auto-rotating mirror */
2823 if (IS_MIRROR(Feld[x][y]) ||
2824 IS_POLAR_CROSS(Feld[x][y]) ||
2825 IS_POLAR(Feld[x][y]) ||
2826 IS_BEAMER(Feld[x][y]) ||
2827 IS_DF_MIRROR(Feld[x][y]) ||
2828 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2829 IS_GRID_WOOD_AUTO(Feld[x][y]))
2831 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2833 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2835 if (button == MB_LEFTBUTTON)
2837 /* left mouse button only for manual adjustment, no auto-rotating;
2838 freeze mirror for until mouse button released */
2842 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2844 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2848 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2850 int edge = Hit[x][y];
2856 DrawLaser(edge - 1, DL_LASER_DISABLED);
2860 else if (ObjHit(x, y, HIT_POS_CENTER))
2862 int edge = Hit[x][y];
2866 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2870 DrawLaser(edge - 1, DL_LASER_DISABLED);
2877 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2882 if ((IS_BEAMER(Feld[x][y]) ||
2883 IS_POLAR(Feld[x][y]) ||
2884 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2888 if (IS_BEAMER(Feld[x][y]))
2891 printf("TEST (%d, %d) [%d] [%d]\n",
2893 laser.beamer_edge, laser.beamer[1].num);
2903 DrawLaser(0, DL_LASER_ENABLED);
2907 void AutoRotateMirrors()
2911 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2914 for (x = 0; x < lev_fieldx; x++)
2916 for (y = 0; y < lev_fieldy; y++)
2918 int element = Feld[x][y];
2920 /* do not rotate objects hit by the laser after the game was solved */
2921 if (game_mm.level_solved && Hit[x][y])
2924 if (IS_DF_MIRROR_AUTO(element) ||
2925 IS_GRID_WOOD_AUTO(element) ||
2926 IS_GRID_STEEL_AUTO(element) ||
2927 element == EL_REFRACTOR)
2928 RotateMirror(x, y, MB_RIGHTBUTTON);
2933 boolean ObjHit(int obx, int oby, int bits)
2940 if (bits & HIT_POS_CENTER)
2942 if (CheckLaserPixel(SX + obx + 15,
2947 if (bits & HIT_POS_EDGE)
2949 for (i = 0; i < 4; i++)
2950 if (CheckLaserPixel(SX + obx + 31 * (i % 2),
2951 SY + oby + 31 * (i / 2)))
2955 if (bits & HIT_POS_BETWEEN)
2957 for (i = 0; i < 4; i++)
2958 if (CheckLaserPixel(SX + 4 + obx + 22 * (i % 2),
2959 SY + 4 + oby + 22 * (i / 2)))
2966 void DeletePacMan(int px, int py)
2972 if (game_mm.num_pacman <= 1)
2974 game_mm.num_pacman = 0;
2978 for (i = 0; i < game_mm.num_pacman; i++)
2979 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2982 game_mm.num_pacman--;
2984 for (j = i; j < game_mm.num_pacman; j++)
2986 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
2987 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
2988 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2992 void ColorCycling(void)
2994 static int CC, Cc = 0;
2996 static int color, old = 0xF00, new = 0x010, mult = 1;
2997 static unsigned short red, green, blue;
2999 if (color_status == STATIC_COLORS)
3004 if (CC < Cc || CC > Cc + 2)
3008 color = old + new * mult;
3014 if (ABS(mult) == 16)
3024 red = 0x0e00 * ((color & 0xF00) >> 8);
3025 green = 0x0e00 * ((color & 0x0F0) >> 4);
3026 blue = 0x0e00 * ((color & 0x00F));
3027 SetRGB(pen_magicolor[0], red, green, blue);
3029 red = 0x1111 * ((color & 0xF00) >> 8);
3030 green = 0x1111 * ((color & 0x0F0) >> 4);
3031 blue = 0x1111 * ((color & 0x00F));
3032 SetRGB(pen_magicolor[1], red, green, blue);
3036 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3043 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3046 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3048 element = Feld[x][y];
3050 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3051 StartMoving_MM(x, y);
3052 else if (IS_MOVING(x, y))
3053 ContinueMoving_MM(x, y);
3054 else if (IS_EXPLODING(element))
3055 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3056 else if (element == EL_EXIT_OPENING)
3058 else if (element == EL_GRAY_BALL_OPENING)
3059 OpenSurpriseBall(x, y);
3060 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3062 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3066 AutoRotateMirrors();
3069 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3071 /* redraw after Explode_MM() ... */
3073 DrawLaser(0, DL_LASER_ENABLED);
3074 laser.redraw = FALSE;
3079 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3083 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3085 DrawLaser(0, DL_LASER_DISABLED);
3090 if (FrameReached(&energy_delay, ENERGY_DELAY))
3092 if (game_mm.energy_left > 0)
3094 game_mm.energy_left--;
3097 BlitBitmap(pix[PIX_DOOR], drawto,
3098 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3099 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3100 DX_ENERGY, DY_ENERGY);
3102 redraw_mask |= REDRAW_DOOR_1;
3104 else if (setup.time_limit && !game_mm.game_over)
3108 for (i = 15; i >= 0; i--)
3111 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3113 pen_ray = GetPixelFromRGB(window,
3114 native_mm_level.laser_red * 0x11 * i,
3115 native_mm_level.laser_green * 0x11 * i,
3116 native_mm_level.laser_blue * 0x11 * i);
3118 DrawLaser(0, DL_LASER_ENABLED);
3123 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3126 DrawLaser(0, DL_LASER_DISABLED);
3127 game_mm.game_over = TRUE;
3128 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3131 if (Request("Out of magic energy ! Play it again ?",
3132 REQ_ASK | REQ_STAY_CLOSED))
3138 game_status = MAINMENU;
3147 element = laser.dest_element;
3150 if (element != Feld[ELX][ELY])
3152 printf("element == %d, Feld[ELX][ELY] == %d\n",
3153 element, Feld[ELX][ELY]);
3157 if (!laser.overloaded && laser.overload_value == 0 &&
3158 element != EL_BOMB &&
3159 element != EL_MINE &&
3160 element != EL_BALL_GRAY &&
3161 element != EL_BLOCK_STONE &&
3162 element != EL_BLOCK_WOOD &&
3163 element != EL_FUSE_ON &&
3164 element != EL_FUEL_FULL &&
3165 !IS_WALL_ICE(element) &&
3166 !IS_WALL_AMOEBA(element))
3169 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3170 (!laser.overloaded && laser.overload_value > 0)) &&
3171 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3173 if (laser.overloaded)
3174 laser.overload_value++;
3176 laser.overload_value--;
3178 if (game_mm.cheat_no_overload)
3180 laser.overloaded = FALSE;
3181 laser.overload_value = 0;
3184 game_mm.laser_overload_value = laser.overload_value;
3186 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3188 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3189 int color_down = 0xFF - color_up;
3192 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3193 (15 - (laser.overload_value / 6)) * color_scale);
3196 GetPixelFromRGB(window,
3197 (native_mm_level.laser_red ? 0xFF : color_up),
3198 (native_mm_level.laser_green ? color_down : 0x00),
3199 (native_mm_level.laser_blue ? color_down : 0x00));
3201 DrawLaser(0, DL_LASER_ENABLED);
3207 if (!laser.overloaded)
3208 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3209 else if (setup.sound_loops)
3210 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3212 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3214 if (laser.overloaded)
3217 BlitBitmap(pix[PIX_DOOR], drawto,
3218 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3219 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3220 - laser.overload_value,
3221 OVERLOAD_XSIZE, laser.overload_value,
3222 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3223 - laser.overload_value);
3225 redraw_mask |= REDRAW_DOOR_1;
3230 BlitBitmap(pix[PIX_DOOR], drawto,
3231 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3232 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3233 DX_OVERLOAD, DY_OVERLOAD);
3235 redraw_mask |= REDRAW_DOOR_1;
3238 if (laser.overload_value == MAX_LASER_OVERLOAD)
3242 for (i = 15; i >= 0; i--)
3245 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3248 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3250 DrawLaser(0, DL_LASER_ENABLED);
3255 DrawLaser(0, DL_LASER_DISABLED);
3257 game_mm.game_over = TRUE;
3258 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3261 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3262 REQ_ASK | REQ_STAY_CLOSED))
3268 game_status = MAINMENU;
3282 if (element == EL_BOMB && CT > 75)
3284 if (game_mm.cheat_no_explosion)
3288 laser.num_damages--;
3289 DrawLaser(0, DL_LASER_DISABLED);
3290 laser.num_edges = 0;
3295 laser.dest_element = EL_EXPLODING_OPAQUE;
3299 laser.num_damages--;
3300 DrawLaser(0, DL_LASER_DISABLED);
3302 laser.num_edges = 0;
3303 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3305 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3306 REQ_ASK | REQ_STAY_CLOSED))
3312 game_status = MAINMENU;
3320 if (element == EL_FUSE_ON && CT > 25)
3322 laser.fuse_off = TRUE;
3326 DrawLaser(0, DL_LASER_DISABLED);
3327 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3330 if (element == EL_BALL_GRAY && CT > 75)
3332 static int new_elements[] =
3335 EL_MIRROR_FIXED_START,
3337 EL_POLAR_CROSS_START,
3343 int num_new_elements = sizeof(new_elements) / sizeof(int);
3344 int new_element = new_elements[RND(num_new_elements)];
3346 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3347 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3349 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3360 element = EL_MIRROR_START + RND(16);
3366 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3373 element = (rnd == 0 ? EL_FUSE_ON :
3374 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3375 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3376 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3377 EL_MIRROR_FIXED_START + rnd - 25);
3382 graphic = el2gfx(element);
3384 for (i = 0; i < 50; i++)
3390 BlitBitmap(pix[PIX_BACK], drawto,
3391 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3392 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3393 SX + ELX * TILEX + x,
3394 SY + ELY * TILEY + y);
3396 MarkTileDirty(ELX, ELY);
3399 DrawLaser(0, DL_LASER_ENABLED);
3404 Feld[ELX][ELY] = element;
3405 DrawField_MM(ELX, ELY);
3408 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3411 /* above stuff: GRAY BALL -> PRISM !!! */
3413 LX = ELX * TILEX + 14;
3414 LY = ELY * TILEY + 14;
3415 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3422 laser.num_edges -= 2;
3423 laser.num_damages--;
3427 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3428 if (laser.damage[i].is_mirror)
3432 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3434 DrawLaser(0, DL_LASER_DISABLED);
3436 DrawLaser(0, DL_LASER_DISABLED);
3442 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3449 if (IS_WALL_ICE(element) && CT > 50)
3451 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3454 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3455 Store[ELX][ELY] = EL_WALL_ICE;
3456 Store2[ELX][ELY] = laser.wall_mask;
3458 laser.dest_element = Feld[ELX][ELY];
3463 for (i = 0; i < 5; i++)
3469 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3473 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3478 if (Feld[ELX][ELY] == EL_WALL_ICE)
3479 Feld[ELX][ELY] = EL_EMPTY;
3483 LX = laser.edge[laser.num_edges].x - (SX + 2);
3484 LY = laser.edge[laser.num_edges].y - (SY + 2);
3487 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3488 if (laser.damage[i].is_mirror)
3492 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3494 DrawLaser(0, DL_LASER_DISABLED);
3501 if (IS_WALL_AMOEBA(element) && CT > 60)
3503 int k1, k2, k3, dx, dy, de, dm;
3504 int element2 = Feld[ELX][ELY];
3506 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3509 for (i = laser.num_damages - 1; i >= 0; i--)
3510 if (laser.damage[i].is_mirror)
3513 r = laser.num_edges;
3514 d = laser.num_damages;
3521 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3524 DrawLaser(0, DL_LASER_ENABLED);
3527 x = laser.damage[k1].x;
3528 y = laser.damage[k1].y;
3533 for (i = 0; i < 4; i++)
3535 if (laser.wall_mask & (1 << i))
3537 if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3538 SY + ELY * TILEY + 31 * (i / 2)))
3541 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3542 SY + ELY * TILEY + 14 + (i / 2) * 2))
3549 for (i = 0; i < 4; i++)
3551 if (laser.wall_mask & (1 << i))
3553 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3554 SY + ELY * TILEY + 31 * (i / 2)))
3561 if (laser.num_beamers > 0 ||
3562 k1 < 1 || k2 < 4 || k3 < 4 ||
3563 CheckLaserPixel(SX + ELX * TILEX + 14,
3564 SY + ELY * TILEY + 14))
3566 laser.num_edges = r;
3567 laser.num_damages = d;
3569 DrawLaser(0, DL_LASER_DISABLED);
3572 Feld[ELX][ELY] = element | laser.wall_mask;
3576 de = Feld[ELX][ELY];
3577 dm = laser.wall_mask;
3581 int x = ELX, y = ELY;
3582 int wall_mask = laser.wall_mask;
3585 DrawLaser(0, DL_LASER_ENABLED);
3587 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3589 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3590 Store[x][y] = EL_WALL_AMOEBA;
3591 Store2[x][y] = wall_mask;
3597 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3599 DrawLaser(0, DL_LASER_ENABLED);
3601 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3603 for (i = 4; i >= 0; i--)
3605 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3611 DrawLaser(0, DL_LASER_ENABLED);
3616 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3617 laser.stops_inside_element && CT > 75)
3622 if (ABS(XS) > ABS(YS))
3629 for (i = 0; i < 4; i++)
3636 x = ELX + Step[k * 4].x;
3637 y = ELY + Step[k * 4].y;
3639 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3642 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3650 laser.overloaded = (element == EL_BLOCK_STONE);
3655 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3658 Feld[x][y] = element;
3660 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3663 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3665 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3666 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3674 if (element == EL_FUEL_FULL && CT > 10)
3676 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3679 BlitBitmap(pix[PIX_DOOR], drawto,
3680 DOOR_GFX_PAGEX4 + XX_ENERGY,
3681 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3682 ENERGY_XSIZE, i, DX_ENERGY,
3683 DY_ENERGY + ENERGY_YSIZE - i);
3686 redraw_mask |= REDRAW_DOOR_1;
3692 game_mm.energy_left = MAX_LASER_ENERGY;
3693 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3694 DrawField_MM(ELX, ELY);
3696 DrawLaser(0, DL_LASER_ENABLED);
3704 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3706 ClickElement(action.lx, action.ly, action.button);
3708 GameActions_MM_Ext(action, warp_mode);
3713 int mx, my, ox, oy, nx, ny;
3717 if (++pacman_nr >= game_mm.num_pacman)
3720 game_mm.pacman[pacman_nr].dir--;
3722 for (l = 1; l < 5; l++)
3724 game_mm.pacman[pacman_nr].dir++;
3726 if (game_mm.pacman[pacman_nr].dir > 4)
3727 game_mm.pacman[pacman_nr].dir = 1;
3729 if (game_mm.pacman[pacman_nr].dir % 2)
3732 my = game_mm.pacman[pacman_nr].dir - 2;
3737 mx = 3 - game_mm.pacman[pacman_nr].dir;
3740 ox = game_mm.pacman[pacman_nr].x;
3741 oy = game_mm.pacman[pacman_nr].y;
3744 element = Feld[nx][ny];
3746 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3749 if (!IS_EATABLE4PACMAN(element))
3752 if (ObjHit(nx, ny, HIT_POS_CENTER))
3755 Feld[ox][oy] = EL_EMPTY;
3757 EL_PACMAN_RIGHT - 1 +
3758 (game_mm.pacman[pacman_nr].dir - 1 +
3759 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3761 game_mm.pacman[pacman_nr].x = nx;
3762 game_mm.pacman[pacman_nr].y = ny;
3764 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3766 if (element != EL_EMPTY)
3768 int graphic = el2gfx(Feld[nx][ny]);
3773 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3776 ox = SX + ox * TILEX;
3777 oy = SY + oy * TILEY;
3779 for (i = 1; i < 33; i += 2)
3780 BlitBitmap(bitmap, window,
3781 src_x, src_y, TILEX, TILEY,
3782 ox + i * mx, oy + i * my);
3783 Ct = Ct + FrameCounter - CT;
3786 DrawField_MM(nx, ny);
3789 if (!laser.fuse_off)
3791 DrawLaser(0, DL_LASER_ENABLED);
3793 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3795 AddDamagedField(nx, ny);
3797 laser.damage[laser.num_damages - 1].edge = 0;
3801 if (element == EL_BOMB)
3802 DeletePacMan(nx, ny);
3804 if (IS_WALL_AMOEBA(element) &&
3805 (LX + 2 * XS) / TILEX == nx &&
3806 (LY + 2 * YS) / TILEY == ny)
3819 boolean raise_level = FALSE;
3822 if (local_player->MovPos)
3825 local_player->LevelSolved = FALSE;
3828 if (game_mm.energy_left)
3830 if (setup.sound_loops)
3831 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3832 SND_CTRL_PLAY_LOOP);
3834 while (game_mm.energy_left > 0)
3836 if (!setup.sound_loops)
3837 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3840 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3841 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3846 game_mm.energy_left--;
3847 if (game_mm.energy_left >= 0)
3850 BlitBitmap(pix[PIX_DOOR], drawto,
3851 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3852 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3853 DX_ENERGY, DY_ENERGY);
3855 redraw_mask |= REDRAW_DOOR_1;
3862 if (setup.sound_loops)
3863 StopSound(SND_SIRR);
3865 else if (native_mm_level.time == 0) /* level without time limit */
3867 if (setup.sound_loops)
3868 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3869 SND_CTRL_PLAY_LOOP);
3871 while (TimePlayed < 999)
3873 if (!setup.sound_loops)
3874 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3875 if (TimePlayed < 999 && !(TimePlayed % 10))
3876 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3877 if (TimePlayed < 900 && !(TimePlayed % 10))
3883 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3890 if (setup.sound_loops)
3891 StopSound(SND_SIRR);
3898 CloseDoor(DOOR_CLOSE_1);
3900 Request("Level solved !", REQ_CONFIRM);
3902 if (level_nr == leveldir_current->handicap_level)
3904 leveldir_current->handicap_level++;
3905 SaveLevelSetup_SeriesInfo();
3908 if (level_editor_test_game)
3909 game_mm.score = -1; /* no highscore when playing from editor */
3910 else if (level_nr < leveldir_current->last_level)
3911 raise_level = TRUE; /* advance to next level */
3913 if ((hi_pos = NewHiScore_MM()) >= 0)
3915 game_status = HALLOFFAME;
3917 // DrawHallOfFame(hi_pos);
3924 game_status = MAINMENU;
3940 // LoadScore(level_nr);
3942 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3943 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3946 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3948 if (game_mm.score > highscore[k].Score)
3950 /* player has made it to the hall of fame */
3952 if (k < MAX_SCORE_ENTRIES - 1)
3954 int m = MAX_SCORE_ENTRIES - 1;
3957 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3958 if (!strcmp(setup.player_name, highscore[l].Name))
3960 if (m == k) /* player's new highscore overwrites his old one */
3964 for (l = m; l>k; l--)
3966 strcpy(highscore[l].Name, highscore[l - 1].Name);
3967 highscore[l].Score = highscore[l - 1].Score;
3974 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3975 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3976 highscore[k].Score = game_mm.score;
3983 else if (!strncmp(setup.player_name, highscore[k].Name,
3984 MAX_PLAYER_NAME_LEN))
3985 break; /* player already there with a higher score */
3990 // if (position >= 0)
3991 // SaveScore(level_nr);
3996 static void InitMovingField_MM(int x, int y, int direction)
3998 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3999 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4001 MovDir[x][y] = direction;
4002 MovDir[newx][newy] = direction;
4004 if (Feld[newx][newy] == EL_EMPTY)
4005 Feld[newx][newy] = EL_BLOCKED;
4008 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4010 int direction = MovDir[x][y];
4011 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4012 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4018 static void Blocked2Moving_MM(int x, int y,
4019 int *comes_from_x, int *comes_from_y)
4021 int oldx = x, oldy = y;
4022 int direction = MovDir[x][y];
4024 if (direction == MV_LEFT)
4026 else if (direction == MV_RIGHT)
4028 else if (direction == MV_UP)
4030 else if (direction == MV_DOWN)
4033 *comes_from_x = oldx;
4034 *comes_from_y = oldy;
4037 static int MovingOrBlocked2Element_MM(int x, int y)
4039 int element = Feld[x][y];
4041 if (element == EL_BLOCKED)
4045 Blocked2Moving_MM(x, y, &oldx, &oldy);
4047 return Feld[oldx][oldy];
4054 static void RemoveField(int x, int y)
4056 Feld[x][y] = EL_EMPTY;
4063 static void RemoveMovingField_MM(int x, int y)
4065 int oldx = x, oldy = y, newx = x, newy = y;
4067 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4070 if (IS_MOVING(x, y))
4072 Moving2Blocked_MM(x, y, &newx, &newy);
4073 if (Feld[newx][newy] != EL_BLOCKED)
4076 else if (Feld[x][y] == EL_BLOCKED)
4078 Blocked2Moving_MM(x, y, &oldx, &oldy);
4079 if (!IS_MOVING(oldx, oldy))
4083 Feld[oldx][oldy] = EL_EMPTY;
4084 Feld[newx][newy] = EL_EMPTY;
4085 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4086 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4088 DrawLevelField_MM(oldx, oldy);
4089 DrawLevelField_MM(newx, newy);
4092 void PlaySoundLevel(int x, int y, int sound_nr)
4094 int sx = SCREENX(x), sy = SCREENY(y);
4096 int silence_distance = 8;
4098 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4099 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4102 if (!IN_LEV_FIELD(x, y) ||
4103 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4104 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4107 volume = SOUND_MAX_VOLUME;
4110 stereo = (sx - SCR_FIELDX/2) * 12;
4112 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4113 if (stereo > SOUND_MAX_RIGHT)
4114 stereo = SOUND_MAX_RIGHT;
4115 if (stereo < SOUND_MAX_LEFT)
4116 stereo = SOUND_MAX_LEFT;
4119 if (!IN_SCR_FIELD(sx, sy))
4121 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4122 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4124 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4127 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4130 static void RaiseScore_MM(int value)
4132 game_mm.score += value;
4135 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4140 void RaiseScoreElement_MM(int element)
4145 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4149 RaiseScore_MM(native_mm_level.score[SC_KEY]);