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 RaiseScoreElement_MM(int);
96 static void RemoveMovingField_MM(int, int);
97 static void InitMovingField_MM(int, int, int);
98 static void ContinueMoving_MM(int, int);
99 static void Moving2Blocked_MM(int, int, int *, int *);
101 /* bitmap for laser beam detection */
102 static Bitmap *laser_bitmap = NULL;
104 /* variables for laser control */
105 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
106 static int hold_x = -1, hold_y = -1;
108 /* variables for pacman control */
109 static int pacman_nr = -1;
111 /* various game engine delay counters */
112 static unsigned int rotate_delay = 0;
113 static unsigned int pacman_delay = 0;
114 static unsigned int energy_delay = 0;
115 static unsigned int overload_delay = 0;
117 /* element masks for scanning pixels of MM elements */
118 static const char mm_masks[10][16][16 + 1] =
302 static int get_element_angle(int element)
304 int element_phase = get_element_phase(element);
306 if (IS_MIRROR_FIXED(element) ||
307 IS_MCDUFFIN(element) ||
309 IS_RECEIVER(element))
310 return 4 * element_phase;
312 return element_phase;
315 static int get_opposite_angle(int angle)
317 int opposite_angle = angle + ANG_RAY_180;
319 /* make sure "opposite_angle" is in valid interval [0, 15] */
320 return (opposite_angle + 16) % 16;
323 static int get_mirrored_angle(int laser_angle, int mirror_angle)
325 int reflected_angle = 16 - laser_angle + mirror_angle;
327 /* make sure "reflected_angle" is in valid interval [0, 15] */
328 return (reflected_angle + 16) % 16;
331 static void DrawLaserLines(struct XY *points, int num_points, int mode)
333 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
334 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
336 DrawLines(drawto, points, num_points, pixel_drawto);
340 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
345 static boolean CheckLaserPixel(int x, int y)
351 pixel = ReadPixel(laser_bitmap, x, y);
355 return (pixel == WHITE_PIXEL);
358 static void CheckExitMM()
360 int exit_element = EL_EMPTY;
364 static int xy[4][2] =
372 for (y = 0; y < lev_fieldy; y++)
374 for (x = 0; x < lev_fieldx; x++)
376 if (Feld[x][y] == EL_EXIT_CLOSED)
378 /* initiate opening animation of exit door */
379 Feld[x][y] = EL_EXIT_OPENING;
381 exit_element = EL_EXIT_OPEN;
385 else if (IS_RECEIVER(Feld[x][y]))
387 /* remove field that blocks receiver */
388 int phase = Feld[x][y] - EL_RECEIVER_START;
389 int blocking_x, blocking_y;
391 blocking_x = x + xy[phase][0];
392 blocking_y = y + xy[phase][1];
394 if (IN_LEV_FIELD(blocking_x, blocking_y))
396 Feld[blocking_x][blocking_y] = EL_EMPTY;
398 DrawField_MM(blocking_x, blocking_y);
401 exit_element = EL_RECEIVER;
408 if (exit_element != EL_EMPTY)
409 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
412 static void InitMovDir_MM(int x, int y)
414 int element = Feld[x][y];
415 static int direction[3][4] =
417 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
418 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
419 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
424 case EL_PACMAN_RIGHT:
428 Feld[x][y] = EL_PACMAN;
429 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
437 static void InitField(int x, int y, boolean init_game)
439 int element = Feld[x][y];
444 Feld[x][y] = EL_EMPTY;
449 if (native_mm_level.auto_count_kettles)
450 game_mm.kettles_still_needed++;
453 case EL_LIGHTBULB_OFF:
454 game_mm.lights_still_needed++;
458 if (IS_MIRROR(element) ||
459 IS_BEAMER_OLD(element) ||
460 IS_BEAMER(element) ||
462 IS_POLAR_CROSS(element) ||
463 IS_DF_MIRROR(element) ||
464 IS_DF_MIRROR_AUTO(element) ||
465 IS_GRID_STEEL_AUTO(element) ||
466 IS_GRID_WOOD_AUTO(element) ||
467 IS_FIBRE_OPTIC(element))
469 if (IS_BEAMER_OLD(element))
471 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
472 element = Feld[x][y];
475 if (!IS_FIBRE_OPTIC(element))
477 static int steps_grid_auto = 0;
479 if (game_mm.num_cycle == 0) /* initialize cycle steps for grids */
480 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
482 if (IS_GRID_STEEL_AUTO(element) ||
483 IS_GRID_WOOD_AUTO(element))
484 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
486 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
488 game_mm.cycle[game_mm.num_cycle].x = x;
489 game_mm.cycle[game_mm.num_cycle].y = y;
493 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
495 int beamer_nr = BEAMER_NR(element);
496 int nr = laser.beamer[beamer_nr][0].num;
498 laser.beamer[beamer_nr][nr].x = x;
499 laser.beamer[beamer_nr][nr].y = y;
500 laser.beamer[beamer_nr][nr].num = 1;
503 else if (IS_PACMAN(element))
507 else if (IS_MCDUFFIN(element) || IS_LASER(element))
509 laser.start_edge.x = x;
510 laser.start_edge.y = y;
511 laser.start_angle = get_element_angle(element);
518 static void InitCycleElements_RotateSingleStep()
522 if (game_mm.num_cycle == 0) /* no elements to cycle */
525 for (i = 0; i < game_mm.num_cycle; i++)
527 int x = game_mm.cycle[i].x;
528 int y = game_mm.cycle[i].y;
529 int step = SIGN(game_mm.cycle[i].steps);
530 int last_element = Feld[x][y];
531 int next_element = get_rotated_element(last_element, step);
533 if (!game_mm.cycle[i].steps)
536 Feld[x][y] = next_element;
539 game_mm.cycle[i].steps -= step;
543 static void InitLaser()
545 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
546 int step = (IS_LASER(start_element) ? 4 : 0);
548 LX = laser.start_edge.x * TILEX;
549 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
552 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
554 LY = laser.start_edge.y * TILEY;
555 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
556 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
560 XS = 2 * Step[laser.start_angle].x;
561 YS = 2 * Step[laser.start_angle].y;
563 laser.current_angle = laser.start_angle;
565 laser.num_damages = 0;
567 laser.num_beamers = 0;
568 laser.beamer_edge[0] = 0;
570 laser.dest_element = EL_EMPTY;
573 AddLaserEdge(LX, LY); /* set laser starting edge */
575 pen_ray = GetPixelFromRGB(window,
576 native_mm_level.laser_red * 0xFF,
577 native_mm_level.laser_green * 0xFF,
578 native_mm_level.laser_blue * 0xFF);
581 void InitGameEngine_MM()
587 /* initialize laser bitmap to current playfield (screen) size */
588 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
589 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
593 /* set global game control values */
594 game_mm.num_cycle = 0;
595 game_mm.num_pacman = 0;
598 game_mm.energy_left = 0; // later set to "native_mm_level.time"
599 game_mm.kettles_still_needed =
600 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
601 game_mm.lights_still_needed = 0;
602 game_mm.num_keys = 0;
604 game_mm.level_solved = FALSE;
605 game_mm.game_over = FALSE;
606 game_mm.game_over_cause = 0;
608 game_mm.laser_overload_value = 0;
609 game_mm.laser_enabled = FALSE;
611 /* set global laser control values (must be set before "InitLaser()") */
612 laser.start_edge.x = 0;
613 laser.start_edge.y = 0;
614 laser.start_angle = 0;
616 for (i = 0; i < MAX_NUM_BEAMERS; i++)
617 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
619 laser.overloaded = FALSE;
620 laser.overload_value = 0;
621 laser.fuse_off = FALSE;
622 laser.fuse_x = laser.fuse_y = -1;
624 laser.dest_element = EL_EMPTY;
643 ClickElement(-1, -1, -1);
645 for (x = 0; x < lev_fieldx; x++)
647 for (y = 0; y < lev_fieldy; y++)
649 Feld[x][y] = Ur[x][y];
650 Hit[x][y] = Box[x][y] = 0;
652 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
653 Store[x][y] = Store2[x][y] = 0;
657 InitField(x, y, TRUE);
662 CloseDoor(DOOR_CLOSE_1);
668 void InitGameActions_MM()
670 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
671 int cycle_steps_done = 0;
677 /* copy default game door content to main double buffer */
678 BlitBitmap(pix[PIX_DOOR], drawto,
679 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
683 DrawText(DX_LEVEL, DY_LEVEL,
684 int2str(level_nr, 2), FONT_TEXT_2);
685 DrawText(DX_KETTLES, DY_KETTLES,
686 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
687 DrawText(DX_SCORE, DY_SCORE,
688 int2str(game_mm.score, 4), FONT_TEXT_2);
697 /* copy actual game door content to door double buffer for OpenDoor() */
698 BlitBitmap(drawto, pix[PIX_DB_DOOR],
699 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
703 OpenDoor(DOOR_OPEN_ALL);
706 for (i = 0; i <= num_init_game_frames; i++)
708 if (i == num_init_game_frames)
709 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
710 else if (setup.sound_loops)
711 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
713 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
715 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
717 UpdateAndDisplayGameControlValues();
719 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
721 InitCycleElements_RotateSingleStep();
731 if (setup.quick_doors)
737 if (setup.sound_music && num_bg_loops)
738 PlayMusic(level_nr % num_bg_loops);
743 if (game_mm.kettles_still_needed == 0)
747 void AddLaserEdge(int lx, int ly)
749 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
751 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
756 laser.edge[laser.num_edges].x = SX + 2 + lx;
757 laser.edge[laser.num_edges].y = SY + 2 + ly;
763 void AddDamagedField(int ex, int ey)
765 laser.damage[laser.num_damages].is_mirror = FALSE;
766 laser.damage[laser.num_damages].angle = laser.current_angle;
767 laser.damage[laser.num_damages].edge = laser.num_edges;
768 laser.damage[laser.num_damages].x = ex;
769 laser.damage[laser.num_damages].y = ey;
779 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
780 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
782 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
788 static int getMaskFromElement(int element)
790 if (IS_GRID(element))
791 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
792 else if (IS_MCDUFFIN(element))
793 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
794 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
795 return IMG_MM_MASK_RECTANGLE;
797 return IMG_MM_MASK_CIRCLE;
805 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
806 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
809 /* follow laser beam until it hits something (at least the screen border) */
810 while (hit_mask == HIT_MASK_NO_HIT)
816 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
817 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
819 printf("ScanPixel: touched screen border!\n");
825 for (i = 0; i < 4; i++)
827 int px = LX + (i % 2) * 2;
828 int py = LY + (i / 2) * 2;
831 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
832 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
835 if (IN_LEV_FIELD(lx, ly))
837 int element = Feld[lx][ly];
839 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
843 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
845 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
847 pixel = ((element & (1 << pos)) ? 1 : 0);
851 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
853 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
858 pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
859 SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
862 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
863 hit_mask |= (1 << i);
866 if (hit_mask == HIT_MASK_NO_HIT)
868 /* hit nothing -- go on with another step */
880 int end = 0, rf = laser.num_edges;
882 /* do not scan laser again after the game was lost for whatever reason */
883 if (game_mm.game_over)
886 laser.overloaded = FALSE;
887 laser.stops_inside_element = FALSE;
889 DrawLaser(0, DL_LASER_ENABLED);
892 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
900 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
903 laser.overloaded = TRUE;
908 hit_mask = ScanPixel();
911 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
915 /* hit something -- check out what it was */
916 ELX = (LX + XS) / TILEX;
917 ELY = (LY + YS) / TILEY;
920 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
921 hit_mask, LX, LY, ELX, ELY);
924 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
927 laser.dest_element = element;
932 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
934 /* we have hit the top-right and bottom-left element --
935 choose the bottom-left one */
936 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
937 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
938 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
939 ELX = (LX - 2) / TILEX;
940 ELY = (LY + 2) / TILEY;
943 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
945 /* we have hit the top-left and bottom-right element --
946 choose the top-left one */
947 /* !!! SEE ABOVE !!! */
948 ELX = (LX - 2) / TILEX;
949 ELY = (LY - 2) / TILEY;
953 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
954 hit_mask, LX, LY, ELX, ELY);
957 element = Feld[ELX][ELY];
958 laser.dest_element = element;
961 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
964 LX % TILEX, LY % TILEY,
969 if (!IN_LEV_FIELD(ELX, ELY))
970 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
973 if (element == EL_EMPTY)
975 if (!HitOnlyAnEdge(element, hit_mask))
978 else if (element == EL_FUSE_ON)
980 if (HitPolarizer(element, hit_mask))
983 else if (IS_GRID(element) || IS_DF_GRID(element))
985 if (HitPolarizer(element, hit_mask))
988 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
989 element == EL_GATE_STONE || element == EL_GATE_WOOD)
991 if (HitBlock(element, hit_mask))
998 else if (IS_MCDUFFIN(element))
1000 if (HitLaserSource(element, hit_mask))
1003 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1004 IS_RECEIVER(element))
1006 if (HitLaserDestination(element, hit_mask))
1009 else if (IS_WALL(element))
1011 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1013 if (HitReflectingWalls(element, hit_mask))
1018 if (HitAbsorbingWalls(element, hit_mask))
1024 if (HitElement(element, hit_mask))
1029 DrawLaser(rf - 1, DL_LASER_ENABLED);
1030 rf = laser.num_edges;
1034 if (laser.dest_element != Feld[ELX][ELY])
1036 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
1037 laser.dest_element, Feld[ELX][ELY]);
1041 if (!end && !laser.stops_inside_element && !StepBehind())
1044 printf("ScanLaser: Go one step back\n");
1050 AddLaserEdge(LX, LY);
1054 DrawLaser(rf - 1, DL_LASER_ENABLED);
1056 Ct = CT = FrameCounter;
1059 if (!IN_LEV_FIELD(ELX, ELY))
1060 printf("WARNING! (2) %d, %d\n", ELX, ELY);
1064 void DrawLaserExt(int start_edge, int num_edges, int mode)
1070 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
1071 start_edge, num_edges, mode);
1076 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
1083 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
1089 if (mode == DL_LASER_DISABLED)
1091 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
1095 /* now draw the laser to the backbuffer and (if enabled) to the screen */
1096 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1098 redraw_mask |= REDRAW_FIELD;
1100 if (mode == DL_LASER_ENABLED)
1103 /* after the laser was deleted, the "damaged" graphics must be restored */
1104 if (laser.num_damages)
1106 int damage_start = 0;
1109 /* determine the starting edge, from which graphics need to be restored */
1112 for (i = 0; i < laser.num_damages; i++)
1114 if (laser.damage[i].edge == start_edge + 1)
1123 /* restore graphics from this starting edge to the end of damage list */
1124 for (i = damage_start; i < laser.num_damages; i++)
1126 int lx = laser.damage[i].x;
1127 int ly = laser.damage[i].y;
1128 int element = Feld[lx][ly];
1130 if (Hit[lx][ly] == laser.damage[i].edge)
1131 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1134 if (Box[lx][ly] == laser.damage[i].edge)
1137 if (IS_DRAWABLE(element))
1138 DrawField_MM(lx, ly);
1141 elx = laser.damage[damage_start].x;
1142 ely = laser.damage[damage_start].y;
1143 element = Feld[elx][ely];
1146 if (IS_BEAMER(element))
1150 for (i = 0; i < laser.num_beamers; i++)
1151 printf("-> %d\n", laser.beamer_edge[i]);
1152 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
1153 mode, elx, ely, Hit[elx][ely], start_edge);
1154 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
1155 get_element_angle(element), laser.damage[damage_start].angle);
1159 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1160 laser.num_beamers > 0 &&
1161 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1163 /* element is outgoing beamer */
1164 laser.num_damages = damage_start + 1;
1166 if (IS_BEAMER(element))
1167 laser.current_angle = get_element_angle(element);
1171 /* element is incoming beamer or other element */
1172 laser.num_damages = damage_start;
1173 laser.current_angle = laser.damage[laser.num_damages].angle;
1178 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
1180 elx = laser.start_edge.x;
1181 ely = laser.start_edge.y;
1182 element = Feld[elx][ely];
1185 laser.num_edges = start_edge + 1;
1186 if (start_edge == 0)
1187 laser.current_angle = laser.start_angle;
1189 LX = laser.edge[start_edge].x - (SX + 2);
1190 LY = laser.edge[start_edge].y - (SY + 2);
1191 XS = 2 * Step[laser.current_angle].x;
1192 YS = 2 * Step[laser.current_angle].y;
1195 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
1201 if (IS_BEAMER(element) ||
1202 IS_FIBRE_OPTIC(element) ||
1203 IS_PACMAN(element) ||
1204 IS_POLAR(element) ||
1205 IS_POLAR_CROSS(element) ||
1206 element == EL_FUSE_ON)
1211 printf("element == %d\n", element);
1214 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
1215 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1219 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1220 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1221 (laser.num_beamers == 0 ||
1222 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1224 /* element is incoming beamer or other element */
1225 step_size = -step_size;
1230 if (IS_BEAMER(element))
1232 printf("start_edge == %d, laser.beamer_edge == %d\n",
1233 start_edge, laser.beamer_edge);
1237 LX += step_size * XS;
1238 LY += step_size * YS;
1240 else if (element != EL_EMPTY)
1249 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
1254 void DrawLaser(int start_edge, int mode)
1256 if (laser.num_edges - start_edge < 0)
1258 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
1263 /* check if laser is interrupted by beamer element */
1264 if (laser.num_beamers > 0 &&
1265 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1267 if (mode == DL_LASER_ENABLED)
1270 int tmp_start_edge = start_edge;
1272 /* draw laser segments forward from the start to the last beamer */
1273 for (i = 0; i < laser.num_beamers; i++)
1275 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1277 if (tmp_num_edges <= 0)
1281 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
1282 i, laser.beamer_edge[i], tmp_start_edge);
1285 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1287 tmp_start_edge = laser.beamer_edge[i];
1290 /* draw last segment from last beamer to the end */
1291 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1297 int last_num_edges = laser.num_edges;
1298 int num_beamers = laser.num_beamers;
1300 /* delete laser segments backward from the end to the first beamer */
1301 for (i = num_beamers - 1; i >= 0; i--)
1303 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1305 if (laser.beamer_edge[i] - start_edge <= 0)
1308 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1310 last_num_edges = laser.beamer_edge[i];
1311 laser.num_beamers--;
1315 if (last_num_edges - start_edge <= 0)
1316 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1317 last_num_edges, start_edge);
1320 // special case when rotating first beamer: delete laser edge on beamer
1321 // (but do not start scanning on previous edge to prevent mirror sound)
1322 if (last_num_edges - start_edge == 1 && start_edge > 0)
1323 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1325 /* delete first segment from start to the first beamer */
1326 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1331 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1334 game_mm.laser_enabled = mode;
1339 DrawLaser(0, game_mm.laser_enabled);
1342 boolean HitElement(int element, int hit_mask)
1344 if (HitOnlyAnEdge(element, hit_mask))
1347 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1348 element = MovingOrBlocked2Element_MM(ELX, ELY);
1351 printf("HitElement (1): element == %d\n", element);
1355 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1356 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1358 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1361 AddDamagedField(ELX, ELY);
1363 /* this is more precise: check if laser would go through the center */
1364 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1366 /* skip the whole element before continuing the scan */
1372 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1374 if (LX/TILEX > ELX || LY/TILEY > ELY)
1376 /* skipping scan positions to the right and down skips one scan
1377 position too much, because this is only the top left scan position
1378 of totally four scan positions (plus one to the right, one to the
1379 bottom and one to the bottom right) */
1389 printf("HitElement (2): element == %d\n", element);
1392 if (LX + 5 * XS < 0 ||
1402 printf("HitElement (3): element == %d\n", element);
1405 if (IS_POLAR(element) &&
1406 ((element - EL_POLAR_START) % 2 ||
1407 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1409 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1411 laser.num_damages--;
1416 if (IS_POLAR_CROSS(element) &&
1417 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1419 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1421 laser.num_damages--;
1426 if (!IS_BEAMER(element) &&
1427 !IS_FIBRE_OPTIC(element) &&
1428 !IS_GRID_WOOD(element) &&
1429 element != EL_FUEL_EMPTY)
1432 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1433 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1435 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1438 LX = ELX * TILEX + 14;
1439 LY = ELY * TILEY + 14;
1441 AddLaserEdge(LX, LY);
1444 if (IS_MIRROR(element) ||
1445 IS_MIRROR_FIXED(element) ||
1446 IS_POLAR(element) ||
1447 IS_POLAR_CROSS(element) ||
1448 IS_DF_MIRROR(element) ||
1449 IS_DF_MIRROR_AUTO(element) ||
1450 element == EL_PRISM ||
1451 element == EL_REFRACTOR)
1453 int current_angle = laser.current_angle;
1456 laser.num_damages--;
1458 AddDamagedField(ELX, ELY);
1460 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1463 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1465 if (IS_MIRROR(element) ||
1466 IS_MIRROR_FIXED(element) ||
1467 IS_DF_MIRROR(element) ||
1468 IS_DF_MIRROR_AUTO(element))
1469 laser.current_angle = get_mirrored_angle(laser.current_angle,
1470 get_element_angle(element));
1472 if (element == EL_PRISM || element == EL_REFRACTOR)
1473 laser.current_angle = RND(16);
1475 XS = 2 * Step[laser.current_angle].x;
1476 YS = 2 * Step[laser.current_angle].y;
1478 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1483 LX += step_size * XS;
1484 LY += step_size * YS;
1487 /* draw sparkles on mirror */
1488 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1489 current_angle != laser.current_angle)
1491 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1495 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1496 current_angle != laser.current_angle)
1497 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1500 (get_opposite_angle(laser.current_angle) ==
1501 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1503 return (laser.overloaded ? TRUE : FALSE);
1506 if (element == EL_FUEL_FULL)
1508 laser.stops_inside_element = TRUE;
1513 if (element == EL_BOMB || element == EL_MINE)
1515 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1517 if (element == EL_MINE)
1518 laser.overloaded = TRUE;
1521 if (element == EL_KETTLE ||
1522 element == EL_CELL ||
1523 element == EL_KEY ||
1524 element == EL_LIGHTBALL ||
1525 element == EL_PACMAN ||
1528 if (!IS_PACMAN(element))
1531 if (element == EL_PACMAN)
1534 if (element == EL_KETTLE || element == EL_CELL)
1536 if (game_mm.kettles_still_needed > 0)
1537 game_mm.kettles_still_needed--;
1539 if (game_mm.kettles_still_needed == 0)
1543 DrawLaser(0, DL_LASER_ENABLED);
1546 else if (element == EL_KEY)
1550 else if (IS_PACMAN(element))
1552 DeletePacMan(ELX, ELY);
1555 RaiseScoreElement_MM(element);
1560 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1562 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1564 DrawLaser(0, DL_LASER_ENABLED);
1566 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1568 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1569 game_mm.lights_still_needed--;
1573 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1574 game_mm.lights_still_needed++;
1577 DrawField_MM(ELX, ELY);
1578 DrawLaser(0, DL_LASER_ENABLED);
1583 laser.stops_inside_element = TRUE;
1589 printf("HitElement (4): element == %d\n", element);
1592 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1593 laser.num_beamers < MAX_NUM_BEAMERS &&
1594 laser.beamer[BEAMER_NR(element)][1].num)
1596 int beamer_angle = get_element_angle(element);
1597 int beamer_nr = BEAMER_NR(element);
1601 printf("HitElement (BEAMER): element == %d\n", element);
1604 laser.num_damages--;
1606 if (IS_FIBRE_OPTIC(element) ||
1607 laser.current_angle == get_opposite_angle(beamer_angle))
1611 LX = ELX * TILEX + 14;
1612 LY = ELY * TILEY + 14;
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 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1623 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1624 ELX = laser.beamer[beamer_nr][pos].x;
1625 ELY = laser.beamer[beamer_nr][pos].y;
1626 LX = ELX * TILEX + 14;
1627 LY = ELY * TILEY + 14;
1629 if (IS_BEAMER(element))
1631 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1632 XS = 2 * Step[laser.current_angle].x;
1633 YS = 2 * Step[laser.current_angle].y;
1636 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1638 AddLaserEdge(LX, LY);
1639 AddDamagedField(ELX, ELY);
1641 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1644 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1646 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1651 LX += step_size * XS;
1652 LY += step_size * YS;
1654 laser.num_beamers++;
1663 boolean HitOnlyAnEdge(int element, int hit_mask)
1665 /* check if the laser hit only the edge of an element and, if so, go on */
1668 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1671 if ((hit_mask == HIT_MASK_TOPLEFT ||
1672 hit_mask == HIT_MASK_TOPRIGHT ||
1673 hit_mask == HIT_MASK_BOTTOMLEFT ||
1674 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1675 laser.current_angle % 4) /* angle is not 90° */
1679 if (hit_mask == HIT_MASK_TOPLEFT)
1684 else if (hit_mask == HIT_MASK_TOPRIGHT)
1689 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1694 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1700 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1706 printf("[HitOnlyAnEdge() == TRUE]\n");
1713 printf("[HitOnlyAnEdge() == FALSE]\n");
1719 boolean HitPolarizer(int element, int hit_mask)
1721 if (HitOnlyAnEdge(element, hit_mask))
1724 if (IS_DF_GRID(element))
1726 int grid_angle = get_element_angle(element);
1729 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1730 grid_angle, laser.current_angle);
1733 AddLaserEdge(LX, LY);
1734 AddDamagedField(ELX, ELY);
1737 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1739 if (laser.current_angle == grid_angle ||
1740 laser.current_angle == get_opposite_angle(grid_angle))
1742 /* skip the whole element before continuing the scan */
1748 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1750 if (LX/TILEX > ELX || LY/TILEY > ELY)
1752 /* skipping scan positions to the right and down skips one scan
1753 position too much, because this is only the top left scan position
1754 of totally four scan positions (plus one to the right, one to the
1755 bottom and one to the bottom right) */
1761 AddLaserEdge(LX, LY);
1767 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1769 LX / TILEX, LY / TILEY,
1770 LX % TILEX, LY % TILEY);
1775 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1777 return HitReflectingWalls(element, hit_mask);
1781 return HitAbsorbingWalls(element, hit_mask);
1784 else if (IS_GRID_STEEL(element))
1786 return HitReflectingWalls(element, hit_mask);
1788 else /* IS_GRID_WOOD */
1790 return HitAbsorbingWalls(element, hit_mask);
1796 boolean HitBlock(int element, int hit_mask)
1798 boolean check = FALSE;
1800 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1801 game_mm.num_keys == 0)
1804 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1807 int ex = ELX * TILEX + 14;
1808 int ey = ELY * TILEY + 14;
1812 for (i = 1; i < 32; i++)
1817 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1822 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1823 return HitAbsorbingWalls(element, hit_mask);
1827 AddLaserEdge(LX - XS, LY - YS);
1828 AddDamagedField(ELX, ELY);
1831 Box[ELX][ELY] = laser.num_edges;
1833 return HitReflectingWalls(element, hit_mask);
1836 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1838 int xs = XS / 2, ys = YS / 2;
1839 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1840 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1842 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1843 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1845 laser.overloaded = (element == EL_GATE_STONE);
1850 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1851 (hit_mask == HIT_MASK_TOP ||
1852 hit_mask == HIT_MASK_LEFT ||
1853 hit_mask == HIT_MASK_RIGHT ||
1854 hit_mask == HIT_MASK_BOTTOM))
1855 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1856 hit_mask == HIT_MASK_BOTTOM),
1857 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1858 hit_mask == HIT_MASK_RIGHT));
1859 AddLaserEdge(LX, LY);
1865 if (element == EL_GATE_STONE && Box[ELX][ELY])
1867 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1879 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1881 int xs = XS / 2, ys = YS / 2;
1882 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1883 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1885 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1886 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1888 laser.overloaded = (element == EL_BLOCK_STONE);
1893 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1894 (hit_mask == HIT_MASK_TOP ||
1895 hit_mask == HIT_MASK_LEFT ||
1896 hit_mask == HIT_MASK_RIGHT ||
1897 hit_mask == HIT_MASK_BOTTOM))
1898 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1899 hit_mask == HIT_MASK_BOTTOM),
1900 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1901 hit_mask == HIT_MASK_RIGHT));
1902 AddDamagedField(ELX, ELY);
1904 LX = ELX * TILEX + 14;
1905 LY = ELY * TILEY + 14;
1907 AddLaserEdge(LX, LY);
1909 laser.stops_inside_element = TRUE;
1917 boolean HitLaserSource(int element, int hit_mask)
1919 if (HitOnlyAnEdge(element, hit_mask))
1922 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1924 laser.overloaded = TRUE;
1929 boolean HitLaserDestination(int element, int hit_mask)
1931 if (HitOnlyAnEdge(element, hit_mask))
1934 if (element != EL_EXIT_OPEN &&
1935 !(IS_RECEIVER(element) &&
1936 game_mm.kettles_still_needed == 0 &&
1937 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1939 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1944 if (IS_RECEIVER(element) ||
1945 (IS_22_5_ANGLE(laser.current_angle) &&
1946 (ELX != (LX + 6 * XS) / TILEX ||
1947 ELY != (LY + 6 * YS) / TILEY ||
1956 LX = ELX * TILEX + 14;
1957 LY = ELY * TILEY + 14;
1959 laser.stops_inside_element = TRUE;
1962 AddLaserEdge(LX, LY);
1963 AddDamagedField(ELX, ELY);
1965 if (game_mm.lights_still_needed == 0)
1966 game_mm.level_solved = TRUE;
1971 boolean HitReflectingWalls(int element, int hit_mask)
1973 /* check if laser hits side of a wall with an angle that is not 90° */
1974 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1975 hit_mask == HIT_MASK_LEFT ||
1976 hit_mask == HIT_MASK_RIGHT ||
1977 hit_mask == HIT_MASK_BOTTOM))
1979 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1984 if (!IS_DF_GRID(element))
1985 AddLaserEdge(LX, LY);
1987 /* check if laser hits wall with an angle of 45° */
1988 if (!IS_22_5_ANGLE(laser.current_angle))
1990 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1993 laser.current_angle = get_mirrored_angle(laser.current_angle,
1996 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1999 laser.current_angle = get_mirrored_angle(laser.current_angle,
2003 AddLaserEdge(LX, LY);
2005 XS = 2 * Step[laser.current_angle].x;
2006 YS = 2 * Step[laser.current_angle].y;
2010 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2012 laser.current_angle = get_mirrored_angle(laser.current_angle,
2017 if (!IS_DF_GRID(element))
2018 AddLaserEdge(LX, LY);
2023 if (!IS_DF_GRID(element))
2024 AddLaserEdge(LX, LY + YS / 2);
2027 if (!IS_DF_GRID(element))
2028 AddLaserEdge(LX, LY);
2031 YS = 2 * Step[laser.current_angle].y;
2035 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2037 laser.current_angle = get_mirrored_angle(laser.current_angle,
2042 if (!IS_DF_GRID(element))
2043 AddLaserEdge(LX, LY);
2048 if (!IS_DF_GRID(element))
2049 AddLaserEdge(LX + XS / 2, LY);
2052 if (!IS_DF_GRID(element))
2053 AddLaserEdge(LX, LY);
2056 XS = 2 * Step[laser.current_angle].x;
2062 /* reflection at the edge of reflecting DF style wall */
2063 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2065 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2066 hit_mask == HIT_MASK_TOPRIGHT) ||
2067 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2068 hit_mask == HIT_MASK_TOPLEFT) ||
2069 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2070 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2071 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2072 hit_mask == HIT_MASK_BOTTOMRIGHT))
2075 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2076 ANG_MIRROR_135 : ANG_MIRROR_45);
2078 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2080 AddDamagedField(ELX, ELY);
2081 AddLaserEdge(LX, LY);
2083 laser.current_angle = get_mirrored_angle(laser.current_angle,
2091 AddLaserEdge(LX, LY);
2097 /* reflection inside an edge of reflecting DF style wall */
2098 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2100 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2101 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2102 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2103 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2104 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2105 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2106 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2107 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2110 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2111 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2112 ANG_MIRROR_135 : ANG_MIRROR_45);
2114 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2117 AddDamagedField(ELX, ELY);
2120 AddLaserEdge(LX - XS, LY - YS);
2121 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2122 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2124 laser.current_angle = get_mirrored_angle(laser.current_angle,
2132 AddLaserEdge(LX, LY);
2138 /* check if laser hits DF style wall with an angle of 90° */
2139 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2141 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2142 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2143 (IS_VERT_ANGLE(laser.current_angle) &&
2144 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2146 /* laser at last step touched nothing or the same side of the wall */
2147 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2149 AddDamagedField(ELX, ELY);
2156 last_hit_mask = hit_mask;
2163 if (!HitOnlyAnEdge(element, hit_mask))
2165 laser.overloaded = TRUE;
2173 boolean HitAbsorbingWalls(int element, int hit_mask)
2175 if (HitOnlyAnEdge(element, hit_mask))
2179 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2181 AddLaserEdge(LX - XS, LY - YS);
2188 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2190 AddLaserEdge(LX - XS, LY - YS);
2196 if (IS_WALL_WOOD(element) ||
2197 IS_DF_WALL_WOOD(element) ||
2198 IS_GRID_WOOD(element) ||
2199 IS_GRID_WOOD_FIXED(element) ||
2200 IS_GRID_WOOD_AUTO(element) ||
2201 element == EL_FUSE_ON ||
2202 element == EL_BLOCK_WOOD ||
2203 element == EL_GATE_WOOD)
2205 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2210 if (IS_WALL_ICE(element))
2214 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
2215 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
2217 /* check if laser hits wall with an angle of 90° */
2218 if (IS_90_ANGLE(laser.current_angle))
2219 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2221 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2225 for (i = 0; i < 4; i++)
2227 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2228 mask = 15 - (8 >> i);
2229 else if (ABS(XS) == 4 &&
2231 (XS > 0) == (i % 2) &&
2232 (YS < 0) == (i / 2))
2233 mask = 3 + (i / 2) * 9;
2234 else if (ABS(YS) == 4 &&
2236 (XS < 0) == (i % 2) &&
2237 (YS > 0) == (i / 2))
2238 mask = 5 + (i % 2) * 5;
2242 laser.wall_mask = mask;
2244 else if (IS_WALL_AMOEBA(element))
2246 int elx = (LX - 2 * XS) / TILEX;
2247 int ely = (LY - 2 * YS) / TILEY;
2248 int element2 = Feld[elx][ely];
2251 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2253 laser.dest_element = EL_EMPTY;
2261 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2262 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2264 if (IS_90_ANGLE(laser.current_angle))
2265 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2267 laser.dest_element = element2 | EL_WALL_AMOEBA;
2269 laser.wall_mask = mask;
2275 void OpenExit(int x, int y)
2279 if (!MovDelay[x][y]) /* next animation frame */
2280 MovDelay[x][y] = 4 * delay;
2282 if (MovDelay[x][y]) /* wait some time before next frame */
2287 phase = MovDelay[x][y] / delay;
2289 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2290 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2292 if (!MovDelay[x][y])
2294 Feld[x][y] = EL_EXIT_OPEN;
2300 void OpenSurpriseBall(int x, int y)
2304 if (!MovDelay[x][y]) /* next animation frame */
2305 MovDelay[x][y] = 50 * delay;
2307 if (MovDelay[x][y]) /* wait some time before next frame */
2311 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2314 int graphic = el2gfx(Store[x][y]);
2316 int dx = RND(26), dy = RND(26);
2318 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2320 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2321 SX + x * TILEX + dx, SY + y * TILEY + dy);
2323 MarkTileDirty(x, y);
2326 if (!MovDelay[x][y])
2328 Feld[x][y] = Store[x][y];
2337 void MeltIce(int x, int y)
2342 if (!MovDelay[x][y]) /* next animation frame */
2343 MovDelay[x][y] = frames * delay;
2345 if (MovDelay[x][y]) /* wait some time before next frame */
2348 int wall_mask = Store2[x][y];
2349 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2352 phase = frames - MovDelay[x][y] / delay - 1;
2354 if (!MovDelay[x][y])
2358 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2359 Store[x][y] = Store2[x][y] = 0;
2361 DrawWalls_MM(x, y, Feld[x][y]);
2363 if (Feld[x][y] == EL_WALL_ICE)
2364 Feld[x][y] = EL_EMPTY;
2366 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2367 if (laser.damage[i].is_mirror)
2371 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2373 DrawLaser(0, DL_LASER_DISABLED);
2377 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2379 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2381 laser.redraw = TRUE;
2386 void GrowAmoeba(int x, int y)
2391 if (!MovDelay[x][y]) /* next animation frame */
2392 MovDelay[x][y] = frames * delay;
2394 if (MovDelay[x][y]) /* wait some time before next frame */
2397 int wall_mask = Store2[x][y];
2398 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2401 phase = MovDelay[x][y] / delay;
2403 if (!MovDelay[x][y])
2405 Feld[x][y] = real_element;
2406 Store[x][y] = Store2[x][y] = 0;
2408 DrawWalls_MM(x, y, Feld[x][y]);
2409 DrawLaser(0, DL_LASER_ENABLED);
2411 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2413 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2418 static void Explode_MM(int x, int y, int phase, int mode)
2420 int num_phase = 9, delay = 2;
2421 int last_phase = num_phase * delay;
2422 int half_phase = (num_phase / 2) * delay;
2424 laser.redraw = TRUE;
2426 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2428 int center_element = Feld[x][y];
2430 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2432 /* put moving element to center field (and let it explode there) */
2433 center_element = MovingOrBlocked2Element_MM(x, y);
2434 RemoveMovingField_MM(x, y);
2436 Feld[x][y] = center_element;
2439 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2440 Store[x][y] = center_element;
2442 Store[x][y] = EL_EMPTY;
2444 Store2[x][y] = mode;
2445 Feld[x][y] = EL_EXPLODING_OPAQUE;
2446 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2452 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2454 if (phase == half_phase)
2456 Feld[x][y] = EL_EXPLODING_TRANSP;
2458 if (x == ELX && y == ELY)
2462 if (phase == last_phase)
2464 if (Store[x][y] == EL_BOMB)
2466 DrawLaser(0, DL_LASER_DISABLED);
2469 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2470 Store[x][y] = EL_EMPTY;
2472 game_mm.game_over = TRUE;
2473 game_mm.game_over_cause = GAME_OVER_BOMB;
2475 laser.overloaded = FALSE;
2477 else if (IS_MCDUFFIN(Store[x][y]))
2479 Store[x][y] = EL_EMPTY;
2482 Feld[x][y] = Store[x][y];
2483 Store[x][y] = Store2[x][y] = 0;
2484 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2486 InitField(x, y, FALSE);
2489 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2491 int graphic = IMG_MM_DEFAULT_EXPLODING;
2492 int graphic_phase = (phase / delay - 1);
2496 if (Store2[x][y] == EX_KETTLE)
2498 if (graphic_phase < 3)
2500 graphic = IMG_MM_KETTLE_EXPLODING;
2502 else if (graphic_phase < 5)
2508 graphic = IMG_EMPTY;
2512 else if (Store2[x][y] == EX_SHORT)
2514 if (graphic_phase < 4)
2520 graphic = IMG_EMPTY;
2525 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2527 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2528 FX + x * TILEX, FY + y * TILEY);
2530 MarkTileDirty(x, y);
2534 static void Bang_MM(int x, int y)
2536 int element = Feld[x][y];
2537 int mode = EX_NORMAL;
2540 DrawLaser(0, DL_LASER_ENABLED);
2559 if (IS_PACMAN(element))
2560 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2561 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2562 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2563 else if (element == EL_KEY)
2564 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2566 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2568 Explode_MM(x, y, EX_PHASE_START, mode);
2571 void TurnRound(int x, int y)
2583 { 0, 0 }, { 0, 0 }, { 0, 0 },
2588 int left, right, back;
2592 { MV_DOWN, MV_UP, MV_RIGHT },
2593 { MV_UP, MV_DOWN, MV_LEFT },
2595 { MV_LEFT, MV_RIGHT, MV_DOWN },
2596 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2597 { MV_RIGHT, MV_LEFT, MV_UP }
2600 int element = Feld[x][y];
2601 int old_move_dir = MovDir[x][y];
2602 int right_dir = turn[old_move_dir].right;
2603 int back_dir = turn[old_move_dir].back;
2604 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2605 int right_x = x + right_dx, right_y = y + right_dy;
2607 if (element == EL_PACMAN)
2609 boolean can_turn_right = FALSE;
2611 if (IN_LEV_FIELD(right_x, right_y) &&
2612 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2613 can_turn_right = TRUE;
2616 MovDir[x][y] = right_dir;
2618 MovDir[x][y] = back_dir;
2624 static void StartMoving_MM(int x, int y)
2626 int element = Feld[x][y];
2631 if (CAN_MOVE(element))
2635 if (MovDelay[x][y]) /* wait some time before next movement */
2643 /* now make next step */
2645 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2647 if (element == EL_PACMAN &&
2648 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2649 !ObjHit(newx, newy, HIT_POS_CENTER))
2651 Store[newx][newy] = Feld[newx][newy];
2652 Feld[newx][newy] = EL_EMPTY;
2654 DrawField_MM(newx, newy);
2656 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2657 ObjHit(newx, newy, HIT_POS_CENTER))
2659 /* object was running against a wall */
2666 InitMovingField_MM(x, y, MovDir[x][y]);
2670 ContinueMoving_MM(x, y);
2673 static void ContinueMoving_MM(int x, int y)
2675 int element = Feld[x][y];
2676 int direction = MovDir[x][y];
2677 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2678 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2679 int horiz_move = (dx!=0);
2680 int newx = x + dx, newy = y + dy;
2681 int step = (horiz_move ? dx : dy) * TILEX / 8;
2683 MovPos[x][y] += step;
2685 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2687 Feld[x][y] = EL_EMPTY;
2688 Feld[newx][newy] = element;
2690 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2691 MovDelay[newx][newy] = 0;
2693 if (!CAN_MOVE(element))
2694 MovDir[newx][newy] = 0;
2697 DrawField_MM(newx, newy);
2699 Stop[newx][newy] = TRUE;
2701 if (element == EL_PACMAN)
2703 if (Store[newx][newy] == EL_BOMB)
2704 Bang_MM(newx, newy);
2706 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2707 (LX + 2 * XS) / TILEX == newx &&
2708 (LY + 2 * YS) / TILEY == newy)
2715 else /* still moving on */
2720 laser.redraw = TRUE;
2723 void ClickElement(int x, int y, int button)
2725 static unsigned int click_delay = 0;
2726 static int click_delay_value = CLICK_DELAY;
2727 static boolean new_button = TRUE;
2732 /* initialize static variables */
2734 click_delay_value = CLICK_DELAY;
2740 /* do not rotate objects hit by the laser after the game was solved */
2741 if (game_mm.level_solved && Hit[x][y])
2744 if (button == MB_RELEASED)
2747 click_delay_value = CLICK_DELAY;
2749 /* release eventually hold auto-rotating mirror */
2750 RotateMirror(x, y, MB_RELEASED);
2755 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2758 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2761 if (!IN_LEV_FIELD(x, y))
2764 if (Feld[x][y] == EL_EMPTY)
2767 element = Feld[x][y];
2769 if (IS_MIRROR(element) ||
2770 IS_BEAMER(element) ||
2771 IS_POLAR(element) ||
2772 IS_POLAR_CROSS(element) ||
2773 IS_DF_MIRROR(element) ||
2774 IS_DF_MIRROR_AUTO(element))
2776 RotateMirror(x, y, button);
2778 else if (IS_MCDUFFIN(element))
2780 if (!laser.fuse_off)
2782 DrawLaser(0, DL_LASER_DISABLED);
2789 element = get_rotated_element(element, BUTTON_ROTATION(button));
2790 laser.start_angle = get_element_angle(element);
2794 Feld[x][y] = element;
2801 if (!laser.fuse_off)
2804 else if (element == EL_FUSE_ON && laser.fuse_off)
2806 if (x != laser.fuse_x || y != laser.fuse_y)
2809 laser.fuse_off = FALSE;
2810 laser.fuse_x = laser.fuse_y = -1;
2812 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2815 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2817 laser.fuse_off = TRUE;
2820 laser.overloaded = FALSE;
2822 DrawLaser(0, DL_LASER_DISABLED);
2823 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2825 else if (element == EL_LIGHTBALL)
2828 RaiseScoreElement_MM(element);
2829 DrawLaser(0, DL_LASER_ENABLED);
2832 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2836 void RotateMirror(int x, int y, int button)
2838 if (button == MB_RELEASED)
2840 /* release eventually hold auto-rotating mirror */
2847 if (IS_MIRROR(Feld[x][y]) ||
2848 IS_POLAR_CROSS(Feld[x][y]) ||
2849 IS_POLAR(Feld[x][y]) ||
2850 IS_BEAMER(Feld[x][y]) ||
2851 IS_DF_MIRROR(Feld[x][y]) ||
2852 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2853 IS_GRID_WOOD_AUTO(Feld[x][y]))
2855 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2857 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2859 if (button == MB_LEFTBUTTON)
2861 /* left mouse button only for manual adjustment, no auto-rotating;
2862 freeze mirror for until mouse button released */
2866 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2868 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2872 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2874 int edge = Hit[x][y];
2880 DrawLaser(edge - 1, DL_LASER_DISABLED);
2884 else if (ObjHit(x, y, HIT_POS_CENTER))
2886 int edge = Hit[x][y];
2890 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2894 DrawLaser(edge - 1, DL_LASER_DISABLED);
2901 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2906 if ((IS_BEAMER(Feld[x][y]) ||
2907 IS_POLAR(Feld[x][y]) ||
2908 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2912 if (IS_BEAMER(Feld[x][y]))
2915 printf("TEST (%d, %d) [%d] [%d]\n",
2917 laser.beamer_edge, laser.beamer[1].num);
2927 DrawLaser(0, DL_LASER_ENABLED);
2931 void AutoRotateMirrors()
2935 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2938 for (x = 0; x < lev_fieldx; x++)
2940 for (y = 0; y < lev_fieldy; y++)
2942 int element = Feld[x][y];
2944 /* do not rotate objects hit by the laser after the game was solved */
2945 if (game_mm.level_solved && Hit[x][y])
2948 if (IS_DF_MIRROR_AUTO(element) ||
2949 IS_GRID_WOOD_AUTO(element) ||
2950 IS_GRID_STEEL_AUTO(element) ||
2951 element == EL_REFRACTOR)
2952 RotateMirror(x, y, MB_RIGHTBUTTON);
2957 boolean ObjHit(int obx, int oby, int bits)
2964 if (bits & HIT_POS_CENTER)
2966 if (CheckLaserPixel(SX + obx + 15,
2971 if (bits & HIT_POS_EDGE)
2973 for (i = 0; i < 4; i++)
2974 if (CheckLaserPixel(SX + obx + 31 * (i % 2),
2975 SY + oby + 31 * (i / 2)))
2979 if (bits & HIT_POS_BETWEEN)
2981 for (i = 0; i < 4; i++)
2982 if (CheckLaserPixel(SX + 4 + obx + 22 * (i % 2),
2983 SY + 4 + oby + 22 * (i / 2)))
2990 void DeletePacMan(int px, int py)
2996 if (game_mm.num_pacman <= 1)
2998 game_mm.num_pacman = 0;
3002 for (i = 0; i < game_mm.num_pacman; i++)
3003 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3006 game_mm.num_pacman--;
3008 for (j = i; j < game_mm.num_pacman; j++)
3010 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3011 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3012 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3016 void ColorCycling(void)
3018 static int CC, Cc = 0;
3020 static int color, old = 0xF00, new = 0x010, mult = 1;
3021 static unsigned short red, green, blue;
3023 if (color_status == STATIC_COLORS)
3028 if (CC < Cc || CC > Cc + 2)
3032 color = old + new * mult;
3038 if (ABS(mult) == 16)
3048 red = 0x0e00 * ((color & 0xF00) >> 8);
3049 green = 0x0e00 * ((color & 0x0F0) >> 4);
3050 blue = 0x0e00 * ((color & 0x00F));
3051 SetRGB(pen_magicolor[0], red, green, blue);
3053 red = 0x1111 * ((color & 0xF00) >> 8);
3054 green = 0x1111 * ((color & 0x0F0) >> 4);
3055 blue = 0x1111 * ((color & 0x00F));
3056 SetRGB(pen_magicolor[1], red, green, blue);
3060 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3067 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3070 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3072 element = Feld[x][y];
3074 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3075 StartMoving_MM(x, y);
3076 else if (IS_MOVING(x, y))
3077 ContinueMoving_MM(x, y);
3078 else if (IS_EXPLODING(element))
3079 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3080 else if (element == EL_EXIT_OPENING)
3082 else if (element == EL_GRAY_BALL_OPENING)
3083 OpenSurpriseBall(x, y);
3084 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3086 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3090 AutoRotateMirrors();
3093 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3095 /* redraw after Explode_MM() ... */
3097 DrawLaser(0, DL_LASER_ENABLED);
3098 laser.redraw = FALSE;
3103 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3107 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3109 DrawLaser(0, DL_LASER_DISABLED);
3114 if (FrameReached(&energy_delay, ENERGY_DELAY))
3116 if (game_mm.energy_left > 0)
3118 game_mm.energy_left--;
3121 BlitBitmap(pix[PIX_DOOR], drawto,
3122 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3123 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3124 DX_ENERGY, DY_ENERGY);
3126 redraw_mask |= REDRAW_DOOR_1;
3128 else if (setup.time_limit && !game_mm.game_over)
3132 for (i = 15; i >= 0; i--)
3135 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3137 pen_ray = GetPixelFromRGB(window,
3138 native_mm_level.laser_red * 0x11 * i,
3139 native_mm_level.laser_green * 0x11 * i,
3140 native_mm_level.laser_blue * 0x11 * i);
3142 DrawLaser(0, DL_LASER_ENABLED);
3147 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3150 DrawLaser(0, DL_LASER_DISABLED);
3151 game_mm.game_over = TRUE;
3152 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3155 if (Request("Out of magic energy ! Play it again ?",
3156 REQ_ASK | REQ_STAY_CLOSED))
3162 game_status = MAINMENU;
3171 element = laser.dest_element;
3174 if (element != Feld[ELX][ELY])
3176 printf("element == %d, Feld[ELX][ELY] == %d\n",
3177 element, Feld[ELX][ELY]);
3181 if (!laser.overloaded && laser.overload_value == 0 &&
3182 element != EL_BOMB &&
3183 element != EL_MINE &&
3184 element != EL_BALL_GRAY &&
3185 element != EL_BLOCK_STONE &&
3186 element != EL_BLOCK_WOOD &&
3187 element != EL_FUSE_ON &&
3188 element != EL_FUEL_FULL &&
3189 !IS_WALL_ICE(element) &&
3190 !IS_WALL_AMOEBA(element))
3193 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3194 (!laser.overloaded && laser.overload_value > 0)) &&
3195 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3197 if (laser.overloaded)
3198 laser.overload_value++;
3200 laser.overload_value--;
3202 if (game_mm.cheat_no_overload)
3204 laser.overloaded = FALSE;
3205 laser.overload_value = 0;
3208 game_mm.laser_overload_value = laser.overload_value;
3210 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3212 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3213 int color_down = 0xFF - color_up;
3216 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3217 (15 - (laser.overload_value / 6)) * color_scale);
3220 GetPixelFromRGB(window,
3221 (native_mm_level.laser_red ? 0xFF : color_up),
3222 (native_mm_level.laser_green ? color_down : 0x00),
3223 (native_mm_level.laser_blue ? color_down : 0x00));
3225 DrawLaser(0, DL_LASER_ENABLED);
3231 if (!laser.overloaded)
3232 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3233 else if (setup.sound_loops)
3234 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3236 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3238 if (laser.overloaded)
3241 BlitBitmap(pix[PIX_DOOR], drawto,
3242 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3243 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3244 - laser.overload_value,
3245 OVERLOAD_XSIZE, laser.overload_value,
3246 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3247 - laser.overload_value);
3249 redraw_mask |= REDRAW_DOOR_1;
3254 BlitBitmap(pix[PIX_DOOR], drawto,
3255 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3256 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3257 DX_OVERLOAD, DY_OVERLOAD);
3259 redraw_mask |= REDRAW_DOOR_1;
3262 if (laser.overload_value == MAX_LASER_OVERLOAD)
3266 for (i = 15; i >= 0; i--)
3269 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3272 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3274 DrawLaser(0, DL_LASER_ENABLED);
3279 DrawLaser(0, DL_LASER_DISABLED);
3281 game_mm.game_over = TRUE;
3282 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3285 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3286 REQ_ASK | REQ_STAY_CLOSED))
3292 game_status = MAINMENU;
3306 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3308 if (game_mm.cheat_no_explosion)
3312 laser.num_damages--;
3313 DrawLaser(0, DL_LASER_DISABLED);
3314 laser.num_edges = 0;
3319 laser.dest_element = EL_EXPLODING_OPAQUE;
3323 laser.num_damages--;
3324 DrawLaser(0, DL_LASER_DISABLED);
3326 laser.num_edges = 0;
3327 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3329 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3330 REQ_ASK | REQ_STAY_CLOSED))
3336 game_status = MAINMENU;
3344 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3346 laser.fuse_off = TRUE;
3350 DrawLaser(0, DL_LASER_DISABLED);
3351 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3354 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3356 static int new_elements[] =
3359 EL_MIRROR_FIXED_START,
3361 EL_POLAR_CROSS_START,
3367 int num_new_elements = sizeof(new_elements) / sizeof(int);
3368 int new_element = new_elements[RND(num_new_elements)];
3370 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3371 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3373 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3384 element = EL_MIRROR_START + RND(16);
3390 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3397 element = (rnd == 0 ? EL_FUSE_ON :
3398 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3399 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3400 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3401 EL_MIRROR_FIXED_START + rnd - 25);
3406 graphic = el2gfx(element);
3408 for (i = 0; i < 50; i++)
3414 BlitBitmap(pix[PIX_BACK], drawto,
3415 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3416 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3417 SX + ELX * TILEX + x,
3418 SY + ELY * TILEY + y);
3420 MarkTileDirty(ELX, ELY);
3423 DrawLaser(0, DL_LASER_ENABLED);
3428 Feld[ELX][ELY] = element;
3429 DrawField_MM(ELX, ELY);
3432 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3435 /* above stuff: GRAY BALL -> PRISM !!! */
3437 LX = ELX * TILEX + 14;
3438 LY = ELY * TILEY + 14;
3439 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3446 laser.num_edges -= 2;
3447 laser.num_damages--;
3451 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3452 if (laser.damage[i].is_mirror)
3456 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3458 DrawLaser(0, DL_LASER_DISABLED);
3460 DrawLaser(0, DL_LASER_DISABLED);
3466 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3473 if (IS_WALL_ICE(element) && CT > 50)
3475 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3478 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3479 Store[ELX][ELY] = EL_WALL_ICE;
3480 Store2[ELX][ELY] = laser.wall_mask;
3482 laser.dest_element = Feld[ELX][ELY];
3487 for (i = 0; i < 5; i++)
3493 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3497 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3502 if (Feld[ELX][ELY] == EL_WALL_ICE)
3503 Feld[ELX][ELY] = EL_EMPTY;
3507 LX = laser.edge[laser.num_edges].x - (SX + 2);
3508 LY = laser.edge[laser.num_edges].y - (SY + 2);
3511 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3512 if (laser.damage[i].is_mirror)
3516 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3518 DrawLaser(0, DL_LASER_DISABLED);
3525 if (IS_WALL_AMOEBA(element) && CT > 60)
3527 int k1, k2, k3, dx, dy, de, dm;
3528 int element2 = Feld[ELX][ELY];
3530 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3533 for (i = laser.num_damages - 1; i >= 0; i--)
3534 if (laser.damage[i].is_mirror)
3537 r = laser.num_edges;
3538 d = laser.num_damages;
3545 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3548 DrawLaser(0, DL_LASER_ENABLED);
3551 x = laser.damage[k1].x;
3552 y = laser.damage[k1].y;
3557 for (i = 0; i < 4; i++)
3559 if (laser.wall_mask & (1 << i))
3561 if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3562 SY + ELY * TILEY + 31 * (i / 2)))
3565 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3566 SY + ELY * TILEY + 14 + (i / 2) * 2))
3573 for (i = 0; i < 4; i++)
3575 if (laser.wall_mask & (1 << i))
3577 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3578 SY + ELY * TILEY + 31 * (i / 2)))
3585 if (laser.num_beamers > 0 ||
3586 k1 < 1 || k2 < 4 || k3 < 4 ||
3587 CheckLaserPixel(SX + ELX * TILEX + 14,
3588 SY + ELY * TILEY + 14))
3590 laser.num_edges = r;
3591 laser.num_damages = d;
3593 DrawLaser(0, DL_LASER_DISABLED);
3596 Feld[ELX][ELY] = element | laser.wall_mask;
3600 de = Feld[ELX][ELY];
3601 dm = laser.wall_mask;
3605 int x = ELX, y = ELY;
3606 int wall_mask = laser.wall_mask;
3609 DrawLaser(0, DL_LASER_ENABLED);
3611 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3613 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3614 Store[x][y] = EL_WALL_AMOEBA;
3615 Store2[x][y] = wall_mask;
3621 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3623 DrawLaser(0, DL_LASER_ENABLED);
3625 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3627 for (i = 4; i >= 0; i--)
3629 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3635 DrawLaser(0, DL_LASER_ENABLED);
3640 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3641 laser.stops_inside_element && CT > native_mm_level.time_block)
3646 if (ABS(XS) > ABS(YS))
3653 for (i = 0; i < 4; i++)
3660 x = ELX + Step[k * 4].x;
3661 y = ELY + Step[k * 4].y;
3663 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3666 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3674 laser.overloaded = (element == EL_BLOCK_STONE);
3679 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3682 Feld[x][y] = element;
3684 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3687 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3689 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3690 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3698 if (element == EL_FUEL_FULL && CT > 10)
3700 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3703 BlitBitmap(pix[PIX_DOOR], drawto,
3704 DOOR_GFX_PAGEX4 + XX_ENERGY,
3705 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3706 ENERGY_XSIZE, i, DX_ENERGY,
3707 DY_ENERGY + ENERGY_YSIZE - i);
3710 redraw_mask |= REDRAW_DOOR_1;
3716 game_mm.energy_left = MAX_LASER_ENERGY;
3717 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3718 DrawField_MM(ELX, ELY);
3720 DrawLaser(0, DL_LASER_ENABLED);
3728 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3730 ClickElement(action.lx, action.ly, action.button);
3732 GameActions_MM_Ext(action, warp_mode);
3737 int mx, my, ox, oy, nx, ny;
3741 if (++pacman_nr >= game_mm.num_pacman)
3744 game_mm.pacman[pacman_nr].dir--;
3746 for (l = 1; l < 5; l++)
3748 game_mm.pacman[pacman_nr].dir++;
3750 if (game_mm.pacman[pacman_nr].dir > 4)
3751 game_mm.pacman[pacman_nr].dir = 1;
3753 if (game_mm.pacman[pacman_nr].dir % 2)
3756 my = game_mm.pacman[pacman_nr].dir - 2;
3761 mx = 3 - game_mm.pacman[pacman_nr].dir;
3764 ox = game_mm.pacman[pacman_nr].x;
3765 oy = game_mm.pacman[pacman_nr].y;
3768 element = Feld[nx][ny];
3770 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3773 if (!IS_EATABLE4PACMAN(element))
3776 if (ObjHit(nx, ny, HIT_POS_CENTER))
3779 Feld[ox][oy] = EL_EMPTY;
3781 EL_PACMAN_RIGHT - 1 +
3782 (game_mm.pacman[pacman_nr].dir - 1 +
3783 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3785 game_mm.pacman[pacman_nr].x = nx;
3786 game_mm.pacman[pacman_nr].y = ny;
3788 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3790 if (element != EL_EMPTY)
3792 int graphic = el2gfx(Feld[nx][ny]);
3797 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3800 ox = SX + ox * TILEX;
3801 oy = SY + oy * TILEY;
3803 for (i = 1; i < 33; i += 2)
3804 BlitBitmap(bitmap, window,
3805 src_x, src_y, TILEX, TILEY,
3806 ox + i * mx, oy + i * my);
3807 Ct = Ct + FrameCounter - CT;
3810 DrawField_MM(nx, ny);
3813 if (!laser.fuse_off)
3815 DrawLaser(0, DL_LASER_ENABLED);
3817 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3819 AddDamagedField(nx, ny);
3821 laser.damage[laser.num_damages - 1].edge = 0;
3825 if (element == EL_BOMB)
3826 DeletePacMan(nx, ny);
3828 if (IS_WALL_AMOEBA(element) &&
3829 (LX + 2 * XS) / TILEX == nx &&
3830 (LY + 2 * YS) / TILEY == ny)
3843 boolean raise_level = FALSE;
3846 if (local_player->MovPos)
3849 local_player->LevelSolved = FALSE;
3852 if (game_mm.energy_left)
3854 if (setup.sound_loops)
3855 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3856 SND_CTRL_PLAY_LOOP);
3858 while (game_mm.energy_left > 0)
3860 if (!setup.sound_loops)
3861 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3864 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3865 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3870 game_mm.energy_left--;
3871 if (game_mm.energy_left >= 0)
3874 BlitBitmap(pix[PIX_DOOR], drawto,
3875 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3876 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3877 DX_ENERGY, DY_ENERGY);
3879 redraw_mask |= REDRAW_DOOR_1;
3886 if (setup.sound_loops)
3887 StopSound(SND_SIRR);
3889 else if (native_mm_level.time == 0) /* level without time limit */
3891 if (setup.sound_loops)
3892 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3893 SND_CTRL_PLAY_LOOP);
3895 while (TimePlayed < 999)
3897 if (!setup.sound_loops)
3898 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3899 if (TimePlayed < 999 && !(TimePlayed % 10))
3900 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3901 if (TimePlayed < 900 && !(TimePlayed % 10))
3907 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3914 if (setup.sound_loops)
3915 StopSound(SND_SIRR);
3922 CloseDoor(DOOR_CLOSE_1);
3924 Request("Level solved !", REQ_CONFIRM);
3926 if (level_nr == leveldir_current->handicap_level)
3928 leveldir_current->handicap_level++;
3929 SaveLevelSetup_SeriesInfo();
3932 if (level_editor_test_game)
3933 game_mm.score = -1; /* no highscore when playing from editor */
3934 else if (level_nr < leveldir_current->last_level)
3935 raise_level = TRUE; /* advance to next level */
3937 if ((hi_pos = NewHiScore_MM()) >= 0)
3939 game_status = HALLOFFAME;
3941 // DrawHallOfFame(hi_pos);
3948 game_status = MAINMENU;
3964 // LoadScore(level_nr);
3966 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3967 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3970 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3972 if (game_mm.score > highscore[k].Score)
3974 /* player has made it to the hall of fame */
3976 if (k < MAX_SCORE_ENTRIES - 1)
3978 int m = MAX_SCORE_ENTRIES - 1;
3981 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3982 if (!strcmp(setup.player_name, highscore[l].Name))
3984 if (m == k) /* player's new highscore overwrites his old one */
3988 for (l = m; l>k; l--)
3990 strcpy(highscore[l].Name, highscore[l - 1].Name);
3991 highscore[l].Score = highscore[l - 1].Score;
3998 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3999 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4000 highscore[k].Score = game_mm.score;
4007 else if (!strncmp(setup.player_name, highscore[k].Name,
4008 MAX_PLAYER_NAME_LEN))
4009 break; /* player already there with a higher score */
4014 // if (position >= 0)
4015 // SaveScore(level_nr);
4020 static void InitMovingField_MM(int x, int y, int direction)
4022 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4023 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4025 MovDir[x][y] = direction;
4026 MovDir[newx][newy] = direction;
4028 if (Feld[newx][newy] == EL_EMPTY)
4029 Feld[newx][newy] = EL_BLOCKED;
4032 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4034 int direction = MovDir[x][y];
4035 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4036 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4042 static void Blocked2Moving_MM(int x, int y,
4043 int *comes_from_x, int *comes_from_y)
4045 int oldx = x, oldy = y;
4046 int direction = MovDir[x][y];
4048 if (direction == MV_LEFT)
4050 else if (direction == MV_RIGHT)
4052 else if (direction == MV_UP)
4054 else if (direction == MV_DOWN)
4057 *comes_from_x = oldx;
4058 *comes_from_y = oldy;
4061 static int MovingOrBlocked2Element_MM(int x, int y)
4063 int element = Feld[x][y];
4065 if (element == EL_BLOCKED)
4069 Blocked2Moving_MM(x, y, &oldx, &oldy);
4071 return Feld[oldx][oldy];
4078 static void RemoveField(int x, int y)
4080 Feld[x][y] = EL_EMPTY;
4087 static void RemoveMovingField_MM(int x, int y)
4089 int oldx = x, oldy = y, newx = x, newy = y;
4091 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4094 if (IS_MOVING(x, y))
4096 Moving2Blocked_MM(x, y, &newx, &newy);
4097 if (Feld[newx][newy] != EL_BLOCKED)
4100 else if (Feld[x][y] == EL_BLOCKED)
4102 Blocked2Moving_MM(x, y, &oldx, &oldy);
4103 if (!IS_MOVING(oldx, oldy))
4107 Feld[oldx][oldy] = EL_EMPTY;
4108 Feld[newx][newy] = EL_EMPTY;
4109 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4110 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4112 DrawLevelField_MM(oldx, oldy);
4113 DrawLevelField_MM(newx, newy);
4116 void PlaySoundLevel(int x, int y, int sound_nr)
4118 int sx = SCREENX(x), sy = SCREENY(y);
4120 int silence_distance = 8;
4122 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4123 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4126 if (!IN_LEV_FIELD(x, y) ||
4127 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4128 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4131 volume = SOUND_MAX_VOLUME;
4134 stereo = (sx - SCR_FIELDX/2) * 12;
4136 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4137 if (stereo > SOUND_MAX_RIGHT)
4138 stereo = SOUND_MAX_RIGHT;
4139 if (stereo < SOUND_MAX_LEFT)
4140 stereo = SOUND_MAX_LEFT;
4143 if (!IN_SCR_FIELD(sx, sy))
4145 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4146 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4148 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4151 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4154 static void RaiseScore_MM(int value)
4156 game_mm.score += value;
4159 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4164 void RaiseScoreElement_MM(int element)
4169 case EL_PACMAN_RIGHT:
4171 case EL_PACMAN_LEFT:
4172 case EL_PACMAN_DOWN:
4173 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4177 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4182 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4186 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);