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);
3152 DrawLaser(0, DL_LASER_DISABLED);
3153 game_mm.game_over = TRUE;
3154 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3157 if (Request("Out of magic energy ! Play it again ?",
3158 REQ_ASK | REQ_STAY_CLOSED))
3164 game_status = MAINMENU;
3173 element = laser.dest_element;
3176 if (element != Feld[ELX][ELY])
3178 printf("element == %d, Feld[ELX][ELY] == %d\n",
3179 element, Feld[ELX][ELY]);
3183 if (!laser.overloaded && laser.overload_value == 0 &&
3184 element != EL_BOMB &&
3185 element != EL_MINE &&
3186 element != EL_BALL_GRAY &&
3187 element != EL_BLOCK_STONE &&
3188 element != EL_BLOCK_WOOD &&
3189 element != EL_FUSE_ON &&
3190 element != EL_FUEL_FULL &&
3191 !IS_WALL_ICE(element) &&
3192 !IS_WALL_AMOEBA(element))
3195 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3196 (!laser.overloaded && laser.overload_value > 0)) &&
3197 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3199 if (laser.overloaded)
3200 laser.overload_value++;
3202 laser.overload_value--;
3204 if (game_mm.cheat_no_overload)
3206 laser.overloaded = FALSE;
3207 laser.overload_value = 0;
3210 game_mm.laser_overload_value = laser.overload_value;
3212 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3214 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3215 int color_down = 0xFF - color_up;
3218 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3219 (15 - (laser.overload_value / 6)) * color_scale);
3222 GetPixelFromRGB(window,
3223 (native_mm_level.laser_red ? 0xFF : color_up),
3224 (native_mm_level.laser_green ? color_down : 0x00),
3225 (native_mm_level.laser_blue ? color_down : 0x00));
3227 DrawLaser(0, DL_LASER_ENABLED);
3233 if (!laser.overloaded)
3234 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3235 else if (setup.sound_loops)
3236 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3238 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3240 if (laser.overloaded)
3243 BlitBitmap(pix[PIX_DOOR], drawto,
3244 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3245 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3246 - laser.overload_value,
3247 OVERLOAD_XSIZE, laser.overload_value,
3248 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3249 - laser.overload_value);
3251 redraw_mask |= REDRAW_DOOR_1;
3256 BlitBitmap(pix[PIX_DOOR], drawto,
3257 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3258 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3259 DX_OVERLOAD, DY_OVERLOAD);
3261 redraw_mask |= REDRAW_DOOR_1;
3264 if (laser.overload_value == MAX_LASER_OVERLOAD)
3268 for (i = 15; i >= 0; i--)
3271 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3274 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3276 DrawLaser(0, DL_LASER_ENABLED);
3281 DrawLaser(0, DL_LASER_DISABLED);
3283 game_mm.game_over = TRUE;
3284 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3287 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3288 REQ_ASK | REQ_STAY_CLOSED))
3294 game_status = MAINMENU;
3308 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3310 if (game_mm.cheat_no_explosion)
3314 laser.num_damages--;
3315 DrawLaser(0, DL_LASER_DISABLED);
3316 laser.num_edges = 0;
3321 laser.dest_element = EL_EXPLODING_OPAQUE;
3325 laser.num_damages--;
3326 DrawLaser(0, DL_LASER_DISABLED);
3328 laser.num_edges = 0;
3329 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3331 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3332 REQ_ASK | REQ_STAY_CLOSED))
3338 game_status = MAINMENU;
3346 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3348 laser.fuse_off = TRUE;
3352 DrawLaser(0, DL_LASER_DISABLED);
3353 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3356 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3358 static int new_elements[] =
3361 EL_MIRROR_FIXED_START,
3363 EL_POLAR_CROSS_START,
3369 int num_new_elements = sizeof(new_elements) / sizeof(int);
3370 int new_element = new_elements[RND(num_new_elements)];
3372 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3373 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3375 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3386 element = EL_MIRROR_START + RND(16);
3392 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3399 element = (rnd == 0 ? EL_FUSE_ON :
3400 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3401 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3402 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3403 EL_MIRROR_FIXED_START + rnd - 25);
3408 graphic = el2gfx(element);
3410 for (i = 0; i < 50; i++)
3416 BlitBitmap(pix[PIX_BACK], drawto,
3417 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3418 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3419 SX + ELX * TILEX + x,
3420 SY + ELY * TILEY + y);
3422 MarkTileDirty(ELX, ELY);
3425 DrawLaser(0, DL_LASER_ENABLED);
3430 Feld[ELX][ELY] = element;
3431 DrawField_MM(ELX, ELY);
3434 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3437 /* above stuff: GRAY BALL -> PRISM !!! */
3439 LX = ELX * TILEX + 14;
3440 LY = ELY * TILEY + 14;
3441 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3448 laser.num_edges -= 2;
3449 laser.num_damages--;
3453 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3454 if (laser.damage[i].is_mirror)
3458 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3460 DrawLaser(0, DL_LASER_DISABLED);
3462 DrawLaser(0, DL_LASER_DISABLED);
3468 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3475 if (IS_WALL_ICE(element) && CT > 50)
3477 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3480 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3481 Store[ELX][ELY] = EL_WALL_ICE;
3482 Store2[ELX][ELY] = laser.wall_mask;
3484 laser.dest_element = Feld[ELX][ELY];
3489 for (i = 0; i < 5; i++)
3495 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3499 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3504 if (Feld[ELX][ELY] == EL_WALL_ICE)
3505 Feld[ELX][ELY] = EL_EMPTY;
3509 LX = laser.edge[laser.num_edges].x - (SX + 2);
3510 LY = laser.edge[laser.num_edges].y - (SY + 2);
3513 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3514 if (laser.damage[i].is_mirror)
3518 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3520 DrawLaser(0, DL_LASER_DISABLED);
3527 if (IS_WALL_AMOEBA(element) && CT > 60)
3529 int k1, k2, k3, dx, dy, de, dm;
3530 int element2 = Feld[ELX][ELY];
3532 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3535 for (i = laser.num_damages - 1; i >= 0; i--)
3536 if (laser.damage[i].is_mirror)
3539 r = laser.num_edges;
3540 d = laser.num_damages;
3547 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3550 DrawLaser(0, DL_LASER_ENABLED);
3553 x = laser.damage[k1].x;
3554 y = laser.damage[k1].y;
3559 for (i = 0; i < 4; i++)
3561 if (laser.wall_mask & (1 << i))
3563 if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3564 SY + ELY * TILEY + 31 * (i / 2)))
3567 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3568 SY + ELY * TILEY + 14 + (i / 2) * 2))
3575 for (i = 0; i < 4; i++)
3577 if (laser.wall_mask & (1 << i))
3579 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3580 SY + ELY * TILEY + 31 * (i / 2)))
3587 if (laser.num_beamers > 0 ||
3588 k1 < 1 || k2 < 4 || k3 < 4 ||
3589 CheckLaserPixel(SX + ELX * TILEX + 14,
3590 SY + ELY * TILEY + 14))
3592 laser.num_edges = r;
3593 laser.num_damages = d;
3595 DrawLaser(0, DL_LASER_DISABLED);
3598 Feld[ELX][ELY] = element | laser.wall_mask;
3602 de = Feld[ELX][ELY];
3603 dm = laser.wall_mask;
3607 int x = ELX, y = ELY;
3608 int wall_mask = laser.wall_mask;
3611 DrawLaser(0, DL_LASER_ENABLED);
3613 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3615 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3616 Store[x][y] = EL_WALL_AMOEBA;
3617 Store2[x][y] = wall_mask;
3623 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3625 DrawLaser(0, DL_LASER_ENABLED);
3627 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3629 for (i = 4; i >= 0; i--)
3631 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3637 DrawLaser(0, DL_LASER_ENABLED);
3642 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3643 laser.stops_inside_element && CT > native_mm_level.time_block)
3648 if (ABS(XS) > ABS(YS))
3655 for (i = 0; i < 4; i++)
3662 x = ELX + Step[k * 4].x;
3663 y = ELY + Step[k * 4].y;
3665 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3668 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3676 laser.overloaded = (element == EL_BLOCK_STONE);
3681 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3684 Feld[x][y] = element;
3686 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3689 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3691 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3692 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3700 if (element == EL_FUEL_FULL && CT > 10)
3702 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3705 BlitBitmap(pix[PIX_DOOR], drawto,
3706 DOOR_GFX_PAGEX4 + XX_ENERGY,
3707 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3708 ENERGY_XSIZE, i, DX_ENERGY,
3709 DY_ENERGY + ENERGY_YSIZE - i);
3712 redraw_mask |= REDRAW_DOOR_1;
3718 game_mm.energy_left = MAX_LASER_ENERGY;
3719 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3720 DrawField_MM(ELX, ELY);
3722 DrawLaser(0, DL_LASER_ENABLED);
3730 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3732 ClickElement(action.lx, action.ly, action.button);
3734 GameActions_MM_Ext(action, warp_mode);
3739 int mx, my, ox, oy, nx, ny;
3743 if (++pacman_nr >= game_mm.num_pacman)
3746 game_mm.pacman[pacman_nr].dir--;
3748 for (l = 1; l < 5; l++)
3750 game_mm.pacman[pacman_nr].dir++;
3752 if (game_mm.pacman[pacman_nr].dir > 4)
3753 game_mm.pacman[pacman_nr].dir = 1;
3755 if (game_mm.pacman[pacman_nr].dir % 2)
3758 my = game_mm.pacman[pacman_nr].dir - 2;
3763 mx = 3 - game_mm.pacman[pacman_nr].dir;
3766 ox = game_mm.pacman[pacman_nr].x;
3767 oy = game_mm.pacman[pacman_nr].y;
3770 element = Feld[nx][ny];
3772 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3775 if (!IS_EATABLE4PACMAN(element))
3778 if (ObjHit(nx, ny, HIT_POS_CENTER))
3781 Feld[ox][oy] = EL_EMPTY;
3783 EL_PACMAN_RIGHT - 1 +
3784 (game_mm.pacman[pacman_nr].dir - 1 +
3785 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3787 game_mm.pacman[pacman_nr].x = nx;
3788 game_mm.pacman[pacman_nr].y = ny;
3790 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3792 if (element != EL_EMPTY)
3794 int graphic = el2gfx(Feld[nx][ny]);
3799 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3802 ox = SX + ox * TILEX;
3803 oy = SY + oy * TILEY;
3805 for (i = 1; i < 33; i += 2)
3806 BlitBitmap(bitmap, window,
3807 src_x, src_y, TILEX, TILEY,
3808 ox + i * mx, oy + i * my);
3809 Ct = Ct + FrameCounter - CT;
3812 DrawField_MM(nx, ny);
3815 if (!laser.fuse_off)
3817 DrawLaser(0, DL_LASER_ENABLED);
3819 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3821 AddDamagedField(nx, ny);
3823 laser.damage[laser.num_damages - 1].edge = 0;
3827 if (element == EL_BOMB)
3828 DeletePacMan(nx, ny);
3830 if (IS_WALL_AMOEBA(element) &&
3831 (LX + 2 * XS) / TILEX == nx &&
3832 (LY + 2 * YS) / TILEY == ny)
3845 boolean raise_level = FALSE;
3848 if (local_player->MovPos)
3851 local_player->LevelSolved = FALSE;
3854 if (game_mm.energy_left)
3856 if (setup.sound_loops)
3857 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3858 SND_CTRL_PLAY_LOOP);
3860 while (game_mm.energy_left > 0)
3862 if (!setup.sound_loops)
3863 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3866 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3867 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3872 game_mm.energy_left--;
3873 if (game_mm.energy_left >= 0)
3876 BlitBitmap(pix[PIX_DOOR], drawto,
3877 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3878 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3879 DX_ENERGY, DY_ENERGY);
3881 redraw_mask |= REDRAW_DOOR_1;
3888 if (setup.sound_loops)
3889 StopSound(SND_SIRR);
3891 else if (native_mm_level.time == 0) /* level without time limit */
3893 if (setup.sound_loops)
3894 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3895 SND_CTRL_PLAY_LOOP);
3897 while (TimePlayed < 999)
3899 if (!setup.sound_loops)
3900 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3901 if (TimePlayed < 999 && !(TimePlayed % 10))
3902 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3903 if (TimePlayed < 900 && !(TimePlayed % 10))
3909 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3916 if (setup.sound_loops)
3917 StopSound(SND_SIRR);
3924 CloseDoor(DOOR_CLOSE_1);
3926 Request("Level solved !", REQ_CONFIRM);
3928 if (level_nr == leveldir_current->handicap_level)
3930 leveldir_current->handicap_level++;
3931 SaveLevelSetup_SeriesInfo();
3934 if (level_editor_test_game)
3935 game_mm.score = -1; /* no highscore when playing from editor */
3936 else if (level_nr < leveldir_current->last_level)
3937 raise_level = TRUE; /* advance to next level */
3939 if ((hi_pos = NewHiScore_MM()) >= 0)
3941 game_status = HALLOFFAME;
3943 // DrawHallOfFame(hi_pos);
3950 game_status = MAINMENU;
3966 // LoadScore(level_nr);
3968 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3969 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3972 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3974 if (game_mm.score > highscore[k].Score)
3976 /* player has made it to the hall of fame */
3978 if (k < MAX_SCORE_ENTRIES - 1)
3980 int m = MAX_SCORE_ENTRIES - 1;
3983 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3984 if (!strcmp(setup.player_name, highscore[l].Name))
3986 if (m == k) /* player's new highscore overwrites his old one */
3990 for (l = m; l>k; l--)
3992 strcpy(highscore[l].Name, highscore[l - 1].Name);
3993 highscore[l].Score = highscore[l - 1].Score;
4000 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4001 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4002 highscore[k].Score = game_mm.score;
4009 else if (!strncmp(setup.player_name, highscore[k].Name,
4010 MAX_PLAYER_NAME_LEN))
4011 break; /* player already there with a higher score */
4016 // if (position >= 0)
4017 // SaveScore(level_nr);
4022 static void InitMovingField_MM(int x, int y, int direction)
4024 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4025 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4027 MovDir[x][y] = direction;
4028 MovDir[newx][newy] = direction;
4030 if (Feld[newx][newy] == EL_EMPTY)
4031 Feld[newx][newy] = EL_BLOCKED;
4034 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4036 int direction = MovDir[x][y];
4037 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4038 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4044 static void Blocked2Moving_MM(int x, int y,
4045 int *comes_from_x, int *comes_from_y)
4047 int oldx = x, oldy = y;
4048 int direction = MovDir[x][y];
4050 if (direction == MV_LEFT)
4052 else if (direction == MV_RIGHT)
4054 else if (direction == MV_UP)
4056 else if (direction == MV_DOWN)
4059 *comes_from_x = oldx;
4060 *comes_from_y = oldy;
4063 static int MovingOrBlocked2Element_MM(int x, int y)
4065 int element = Feld[x][y];
4067 if (element == EL_BLOCKED)
4071 Blocked2Moving_MM(x, y, &oldx, &oldy);
4073 return Feld[oldx][oldy];
4080 static void RemoveField(int x, int y)
4082 Feld[x][y] = EL_EMPTY;
4089 static void RemoveMovingField_MM(int x, int y)
4091 int oldx = x, oldy = y, newx = x, newy = y;
4093 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4096 if (IS_MOVING(x, y))
4098 Moving2Blocked_MM(x, y, &newx, &newy);
4099 if (Feld[newx][newy] != EL_BLOCKED)
4102 else if (Feld[x][y] == EL_BLOCKED)
4104 Blocked2Moving_MM(x, y, &oldx, &oldy);
4105 if (!IS_MOVING(oldx, oldy))
4109 Feld[oldx][oldy] = EL_EMPTY;
4110 Feld[newx][newy] = EL_EMPTY;
4111 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4112 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4114 DrawLevelField_MM(oldx, oldy);
4115 DrawLevelField_MM(newx, newy);
4118 void PlaySoundLevel(int x, int y, int sound_nr)
4120 int sx = SCREENX(x), sy = SCREENY(y);
4122 int silence_distance = 8;
4124 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4125 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4128 if (!IN_LEV_FIELD(x, y) ||
4129 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4130 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4133 volume = SOUND_MAX_VOLUME;
4136 stereo = (sx - SCR_FIELDX/2) * 12;
4138 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4139 if (stereo > SOUND_MAX_RIGHT)
4140 stereo = SOUND_MAX_RIGHT;
4141 if (stereo < SOUND_MAX_LEFT)
4142 stereo = SOUND_MAX_LEFT;
4145 if (!IN_SCR_FIELD(sx, sy))
4147 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4148 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4150 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4153 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4156 static void RaiseScore_MM(int value)
4158 game_mm.score += value;
4161 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4166 void RaiseScoreElement_MM(int element)
4171 case EL_PACMAN_RIGHT:
4173 case EL_PACMAN_LEFT:
4174 case EL_PACMAN_DOWN:
4175 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4179 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4184 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4188 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4197 /* ------------------------------------------------------------------------- */
4198 /* Mirror Magic game engine snapshot handling functions */
4199 /* ------------------------------------------------------------------------- */
4201 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4205 engine_snapshot_mm.game_mm = game_mm;
4206 engine_snapshot_mm.laser = laser;
4208 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4210 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4212 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4213 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4214 engine_snapshot_mm.Box[x][y] = Box[x][y];
4215 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4216 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4220 engine_snapshot_mm.LX = LX;
4221 engine_snapshot_mm.LY = LY;
4222 engine_snapshot_mm.XS = XS;
4223 engine_snapshot_mm.YS = YS;
4224 engine_snapshot_mm.ELX = ELX;
4225 engine_snapshot_mm.ELY = ELY;
4226 engine_snapshot_mm.CT = CT;
4227 engine_snapshot_mm.Ct = Ct;
4229 engine_snapshot_mm.last_LX = last_LX;
4230 engine_snapshot_mm.last_LY = last_LY;
4231 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4232 engine_snapshot_mm.hold_x = hold_x;
4233 engine_snapshot_mm.hold_y = hold_y;
4234 engine_snapshot_mm.pacman_nr = pacman_nr;
4236 engine_snapshot_mm.rotate_delay = rotate_delay;
4237 engine_snapshot_mm.pacman_delay = pacman_delay;
4238 engine_snapshot_mm.energy_delay = energy_delay;
4239 engine_snapshot_mm.overload_delay = overload_delay;
4242 void LoadEngineSnapshotValues_MM()
4246 /* stored engine snapshot buffers already restored at this point */
4248 game_mm = engine_snapshot_mm.game_mm;
4249 laser = engine_snapshot_mm.laser;
4251 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4253 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4255 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4256 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4257 Box[x][y] = engine_snapshot_mm.Box[x][y];
4258 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4259 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4263 LX = engine_snapshot_mm.LX;
4264 LY = engine_snapshot_mm.LY;
4265 XS = engine_snapshot_mm.XS;
4266 YS = engine_snapshot_mm.YS;
4267 ELX = engine_snapshot_mm.ELX;
4268 ELY = engine_snapshot_mm.ELY;
4269 CT = engine_snapshot_mm.CT;
4270 Ct = engine_snapshot_mm.Ct;
4272 last_LX = engine_snapshot_mm.last_LX;
4273 last_LY = engine_snapshot_mm.last_LY;
4274 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4275 hold_x = engine_snapshot_mm.hold_x;
4276 hold_y = engine_snapshot_mm.hold_y;
4277 pacman_nr = engine_snapshot_mm.pacman_nr;
4279 rotate_delay = engine_snapshot_mm.rotate_delay;
4280 pacman_delay = engine_snapshot_mm.pacman_delay;
4281 energy_delay = engine_snapshot_mm.energy_delay;
4282 overload_delay = engine_snapshot_mm.overload_delay;
4284 RedrawPlayfield_MM(TRUE);