1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 /* graphic position values for game controls */
21 #define ENERGY_XSIZE 32
22 #define ENERGY_YSIZE MAX_LASER_ENERGY
23 #define OVERLOAD_XSIZE ENERGY_XSIZE
24 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
26 /* values for Explode_MM() */
27 #define EX_PHASE_START 0
32 /* special positions in the game control window (relative to control window) */
41 #define XX_OVERLOAD 60
42 #define YY_OVERLOAD YY_ENERGY
44 /* special positions in the game control window (relative to main window) */
45 #define DX_LEVEL (DX + XX_LEVEL)
46 #define DY_LEVEL (DY + YY_LEVEL)
47 #define DX_KETTLES (DX + XX_KETTLES)
48 #define DY_KETTLES (DY + YY_KETTLES)
49 #define DX_SCORE (DX + XX_SCORE)
50 #define DY_SCORE (DY + YY_SCORE)
51 #define DX_ENERGY (DX + XX_ENERGY)
52 #define DY_ENERGY (DY + YY_ENERGY)
53 #define DX_OVERLOAD (DX + XX_OVERLOAD)
54 #define DY_OVERLOAD (DY + YY_OVERLOAD)
56 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
57 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
59 /* game button identifiers */
60 #define GAME_CTRL_ID_LEFT 0
61 #define GAME_CTRL_ID_MIDDLE 1
62 #define GAME_CTRL_ID_RIGHT 2
64 #define NUM_GAME_BUTTONS 3
66 /* values for DrawLaser() */
67 #define DL_LASER_DISABLED 0
68 #define DL_LASER_ENABLED 1
70 /* values for 'click_delay_value' in ClickElement() */
71 #define CLICK_DELAY_FIRST 12 /* delay (frames) after first click */
72 #define CLICK_DELAY 6 /* delay (frames) for pressed butten */
74 #define AUTO_ROTATE_DELAY CLICK_DELAY
75 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
76 #define NUM_INIT_CYCLE_STEPS 16
77 #define PACMAN_MOVE_DELAY 12
78 #define ENERGY_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
79 #define HEALTH_DEC_DELAY 3
80 #define HEALTH_INC_DELAY 9
81 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
83 #define BEGIN_NO_HEADLESS \
85 boolean last_headless = program.headless; \
87 program.headless = FALSE; \
89 #define END_NO_HEADLESS \
90 program.headless = last_headless; \
93 /* forward declaration for internal use */
94 static int MovingOrBlocked2Element_MM(int, int);
95 static void Bang_MM(int, int);
96 static void RaiseScore_MM(int);
97 static void RaiseScoreElement_MM(int);
98 static void RemoveMovingField_MM(int, int);
99 static void InitMovingField_MM(int, int, int);
100 static void ContinueMoving_MM(int, int);
101 static void Moving2Blocked_MM(int, int, int *, int *);
103 /* bitmap for laser beam detection */
104 static Bitmap *laser_bitmap = NULL;
106 /* variables for laser control */
107 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
108 static int hold_x = -1, hold_y = -1;
110 /* variables for pacman control */
111 static int pacman_nr = -1;
113 /* various game engine delay counters */
114 static unsigned int rotate_delay = 0;
115 static unsigned int pacman_delay = 0;
116 static unsigned int energy_delay = 0;
117 static unsigned int overload_delay = 0;
119 /* element masks for scanning pixels of MM elements */
120 static const char mm_masks[10][16][16 + 1] =
304 static int get_element_angle(int element)
306 int element_phase = get_element_phase(element);
308 if (IS_MIRROR_FIXED(element) ||
309 IS_MCDUFFIN(element) ||
311 IS_RECEIVER(element))
312 return 4 * element_phase;
314 return element_phase;
317 static int get_opposite_angle(int angle)
319 int opposite_angle = angle + ANG_RAY_180;
321 /* make sure "opposite_angle" is in valid interval [0, 15] */
322 return (opposite_angle + 16) % 16;
325 static int get_mirrored_angle(int laser_angle, int mirror_angle)
327 int reflected_angle = 16 - laser_angle + mirror_angle;
329 /* make sure "reflected_angle" is in valid interval [0, 15] */
330 return (reflected_angle + 16) % 16;
333 static void DrawLaserLines(struct XY *points, int num_points, int mode)
335 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
336 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
338 DrawLines(drawto, points, num_points, pixel_drawto);
342 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
347 static boolean CheckLaserPixel(int x, int y)
353 pixel = ReadPixel(laser_bitmap, x, y);
357 return (pixel == WHITE_PIXEL);
360 static void CheckExitMM()
362 int exit_element = EL_EMPTY;
366 static int xy[4][2] =
374 for (y = 0; y < lev_fieldy; y++)
376 for (x = 0; x < lev_fieldx; x++)
378 if (Feld[x][y] == EL_EXIT_CLOSED)
380 /* initiate opening animation of exit door */
381 Feld[x][y] = EL_EXIT_OPENING;
383 exit_element = EL_EXIT_OPEN;
387 else if (IS_RECEIVER(Feld[x][y]))
389 /* remove field that blocks receiver */
390 int phase = Feld[x][y] - EL_RECEIVER_START;
391 int blocking_x, blocking_y;
393 blocking_x = x + xy[phase][0];
394 blocking_y = y + xy[phase][1];
396 if (IN_LEV_FIELD(blocking_x, blocking_y))
398 Feld[blocking_x][blocking_y] = EL_EMPTY;
400 DrawField_MM(blocking_x, blocking_y);
403 exit_element = EL_RECEIVER;
410 if (exit_element != EL_EMPTY)
411 PlayLevelSound_MM(exit_x, exit_y, exit_element, MM_ACTION_OPENING);
414 static void InitMovDir_MM(int x, int y)
416 int element = Feld[x][y];
417 static int direction[3][4] =
419 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
420 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
421 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
426 case EL_PACMAN_RIGHT:
430 Feld[x][y] = EL_PACMAN;
431 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
439 static void InitField(int x, int y, boolean init_game)
441 int element = Feld[x][y];
446 Feld[x][y] = EL_EMPTY;
451 if (native_mm_level.auto_count_kettles)
452 game_mm.kettles_still_needed++;
455 case EL_LIGHTBULB_OFF:
456 game_mm.lights_still_needed++;
460 if (IS_MIRROR(element) ||
461 IS_BEAMER_OLD(element) ||
462 IS_BEAMER(element) ||
464 IS_POLAR_CROSS(element) ||
465 IS_DF_MIRROR(element) ||
466 IS_DF_MIRROR_AUTO(element) ||
467 IS_GRID_STEEL_AUTO(element) ||
468 IS_GRID_WOOD_AUTO(element) ||
469 IS_FIBRE_OPTIC(element))
471 if (IS_BEAMER_OLD(element))
473 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
474 element = Feld[x][y];
477 if (!IS_FIBRE_OPTIC(element))
479 static int steps_grid_auto = 0;
481 if (game_mm.num_cycle == 0) /* initialize cycle steps for grids */
482 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
484 if (IS_GRID_STEEL_AUTO(element) ||
485 IS_GRID_WOOD_AUTO(element))
486 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
488 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
490 game_mm.cycle[game_mm.num_cycle].x = x;
491 game_mm.cycle[game_mm.num_cycle].y = y;
495 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
497 int beamer_nr = BEAMER_NR(element);
498 int nr = laser.beamer[beamer_nr][0].num;
500 laser.beamer[beamer_nr][nr].x = x;
501 laser.beamer[beamer_nr][nr].y = y;
502 laser.beamer[beamer_nr][nr].num = 1;
505 else if (IS_PACMAN(element))
509 else if (IS_MCDUFFIN(element) || IS_LASER(element))
511 laser.start_edge.x = x;
512 laser.start_edge.y = y;
513 laser.start_angle = get_element_angle(element);
520 static void InitCycleElements_RotateSingleStep()
524 if (game_mm.num_cycle == 0) /* no elements to cycle */
527 for (i = 0; i < game_mm.num_cycle; i++)
529 int x = game_mm.cycle[i].x;
530 int y = game_mm.cycle[i].y;
531 int step = SIGN(game_mm.cycle[i].steps);
532 int last_element = Feld[x][y];
533 int next_element = get_rotated_element(last_element, step);
535 if (!game_mm.cycle[i].steps)
538 Feld[x][y] = next_element;
541 game_mm.cycle[i].steps -= step;
545 static void InitLaser()
547 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
548 int step = (IS_LASER(start_element) ? 4 : 0);
550 LX = laser.start_edge.x * TILEX;
551 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
554 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
556 LY = laser.start_edge.y * TILEY;
557 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
558 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
562 XS = 2 * Step[laser.start_angle].x;
563 YS = 2 * Step[laser.start_angle].y;
565 laser.current_angle = laser.start_angle;
567 laser.num_damages = 0;
569 laser.num_beamers = 0;
570 laser.beamer_edge[0] = 0;
572 laser.dest_element = EL_EMPTY;
575 AddLaserEdge(LX, LY); /* set laser starting edge */
577 pen_ray = GetPixelFromRGB(window,
578 native_mm_level.laser_red * 0xFF,
579 native_mm_level.laser_green * 0xFF,
580 native_mm_level.laser_blue * 0xFF);
583 void InitGameEngine_MM()
589 /* initialize laser bitmap to current playfield (screen) size */
590 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
591 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
595 /* set global game control values */
596 game_mm.num_cycle = 0;
597 game_mm.num_pacman = 0;
600 game_mm.energy_left = 0; // later set to "native_mm_level.time"
601 game_mm.kettles_still_needed =
602 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
603 game_mm.lights_still_needed = 0;
604 game_mm.num_keys = 0;
606 game_mm.level_solved = FALSE;
607 game_mm.game_over = FALSE;
608 game_mm.game_over_cause = 0;
610 game_mm.laser_overload_value = 0;
611 game_mm.laser_enabled = FALSE;
613 /* set global laser control values (must be set before "InitLaser()") */
614 laser.start_edge.x = 0;
615 laser.start_edge.y = 0;
616 laser.start_angle = 0;
618 for (i = 0; i < MAX_NUM_BEAMERS; i++)
619 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
621 laser.overloaded = FALSE;
622 laser.overload_value = 0;
623 laser.fuse_off = FALSE;
624 laser.fuse_x = laser.fuse_y = -1;
626 laser.dest_element = EL_EMPTY;
645 ClickElement(-1, -1, -1);
647 for (x = 0; x < lev_fieldx; x++)
649 for (y = 0; y < lev_fieldy; y++)
651 Feld[x][y] = Ur[x][y];
652 Hit[x][y] = Box[x][y] = 0;
654 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
655 Store[x][y] = Store2[x][y] = 0;
659 InitField(x, y, TRUE);
664 CloseDoor(DOOR_CLOSE_1);
670 void InitGameActions_MM()
672 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
673 int cycle_steps_done = 0;
679 /* copy default game door content to main double buffer */
680 BlitBitmap(pix[PIX_DOOR], drawto,
681 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
685 DrawText(DX_LEVEL, DY_LEVEL,
686 int2str(level_nr, 2), FONT_TEXT_2);
687 DrawText(DX_KETTLES, DY_KETTLES,
688 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
689 DrawText(DX_SCORE, DY_SCORE,
690 int2str(game_mm.score, 4), FONT_TEXT_2);
699 /* copy actual game door content to door double buffer for OpenDoor() */
700 BlitBitmap(drawto, pix[PIX_DB_DOOR],
701 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
705 OpenDoor(DOOR_OPEN_ALL);
708 for (i = 0; i <= num_init_game_frames; i++)
710 if (i == num_init_game_frames)
711 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
712 else if (setup.sound_loops)
713 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
715 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
717 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
719 UpdateAndDisplayGameControlValues();
721 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
723 InitCycleElements_RotateSingleStep();
733 if (setup.quick_doors)
739 if (setup.sound_music && num_bg_loops)
740 PlayMusic(level_nr % num_bg_loops);
745 if (game_mm.kettles_still_needed == 0)
748 SetTileCursorXY(laser.start_edge.x, laser.start_edge.y);
749 SetTileCursorActive(TRUE);
752 void AddLaserEdge(int lx, int ly)
754 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
756 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
761 laser.edge[laser.num_edges].x = SX + 2 + lx;
762 laser.edge[laser.num_edges].y = SY + 2 + ly;
768 void AddDamagedField(int ex, int ey)
770 laser.damage[laser.num_damages].is_mirror = FALSE;
771 laser.damage[laser.num_damages].angle = laser.current_angle;
772 laser.damage[laser.num_damages].edge = laser.num_edges;
773 laser.damage[laser.num_damages].x = ex;
774 laser.damage[laser.num_damages].y = ey;
784 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
785 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
787 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
793 static int getMaskFromElement(int element)
795 if (IS_GRID(element))
796 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
797 else if (IS_MCDUFFIN(element))
798 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
799 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
800 return IMG_MM_MASK_RECTANGLE;
802 return IMG_MM_MASK_CIRCLE;
810 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
811 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
814 /* follow laser beam until it hits something (at least the screen border) */
815 while (hit_mask == HIT_MASK_NO_HIT)
821 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
822 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
824 printf("ScanPixel: touched screen border!\n");
830 for (i = 0; i < 4; i++)
832 int px = LX + (i % 2) * 2;
833 int py = LY + (i / 2) * 2;
836 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
837 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
840 if (IN_LEV_FIELD(lx, ly))
842 int element = Feld[lx][ly];
844 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
848 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
850 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
852 pixel = ((element & (1 << pos)) ? 1 : 0);
856 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
858 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
863 pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
864 SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
867 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
868 hit_mask |= (1 << i);
871 if (hit_mask == HIT_MASK_NO_HIT)
873 /* hit nothing -- go on with another step */
885 int end = 0, rf = laser.num_edges;
887 /* do not scan laser again after the game was lost for whatever reason */
888 if (game_mm.game_over)
891 laser.overloaded = FALSE;
892 laser.stops_inside_element = FALSE;
894 DrawLaser(0, DL_LASER_ENABLED);
897 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
905 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
908 laser.overloaded = TRUE;
913 hit_mask = ScanPixel();
916 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
920 /* hit something -- check out what it was */
921 ELX = (LX + XS) / TILEX;
922 ELY = (LY + YS) / TILEY;
925 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
926 hit_mask, LX, LY, ELX, ELY);
929 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
932 laser.dest_element = element;
937 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
939 /* we have hit the top-right and bottom-left element --
940 choose the bottom-left one */
941 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
942 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
943 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
944 ELX = (LX - 2) / TILEX;
945 ELY = (LY + 2) / TILEY;
948 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
950 /* we have hit the top-left and bottom-right element --
951 choose the top-left one */
952 /* !!! SEE ABOVE !!! */
953 ELX = (LX - 2) / TILEX;
954 ELY = (LY - 2) / TILEY;
958 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
959 hit_mask, LX, LY, ELX, ELY);
962 element = Feld[ELX][ELY];
963 laser.dest_element = element;
966 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
969 LX % TILEX, LY % TILEY,
974 if (!IN_LEV_FIELD(ELX, ELY))
975 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
978 if (element == EL_EMPTY)
980 if (!HitOnlyAnEdge(element, hit_mask))
983 else if (element == EL_FUSE_ON)
985 if (HitPolarizer(element, hit_mask))
988 else if (IS_GRID(element) || IS_DF_GRID(element))
990 if (HitPolarizer(element, hit_mask))
993 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
994 element == EL_GATE_STONE || element == EL_GATE_WOOD)
996 if (HitBlock(element, hit_mask))
1003 else if (IS_MCDUFFIN(element))
1005 if (HitLaserSource(element, hit_mask))
1008 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
1009 IS_RECEIVER(element))
1011 if (HitLaserDestination(element, hit_mask))
1014 else if (IS_WALL(element))
1016 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
1018 if (HitReflectingWalls(element, hit_mask))
1023 if (HitAbsorbingWalls(element, hit_mask))
1029 if (HitElement(element, hit_mask))
1034 DrawLaser(rf - 1, DL_LASER_ENABLED);
1035 rf = laser.num_edges;
1039 if (laser.dest_element != Feld[ELX][ELY])
1041 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
1042 laser.dest_element, Feld[ELX][ELY]);
1046 if (!end && !laser.stops_inside_element && !StepBehind())
1049 printf("ScanLaser: Go one step back\n");
1055 AddLaserEdge(LX, LY);
1059 DrawLaser(rf - 1, DL_LASER_ENABLED);
1061 Ct = CT = FrameCounter;
1064 if (!IN_LEV_FIELD(ELX, ELY))
1065 printf("WARNING! (2) %d, %d\n", ELX, ELY);
1069 void DrawLaserExt(int start_edge, int num_edges, int mode)
1075 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
1076 start_edge, num_edges, mode);
1081 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
1088 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
1094 if (mode == DL_LASER_DISABLED)
1096 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
1100 /* now draw the laser to the backbuffer and (if enabled) to the screen */
1101 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1103 redraw_mask |= REDRAW_FIELD;
1105 if (mode == DL_LASER_ENABLED)
1108 /* after the laser was deleted, the "damaged" graphics must be restored */
1109 if (laser.num_damages)
1111 int damage_start = 0;
1114 /* determine the starting edge, from which graphics need to be restored */
1117 for (i = 0; i < laser.num_damages; i++)
1119 if (laser.damage[i].edge == start_edge + 1)
1128 /* restore graphics from this starting edge to the end of damage list */
1129 for (i = damage_start; i < laser.num_damages; i++)
1131 int lx = laser.damage[i].x;
1132 int ly = laser.damage[i].y;
1133 int element = Feld[lx][ly];
1135 if (Hit[lx][ly] == laser.damage[i].edge)
1136 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1139 if (Box[lx][ly] == laser.damage[i].edge)
1142 if (IS_DRAWABLE(element))
1143 DrawField_MM(lx, ly);
1146 elx = laser.damage[damage_start].x;
1147 ely = laser.damage[damage_start].y;
1148 element = Feld[elx][ely];
1151 if (IS_BEAMER(element))
1155 for (i = 0; i < laser.num_beamers; i++)
1156 printf("-> %d\n", laser.beamer_edge[i]);
1157 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
1158 mode, elx, ely, Hit[elx][ely], start_edge);
1159 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
1160 get_element_angle(element), laser.damage[damage_start].angle);
1164 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1165 laser.num_beamers > 0 &&
1166 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1168 /* element is outgoing beamer */
1169 laser.num_damages = damage_start + 1;
1171 if (IS_BEAMER(element))
1172 laser.current_angle = get_element_angle(element);
1176 /* element is incoming beamer or other element */
1177 laser.num_damages = damage_start;
1178 laser.current_angle = laser.damage[laser.num_damages].angle;
1183 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
1185 elx = laser.start_edge.x;
1186 ely = laser.start_edge.y;
1187 element = Feld[elx][ely];
1190 laser.num_edges = start_edge + 1;
1191 if (start_edge == 0)
1192 laser.current_angle = laser.start_angle;
1194 LX = laser.edge[start_edge].x - (SX + 2);
1195 LY = laser.edge[start_edge].y - (SY + 2);
1196 XS = 2 * Step[laser.current_angle].x;
1197 YS = 2 * Step[laser.current_angle].y;
1200 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
1206 if (IS_BEAMER(element) ||
1207 IS_FIBRE_OPTIC(element) ||
1208 IS_PACMAN(element) ||
1209 IS_POLAR(element) ||
1210 IS_POLAR_CROSS(element) ||
1211 element == EL_FUSE_ON)
1216 printf("element == %d\n", element);
1219 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
1220 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1224 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1225 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1226 (laser.num_beamers == 0 ||
1227 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1229 /* element is incoming beamer or other element */
1230 step_size = -step_size;
1235 if (IS_BEAMER(element))
1237 printf("start_edge == %d, laser.beamer_edge == %d\n",
1238 start_edge, laser.beamer_edge);
1242 LX += step_size * XS;
1243 LY += step_size * YS;
1245 else if (element != EL_EMPTY)
1254 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
1259 void DrawLaser(int start_edge, int mode)
1261 if (laser.num_edges - start_edge < 0)
1263 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
1268 /* check if laser is interrupted by beamer element */
1269 if (laser.num_beamers > 0 &&
1270 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1272 if (mode == DL_LASER_ENABLED)
1275 int tmp_start_edge = start_edge;
1277 /* draw laser segments forward from the start to the last beamer */
1278 for (i = 0; i < laser.num_beamers; i++)
1280 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1282 if (tmp_num_edges <= 0)
1286 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
1287 i, laser.beamer_edge[i], tmp_start_edge);
1290 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1292 tmp_start_edge = laser.beamer_edge[i];
1295 /* draw last segment from last beamer to the end */
1296 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1302 int last_num_edges = laser.num_edges;
1303 int num_beamers = laser.num_beamers;
1305 /* delete laser segments backward from the end to the first beamer */
1306 for (i = num_beamers - 1; i >= 0; i--)
1308 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1310 if (laser.beamer_edge[i] - start_edge <= 0)
1313 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1315 last_num_edges = laser.beamer_edge[i];
1316 laser.num_beamers--;
1320 if (last_num_edges - start_edge <= 0)
1321 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1322 last_num_edges, start_edge);
1325 // special case when rotating first beamer: delete laser edge on beamer
1326 // (but do not start scanning on previous edge to prevent mirror sound)
1327 if (last_num_edges - start_edge == 1 && start_edge > 0)
1328 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1330 /* delete first segment from start to the first beamer */
1331 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1336 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1339 game_mm.laser_enabled = mode;
1344 DrawLaser(0, game_mm.laser_enabled);
1347 boolean HitElement(int element, int hit_mask)
1349 if (HitOnlyAnEdge(element, hit_mask))
1352 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1353 element = MovingOrBlocked2Element_MM(ELX, ELY);
1356 printf("HitElement (1): element == %d\n", element);
1360 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1361 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1363 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1366 AddDamagedField(ELX, ELY);
1368 /* this is more precise: check if laser would go through the center */
1369 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1371 /* skip the whole element before continuing the scan */
1377 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1379 if (LX/TILEX > ELX || LY/TILEY > ELY)
1381 /* skipping scan positions to the right and down skips one scan
1382 position too much, because this is only the top left scan position
1383 of totally four scan positions (plus one to the right, one to the
1384 bottom and one to the bottom right) */
1394 printf("HitElement (2): element == %d\n", element);
1397 if (LX + 5 * XS < 0 ||
1407 printf("HitElement (3): element == %d\n", element);
1410 if (IS_POLAR(element) &&
1411 ((element - EL_POLAR_START) % 2 ||
1412 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1414 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1416 laser.num_damages--;
1421 if (IS_POLAR_CROSS(element) &&
1422 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1424 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1426 laser.num_damages--;
1431 if (!IS_BEAMER(element) &&
1432 !IS_FIBRE_OPTIC(element) &&
1433 !IS_GRID_WOOD(element) &&
1434 element != EL_FUEL_EMPTY)
1437 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1438 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1440 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1443 LX = ELX * TILEX + 14;
1444 LY = ELY * TILEY + 14;
1446 AddLaserEdge(LX, LY);
1449 if (IS_MIRROR(element) ||
1450 IS_MIRROR_FIXED(element) ||
1451 IS_POLAR(element) ||
1452 IS_POLAR_CROSS(element) ||
1453 IS_DF_MIRROR(element) ||
1454 IS_DF_MIRROR_AUTO(element) ||
1455 element == EL_PRISM ||
1456 element == EL_REFRACTOR)
1458 int current_angle = laser.current_angle;
1461 laser.num_damages--;
1463 AddDamagedField(ELX, ELY);
1465 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1468 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1470 if (IS_MIRROR(element) ||
1471 IS_MIRROR_FIXED(element) ||
1472 IS_DF_MIRROR(element) ||
1473 IS_DF_MIRROR_AUTO(element))
1474 laser.current_angle = get_mirrored_angle(laser.current_angle,
1475 get_element_angle(element));
1477 if (element == EL_PRISM || element == EL_REFRACTOR)
1478 laser.current_angle = RND(16);
1480 XS = 2 * Step[laser.current_angle].x;
1481 YS = 2 * Step[laser.current_angle].y;
1483 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1488 LX += step_size * XS;
1489 LY += step_size * YS;
1492 /* draw sparkles on mirror */
1493 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1494 current_angle != laser.current_angle)
1496 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1500 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1501 current_angle != laser.current_angle)
1502 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1505 (get_opposite_angle(laser.current_angle) ==
1506 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1508 return (laser.overloaded ? TRUE : FALSE);
1511 if (element == EL_FUEL_FULL)
1513 laser.stops_inside_element = TRUE;
1518 if (element == EL_BOMB || element == EL_MINE)
1520 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1522 if (element == EL_MINE)
1523 laser.overloaded = TRUE;
1526 if (element == EL_KETTLE ||
1527 element == EL_CELL ||
1528 element == EL_KEY ||
1529 element == EL_LIGHTBALL ||
1530 element == EL_PACMAN ||
1533 if (!IS_PACMAN(element))
1536 if (element == EL_PACMAN)
1539 if (element == EL_KETTLE || element == EL_CELL)
1541 if (game_mm.kettles_still_needed > 0)
1542 game_mm.kettles_still_needed--;
1544 game.snapshot.collected_item = TRUE;
1546 if (game_mm.kettles_still_needed == 0)
1550 DrawLaser(0, DL_LASER_ENABLED);
1553 else if (element == EL_KEY)
1557 else if (IS_PACMAN(element))
1559 DeletePacMan(ELX, ELY);
1562 RaiseScoreElement_MM(element);
1567 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1569 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1571 DrawLaser(0, DL_LASER_ENABLED);
1573 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1575 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1576 game_mm.lights_still_needed--;
1580 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1581 game_mm.lights_still_needed++;
1584 DrawField_MM(ELX, ELY);
1585 DrawLaser(0, DL_LASER_ENABLED);
1590 laser.stops_inside_element = TRUE;
1596 printf("HitElement (4): element == %d\n", element);
1599 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1600 laser.num_beamers < MAX_NUM_BEAMERS &&
1601 laser.beamer[BEAMER_NR(element)][1].num)
1603 int beamer_angle = get_element_angle(element);
1604 int beamer_nr = BEAMER_NR(element);
1608 printf("HitElement (BEAMER): element == %d\n", element);
1611 laser.num_damages--;
1613 if (IS_FIBRE_OPTIC(element) ||
1614 laser.current_angle == get_opposite_angle(beamer_angle))
1618 LX = ELX * TILEX + 14;
1619 LY = ELY * TILEY + 14;
1621 AddLaserEdge(LX, LY);
1622 AddDamagedField(ELX, ELY);
1624 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1627 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1629 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1630 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1631 ELX = laser.beamer[beamer_nr][pos].x;
1632 ELY = laser.beamer[beamer_nr][pos].y;
1633 LX = ELX * TILEX + 14;
1634 LY = ELY * TILEY + 14;
1636 if (IS_BEAMER(element))
1638 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1639 XS = 2 * Step[laser.current_angle].x;
1640 YS = 2 * Step[laser.current_angle].y;
1643 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1645 AddLaserEdge(LX, LY);
1646 AddDamagedField(ELX, ELY);
1648 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1651 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1653 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1658 LX += step_size * XS;
1659 LY += step_size * YS;
1661 laser.num_beamers++;
1670 boolean HitOnlyAnEdge(int element, int hit_mask)
1672 /* check if the laser hit only the edge of an element and, if so, go on */
1675 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1678 if ((hit_mask == HIT_MASK_TOPLEFT ||
1679 hit_mask == HIT_MASK_TOPRIGHT ||
1680 hit_mask == HIT_MASK_BOTTOMLEFT ||
1681 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1682 laser.current_angle % 4) /* angle is not 90° */
1686 if (hit_mask == HIT_MASK_TOPLEFT)
1691 else if (hit_mask == HIT_MASK_TOPRIGHT)
1696 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1701 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1707 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1713 printf("[HitOnlyAnEdge() == TRUE]\n");
1720 printf("[HitOnlyAnEdge() == FALSE]\n");
1726 boolean HitPolarizer(int element, int hit_mask)
1728 if (HitOnlyAnEdge(element, hit_mask))
1731 if (IS_DF_GRID(element))
1733 int grid_angle = get_element_angle(element);
1736 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1737 grid_angle, laser.current_angle);
1740 AddLaserEdge(LX, LY);
1741 AddDamagedField(ELX, ELY);
1744 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1746 if (laser.current_angle == grid_angle ||
1747 laser.current_angle == get_opposite_angle(grid_angle))
1749 /* skip the whole element before continuing the scan */
1755 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1757 if (LX/TILEX > ELX || LY/TILEY > ELY)
1759 /* skipping scan positions to the right and down skips one scan
1760 position too much, because this is only the top left scan position
1761 of totally four scan positions (plus one to the right, one to the
1762 bottom and one to the bottom right) */
1768 AddLaserEdge(LX, LY);
1774 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1776 LX / TILEX, LY / TILEY,
1777 LX % TILEX, LY % TILEY);
1782 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1784 return HitReflectingWalls(element, hit_mask);
1788 return HitAbsorbingWalls(element, hit_mask);
1791 else if (IS_GRID_STEEL(element))
1793 return HitReflectingWalls(element, hit_mask);
1795 else /* IS_GRID_WOOD */
1797 return HitAbsorbingWalls(element, hit_mask);
1803 boolean HitBlock(int element, int hit_mask)
1805 boolean check = FALSE;
1807 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1808 game_mm.num_keys == 0)
1811 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1814 int ex = ELX * TILEX + 14;
1815 int ey = ELY * TILEY + 14;
1819 for (i = 1; i < 32; i++)
1824 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1829 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1830 return HitAbsorbingWalls(element, hit_mask);
1834 AddLaserEdge(LX - XS, LY - YS);
1835 AddDamagedField(ELX, ELY);
1838 Box[ELX][ELY] = laser.num_edges;
1840 return HitReflectingWalls(element, hit_mask);
1843 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1845 int xs = XS / 2, ys = YS / 2;
1846 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1847 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1849 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1850 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1852 laser.overloaded = (element == EL_GATE_STONE);
1857 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1858 (hit_mask == HIT_MASK_TOP ||
1859 hit_mask == HIT_MASK_LEFT ||
1860 hit_mask == HIT_MASK_RIGHT ||
1861 hit_mask == HIT_MASK_BOTTOM))
1862 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1863 hit_mask == HIT_MASK_BOTTOM),
1864 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1865 hit_mask == HIT_MASK_RIGHT));
1866 AddLaserEdge(LX, LY);
1872 if (element == EL_GATE_STONE && Box[ELX][ELY])
1874 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1886 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1888 int xs = XS / 2, ys = YS / 2;
1889 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1890 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1892 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1893 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1895 laser.overloaded = (element == EL_BLOCK_STONE);
1900 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1901 (hit_mask == HIT_MASK_TOP ||
1902 hit_mask == HIT_MASK_LEFT ||
1903 hit_mask == HIT_MASK_RIGHT ||
1904 hit_mask == HIT_MASK_BOTTOM))
1905 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1906 hit_mask == HIT_MASK_BOTTOM),
1907 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1908 hit_mask == HIT_MASK_RIGHT));
1909 AddDamagedField(ELX, ELY);
1911 LX = ELX * TILEX + 14;
1912 LY = ELY * TILEY + 14;
1914 AddLaserEdge(LX, LY);
1916 laser.stops_inside_element = TRUE;
1924 boolean HitLaserSource(int element, int hit_mask)
1926 if (HitOnlyAnEdge(element, hit_mask))
1929 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1931 laser.overloaded = TRUE;
1936 boolean HitLaserDestination(int element, int hit_mask)
1938 if (HitOnlyAnEdge(element, hit_mask))
1941 if (element != EL_EXIT_OPEN &&
1942 !(IS_RECEIVER(element) &&
1943 game_mm.kettles_still_needed == 0 &&
1944 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1946 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1951 if (IS_RECEIVER(element) ||
1952 (IS_22_5_ANGLE(laser.current_angle) &&
1953 (ELX != (LX + 6 * XS) / TILEX ||
1954 ELY != (LY + 6 * YS) / TILEY ||
1963 LX = ELX * TILEX + 14;
1964 LY = ELY * TILEY + 14;
1966 laser.stops_inside_element = TRUE;
1969 AddLaserEdge(LX, LY);
1970 AddDamagedField(ELX, ELY);
1972 if (game_mm.lights_still_needed == 0)
1974 game_mm.level_solved = TRUE;
1976 SetTileCursorActive(FALSE);
1982 boolean HitReflectingWalls(int element, int hit_mask)
1984 /* check if laser hits side of a wall with an angle that is not 90° */
1985 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1986 hit_mask == HIT_MASK_LEFT ||
1987 hit_mask == HIT_MASK_RIGHT ||
1988 hit_mask == HIT_MASK_BOTTOM))
1990 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1995 if (!IS_DF_GRID(element))
1996 AddLaserEdge(LX, LY);
1998 /* check if laser hits wall with an angle of 45° */
1999 if (!IS_22_5_ANGLE(laser.current_angle))
2001 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2004 laser.current_angle = get_mirrored_angle(laser.current_angle,
2007 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2010 laser.current_angle = get_mirrored_angle(laser.current_angle,
2014 AddLaserEdge(LX, LY);
2016 XS = 2 * Step[laser.current_angle].x;
2017 YS = 2 * Step[laser.current_angle].y;
2021 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2023 laser.current_angle = get_mirrored_angle(laser.current_angle,
2028 if (!IS_DF_GRID(element))
2029 AddLaserEdge(LX, LY);
2034 if (!IS_DF_GRID(element))
2035 AddLaserEdge(LX, LY + YS / 2);
2038 if (!IS_DF_GRID(element))
2039 AddLaserEdge(LX, LY);
2042 YS = 2 * Step[laser.current_angle].y;
2046 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2048 laser.current_angle = get_mirrored_angle(laser.current_angle,
2053 if (!IS_DF_GRID(element))
2054 AddLaserEdge(LX, LY);
2059 if (!IS_DF_GRID(element))
2060 AddLaserEdge(LX + XS / 2, LY);
2063 if (!IS_DF_GRID(element))
2064 AddLaserEdge(LX, LY);
2067 XS = 2 * Step[laser.current_angle].x;
2073 /* reflection at the edge of reflecting DF style wall */
2074 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2076 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2077 hit_mask == HIT_MASK_TOPRIGHT) ||
2078 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2079 hit_mask == HIT_MASK_TOPLEFT) ||
2080 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2081 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2082 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2083 hit_mask == HIT_MASK_BOTTOMRIGHT))
2086 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2087 ANG_MIRROR_135 : ANG_MIRROR_45);
2089 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2091 AddDamagedField(ELX, ELY);
2092 AddLaserEdge(LX, LY);
2094 laser.current_angle = get_mirrored_angle(laser.current_angle,
2102 AddLaserEdge(LX, LY);
2108 /* reflection inside an edge of reflecting DF style wall */
2109 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2111 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2112 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2113 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2114 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2115 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2116 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2117 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2118 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2121 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2122 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2123 ANG_MIRROR_135 : ANG_MIRROR_45);
2125 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2128 AddDamagedField(ELX, ELY);
2131 AddLaserEdge(LX - XS, LY - YS);
2132 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2133 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2135 laser.current_angle = get_mirrored_angle(laser.current_angle,
2143 AddLaserEdge(LX, LY);
2149 /* check if laser hits DF style wall with an angle of 90° */
2150 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2152 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2153 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2154 (IS_VERT_ANGLE(laser.current_angle) &&
2155 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2157 /* laser at last step touched nothing or the same side of the wall */
2158 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2160 AddDamagedField(ELX, ELY);
2167 last_hit_mask = hit_mask;
2174 if (!HitOnlyAnEdge(element, hit_mask))
2176 laser.overloaded = TRUE;
2184 boolean HitAbsorbingWalls(int element, int hit_mask)
2186 if (HitOnlyAnEdge(element, hit_mask))
2190 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2192 AddLaserEdge(LX - XS, LY - YS);
2199 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2201 AddLaserEdge(LX - XS, LY - YS);
2207 if (IS_WALL_WOOD(element) ||
2208 IS_DF_WALL_WOOD(element) ||
2209 IS_GRID_WOOD(element) ||
2210 IS_GRID_WOOD_FIXED(element) ||
2211 IS_GRID_WOOD_AUTO(element) ||
2212 element == EL_FUSE_ON ||
2213 element == EL_BLOCK_WOOD ||
2214 element == EL_GATE_WOOD)
2216 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2221 if (IS_WALL_ICE(element))
2225 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
2226 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
2228 /* check if laser hits wall with an angle of 90° */
2229 if (IS_90_ANGLE(laser.current_angle))
2230 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2232 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2236 for (i = 0; i < 4; i++)
2238 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2239 mask = 15 - (8 >> i);
2240 else if (ABS(XS) == 4 &&
2242 (XS > 0) == (i % 2) &&
2243 (YS < 0) == (i / 2))
2244 mask = 3 + (i / 2) * 9;
2245 else if (ABS(YS) == 4 &&
2247 (XS < 0) == (i % 2) &&
2248 (YS > 0) == (i / 2))
2249 mask = 5 + (i % 2) * 5;
2253 laser.wall_mask = mask;
2255 else if (IS_WALL_AMOEBA(element))
2257 int elx = (LX - 2 * XS) / TILEX;
2258 int ely = (LY - 2 * YS) / TILEY;
2259 int element2 = Feld[elx][ely];
2262 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2264 laser.dest_element = EL_EMPTY;
2272 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2273 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2275 if (IS_90_ANGLE(laser.current_angle))
2276 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2278 laser.dest_element = element2 | EL_WALL_AMOEBA;
2280 laser.wall_mask = mask;
2286 void OpenExit(int x, int y)
2290 if (!MovDelay[x][y]) /* next animation frame */
2291 MovDelay[x][y] = 4 * delay;
2293 if (MovDelay[x][y]) /* wait some time before next frame */
2298 phase = MovDelay[x][y] / delay;
2300 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2301 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2303 if (!MovDelay[x][y])
2305 Feld[x][y] = EL_EXIT_OPEN;
2311 void OpenSurpriseBall(int x, int y)
2315 if (!MovDelay[x][y]) /* next animation frame */
2316 MovDelay[x][y] = 50 * delay;
2318 if (MovDelay[x][y]) /* wait some time before next frame */
2322 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2325 int graphic = el2gfx(Store[x][y]);
2327 int dx = RND(26), dy = RND(26);
2329 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2331 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2332 SX + x * TILEX + dx, SY + y * TILEY + dy);
2334 MarkTileDirty(x, y);
2337 if (!MovDelay[x][y])
2339 Feld[x][y] = Store[x][y];
2348 void MeltIce(int x, int y)
2353 if (!MovDelay[x][y]) /* next animation frame */
2354 MovDelay[x][y] = frames * delay;
2356 if (MovDelay[x][y]) /* wait some time before next frame */
2359 int wall_mask = Store2[x][y];
2360 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2363 phase = frames - MovDelay[x][y] / delay - 1;
2365 if (!MovDelay[x][y])
2369 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2370 Store[x][y] = Store2[x][y] = 0;
2372 DrawWalls_MM(x, y, Feld[x][y]);
2374 if (Feld[x][y] == EL_WALL_ICE)
2375 Feld[x][y] = EL_EMPTY;
2377 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2378 if (laser.damage[i].is_mirror)
2382 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2384 DrawLaser(0, DL_LASER_DISABLED);
2388 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2390 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2392 laser.redraw = TRUE;
2397 void GrowAmoeba(int x, int y)
2402 if (!MovDelay[x][y]) /* next animation frame */
2403 MovDelay[x][y] = frames * delay;
2405 if (MovDelay[x][y]) /* wait some time before next frame */
2408 int wall_mask = Store2[x][y];
2409 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2412 phase = MovDelay[x][y] / delay;
2414 if (!MovDelay[x][y])
2416 Feld[x][y] = real_element;
2417 Store[x][y] = Store2[x][y] = 0;
2419 DrawWalls_MM(x, y, Feld[x][y]);
2420 DrawLaser(0, DL_LASER_ENABLED);
2422 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2424 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2429 static void Explode_MM(int x, int y, int phase, int mode)
2431 int num_phase = 9, delay = 2;
2432 int last_phase = num_phase * delay;
2433 int half_phase = (num_phase / 2) * delay;
2435 laser.redraw = TRUE;
2437 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2439 int center_element = Feld[x][y];
2441 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2443 /* put moving element to center field (and let it explode there) */
2444 center_element = MovingOrBlocked2Element_MM(x, y);
2445 RemoveMovingField_MM(x, y);
2447 Feld[x][y] = center_element;
2450 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2451 Store[x][y] = center_element;
2453 Store[x][y] = EL_EMPTY;
2455 Store2[x][y] = mode;
2456 Feld[x][y] = EL_EXPLODING_OPAQUE;
2457 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2463 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2465 if (phase == half_phase)
2467 Feld[x][y] = EL_EXPLODING_TRANSP;
2469 if (x == ELX && y == ELY)
2473 if (phase == last_phase)
2475 if (Store[x][y] == EL_BOMB)
2477 DrawLaser(0, DL_LASER_DISABLED);
2480 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2481 Store[x][y] = EL_EMPTY;
2483 game_mm.game_over = TRUE;
2484 game_mm.game_over_cause = GAME_OVER_BOMB;
2486 SetTileCursorActive(FALSE);
2488 laser.overloaded = FALSE;
2490 else if (IS_MCDUFFIN(Store[x][y]))
2492 Store[x][y] = EL_EMPTY;
2495 Feld[x][y] = Store[x][y];
2496 Store[x][y] = Store2[x][y] = 0;
2497 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2499 InitField(x, y, FALSE);
2502 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2504 int graphic = IMG_MM_DEFAULT_EXPLODING;
2505 int graphic_phase = (phase / delay - 1);
2509 if (Store2[x][y] == EX_KETTLE)
2511 if (graphic_phase < 3)
2513 graphic = IMG_MM_KETTLE_EXPLODING;
2515 else if (graphic_phase < 5)
2521 graphic = IMG_EMPTY;
2525 else if (Store2[x][y] == EX_SHORT)
2527 if (graphic_phase < 4)
2533 graphic = IMG_EMPTY;
2538 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2540 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2541 FX + x * TILEX, FY + y * TILEY);
2543 MarkTileDirty(x, y);
2547 static void Bang_MM(int x, int y)
2549 int element = Feld[x][y];
2550 int mode = EX_NORMAL;
2553 DrawLaser(0, DL_LASER_ENABLED);
2572 if (IS_PACMAN(element))
2573 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2574 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2575 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2576 else if (element == EL_KEY)
2577 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2579 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2581 Explode_MM(x, y, EX_PHASE_START, mode);
2584 void TurnRound(int x, int y)
2596 { 0, 0 }, { 0, 0 }, { 0, 0 },
2601 int left, right, back;
2605 { MV_DOWN, MV_UP, MV_RIGHT },
2606 { MV_UP, MV_DOWN, MV_LEFT },
2608 { MV_LEFT, MV_RIGHT, MV_DOWN },
2609 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2610 { MV_RIGHT, MV_LEFT, MV_UP }
2613 int element = Feld[x][y];
2614 int old_move_dir = MovDir[x][y];
2615 int right_dir = turn[old_move_dir].right;
2616 int back_dir = turn[old_move_dir].back;
2617 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2618 int right_x = x + right_dx, right_y = y + right_dy;
2620 if (element == EL_PACMAN)
2622 boolean can_turn_right = FALSE;
2624 if (IN_LEV_FIELD(right_x, right_y) &&
2625 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2626 can_turn_right = TRUE;
2629 MovDir[x][y] = right_dir;
2631 MovDir[x][y] = back_dir;
2637 static void StartMoving_MM(int x, int y)
2639 int element = Feld[x][y];
2644 if (CAN_MOVE(element))
2648 if (MovDelay[x][y]) /* wait some time before next movement */
2656 /* now make next step */
2658 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2660 if (element == EL_PACMAN &&
2661 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2662 !ObjHit(newx, newy, HIT_POS_CENTER))
2664 Store[newx][newy] = Feld[newx][newy];
2665 Feld[newx][newy] = EL_EMPTY;
2667 DrawField_MM(newx, newy);
2669 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2670 ObjHit(newx, newy, HIT_POS_CENTER))
2672 /* object was running against a wall */
2679 InitMovingField_MM(x, y, MovDir[x][y]);
2683 ContinueMoving_MM(x, y);
2686 static void ContinueMoving_MM(int x, int y)
2688 int element = Feld[x][y];
2689 int direction = MovDir[x][y];
2690 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2691 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2692 int horiz_move = (dx!=0);
2693 int newx = x + dx, newy = y + dy;
2694 int step = (horiz_move ? dx : dy) * TILEX / 8;
2696 MovPos[x][y] += step;
2698 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2700 Feld[x][y] = EL_EMPTY;
2701 Feld[newx][newy] = element;
2703 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2704 MovDelay[newx][newy] = 0;
2706 if (!CAN_MOVE(element))
2707 MovDir[newx][newy] = 0;
2710 DrawField_MM(newx, newy);
2712 Stop[newx][newy] = TRUE;
2714 if (element == EL_PACMAN)
2716 if (Store[newx][newy] == EL_BOMB)
2717 Bang_MM(newx, newy);
2719 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2720 (LX + 2 * XS) / TILEX == newx &&
2721 (LY + 2 * YS) / TILEY == newy)
2728 else /* still moving on */
2733 laser.redraw = TRUE;
2736 boolean ClickElement(int x, int y, int button)
2738 static unsigned int click_delay = 0;
2739 static int click_delay_value = CLICK_DELAY;
2740 static boolean new_button = TRUE;
2741 boolean element_clicked = FALSE;
2746 /* initialize static variables */
2748 click_delay_value = CLICK_DELAY;
2754 /* do not rotate objects hit by the laser after the game was solved */
2755 if (game_mm.level_solved && Hit[x][y])
2758 if (button == MB_RELEASED)
2761 click_delay_value = CLICK_DELAY;
2763 /* release eventually hold auto-rotating mirror */
2764 RotateMirror(x, y, MB_RELEASED);
2769 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2772 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2775 if (!IN_LEV_FIELD(x, y))
2778 if (Feld[x][y] == EL_EMPTY)
2781 element = Feld[x][y];
2783 if (IS_MIRROR(element) ||
2784 IS_BEAMER(element) ||
2785 IS_POLAR(element) ||
2786 IS_POLAR_CROSS(element) ||
2787 IS_DF_MIRROR(element) ||
2788 IS_DF_MIRROR_AUTO(element))
2790 RotateMirror(x, y, button);
2792 element_clicked = TRUE;
2794 else if (IS_MCDUFFIN(element))
2796 if (!laser.fuse_off)
2798 DrawLaser(0, DL_LASER_DISABLED);
2805 element = get_rotated_element(element, BUTTON_ROTATION(button));
2806 laser.start_angle = get_element_angle(element);
2810 Feld[x][y] = element;
2817 if (!laser.fuse_off)
2820 element_clicked = TRUE;
2822 else if (element == EL_FUSE_ON && laser.fuse_off)
2824 if (x != laser.fuse_x || y != laser.fuse_y)
2827 laser.fuse_off = FALSE;
2828 laser.fuse_x = laser.fuse_y = -1;
2830 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2833 element_clicked = TRUE;
2835 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2837 laser.fuse_off = TRUE;
2840 laser.overloaded = FALSE;
2842 DrawLaser(0, DL_LASER_DISABLED);
2843 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2845 element_clicked = TRUE;
2847 else if (element == EL_LIGHTBALL)
2850 RaiseScoreElement_MM(element);
2851 DrawLaser(0, DL_LASER_ENABLED);
2853 element_clicked = TRUE;
2856 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2859 return element_clicked;
2862 void RotateMirror(int x, int y, int button)
2864 if (button == MB_RELEASED)
2866 /* release eventually hold auto-rotating mirror */
2873 if (IS_MIRROR(Feld[x][y]) ||
2874 IS_POLAR_CROSS(Feld[x][y]) ||
2875 IS_POLAR(Feld[x][y]) ||
2876 IS_BEAMER(Feld[x][y]) ||
2877 IS_DF_MIRROR(Feld[x][y]) ||
2878 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2879 IS_GRID_WOOD_AUTO(Feld[x][y]))
2881 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2883 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2885 if (button == MB_LEFTBUTTON)
2887 /* left mouse button only for manual adjustment, no auto-rotating;
2888 freeze mirror for until mouse button released */
2892 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2894 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2898 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2900 int edge = Hit[x][y];
2906 DrawLaser(edge - 1, DL_LASER_DISABLED);
2910 else if (ObjHit(x, y, HIT_POS_CENTER))
2912 int edge = Hit[x][y];
2916 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2920 DrawLaser(edge - 1, DL_LASER_DISABLED);
2927 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2932 if ((IS_BEAMER(Feld[x][y]) ||
2933 IS_POLAR(Feld[x][y]) ||
2934 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2938 if (IS_BEAMER(Feld[x][y]))
2941 printf("TEST (%d, %d) [%d] [%d]\n",
2943 laser.beamer_edge, laser.beamer[1].num);
2953 DrawLaser(0, DL_LASER_ENABLED);
2957 void AutoRotateMirrors()
2961 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2964 for (x = 0; x < lev_fieldx; x++)
2966 for (y = 0; y < lev_fieldy; y++)
2968 int element = Feld[x][y];
2970 /* do not rotate objects hit by the laser after the game was solved */
2971 if (game_mm.level_solved && Hit[x][y])
2974 if (IS_DF_MIRROR_AUTO(element) ||
2975 IS_GRID_WOOD_AUTO(element) ||
2976 IS_GRID_STEEL_AUTO(element) ||
2977 element == EL_REFRACTOR)
2978 RotateMirror(x, y, MB_RIGHTBUTTON);
2983 boolean ObjHit(int obx, int oby, int bits)
2990 if (bits & HIT_POS_CENTER)
2992 if (CheckLaserPixel(SX + obx + 15,
2997 if (bits & HIT_POS_EDGE)
2999 for (i = 0; i < 4; i++)
3000 if (CheckLaserPixel(SX + obx + 31 * (i % 2),
3001 SY + oby + 31 * (i / 2)))
3005 if (bits & HIT_POS_BETWEEN)
3007 for (i = 0; i < 4; i++)
3008 if (CheckLaserPixel(SX + 4 + obx + 22 * (i % 2),
3009 SY + 4 + oby + 22 * (i / 2)))
3016 void DeletePacMan(int px, int py)
3022 if (game_mm.num_pacman <= 1)
3024 game_mm.num_pacman = 0;
3028 for (i = 0; i < game_mm.num_pacman; i++)
3029 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3032 game_mm.num_pacman--;
3034 for (j = i; j < game_mm.num_pacman; j++)
3036 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3037 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3038 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3042 void ColorCycling(void)
3044 static int CC, Cc = 0;
3046 static int color, old = 0xF00, new = 0x010, mult = 1;
3047 static unsigned short red, green, blue;
3049 if (color_status == STATIC_COLORS)
3054 if (CC < Cc || CC > Cc + 2)
3058 color = old + new * mult;
3064 if (ABS(mult) == 16)
3074 red = 0x0e00 * ((color & 0xF00) >> 8);
3075 green = 0x0e00 * ((color & 0x0F0) >> 4);
3076 blue = 0x0e00 * ((color & 0x00F));
3077 SetRGB(pen_magicolor[0], red, green, blue);
3079 red = 0x1111 * ((color & 0xF00) >> 8);
3080 green = 0x1111 * ((color & 0x0F0) >> 4);
3081 blue = 0x1111 * ((color & 0x00F));
3082 SetRGB(pen_magicolor[1], red, green, blue);
3086 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3093 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3096 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3098 element = Feld[x][y];
3100 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3101 StartMoving_MM(x, y);
3102 else if (IS_MOVING(x, y))
3103 ContinueMoving_MM(x, y);
3104 else if (IS_EXPLODING(element))
3105 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3106 else if (element == EL_EXIT_OPENING)
3108 else if (element == EL_GRAY_BALL_OPENING)
3109 OpenSurpriseBall(x, y);
3110 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3112 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3116 AutoRotateMirrors();
3119 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3121 /* redraw after Explode_MM() ... */
3123 DrawLaser(0, DL_LASER_ENABLED);
3124 laser.redraw = FALSE;
3129 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3133 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3135 DrawLaser(0, DL_LASER_DISABLED);
3140 if (FrameReached(&energy_delay, ENERGY_DELAY))
3142 if (game_mm.energy_left > 0)
3144 game_mm.energy_left--;
3147 BlitBitmap(pix[PIX_DOOR], drawto,
3148 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3149 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3150 DX_ENERGY, DY_ENERGY);
3152 redraw_mask |= REDRAW_DOOR_1;
3154 else if (setup.time_limit && !game_mm.game_over)
3158 for (i = 15; i >= 0; i--)
3161 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3163 pen_ray = GetPixelFromRGB(window,
3164 native_mm_level.laser_red * 0x11 * i,
3165 native_mm_level.laser_green * 0x11 * i,
3166 native_mm_level.laser_blue * 0x11 * i);
3168 DrawLaser(0, DL_LASER_ENABLED);
3173 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3178 DrawLaser(0, DL_LASER_DISABLED);
3179 game_mm.game_over = TRUE;
3180 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3182 SetTileCursorActive(FALSE);
3185 if (Request("Out of magic energy ! Play it again ?",
3186 REQ_ASK | REQ_STAY_CLOSED))
3192 game_status = MAINMENU;
3201 element = laser.dest_element;
3204 if (element != Feld[ELX][ELY])
3206 printf("element == %d, Feld[ELX][ELY] == %d\n",
3207 element, Feld[ELX][ELY]);
3211 if (!laser.overloaded && laser.overload_value == 0 &&
3212 element != EL_BOMB &&
3213 element != EL_MINE &&
3214 element != EL_BALL_GRAY &&
3215 element != EL_BLOCK_STONE &&
3216 element != EL_BLOCK_WOOD &&
3217 element != EL_FUSE_ON &&
3218 element != EL_FUEL_FULL &&
3219 !IS_WALL_ICE(element) &&
3220 !IS_WALL_AMOEBA(element))
3223 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3224 (!laser.overloaded && laser.overload_value > 0)) &&
3225 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3227 if (laser.overloaded)
3228 laser.overload_value++;
3230 laser.overload_value--;
3232 if (game_mm.cheat_no_overload)
3234 laser.overloaded = FALSE;
3235 laser.overload_value = 0;
3238 game_mm.laser_overload_value = laser.overload_value;
3240 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3242 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3243 int color_down = 0xFF - color_up;
3246 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3247 (15 - (laser.overload_value / 6)) * color_scale);
3250 GetPixelFromRGB(window,
3251 (native_mm_level.laser_red ? 0xFF : color_up),
3252 (native_mm_level.laser_green ? color_down : 0x00),
3253 (native_mm_level.laser_blue ? color_down : 0x00));
3255 DrawLaser(0, DL_LASER_ENABLED);
3261 if (!laser.overloaded)
3262 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3263 else if (setup.sound_loops)
3264 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3266 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3268 if (laser.overloaded)
3271 BlitBitmap(pix[PIX_DOOR], drawto,
3272 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3273 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3274 - laser.overload_value,
3275 OVERLOAD_XSIZE, laser.overload_value,
3276 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3277 - laser.overload_value);
3279 redraw_mask |= REDRAW_DOOR_1;
3284 BlitBitmap(pix[PIX_DOOR], drawto,
3285 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3286 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3287 DX_OVERLOAD, DY_OVERLOAD);
3289 redraw_mask |= REDRAW_DOOR_1;
3292 if (laser.overload_value == MAX_LASER_OVERLOAD)
3296 for (i = 15; i >= 0; i--)
3299 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3302 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3304 DrawLaser(0, DL_LASER_ENABLED);
3309 DrawLaser(0, DL_LASER_DISABLED);
3311 game_mm.game_over = TRUE;
3312 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3314 SetTileCursorActive(FALSE);
3317 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3318 REQ_ASK | REQ_STAY_CLOSED))
3324 game_status = MAINMENU;
3338 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3340 if (game_mm.cheat_no_explosion)
3344 laser.num_damages--;
3345 DrawLaser(0, DL_LASER_DISABLED);
3346 laser.num_edges = 0;
3351 laser.dest_element = EL_EXPLODING_OPAQUE;
3355 laser.num_damages--;
3356 DrawLaser(0, DL_LASER_DISABLED);
3358 laser.num_edges = 0;
3359 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3361 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3362 REQ_ASK | REQ_STAY_CLOSED))
3368 game_status = MAINMENU;
3376 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3378 laser.fuse_off = TRUE;
3382 DrawLaser(0, DL_LASER_DISABLED);
3383 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3386 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3388 static int new_elements[] =
3391 EL_MIRROR_FIXED_START,
3393 EL_POLAR_CROSS_START,
3399 int num_new_elements = sizeof(new_elements) / sizeof(int);
3400 int new_element = new_elements[RND(num_new_elements)];
3402 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3403 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3405 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3416 element = EL_MIRROR_START + RND(16);
3422 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3429 element = (rnd == 0 ? EL_FUSE_ON :
3430 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3431 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3432 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3433 EL_MIRROR_FIXED_START + rnd - 25);
3438 graphic = el2gfx(element);
3440 for (i = 0; i < 50; i++)
3446 BlitBitmap(pix[PIX_BACK], drawto,
3447 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3448 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3449 SX + ELX * TILEX + x,
3450 SY + ELY * TILEY + y);
3452 MarkTileDirty(ELX, ELY);
3455 DrawLaser(0, DL_LASER_ENABLED);
3460 Feld[ELX][ELY] = element;
3461 DrawField_MM(ELX, ELY);
3464 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3467 /* above stuff: GRAY BALL -> PRISM !!! */
3469 LX = ELX * TILEX + 14;
3470 LY = ELY * TILEY + 14;
3471 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3478 laser.num_edges -= 2;
3479 laser.num_damages--;
3483 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3484 if (laser.damage[i].is_mirror)
3488 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3490 DrawLaser(0, DL_LASER_DISABLED);
3492 DrawLaser(0, DL_LASER_DISABLED);
3498 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3505 if (IS_WALL_ICE(element) && CT > 50)
3507 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3510 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3511 Store[ELX][ELY] = EL_WALL_ICE;
3512 Store2[ELX][ELY] = laser.wall_mask;
3514 laser.dest_element = Feld[ELX][ELY];
3519 for (i = 0; i < 5; i++)
3525 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3529 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3534 if (Feld[ELX][ELY] == EL_WALL_ICE)
3535 Feld[ELX][ELY] = EL_EMPTY;
3539 LX = laser.edge[laser.num_edges].x - (SX + 2);
3540 LY = laser.edge[laser.num_edges].y - (SY + 2);
3543 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3544 if (laser.damage[i].is_mirror)
3548 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3550 DrawLaser(0, DL_LASER_DISABLED);
3557 if (IS_WALL_AMOEBA(element) && CT > 60)
3559 int k1, k2, k3, dx, dy, de, dm;
3560 int element2 = Feld[ELX][ELY];
3562 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3565 for (i = laser.num_damages - 1; i >= 0; i--)
3566 if (laser.damage[i].is_mirror)
3569 r = laser.num_edges;
3570 d = laser.num_damages;
3577 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3580 DrawLaser(0, DL_LASER_ENABLED);
3583 x = laser.damage[k1].x;
3584 y = laser.damage[k1].y;
3589 for (i = 0; i < 4; i++)
3591 if (laser.wall_mask & (1 << i))
3593 if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3594 SY + ELY * TILEY + 31 * (i / 2)))
3597 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3598 SY + ELY * TILEY + 14 + (i / 2) * 2))
3605 for (i = 0; i < 4; i++)
3607 if (laser.wall_mask & (1 << i))
3609 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3610 SY + ELY * TILEY + 31 * (i / 2)))
3617 if (laser.num_beamers > 0 ||
3618 k1 < 1 || k2 < 4 || k3 < 4 ||
3619 CheckLaserPixel(SX + ELX * TILEX + 14,
3620 SY + ELY * TILEY + 14))
3622 laser.num_edges = r;
3623 laser.num_damages = d;
3625 DrawLaser(0, DL_LASER_DISABLED);
3628 Feld[ELX][ELY] = element | laser.wall_mask;
3632 de = Feld[ELX][ELY];
3633 dm = laser.wall_mask;
3637 int x = ELX, y = ELY;
3638 int wall_mask = laser.wall_mask;
3641 DrawLaser(0, DL_LASER_ENABLED);
3643 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3645 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3646 Store[x][y] = EL_WALL_AMOEBA;
3647 Store2[x][y] = wall_mask;
3653 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3655 DrawLaser(0, DL_LASER_ENABLED);
3657 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3659 for (i = 4; i >= 0; i--)
3661 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3667 DrawLaser(0, DL_LASER_ENABLED);
3672 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3673 laser.stops_inside_element && CT > native_mm_level.time_block)
3678 if (ABS(XS) > ABS(YS))
3685 for (i = 0; i < 4; i++)
3692 x = ELX + Step[k * 4].x;
3693 y = ELY + Step[k * 4].y;
3695 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3698 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3706 laser.overloaded = (element == EL_BLOCK_STONE);
3711 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3714 Feld[x][y] = element;
3716 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3719 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3721 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3722 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3730 if (element == EL_FUEL_FULL && CT > 10)
3732 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3735 BlitBitmap(pix[PIX_DOOR], drawto,
3736 DOOR_GFX_PAGEX4 + XX_ENERGY,
3737 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3738 ENERGY_XSIZE, i, DX_ENERGY,
3739 DY_ENERGY + ENERGY_YSIZE - i);
3742 redraw_mask |= REDRAW_DOOR_1;
3748 game_mm.energy_left = MAX_LASER_ENERGY;
3749 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3750 DrawField_MM(ELX, ELY);
3752 DrawLaser(0, DL_LASER_ENABLED);
3760 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3762 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3763 boolean button_released = (action.button == MB_RELEASED);
3765 GameActions_MM_Ext(action, warp_mode);
3767 CheckSingleStepMode_MM(element_clicked, button_released);
3772 int mx, my, ox, oy, nx, ny;
3776 if (++pacman_nr >= game_mm.num_pacman)
3779 game_mm.pacman[pacman_nr].dir--;
3781 for (l = 1; l < 5; l++)
3783 game_mm.pacman[pacman_nr].dir++;
3785 if (game_mm.pacman[pacman_nr].dir > 4)
3786 game_mm.pacman[pacman_nr].dir = 1;
3788 if (game_mm.pacman[pacman_nr].dir % 2)
3791 my = game_mm.pacman[pacman_nr].dir - 2;
3796 mx = 3 - game_mm.pacman[pacman_nr].dir;
3799 ox = game_mm.pacman[pacman_nr].x;
3800 oy = game_mm.pacman[pacman_nr].y;
3803 element = Feld[nx][ny];
3805 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3808 if (!IS_EATABLE4PACMAN(element))
3811 if (ObjHit(nx, ny, HIT_POS_CENTER))
3814 Feld[ox][oy] = EL_EMPTY;
3816 EL_PACMAN_RIGHT - 1 +
3817 (game_mm.pacman[pacman_nr].dir - 1 +
3818 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3820 game_mm.pacman[pacman_nr].x = nx;
3821 game_mm.pacman[pacman_nr].y = ny;
3823 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3825 if (element != EL_EMPTY)
3827 int graphic = el2gfx(Feld[nx][ny]);
3832 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3835 ox = SX + ox * TILEX;
3836 oy = SY + oy * TILEY;
3838 for (i = 1; i < 33; i += 2)
3839 BlitBitmap(bitmap, window,
3840 src_x, src_y, TILEX, TILEY,
3841 ox + i * mx, oy + i * my);
3842 Ct = Ct + FrameCounter - CT;
3845 DrawField_MM(nx, ny);
3848 if (!laser.fuse_off)
3850 DrawLaser(0, DL_LASER_ENABLED);
3852 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3854 AddDamagedField(nx, ny);
3856 laser.damage[laser.num_damages - 1].edge = 0;
3860 if (element == EL_BOMB)
3861 DeletePacMan(nx, ny);
3863 if (IS_WALL_AMOEBA(element) &&
3864 (LX + 2 * XS) / TILEX == nx &&
3865 (LY + 2 * YS) / TILEY == ny)
3878 boolean raise_level = FALSE;
3881 if (local_player->MovPos)
3884 local_player->LevelSolved = FALSE;
3887 if (game_mm.energy_left)
3889 if (setup.sound_loops)
3890 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3891 SND_CTRL_PLAY_LOOP);
3893 while (game_mm.energy_left > 0)
3895 if (!setup.sound_loops)
3896 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3899 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3900 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3905 game_mm.energy_left--;
3906 if (game_mm.energy_left >= 0)
3909 BlitBitmap(pix[PIX_DOOR], drawto,
3910 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3911 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3912 DX_ENERGY, DY_ENERGY);
3914 redraw_mask |= REDRAW_DOOR_1;
3921 if (setup.sound_loops)
3922 StopSound(SND_SIRR);
3924 else if (native_mm_level.time == 0) /* level without time limit */
3926 if (setup.sound_loops)
3927 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3928 SND_CTRL_PLAY_LOOP);
3930 while (TimePlayed < 999)
3932 if (!setup.sound_loops)
3933 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3934 if (TimePlayed < 999 && !(TimePlayed % 10))
3935 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3936 if (TimePlayed < 900 && !(TimePlayed % 10))
3942 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3949 if (setup.sound_loops)
3950 StopSound(SND_SIRR);
3957 CloseDoor(DOOR_CLOSE_1);
3959 Request("Level solved !", REQ_CONFIRM);
3961 if (level_nr == leveldir_current->handicap_level)
3963 leveldir_current->handicap_level++;
3964 SaveLevelSetup_SeriesInfo();
3967 if (level_editor_test_game)
3968 game_mm.score = -1; /* no highscore when playing from editor */
3969 else if (level_nr < leveldir_current->last_level)
3970 raise_level = TRUE; /* advance to next level */
3972 if ((hi_pos = NewHiScore_MM()) >= 0)
3974 game_status = HALLOFFAME;
3976 // DrawHallOfFame(hi_pos);
3983 game_status = MAINMENU;
3999 // LoadScore(level_nr);
4001 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
4002 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
4005 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4007 if (game_mm.score > highscore[k].Score)
4009 /* player has made it to the hall of fame */
4011 if (k < MAX_SCORE_ENTRIES - 1)
4013 int m = MAX_SCORE_ENTRIES - 1;
4016 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4017 if (!strcmp(setup.player_name, highscore[l].Name))
4019 if (m == k) /* player's new highscore overwrites his old one */
4023 for (l = m; l>k; l--)
4025 strcpy(highscore[l].Name, highscore[l - 1].Name);
4026 highscore[l].Score = highscore[l - 1].Score;
4033 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4034 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4035 highscore[k].Score = game_mm.score;
4042 else if (!strncmp(setup.player_name, highscore[k].Name,
4043 MAX_PLAYER_NAME_LEN))
4044 break; /* player already there with a higher score */
4049 // if (position >= 0)
4050 // SaveScore(level_nr);
4055 static void InitMovingField_MM(int x, int y, int direction)
4057 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4058 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4060 MovDir[x][y] = direction;
4061 MovDir[newx][newy] = direction;
4063 if (Feld[newx][newy] == EL_EMPTY)
4064 Feld[newx][newy] = EL_BLOCKED;
4067 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4069 int direction = MovDir[x][y];
4070 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4071 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4077 static void Blocked2Moving_MM(int x, int y,
4078 int *comes_from_x, int *comes_from_y)
4080 int oldx = x, oldy = y;
4081 int direction = MovDir[x][y];
4083 if (direction == MV_LEFT)
4085 else if (direction == MV_RIGHT)
4087 else if (direction == MV_UP)
4089 else if (direction == MV_DOWN)
4092 *comes_from_x = oldx;
4093 *comes_from_y = oldy;
4096 static int MovingOrBlocked2Element_MM(int x, int y)
4098 int element = Feld[x][y];
4100 if (element == EL_BLOCKED)
4104 Blocked2Moving_MM(x, y, &oldx, &oldy);
4106 return Feld[oldx][oldy];
4113 static void RemoveField(int x, int y)
4115 Feld[x][y] = EL_EMPTY;
4122 static void RemoveMovingField_MM(int x, int y)
4124 int oldx = x, oldy = y, newx = x, newy = y;
4126 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4129 if (IS_MOVING(x, y))
4131 Moving2Blocked_MM(x, y, &newx, &newy);
4132 if (Feld[newx][newy] != EL_BLOCKED)
4135 else if (Feld[x][y] == EL_BLOCKED)
4137 Blocked2Moving_MM(x, y, &oldx, &oldy);
4138 if (!IS_MOVING(oldx, oldy))
4142 Feld[oldx][oldy] = EL_EMPTY;
4143 Feld[newx][newy] = EL_EMPTY;
4144 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4145 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4147 DrawLevelField_MM(oldx, oldy);
4148 DrawLevelField_MM(newx, newy);
4151 void PlaySoundLevel(int x, int y, int sound_nr)
4153 int sx = SCREENX(x), sy = SCREENY(y);
4155 int silence_distance = 8;
4157 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4158 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4161 if (!IN_LEV_FIELD(x, y) ||
4162 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4163 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4166 volume = SOUND_MAX_VOLUME;
4169 stereo = (sx - SCR_FIELDX/2) * 12;
4171 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4172 if (stereo > SOUND_MAX_RIGHT)
4173 stereo = SOUND_MAX_RIGHT;
4174 if (stereo < SOUND_MAX_LEFT)
4175 stereo = SOUND_MAX_LEFT;
4178 if (!IN_SCR_FIELD(sx, sy))
4180 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4181 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4183 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4186 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4189 static void RaiseScore_MM(int value)
4191 game_mm.score += value;
4194 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4199 void RaiseScoreElement_MM(int element)
4204 case EL_PACMAN_RIGHT:
4206 case EL_PACMAN_LEFT:
4207 case EL_PACMAN_DOWN:
4208 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4212 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4217 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4221 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4230 /* ------------------------------------------------------------------------- */
4231 /* Mirror Magic game engine snapshot handling functions */
4232 /* ------------------------------------------------------------------------- */
4234 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4238 engine_snapshot_mm.game_mm = game_mm;
4239 engine_snapshot_mm.laser = laser;
4241 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4243 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4245 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4246 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4247 engine_snapshot_mm.Box[x][y] = Box[x][y];
4248 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4249 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4253 engine_snapshot_mm.LX = LX;
4254 engine_snapshot_mm.LY = LY;
4255 engine_snapshot_mm.XS = XS;
4256 engine_snapshot_mm.YS = YS;
4257 engine_snapshot_mm.ELX = ELX;
4258 engine_snapshot_mm.ELY = ELY;
4259 engine_snapshot_mm.CT = CT;
4260 engine_snapshot_mm.Ct = Ct;
4262 engine_snapshot_mm.last_LX = last_LX;
4263 engine_snapshot_mm.last_LY = last_LY;
4264 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4265 engine_snapshot_mm.hold_x = hold_x;
4266 engine_snapshot_mm.hold_y = hold_y;
4267 engine_snapshot_mm.pacman_nr = pacman_nr;
4269 engine_snapshot_mm.rotate_delay = rotate_delay;
4270 engine_snapshot_mm.pacman_delay = pacman_delay;
4271 engine_snapshot_mm.energy_delay = energy_delay;
4272 engine_snapshot_mm.overload_delay = overload_delay;
4275 void LoadEngineSnapshotValues_MM()
4279 /* stored engine snapshot buffers already restored at this point */
4281 game_mm = engine_snapshot_mm.game_mm;
4282 laser = engine_snapshot_mm.laser;
4284 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4286 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4288 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4289 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4290 Box[x][y] = engine_snapshot_mm.Box[x][y];
4291 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4292 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4296 LX = engine_snapshot_mm.LX;
4297 LY = engine_snapshot_mm.LY;
4298 XS = engine_snapshot_mm.XS;
4299 YS = engine_snapshot_mm.YS;
4300 ELX = engine_snapshot_mm.ELX;
4301 ELY = engine_snapshot_mm.ELY;
4302 CT = engine_snapshot_mm.CT;
4303 Ct = engine_snapshot_mm.Ct;
4305 last_LX = engine_snapshot_mm.last_LX;
4306 last_LY = engine_snapshot_mm.last_LY;
4307 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4308 hold_x = engine_snapshot_mm.hold_x;
4309 hold_y = engine_snapshot_mm.hold_y;
4310 pacman_nr = engine_snapshot_mm.pacman_nr;
4312 rotate_delay = engine_snapshot_mm.rotate_delay;
4313 pacman_delay = engine_snapshot_mm.pacman_delay;
4314 energy_delay = engine_snapshot_mm.energy_delay;
4315 overload_delay = engine_snapshot_mm.overload_delay;
4317 RedrawPlayfield_MM(TRUE);
4320 static int getAngleFromTouchDelta(int dx, int dy, int base)
4322 double pi = 3.141592653;
4323 double rad = atan2((double)-dy, (double)dx);
4324 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4325 double deg = rad2 * 180.0 / pi;
4327 return (int)(deg * base / 360.0 + 0.5) % base;
4330 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4332 // calculate start (source) position to be at the middle of the tile
4333 int src_mx = SX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4334 int src_my = SY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4335 int dx = dst_mx - src_mx;
4336 int dy = dst_my - src_my;
4345 if (!IN_LEV_FIELD(x, y))
4348 element = Feld[x][y];
4350 if (!IS_MCDUFFIN(element) &&
4351 !IS_MIRROR(element) &&
4352 !IS_BEAMER(element) &&
4353 !IS_POLAR(element) &&
4354 !IS_POLAR_CROSS(element) &&
4355 !IS_DF_MIRROR(element))
4358 angle_old = get_element_angle(element);
4360 if (IS_MCDUFFIN(element))
4362 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4363 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4364 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4365 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4368 else if (IS_MIRROR(element) ||
4369 IS_DF_MIRROR(element))
4371 for (i = 0; i < laser.num_damages; i++)
4373 if (laser.damage[i].x == x &&
4374 laser.damage[i].y == y &&
4375 ObjHit(x, y, HIT_POS_CENTER))
4377 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4378 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4385 if (angle_new == -1)
4387 if (IS_MIRROR(element) ||
4388 IS_DF_MIRROR(element) ||
4392 if (IS_POLAR_CROSS(element))
4395 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4398 button = (angle_new == angle_old ? 0 :
4399 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4400 MB_LEFTBUTTON : MB_RIGHTBUTTON);