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 if (game_mm.kettles_still_needed == 0)
1548 DrawLaser(0, DL_LASER_ENABLED);
1551 else if (element == EL_KEY)
1555 else if (IS_PACMAN(element))
1557 DeletePacMan(ELX, ELY);
1560 RaiseScoreElement_MM(element);
1565 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1567 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1569 DrawLaser(0, DL_LASER_ENABLED);
1571 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1573 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1574 game_mm.lights_still_needed--;
1578 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1579 game_mm.lights_still_needed++;
1582 DrawField_MM(ELX, ELY);
1583 DrawLaser(0, DL_LASER_ENABLED);
1588 laser.stops_inside_element = TRUE;
1594 printf("HitElement (4): element == %d\n", element);
1597 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1598 laser.num_beamers < MAX_NUM_BEAMERS &&
1599 laser.beamer[BEAMER_NR(element)][1].num)
1601 int beamer_angle = get_element_angle(element);
1602 int beamer_nr = BEAMER_NR(element);
1606 printf("HitElement (BEAMER): element == %d\n", element);
1609 laser.num_damages--;
1611 if (IS_FIBRE_OPTIC(element) ||
1612 laser.current_angle == get_opposite_angle(beamer_angle))
1616 LX = ELX * TILEX + 14;
1617 LY = ELY * TILEY + 14;
1619 AddLaserEdge(LX, LY);
1620 AddDamagedField(ELX, ELY);
1622 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1625 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1627 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1628 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1629 ELX = laser.beamer[beamer_nr][pos].x;
1630 ELY = laser.beamer[beamer_nr][pos].y;
1631 LX = ELX * TILEX + 14;
1632 LY = ELY * TILEY + 14;
1634 if (IS_BEAMER(element))
1636 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1637 XS = 2 * Step[laser.current_angle].x;
1638 YS = 2 * Step[laser.current_angle].y;
1641 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1643 AddLaserEdge(LX, LY);
1644 AddDamagedField(ELX, ELY);
1646 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1649 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1651 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1656 LX += step_size * XS;
1657 LY += step_size * YS;
1659 laser.num_beamers++;
1668 boolean HitOnlyAnEdge(int element, int hit_mask)
1670 /* check if the laser hit only the edge of an element and, if so, go on */
1673 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1676 if ((hit_mask == HIT_MASK_TOPLEFT ||
1677 hit_mask == HIT_MASK_TOPRIGHT ||
1678 hit_mask == HIT_MASK_BOTTOMLEFT ||
1679 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1680 laser.current_angle % 4) /* angle is not 90° */
1684 if (hit_mask == HIT_MASK_TOPLEFT)
1689 else if (hit_mask == HIT_MASK_TOPRIGHT)
1694 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1699 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1705 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1711 printf("[HitOnlyAnEdge() == TRUE]\n");
1718 printf("[HitOnlyAnEdge() == FALSE]\n");
1724 boolean HitPolarizer(int element, int hit_mask)
1726 if (HitOnlyAnEdge(element, hit_mask))
1729 if (IS_DF_GRID(element))
1731 int grid_angle = get_element_angle(element);
1734 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1735 grid_angle, laser.current_angle);
1738 AddLaserEdge(LX, LY);
1739 AddDamagedField(ELX, ELY);
1742 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1744 if (laser.current_angle == grid_angle ||
1745 laser.current_angle == get_opposite_angle(grid_angle))
1747 /* skip the whole element before continuing the scan */
1753 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1755 if (LX/TILEX > ELX || LY/TILEY > ELY)
1757 /* skipping scan positions to the right and down skips one scan
1758 position too much, because this is only the top left scan position
1759 of totally four scan positions (plus one to the right, one to the
1760 bottom and one to the bottom right) */
1766 AddLaserEdge(LX, LY);
1772 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1774 LX / TILEX, LY / TILEY,
1775 LX % TILEX, LY % TILEY);
1780 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1782 return HitReflectingWalls(element, hit_mask);
1786 return HitAbsorbingWalls(element, hit_mask);
1789 else if (IS_GRID_STEEL(element))
1791 return HitReflectingWalls(element, hit_mask);
1793 else /* IS_GRID_WOOD */
1795 return HitAbsorbingWalls(element, hit_mask);
1801 boolean HitBlock(int element, int hit_mask)
1803 boolean check = FALSE;
1805 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1806 game_mm.num_keys == 0)
1809 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1812 int ex = ELX * TILEX + 14;
1813 int ey = ELY * TILEY + 14;
1817 for (i = 1; i < 32; i++)
1822 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1827 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1828 return HitAbsorbingWalls(element, hit_mask);
1832 AddLaserEdge(LX - XS, LY - YS);
1833 AddDamagedField(ELX, ELY);
1836 Box[ELX][ELY] = laser.num_edges;
1838 return HitReflectingWalls(element, hit_mask);
1841 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1843 int xs = XS / 2, ys = YS / 2;
1844 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1845 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1847 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1848 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1850 laser.overloaded = (element == EL_GATE_STONE);
1855 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1856 (hit_mask == HIT_MASK_TOP ||
1857 hit_mask == HIT_MASK_LEFT ||
1858 hit_mask == HIT_MASK_RIGHT ||
1859 hit_mask == HIT_MASK_BOTTOM))
1860 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1861 hit_mask == HIT_MASK_BOTTOM),
1862 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1863 hit_mask == HIT_MASK_RIGHT));
1864 AddLaserEdge(LX, LY);
1870 if (element == EL_GATE_STONE && Box[ELX][ELY])
1872 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1884 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1886 int xs = XS / 2, ys = YS / 2;
1887 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1888 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1890 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1891 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1893 laser.overloaded = (element == EL_BLOCK_STONE);
1898 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1899 (hit_mask == HIT_MASK_TOP ||
1900 hit_mask == HIT_MASK_LEFT ||
1901 hit_mask == HIT_MASK_RIGHT ||
1902 hit_mask == HIT_MASK_BOTTOM))
1903 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1904 hit_mask == HIT_MASK_BOTTOM),
1905 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1906 hit_mask == HIT_MASK_RIGHT));
1907 AddDamagedField(ELX, ELY);
1909 LX = ELX * TILEX + 14;
1910 LY = ELY * TILEY + 14;
1912 AddLaserEdge(LX, LY);
1914 laser.stops_inside_element = TRUE;
1922 boolean HitLaserSource(int element, int hit_mask)
1924 if (HitOnlyAnEdge(element, hit_mask))
1927 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1929 laser.overloaded = TRUE;
1934 boolean HitLaserDestination(int element, int hit_mask)
1936 if (HitOnlyAnEdge(element, hit_mask))
1939 if (element != EL_EXIT_OPEN &&
1940 !(IS_RECEIVER(element) &&
1941 game_mm.kettles_still_needed == 0 &&
1942 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1944 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1949 if (IS_RECEIVER(element) ||
1950 (IS_22_5_ANGLE(laser.current_angle) &&
1951 (ELX != (LX + 6 * XS) / TILEX ||
1952 ELY != (LY + 6 * YS) / TILEY ||
1961 LX = ELX * TILEX + 14;
1962 LY = ELY * TILEY + 14;
1964 laser.stops_inside_element = TRUE;
1967 AddLaserEdge(LX, LY);
1968 AddDamagedField(ELX, ELY);
1970 if (game_mm.lights_still_needed == 0)
1972 game_mm.level_solved = TRUE;
1974 SetTileCursorActive(FALSE);
1980 boolean HitReflectingWalls(int element, int hit_mask)
1982 /* check if laser hits side of a wall with an angle that is not 90° */
1983 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1984 hit_mask == HIT_MASK_LEFT ||
1985 hit_mask == HIT_MASK_RIGHT ||
1986 hit_mask == HIT_MASK_BOTTOM))
1988 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1993 if (!IS_DF_GRID(element))
1994 AddLaserEdge(LX, LY);
1996 /* check if laser hits wall with an angle of 45° */
1997 if (!IS_22_5_ANGLE(laser.current_angle))
1999 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2002 laser.current_angle = get_mirrored_angle(laser.current_angle,
2005 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2008 laser.current_angle = get_mirrored_angle(laser.current_angle,
2012 AddLaserEdge(LX, LY);
2014 XS = 2 * Step[laser.current_angle].x;
2015 YS = 2 * Step[laser.current_angle].y;
2019 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
2021 laser.current_angle = get_mirrored_angle(laser.current_angle,
2026 if (!IS_DF_GRID(element))
2027 AddLaserEdge(LX, LY);
2032 if (!IS_DF_GRID(element))
2033 AddLaserEdge(LX, LY + YS / 2);
2036 if (!IS_DF_GRID(element))
2037 AddLaserEdge(LX, LY);
2040 YS = 2 * Step[laser.current_angle].y;
2044 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
2046 laser.current_angle = get_mirrored_angle(laser.current_angle,
2051 if (!IS_DF_GRID(element))
2052 AddLaserEdge(LX, LY);
2057 if (!IS_DF_GRID(element))
2058 AddLaserEdge(LX + XS / 2, LY);
2061 if (!IS_DF_GRID(element))
2062 AddLaserEdge(LX, LY);
2065 XS = 2 * Step[laser.current_angle].x;
2071 /* reflection at the edge of reflecting DF style wall */
2072 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2074 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2075 hit_mask == HIT_MASK_TOPRIGHT) ||
2076 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2077 hit_mask == HIT_MASK_TOPLEFT) ||
2078 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2079 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2080 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2081 hit_mask == HIT_MASK_BOTTOMRIGHT))
2084 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2085 ANG_MIRROR_135 : ANG_MIRROR_45);
2087 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2089 AddDamagedField(ELX, ELY);
2090 AddLaserEdge(LX, LY);
2092 laser.current_angle = get_mirrored_angle(laser.current_angle,
2100 AddLaserEdge(LX, LY);
2106 /* reflection inside an edge of reflecting DF style wall */
2107 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2109 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2110 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2111 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2112 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2113 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2114 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2115 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2116 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2119 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2120 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2121 ANG_MIRROR_135 : ANG_MIRROR_45);
2123 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2126 AddDamagedField(ELX, ELY);
2129 AddLaserEdge(LX - XS, LY - YS);
2130 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2131 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2133 laser.current_angle = get_mirrored_angle(laser.current_angle,
2141 AddLaserEdge(LX, LY);
2147 /* check if laser hits DF style wall with an angle of 90° */
2148 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2150 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2151 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2152 (IS_VERT_ANGLE(laser.current_angle) &&
2153 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2155 /* laser at last step touched nothing or the same side of the wall */
2156 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2158 AddDamagedField(ELX, ELY);
2165 last_hit_mask = hit_mask;
2172 if (!HitOnlyAnEdge(element, hit_mask))
2174 laser.overloaded = TRUE;
2182 boolean HitAbsorbingWalls(int element, int hit_mask)
2184 if (HitOnlyAnEdge(element, hit_mask))
2188 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2190 AddLaserEdge(LX - XS, LY - YS);
2197 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2199 AddLaserEdge(LX - XS, LY - YS);
2205 if (IS_WALL_WOOD(element) ||
2206 IS_DF_WALL_WOOD(element) ||
2207 IS_GRID_WOOD(element) ||
2208 IS_GRID_WOOD_FIXED(element) ||
2209 IS_GRID_WOOD_AUTO(element) ||
2210 element == EL_FUSE_ON ||
2211 element == EL_BLOCK_WOOD ||
2212 element == EL_GATE_WOOD)
2214 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2219 if (IS_WALL_ICE(element))
2223 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
2224 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
2226 /* check if laser hits wall with an angle of 90° */
2227 if (IS_90_ANGLE(laser.current_angle))
2228 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2230 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2234 for (i = 0; i < 4; i++)
2236 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2237 mask = 15 - (8 >> i);
2238 else if (ABS(XS) == 4 &&
2240 (XS > 0) == (i % 2) &&
2241 (YS < 0) == (i / 2))
2242 mask = 3 + (i / 2) * 9;
2243 else if (ABS(YS) == 4 &&
2245 (XS < 0) == (i % 2) &&
2246 (YS > 0) == (i / 2))
2247 mask = 5 + (i % 2) * 5;
2251 laser.wall_mask = mask;
2253 else if (IS_WALL_AMOEBA(element))
2255 int elx = (LX - 2 * XS) / TILEX;
2256 int ely = (LY - 2 * YS) / TILEY;
2257 int element2 = Feld[elx][ely];
2260 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2262 laser.dest_element = EL_EMPTY;
2270 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2271 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2273 if (IS_90_ANGLE(laser.current_angle))
2274 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2276 laser.dest_element = element2 | EL_WALL_AMOEBA;
2278 laser.wall_mask = mask;
2284 void OpenExit(int x, int y)
2288 if (!MovDelay[x][y]) /* next animation frame */
2289 MovDelay[x][y] = 4 * delay;
2291 if (MovDelay[x][y]) /* wait some time before next frame */
2296 phase = MovDelay[x][y] / delay;
2298 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2299 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2301 if (!MovDelay[x][y])
2303 Feld[x][y] = EL_EXIT_OPEN;
2309 void OpenSurpriseBall(int x, int y)
2313 if (!MovDelay[x][y]) /* next animation frame */
2314 MovDelay[x][y] = 50 * delay;
2316 if (MovDelay[x][y]) /* wait some time before next frame */
2320 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2323 int graphic = el2gfx(Store[x][y]);
2325 int dx = RND(26), dy = RND(26);
2327 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2329 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2330 SX + x * TILEX + dx, SY + y * TILEY + dy);
2332 MarkTileDirty(x, y);
2335 if (!MovDelay[x][y])
2337 Feld[x][y] = Store[x][y];
2346 void MeltIce(int x, int y)
2351 if (!MovDelay[x][y]) /* next animation frame */
2352 MovDelay[x][y] = frames * delay;
2354 if (MovDelay[x][y]) /* wait some time before next frame */
2357 int wall_mask = Store2[x][y];
2358 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2361 phase = frames - MovDelay[x][y] / delay - 1;
2363 if (!MovDelay[x][y])
2367 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2368 Store[x][y] = Store2[x][y] = 0;
2370 DrawWalls_MM(x, y, Feld[x][y]);
2372 if (Feld[x][y] == EL_WALL_ICE)
2373 Feld[x][y] = EL_EMPTY;
2375 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2376 if (laser.damage[i].is_mirror)
2380 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2382 DrawLaser(0, DL_LASER_DISABLED);
2386 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2388 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2390 laser.redraw = TRUE;
2395 void GrowAmoeba(int x, int y)
2400 if (!MovDelay[x][y]) /* next animation frame */
2401 MovDelay[x][y] = frames * delay;
2403 if (MovDelay[x][y]) /* wait some time before next frame */
2406 int wall_mask = Store2[x][y];
2407 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2410 phase = MovDelay[x][y] / delay;
2412 if (!MovDelay[x][y])
2414 Feld[x][y] = real_element;
2415 Store[x][y] = Store2[x][y] = 0;
2417 DrawWalls_MM(x, y, Feld[x][y]);
2418 DrawLaser(0, DL_LASER_ENABLED);
2420 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2422 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2427 static void Explode_MM(int x, int y, int phase, int mode)
2429 int num_phase = 9, delay = 2;
2430 int last_phase = num_phase * delay;
2431 int half_phase = (num_phase / 2) * delay;
2433 laser.redraw = TRUE;
2435 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2437 int center_element = Feld[x][y];
2439 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2441 /* put moving element to center field (and let it explode there) */
2442 center_element = MovingOrBlocked2Element_MM(x, y);
2443 RemoveMovingField_MM(x, y);
2445 Feld[x][y] = center_element;
2448 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2449 Store[x][y] = center_element;
2451 Store[x][y] = EL_EMPTY;
2453 Store2[x][y] = mode;
2454 Feld[x][y] = EL_EXPLODING_OPAQUE;
2455 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2461 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2463 if (phase == half_phase)
2465 Feld[x][y] = EL_EXPLODING_TRANSP;
2467 if (x == ELX && y == ELY)
2471 if (phase == last_phase)
2473 if (Store[x][y] == EL_BOMB)
2475 DrawLaser(0, DL_LASER_DISABLED);
2478 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2479 Store[x][y] = EL_EMPTY;
2481 game_mm.game_over = TRUE;
2482 game_mm.game_over_cause = GAME_OVER_BOMB;
2484 SetTileCursorActive(FALSE);
2486 laser.overloaded = FALSE;
2488 else if (IS_MCDUFFIN(Store[x][y]))
2490 Store[x][y] = EL_EMPTY;
2493 Feld[x][y] = Store[x][y];
2494 Store[x][y] = Store2[x][y] = 0;
2495 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2497 InitField(x, y, FALSE);
2500 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2502 int graphic = IMG_MM_DEFAULT_EXPLODING;
2503 int graphic_phase = (phase / delay - 1);
2507 if (Store2[x][y] == EX_KETTLE)
2509 if (graphic_phase < 3)
2511 graphic = IMG_MM_KETTLE_EXPLODING;
2513 else if (graphic_phase < 5)
2519 graphic = IMG_EMPTY;
2523 else if (Store2[x][y] == EX_SHORT)
2525 if (graphic_phase < 4)
2531 graphic = IMG_EMPTY;
2536 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2538 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2539 FX + x * TILEX, FY + y * TILEY);
2541 MarkTileDirty(x, y);
2545 static void Bang_MM(int x, int y)
2547 int element = Feld[x][y];
2548 int mode = EX_NORMAL;
2551 DrawLaser(0, DL_LASER_ENABLED);
2570 if (IS_PACMAN(element))
2571 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2572 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2573 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2574 else if (element == EL_KEY)
2575 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2577 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2579 Explode_MM(x, y, EX_PHASE_START, mode);
2582 void TurnRound(int x, int y)
2594 { 0, 0 }, { 0, 0 }, { 0, 0 },
2599 int left, right, back;
2603 { MV_DOWN, MV_UP, MV_RIGHT },
2604 { MV_UP, MV_DOWN, MV_LEFT },
2606 { MV_LEFT, MV_RIGHT, MV_DOWN },
2607 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2608 { MV_RIGHT, MV_LEFT, MV_UP }
2611 int element = Feld[x][y];
2612 int old_move_dir = MovDir[x][y];
2613 int right_dir = turn[old_move_dir].right;
2614 int back_dir = turn[old_move_dir].back;
2615 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2616 int right_x = x + right_dx, right_y = y + right_dy;
2618 if (element == EL_PACMAN)
2620 boolean can_turn_right = FALSE;
2622 if (IN_LEV_FIELD(right_x, right_y) &&
2623 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2624 can_turn_right = TRUE;
2627 MovDir[x][y] = right_dir;
2629 MovDir[x][y] = back_dir;
2635 static void StartMoving_MM(int x, int y)
2637 int element = Feld[x][y];
2642 if (CAN_MOVE(element))
2646 if (MovDelay[x][y]) /* wait some time before next movement */
2654 /* now make next step */
2656 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2658 if (element == EL_PACMAN &&
2659 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2660 !ObjHit(newx, newy, HIT_POS_CENTER))
2662 Store[newx][newy] = Feld[newx][newy];
2663 Feld[newx][newy] = EL_EMPTY;
2665 DrawField_MM(newx, newy);
2667 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2668 ObjHit(newx, newy, HIT_POS_CENTER))
2670 /* object was running against a wall */
2677 InitMovingField_MM(x, y, MovDir[x][y]);
2681 ContinueMoving_MM(x, y);
2684 static void ContinueMoving_MM(int x, int y)
2686 int element = Feld[x][y];
2687 int direction = MovDir[x][y];
2688 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2689 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2690 int horiz_move = (dx!=0);
2691 int newx = x + dx, newy = y + dy;
2692 int step = (horiz_move ? dx : dy) * TILEX / 8;
2694 MovPos[x][y] += step;
2696 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2698 Feld[x][y] = EL_EMPTY;
2699 Feld[newx][newy] = element;
2701 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2702 MovDelay[newx][newy] = 0;
2704 if (!CAN_MOVE(element))
2705 MovDir[newx][newy] = 0;
2708 DrawField_MM(newx, newy);
2710 Stop[newx][newy] = TRUE;
2712 if (element == EL_PACMAN)
2714 if (Store[newx][newy] == EL_BOMB)
2715 Bang_MM(newx, newy);
2717 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2718 (LX + 2 * XS) / TILEX == newx &&
2719 (LY + 2 * YS) / TILEY == newy)
2726 else /* still moving on */
2731 laser.redraw = TRUE;
2734 void ClickElement(int x, int y, int button)
2736 static unsigned int click_delay = 0;
2737 static int click_delay_value = CLICK_DELAY;
2738 static boolean new_button = TRUE;
2743 /* initialize static variables */
2745 click_delay_value = CLICK_DELAY;
2751 /* do not rotate objects hit by the laser after the game was solved */
2752 if (game_mm.level_solved && Hit[x][y])
2755 if (button == MB_RELEASED)
2758 click_delay_value = CLICK_DELAY;
2760 /* release eventually hold auto-rotating mirror */
2761 RotateMirror(x, y, MB_RELEASED);
2766 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2769 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2772 if (!IN_LEV_FIELD(x, y))
2775 if (Feld[x][y] == EL_EMPTY)
2778 element = Feld[x][y];
2780 if (IS_MIRROR(element) ||
2781 IS_BEAMER(element) ||
2782 IS_POLAR(element) ||
2783 IS_POLAR_CROSS(element) ||
2784 IS_DF_MIRROR(element) ||
2785 IS_DF_MIRROR_AUTO(element))
2787 RotateMirror(x, y, button);
2789 else if (IS_MCDUFFIN(element))
2791 if (!laser.fuse_off)
2793 DrawLaser(0, DL_LASER_DISABLED);
2800 element = get_rotated_element(element, BUTTON_ROTATION(button));
2801 laser.start_angle = get_element_angle(element);
2805 Feld[x][y] = element;
2812 if (!laser.fuse_off)
2815 else if (element == EL_FUSE_ON && laser.fuse_off)
2817 if (x != laser.fuse_x || y != laser.fuse_y)
2820 laser.fuse_off = FALSE;
2821 laser.fuse_x = laser.fuse_y = -1;
2823 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2826 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2828 laser.fuse_off = TRUE;
2831 laser.overloaded = FALSE;
2833 DrawLaser(0, DL_LASER_DISABLED);
2834 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2836 else if (element == EL_LIGHTBALL)
2839 RaiseScoreElement_MM(element);
2840 DrawLaser(0, DL_LASER_ENABLED);
2843 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2847 void RotateMirror(int x, int y, int button)
2849 if (button == MB_RELEASED)
2851 /* release eventually hold auto-rotating mirror */
2858 if (IS_MIRROR(Feld[x][y]) ||
2859 IS_POLAR_CROSS(Feld[x][y]) ||
2860 IS_POLAR(Feld[x][y]) ||
2861 IS_BEAMER(Feld[x][y]) ||
2862 IS_DF_MIRROR(Feld[x][y]) ||
2863 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2864 IS_GRID_WOOD_AUTO(Feld[x][y]))
2866 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2868 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2870 if (button == MB_LEFTBUTTON)
2872 /* left mouse button only for manual adjustment, no auto-rotating;
2873 freeze mirror for until mouse button released */
2877 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2879 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2883 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2885 int edge = Hit[x][y];
2891 DrawLaser(edge - 1, DL_LASER_DISABLED);
2895 else if (ObjHit(x, y, HIT_POS_CENTER))
2897 int edge = Hit[x][y];
2901 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2905 DrawLaser(edge - 1, DL_LASER_DISABLED);
2912 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2917 if ((IS_BEAMER(Feld[x][y]) ||
2918 IS_POLAR(Feld[x][y]) ||
2919 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2923 if (IS_BEAMER(Feld[x][y]))
2926 printf("TEST (%d, %d) [%d] [%d]\n",
2928 laser.beamer_edge, laser.beamer[1].num);
2938 DrawLaser(0, DL_LASER_ENABLED);
2942 void AutoRotateMirrors()
2946 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2949 for (x = 0; x < lev_fieldx; x++)
2951 for (y = 0; y < lev_fieldy; y++)
2953 int element = Feld[x][y];
2955 /* do not rotate objects hit by the laser after the game was solved */
2956 if (game_mm.level_solved && Hit[x][y])
2959 if (IS_DF_MIRROR_AUTO(element) ||
2960 IS_GRID_WOOD_AUTO(element) ||
2961 IS_GRID_STEEL_AUTO(element) ||
2962 element == EL_REFRACTOR)
2963 RotateMirror(x, y, MB_RIGHTBUTTON);
2968 boolean ObjHit(int obx, int oby, int bits)
2975 if (bits & HIT_POS_CENTER)
2977 if (CheckLaserPixel(SX + obx + 15,
2982 if (bits & HIT_POS_EDGE)
2984 for (i = 0; i < 4; i++)
2985 if (CheckLaserPixel(SX + obx + 31 * (i % 2),
2986 SY + oby + 31 * (i / 2)))
2990 if (bits & HIT_POS_BETWEEN)
2992 for (i = 0; i < 4; i++)
2993 if (CheckLaserPixel(SX + 4 + obx + 22 * (i % 2),
2994 SY + 4 + oby + 22 * (i / 2)))
3001 void DeletePacMan(int px, int py)
3007 if (game_mm.num_pacman <= 1)
3009 game_mm.num_pacman = 0;
3013 for (i = 0; i < game_mm.num_pacman; i++)
3014 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
3017 game_mm.num_pacman--;
3019 for (j = i; j < game_mm.num_pacman; j++)
3021 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
3022 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
3023 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
3027 void ColorCycling(void)
3029 static int CC, Cc = 0;
3031 static int color, old = 0xF00, new = 0x010, mult = 1;
3032 static unsigned short red, green, blue;
3034 if (color_status == STATIC_COLORS)
3039 if (CC < Cc || CC > Cc + 2)
3043 color = old + new * mult;
3049 if (ABS(mult) == 16)
3059 red = 0x0e00 * ((color & 0xF00) >> 8);
3060 green = 0x0e00 * ((color & 0x0F0) >> 4);
3061 blue = 0x0e00 * ((color & 0x00F));
3062 SetRGB(pen_magicolor[0], red, green, blue);
3064 red = 0x1111 * ((color & 0xF00) >> 8);
3065 green = 0x1111 * ((color & 0x0F0) >> 4);
3066 blue = 0x1111 * ((color & 0x00F));
3067 SetRGB(pen_magicolor[1], red, green, blue);
3071 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3078 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3081 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3083 element = Feld[x][y];
3085 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3086 StartMoving_MM(x, y);
3087 else if (IS_MOVING(x, y))
3088 ContinueMoving_MM(x, y);
3089 else if (IS_EXPLODING(element))
3090 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3091 else if (element == EL_EXIT_OPENING)
3093 else if (element == EL_GRAY_BALL_OPENING)
3094 OpenSurpriseBall(x, y);
3095 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3097 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3101 AutoRotateMirrors();
3104 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3106 /* redraw after Explode_MM() ... */
3108 DrawLaser(0, DL_LASER_ENABLED);
3109 laser.redraw = FALSE;
3114 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3118 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3120 DrawLaser(0, DL_LASER_DISABLED);
3125 if (FrameReached(&energy_delay, ENERGY_DELAY))
3127 if (game_mm.energy_left > 0)
3129 game_mm.energy_left--;
3132 BlitBitmap(pix[PIX_DOOR], drawto,
3133 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3134 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3135 DX_ENERGY, DY_ENERGY);
3137 redraw_mask |= REDRAW_DOOR_1;
3139 else if (setup.time_limit && !game_mm.game_over)
3143 for (i = 15; i >= 0; i--)
3146 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3148 pen_ray = GetPixelFromRGB(window,
3149 native_mm_level.laser_red * 0x11 * i,
3150 native_mm_level.laser_green * 0x11 * i,
3151 native_mm_level.laser_blue * 0x11 * i);
3153 DrawLaser(0, DL_LASER_ENABLED);
3158 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3163 DrawLaser(0, DL_LASER_DISABLED);
3164 game_mm.game_over = TRUE;
3165 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3167 SetTileCursorActive(FALSE);
3170 if (Request("Out of magic energy ! Play it again ?",
3171 REQ_ASK | REQ_STAY_CLOSED))
3177 game_status = MAINMENU;
3186 element = laser.dest_element;
3189 if (element != Feld[ELX][ELY])
3191 printf("element == %d, Feld[ELX][ELY] == %d\n",
3192 element, Feld[ELX][ELY]);
3196 if (!laser.overloaded && laser.overload_value == 0 &&
3197 element != EL_BOMB &&
3198 element != EL_MINE &&
3199 element != EL_BALL_GRAY &&
3200 element != EL_BLOCK_STONE &&
3201 element != EL_BLOCK_WOOD &&
3202 element != EL_FUSE_ON &&
3203 element != EL_FUEL_FULL &&
3204 !IS_WALL_ICE(element) &&
3205 !IS_WALL_AMOEBA(element))
3208 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3209 (!laser.overloaded && laser.overload_value > 0)) &&
3210 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3212 if (laser.overloaded)
3213 laser.overload_value++;
3215 laser.overload_value--;
3217 if (game_mm.cheat_no_overload)
3219 laser.overloaded = FALSE;
3220 laser.overload_value = 0;
3223 game_mm.laser_overload_value = laser.overload_value;
3225 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3227 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3228 int color_down = 0xFF - color_up;
3231 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3232 (15 - (laser.overload_value / 6)) * color_scale);
3235 GetPixelFromRGB(window,
3236 (native_mm_level.laser_red ? 0xFF : color_up),
3237 (native_mm_level.laser_green ? color_down : 0x00),
3238 (native_mm_level.laser_blue ? color_down : 0x00));
3240 DrawLaser(0, DL_LASER_ENABLED);
3246 if (!laser.overloaded)
3247 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3248 else if (setup.sound_loops)
3249 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3251 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3253 if (laser.overloaded)
3256 BlitBitmap(pix[PIX_DOOR], drawto,
3257 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3258 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3259 - laser.overload_value,
3260 OVERLOAD_XSIZE, laser.overload_value,
3261 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3262 - laser.overload_value);
3264 redraw_mask |= REDRAW_DOOR_1;
3269 BlitBitmap(pix[PIX_DOOR], drawto,
3270 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3271 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3272 DX_OVERLOAD, DY_OVERLOAD);
3274 redraw_mask |= REDRAW_DOOR_1;
3277 if (laser.overload_value == MAX_LASER_OVERLOAD)
3281 for (i = 15; i >= 0; i--)
3284 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3287 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3289 DrawLaser(0, DL_LASER_ENABLED);
3294 DrawLaser(0, DL_LASER_DISABLED);
3296 game_mm.game_over = TRUE;
3297 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3299 SetTileCursorActive(FALSE);
3302 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3303 REQ_ASK | REQ_STAY_CLOSED))
3309 game_status = MAINMENU;
3323 if (element == EL_BOMB && CT > native_mm_level.time_bomb)
3325 if (game_mm.cheat_no_explosion)
3329 laser.num_damages--;
3330 DrawLaser(0, DL_LASER_DISABLED);
3331 laser.num_edges = 0;
3336 laser.dest_element = EL_EXPLODING_OPAQUE;
3340 laser.num_damages--;
3341 DrawLaser(0, DL_LASER_DISABLED);
3343 laser.num_edges = 0;
3344 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3346 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3347 REQ_ASK | REQ_STAY_CLOSED))
3353 game_status = MAINMENU;
3361 if (element == EL_FUSE_ON && CT > native_mm_level.time_fuse)
3363 laser.fuse_off = TRUE;
3367 DrawLaser(0, DL_LASER_DISABLED);
3368 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3371 if (element == EL_BALL_GRAY && CT > native_mm_level.time_ball)
3373 static int new_elements[] =
3376 EL_MIRROR_FIXED_START,
3378 EL_POLAR_CROSS_START,
3384 int num_new_elements = sizeof(new_elements) / sizeof(int);
3385 int new_element = new_elements[RND(num_new_elements)];
3387 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3388 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3390 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3401 element = EL_MIRROR_START + RND(16);
3407 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3414 element = (rnd == 0 ? EL_FUSE_ON :
3415 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3416 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3417 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3418 EL_MIRROR_FIXED_START + rnd - 25);
3423 graphic = el2gfx(element);
3425 for (i = 0; i < 50; i++)
3431 BlitBitmap(pix[PIX_BACK], drawto,
3432 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3433 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3434 SX + ELX * TILEX + x,
3435 SY + ELY * TILEY + y);
3437 MarkTileDirty(ELX, ELY);
3440 DrawLaser(0, DL_LASER_ENABLED);
3445 Feld[ELX][ELY] = element;
3446 DrawField_MM(ELX, ELY);
3449 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3452 /* above stuff: GRAY BALL -> PRISM !!! */
3454 LX = ELX * TILEX + 14;
3455 LY = ELY * TILEY + 14;
3456 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3463 laser.num_edges -= 2;
3464 laser.num_damages--;
3468 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3469 if (laser.damage[i].is_mirror)
3473 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3475 DrawLaser(0, DL_LASER_DISABLED);
3477 DrawLaser(0, DL_LASER_DISABLED);
3483 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3490 if (IS_WALL_ICE(element) && CT > 50)
3492 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3495 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3496 Store[ELX][ELY] = EL_WALL_ICE;
3497 Store2[ELX][ELY] = laser.wall_mask;
3499 laser.dest_element = Feld[ELX][ELY];
3504 for (i = 0; i < 5; i++)
3510 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3514 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3519 if (Feld[ELX][ELY] == EL_WALL_ICE)
3520 Feld[ELX][ELY] = EL_EMPTY;
3524 LX = laser.edge[laser.num_edges].x - (SX + 2);
3525 LY = laser.edge[laser.num_edges].y - (SY + 2);
3528 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3529 if (laser.damage[i].is_mirror)
3533 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3535 DrawLaser(0, DL_LASER_DISABLED);
3542 if (IS_WALL_AMOEBA(element) && CT > 60)
3544 int k1, k2, k3, dx, dy, de, dm;
3545 int element2 = Feld[ELX][ELY];
3547 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3550 for (i = laser.num_damages - 1; i >= 0; i--)
3551 if (laser.damage[i].is_mirror)
3554 r = laser.num_edges;
3555 d = laser.num_damages;
3562 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3565 DrawLaser(0, DL_LASER_ENABLED);
3568 x = laser.damage[k1].x;
3569 y = laser.damage[k1].y;
3574 for (i = 0; i < 4; i++)
3576 if (laser.wall_mask & (1 << i))
3578 if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3579 SY + ELY * TILEY + 31 * (i / 2)))
3582 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3583 SY + ELY * TILEY + 14 + (i / 2) * 2))
3590 for (i = 0; i < 4; i++)
3592 if (laser.wall_mask & (1 << i))
3594 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3595 SY + ELY * TILEY + 31 * (i / 2)))
3602 if (laser.num_beamers > 0 ||
3603 k1 < 1 || k2 < 4 || k3 < 4 ||
3604 CheckLaserPixel(SX + ELX * TILEX + 14,
3605 SY + ELY * TILEY + 14))
3607 laser.num_edges = r;
3608 laser.num_damages = d;
3610 DrawLaser(0, DL_LASER_DISABLED);
3613 Feld[ELX][ELY] = element | laser.wall_mask;
3617 de = Feld[ELX][ELY];
3618 dm = laser.wall_mask;
3622 int x = ELX, y = ELY;
3623 int wall_mask = laser.wall_mask;
3626 DrawLaser(0, DL_LASER_ENABLED);
3628 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3630 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3631 Store[x][y] = EL_WALL_AMOEBA;
3632 Store2[x][y] = wall_mask;
3638 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3640 DrawLaser(0, DL_LASER_ENABLED);
3642 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3644 for (i = 4; i >= 0; i--)
3646 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3652 DrawLaser(0, DL_LASER_ENABLED);
3657 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3658 laser.stops_inside_element && CT > native_mm_level.time_block)
3663 if (ABS(XS) > ABS(YS))
3670 for (i = 0; i < 4; i++)
3677 x = ELX + Step[k * 4].x;
3678 y = ELY + Step[k * 4].y;
3680 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3683 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3691 laser.overloaded = (element == EL_BLOCK_STONE);
3696 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3699 Feld[x][y] = element;
3701 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3704 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3706 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3707 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3715 if (element == EL_FUEL_FULL && CT > 10)
3717 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3720 BlitBitmap(pix[PIX_DOOR], drawto,
3721 DOOR_GFX_PAGEX4 + XX_ENERGY,
3722 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3723 ENERGY_XSIZE, i, DX_ENERGY,
3724 DY_ENERGY + ENERGY_YSIZE - i);
3727 redraw_mask |= REDRAW_DOOR_1;
3733 game_mm.energy_left = MAX_LASER_ENERGY;
3734 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3735 DrawField_MM(ELX, ELY);
3737 DrawLaser(0, DL_LASER_ENABLED);
3745 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3747 ClickElement(action.lx, action.ly, action.button);
3749 GameActions_MM_Ext(action, warp_mode);
3754 int mx, my, ox, oy, nx, ny;
3758 if (++pacman_nr >= game_mm.num_pacman)
3761 game_mm.pacman[pacman_nr].dir--;
3763 for (l = 1; l < 5; l++)
3765 game_mm.pacman[pacman_nr].dir++;
3767 if (game_mm.pacman[pacman_nr].dir > 4)
3768 game_mm.pacman[pacman_nr].dir = 1;
3770 if (game_mm.pacman[pacman_nr].dir % 2)
3773 my = game_mm.pacman[pacman_nr].dir - 2;
3778 mx = 3 - game_mm.pacman[pacman_nr].dir;
3781 ox = game_mm.pacman[pacman_nr].x;
3782 oy = game_mm.pacman[pacman_nr].y;
3785 element = Feld[nx][ny];
3787 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3790 if (!IS_EATABLE4PACMAN(element))
3793 if (ObjHit(nx, ny, HIT_POS_CENTER))
3796 Feld[ox][oy] = EL_EMPTY;
3798 EL_PACMAN_RIGHT - 1 +
3799 (game_mm.pacman[pacman_nr].dir - 1 +
3800 (game_mm.pacman[pacman_nr].dir % 2) * 2);
3802 game_mm.pacman[pacman_nr].x = nx;
3803 game_mm.pacman[pacman_nr].y = ny;
3805 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3807 if (element != EL_EMPTY)
3809 int graphic = el2gfx(Feld[nx][ny]);
3814 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3817 ox = SX + ox * TILEX;
3818 oy = SY + oy * TILEY;
3820 for (i = 1; i < 33; i += 2)
3821 BlitBitmap(bitmap, window,
3822 src_x, src_y, TILEX, TILEY,
3823 ox + i * mx, oy + i * my);
3824 Ct = Ct + FrameCounter - CT;
3827 DrawField_MM(nx, ny);
3830 if (!laser.fuse_off)
3832 DrawLaser(0, DL_LASER_ENABLED);
3834 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3836 AddDamagedField(nx, ny);
3838 laser.damage[laser.num_damages - 1].edge = 0;
3842 if (element == EL_BOMB)
3843 DeletePacMan(nx, ny);
3845 if (IS_WALL_AMOEBA(element) &&
3846 (LX + 2 * XS) / TILEX == nx &&
3847 (LY + 2 * YS) / TILEY == ny)
3860 boolean raise_level = FALSE;
3863 if (local_player->MovPos)
3866 local_player->LevelSolved = FALSE;
3869 if (game_mm.energy_left)
3871 if (setup.sound_loops)
3872 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3873 SND_CTRL_PLAY_LOOP);
3875 while (game_mm.energy_left > 0)
3877 if (!setup.sound_loops)
3878 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3881 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3882 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3887 game_mm.energy_left--;
3888 if (game_mm.energy_left >= 0)
3891 BlitBitmap(pix[PIX_DOOR], drawto,
3892 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3893 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3894 DX_ENERGY, DY_ENERGY);
3896 redraw_mask |= REDRAW_DOOR_1;
3903 if (setup.sound_loops)
3904 StopSound(SND_SIRR);
3906 else if (native_mm_level.time == 0) /* level without time limit */
3908 if (setup.sound_loops)
3909 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3910 SND_CTRL_PLAY_LOOP);
3912 while (TimePlayed < 999)
3914 if (!setup.sound_loops)
3915 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3916 if (TimePlayed < 999 && !(TimePlayed % 10))
3917 RaiseScore_MM(native_mm_level.score[SC_TIME_BONUS]);
3918 if (TimePlayed < 900 && !(TimePlayed % 10))
3924 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3931 if (setup.sound_loops)
3932 StopSound(SND_SIRR);
3939 CloseDoor(DOOR_CLOSE_1);
3941 Request("Level solved !", REQ_CONFIRM);
3943 if (level_nr == leveldir_current->handicap_level)
3945 leveldir_current->handicap_level++;
3946 SaveLevelSetup_SeriesInfo();
3949 if (level_editor_test_game)
3950 game_mm.score = -1; /* no highscore when playing from editor */
3951 else if (level_nr < leveldir_current->last_level)
3952 raise_level = TRUE; /* advance to next level */
3954 if ((hi_pos = NewHiScore_MM()) >= 0)
3956 game_status = HALLOFFAME;
3958 // DrawHallOfFame(hi_pos);
3965 game_status = MAINMENU;
3981 // LoadScore(level_nr);
3983 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3984 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3987 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3989 if (game_mm.score > highscore[k].Score)
3991 /* player has made it to the hall of fame */
3993 if (k < MAX_SCORE_ENTRIES - 1)
3995 int m = MAX_SCORE_ENTRIES - 1;
3998 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3999 if (!strcmp(setup.player_name, highscore[l].Name))
4001 if (m == k) /* player's new highscore overwrites his old one */
4005 for (l = m; l>k; l--)
4007 strcpy(highscore[l].Name, highscore[l - 1].Name);
4008 highscore[l].Score = highscore[l - 1].Score;
4015 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4016 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4017 highscore[k].Score = game_mm.score;
4024 else if (!strncmp(setup.player_name, highscore[k].Name,
4025 MAX_PLAYER_NAME_LEN))
4026 break; /* player already there with a higher score */
4031 // if (position >= 0)
4032 // SaveScore(level_nr);
4037 static void InitMovingField_MM(int x, int y, int direction)
4039 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4040 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4042 MovDir[x][y] = direction;
4043 MovDir[newx][newy] = direction;
4045 if (Feld[newx][newy] == EL_EMPTY)
4046 Feld[newx][newy] = EL_BLOCKED;
4049 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
4051 int direction = MovDir[x][y];
4052 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
4053 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
4059 static void Blocked2Moving_MM(int x, int y,
4060 int *comes_from_x, int *comes_from_y)
4062 int oldx = x, oldy = y;
4063 int direction = MovDir[x][y];
4065 if (direction == MV_LEFT)
4067 else if (direction == MV_RIGHT)
4069 else if (direction == MV_UP)
4071 else if (direction == MV_DOWN)
4074 *comes_from_x = oldx;
4075 *comes_from_y = oldy;
4078 static int MovingOrBlocked2Element_MM(int x, int y)
4080 int element = Feld[x][y];
4082 if (element == EL_BLOCKED)
4086 Blocked2Moving_MM(x, y, &oldx, &oldy);
4088 return Feld[oldx][oldy];
4095 static void RemoveField(int x, int y)
4097 Feld[x][y] = EL_EMPTY;
4104 static void RemoveMovingField_MM(int x, int y)
4106 int oldx = x, oldy = y, newx = x, newy = y;
4108 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4111 if (IS_MOVING(x, y))
4113 Moving2Blocked_MM(x, y, &newx, &newy);
4114 if (Feld[newx][newy] != EL_BLOCKED)
4117 else if (Feld[x][y] == EL_BLOCKED)
4119 Blocked2Moving_MM(x, y, &oldx, &oldy);
4120 if (!IS_MOVING(oldx, oldy))
4124 Feld[oldx][oldy] = EL_EMPTY;
4125 Feld[newx][newy] = EL_EMPTY;
4126 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4127 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4129 DrawLevelField_MM(oldx, oldy);
4130 DrawLevelField_MM(newx, newy);
4133 void PlaySoundLevel(int x, int y, int sound_nr)
4135 int sx = SCREENX(x), sy = SCREENY(y);
4137 int silence_distance = 8;
4139 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4140 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4143 if (!IN_LEV_FIELD(x, y) ||
4144 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4145 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4148 volume = SOUND_MAX_VOLUME;
4151 stereo = (sx - SCR_FIELDX/2) * 12;
4153 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4154 if (stereo > SOUND_MAX_RIGHT)
4155 stereo = SOUND_MAX_RIGHT;
4156 if (stereo < SOUND_MAX_LEFT)
4157 stereo = SOUND_MAX_LEFT;
4160 if (!IN_SCR_FIELD(sx, sy))
4162 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4163 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4165 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4168 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4171 static void RaiseScore_MM(int value)
4173 game_mm.score += value;
4176 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4181 void RaiseScoreElement_MM(int element)
4186 case EL_PACMAN_RIGHT:
4188 case EL_PACMAN_LEFT:
4189 case EL_PACMAN_DOWN:
4190 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4194 RaiseScore_MM(native_mm_level.score[SC_KEY]);
4199 RaiseScore_MM(native_mm_level.score[SC_COLLECTIBLE]);
4203 RaiseScore_MM(native_mm_level.score[SC_LIGHTBALL]);
4212 /* ------------------------------------------------------------------------- */
4213 /* Mirror Magic game engine snapshot handling functions */
4214 /* ------------------------------------------------------------------------- */
4216 void SaveEngineSnapshotValues_MM(ListNode **buffers)
4220 engine_snapshot_mm.game_mm = game_mm;
4221 engine_snapshot_mm.laser = laser;
4223 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4225 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4227 engine_snapshot_mm.Ur[x][y] = Ur[x][y];
4228 engine_snapshot_mm.Hit[x][y] = Hit[x][y];
4229 engine_snapshot_mm.Box[x][y] = Box[x][y];
4230 engine_snapshot_mm.Angle[x][y] = Angle[x][y];
4231 engine_snapshot_mm.Frame[x][y] = Frame[x][y];
4235 engine_snapshot_mm.LX = LX;
4236 engine_snapshot_mm.LY = LY;
4237 engine_snapshot_mm.XS = XS;
4238 engine_snapshot_mm.YS = YS;
4239 engine_snapshot_mm.ELX = ELX;
4240 engine_snapshot_mm.ELY = ELY;
4241 engine_snapshot_mm.CT = CT;
4242 engine_snapshot_mm.Ct = Ct;
4244 engine_snapshot_mm.last_LX = last_LX;
4245 engine_snapshot_mm.last_LY = last_LY;
4246 engine_snapshot_mm.last_hit_mask = last_hit_mask;
4247 engine_snapshot_mm.hold_x = hold_x;
4248 engine_snapshot_mm.hold_y = hold_y;
4249 engine_snapshot_mm.pacman_nr = pacman_nr;
4251 engine_snapshot_mm.rotate_delay = rotate_delay;
4252 engine_snapshot_mm.pacman_delay = pacman_delay;
4253 engine_snapshot_mm.energy_delay = energy_delay;
4254 engine_snapshot_mm.overload_delay = overload_delay;
4257 void LoadEngineSnapshotValues_MM()
4261 /* stored engine snapshot buffers already restored at this point */
4263 game_mm = engine_snapshot_mm.game_mm;
4264 laser = engine_snapshot_mm.laser;
4266 for (x = 0; x < MAX_PLAYFIELD_WIDTH; x++)
4268 for (y = 0; y < MAX_PLAYFIELD_HEIGHT; y++)
4270 Ur[x][y] = engine_snapshot_mm.Ur[x][y];
4271 Hit[x][y] = engine_snapshot_mm.Hit[x][y];
4272 Box[x][y] = engine_snapshot_mm.Box[x][y];
4273 Angle[x][y] = engine_snapshot_mm.Angle[x][y];
4274 Frame[x][y] = engine_snapshot_mm.Frame[x][y];
4278 LX = engine_snapshot_mm.LX;
4279 LY = engine_snapshot_mm.LY;
4280 XS = engine_snapshot_mm.XS;
4281 YS = engine_snapshot_mm.YS;
4282 ELX = engine_snapshot_mm.ELX;
4283 ELY = engine_snapshot_mm.ELY;
4284 CT = engine_snapshot_mm.CT;
4285 Ct = engine_snapshot_mm.Ct;
4287 last_LX = engine_snapshot_mm.last_LX;
4288 last_LY = engine_snapshot_mm.last_LY;
4289 last_hit_mask = engine_snapshot_mm.last_hit_mask;
4290 hold_x = engine_snapshot_mm.hold_x;
4291 hold_y = engine_snapshot_mm.hold_y;
4292 pacman_nr = engine_snapshot_mm.pacman_nr;
4294 rotate_delay = engine_snapshot_mm.rotate_delay;
4295 pacman_delay = engine_snapshot_mm.pacman_delay;
4296 energy_delay = engine_snapshot_mm.energy_delay;
4297 overload_delay = engine_snapshot_mm.overload_delay;
4299 RedrawPlayfield_MM(TRUE);
4302 static int getAngleFromTouchDelta(int dx, int dy, int base)
4304 double pi = 3.141592653;
4305 double rad = atan2((double)-dy, (double)dx);
4306 double rad2 = (rad < 0 ? rad + 2 * pi : rad);
4307 double deg = rad2 * 180.0 / pi;
4309 return (int)(deg * base / 360.0 + 0.5) % base;
4312 int getButtonFromTouchPosition(int x, int y, int dst_mx, int dst_my)
4314 // calculate start (source) position to be at the middle of the tile
4315 int src_mx = SX + x * TILESIZE_VAR + TILESIZE_VAR / 2;
4316 int src_my = SY + y * TILESIZE_VAR + TILESIZE_VAR / 2;
4317 int dx = dst_mx - src_mx;
4318 int dy = dst_my - src_my;
4327 if (!IN_LEV_FIELD(x, y))
4330 element = Feld[x][y];
4332 if (!IS_MCDUFFIN(element) &&
4333 !IS_MIRROR(element) &&
4334 !IS_BEAMER(element) &&
4335 !IS_POLAR(element) &&
4336 !IS_POLAR_CROSS(element) &&
4337 !IS_DF_MIRROR(element))
4340 angle_old = get_element_angle(element);
4342 if (IS_MCDUFFIN(element))
4344 angle_new = (dx > 0 && ABS(dy) < ABS(dx) ? ANG_RAY_RIGHT :
4345 dy < 0 && ABS(dx) < ABS(dy) ? ANG_RAY_UP :
4346 dx < 0 && ABS(dy) < ABS(dx) ? ANG_RAY_LEFT :
4347 dy > 0 && ABS(dx) < ABS(dy) ? ANG_RAY_DOWN :
4350 else if (IS_MIRROR(element) ||
4351 IS_DF_MIRROR(element))
4353 for (i = 0; i < laser.num_damages; i++)
4355 if (laser.damage[i].x == x &&
4356 laser.damage[i].y == y &&
4357 ObjHit(x, y, HIT_POS_CENTER))
4359 angle_old = get_mirrored_angle(laser.damage[i].angle, angle_old);
4360 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4367 if (angle_new == -1)
4369 if (IS_MIRROR(element) ||
4370 IS_DF_MIRROR(element) ||
4374 if (IS_POLAR_CROSS(element))
4377 angle_new = getAngleFromTouchDelta(dx, dy, base) % phases;
4380 button = (angle_new == angle_old ? 0 :
4381 (angle_new - angle_old + phases) % phases < (phases / 2) ?
4382 MB_LEFTBUTTON : MB_RIGHTBUTTON);