1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 /* graphic position values for game controls */
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 /* values for Explode_MM() */
27 #define EX_PHASE_START 0
32 /* special positions in the game control window (relative to control window) */
41 #define XX_OVERLOAD 60
42 #define YY_OVERLOAD YY_ENERGY
44 /* special positions in the game control window (relative to main window) */
45 #define DX_LEVEL (DX + XX_LEVEL)
46 #define DY_LEVEL (DY + YY_LEVEL)
47 #define DX_KETTLES (DX + XX_KETTLES)
48 #define DY_KETTLES (DY + YY_KETTLES)
49 #define DX_SCORE (DX + XX_SCORE)
50 #define DY_SCORE (DY + YY_SCORE)
51 #define DX_ENERGY (DX + XX_ENERGY)
52 #define DY_ENERGY (DY + YY_ENERGY)
53 #define DX_OVERLOAD (DX + XX_OVERLOAD)
54 #define DY_OVERLOAD (DY + YY_OVERLOAD)
56 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
57 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
59 /* game button identifiers */
60 #define GAME_CTRL_ID_LEFT 0
61 #define GAME_CTRL_ID_MIDDLE 1
62 #define GAME_CTRL_ID_RIGHT 2
64 #define NUM_GAME_BUTTONS 3
66 /* values for DrawLaser() */
67 #define DL_LASER_DISABLED 0
68 #define DL_LASER_ENABLED 1
70 /* values for 'click_delay_value' in ClickElement() */
71 #define CLICK_DELAY_FIRST 12 /* delay (frames) after first click */
72 #define CLICK_DELAY 6 /* delay (frames) for pressed butten */
74 #define AUTO_ROTATE_DELAY CLICK_DELAY
75 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
76 #define NUM_INIT_CYCLE_STEPS 16
77 #define PACMAN_MOVE_DELAY 12
78 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
79 #define HEALTH_DEC_DELAY 3
80 #define HEALTH_INC_DELAY 9
81 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
83 #define BEGIN_NO_HEADLESS \
85 boolean last_headless = program.headless; \
87 program.headless = FALSE; \
89 #define END_NO_HEADLESS \
90 program.headless = last_headless; \
93 /* forward declaration for internal use */
94 static int MovingOrBlocked2Element_MM(int, int);
95 static void Bang_MM(int, int);
96 static void RaiseScore_MM(int);
97 static void RaiseScoreElement_MM(int);
98 static void RemoveMovingField_MM(int, int);
99 static void InitMovingField_MM(int, int, int);
100 static void ContinueMoving_MM(int, int);
101 static void Moving2Blocked_MM(int, int, int *, int *);
103 /* bitmap for laser beam detection */
104 static Bitmap *laser_bitmap = NULL;
106 /* variables for laser control */
107 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
108 static int hold_x = -1, hold_y = -1;
110 /* variables for pacman control */
111 static int pacman_nr = -1;
113 /* various game engine delay counters */
114 static unsigned int rotate_delay = 0;
115 static unsigned int pacman_delay = 0;
116 static unsigned int energy_delay = 0;
117 static unsigned int overload_delay = 0;
119 /* element masks for scanning pixels of MM elements */
120 static const char mm_masks[10][16][16 + 1] =
304 static int get_element_angle(int element)
306 int element_phase = get_element_phase(element);
308 if (IS_MIRROR_FIXED(element) ||
309 IS_MCDUFFIN(element) ||
311 IS_RECEIVER(element))
312 return 4 * element_phase;
314 return element_phase;
317 static int get_opposite_angle(int angle)
319 int opposite_angle = angle + ANG_RAY_180;
321 /* make sure "opposite_angle" is in valid interval [0, 15] */
322 return (opposite_angle + 16) % 16;
325 static int get_mirrored_angle(int laser_angle, int mirror_angle)
327 int reflected_angle = 16 - laser_angle + mirror_angle;
329 /* make sure "reflected_angle" is in valid interval [0, 15] */
330 return (reflected_angle + 16) % 16;
333 static void DrawLaserLines(struct XY *points, int num_points, int mode)
335 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
336 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
338 DrawLines(drawto, points, num_points, pixel_drawto);
342 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
347 static boolean CheckLaserPixel(int x, int y)
353 pixel = ReadPixel(laser_bitmap, x, y);
357 return (pixel == WHITE_PIXEL);
360 static void CheckExitMM()
362 int exit_element = EL_EMPTY;
366 static int xy[4][2] =
374 for (y = 0; y < lev_fieldy; y++)
376 for (x = 0; x < lev_fieldx; x++)
378 if (Feld[x][y] == EL_EXIT_CLOSED)
380 /* initiate opening animation of exit door */
381 Feld[x][y] = EL_EXIT_OPENING;
383 exit_element = EL_EXIT_OPEN;
387 else if (IS_RECEIVER(Feld[x][y]))
389 /* remove field that blocks receiver */
390 int phase = Feld[x][y] - EL_RECEIVER_START;
391 int blocking_x, blocking_y;
393 blocking_x = x + xy[phase][0];
394 blocking_y = y + xy[phase][1];
396 if (IN_LEV_FIELD(blocking_x, blocking_y))
398 Feld[blocking_x][blocking_y] = EL_EMPTY;
400 DrawField_MM(blocking_x, blocking_y);
403 exit_element = EL_RECEIVER;
410 if (exit_element != EL_EMPTY)
411 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
414 static void InitMovDir_MM(int x, int y)
416 int element = Feld[x][y];
417 static int direction[3][4] =
419 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
420 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
421 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
426 case EL_PACMAN_RIGHT:
430 Feld[x][y] = EL_PACMAN;
431 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
439 static void InitField(int x, int y, boolean init_game)
441 int element = Feld[x][y];
446 Feld[x][y] = EL_EMPTY;
451 if (native_mm_level.auto_count_kettles)
452 game_mm.kettles_still_needed++;
455 case EL_LIGHTBULB_OFF:
456 game_mm.lights_still_needed++;
460 if (IS_MIRROR(element) ||
461 IS_BEAMER_OLD(element) ||
462 IS_BEAMER(element) ||
464 IS_POLAR_CROSS(element) ||
465 IS_DF_MIRROR(element) ||
466 IS_DF_MIRROR_AUTO(element) ||
467 IS_GRID_STEEL_AUTO(element) ||
468 IS_GRID_WOOD_AUTO(element) ||
469 IS_FIBRE_OPTIC(element))
471 if (IS_BEAMER_OLD(element))
473 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
474 element = Feld[x][y];
477 if (!IS_FIBRE_OPTIC(element))
479 static int steps_grid_auto = 0;
481 if (game_mm.num_cycle == 0) /* initialize cycle steps for grids */
482 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
484 if (IS_GRID_STEEL_AUTO(element) ||
485 IS_GRID_WOOD_AUTO(element))
486 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
488 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
490 game_mm.cycle[game_mm.num_cycle].x = x;
491 game_mm.cycle[game_mm.num_cycle].y = y;
495 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
497 int beamer_nr = BEAMER_NR(element);
498 int nr = laser.beamer[beamer_nr][0].num;
500 laser.beamer[beamer_nr][nr].x = x;
501 laser.beamer[beamer_nr][nr].y = y;
502 laser.beamer[beamer_nr][nr].num = 1;
505 else if (IS_PACMAN(element))
509 else if (IS_MCDUFFIN(element) || IS_LASER(element))
511 laser.start_edge.x = x;
512 laser.start_edge.y = y;
513 laser.start_angle = get_element_angle(element);
520 static void InitCycleElements_RotateSingleStep()
524 if (game_mm.num_cycle == 0) /* no elements to cycle */
527 for (i = 0; i < game_mm.num_cycle; i++)
529 int x = game_mm.cycle[i].x;
530 int y = game_mm.cycle[i].y;
531 int step = SIGN(game_mm.cycle[i].steps);
532 int last_element = Feld[x][y];
533 int next_element = get_rotated_element(last_element, step);
535 if (!game_mm.cycle[i].steps)
538 Feld[x][y] = next_element;
541 game_mm.cycle[i].steps -= step;
545 static void InitLaser()
547 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
548 int step = (IS_LASER(start_element) ? 4 : 0);
550 LX = laser.start_edge.x * TILEX;
551 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
554 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
556 LY = laser.start_edge.y * TILEY;
557 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
558 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
562 XS = 2 * Step[laser.start_angle].x;
563 YS = 2 * Step[laser.start_angle].y;
565 laser.current_angle = laser.start_angle;
567 laser.num_damages = 0;
569 laser.num_beamers = 0;
570 laser.beamer_edge[0] = 0;
572 laser.dest_element = EL_EMPTY;
575 AddLaserEdge(LX, LY); /* set laser starting edge */
577 pen_ray = GetPixelFromRGB(window,
578 native_mm_level.laser_red * 0xFF,
579 native_mm_level.laser_green * 0xFF,
580 native_mm_level.laser_blue * 0xFF);
583 void InitGameEngine_MM()
589 /* initialize laser bitmap to current playfield (screen) size */
590 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
591 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
595 /* set global game control values */
596 game_mm.num_cycle = 0;
597 game_mm.num_pacman = 0;
600 game_mm.energy_left = 0; // later set to "native_mm_level.time"
601 game_mm.kettles_still_needed =
602 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
603 game_mm.lights_still_needed = 0;
604 game_mm.num_keys = 0;
606 game_mm.level_solved = FALSE;
607 game_mm.game_over = FALSE;
608 game_mm.game_over_cause = 0;
610 game_mm.laser_overload_value = 0;
611 game_mm.laser_enabled = FALSE;
613 /* set global laser control values (must be set before "InitLaser()") */
614 laser.start_edge.x = 0;
615 laser.start_edge.y = 0;
616 laser.start_angle = 0;
618 for (i = 0; i < MAX_NUM_BEAMERS; i++)
619 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
621 laser.overloaded = FALSE;
622 laser.overload_value = 0;
623 laser.fuse_off = FALSE;
624 laser.fuse_x = laser.fuse_y = -1;
626 laser.dest_element = EL_EMPTY;
645 ClickElement(-1, -1, -1);
647 for (x = 0; x < lev_fieldx; x++)
649 for (y = 0; y < lev_fieldy; y++)
651 Feld[x][y] = Ur[x][y];
652 Hit[x][y] = Box[x][y] = 0;
654 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
655 Store[x][y] = Store2[x][y] = 0;
659 InitField(x, y, TRUE);
664 CloseDoor(DOOR_CLOSE_1);
670 void InitGameActions_MM()
672 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
673 int cycle_steps_done = 0;
679 /* copy default game door content to main double buffer */
680 BlitBitmap(pix[PIX_DOOR], drawto,
681 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
685 DrawText(DX_LEVEL, DY_LEVEL,
686 int2str(level_nr, 2), FONT_TEXT_2);
687 DrawText(DX_KETTLES, DY_KETTLES,
688 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
689 DrawText(DX_SCORE, DY_SCORE,
690 int2str(game_mm.score, 4), FONT_TEXT_2);
699 /* copy actual game door content to door double buffer for OpenDoor() */
700 BlitBitmap(drawto, pix[PIX_DB_DOOR],
701 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
705 OpenDoor(DOOR_OPEN_ALL);
708 for (i = 0; i <= num_init_game_frames; i++)
710 if (i == num_init_game_frames)
711 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
712 else if (setup.sound_loops)
713 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
715 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
717 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
719 UpdateAndDisplayGameControlValues();
721 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
723 InitCycleElements_RotateSingleStep();
733 if (setup.quick_doors)
739 if (setup.sound_music && num_bg_loops)
740 PlayMusic(level_nr % num_bg_loops);
745 if (game_mm.kettles_still_needed == 0)
749 void AddLaserEdge(int lx, int ly)
751 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
753 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
758 laser.edge[laser.num_edges].x = SX + 2 + lx;
759 laser.edge[laser.num_edges].y = SY + 2 + ly;
765 void AddDamagedField(int ex, int ey)
767 laser.damage[laser.num_damages].is_mirror = FALSE;
768 laser.damage[laser.num_damages].angle = laser.current_angle;
769 laser.damage[laser.num_damages].edge = laser.num_edges;
770 laser.damage[laser.num_damages].x = ex;
771 laser.damage[laser.num_damages].y = ey;
781 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
782 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
784 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
790 static int getMaskFromElement(int element)
792 if (IS_GRID(element))
793 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
794 else if (IS_MCDUFFIN(element))
795 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
796 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
797 return IMG_MM_MASK_RECTANGLE;
799 return IMG_MM_MASK_CIRCLE;
807 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
808 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
811 /* follow laser beam until it hits something (at least the screen border) */
812 while (hit_mask == HIT_MASK_NO_HIT)
818 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
819 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
821 printf("ScanPixel: touched screen border!\n");
827 for (i = 0; i < 4; i++)
829 int px = LX + (i % 2) * 2;
830 int py = LY + (i / 2) * 2;
833 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
834 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
837 if (IN_LEV_FIELD(lx, ly))
839 int element = Feld[lx][ly];
841 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
845 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
847 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
849 pixel = ((element & (1 << pos)) ? 1 : 0);
853 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
855 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
860 pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
861 SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
864 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
865 hit_mask |= (1 << i);
868 if (hit_mask == HIT_MASK_NO_HIT)
870 /* hit nothing -- go on with another step */
882 int end = 0, rf = laser.num_edges;
884 /* do not scan laser again after the game was lost for whatever reason */
885 if (game_mm.game_over)
888 laser.overloaded = FALSE;
889 laser.stops_inside_element = FALSE;
891 DrawLaser(0, DL_LASER_ENABLED);
894 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
902 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
905 laser.overloaded = TRUE;
910 hit_mask = ScanPixel();
913 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
917 /* hit something -- check out what it was */
918 ELX = (LX + XS) / TILEX;
919 ELY = (LY + YS) / TILEY;
922 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
923 hit_mask, LX, LY, ELX, ELY);
926 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
929 laser.dest_element = element;
934 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
936 /* we have hit the top-right and bottom-left element --
937 choose the bottom-left one */
938 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
939 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
940 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
941 ELX = (LX - 2) / TILEX;
942 ELY = (LY + 2) / TILEY;
945 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
947 /* we have hit the top-left and bottom-right element --
948 choose the top-left one */
949 /* !!! SEE ABOVE !!! */
950 ELX = (LX - 2) / TILEX;
951 ELY = (LY - 2) / TILEY;
955 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
956 hit_mask, LX, LY, ELX, ELY);
959 element = Feld[ELX][ELY];
960 laser.dest_element = element;
963 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
966 LX % TILEX, LY % TILEY,
971 if (!IN_LEV_FIELD(ELX, ELY))
972 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
975 if (element == EL_EMPTY)
977 if (!HitOnlyAnEdge(element, hit_mask))
980 else if (element == EL_FUSE_ON)
982 if (HitPolarizer(element, hit_mask))
985 else if (IS_GRID(element) || IS_DF_GRID(element))
987 if (HitPolarizer(element, hit_mask))
990 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
991 element == EL_GATE_STONE || element == EL_GATE_WOOD)
993 if (HitBlock(element, hit_mask))
1000 else if (IS_MCDUFFIN(element))
1002 if (HitLaserSource(element, hit_mask))
1005 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1006 IS_RECEIVER(element))
1008 if (HitLaserDestination(element, hit_mask))
1011 else if (IS_WALL(element))
1013 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1015 if (HitReflectingWalls(element, hit_mask))
1020 if (HitAbsorbingWalls(element, hit_mask))
1026 if (HitElement(element, hit_mask))
1031 DrawLaser(rf - 1, DL_LASER_ENABLED);
1032 rf = laser.num_edges;
1036 if (laser.dest_element != Feld[ELX][ELY])
1038 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
1039 laser.dest_element, Feld[ELX][ELY]);
1043 if (!end && !laser.stops_inside_element && !StepBehind())
1046 printf("ScanLaser: Go one step back\n");
1052 AddLaserEdge(LX, LY);
1056 DrawLaser(rf - 1, DL_LASER_ENABLED);
1058 Ct = CT = FrameCounter;
1061 if (!IN_LEV_FIELD(ELX, ELY))
1062 printf("WARNING! (2) %d, %d\n", ELX, ELY);
1066 void DrawLaserExt(int start_edge, int num_edges, int mode)
1072 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
1073 start_edge, num_edges, mode);
1078 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
1085 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
1091 if (mode == DL_LASER_DISABLED)
1093 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
1097 /* now draw the laser to the backbuffer and (if enabled) to the screen */
1098 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1100 redraw_mask |= REDRAW_FIELD;
1102 if (mode == DL_LASER_ENABLED)
1105 /* after the laser was deleted, the "damaged" graphics must be restored */
1106 if (laser.num_damages)
1108 int damage_start = 0;
1111 /* determine the starting edge, from which graphics need to be restored */
1114 for (i = 0; i < laser.num_damages; i++)
1116 if (laser.damage[i].edge == start_edge + 1)
1125 /* restore graphics from this starting edge to the end of damage list */
1126 for (i = damage_start; i < laser.num_damages; i++)
1128 int lx = laser.damage[i].x;
1129 int ly = laser.damage[i].y;
1130 int element = Feld[lx][ly];
1132 if (Hit[lx][ly] == laser.damage[i].edge)
1133 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1136 if (Box[lx][ly] == laser.damage[i].edge)
1139 if (IS_DRAWABLE(element))
1140 DrawField_MM(lx, ly);
1143 elx = laser.damage[damage_start].x;
1144 ely = laser.damage[damage_start].y;
1145 element = Feld[elx][ely];
1148 if (IS_BEAMER(element))
1152 for (i = 0; i < laser.num_beamers; i++)
1153 printf("-> %d\n", laser.beamer_edge[i]);
1154 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
1155 mode, elx, ely, Hit[elx][ely], start_edge);
1156 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
1157 get_element_angle(element), laser.damage[damage_start].angle);
1161 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1162 laser.num_beamers > 0 &&
1163 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1165 /* element is outgoing beamer */
1166 laser.num_damages = damage_start + 1;
1168 if (IS_BEAMER(element))
1169 laser.current_angle = get_element_angle(element);
1173 /* element is incoming beamer or other element */
1174 laser.num_damages = damage_start;
1175 laser.current_angle = laser.damage[laser.num_damages].angle;
1180 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
1182 elx = laser.start_edge.x;
1183 ely = laser.start_edge.y;
1184 element = Feld[elx][ely];
1187 laser.num_edges = start_edge + 1;
1188 if (start_edge == 0)
1189 laser.current_angle = laser.start_angle;
1191 LX = laser.edge[start_edge].x - (SX + 2);
1192 LY = laser.edge[start_edge].y - (SY + 2);
1193 XS = 2 * Step[laser.current_angle].x;
1194 YS = 2 * Step[laser.current_angle].y;
1197 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
1203 if (IS_BEAMER(element) ||
1204 IS_FIBRE_OPTIC(element) ||
1205 IS_PACMAN(element) ||
1206 IS_POLAR(element) ||
1207 IS_POLAR_CROSS(element) ||
1208 element == EL_FUSE_ON)
1213 printf("element == %d\n", element);
1216 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
1217 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1221 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1222 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1223 (laser.num_beamers == 0 ||
1224 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1226 /* element is incoming beamer or other element */
1227 step_size = -step_size;
1232 if (IS_BEAMER(element))
1234 printf("start_edge == %d, laser.beamer_edge == %d\n",
1235 start_edge, laser.beamer_edge);
1239 LX += step_size * XS;
1240 LY += step_size * YS;
1242 else if (element != EL_EMPTY)
1251 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
1256 void DrawLaser(int start_edge, int mode)
1258 if (laser.num_edges - start_edge < 0)
1260 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
1265 /* check if laser is interrupted by beamer element */
1266 if (laser.num_beamers > 0 &&
1267 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1269 if (mode == DL_LASER_ENABLED)
1272 int tmp_start_edge = start_edge;
1274 /* draw laser segments forward from the start to the last beamer */
1275 for (i = 0; i < laser.num_beamers; i++)
1277 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1279 if (tmp_num_edges <= 0)
1283 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
1284 i, laser.beamer_edge[i], tmp_start_edge);
1287 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1289 tmp_start_edge = laser.beamer_edge[i];
1292 /* draw last segment from last beamer to the end */
1293 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1299 int last_num_edges = laser.num_edges;
1300 int num_beamers = laser.num_beamers;
1302 /* delete laser segments backward from the end to the first beamer */
1303 for (i = num_beamers - 1; i >= 0; i--)
1305 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1307 if (laser.beamer_edge[i] - start_edge <= 0)
1310 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1312 last_num_edges = laser.beamer_edge[i];
1313 laser.num_beamers--;
1317 if (last_num_edges - start_edge <= 0)
1318 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1319 last_num_edges, start_edge);
1322 // special case when rotating first beamer: delete laser edge on beamer
1323 // (but do not start scanning on previous edge to prevent mirror sound)
1324 if (last_num_edges - start_edge == 1 && start_edge > 0)
1325 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1327 /* delete first segment from start to the first beamer */
1328 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1333 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1336 game_mm.laser_enabled = mode;
1341 DrawLaser(0, game_mm.laser_enabled);
1344 boolean HitElement(int element, int hit_mask)
1346 if (HitOnlyAnEdge(element, hit_mask))
1349 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1350 element = MovingOrBlocked2Element_MM(ELX, ELY);
1353 printf("HitElement (1): element == %d\n", element);
1357 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1358 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1360 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1363 AddDamagedField(ELX, ELY);
1365 /* this is more precise: check if laser would go through the center */
1366 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1368 /* skip the whole element before continuing the scan */
1374 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1376 if (LX/TILEX > ELX || LY/TILEY > ELY)
1378 /* skipping scan positions to the right and down skips one scan
1379 position too much, because this is only the top left scan position
1380 of totally four scan positions (plus one to the right, one to the
1381 bottom and one to the bottom right) */
1391 printf("HitElement (2): element == %d\n", element);
1394 if (LX + 5 * XS < 0 ||
1404 printf("HitElement (3): element == %d\n", element);
1407 if (IS_POLAR(element) &&
1408 ((element - EL_POLAR_START) % 2 ||
1409 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1411 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1413 laser.num_damages--;
1418 if (IS_POLAR_CROSS(element) &&
1419 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1421 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1423 laser.num_damages--;
1428 if (!IS_BEAMER(element) &&
1429 !IS_FIBRE_OPTIC(element) &&
1430 !IS_GRID_WOOD(element) &&
1431 element != EL_FUEL_EMPTY)
1434 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1435 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1437 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1440 LX = ELX * TILEX + 14;
1441 LY = ELY * TILEY + 14;
1443 AddLaserEdge(LX, LY);
1446 if (IS_MIRROR(element) ||
1447 IS_MIRROR_FIXED(element) ||
1448 IS_POLAR(element) ||
1449 IS_POLAR_CROSS(element) ||
1450 IS_DF_MIRROR(element) ||
1451 IS_DF_MIRROR_AUTO(element) ||
1452 element == EL_PRISM ||
1453 element == EL_REFRACTOR)
1455 int current_angle = laser.current_angle;
1458 laser.num_damages--;
1460 AddDamagedField(ELX, ELY);
1462 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1465 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1467 if (IS_MIRROR(element) ||
1468 IS_MIRROR_FIXED(element) ||
1469 IS_DF_MIRROR(element) ||
1470 IS_DF_MIRROR_AUTO(element))
1471 laser.current_angle = get_mirrored_angle(laser.current_angle,
1472 get_element_angle(element));
1474 if (element == EL_PRISM || element == EL_REFRACTOR)
1475 laser.current_angle = RND(16);
1477 XS = 2 * Step[laser.current_angle].x;
1478 YS = 2 * Step[laser.current_angle].y;
1480 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1485 LX += step_size * XS;
1486 LY += step_size * YS;
1489 /* draw sparkles on mirror */
1490 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1491 current_angle != laser.current_angle)
1493 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1497 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1498 current_angle != laser.current_angle)
1499 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1502 (get_opposite_angle(laser.current_angle) ==
1503 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1505 return (laser.overloaded ? TRUE : FALSE);
1508 if (element == EL_FUEL_FULL)
1510 laser.stops_inside_element = TRUE;
1515 if (element == EL_BOMB || element == EL_MINE)
1517 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1519 if (element == EL_MINE)
1520 laser.overloaded = TRUE;
1523 if (element == EL_KETTLE ||
1524 element == EL_CELL ||
1525 element == EL_KEY ||
1526 element == EL_LIGHTBALL ||
1527 element == EL_PACMAN ||
1530 if (!IS_PACMAN(element))
1533 if (element == EL_PACMAN)
1536 if (element == EL_KETTLE || element == EL_CELL)
1538 if (game_mm.kettles_still_needed > 0)
1539 game_mm.kettles_still_needed--;
1541 if (game_mm.kettles_still_needed == 0)
1545 DrawLaser(0, DL_LASER_ENABLED);
1548 else if (element == EL_KEY)
1552 else if (IS_PACMAN(element))
1554 DeletePacMan(ELX, ELY);
1557 RaiseScoreElement_MM(element);
1562 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1564 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1566 DrawLaser(0, DL_LASER_ENABLED);
1568 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1570 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1571 game_mm.lights_still_needed--;
1575 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1576 game_mm.lights_still_needed++;
1579 DrawField_MM(ELX, ELY);
1580 DrawLaser(0, DL_LASER_ENABLED);
1585 laser.stops_inside_element = TRUE;
1591 printf("HitElement (4): element == %d\n", element);
1594 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1595 laser.num_beamers < MAX_NUM_BEAMERS &&
1596 laser.beamer[BEAMER_NR(element)][1].num)
1598 int beamer_angle = get_element_angle(element);
1599 int beamer_nr = BEAMER_NR(element);
1603 printf("HitElement (BEAMER): element == %d\n", element);
1606 laser.num_damages--;
1608 if (IS_FIBRE_OPTIC(element) ||
1609 laser.current_angle == get_opposite_angle(beamer_angle))
1613 LX = ELX * TILEX + 14;
1614 LY = ELY * TILEY + 14;
1616 AddLaserEdge(LX, LY);
1617 AddDamagedField(ELX, ELY);
1619 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1622 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1624 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1625 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1626 ELX = laser.beamer[beamer_nr][pos].x;
1627 ELY = laser.beamer[beamer_nr][pos].y;
1628 LX = ELX * TILEX + 14;
1629 LY = ELY * TILEY + 14;
1631 if (IS_BEAMER(element))
1633 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1634 XS = 2 * Step[laser.current_angle].x;
1635 YS = 2 * Step[laser.current_angle].y;
1638 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1640 AddLaserEdge(LX, LY);
1641 AddDamagedField(ELX, ELY);
1643 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1646 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1648 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1653 LX += step_size * XS;
1654 LY += step_size * YS;
1656 laser.num_beamers++;
1665 boolean HitOnlyAnEdge(int element, int hit_mask)
1667 /* check if the laser hit only the edge of an element and, if so, go on */
1670 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1673 if ((hit_mask == HIT_MASK_TOPLEFT ||
1674 hit_mask == HIT_MASK_TOPRIGHT ||
1675 hit_mask == HIT_MASK_BOTTOMLEFT ||
1676 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1677 laser.current_angle % 4) /* angle is not 90° */
1681 if (hit_mask == HIT_MASK_TOPLEFT)
1686 else if (hit_mask == HIT_MASK_TOPRIGHT)
1691 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1696 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1702 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1708 printf("[HitOnlyAnEdge() == TRUE]\n");
1715 printf("[HitOnlyAnEdge() == FALSE]\n");
1721 boolean HitPolarizer(int element, int hit_mask)
1723 if (HitOnlyAnEdge(element, hit_mask))
1726 if (IS_DF_GRID(element))
1728 int grid_angle = get_element_angle(element);
1731 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1732 grid_angle, laser.current_angle);
1735 AddLaserEdge(LX, LY);
1736 AddDamagedField(ELX, ELY);
1739 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1741 if (laser.current_angle == grid_angle ||
1742 laser.current_angle == get_opposite_angle(grid_angle))
1744 /* skip the whole element before continuing the scan */
1750 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1752 if (LX/TILEX > ELX || LY/TILEY > ELY)
1754 /* skipping scan positions to the right and down skips one scan
1755 position too much, because this is only the top left scan position
1756 of totally four scan positions (plus one to the right, one to the
1757 bottom and one to the bottom right) */
1763 AddLaserEdge(LX, LY);
1769 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1771 LX / TILEX, LY / TILEY,
1772 LX % TILEX, LY % TILEY);
1777 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1779 return HitReflectingWalls(element, hit_mask);
1783 return HitAbsorbingWalls(element, hit_mask);
1786 else if (IS_GRID_STEEL(element))
1788 return HitReflectingWalls(element, hit_mask);
1790 else /* IS_GRID_WOOD */
1792 return HitAbsorbingWalls(element, hit_mask);
1798 boolean HitBlock(int element, int hit_mask)
1800 boolean check = FALSE;
1802 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1803 game_mm.num_keys == 0)
1806 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1809 int ex = ELX * TILEX + 14;
1810 int ey = ELY * TILEY + 14;
1814 for (i = 1; i < 32; i++)
1819 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1824 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1825 return HitAbsorbingWalls(element, hit_mask);
1829 AddLaserEdge(LX - XS, LY - YS);
1830 AddDamagedField(ELX, ELY);
1833 Box[ELX][ELY] = laser.num_edges;
1835 return HitReflectingWalls(element, hit_mask);
1838 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1840 int xs = XS / 2, ys = YS / 2;
1841 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1842 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1844 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1845 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1847 laser.overloaded = (element == EL_GATE_STONE);
1852 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1853 (hit_mask == HIT_MASK_TOP ||
1854 hit_mask == HIT_MASK_LEFT ||
1855 hit_mask == HIT_MASK_RIGHT ||
1856 hit_mask == HIT_MASK_BOTTOM))
1857 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1858 hit_mask == HIT_MASK_BOTTOM),
1859 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1860 hit_mask == HIT_MASK_RIGHT));
1861 AddLaserEdge(LX, LY);
1867 if (element == EL_GATE_STONE && Box[ELX][ELY])
1869 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1881 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1883 int xs = XS / 2, ys = YS / 2;
1884 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1885 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1887 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1888 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1890 laser.overloaded = (element == EL_BLOCK_STONE);
1895 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1896 (hit_mask == HIT_MASK_TOP ||
1897 hit_mask == HIT_MASK_LEFT ||
1898 hit_mask == HIT_MASK_RIGHT ||
1899 hit_mask == HIT_MASK_BOTTOM))
1900 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1901 hit_mask == HIT_MASK_BOTTOM),
1902 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1903 hit_mask == HIT_MASK_RIGHT));
1904 AddDamagedField(ELX, ELY);
1906 LX = ELX * TILEX + 14;
1907 LY = ELY * TILEY + 14;
1909 AddLaserEdge(LX, LY);
1911 laser.stops_inside_element = TRUE;
1919 boolean HitLaserSource(int element, int hit_mask)
1921 if (HitOnlyAnEdge(element, hit_mask))
1924 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1926 laser.overloaded = TRUE;
1931 boolean HitLaserDestination(int element, int hit_mask)
1933 if (HitOnlyAnEdge(element, hit_mask))
1936 if (element != EL_EXIT_OPEN &&
1937 !(IS_RECEIVER(element) &&
1938 game_mm.kettles_still_needed == 0 &&
1939 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1941 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1946 if (IS_RECEIVER(element) ||
1947 (IS_22_5_ANGLE(laser.current_angle) &&
1948 (ELX != (LX + 6 * XS) / TILEX ||
1949 ELY != (LY + 6 * YS) / TILEY ||
1958 LX = ELX * TILEX + 14;
1959 LY = ELY * TILEY + 14;
1961 laser.stops_inside_element = TRUE;
1964 AddLaserEdge(LX, LY);
1965 AddDamagedField(ELX, ELY);
1967 if (game_mm.lights_still_needed == 0)
1968 game_mm.level_solved = TRUE;
1973 boolean HitReflectingWalls(int element, int hit_mask)
1975 /* check if laser hits side of a wall with an angle that is not 90° */
1976 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1977 hit_mask == HIT_MASK_LEFT ||
1978 hit_mask == HIT_MASK_RIGHT ||
1979 hit_mask == HIT_MASK_BOTTOM))
1981 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1986 if (!IS_DF_GRID(element))
1987 AddLaserEdge(LX, LY);
1989 /* check if laser hits wall with an angle of 45° */
1990 if (!IS_22_5_ANGLE(laser.current_angle))
1992 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1995 laser.current_angle = get_mirrored_angle(laser.current_angle,
1998 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2001 laser.current_angle = get_mirrored_angle(laser.current_angle,
2005 AddLaserEdge(LX, LY);
2007 XS = 2 * Step[laser.current_angle].x;
2008 YS = 2 * Step[laser.current_angle].y;
2012 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2014 laser.current_angle = get_mirrored_angle(laser.current_angle,
2019 if (!IS_DF_GRID(element))
2020 AddLaserEdge(LX, LY);
2025 if (!IS_DF_GRID(element))
2026 AddLaserEdge(LX, LY + YS / 2);
2029 if (!IS_DF_GRID(element))
2030 AddLaserEdge(LX, LY);
2033 YS = 2 * Step[laser.current_angle].y;
2037 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2039 laser.current_angle = get_mirrored_angle(laser.current_angle,
2044 if (!IS_DF_GRID(element))
2045 AddLaserEdge(LX, LY);
2050 if (!IS_DF_GRID(element))
2051 AddLaserEdge(LX + XS / 2, LY);
2054 if (!IS_DF_GRID(element))
2055 AddLaserEdge(LX, LY);
2058 XS = 2 * Step[laser.current_angle].x;
2064 /* reflection at the edge of reflecting DF style wall */
2065 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2067 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2068 hit_mask == HIT_MASK_TOPRIGHT) ||
2069 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2070 hit_mask == HIT_MASK_TOPLEFT) ||
2071 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2072 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2073 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2074 hit_mask == HIT_MASK_BOTTOMRIGHT))
2077 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2078 ANG_MIRROR_135 : ANG_MIRROR_45);
2080 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2082 AddDamagedField(ELX, ELY);
2083 AddLaserEdge(LX, LY);
2085 laser.current_angle = get_mirrored_angle(laser.current_angle,
2093 AddLaserEdge(LX, LY);
2099 /* reflection inside an edge of reflecting DF style wall */
2100 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2102 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2103 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2104 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2105 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2106 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2107 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2108 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2109 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2112 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2113 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2114 ANG_MIRROR_135 : ANG_MIRROR_45);
2116 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2119 AddDamagedField(ELX, ELY);
2122 AddLaserEdge(LX - XS, LY - YS);
2123 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2124 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2126 laser.current_angle = get_mirrored_angle(laser.current_angle,
2134 AddLaserEdge(LX, LY);
2140 /* check if laser hits DF style wall with an angle of 90° */
2141 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2143 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2144 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2145 (IS_VERT_ANGLE(laser.current_angle) &&
2146 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2148 /* laser at last step touched nothing or the same side of the wall */
2149 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2151 AddDamagedField(ELX, ELY);
2158 last_hit_mask = hit_mask;
2165 if (!HitOnlyAnEdge(element, hit_mask))
2167 laser.overloaded = TRUE;
2175 boolean HitAbsorbingWalls(int element, int hit_mask)
2177 if (HitOnlyAnEdge(element, hit_mask))
2181 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2183 AddLaserEdge(LX - XS, LY - YS);
2190 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2192 AddLaserEdge(LX - XS, LY - YS);
2198 if (IS_WALL_WOOD(element) ||
2199 IS_DF_WALL_WOOD(element) ||
2200 IS_GRID_WOOD(element) ||
2201 IS_GRID_WOOD_FIXED(element) ||
2202 IS_GRID_WOOD_AUTO(element) ||
2203 element == EL_FUSE_ON ||
2204 element == EL_BLOCK_WOOD ||
2205 element == EL_GATE_WOOD)
2207 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2212 if (IS_WALL_ICE(element))
2216 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
2217 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
2219 /* check if laser hits wall with an angle of 90° */
2220 if (IS_90_ANGLE(laser.current_angle))
2221 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2223 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2227 for (i = 0; i < 4; i++)
2229 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2230 mask = 15 - (8 >> i);
2231 else if (ABS(XS) == 4 &&
2233 (XS > 0) == (i % 2) &&
2234 (YS < 0) == (i / 2))
2235 mask = 3 + (i / 2) * 9;
2236 else if (ABS(YS) == 4 &&
2238 (XS < 0) == (i % 2) &&
2239 (YS > 0) == (i / 2))
2240 mask = 5 + (i % 2) * 5;
2244 laser.wall_mask = mask;
2246 else if (IS_WALL_AMOEBA(element))
2248 int elx = (LX - 2 * XS) / TILEX;
2249 int ely = (LY - 2 * YS) / TILEY;
2250 int element2 = Feld[elx][ely];
2253 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2255 laser.dest_element = EL_EMPTY;
2263 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2264 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2266 if (IS_90_ANGLE(laser.current_angle))
2267 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2269 laser.dest_element = element2 | EL_WALL_AMOEBA;
2271 laser.wall_mask = mask;
2277 void OpenExit(int x, int y)
2281 if (!MovDelay[x][y]) /* next animation frame */
2282 MovDelay[x][y] = 4 * delay;
2284 if (MovDelay[x][y]) /* wait some time before next frame */
2289 phase = MovDelay[x][y] / delay;
2291 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2292 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2294 if (!MovDelay[x][y])
2296 Feld[x][y] = EL_EXIT_OPEN;
2302 void OpenSurpriseBall(int x, int y)
2306 if (!MovDelay[x][y]) /* next animation frame */
2307 MovDelay[x][y] = 50 * delay;
2309 if (MovDelay[x][y]) /* wait some time before next frame */
2313 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2316 int graphic = el2gfx(Store[x][y]);
2318 int dx = RND(26), dy = RND(26);
2320 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2322 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2323 SX + x * TILEX + dx, SY + y * TILEY + dy);
2325 MarkTileDirty(x, y);
2328 if (!MovDelay[x][y])
2330 Feld[x][y] = Store[x][y];
2339 void MeltIce(int x, int y)
2344 if (!MovDelay[x][y]) /* next animation frame */
2345 MovDelay[x][y] = frames * delay;
2347 if (MovDelay[x][y]) /* wait some time before next frame */
2350 int wall_mask = Store2[x][y];
2351 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2354 phase = frames - MovDelay[x][y] / delay - 1;
2356 if (!MovDelay[x][y])
2360 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2361 Store[x][y] = Store2[x][y] = 0;
2363 DrawWalls_MM(x, y, Feld[x][y]);
2365 if (Feld[x][y] == EL_WALL_ICE)
2366 Feld[x][y] = EL_EMPTY;
2368 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2369 if (laser.damage[i].is_mirror)
2373 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2375 DrawLaser(0, DL_LASER_DISABLED);
2379 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2381 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2383 laser.redraw = TRUE;
2388 void GrowAmoeba(int x, int y)
2393 if (!MovDelay[x][y]) /* next animation frame */
2394 MovDelay[x][y] = frames * delay;
2396 if (MovDelay[x][y]) /* wait some time before next frame */
2399 int wall_mask = Store2[x][y];
2400 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2403 phase = MovDelay[x][y] / delay;
2405 if (!MovDelay[x][y])
2407 Feld[x][y] = real_element;
2408 Store[x][y] = Store2[x][y] = 0;
2410 DrawWalls_MM(x, y, Feld[x][y]);
2411 DrawLaser(0, DL_LASER_ENABLED);
2413 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2415 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2420 static void Explode_MM(int x, int y, int phase, int mode)
2422 int num_phase = 9, delay = 2;
2423 int last_phase = num_phase * delay;
2424 int half_phase = (num_phase / 2) * delay;
2426 laser.redraw = TRUE;
2428 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2430 int center_element = Feld[x][y];
2432 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2434 /* put moving element to center field (and let it explode there) */
2435 center_element = MovingOrBlocked2Element_MM(x, y);
2436 RemoveMovingField_MM(x, y);
2438 Feld[x][y] = center_element;
2441 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2442 Store[x][y] = center_element;
2444 Store[x][y] = EL_EMPTY;
2446 Store2[x][y] = mode;
2447 Feld[x][y] = EL_EXPLODING_OPAQUE;
2448 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2454 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2456 if (phase == half_phase)
2458 Feld[x][y] = EL_EXPLODING_TRANSP;
2460 if (x == ELX && y == ELY)
2464 if (phase == last_phase)
2466 if (Store[x][y] == EL_BOMB)
2468 DrawLaser(0, DL_LASER_DISABLED);
2471 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2472 Store[x][y] = EL_EMPTY;
2474 game_mm.game_over = TRUE;
2475 game_mm.game_over_cause = GAME_OVER_BOMB;
2477 laser.overloaded = FALSE;
2479 else if (IS_MCDUFFIN(Store[x][y]))
2481 Store[x][y] = EL_EMPTY;
2484 Feld[x][y] = Store[x][y];
2485 Store[x][y] = Store2[x][y] = 0;
2486 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2488 InitField(x, y, FALSE);
2491 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2493 int graphic = IMG_MM_DEFAULT_EXPLODING;
2494 int graphic_phase = (phase / delay - 1);
2498 if (Store2[x][y] == EX_KETTLE)
2500 if (graphic_phase < 3)
2502 graphic = IMG_MM_KETTLE_EXPLODING;
2504 else if (graphic_phase < 5)
2510 graphic = IMG_EMPTY;
2514 else if (Store2[x][y] == EX_SHORT)
2516 if (graphic_phase < 4)
2522 graphic = IMG_EMPTY;
2527 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2529 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2530 FX + x * TILEX, FY + y * TILEY);
2532 MarkTileDirty(x, y);
2536 static void Bang_MM(int x, int y)
2538 int element = Feld[x][y];
2539 int mode = EX_NORMAL;
2542 DrawLaser(0, DL_LASER_ENABLED);
2561 if (IS_PACMAN(element))
2562 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2563 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2564 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2565 else if (element == EL_KEY)
2566 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2568 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2570 Explode_MM(x, y, EX_PHASE_START, mode);
2573 void TurnRound(int x, int y)
2585 { 0, 0 }, { 0, 0 }, { 0, 0 },
2590 int left, right, back;
2594 { MV_DOWN, MV_UP, MV_RIGHT },
2595 { MV_UP, MV_DOWN, MV_LEFT },
2597 { MV_LEFT, MV_RIGHT, MV_DOWN },
2598 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2599 { MV_RIGHT, MV_LEFT, MV_UP }
2602 int element = Feld[x][y];
2603 int old_move_dir = MovDir[x][y];
2604 int right_dir = turn[old_move_dir].right;
2605 int back_dir = turn[old_move_dir].back;
2606 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2607 int right_x = x + right_dx, right_y = y + right_dy;
2609 if (element == EL_PACMAN)
2611 boolean can_turn_right = FALSE;
2613 if (IN_LEV_FIELD(right_x, right_y) &&
2614 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2615 can_turn_right = TRUE;
2618 MovDir[x][y] = right_dir;
2620 MovDir[x][y] = back_dir;
2626 static void StartMoving_MM(int x, int y)
2628 int element = Feld[x][y];
2633 if (CAN_MOVE(element))
2637 if (MovDelay[x][y]) /* wait some time before next movement */
2645 /* now make next step */
2647 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2649 if (element == EL_PACMAN &&
2650 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2651 !ObjHit(newx, newy, HIT_POS_CENTER))
2653 Store[newx][newy] = Feld[newx][newy];
2654 Feld[newx][newy] = EL_EMPTY;
2656 DrawField_MM(newx, newy);
2658 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2659 ObjHit(newx, newy, HIT_POS_CENTER))
2661 /* object was running against a wall */
2668 InitMovingField_MM(x, y, MovDir[x][y]);
2672 ContinueMoving_MM(x, y);
2675 static void ContinueMoving_MM(int x, int y)
2677 int element = Feld[x][y];
2678 int direction = MovDir[x][y];
2679 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2680 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2681 int horiz_move = (dx!=0);
2682 int newx = x + dx, newy = y + dy;
2683 int step = (horiz_move ? dx : dy) * TILEX / 8;
2685 MovPos[x][y] += step;
2687 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2689 Feld[x][y] = EL_EMPTY;
2690 Feld[newx][newy] = element;
2692 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2693 MovDelay[newx][newy] = 0;
2695 if (!CAN_MOVE(element))
2696 MovDir[newx][newy] = 0;
2699 DrawField_MM(newx, newy);
2701 Stop[newx][newy] = TRUE;
2703 if (element == EL_PACMAN)
2705 if (Store[newx][newy] == EL_BOMB)
2706 Bang_MM(newx, newy);
2708 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2709 (LX + 2 * XS) / TILEX == newx &&
2710 (LY + 2 * YS) / TILEY == newy)
2717 else /* still moving on */
2722 laser.redraw = TRUE;
2725 void ClickElement(int x, int y, int button)
2727 static unsigned int click_delay = 0;
2728 static int click_delay_value = CLICK_DELAY;
2729 static boolean new_button = TRUE;
2734 /* initialize static variables */
2736 click_delay_value = CLICK_DELAY;
2742 /* do not rotate objects hit by the laser after the game was solved */
2743 if (game_mm.level_solved && Hit[x][y])
2746 if (button == MB_RELEASED)
2749 click_delay_value = CLICK_DELAY;
2751 /* release eventually hold auto-rotating mirror */
2752 RotateMirror(x, y, MB_RELEASED);
2757 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2760 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2763 if (!IN_LEV_FIELD(x, y))
2766 if (Feld[x][y] == EL_EMPTY)
2769 element = Feld[x][y];
2771 if (IS_MIRROR(element) ||
2772 IS_BEAMER(element) ||
2773 IS_POLAR(element) ||
2774 IS_POLAR_CROSS(element) ||
2775 IS_DF_MIRROR(element) ||
2776 IS_DF_MIRROR_AUTO(element))
2778 RotateMirror(x, y, button);
2780 else if (IS_MCDUFFIN(element))
2782 if (!laser.fuse_off)
2784 DrawLaser(0, DL_LASER_DISABLED);
2791 element = get_rotated_element(element, BUTTON_ROTATION(button));
2792 laser.start_angle = get_element_angle(element);
2796 Feld[x][y] = element;
2803 if (!laser.fuse_off)
2806 else if (element == EL_FUSE_ON && laser.fuse_off)
2808 if (x != laser.fuse_x || y != laser.fuse_y)
2811 laser.fuse_off = FALSE;
2812 laser.fuse_x = laser.fuse_y = -1;
2814 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2817 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2819 laser.fuse_off = TRUE;
2822 laser.overloaded = FALSE;
2824 DrawLaser(0, DL_LASER_DISABLED);
2825 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2827 else if (element == EL_LIGHTBALL)
2830 RaiseScoreElement_MM(element);
2831 DrawLaser(0, DL_LASER_ENABLED);
2834 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2838 void RotateMirror(int x, int y, int button)
2840 if (button == MB_RELEASED)
2842 /* release eventually hold auto-rotating mirror */
2849 if (IS_MIRROR(Feld[x][y]) ||
2850 IS_POLAR_CROSS(Feld[x][y]) ||
2851 IS_POLAR(Feld[x][y]) ||
2852 IS_BEAMER(Feld[x][y]) ||
2853 IS_DF_MIRROR(Feld[x][y]) ||
2854 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2855 IS_GRID_WOOD_AUTO(Feld[x][y]))
2857 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2859 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2861 if (button == MB_LEFTBUTTON)
2863 /* left mouse button only for manual adjustment, no auto-rotating;
2864 freeze mirror for until mouse button released */
2868 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2870 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2874 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2876 int edge = Hit[x][y];
2882 DrawLaser(edge - 1, DL_LASER_DISABLED);
2886 else if (ObjHit(x, y, HIT_POS_CENTER))
2888 int edge = Hit[x][y];
2892 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2896 DrawLaser(edge - 1, DL_LASER_DISABLED);
2903 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2908 if ((IS_BEAMER(Feld[x][y]) ||
2909 IS_POLAR(Feld[x][y]) ||
2910 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2914 if (IS_BEAMER(Feld[x][y]))
2917 printf("TEST (%d, %d) [%d] [%d]\n",
2919 laser.beamer_edge, laser.beamer[1].num);
2929 DrawLaser(0, DL_LASER_ENABLED);
2933 void AutoRotateMirrors()
2937 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2940 for (x = 0; x < lev_fieldx; x++)
2942 for (y = 0; y < lev_fieldy; y++)
2944 int element = Feld[x][y];
2946 /* do not rotate objects hit by the laser after the game was solved */
2947 if (game_mm.level_solved && Hit[x][y])
2950 if (IS_DF_MIRROR_AUTO(element) ||
2951 IS_GRID_WOOD_AUTO(element) ||
2952 IS_GRID_STEEL_AUTO(element) ||
2953 element == EL_REFRACTOR)
2954 RotateMirror(x, y, MB_RIGHTBUTTON);
2959 boolean ObjHit(int obx, int oby, int bits)
2966 if (bits & HIT_POS_CENTER)
2968 if (CheckLaserPixel(SX + obx + 15,
2973 if (bits & HIT_POS_EDGE)
2975 for (i = 0; i < 4; i++)
2976 if (CheckLaserPixel(SX + obx + 31 * (i % 2),
2977 SY + oby + 31 * (i / 2)))
2981 if (bits & HIT_POS_BETWEEN)
2983 for (i = 0; i < 4; i++)
2984 if (CheckLaserPixel(SX + 4 + obx + 22 * (i % 2),
2985 SY + 4 + oby + 22 * (i / 2)))
2992 void DeletePacMan(int px, int py)
2998 if (game_mm.num_pacman <= 1)
3000 game_mm.num_pacman = 0;
3004 for (i = 0; i < game_mm.num_pacman; i++)
3005 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3008 game_mm.num_pacman--;
3010 for (j = i; j < game_mm.num_pacman; j++)
3012 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3013 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3014 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3018 void ColorCycling(void)
3020 static int CC, Cc = 0;
3022 static int color, old = 0xF00, new = 0x010, mult = 1;
3023 static unsigned short red, green, blue;
3025 if (color_status == STATIC_COLORS)
3030 if (CC < Cc || CC > Cc + 2)
3034 color = old + new * mult;
3040 if (ABS(mult) == 16)
3050 red = 0x0e00 * ((color & 0xF00) >> 8);
3051 green = 0x0e00 * ((color & 0x0F0) >> 4);
3052 blue = 0x0e00 * ((color & 0x00F));
3053 SetRGB(pen_magicolor[0], red, green, blue);
3055 red = 0x1111 * ((color & 0xF00) >> 8);
3056 green = 0x1111 * ((color & 0x0F0) >> 4);
3057 blue = 0x1111 * ((color & 0x00F));
3058 SetRGB(pen_magicolor[1], red, green, blue);
3062 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3069 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3072 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3074 element = Feld[x][y];
3076 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3077 StartMoving_MM(x, y);
3078 else if (IS_MOVING(x, y))
3079 ContinueMoving_MM(x, y);
3080 else if (IS_EXPLODING(element))
3081 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3082 else if (element == EL_EXIT_OPENING)
3084 else if (element == EL_GRAY_BALL_OPENING)
3085 OpenSurpriseBall(x, y);
3086 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3088 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3092 AutoRotateMirrors();
3095 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3097 /* redraw after Explode_MM() ... */
3099 DrawLaser(0, DL_LASER_ENABLED);
3100 laser.redraw = FALSE;
3105 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3109 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3111 DrawLaser(0, DL_LASER_DISABLED);
3116 if (FrameReached(&energy_delay, ENERGY_DELAY))
3118 if (game_mm.energy_left > 0)
3120 game_mm.energy_left--;
3123 BlitBitmap(pix[PIX_DOOR], drawto,
3124 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3125 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3126 DX_ENERGY, DY_ENERGY);
3128 redraw_mask |= REDRAW_DOOR_1;
3130 else if (setup.time_limit && !game_mm.game_over)
3134 for (i = 15; i >= 0; i--)
3137 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3139 pen_ray = GetPixelFromRGB(window,
3140 native_mm_level.laser_red * 0x11 * i,
3141 native_mm_level.laser_green * 0x11 * i,
3142 native_mm_level.laser_blue * 0x11 * i);
3144 DrawLaser(0, DL_LASER_ENABLED);
3149 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3154 DrawLaser(0, DL_LASER_DISABLED);
3155 game_mm.game_over = TRUE;
3156 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3159 if (Request("Out of magic energy ! Play it again ?",
3160 REQ_ASK | REQ_STAY_CLOSED))
3166 game_status = MAINMENU;
3175 element = laser.dest_element;
3178 if (element != Feld[ELX][ELY])
3180 printf("element == %d, Feld[ELX][ELY] == %d\n",
3181 element, Feld[ELX][ELY]);
3185 if (!laser.overloaded && laser.overload_value == 0 &&
3186 element != EL_BOMB &&
3187 element != EL_MINE &&
3188 element != EL_BALL_GRAY &&
3189 element != EL_BLOCK_STONE &&
3190 element != EL_BLOCK_WOOD &&
3191 element != EL_FUSE_ON &&
3192 element != EL_FUEL_FULL &&
3193 !IS_WALL_ICE(element) &&
3194 !IS_WALL_AMOEBA(element))
3197 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3198 (!laser.overloaded && laser.overload_value > 0)) &&
3199 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3201 if (laser.overloaded)
3202 laser.overload_value++;
3204 laser.overload_value--;
3206 if (game_mm.cheat_no_overload)
3208 laser.overloaded = FALSE;
3209 laser.overload_value = 0;
3212 game_mm.laser_overload_value = laser.overload_value;
3214 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3216 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3217 int color_down = 0xFF - color_up;
3220 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3221 (15 - (laser.overload_value / 6)) * color_scale);
3224 GetPixelFromRGB(window,
3225 (native_mm_level.laser_red ? 0xFF : color_up),
3226 (native_mm_level.laser_green ? color_down : 0x00),
3227 (native_mm_level.laser_blue ? color_down : 0x00));
3229 DrawLaser(0, DL_LASER_ENABLED);
3235 if (!laser.overloaded)
3236 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3237 else if (setup.sound_loops)
3238 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3240 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3242 if (laser.overloaded)
3245 BlitBitmap(pix[PIX_DOOR], drawto,
3246 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3247 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3248 - laser.overload_value,
3249 OVERLOAD_XSIZE, laser.overload_value,
3250 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3251 - laser.overload_value);
3253 redraw_mask |= REDRAW_DOOR_1;
3258 BlitBitmap(pix[PIX_DOOR], drawto,
3259 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3260 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3261 DX_OVERLOAD, DY_OVERLOAD);
3263 redraw_mask |= REDRAW_DOOR_1;
3266 if (laser.overload_value == MAX_LASER_OVERLOAD)
3270 for (i = 15; i >= 0; i--)
3273 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3276 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3278 DrawLaser(0, DL_LASER_ENABLED);
3283 DrawLaser(0, DL_LASER_DISABLED);
3285 game_mm.game_over = TRUE;
3286 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3289 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3290 REQ_ASK | REQ_STAY_CLOSED))
3296 game_status = MAINMENU;
3310 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3312 if (game_mm.cheat_no_explosion)
3316 laser.num_damages--;
3317 DrawLaser(0, DL_LASER_DISABLED);
3318 laser.num_edges = 0;
3323 laser.dest_element = EL_EXPLODING_OPAQUE;
3327 laser.num_damages--;
3328 DrawLaser(0, DL_LASER_DISABLED);
3330 laser.num_edges = 0;
3331 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3333 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3334 REQ_ASK | REQ_STAY_CLOSED))
3340 game_status = MAINMENU;
3348 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3350 laser.fuse_off = TRUE;
3354 DrawLaser(0, DL_LASER_DISABLED);
3355 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3358 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3360 static int new_elements[] =
3363 EL_MIRROR_FIXED_START,
3365 EL_POLAR_CROSS_START,
3371 int num_new_elements = sizeof(new_elements) / sizeof(int);
3372 int new_element = new_elements[RND(num_new_elements)];
3374 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3375 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3377 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3388 element = EL_MIRROR_START + RND(16);
3394 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3401 element = (rnd == 0 ? EL_FUSE_ON :
3402 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3403 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3404 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3405 EL_MIRROR_FIXED_START + rnd - 25);
3410 graphic = el2gfx(element);
3412 for (i = 0; i < 50; i++)
3418 BlitBitmap(pix[PIX_BACK], drawto,
3419 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3420 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3421 SX + ELX * TILEX + x,
3422 SY + ELY * TILEY + y);
3424 MarkTileDirty(ELX, ELY);
3427 DrawLaser(0, DL_LASER_ENABLED);
3432 Feld[ELX][ELY] = element;
3433 DrawField_MM(ELX, ELY);
3436 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3439 /* above stuff: GRAY BALL -> PRISM !!! */
3441 LX = ELX * TILEX + 14;
3442 LY = ELY * TILEY + 14;
3443 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3450 laser.num_edges -= 2;
3451 laser.num_damages--;
3455 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3456 if (laser.damage[i].is_mirror)
3460 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3462 DrawLaser(0, DL_LASER_DISABLED);
3464 DrawLaser(0, DL_LASER_DISABLED);
3470 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3477 if (IS_WALL_ICE(element) && CT > 50)
3479 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3482 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3483 Store[ELX][ELY] = EL_WALL_ICE;
3484 Store2[ELX][ELY] = laser.wall_mask;
3486 laser.dest_element = Feld[ELX][ELY];
3491 for (i = 0; i < 5; i++)
3497 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3501 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3506 if (Feld[ELX][ELY] == EL_WALL_ICE)
3507 Feld[ELX][ELY] = EL_EMPTY;
3511 LX = laser.edge[laser.num_edges].x - (SX + 2);
3512 LY = laser.edge[laser.num_edges].y - (SY + 2);
3515 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3516 if (laser.damage[i].is_mirror)
3520 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3522 DrawLaser(0, DL_LASER_DISABLED);
3529 if (IS_WALL_AMOEBA(element) && CT > 60)
3531 int k1, k2, k3, dx, dy, de, dm;
3532 int element2 = Feld[ELX][ELY];
3534 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3537 for (i = laser.num_damages - 1; i >= 0; i--)
3538 if (laser.damage[i].is_mirror)
3541 r = laser.num_edges;
3542 d = laser.num_damages;
3549 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3552 DrawLaser(0, DL_LASER_ENABLED);
3555 x = laser.damage[k1].x;
3556 y = laser.damage[k1].y;
3561 for (i = 0; i < 4; i++)
3563 if (laser.wall_mask & (1 << i))
3565 if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3566 SY + ELY * TILEY + 31 * (i / 2)))
3569 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3570 SY + ELY * TILEY + 14 + (i / 2) * 2))
3577 for (i = 0; i < 4; i++)
3579 if (laser.wall_mask & (1 << i))
3581 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3582 SY + ELY * TILEY + 31 * (i / 2)))
3589 if (laser.num_beamers > 0 ||
3590 k1 < 1 || k2 < 4 || k3 < 4 ||
3591 CheckLaserPixel(SX + ELX * TILEX + 14,
3592 SY + ELY * TILEY + 14))
3594 laser.num_edges = r;
3595 laser.num_damages = d;
3597 DrawLaser(0, DL_LASER_DISABLED);
3600 Feld[ELX][ELY] = element | laser.wall_mask;
3604 de = Feld[ELX][ELY];
3605 dm = laser.wall_mask;
3609 int x = ELX, y = ELY;
3610 int wall_mask = laser.wall_mask;
3613 DrawLaser(0, DL_LASER_ENABLED);
3615 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3617 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3618 Store[x][y] = EL_WALL_AMOEBA;
3619 Store2[x][y] = wall_mask;
3625 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3627 DrawLaser(0, DL_LASER_ENABLED);
3629 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3631 for (i = 4; i >= 0; i--)
3633 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3639 DrawLaser(0, DL_LASER_ENABLED);
3644 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3645 laser.stops_inside_element && CT > native_mm_level.time_block)
3650 if (ABS(XS) > ABS(YS))
3657 for (i = 0; i < 4; i++)
3664 x = ELX + Step[k * 4].x;
3665 y = ELY + Step[k * 4].y;
3667 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3670 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3678 laser.overloaded = (element == EL_BLOCK_STONE);
3683 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3686 Feld[x][y] = element;
3688 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3691 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3693 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3694 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3702 if (element == EL_FUEL_FULL && CT > 10)
3704 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3707 BlitBitmap(pix[PIX_DOOR], drawto,
3708 DOOR_GFX_PAGEX4 + XX_ENERGY,
3709 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3710 ENERGY_XSIZE, i, DX_ENERGY,
3711 DY_ENERGY + ENERGY_YSIZE - i);
3714 redraw_mask |= REDRAW_DOOR_1;
3720 game_mm.energy_left = MAX_LASER_ENERGY;
3721 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3722 DrawField_MM(ELX, ELY);
3724 DrawLaser(0, DL_LASER_ENABLED);
3732 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3734 ClickElement(action.lx, action.ly, action.button);
3736 GameActions_MM_Ext(action, warp_mode);
3741 int mx, my, ox, oy, nx, ny;
3745 if (++pacman_nr >= game_mm.num_pacman)
3748 game_mm.pacman[pacman_nr].dir--;
3750 for (l = 1; l < 5; l++)
3752 game_mm.pacman[pacman_nr].dir++;
3754 if (game_mm.pacman[pacman_nr].dir > 4)
3755 game_mm.pacman[pacman_nr].dir = 1;
3757 if (game_mm.pacman[pacman_nr].dir % 2)
3760 my = game_mm.pacman[pacman_nr].dir - 2;
3765 mx = 3 - game_mm.pacman[pacman_nr].dir;
3768 ox = game_mm.pacman[pacman_nr].x;
3769 oy = game_mm.pacman[pacman_nr].y;
3772 element = Feld[nx][ny];
3774 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3777 if (!IS_EATABLE4PACMAN(element))
3780 if (ObjHit(nx, ny, HIT_POS_CENTER))
3783 Feld[ox][oy] = EL_EMPTY;
3785 EL_PACMAN_RIGHT - 1 +
3786 (game_mm.pacman[pacman_nr].dir - 1 +
3787 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3789 game_mm.pacman[pacman_nr].x = nx;
3790 game_mm.pacman[pacman_nr].y = ny;
3792 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3794 if (element != EL_EMPTY)
3796 int graphic = el2gfx(Feld[nx][ny]);
3801 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3804 ox = SX + ox * TILEX;
3805 oy = SY + oy * TILEY;
3807 for (i = 1; i < 33; i += 2)
3808 BlitBitmap(bitmap, window,
3809 src_x, src_y, TILEX, TILEY,
3810 ox + i * mx, oy + i * my);
3811 Ct = Ct + FrameCounter - CT;
3814 DrawField_MM(nx, ny);
3817 if (!laser.fuse_off)
3819 DrawLaser(0, DL_LASER_ENABLED);
3821 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3823 AddDamagedField(nx, ny);
3825 laser.damage[laser.num_damages - 1].edge = 0;
3829 if (element == EL_BOMB)
3830 DeletePacMan(nx, ny);
3832 if (IS_WALL_AMOEBA(element) &&
3833 (LX + 2 * XS) / TILEX == nx &&
3834 (LY + 2 * YS) / TILEY == ny)
3847 boolean raise_level = FALSE;
3850 if (local_player->MovPos)
3853 local_player->LevelSolved = FALSE;
3856 if (game_mm.energy_left)
3858 if (setup.sound_loops)
3859 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3860 SND_CTRL_PLAY_LOOP);
3862 while (game_mm.energy_left > 0)
3864 if (!setup.sound_loops)
3865 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3868 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3869 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3874 game_mm.energy_left--;
3875 if (game_mm.energy_left >= 0)
3878 BlitBitmap(pix[PIX_DOOR], drawto,
3879 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3880 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3881 DX_ENERGY, DY_ENERGY);
3883 redraw_mask |= REDRAW_DOOR_1;
3890 if (setup.sound_loops)
3891 StopSound(SND_SIRR);
3893 else if (native_mm_level.time == 0) /* level without time limit */
3895 if (setup.sound_loops)
3896 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3897 SND_CTRL_PLAY_LOOP);
3899 while (TimePlayed < 999)
3901 if (!setup.sound_loops)
3902 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3903 if (TimePlayed < 999 && !(TimePlayed % 10))
3904 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3905 if (TimePlayed < 900 && !(TimePlayed % 10))
3911 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3918 if (setup.sound_loops)
3919 StopSound(SND_SIRR);
3926 CloseDoor(DOOR_CLOSE_1);
3928 Request("Level solved !", REQ_CONFIRM);
3930 if (level_nr == leveldir_current->handicap_level)
3932 leveldir_current->handicap_level++;
3933 SaveLevelSetup_SeriesInfo();
3936 if (level_editor_test_game)
3937 game_mm.score = -1; /* no highscore when playing from editor */
3938 else if (level_nr < leveldir_current->last_level)
3939 raise_level = TRUE; /* advance to next level */
3941 if ((hi_pos = NewHiScore_MM()) >= 0)
3943 game_status = HALLOFFAME;
3945 // DrawHallOfFame(hi_pos);
3952 game_status = MAINMENU;
3968 // LoadScore(level_nr);
3970 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3971 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3974 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3976 if (game_mm.score > highscore[k].Score)
3978 /* player has made it to the hall of fame */
3980 if (k < MAX_SCORE_ENTRIES - 1)
3982 int m = MAX_SCORE_ENTRIES - 1;
3985 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3986 if (!strcmp(setup.player_name, highscore[l].Name))
3988 if (m == k) /* player's new highscore overwrites his old one */
3992 for (l = m; l>k; l--)
3994 strcpy(highscore[l].Name, highscore[l - 1].Name);
3995 highscore[l].Score = highscore[l - 1].Score;
4002 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4003 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4004 highscore[k].Score = game_mm.score;
4011 else if (!strncmp(setup.player_name, highscore[k].Name,
4012 MAX_PLAYER_NAME_LEN))
4013 break; /* player already there with a higher score */
4018 // if (position >= 0)
4019 // SaveScore(level_nr);
4024 static void InitMovingField_MM(int x, int y, int direction)
4026 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4027 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4029 MovDir[x][y] = direction;
4030 MovDir[newx][newy] = direction;
4032 if (Feld[newx][newy] == EL_EMPTY)
4033 Feld[newx][newy] = EL_BLOCKED;
4036 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4038 int direction = MovDir[x][y];
4039 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4040 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4046 static void Blocked2Moving_MM(int x, int y,
4047 int *comes_from_x, int *comes_from_y)
4049 int oldx = x, oldy = y;
4050 int direction = MovDir[x][y];
4052 if (direction == MV_LEFT)
4054 else if (direction == MV_RIGHT)
4056 else if (direction == MV_UP)
4058 else if (direction == MV_DOWN)
4061 *comes_from_x = oldx;
4062 *comes_from_y = oldy;
4065 static int MovingOrBlocked2Element_MM(int x, int y)
4067 int element = Feld[x][y];
4069 if (element == EL_BLOCKED)
4073 Blocked2Moving_MM(x, y, &oldx, &oldy);
4075 return Feld[oldx][oldy];
4082 static void RemoveField(int x, int y)
4084 Feld[x][y] = EL_EMPTY;
4091 static void RemoveMovingField_MM(int x, int y)
4093 int oldx = x, oldy = y, newx = x, newy = y;
4095 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4098 if (IS_MOVING(x, y))
4100 Moving2Blocked_MM(x, y, &newx, &newy);
4101 if (Feld[newx][newy] != EL_BLOCKED)
4104 else if (Feld[x][y] == EL_BLOCKED)
4106 Blocked2Moving_MM(x, y, &oldx, &oldy);
4107 if (!IS_MOVING(oldx, oldy))
4111 Feld[oldx][oldy] = EL_EMPTY;
4112 Feld[newx][newy] = EL_EMPTY;
4113 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4114 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4116 DrawLevelField_MM(oldx, oldy);
4117 DrawLevelField_MM(newx, newy);
4120 void PlaySoundLevel(int x, int y, int sound_nr)
4122 int sx = SCREENX(x), sy = SCREENY(y);
4124 int silence_distance = 8;
4126 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4127 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4130 if (!IN_LEV_FIELD(x, y) ||
4131 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4132 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4135 volume = SOUND_MAX_VOLUME;
4138 stereo = (sx - SCR_FIELDX/2) * 12;
4140 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4141 if (stereo > SOUND_MAX_RIGHT)
4142 stereo = SOUND_MAX_RIGHT;
4143 if (stereo < SOUND_MAX_LEFT)
4144 stereo = SOUND_MAX_LEFT;
4147 if (!IN_SCR_FIELD(sx, sy))
4149 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4150 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4152 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4155 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4158 static void RaiseScore_MM(int value)
4160 game_mm.score += value;
4163 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4168 void RaiseScoreElement_MM(int element)
4173 case EL_PACMAN_RIGHT:
4175 case EL_PACMAN_LEFT:
4176 case EL_PACMAN_DOWN:
4177 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4181 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4186 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4190 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4199 /* ------------------------------------------------------------------------- */
4200 /* Mirror Magic game engine snapshot handling functions */
4201 /* ------------------------------------------------------------------------- */
4203 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4207 engine_snapshot_mm.game_mm = game_mm;
4208 engine_snapshot_mm.laser = laser;
4210 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4212 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4214 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4215 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4216 engine_snapshot_mm.Box[x][y] = Box[x][y];
4217 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4218 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4222 engine_snapshot_mm.LX = LX;
4223 engine_snapshot_mm.LY = LY;
4224 engine_snapshot_mm.XS = XS;
4225 engine_snapshot_mm.YS = YS;
4226 engine_snapshot_mm.ELX = ELX;
4227 engine_snapshot_mm.ELY = ELY;
4228 engine_snapshot_mm.CT = CT;
4229 engine_snapshot_mm.Ct = Ct;
4231 engine_snapshot_mm.last_LX = last_LX;
4232 engine_snapshot_mm.last_LY = last_LY;
4233 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4234 engine_snapshot_mm.hold_x = hold_x;
4235 engine_snapshot_mm.hold_y = hold_y;
4236 engine_snapshot_mm.pacman_nr = pacman_nr;
4238 engine_snapshot_mm.rotate_delay = rotate_delay;
4239 engine_snapshot_mm.pacman_delay = pacman_delay;
4240 engine_snapshot_mm.energy_delay = energy_delay;
4241 engine_snapshot_mm.overload_delay = overload_delay;
4244 void LoadEngineSnapshotValues_MM()
4248 /* stored engine snapshot buffers already restored at this point */
4250 game_mm = engine_snapshot_mm.game_mm;
4251 laser = engine_snapshot_mm.laser;
4253 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4255 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4257 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4258 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4259 Box[x][y] = engine_snapshot_mm.Box[x][y];
4260 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4261 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4265 LX = engine_snapshot_mm.LX;
4266 LY = engine_snapshot_mm.LY;
4267 XS = engine_snapshot_mm.XS;
4268 YS = engine_snapshot_mm.YS;
4269 ELX = engine_snapshot_mm.ELX;
4270 ELY = engine_snapshot_mm.ELY;
4271 CT = engine_snapshot_mm.CT;
4272 Ct = engine_snapshot_mm.Ct;
4274 last_LX = engine_snapshot_mm.last_LX;
4275 last_LY = engine_snapshot_mm.last_LY;
4276 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4277 hold_x = engine_snapshot_mm.hold_x;
4278 hold_y = engine_snapshot_mm.hold_y;
4279 pacman_nr = engine_snapshot_mm.pacman_nr;
4281 rotate_delay = engine_snapshot_mm.rotate_delay;
4282 pacman_delay = engine_snapshot_mm.pacman_delay;
4283 energy_delay = engine_snapshot_mm.energy_delay;
4284 overload_delay = engine_snapshot_mm.overload_delay;
4286 RedrawPlayfield_MM(TRUE);
4289 static int getAngleFromTouchDelta(int dx, int dy, int base)
4291 double pi = 3.141592653;
4292 double rad = atan2((double)-dy, (double)dx);
4293 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4294 double deg = rad2 * 180.0 / pi;
4296 return (int)(deg * base / 360.0 + 0.5) % base;
4299 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4301 // calculate start (source) position to be at the middle of the tile
4302 int src_mx = SX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4303 int src_my = SY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4304 int dx = dst_mx - src_mx;
4305 int dy = dst_my - src_my;
4314 if (!IN_LEV_FIELD(x, y))
4317 element = Feld[x][y];
4319 if (!IS_MCDUFFIN(element) &&
4320 !IS_MIRROR(element) &&
4321 !IS_BEAMER(element) &&
4322 !IS_POLAR(element) &&
4323 !IS_POLAR_CROSS(element) &&
4324 !IS_DF_MIRROR(element))
4327 angle_old = get_element_angle(element);
4329 if (IS_MCDUFFIN(element))
4331 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4332 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4333 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4334 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4337 else if (IS_MIRROR(element) ||
4338 IS_DF_MIRROR(element))
4340 for (i = 0; i < laser.num_damages; i++)
4342 if (laser.damage[i].x == x &&
4343 laser.damage[i].y == y &&
4344 ObjHit(x, y, HIT_POS_CENTER))
4346 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4347 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4354 if (angle_new == -1)
4356 if (IS_MIRROR(element) ||
4357 IS_DF_MIRROR(element) ||
4361 if (IS_POLAR_CROSS(element))
4364 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4367 button = (angle_new == angle_old ? 0 :
4368 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4369 MB_LEFTBUTTON : MB_RIGHTBUTTON);