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;
2494 game.restart_game_message = "Bomb killed Mc Duffin ! Play it again ?";
2497 Feld[x][y] = Store[x][y];
2498 Store[x][y] = Store2[x][y] = 0;
2499 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2501 InitField(x, y, FALSE);
2504 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2506 int graphic = IMG_MM_DEFAULT_EXPLODING;
2507 int graphic_phase = (phase / delay - 1);
2511 if (Store2[x][y] == EX_KETTLE)
2513 if (graphic_phase < 3)
2515 graphic = IMG_MM_KETTLE_EXPLODING;
2517 else if (graphic_phase < 5)
2523 graphic = IMG_EMPTY;
2527 else if (Store2[x][y] == EX_SHORT)
2529 if (graphic_phase < 4)
2535 graphic = IMG_EMPTY;
2540 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2542 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2543 FX + x * TILEX, FY + y * TILEY);
2545 MarkTileDirty(x, y);
2549 static void Bang_MM(int x, int y)
2551 int element = Feld[x][y];
2552 int mode = EX_NORMAL;
2555 DrawLaser(0, DL_LASER_ENABLED);
2574 if (IS_PACMAN(element))
2575 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2576 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2577 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2578 else if (element == EL_KEY)
2579 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2581 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2583 Explode_MM(x, y, EX_PHASE_START, mode);
2586 void TurnRound(int x, int y)
2598 { 0, 0 }, { 0, 0 }, { 0, 0 },
2603 int left, right, back;
2607 { MV_DOWN, MV_UP, MV_RIGHT },
2608 { MV_UP, MV_DOWN, MV_LEFT },
2610 { MV_LEFT, MV_RIGHT, MV_DOWN },
2611 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2612 { MV_RIGHT, MV_LEFT, MV_UP }
2615 int element = Feld[x][y];
2616 int old_move_dir = MovDir[x][y];
2617 int right_dir = turn[old_move_dir].right;
2618 int back_dir = turn[old_move_dir].back;
2619 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2620 int right_x = x + right_dx, right_y = y + right_dy;
2622 if (element == EL_PACMAN)
2624 boolean can_turn_right = FALSE;
2626 if (IN_LEV_FIELD(right_x, right_y) &&
2627 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2628 can_turn_right = TRUE;
2631 MovDir[x][y] = right_dir;
2633 MovDir[x][y] = back_dir;
2639 static void StartMoving_MM(int x, int y)
2641 int element = Feld[x][y];
2646 if (CAN_MOVE(element))
2650 if (MovDelay[x][y]) /* wait some time before next movement */
2658 /* now make next step */
2660 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2662 if (element == EL_PACMAN &&
2663 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2664 !ObjHit(newx, newy, HIT_POS_CENTER))
2666 Store[newx][newy] = Feld[newx][newy];
2667 Feld[newx][newy] = EL_EMPTY;
2669 DrawField_MM(newx, newy);
2671 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2672 ObjHit(newx, newy, HIT_POS_CENTER))
2674 /* object was running against a wall */
2681 InitMovingField_MM(x, y, MovDir[x][y]);
2685 ContinueMoving_MM(x, y);
2688 static void ContinueMoving_MM(int x, int y)
2690 int element = Feld[x][y];
2691 int direction = MovDir[x][y];
2692 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2693 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2694 int horiz_move = (dx!=0);
2695 int newx = x + dx, newy = y + dy;
2696 int step = (horiz_move ? dx : dy) * TILEX / 8;
2698 MovPos[x][y] += step;
2700 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2702 Feld[x][y] = EL_EMPTY;
2703 Feld[newx][newy] = element;
2705 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2706 MovDelay[newx][newy] = 0;
2708 if (!CAN_MOVE(element))
2709 MovDir[newx][newy] = 0;
2712 DrawField_MM(newx, newy);
2714 Stop[newx][newy] = TRUE;
2716 if (element == EL_PACMAN)
2718 if (Store[newx][newy] == EL_BOMB)
2719 Bang_MM(newx, newy);
2721 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2722 (LX + 2 * XS) / TILEX == newx &&
2723 (LY + 2 * YS) / TILEY == newy)
2730 else /* still moving on */
2735 laser.redraw = TRUE;
2738 boolean ClickElement(int x, int y, int button)
2740 static unsigned int click_delay = 0;
2741 static int click_delay_value = CLICK_DELAY;
2742 static boolean new_button = TRUE;
2743 boolean element_clicked = FALSE;
2748 /* initialize static variables */
2750 click_delay_value = CLICK_DELAY;
2756 /* do not rotate objects hit by the laser after the game was solved */
2757 if (game_mm.level_solved && Hit[x][y])
2760 if (button == MB_RELEASED)
2763 click_delay_value = CLICK_DELAY;
2765 /* release eventually hold auto-rotating mirror */
2766 RotateMirror(x, y, MB_RELEASED);
2771 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2774 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2777 if (!IN_LEV_FIELD(x, y))
2780 if (Feld[x][y] == EL_EMPTY)
2783 element = Feld[x][y];
2785 if (IS_MIRROR(element) ||
2786 IS_BEAMER(element) ||
2787 IS_POLAR(element) ||
2788 IS_POLAR_CROSS(element) ||
2789 IS_DF_MIRROR(element) ||
2790 IS_DF_MIRROR_AUTO(element))
2792 RotateMirror(x, y, button);
2794 element_clicked = TRUE;
2796 else if (IS_MCDUFFIN(element))
2798 if (!laser.fuse_off)
2800 DrawLaser(0, DL_LASER_DISABLED);
2807 element = get_rotated_element(element, BUTTON_ROTATION(button));
2808 laser.start_angle = get_element_angle(element);
2812 Feld[x][y] = element;
2819 if (!laser.fuse_off)
2822 element_clicked = TRUE;
2824 else if (element == EL_FUSE_ON && laser.fuse_off)
2826 if (x != laser.fuse_x || y != laser.fuse_y)
2829 laser.fuse_off = FALSE;
2830 laser.fuse_x = laser.fuse_y = -1;
2832 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2835 element_clicked = TRUE;
2837 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2839 laser.fuse_off = TRUE;
2842 laser.overloaded = FALSE;
2844 DrawLaser(0, DL_LASER_DISABLED);
2845 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2847 element_clicked = TRUE;
2849 else if (element == EL_LIGHTBALL)
2852 RaiseScoreElement_MM(element);
2853 DrawLaser(0, DL_LASER_ENABLED);
2855 element_clicked = TRUE;
2858 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2861 return element_clicked;
2864 void RotateMirror(int x, int y, int button)
2866 if (button == MB_RELEASED)
2868 /* release eventually hold auto-rotating mirror */
2875 if (IS_MIRROR(Feld[x][y]) ||
2876 IS_POLAR_CROSS(Feld[x][y]) ||
2877 IS_POLAR(Feld[x][y]) ||
2878 IS_BEAMER(Feld[x][y]) ||
2879 IS_DF_MIRROR(Feld[x][y]) ||
2880 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2881 IS_GRID_WOOD_AUTO(Feld[x][y]))
2883 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2885 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2887 if (button == MB_LEFTBUTTON)
2889 /* left mouse button only for manual adjustment, no auto-rotating;
2890 freeze mirror for until mouse button released */
2894 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2896 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2900 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2902 int edge = Hit[x][y];
2908 DrawLaser(edge - 1, DL_LASER_DISABLED);
2912 else if (ObjHit(x, y, HIT_POS_CENTER))
2914 int edge = Hit[x][y];
2918 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2922 DrawLaser(edge - 1, DL_LASER_DISABLED);
2929 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2934 if ((IS_BEAMER(Feld[x][y]) ||
2935 IS_POLAR(Feld[x][y]) ||
2936 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2940 if (IS_BEAMER(Feld[x][y]))
2943 printf("TEST (%d, %d) [%d] [%d]\n",
2945 laser.beamer_edge, laser.beamer[1].num);
2955 DrawLaser(0, DL_LASER_ENABLED);
2959 void AutoRotateMirrors()
2963 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2966 for (x = 0; x < lev_fieldx; x++)
2968 for (y = 0; y < lev_fieldy; y++)
2970 int element = Feld[x][y];
2972 /* do not rotate objects hit by the laser after the game was solved */
2973 if (game_mm.level_solved && Hit[x][y])
2976 if (IS_DF_MIRROR_AUTO(element) ||
2977 IS_GRID_WOOD_AUTO(element) ||
2978 IS_GRID_STEEL_AUTO(element) ||
2979 element == EL_REFRACTOR)
2980 RotateMirror(x, y, MB_RIGHTBUTTON);
2985 boolean ObjHit(int obx, int oby, int bits)
2992 if (bits & HIT_POS_CENTER)
2994 if (CheckLaserPixel(SX + obx + 15,
2999 if (bits & HIT_POS_EDGE)
3001 for (i = 0; i < 4; i++)
3002 if (CheckLaserPixel(SX + obx + 31 * (i % 2),
3003 SY + oby + 31 * (i / 2)))
3007 if (bits & HIT_POS_BETWEEN)
3009 for (i = 0; i < 4; i++)
3010 if (CheckLaserPixel(SX + 4 + obx + 22 * (i % 2),
3011 SY + 4 + oby + 22 * (i / 2)))
3018 void DeletePacMan(int px, int py)
3024 if (game_mm.num_pacman <= 1)
3026 game_mm.num_pacman = 0;
3030 for (i = 0; i < game_mm.num_pacman; i++)
3031 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3034 game_mm.num_pacman--;
3036 for (j = i; j < game_mm.num_pacman; j++)
3038 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3039 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3040 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3044 void ColorCycling(void)
3046 static int CC, Cc = 0;
3048 static int color, old = 0xF00, new = 0x010, mult = 1;
3049 static unsigned short red, green, blue;
3051 if (color_status == STATIC_COLORS)
3056 if (CC < Cc || CC > Cc + 2)
3060 color = old + new * mult;
3066 if (ABS(mult) == 16)
3076 red = 0x0e00 * ((color & 0xF00) >> 8);
3077 green = 0x0e00 * ((color & 0x0F0) >> 4);
3078 blue = 0x0e00 * ((color & 0x00F));
3079 SetRGB(pen_magicolor[0], red, green, blue);
3081 red = 0x1111 * ((color & 0xF00) >> 8);
3082 green = 0x1111 * ((color & 0x0F0) >> 4);
3083 blue = 0x1111 * ((color & 0x00F));
3084 SetRGB(pen_magicolor[1], red, green, blue);
3088 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3095 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3098 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3100 element = Feld[x][y];
3102 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3103 StartMoving_MM(x, y);
3104 else if (IS_MOVING(x, y))
3105 ContinueMoving_MM(x, y);
3106 else if (IS_EXPLODING(element))
3107 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3108 else if (element == EL_EXIT_OPENING)
3110 else if (element == EL_GRAY_BALL_OPENING)
3111 OpenSurpriseBall(x, y);
3112 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3114 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3118 AutoRotateMirrors();
3121 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3123 /* redraw after Explode_MM() ... */
3125 DrawLaser(0, DL_LASER_ENABLED);
3126 laser.redraw = FALSE;
3131 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3135 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3137 DrawLaser(0, DL_LASER_DISABLED);
3142 if (FrameReached(&energy_delay, ENERGY_DELAY))
3144 if (game_mm.energy_left > 0)
3146 game_mm.energy_left--;
3149 BlitBitmap(pix[PIX_DOOR], drawto,
3150 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3151 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3152 DX_ENERGY, DY_ENERGY);
3154 redraw_mask |= REDRAW_DOOR_1;
3156 else if (setup.time_limit && !game_mm.game_over)
3160 for (i = 15; i >= 0; i--)
3163 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3165 pen_ray = GetPixelFromRGB(window,
3166 native_mm_level.laser_red * 0x11 * i,
3167 native_mm_level.laser_green * 0x11 * i,
3168 native_mm_level.laser_blue * 0x11 * i);
3170 DrawLaser(0, DL_LASER_ENABLED);
3175 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3180 DrawLaser(0, DL_LASER_DISABLED);
3181 game_mm.game_over = TRUE;
3182 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3184 SetTileCursorActive(FALSE);
3186 game.restart_game_message = "Out of magic energy ! Play it again ?";
3189 if (Request("Out of magic energy ! Play it again ?",
3190 REQ_ASK | REQ_STAY_CLOSED))
3196 game_status = MAINMENU;
3205 element = laser.dest_element;
3208 if (element != Feld[ELX][ELY])
3210 printf("element == %d, Feld[ELX][ELY] == %d\n",
3211 element, Feld[ELX][ELY]);
3215 if (!laser.overloaded && laser.overload_value == 0 &&
3216 element != EL_BOMB &&
3217 element != EL_MINE &&
3218 element != EL_BALL_GRAY &&
3219 element != EL_BLOCK_STONE &&
3220 element != EL_BLOCK_WOOD &&
3221 element != EL_FUSE_ON &&
3222 element != EL_FUEL_FULL &&
3223 !IS_WALL_ICE(element) &&
3224 !IS_WALL_AMOEBA(element))
3227 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3228 (!laser.overloaded && laser.overload_value > 0)) &&
3229 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3231 if (laser.overloaded)
3232 laser.overload_value++;
3234 laser.overload_value--;
3236 if (game_mm.cheat_no_overload)
3238 laser.overloaded = FALSE;
3239 laser.overload_value = 0;
3242 game_mm.laser_overload_value = laser.overload_value;
3244 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3246 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3247 int color_down = 0xFF - color_up;
3250 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3251 (15 - (laser.overload_value / 6)) * color_scale);
3254 GetPixelFromRGB(window,
3255 (native_mm_level.laser_red ? 0xFF : color_up),
3256 (native_mm_level.laser_green ? color_down : 0x00),
3257 (native_mm_level.laser_blue ? color_down : 0x00));
3259 DrawLaser(0, DL_LASER_ENABLED);
3265 if (!laser.overloaded)
3266 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3267 else if (setup.sound_loops)
3268 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3270 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3272 if (laser.overloaded)
3275 BlitBitmap(pix[PIX_DOOR], drawto,
3276 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3277 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3278 - laser.overload_value,
3279 OVERLOAD_XSIZE, laser.overload_value,
3280 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3281 - laser.overload_value);
3283 redraw_mask |= REDRAW_DOOR_1;
3288 BlitBitmap(pix[PIX_DOOR], drawto,
3289 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3290 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3291 DX_OVERLOAD, DY_OVERLOAD);
3293 redraw_mask |= REDRAW_DOOR_1;
3296 if (laser.overload_value == MAX_LASER_OVERLOAD)
3300 for (i = 15; i >= 0; i--)
3303 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3306 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3308 DrawLaser(0, DL_LASER_ENABLED);
3313 DrawLaser(0, DL_LASER_DISABLED);
3315 game_mm.game_over = TRUE;
3316 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3318 SetTileCursorActive(FALSE);
3320 game.restart_game_message = "Magic spell hit Mc Duffin ! Play it again ?";
3323 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3324 REQ_ASK | REQ_STAY_CLOSED))
3330 game_status = MAINMENU;
3344 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3346 if (game_mm.cheat_no_explosion)
3350 laser.num_damages--;
3351 DrawLaser(0, DL_LASER_DISABLED);
3352 laser.num_edges = 0;
3357 laser.dest_element = EL_EXPLODING_OPAQUE;
3361 laser.num_damages--;
3362 DrawLaser(0, DL_LASER_DISABLED);
3364 laser.num_edges = 0;
3365 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3367 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3368 REQ_ASK | REQ_STAY_CLOSED))
3374 game_status = MAINMENU;
3382 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3384 laser.fuse_off = TRUE;
3388 DrawLaser(0, DL_LASER_DISABLED);
3389 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3392 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3394 static int new_elements[] =
3397 EL_MIRROR_FIXED_START,
3399 EL_POLAR_CROSS_START,
3405 int num_new_elements = sizeof(new_elements) / sizeof(int);
3406 int new_element = new_elements[RND(num_new_elements)];
3408 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3409 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3411 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3422 element = EL_MIRROR_START + RND(16);
3428 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3435 element = (rnd == 0 ? EL_FUSE_ON :
3436 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3437 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3438 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3439 EL_MIRROR_FIXED_START + rnd - 25);
3444 graphic = el2gfx(element);
3446 for (i = 0; i < 50; i++)
3452 BlitBitmap(pix[PIX_BACK], drawto,
3453 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3454 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3455 SX + ELX * TILEX + x,
3456 SY + ELY * TILEY + y);
3458 MarkTileDirty(ELX, ELY);
3461 DrawLaser(0, DL_LASER_ENABLED);
3466 Feld[ELX][ELY] = element;
3467 DrawField_MM(ELX, ELY);
3470 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3473 /* above stuff: GRAY BALL -> PRISM !!! */
3475 LX = ELX * TILEX + 14;
3476 LY = ELY * TILEY + 14;
3477 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3484 laser.num_edges -= 2;
3485 laser.num_damages--;
3489 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3490 if (laser.damage[i].is_mirror)
3494 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3496 DrawLaser(0, DL_LASER_DISABLED);
3498 DrawLaser(0, DL_LASER_DISABLED);
3504 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3511 if (IS_WALL_ICE(element) && CT > 50)
3513 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3516 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3517 Store[ELX][ELY] = EL_WALL_ICE;
3518 Store2[ELX][ELY] = laser.wall_mask;
3520 laser.dest_element = Feld[ELX][ELY];
3525 for (i = 0; i < 5; i++)
3531 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3535 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3540 if (Feld[ELX][ELY] == EL_WALL_ICE)
3541 Feld[ELX][ELY] = EL_EMPTY;
3545 LX = laser.edge[laser.num_edges].x - (SX + 2);
3546 LY = laser.edge[laser.num_edges].y - (SY + 2);
3549 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3550 if (laser.damage[i].is_mirror)
3554 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3556 DrawLaser(0, DL_LASER_DISABLED);
3563 if (IS_WALL_AMOEBA(element) && CT > 60)
3565 int k1, k2, k3, dx, dy, de, dm;
3566 int element2 = Feld[ELX][ELY];
3568 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3571 for (i = laser.num_damages - 1; i >= 0; i--)
3572 if (laser.damage[i].is_mirror)
3575 r = laser.num_edges;
3576 d = laser.num_damages;
3583 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3586 DrawLaser(0, DL_LASER_ENABLED);
3589 x = laser.damage[k1].x;
3590 y = laser.damage[k1].y;
3595 for (i = 0; i < 4; i++)
3597 if (laser.wall_mask & (1 << i))
3599 if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3600 SY + ELY * TILEY + 31 * (i / 2)))
3603 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3604 SY + ELY * TILEY + 14 + (i / 2) * 2))
3611 for (i = 0; i < 4; i++)
3613 if (laser.wall_mask & (1 << i))
3615 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3616 SY + ELY * TILEY + 31 * (i / 2)))
3623 if (laser.num_beamers > 0 ||
3624 k1 < 1 || k2 < 4 || k3 < 4 ||
3625 CheckLaserPixel(SX + ELX * TILEX + 14,
3626 SY + ELY * TILEY + 14))
3628 laser.num_edges = r;
3629 laser.num_damages = d;
3631 DrawLaser(0, DL_LASER_DISABLED);
3634 Feld[ELX][ELY] = element | laser.wall_mask;
3638 de = Feld[ELX][ELY];
3639 dm = laser.wall_mask;
3643 int x = ELX, y = ELY;
3644 int wall_mask = laser.wall_mask;
3647 DrawLaser(0, DL_LASER_ENABLED);
3649 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3651 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3652 Store[x][y] = EL_WALL_AMOEBA;
3653 Store2[x][y] = wall_mask;
3659 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3661 DrawLaser(0, DL_LASER_ENABLED);
3663 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3665 for (i = 4; i >= 0; i--)
3667 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3673 DrawLaser(0, DL_LASER_ENABLED);
3678 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3679 laser.stops_inside_element && CT > native_mm_level.time_block)
3684 if (ABS(XS) > ABS(YS))
3691 for (i = 0; i < 4; i++)
3698 x = ELX + Step[k * 4].x;
3699 y = ELY + Step[k * 4].y;
3701 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3704 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3712 laser.overloaded = (element == EL_BLOCK_STONE);
3717 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3720 Feld[x][y] = element;
3722 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3725 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3727 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3728 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3736 if (element == EL_FUEL_FULL && CT > 10)
3738 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3741 BlitBitmap(pix[PIX_DOOR], drawto,
3742 DOOR_GFX_PAGEX4 + XX_ENERGY,
3743 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3744 ENERGY_XSIZE, i, DX_ENERGY,
3745 DY_ENERGY + ENERGY_YSIZE - i);
3748 redraw_mask |= REDRAW_DOOR_1;
3754 game_mm.energy_left = MAX_LASER_ENERGY;
3755 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3756 DrawField_MM(ELX, ELY);
3758 DrawLaser(0, DL_LASER_ENABLED);
3766 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3768 boolean element_clicked = ClickElement(action.lx, action.ly, action.button);
3769 boolean button_released = (action.button == MB_RELEASED);
3771 GameActions_MM_Ext(action, warp_mode);
3773 CheckSingleStepMode_MM(element_clicked, button_released);
3778 int mx, my, ox, oy, nx, ny;
3782 if (++pacman_nr >= game_mm.num_pacman)
3785 game_mm.pacman[pacman_nr].dir--;
3787 for (l = 1; l < 5; l++)
3789 game_mm.pacman[pacman_nr].dir++;
3791 if (game_mm.pacman[pacman_nr].dir > 4)
3792 game_mm.pacman[pacman_nr].dir = 1;
3794 if (game_mm.pacman[pacman_nr].dir % 2)
3797 my = game_mm.pacman[pacman_nr].dir - 2;
3802 mx = 3 - game_mm.pacman[pacman_nr].dir;
3805 ox = game_mm.pacman[pacman_nr].x;
3806 oy = game_mm.pacman[pacman_nr].y;
3809 element = Feld[nx][ny];
3811 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3814 if (!IS_EATABLE4PACMAN(element))
3817 if (ObjHit(nx, ny, HIT_POS_CENTER))
3820 Feld[ox][oy] = EL_EMPTY;
3822 EL_PACMAN_RIGHT - 1 +
3823 (game_mm.pacman[pacman_nr].dir - 1 +
3824 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3826 game_mm.pacman[pacman_nr].x = nx;
3827 game_mm.pacman[pacman_nr].y = ny;
3829 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3831 if (element != EL_EMPTY)
3833 int graphic = el2gfx(Feld[nx][ny]);
3838 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3841 ox = SX + ox * TILEX;
3842 oy = SY + oy * TILEY;
3844 for (i = 1; i < 33; i += 2)
3845 BlitBitmap(bitmap, window,
3846 src_x, src_y, TILEX, TILEY,
3847 ox + i * mx, oy + i * my);
3848 Ct = Ct + FrameCounter - CT;
3851 DrawField_MM(nx, ny);
3854 if (!laser.fuse_off)
3856 DrawLaser(0, DL_LASER_ENABLED);
3858 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3860 AddDamagedField(nx, ny);
3862 laser.damage[laser.num_damages - 1].edge = 0;
3866 if (element == EL_BOMB)
3867 DeletePacMan(nx, ny);
3869 if (IS_WALL_AMOEBA(element) &&
3870 (LX + 2 * XS) / TILEX == nx &&
3871 (LY + 2 * YS) / TILEY == ny)
3884 boolean raise_level = FALSE;
3887 if (local_player->MovPos)
3890 local_player->LevelSolved = FALSE;
3893 if (game_mm.energy_left)
3895 if (setup.sound_loops)
3896 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3897 SND_CTRL_PLAY_LOOP);
3899 while (game_mm.energy_left > 0)
3901 if (!setup.sound_loops)
3902 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3905 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3906 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3911 game_mm.energy_left--;
3912 if (game_mm.energy_left >= 0)
3915 BlitBitmap(pix[PIX_DOOR], drawto,
3916 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3917 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3918 DX_ENERGY, DY_ENERGY);
3920 redraw_mask |= REDRAW_DOOR_1;
3927 if (setup.sound_loops)
3928 StopSound(SND_SIRR);
3930 else if (native_mm_level.time == 0) /* level without time limit */
3932 if (setup.sound_loops)
3933 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3934 SND_CTRL_PLAY_LOOP);
3936 while (TimePlayed < 999)
3938 if (!setup.sound_loops)
3939 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3940 if (TimePlayed < 999 && !(TimePlayed % 10))
3941 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3942 if (TimePlayed < 900 && !(TimePlayed % 10))
3948 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3955 if (setup.sound_loops)
3956 StopSound(SND_SIRR);
3963 CloseDoor(DOOR_CLOSE_1);
3965 Request("Level solved !", REQ_CONFIRM);
3967 if (level_nr == leveldir_current->handicap_level)
3969 leveldir_current->handicap_level++;
3970 SaveLevelSetup_SeriesInfo();
3973 if (level_editor_test_game)
3974 game_mm.score = -1; /* no highscore when playing from editor */
3975 else if (level_nr < leveldir_current->last_level)
3976 raise_level = TRUE; /* advance to next level */
3978 if ((hi_pos = NewHiScore_MM()) >= 0)
3980 game_status = HALLOFFAME;
3982 // DrawHallOfFame(hi_pos);
3989 game_status = MAINMENU;
4005 // LoadScore(level_nr);
4007 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
4008 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
4011 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4013 if (game_mm.score > highscore[k].Score)
4015 /* player has made it to the hall of fame */
4017 if (k < MAX_SCORE_ENTRIES - 1)
4019 int m = MAX_SCORE_ENTRIES - 1;
4022 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4023 if (!strcmp(setup.player_name, highscore[l].Name))
4025 if (m == k) /* player's new highscore overwrites his old one */
4029 for (l = m; l>k; l--)
4031 strcpy(highscore[l].Name, highscore[l - 1].Name);
4032 highscore[l].Score = highscore[l - 1].Score;
4039 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4040 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4041 highscore[k].Score = game_mm.score;
4048 else if (!strncmp(setup.player_name, highscore[k].Name,
4049 MAX_PLAYER_NAME_LEN))
4050 break; /* player already there with a higher score */
4055 // if (position >= 0)
4056 // SaveScore(level_nr);
4061 static void InitMovingField_MM(int x, int y, int direction)
4063 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4064 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4066 MovDir[x][y] = direction;
4067 MovDir[newx][newy] = direction;
4069 if (Feld[newx][newy] == EL_EMPTY)
4070 Feld[newx][newy] = EL_BLOCKED;
4073 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4075 int direction = MovDir[x][y];
4076 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4077 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4083 static void Blocked2Moving_MM(int x, int y,
4084 int *comes_from_x, int *comes_from_y)
4086 int oldx = x, oldy = y;
4087 int direction = MovDir[x][y];
4089 if (direction == MV_LEFT)
4091 else if (direction == MV_RIGHT)
4093 else if (direction == MV_UP)
4095 else if (direction == MV_DOWN)
4098 *comes_from_x = oldx;
4099 *comes_from_y = oldy;
4102 static int MovingOrBlocked2Element_MM(int x, int y)
4104 int element = Feld[x][y];
4106 if (element == EL_BLOCKED)
4110 Blocked2Moving_MM(x, y, &oldx, &oldy);
4112 return Feld[oldx][oldy];
4119 static void RemoveField(int x, int y)
4121 Feld[x][y] = EL_EMPTY;
4128 static void RemoveMovingField_MM(int x, int y)
4130 int oldx = x, oldy = y, newx = x, newy = y;
4132 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4135 if (IS_MOVING(x, y))
4137 Moving2Blocked_MM(x, y, &newx, &newy);
4138 if (Feld[newx][newy] != EL_BLOCKED)
4141 else if (Feld[x][y] == EL_BLOCKED)
4143 Blocked2Moving_MM(x, y, &oldx, &oldy);
4144 if (!IS_MOVING(oldx, oldy))
4148 Feld[oldx][oldy] = EL_EMPTY;
4149 Feld[newx][newy] = EL_EMPTY;
4150 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4151 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4153 DrawLevelField_MM(oldx, oldy);
4154 DrawLevelField_MM(newx, newy);
4157 void PlaySoundLevel(int x, int y, int sound_nr)
4159 int sx = SCREENX(x), sy = SCREENY(y);
4161 int silence_distance = 8;
4163 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4164 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4167 if (!IN_LEV_FIELD(x, y) ||
4168 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4169 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4172 volume = SOUND_MAX_VOLUME;
4175 stereo = (sx - SCR_FIELDX/2) * 12;
4177 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4178 if (stereo > SOUND_MAX_RIGHT)
4179 stereo = SOUND_MAX_RIGHT;
4180 if (stereo < SOUND_MAX_LEFT)
4181 stereo = SOUND_MAX_LEFT;
4184 if (!IN_SCR_FIELD(sx, sy))
4186 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4187 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4189 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4192 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4195 static void RaiseScore_MM(int value)
4197 game_mm.score += value;
4200 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4205 void RaiseScoreElement_MM(int element)
4210 case EL_PACMAN_RIGHT:
4212 case EL_PACMAN_LEFT:
4213 case EL_PACMAN_DOWN:
4214 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4218 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4223 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4227 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4236 /* ------------------------------------------------------------------------- */
4237 /* Mirror Magic game engine snapshot handling functions */
4238 /* ------------------------------------------------------------------------- */
4240 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4244 engine_snapshot_mm.game_mm = game_mm;
4245 engine_snapshot_mm.laser = laser;
4247 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4249 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4251 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4252 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4253 engine_snapshot_mm.Box[x][y] = Box[x][y];
4254 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4255 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4259 engine_snapshot_mm.LX = LX;
4260 engine_snapshot_mm.LY = LY;
4261 engine_snapshot_mm.XS = XS;
4262 engine_snapshot_mm.YS = YS;
4263 engine_snapshot_mm.ELX = ELX;
4264 engine_snapshot_mm.ELY = ELY;
4265 engine_snapshot_mm.CT = CT;
4266 engine_snapshot_mm.Ct = Ct;
4268 engine_snapshot_mm.last_LX = last_LX;
4269 engine_snapshot_mm.last_LY = last_LY;
4270 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4271 engine_snapshot_mm.hold_x = hold_x;
4272 engine_snapshot_mm.hold_y = hold_y;
4273 engine_snapshot_mm.pacman_nr = pacman_nr;
4275 engine_snapshot_mm.rotate_delay = rotate_delay;
4276 engine_snapshot_mm.pacman_delay = pacman_delay;
4277 engine_snapshot_mm.energy_delay = energy_delay;
4278 engine_snapshot_mm.overload_delay = overload_delay;
4281 void LoadEngineSnapshotValues_MM()
4285 /* stored engine snapshot buffers already restored at this point */
4287 game_mm = engine_snapshot_mm.game_mm;
4288 laser = engine_snapshot_mm.laser;
4290 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4292 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4294 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4295 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4296 Box[x][y] = engine_snapshot_mm.Box[x][y];
4297 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4298 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4302 LX = engine_snapshot_mm.LX;
4303 LY = engine_snapshot_mm.LY;
4304 XS = engine_snapshot_mm.XS;
4305 YS = engine_snapshot_mm.YS;
4306 ELX = engine_snapshot_mm.ELX;
4307 ELY = engine_snapshot_mm.ELY;
4308 CT = engine_snapshot_mm.CT;
4309 Ct = engine_snapshot_mm.Ct;
4311 last_LX = engine_snapshot_mm.last_LX;
4312 last_LY = engine_snapshot_mm.last_LY;
4313 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4314 hold_x = engine_snapshot_mm.hold_x;
4315 hold_y = engine_snapshot_mm.hold_y;
4316 pacman_nr = engine_snapshot_mm.pacman_nr;
4318 rotate_delay = engine_snapshot_mm.rotate_delay;
4319 pacman_delay = engine_snapshot_mm.pacman_delay;
4320 energy_delay = engine_snapshot_mm.energy_delay;
4321 overload_delay = engine_snapshot_mm.overload_delay;
4323 RedrawPlayfield_MM(TRUE);
4326 static int getAngleFromTouchDelta(int dx, int dy, int base)
4328 double pi = 3.141592653;
4329 double rad = atan2((double)-dy, (double)dx);
4330 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4331 double deg = rad2 * 180.0 / pi;
4333 return (int)(deg * base / 360.0 + 0.5) % base;
4336 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4338 // calculate start (source) position to be at the middle of the tile
4339 int src_mx = SX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4340 int src_my = SY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4341 int dx = dst_mx - src_mx;
4342 int dy = dst_my - src_my;
4351 if (!IN_LEV_FIELD(x, y))
4354 element = Feld[x][y];
4356 if (!IS_MCDUFFIN(element) &&
4357 !IS_MIRROR(element) &&
4358 !IS_BEAMER(element) &&
4359 !IS_POLAR(element) &&
4360 !IS_POLAR_CROSS(element) &&
4361 !IS_DF_MIRROR(element))
4364 angle_old = get_element_angle(element);
4366 if (IS_MCDUFFIN(element))
4368 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4369 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4370 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4371 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4374 else if (IS_MIRROR(element) ||
4375 IS_DF_MIRROR(element))
4377 for (i = 0; i < laser.num_damages; i++)
4379 if (laser.damage[i].x == x &&
4380 laser.damage[i].y == y &&
4381 ObjHit(x, y, HIT_POS_CENTER))
4383 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4384 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4391 if (angle_new == -1)
4393 if (IS_MIRROR(element) ||
4394 IS_DF_MIRROR(element) ||
4398 if (IS_POLAR_CROSS(element))
4401 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4404 button = (angle_new == angle_old ? 0 :
4405 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4406 MB_LEFTBUTTON : MB_RIGHTBUTTON);