1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
18 /* graphic position values for game controls */
19 #define ENERGY_XSIZE 32
20 #define ENERGY_YSIZE MAX_LASER_ENERGY
21 #define OVERLOAD_XSIZE ENERGY_XSIZE
22 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
24 /* values for Explode_MM() */
25 #define EX_PHASE_START 0
30 /* special positions in the game control window (relative to control window) */
39 #define XX_OVERLOAD 60
40 #define YY_OVERLOAD YY_ENERGY
42 /* special positions in the game control window (relative to main window) */
43 #define DX_LEVEL (DX + XX_LEVEL)
44 #define DY_LEVEL (DY + YY_LEVEL)
45 #define DX_KETTLES (DX + XX_KETTLES)
46 #define DY_KETTLES (DY + YY_KETTLES)
47 #define DX_SCORE (DX + XX_SCORE)
48 #define DY_SCORE (DY + YY_SCORE)
49 #define DX_ENERGY (DX + XX_ENERGY)
50 #define DY_ENERGY (DY + YY_ENERGY)
51 #define DX_OVERLOAD (DX + XX_OVERLOAD)
52 #define DY_OVERLOAD (DY + YY_OVERLOAD)
54 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
55 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
57 /* game button identifiers */
58 #define GAME_CTRL_ID_LEFT 0
59 #define GAME_CTRL_ID_MIDDLE 1
60 #define GAME_CTRL_ID_RIGHT 2
62 #define NUM_GAME_BUTTONS 3
64 /* values for DrawLaser() */
65 #define DL_LASER_DISABLED 0
66 #define DL_LASER_ENABLED 1
68 /* values for 'click_delay_value' in ClickElement() */
69 #define CLICK_DELAY_FIRST 12 /* delay (frames) after first click */
70 #define CLICK_DELAY 6 /* delay (frames) for pressed butten */
72 #define AUTO_ROTATE_DELAY CLICK_DELAY
73 #define INIT_GAME_ACTIONS_DELAY (ONE_SECOND_DELAY / GAME_FRAME_DELAY)
74 #define NUM_INIT_CYCLE_STEPS 16
75 #define PACMAN_MOVE_DELAY 12
76 #define ENERGY_DELAY (4 * ONE_SECOND_DELAY / GAME_FRAME_DELAY)
77 #define HEALTH_DEC_DELAY 3
78 #define HEALTH_INC_DELAY 9
79 #define HEALTH_DELAY(x) ((x) ? HEALTH_DEC_DELAY : HEALTH_INC_DELAY)
81 #define BEGIN_NO_HEADLESS \
83 boolean last_headless = program.headless; \
85 program.headless = FALSE; \
87 #define END_NO_HEADLESS \
88 program.headless = last_headless; \
91 /* forward declaration for internal use */
92 static int MovingOrBlocked2Element_MM(int, int);
93 static void Bang_MM(int, int);
94 static void RaiseScore_MM(int);
95 static void RemoveMovingField_MM(int, int);
96 static void InitMovingField_MM(int, int, int);
97 static void ContinueMoving_MM(int, int);
98 static void Moving2Blocked_MM(int, int, int *, int *);
100 /* bitmap for laser beam detection */
101 static Bitmap *laser_bitmap = NULL;
103 /* element masks for scanning pixels of MM elements */
104 static const char mm_masks[10][16][16 + 1] =
288 static int get_element_angle(int element)
290 int element_phase = get_element_phase(element);
292 if (IS_MIRROR_FIXED(element) ||
293 IS_MCDUFFIN(element) ||
295 IS_RECEIVER(element))
296 return 4 * element_phase;
298 return element_phase;
301 static int get_opposite_angle(int angle)
303 int opposite_angle = angle + ANG_RAY_180;
305 /* make sure "opposite_angle" is in valid interval [0, 15] */
306 return (opposite_angle + 16) % 16;
309 static int get_mirrored_angle(int laser_angle, int mirror_angle)
311 int reflected_angle = 16 - laser_angle + mirror_angle;
313 /* make sure "reflected_angle" is in valid interval [0, 15] */
314 return (reflected_angle + 16) % 16;
317 static void DrawLaserLines(struct XY *points, int num_points, int mode)
319 Pixel pixel_drawto = (mode == DL_LASER_ENABLED ? pen_ray : pen_bg);
320 Pixel pixel_buffer = (mode == DL_LASER_ENABLED ? WHITE_PIXEL : BLACK_PIXEL);
322 DrawLines(drawto, points, num_points, pixel_drawto);
326 DrawLines(laser_bitmap, points, num_points, pixel_buffer);
331 static boolean CheckLaserPixel(int x, int y)
337 pixel = ReadPixel(laser_bitmap, x, y);
341 return (pixel == WHITE_PIXEL);
344 static void InitMovDir_MM(int x, int y)
346 int element = Feld[x][y];
347 static int direction[3][4] =
349 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
350 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
351 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
356 case EL_PACMAN_RIGHT:
360 Feld[x][y] = EL_PACMAN;
361 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
369 static void InitField(int x, int y, boolean init_game)
371 int element = Feld[x][y];
376 Feld[x][y] = EL_EMPTY;
381 if (native_mm_level.auto_count_kettles)
382 game_mm.kettles_still_needed++;
385 case EL_LIGHTBULB_OFF:
386 game_mm.lights_still_needed++;
390 if (IS_MIRROR(element) ||
391 IS_BEAMER_OLD(element) ||
392 IS_BEAMER(element) ||
394 IS_POLAR_CROSS(element) ||
395 IS_DF_MIRROR(element) ||
396 IS_DF_MIRROR_AUTO(element) ||
397 IS_GRID_STEEL_AUTO(element) ||
398 IS_GRID_WOOD_AUTO(element) ||
399 IS_FIBRE_OPTIC(element))
401 if (IS_BEAMER_OLD(element))
403 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
404 element = Feld[x][y];
407 if (!IS_FIBRE_OPTIC(element))
409 static int steps_grid_auto = 0;
411 if (game_mm.num_cycle == 0) /* initialize cycle steps for grids */
412 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
414 if (IS_GRID_STEEL_AUTO(element) ||
415 IS_GRID_WOOD_AUTO(element))
416 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
418 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
420 game_mm.cycle[game_mm.num_cycle].x = x;
421 game_mm.cycle[game_mm.num_cycle].y = y;
425 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
427 int beamer_nr = BEAMER_NR(element);
428 int nr = laser.beamer[beamer_nr][0].num;
430 laser.beamer[beamer_nr][nr].x = x;
431 laser.beamer[beamer_nr][nr].y = y;
432 laser.beamer[beamer_nr][nr].num = 1;
435 else if (IS_PACMAN(element))
439 else if (IS_MCDUFFIN(element) || IS_LASER(element))
441 laser.start_edge.x = x;
442 laser.start_edge.y = y;
443 laser.start_angle = get_element_angle(element);
450 static void InitCycleElements_RotateSingleStep()
454 if (game_mm.num_cycle == 0) /* no elements to cycle */
457 for (i = 0; i < game_mm.num_cycle; i++)
459 int x = game_mm.cycle[i].x;
460 int y = game_mm.cycle[i].y;
461 int step = SIGN(game_mm.cycle[i].steps);
462 int last_element = Feld[x][y];
463 int next_element = get_rotated_element(last_element, step);
465 if (!game_mm.cycle[i].steps)
468 Feld[x][y] = next_element;
471 game_mm.cycle[i].steps -= step;
475 static void InitLaser()
477 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
478 int step = (IS_LASER(start_element) ? 4 : 0);
480 LX = laser.start_edge.x * TILEX;
481 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
484 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
486 LY = laser.start_edge.y * TILEY;
487 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
488 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
492 XS = 2 * Step[laser.start_angle].x;
493 YS = 2 * Step[laser.start_angle].y;
495 laser.current_angle = laser.start_angle;
497 laser.num_damages = 0;
499 laser.num_beamers = 0;
500 laser.beamer_edge[0] = 0;
502 laser.dest_element = EL_EMPTY;
505 AddLaserEdge(LX, LY); /* set laser starting edge */
507 pen_ray = GetPixelFromRGB(window,
508 native_mm_level.laser_red * 0xFF,
509 native_mm_level.laser_green * 0xFF,
510 native_mm_level.laser_blue * 0xFF);
513 void InitGameEngine_MM()
519 /* initialize laser bitmap to current playfield (screen) size */
520 ReCreateBitmap(&laser_bitmap, drawto->width, drawto->height);
521 ClearRectangle(laser_bitmap, 0, 0, drawto->width, drawto->height);
525 /* set global game control values */
526 game_mm.num_cycle = 0;
527 game_mm.num_pacman = 0;
530 game_mm.energy_left = 0; // later set to "native_mm_level.time"
531 game_mm.kettles_still_needed =
532 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
533 game_mm.lights_still_needed = 0;
534 game_mm.num_keys = 0;
536 game_mm.level_solved = FALSE;
537 game_mm.game_over = FALSE;
538 game_mm.game_over_cause = 0;
540 game_mm.laser_overload_value = 0;
542 /* set global laser control values (must be set before "InitLaser()") */
543 laser.start_edge.x = 0;
544 laser.start_edge.y = 0;
545 laser.start_angle = 0;
547 for (i = 0; i < MAX_NUM_BEAMERS; i++)
548 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
550 laser.overloaded = FALSE;
551 laser.overload_value = 0;
552 laser.fuse_off = FALSE;
553 laser.fuse_x = laser.fuse_y = -1;
555 laser.dest_element = EL_EMPTY;
560 for (x = 0; x < lev_fieldx; x++)
562 for (y = 0; y < lev_fieldy; y++)
564 Feld[x][y] = Ur[x][y];
565 Hit[x][y] = Box[x][y] = 0;
567 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
568 Store[x][y] = Store2[x][y] = 0;
572 InitField(x, y, TRUE);
577 CloseDoor(DOOR_CLOSE_1);
583 void InitGameActions_MM()
585 int num_init_game_frames = INIT_GAME_ACTIONS_DELAY;
586 int cycle_steps_done = 0;
592 /* copy default game door content to main double buffer */
593 BlitBitmap(pix[PIX_DOOR], drawto,
594 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
598 DrawText(DX_LEVEL, DY_LEVEL,
599 int2str(level_nr, 2), FONT_TEXT_2);
600 DrawText(DX_KETTLES, DY_KETTLES,
601 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
602 DrawText(DX_SCORE, DY_SCORE,
603 int2str(game_mm.score, 4), FONT_TEXT_2);
612 /* copy actual game door content to door double buffer for OpenDoor() */
613 BlitBitmap(drawto, pix[PIX_DB_DOOR],
614 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
618 OpenDoor(DOOR_OPEN_ALL);
621 for (i = 0; i <= num_init_game_frames; i++)
623 if (i == num_init_game_frames)
624 StopSound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
625 else if (setup.sound_loops)
626 PlaySoundLoop_MM(SND_MM_GAME_LEVELTIME_CHARGING);
628 PlaySound_MM(SND_MM_GAME_LEVELTIME_CHARGING);
630 game_mm.energy_left = native_mm_level.time * i / num_init_game_frames;
632 UpdateAndDisplayGameControlValues();
634 while (cycle_steps_done < NUM_INIT_CYCLE_STEPS * i / num_init_game_frames)
636 InitCycleElements_RotateSingleStep();
646 if (setup.quick_doors)
652 if (setup.sound_music && num_bg_loops)
653 PlayMusic(level_nr % num_bg_loops);
659 void AddLaserEdge(int lx, int ly)
661 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
663 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
668 laser.edge[laser.num_edges].x = SX + 2 + lx;
669 laser.edge[laser.num_edges].y = SY + 2 + ly;
675 void AddDamagedField(int ex, int ey)
677 laser.damage[laser.num_damages].is_mirror = FALSE;
678 laser.damage[laser.num_damages].angle = laser.current_angle;
679 laser.damage[laser.num_damages].edge = laser.num_edges;
680 laser.damage[laser.num_damages].x = ex;
681 laser.damage[laser.num_damages].y = ey;
691 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
692 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
694 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
700 static int getMaskFromElement(int element)
702 if (IS_GRID(element))
703 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
704 else if (IS_MCDUFFIN(element))
705 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
706 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
707 return IMG_MM_MASK_RECTANGLE;
709 return IMG_MM_MASK_CIRCLE;
717 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
718 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
721 /* follow laser beam until it hits something (at least the screen border) */
722 while (hit_mask == HIT_MASK_NO_HIT)
728 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
729 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
731 printf("ScanPixel: touched screen border!\n");
737 for (i = 0; i < 4; i++)
739 int px = LX + (i % 2) * 2;
740 int py = LY + (i / 2) * 2;
743 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
744 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
747 if (IN_LEV_FIELD(lx, ly))
749 int element = Feld[lx][ly];
751 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
755 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
757 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
759 pixel = ((element & (1 << pos)) ? 1 : 0);
763 int pos = getMaskFromElement(element) - IMG_MM_MASK_MCDUFFIN_RIGHT;
765 pixel = (mm_masks[pos][dy / 2][dx / 2] == 'X' ? 1 : 0);
770 pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
771 SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
774 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
775 hit_mask |= (1 << i);
778 if (hit_mask == HIT_MASK_NO_HIT)
780 /* hit nothing -- go on with another step */
792 int end = 0, rf = laser.num_edges;
794 /* do not scan laser again after the game was lost for whatever reason */
795 if (game_mm.game_over)
798 laser.overloaded = FALSE;
799 laser.stops_inside_element = FALSE;
801 DrawLaser(0, DL_LASER_ENABLED);
804 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
812 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
815 laser.overloaded = TRUE;
820 hit_mask = ScanPixel();
823 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
827 /* hit something -- check out what it was */
828 ELX = (LX + XS) / TILEX;
829 ELY = (LY + YS) / TILEY;
832 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
833 hit_mask, LX, LY, ELX, ELY);
836 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
839 laser.dest_element = element;
844 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
846 /* we have hit the top-right and bottom-left element --
847 choose the bottom-left one */
848 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
849 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
850 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
851 ELX = (LX - 2) / TILEX;
852 ELY = (LY + 2) / TILEY;
855 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
857 /* we have hit the top-left and bottom-right element --
858 choose the top-left one */
859 /* !!! SEE ABOVE !!! */
860 ELX = (LX - 2) / TILEX;
861 ELY = (LY - 2) / TILEY;
865 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
866 hit_mask, LX, LY, ELX, ELY);
869 element = Feld[ELX][ELY];
870 laser.dest_element = element;
873 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
876 LX % TILEX, LY % TILEY,
881 if (!IN_LEV_FIELD(ELX, ELY))
882 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
885 if (element == EL_EMPTY)
887 if (!HitOnlyAnEdge(element, hit_mask))
890 else if (element == EL_FUSE_ON)
892 if (HitPolarizer(element, hit_mask))
895 else if (IS_GRID(element) || IS_DF_GRID(element))
897 if (HitPolarizer(element, hit_mask))
900 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
901 element == EL_GATE_STONE || element == EL_GATE_WOOD)
903 if (HitBlock(element, hit_mask))
910 else if (IS_MCDUFFIN(element))
912 if (HitLaserSource(element, hit_mask))
915 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
916 IS_RECEIVER(element))
918 if (HitLaserDestination(element, hit_mask))
921 else if (IS_WALL(element))
923 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
925 if (HitReflectingWalls(element, hit_mask))
930 if (HitAbsorbingWalls(element, hit_mask))
936 if (HitElement(element, hit_mask))
941 DrawLaser(rf - 1, DL_LASER_ENABLED);
942 rf = laser.num_edges;
946 if (laser.dest_element != Feld[ELX][ELY])
948 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
949 laser.dest_element, Feld[ELX][ELY]);
953 if (!end && !laser.stops_inside_element && !StepBehind())
956 printf("ScanLaser: Go one step back\n");
962 AddLaserEdge(LX, LY);
966 DrawLaser(rf - 1, DL_LASER_ENABLED);
968 Ct = CT = FrameCounter;
971 if (!IN_LEV_FIELD(ELX, ELY))
972 printf("WARNING! (2) %d, %d\n", ELX, ELY);
976 void DrawLaserExt(int start_edge, int num_edges, int mode)
982 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
983 start_edge, num_edges, mode);
988 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
995 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
1001 if (mode == DL_LASER_DISABLED)
1003 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
1007 /* now draw the laser to the backbuffer and (if enabled) to the screen */
1008 DrawLaserLines(&laser.edge[start_edge], num_edges, mode);
1010 redraw_mask |= REDRAW_FIELD;
1012 if (mode == DL_LASER_ENABLED)
1015 /* after the laser was deleted, the "damaged" graphics must be restored */
1016 if (laser.num_damages)
1018 int damage_start = 0;
1021 /* determine the starting edge, from which graphics need to be restored */
1024 for (i = 0; i < laser.num_damages; i++)
1026 if (laser.damage[i].edge == start_edge + 1)
1035 /* restore graphics from this starting edge to the end of damage list */
1036 for (i = damage_start; i < laser.num_damages; i++)
1038 int lx = laser.damage[i].x;
1039 int ly = laser.damage[i].y;
1040 int element = Feld[lx][ly];
1042 if (Hit[lx][ly] == laser.damage[i].edge)
1043 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1046 if (Box[lx][ly] == laser.damage[i].edge)
1049 if (IS_DRAWABLE(element))
1050 DrawField_MM(lx, ly);
1053 elx = laser.damage[damage_start].x;
1054 ely = laser.damage[damage_start].y;
1055 element = Feld[elx][ely];
1058 if (IS_BEAMER(element))
1062 for (i = 0; i < laser.num_beamers; i++)
1063 printf("-> %d\n", laser.beamer_edge[i]);
1064 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
1065 mode, elx, ely, Hit[elx][ely], start_edge);
1066 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
1067 get_element_angle(element), laser.damage[damage_start].angle);
1071 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1072 laser.num_beamers > 0 &&
1073 start_edge == laser.beamer_edge[laser.num_beamers - 1])
1075 /* element is outgoing beamer */
1076 laser.num_damages = damage_start + 1;
1078 if (IS_BEAMER(element))
1079 laser.current_angle = get_element_angle(element);
1083 /* element is incoming beamer or other element */
1084 laser.num_damages = damage_start;
1085 laser.current_angle = laser.damage[laser.num_damages].angle;
1090 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
1092 elx = laser.start_edge.x;
1093 ely = laser.start_edge.y;
1094 element = Feld[elx][ely];
1097 laser.num_edges = start_edge + 1;
1098 if (start_edge == 0)
1099 laser.current_angle = laser.start_angle;
1101 LX = laser.edge[start_edge].x - (SX + 2);
1102 LY = laser.edge[start_edge].y - (SY + 2);
1103 XS = 2 * Step[laser.current_angle].x;
1104 YS = 2 * Step[laser.current_angle].y;
1107 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
1113 if (IS_BEAMER(element) ||
1114 IS_FIBRE_OPTIC(element) ||
1115 IS_PACMAN(element) ||
1116 IS_POLAR(element) ||
1117 IS_POLAR_CROSS(element) ||
1118 element == EL_FUSE_ON)
1123 printf("element == %d\n", element);
1126 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
1127 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
1131 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
1132 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1133 (laser.num_beamers == 0 ||
1134 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
1136 /* element is incoming beamer or other element */
1137 step_size = -step_size;
1142 if (IS_BEAMER(element))
1144 printf("start_edge == %d, laser.beamer_edge == %d\n",
1145 start_edge, laser.beamer_edge);
1149 LX += step_size * XS;
1150 LY += step_size * YS;
1152 else if (element != EL_EMPTY)
1161 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
1166 void DrawLaser(int start_edge, int mode)
1168 if (laser.num_edges - start_edge < 0)
1170 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
1175 /* check if laser is interrupted by beamer element */
1176 if (laser.num_beamers > 0 &&
1177 start_edge < laser.beamer_edge[laser.num_beamers - 1])
1179 if (mode == DL_LASER_ENABLED)
1182 int tmp_start_edge = start_edge;
1184 /* draw laser segments forward from the start to the last beamer */
1185 for (i = 0; i < laser.num_beamers; i++)
1187 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
1189 if (tmp_num_edges <= 0)
1193 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
1194 i, laser.beamer_edge[i], tmp_start_edge);
1197 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
1199 tmp_start_edge = laser.beamer_edge[i];
1202 /* draw last segment from last beamer to the end */
1203 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
1209 int last_num_edges = laser.num_edges;
1210 int num_beamers = laser.num_beamers;
1212 /* delete laser segments backward from the end to the first beamer */
1213 for (i = num_beamers-1; i >= 0; i--)
1215 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
1217 if (laser.beamer_edge[i] - start_edge <= 0)
1220 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
1222 last_num_edges = laser.beamer_edge[i];
1223 laser.num_beamers--;
1227 if (last_num_edges - start_edge <= 0)
1228 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
1229 last_num_edges, start_edge);
1232 // special case when rotating first beamer: delete laser edge on beamer
1233 // (but do not start scanning on previous edge to prevent mirror sound)
1234 if (last_num_edges - start_edge == 1 && start_edge > 0)
1235 DrawLaserLines(&laser.edge[start_edge - 1], 2, DL_LASER_DISABLED);
1237 /* delete first segment from start to the first beamer */
1238 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1243 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1247 boolean HitElement(int element, int hit_mask)
1249 if (HitOnlyAnEdge(element, hit_mask))
1252 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1253 element = MovingOrBlocked2Element_MM(ELX, ELY);
1256 printf("HitElement (1): element == %d\n", element);
1260 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1261 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1263 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1266 AddDamagedField(ELX, ELY);
1268 /* this is more precise: check if laser would go through the center */
1269 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1271 /* skip the whole element before continuing the scan */
1277 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1279 if (LX/TILEX > ELX || LY/TILEY > ELY)
1281 /* skipping scan positions to the right and down skips one scan
1282 position too much, because this is only the top left scan position
1283 of totally four scan positions (plus one to the right, one to the
1284 bottom and one to the bottom right) */
1294 printf("HitElement (2): element == %d\n", element);
1297 if (LX + 5 * XS < 0 ||
1307 printf("HitElement (3): element == %d\n", element);
1310 if (IS_POLAR(element) &&
1311 ((element - EL_POLAR_START) % 2 ||
1312 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1314 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1316 laser.num_damages--;
1321 if (IS_POLAR_CROSS(element) &&
1322 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1324 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1326 laser.num_damages--;
1331 if (!IS_BEAMER(element) &&
1332 !IS_FIBRE_OPTIC(element) &&
1333 !IS_GRID_WOOD(element) &&
1334 element != EL_FUEL_EMPTY)
1337 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1338 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1340 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1343 LX = ELX * TILEX + 14;
1344 LY = ELY * TILEY + 14;
1346 AddLaserEdge(LX, LY);
1349 if (IS_MIRROR(element) ||
1350 IS_MIRROR_FIXED(element) ||
1351 IS_POLAR(element) ||
1352 IS_POLAR_CROSS(element) ||
1353 IS_DF_MIRROR(element) ||
1354 IS_DF_MIRROR_AUTO(element) ||
1355 element == EL_PRISM ||
1356 element == EL_REFRACTOR)
1358 int current_angle = laser.current_angle;
1361 laser.num_damages--;
1363 AddDamagedField(ELX, ELY);
1365 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1368 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1370 if (IS_MIRROR(element) ||
1371 IS_MIRROR_FIXED(element) ||
1372 IS_DF_MIRROR(element) ||
1373 IS_DF_MIRROR_AUTO(element))
1374 laser.current_angle = get_mirrored_angle(laser.current_angle,
1375 get_element_angle(element));
1377 if (element == EL_PRISM || element == EL_REFRACTOR)
1378 laser.current_angle = RND(16);
1380 XS = 2 * Step[laser.current_angle].x;
1381 YS = 2 * Step[laser.current_angle].y;
1383 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1388 LX += step_size * XS;
1389 LY += step_size * YS;
1392 /* draw sparkles on mirror */
1393 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1394 current_angle != laser.current_angle)
1396 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1400 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1401 current_angle != laser.current_angle)
1402 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1405 (get_opposite_angle(laser.current_angle) ==
1406 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1408 return (laser.overloaded ? TRUE : FALSE);
1411 if (element == EL_FUEL_FULL)
1413 laser.stops_inside_element = TRUE;
1418 if (element == EL_BOMB || element == EL_MINE)
1420 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1422 if (element == EL_MINE)
1423 laser.overloaded = TRUE;
1426 if (element == EL_KETTLE ||
1427 element == EL_CELL ||
1428 element == EL_KEY ||
1429 element == EL_LIGHTBALL ||
1430 element == EL_PACMAN ||
1433 if (!IS_PACMAN(element))
1436 if (element == EL_PACMAN)
1439 if (element == EL_KETTLE || element == EL_CELL)
1441 if (game_mm.kettles_still_needed > 0)
1442 game_mm.kettles_still_needed--;
1446 if (game_mm.kettles_still_needed == 0)
1448 int exit_element = (element == EL_KETTLE ? EL_EXIT_OPEN : EL_RECEIVER);
1450 static int xy[4][2] =
1458 PlayLevelSound_MM(ELX, ELY, exit_element, MM_ACTION_OPENING);
1460 for (y = 0; y < lev_fieldy; y++)
1462 for (x = 0; x < lev_fieldx; x++)
1464 /* initiate opening animation of exit door */
1465 if (Feld[x][y] == EL_EXIT_CLOSED)
1466 Feld[x][y] = EL_EXIT_OPENING;
1468 /* remove field that blocks receiver */
1469 if (IS_RECEIVER(Feld[x][y]))
1471 int phase = Feld[x][y] - EL_RECEIVER_START;
1472 int blocking_x, blocking_y;
1474 blocking_x = x + xy[phase][0];
1475 blocking_y = y + xy[phase][1];
1477 if (IN_LEV_FIELD(blocking_x, blocking_y))
1479 Feld[blocking_x][blocking_y] = EL_EMPTY;
1481 DrawField_MM(blocking_x, blocking_y);
1487 DrawLaser(0, DL_LASER_ENABLED);
1490 else if (element == EL_KEY)
1494 else if (element == EL_LIGHTBALL)
1498 else if (IS_PACMAN(element))
1500 DeletePacMan(ELX, ELY);
1507 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1509 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1511 DrawLaser(0, DL_LASER_ENABLED);
1513 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1515 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1516 game_mm.lights_still_needed--;
1520 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1521 game_mm.lights_still_needed++;
1524 DrawField_MM(ELX, ELY);
1525 DrawLaser(0, DL_LASER_ENABLED);
1530 laser.stops_inside_element = TRUE;
1536 printf("HitElement (4): element == %d\n", element);
1539 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1540 laser.num_beamers < MAX_NUM_BEAMERS &&
1541 laser.beamer[BEAMER_NR(element)][1].num)
1543 int beamer_angle = get_element_angle(element);
1544 int beamer_nr = BEAMER_NR(element);
1548 printf("HitElement (BEAMER): element == %d\n", element);
1551 laser.num_damages--;
1553 if (IS_FIBRE_OPTIC(element) ||
1554 laser.current_angle == get_opposite_angle(beamer_angle))
1558 LX = ELX * TILEX + 14;
1559 LY = ELY * TILEY + 14;
1561 AddLaserEdge(LX, LY);
1562 AddDamagedField(ELX, ELY);
1564 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1567 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1569 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1570 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1571 ELX = laser.beamer[beamer_nr][pos].x;
1572 ELY = laser.beamer[beamer_nr][pos].y;
1573 LX = ELX * TILEX + 14;
1574 LY = ELY * TILEY + 14;
1576 if (IS_BEAMER(element))
1578 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1579 XS = 2 * Step[laser.current_angle].x;
1580 YS = 2 * Step[laser.current_angle].y;
1583 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1585 AddLaserEdge(LX, LY);
1586 AddDamagedField(ELX, ELY);
1588 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1591 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1593 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1598 LX += step_size * XS;
1599 LY += step_size * YS;
1601 laser.num_beamers++;
1610 boolean HitOnlyAnEdge(int element, int hit_mask)
1612 /* check if the laser hit only the edge of an element and, if so, go on */
1615 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1618 if ((hit_mask == HIT_MASK_TOPLEFT ||
1619 hit_mask == HIT_MASK_TOPRIGHT ||
1620 hit_mask == HIT_MASK_BOTTOMLEFT ||
1621 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1622 laser.current_angle % 4) /* angle is not 90° */
1626 if (hit_mask == HIT_MASK_TOPLEFT)
1631 else if (hit_mask == HIT_MASK_TOPRIGHT)
1636 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1641 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1647 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1653 printf("[HitOnlyAnEdge() == TRUE]\n");
1660 printf("[HitOnlyAnEdge() == FALSE]\n");
1666 boolean HitPolarizer(int element, int hit_mask)
1668 if (HitOnlyAnEdge(element, hit_mask))
1671 if (IS_DF_GRID(element))
1673 int grid_angle = get_element_angle(element);
1676 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1677 grid_angle, laser.current_angle);
1680 AddLaserEdge(LX, LY);
1681 AddDamagedField(ELX, ELY);
1684 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1686 if (laser.current_angle == grid_angle ||
1687 laser.current_angle == get_opposite_angle(grid_angle))
1689 /* skip the whole element before continuing the scan */
1695 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1697 if (LX/TILEX > ELX || LY/TILEY > ELY)
1699 /* skipping scan positions to the right and down skips one scan
1700 position too much, because this is only the top left scan position
1701 of totally four scan positions (plus one to the right, one to the
1702 bottom and one to the bottom right) */
1708 AddLaserEdge(LX, LY);
1714 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1716 LX / TILEX, LY / TILEY,
1717 LX % TILEX, LY % TILEY);
1722 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1724 return HitReflectingWalls(element, hit_mask);
1728 return HitAbsorbingWalls(element, hit_mask);
1731 else if (IS_GRID_STEEL(element))
1733 return HitReflectingWalls(element, hit_mask);
1735 else /* IS_GRID_WOOD */
1737 return HitAbsorbingWalls(element, hit_mask);
1743 boolean HitBlock(int element, int hit_mask)
1745 boolean check = FALSE;
1747 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1748 game_mm.num_keys == 0)
1751 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1754 int ex = ELX * TILEX + 14;
1755 int ey = ELY * TILEY + 14;
1759 for (i = 1; i < 32; i++)
1764 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1769 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1770 return HitAbsorbingWalls(element, hit_mask);
1774 AddLaserEdge(LX - XS, LY - YS);
1775 AddDamagedField(ELX, ELY);
1778 Box[ELX][ELY] = laser.num_edges;
1780 return HitReflectingWalls(element, hit_mask);
1783 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1785 int xs = XS / 2, ys = YS / 2;
1786 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1787 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1789 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1790 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1792 laser.overloaded = (element == EL_GATE_STONE);
1797 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1798 (hit_mask == HIT_MASK_TOP ||
1799 hit_mask == HIT_MASK_LEFT ||
1800 hit_mask == HIT_MASK_RIGHT ||
1801 hit_mask == HIT_MASK_BOTTOM))
1802 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1803 hit_mask == HIT_MASK_BOTTOM),
1804 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1805 hit_mask == HIT_MASK_RIGHT));
1806 AddLaserEdge(LX, LY);
1812 if (element == EL_GATE_STONE && Box[ELX][ELY])
1814 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1826 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1828 int xs = XS / 2, ys = YS / 2;
1829 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1830 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1832 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1833 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1835 laser.overloaded = (element == EL_BLOCK_STONE);
1840 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1841 (hit_mask == HIT_MASK_TOP ||
1842 hit_mask == HIT_MASK_LEFT ||
1843 hit_mask == HIT_MASK_RIGHT ||
1844 hit_mask == HIT_MASK_BOTTOM))
1845 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1846 hit_mask == HIT_MASK_BOTTOM),
1847 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1848 hit_mask == HIT_MASK_RIGHT));
1849 AddDamagedField(ELX, ELY);
1851 LX = ELX * TILEX + 14;
1852 LY = ELY * TILEY + 14;
1854 AddLaserEdge(LX, LY);
1856 laser.stops_inside_element = TRUE;
1864 boolean HitLaserSource(int element, int hit_mask)
1866 if (HitOnlyAnEdge(element, hit_mask))
1869 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1871 laser.overloaded = TRUE;
1876 boolean HitLaserDestination(int element, int hit_mask)
1878 if (HitOnlyAnEdge(element, hit_mask))
1881 if (element != EL_EXIT_OPEN &&
1882 !(IS_RECEIVER(element) &&
1883 game_mm.kettles_still_needed == 0 &&
1884 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1886 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1891 if (IS_RECEIVER(element) ||
1892 (IS_22_5_ANGLE(laser.current_angle) &&
1893 (ELX != (LX + 6 * XS) / TILEX ||
1894 ELY != (LY + 6 * YS) / TILEY ||
1903 LX = ELX * TILEX + 14;
1904 LY = ELY * TILEY + 14;
1906 laser.stops_inside_element = TRUE;
1909 AddLaserEdge(LX, LY);
1910 AddDamagedField(ELX, ELY);
1912 if (game_mm.lights_still_needed == 0)
1913 game_mm.level_solved = TRUE;
1918 boolean HitReflectingWalls(int element, int hit_mask)
1920 /* check if laser hits side of a wall with an angle that is not 90° */
1921 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1922 hit_mask == HIT_MASK_LEFT ||
1923 hit_mask == HIT_MASK_RIGHT ||
1924 hit_mask == HIT_MASK_BOTTOM))
1926 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
1931 if (!IS_DF_GRID(element))
1932 AddLaserEdge(LX, LY);
1934 /* check if laser hits wall with an angle of 45° */
1935 if (!IS_22_5_ANGLE(laser.current_angle))
1937 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1940 laser.current_angle = get_mirrored_angle(laser.current_angle,
1943 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1946 laser.current_angle = get_mirrored_angle(laser.current_angle,
1950 AddLaserEdge(LX, LY);
1952 XS = 2 * Step[laser.current_angle].x;
1953 YS = 2 * Step[laser.current_angle].y;
1957 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1959 laser.current_angle = get_mirrored_angle(laser.current_angle,
1964 if (!IS_DF_GRID(element))
1965 AddLaserEdge(LX, LY);
1970 if (!IS_DF_GRID(element))
1971 AddLaserEdge(LX, LY + YS / 2);
1974 if (!IS_DF_GRID(element))
1975 AddLaserEdge(LX, LY);
1978 YS = 2 * Step[laser.current_angle].y;
1982 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1984 laser.current_angle = get_mirrored_angle(laser.current_angle,
1989 if (!IS_DF_GRID(element))
1990 AddLaserEdge(LX, LY);
1995 if (!IS_DF_GRID(element))
1996 AddLaserEdge(LX + XS / 2, LY);
1999 if (!IS_DF_GRID(element))
2000 AddLaserEdge(LX, LY);
2003 XS = 2 * Step[laser.current_angle].x;
2009 /* reflection at the edge of reflecting DF style wall */
2010 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2012 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2013 hit_mask == HIT_MASK_TOPRIGHT) ||
2014 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2015 hit_mask == HIT_MASK_TOPLEFT) ||
2016 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2017 hit_mask == HIT_MASK_BOTTOMLEFT) ||
2018 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2019 hit_mask == HIT_MASK_BOTTOMRIGHT))
2022 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
2023 ANG_MIRROR_135 : ANG_MIRROR_45);
2025 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2027 AddDamagedField(ELX, ELY);
2028 AddLaserEdge(LX, LY);
2030 laser.current_angle = get_mirrored_angle(laser.current_angle,
2038 AddLaserEdge(LX, LY);
2044 /* reflection inside an edge of reflecting DF style wall */
2045 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
2047 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
2048 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
2049 ((laser.current_angle == 5 || laser.current_angle == 7) &&
2050 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
2051 ((laser.current_angle == 9 || laser.current_angle == 11) &&
2052 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
2053 ((laser.current_angle == 13 || laser.current_angle == 15) &&
2054 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
2057 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
2058 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
2059 ANG_MIRROR_135 : ANG_MIRROR_45);
2061 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2064 AddDamagedField(ELX, ELY);
2067 AddLaserEdge(LX - XS, LY - YS);
2068 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
2069 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
2071 laser.current_angle = get_mirrored_angle(laser.current_angle,
2079 AddLaserEdge(LX, LY);
2085 /* check if laser hits DF style wall with an angle of 90° */
2086 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
2088 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
2089 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
2090 (IS_VERT_ANGLE(laser.current_angle) &&
2091 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
2093 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
2095 /* laser at last step touched nothing or the same side of the wall */
2096 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
2098 AddDamagedField(ELX, ELY);
2105 last_hit_mask = hit_mask;
2112 if (!HitOnlyAnEdge(element, hit_mask))
2114 laser.overloaded = TRUE;
2122 boolean HitAbsorbingWalls(int element, int hit_mask)
2124 if (HitOnlyAnEdge(element, hit_mask))
2128 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
2130 AddLaserEdge(LX - XS, LY - YS);
2137 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
2139 AddLaserEdge(LX - XS, LY - YS);
2145 if (IS_WALL_WOOD(element) ||
2146 IS_DF_WALL_WOOD(element) ||
2147 IS_GRID_WOOD(element) ||
2148 IS_GRID_WOOD_FIXED(element) ||
2149 IS_GRID_WOOD_AUTO(element) ||
2150 element == EL_FUSE_ON ||
2151 element == EL_BLOCK_WOOD ||
2152 element == EL_GATE_WOOD)
2154 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_HITTING);
2159 if (IS_WALL_ICE(element))
2163 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
2164 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
2166 /* check if laser hits wall with an angle of 90° */
2167 if (IS_90_ANGLE(laser.current_angle))
2168 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2170 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
2174 for (i = 0; i < 4; i++)
2176 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
2177 mask = 15 - (8 >> i);
2178 else if (ABS(XS) == 4 &&
2180 (XS > 0) == (i % 2) &&
2181 (YS < 0) == (i / 2))
2182 mask = 3 + (i / 2) * 9;
2183 else if (ABS(YS) == 4 &&
2185 (XS < 0) == (i % 2) &&
2186 (YS > 0) == (i / 2))
2187 mask = 5 + (i % 2) * 5;
2191 laser.wall_mask = mask;
2193 else if (IS_WALL_AMOEBA(element))
2195 int elx = (LX - 2 * XS) / TILEX;
2196 int ely = (LY - 2 * YS) / TILEY;
2197 int element2 = Feld[elx][ely];
2200 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
2202 laser.dest_element = EL_EMPTY;
2210 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
2211 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
2213 if (IS_90_ANGLE(laser.current_angle))
2214 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
2216 laser.dest_element = element2 | EL_WALL_AMOEBA;
2218 laser.wall_mask = mask;
2224 void OpenExit(int x, int y)
2228 if (!MovDelay[x][y]) /* next animation frame */
2229 MovDelay[x][y] = 4 * delay;
2231 if (MovDelay[x][y]) /* wait some time before next frame */
2236 phase = MovDelay[x][y] / delay;
2238 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2239 DrawGraphicAnimation_MM(x, y, IMG_MM_EXIT_OPENING, 3 - phase);
2241 if (!MovDelay[x][y])
2243 Feld[x][y] = EL_EXIT_OPEN;
2249 void OpenSurpriseBall(int x, int y)
2253 if (!MovDelay[x][y]) /* next animation frame */
2254 MovDelay[x][y] = 50 * delay;
2256 if (MovDelay[x][y]) /* wait some time before next frame */
2260 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2263 int graphic = el2gfx(Store[x][y]);
2265 int dx = RND(26), dy = RND(26);
2267 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
2269 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
2270 SX + x * TILEX + dx, SY + y * TILEY + dy);
2272 MarkTileDirty(x, y);
2275 if (!MovDelay[x][y])
2277 Feld[x][y] = Store[x][y];
2286 void MeltIce(int x, int y)
2291 if (!MovDelay[x][y]) /* next animation frame */
2292 MovDelay[x][y] = frames * delay;
2294 if (MovDelay[x][y]) /* wait some time before next frame */
2297 int wall_mask = Store2[x][y];
2298 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2301 phase = frames - MovDelay[x][y] / delay - 1;
2303 if (!MovDelay[x][y])
2307 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2308 Store[x][y] = Store2[x][y] = 0;
2310 DrawWalls_MM(x, y, Feld[x][y]);
2312 if (Feld[x][y] == EL_WALL_ICE)
2313 Feld[x][y] = EL_EMPTY;
2315 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
2316 if (laser.damage[i].is_mirror)
2320 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2322 DrawLaser(0, DL_LASER_DISABLED);
2326 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2328 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2330 laser.redraw = TRUE;
2335 void GrowAmoeba(int x, int y)
2340 if (!MovDelay[x][y]) /* next animation frame */
2341 MovDelay[x][y] = frames * delay;
2343 if (MovDelay[x][y]) /* wait some time before next frame */
2346 int wall_mask = Store2[x][y];
2347 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2350 phase = MovDelay[x][y] / delay;
2352 if (!MovDelay[x][y])
2354 Feld[x][y] = real_element;
2355 Store[x][y] = Store2[x][y] = 0;
2357 DrawWalls_MM(x, y, Feld[x][y]);
2358 DrawLaser(0, DL_LASER_ENABLED);
2360 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2362 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2367 static void Explode_MM(int x, int y, int phase, int mode)
2369 int num_phase = 9, delay = 2;
2370 int last_phase = num_phase * delay;
2371 int half_phase = (num_phase / 2) * delay;
2373 laser.redraw = TRUE;
2375 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2377 int center_element = Feld[x][y];
2379 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2381 /* put moving element to center field (and let it explode there) */
2382 center_element = MovingOrBlocked2Element_MM(x, y);
2383 RemoveMovingField_MM(x, y);
2385 Feld[x][y] = center_element;
2388 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2389 Store[x][y] = center_element;
2391 Store[x][y] = EL_EMPTY;
2393 Store2[x][y] = mode;
2394 Feld[x][y] = EL_EXPLODING_OPAQUE;
2395 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2401 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2403 if (phase == half_phase)
2405 Feld[x][y] = EL_EXPLODING_TRANSP;
2407 if (x == ELX && y == ELY)
2411 if (phase == last_phase)
2413 if (Store[x][y] == EL_BOMB)
2415 DrawLaser(0, DL_LASER_DISABLED);
2418 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2419 Store[x][y] = EL_EMPTY;
2421 game_mm.game_over = TRUE;
2422 game_mm.game_over_cause = GAME_OVER_BOMB;
2424 laser.overloaded = FALSE;
2426 else if (IS_MCDUFFIN(Store[x][y]))
2428 Store[x][y] = EL_EMPTY;
2431 Feld[x][y] = Store[x][y];
2432 Store[x][y] = Store2[x][y] = 0;
2433 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2435 InitField(x, y, FALSE);
2438 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2440 int graphic = IMG_MM_DEFAULT_EXPLODING;
2441 int graphic_phase = (phase / delay - 1);
2445 if (Store2[x][y] == EX_KETTLE)
2447 if (graphic_phase < 3)
2449 graphic = IMG_MM_KETTLE_EXPLODING;
2451 else if (graphic_phase < 5)
2457 graphic = IMG_EMPTY;
2461 else if (Store2[x][y] == EX_SHORT)
2463 if (graphic_phase < 4)
2469 graphic = IMG_EMPTY;
2474 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2476 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2477 FX + x * TILEX, FY + y * TILEY);
2479 MarkTileDirty(x, y);
2483 static void Bang_MM(int x, int y)
2485 int element = Feld[x][y];
2486 int mode = EX_NORMAL;
2489 DrawLaser(0, DL_LASER_ENABLED);
2508 if (IS_PACMAN(element))
2509 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2510 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2511 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2512 else if (element == EL_KEY)
2513 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2515 PlayLevelSound_MM(x, y, element, MM_ACTION_EXPLODING);
2517 Explode_MM(x, y, EX_PHASE_START, mode);
2520 void TurnRound(int x, int y)
2532 { 0, 0 }, { 0, 0 }, { 0, 0 },
2537 int left, right, back;
2541 { MV_DOWN, MV_UP, MV_RIGHT },
2542 { MV_UP, MV_DOWN, MV_LEFT },
2544 { MV_LEFT, MV_RIGHT, MV_DOWN },
2545 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2546 { MV_RIGHT, MV_LEFT, MV_UP }
2549 int element = Feld[x][y];
2550 int old_move_dir = MovDir[x][y];
2551 int right_dir = turn[old_move_dir].right;
2552 int back_dir = turn[old_move_dir].back;
2553 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2554 int right_x = x + right_dx, right_y = y + right_dy;
2556 if (element == EL_PACMAN)
2558 boolean can_turn_right = FALSE;
2560 if (IN_LEV_FIELD(right_x, right_y) &&
2561 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2562 can_turn_right = TRUE;
2565 MovDir[x][y] = right_dir;
2567 MovDir[x][y] = back_dir;
2573 static void StartMoving_MM(int x, int y)
2575 int element = Feld[x][y];
2580 if (CAN_MOVE(element))
2584 if (MovDelay[x][y]) /* wait some time before next movement */
2592 /* now make next step */
2594 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2596 if (element == EL_PACMAN &&
2597 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2598 !ObjHit(newx, newy, HIT_POS_CENTER))
2600 Store[newx][newy] = Feld[newx][newy];
2601 Feld[newx][newy] = EL_EMPTY;
2603 DrawField_MM(newx, newy);
2605 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2606 ObjHit(newx, newy, HIT_POS_CENTER))
2608 /* object was running against a wall */
2615 InitMovingField_MM(x, y, MovDir[x][y]);
2619 ContinueMoving_MM(x, y);
2622 static void ContinueMoving_MM(int x, int y)
2624 int element = Feld[x][y];
2625 int direction = MovDir[x][y];
2626 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2627 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2628 int horiz_move = (dx!=0);
2629 int newx = x + dx, newy = y + dy;
2630 int step = (horiz_move ? dx : dy) * TILEX / 8;
2632 MovPos[x][y] += step;
2634 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2636 Feld[x][y] = EL_EMPTY;
2637 Feld[newx][newy] = element;
2639 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2640 MovDelay[newx][newy] = 0;
2642 if (!CAN_MOVE(element))
2643 MovDir[newx][newy] = 0;
2646 DrawField_MM(newx, newy);
2648 Stop[newx][newy] = TRUE;
2650 if (element == EL_PACMAN)
2652 if (Store[newx][newy] == EL_BOMB)
2653 Bang_MM(newx, newy);
2655 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2656 (LX + 2 * XS) / TILEX == newx &&
2657 (LY + 2 * YS) / TILEY == newy)
2664 else /* still moving on */
2669 laser.redraw = TRUE;
2672 void ClickElement(int x, int y, int button)
2674 static unsigned int click_delay = 0;
2675 static int click_delay_value = CLICK_DELAY;
2676 static boolean new_button = TRUE;
2679 /* do not rotate objects hit by the laser after the game was solved */
2680 if (game_mm.level_solved && Hit[x][y])
2683 if (button == MB_RELEASED)
2686 click_delay_value = CLICK_DELAY;
2688 /* release eventually hold auto-rotating mirror */
2689 RotateMirror(x, y, MB_RELEASED);
2694 if (!FrameReached(&click_delay, click_delay_value) && !new_button)
2697 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2700 if (!IN_LEV_FIELD(x, y))
2703 if (Feld[x][y] == EL_EMPTY)
2706 element = Feld[x][y];
2708 if (IS_MIRROR(element) ||
2709 IS_BEAMER(element) ||
2710 IS_POLAR(element) ||
2711 IS_POLAR_CROSS(element) ||
2712 IS_DF_MIRROR(element) ||
2713 IS_DF_MIRROR_AUTO(element))
2715 RotateMirror(x, y, button);
2717 else if (IS_MCDUFFIN(element))
2719 if (!laser.fuse_off)
2721 DrawLaser(0, DL_LASER_DISABLED);
2728 element = get_rotated_element(element, BUTTON_ROTATION(button));
2729 laser.start_angle = get_element_angle(element);
2733 Feld[x][y] = element;
2740 if (!laser.fuse_off)
2743 else if (element == EL_FUSE_ON && laser.fuse_off)
2745 if (x != laser.fuse_x || y != laser.fuse_y)
2748 laser.fuse_off = FALSE;
2749 laser.fuse_x = laser.fuse_y = -1;
2751 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2754 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2756 laser.fuse_off = TRUE;
2759 laser.overloaded = FALSE;
2761 DrawLaser(0, DL_LASER_DISABLED);
2762 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2764 else if (element == EL_LIGHTBALL)
2768 DrawLaser(0, DL_LASER_ENABLED);
2771 click_delay_value = (new_button ? CLICK_DELAY_FIRST : CLICK_DELAY);
2775 void RotateMirror(int x, int y, int button)
2777 static int hold_x = -1, hold_y = -1;
2779 if (button == MB_RELEASED)
2781 /* release eventually hold auto-rotating mirror */
2788 if (IS_MIRROR(Feld[x][y]) ||
2789 IS_POLAR_CROSS(Feld[x][y]) ||
2790 IS_POLAR(Feld[x][y]) ||
2791 IS_BEAMER(Feld[x][y]) ||
2792 IS_DF_MIRROR(Feld[x][y]) ||
2793 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2794 IS_GRID_WOOD_AUTO(Feld[x][y]))
2796 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2798 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2800 if (button == MB_LEFTBUTTON)
2802 /* left mouse button only for manual adjustment, no auto-rotating;
2803 freeze mirror for until mouse button released */
2807 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2809 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2813 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2815 int edge = Hit[x][y];
2821 DrawLaser(edge - 1, DL_LASER_DISABLED);
2825 else if (ObjHit(x, y, HIT_POS_CENTER))
2827 int edge = Hit[x][y];
2831 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2835 DrawLaser(edge - 1, DL_LASER_DISABLED);
2842 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2847 if ((IS_BEAMER(Feld[x][y]) ||
2848 IS_POLAR(Feld[x][y]) ||
2849 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2853 if (IS_BEAMER(Feld[x][y]))
2856 printf("TEST (%d, %d) [%d] [%d]\n",
2858 laser.beamer_edge, laser.beamer[1].num);
2868 DrawLaser(0, DL_LASER_ENABLED);
2872 void AutoRotateMirrors()
2874 static unsigned int rotate_delay = 0;
2877 if (!FrameReached(&rotate_delay, AUTO_ROTATE_DELAY))
2880 for (x = 0; x < lev_fieldx; x++)
2882 for (y = 0; y < lev_fieldy; y++)
2884 int element = Feld[x][y];
2886 /* do not rotate objects hit by the laser after the game was solved */
2887 if (game_mm.level_solved && Hit[x][y])
2890 if (IS_DF_MIRROR_AUTO(element) ||
2891 IS_GRID_WOOD_AUTO(element) ||
2892 IS_GRID_STEEL_AUTO(element) ||
2893 element == EL_REFRACTOR)
2894 RotateMirror(x, y, MB_RIGHTBUTTON);
2899 boolean ObjHit(int obx, int oby, int bits)
2906 if (bits & HIT_POS_CENTER)
2908 if (CheckLaserPixel(SX + obx + 15,
2913 if (bits & HIT_POS_EDGE)
2915 for (i = 0; i < 4; i++)
2916 if (CheckLaserPixel(SX + obx + 31 * (i % 2),
2917 SY + oby + 31 * (i / 2)))
2921 if (bits & HIT_POS_BETWEEN)
2923 for (i = 0; i < 4; i++)
2924 if (CheckLaserPixel(SX + 4 + obx + 22 * (i % 2),
2925 SY + 4 + oby + 22 * (i / 2)))
2932 void DeletePacMan(int px, int py)
2938 if (game_mm.num_pacman <= 1)
2940 game_mm.num_pacman = 0;
2944 for (i = 0; i < game_mm.num_pacman; i++)
2945 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2948 game_mm.num_pacman--;
2950 for (j = i; j < game_mm.num_pacman; j++)
2952 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
2953 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
2954 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2958 void ColorCycling(void)
2960 static int CC, Cc = 0;
2962 static int color, old = 0xF00, new = 0x010, mult = 1;
2963 static unsigned short red, green, blue;
2965 if (color_status == STATIC_COLORS)
2970 if (CC < Cc || CC > Cc + 2)
2974 color = old + new * mult;
2980 if (ABS(mult) == 16)
2990 red = 0x0e00 * ((color & 0xF00) >> 8);
2991 green = 0x0e00 * ((color & 0x0F0) >> 4);
2992 blue = 0x0e00 * ((color & 0x00F));
2993 SetRGB(pen_magicolor[0], red, green, blue);
2995 red = 0x1111 * ((color & 0xF00) >> 8);
2996 green = 0x1111 * ((color & 0x0F0) >> 4);
2997 blue = 0x1111 * ((color & 0x00F));
2998 SetRGB(pen_magicolor[1], red, green, blue);
3002 static void GameActions_MM_Ext(struct MouseActionInfo action, boolean warp_mode)
3004 static unsigned int pacman_delay = 0;
3005 static unsigned int energy_delay = 0;
3006 static unsigned int overload_delay = 0;
3012 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3015 for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
3017 element = Feld[x][y];
3019 if (!IS_MOVING(x, y) && CAN_MOVE(element))
3020 StartMoving_MM(x, y);
3021 else if (IS_MOVING(x, y))
3022 ContinueMoving_MM(x, y);
3023 else if (IS_EXPLODING(element))
3024 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
3025 else if (element == EL_EXIT_OPENING)
3027 else if (element == EL_GRAY_BALL_OPENING)
3028 OpenSurpriseBall(x, y);
3029 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
3031 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
3035 AutoRotateMirrors();
3038 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
3040 /* redraw after Explode_MM() ... */
3042 DrawLaser(0, DL_LASER_ENABLED);
3043 laser.redraw = FALSE;
3048 if (game_mm.num_pacman && FrameReached(&pacman_delay, PACMAN_MOVE_DELAY))
3052 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
3054 DrawLaser(0, DL_LASER_DISABLED);
3059 if (FrameReached(&energy_delay, ENERGY_DELAY))
3061 game_mm.energy_left--;
3062 if (game_mm.energy_left >= 0)
3065 BlitBitmap(pix[PIX_DOOR], drawto,
3066 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3067 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3068 DX_ENERGY, DY_ENERGY);
3070 redraw_mask |= REDRAW_DOOR_1;
3072 else if (setup.time_limit)
3076 for (i = 15; i >= 0; i--)
3079 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
3081 pen_ray = GetPixelFromRGB(window,
3082 native_mm_level.laser_red * 0x11 * i,
3083 native_mm_level.laser_green * 0x11 * i,
3084 native_mm_level.laser_blue * 0x11 * i);
3086 DrawLaser(0, DL_LASER_ENABLED);
3091 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3094 DrawLaser(0, DL_LASER_DISABLED);
3095 game_mm.game_over = TRUE;
3096 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
3099 if (Request("Out of magic energy ! Play it again ?",
3100 REQ_ASK | REQ_STAY_CLOSED))
3106 game_status = MAINMENU;
3115 element = laser.dest_element;
3118 if (element != Feld[ELX][ELY])
3120 printf("element == %d, Feld[ELX][ELY] == %d\n",
3121 element, Feld[ELX][ELY]);
3125 if (!laser.overloaded && laser.overload_value == 0 &&
3126 element != EL_BOMB &&
3127 element != EL_MINE &&
3128 element != EL_BALL_GRAY &&
3129 element != EL_BLOCK_STONE &&
3130 element != EL_BLOCK_WOOD &&
3131 element != EL_FUSE_ON &&
3132 element != EL_FUEL_FULL &&
3133 !IS_WALL_ICE(element) &&
3134 !IS_WALL_AMOEBA(element))
3137 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
3138 (!laser.overloaded && laser.overload_value > 0)) &&
3139 FrameReached(&overload_delay, HEALTH_DELAY(laser.overloaded)))
3141 if (laser.overloaded)
3142 laser.overload_value++;
3144 laser.overload_value--;
3146 if (game_mm.cheat_no_overload)
3148 laser.overloaded = FALSE;
3149 laser.overload_value = 0;
3152 game_mm.laser_overload_value = laser.overload_value;
3154 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
3156 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
3157 int color_down = 0xFF - color_up;
3160 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
3161 (15 - (laser.overload_value / 6)) * color_scale);
3164 GetPixelFromRGB(window,
3165 (native_mm_level.laser_red ? 0xFF : color_up),
3166 (native_mm_level.laser_green ? color_down : 0x00),
3167 (native_mm_level.laser_blue ? color_down : 0x00));
3169 DrawLaser(0, DL_LASER_ENABLED);
3175 if (!laser.overloaded)
3176 StopSound_MM(SND_MM_GAME_HEALTH_CHARGING);
3177 else if (setup.sound_loops)
3178 PlaySoundLoop_MM(SND_MM_GAME_HEALTH_CHARGING);
3180 PlaySound_MM(SND_MM_GAME_HEALTH_CHARGING);
3182 if (laser.overloaded)
3185 BlitBitmap(pix[PIX_DOOR], drawto,
3186 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
3187 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
3188 - laser.overload_value,
3189 OVERLOAD_XSIZE, laser.overload_value,
3190 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
3191 - laser.overload_value);
3193 redraw_mask |= REDRAW_DOOR_1;
3198 BlitBitmap(pix[PIX_DOOR], drawto,
3199 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
3200 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
3201 DX_OVERLOAD, DY_OVERLOAD);
3203 redraw_mask |= REDRAW_DOOR_1;
3206 if (laser.overload_value == MAX_LASER_OVERLOAD)
3210 for (i = 15; i >= 0; i--)
3213 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
3216 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
3218 DrawLaser(0, DL_LASER_ENABLED);
3223 DrawLaser(0, DL_LASER_DISABLED);
3225 game_mm.game_over = TRUE;
3226 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
3229 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
3230 REQ_ASK | REQ_STAY_CLOSED))
3236 game_status = MAINMENU;
3250 if (element == EL_BOMB && CT > 75)
3252 if (game_mm.cheat_no_explosion)
3256 laser.num_damages--;
3257 DrawLaser(0, DL_LASER_DISABLED);
3258 laser.num_edges = 0;
3263 laser.dest_element = EL_EXPLODING_OPAQUE;
3267 laser.num_damages--;
3268 DrawLaser(0, DL_LASER_DISABLED);
3270 laser.num_edges = 0;
3271 Bang_MM(laser.start_edge.x, laser.start_edge.y);
3273 if (Request("Bomb killed Mc Duffin ! Play it again ?",
3274 REQ_ASK | REQ_STAY_CLOSED))
3280 game_status = MAINMENU;
3288 if (element == EL_FUSE_ON && CT > 25)
3290 laser.fuse_off = TRUE;
3294 DrawLaser(0, DL_LASER_DISABLED);
3295 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
3298 if (element == EL_BALL_GRAY && CT > 75)
3300 static int new_elements[] =
3303 EL_MIRROR_FIXED_START,
3305 EL_POLAR_CROSS_START,
3311 int num_new_elements = sizeof(new_elements) / sizeof(int);
3312 int new_element = new_elements[RND(num_new_elements)];
3314 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
3315 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
3317 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3328 element = EL_MIRROR_START + RND(16);
3334 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3341 element = (rnd == 0 ? EL_FUSE_ON :
3342 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3343 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3344 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3345 EL_MIRROR_FIXED_START + rnd - 25);
3350 graphic = el2gfx(element);
3352 for (i = 0; i < 50; i++)
3358 BlitBitmap(pix[PIX_BACK], drawto,
3359 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3360 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3361 SX + ELX * TILEX + x,
3362 SY + ELY * TILEY + y);
3364 MarkTileDirty(ELX, ELY);
3367 DrawLaser(0, DL_LASER_ENABLED);
3372 Feld[ELX][ELY] = element;
3373 DrawField_MM(ELX, ELY);
3376 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3379 /* above stuff: GRAY BALL -> PRISM !!! */
3381 LX = ELX * TILEX + 14;
3382 LY = ELY * TILEY + 14;
3383 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3390 laser.num_edges -= 2;
3391 laser.num_damages--;
3395 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3396 if (laser.damage[i].is_mirror)
3400 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3402 DrawLaser(0, DL_LASER_DISABLED);
3404 DrawLaser(0, DL_LASER_DISABLED);
3410 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3417 if (IS_WALL_ICE(element) && CT > 50)
3419 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_SHRINKING);
3422 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3423 Store[ELX][ELY] = EL_WALL_ICE;
3424 Store2[ELX][ELY] = laser.wall_mask;
3426 laser.dest_element = Feld[ELX][ELY];
3431 for (i = 0; i < 5; i++)
3437 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3441 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3446 if (Feld[ELX][ELY] == EL_WALL_ICE)
3447 Feld[ELX][ELY] = EL_EMPTY;
3451 LX = laser.edge[laser.num_edges].x - (SX + 2);
3452 LY = laser.edge[laser.num_edges].y - (SY + 2);
3455 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i >= 0; i--)
3456 if (laser.damage[i].is_mirror)
3460 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3462 DrawLaser(0, DL_LASER_DISABLED);
3469 if (IS_WALL_AMOEBA(element) && CT > 60)
3471 int k1, k2, k3, dx, dy, de, dm;
3472 int element2 = Feld[ELX][ELY];
3474 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3477 for (i = laser.num_damages - 1; i >= 0; i--)
3478 if (laser.damage[i].is_mirror)
3481 r = laser.num_edges;
3482 d = laser.num_damages;
3489 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3492 DrawLaser(0, DL_LASER_ENABLED);
3495 x = laser.damage[k1].x;
3496 y = laser.damage[k1].y;
3501 for (i = 0; i < 4; i++)
3503 if (laser.wall_mask & (1 << i))
3505 if (CheckLaserPixel(SX + ELX * TILEX + 14 + (i % 2) * 2,
3506 SY + ELY * TILEY + 31 * (i / 2)))
3509 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3510 SY + ELY * TILEY + 14 + (i / 2) * 2))
3517 for (i = 0; i < 4; i++)
3519 if (laser.wall_mask & (1 << i))
3521 if (CheckLaserPixel(SX + ELX * TILEX + 31 * (i % 2),
3522 SY + ELY * TILEY + 31 * (i / 2)))
3529 if (laser.num_beamers > 0 ||
3530 k1 < 1 || k2 < 4 || k3 < 4 ||
3531 CheckLaserPixel(SX + ELX * TILEX + 14,
3532 SY + ELY * TILEY + 14))
3534 laser.num_edges = r;
3535 laser.num_damages = d;
3537 DrawLaser(0, DL_LASER_DISABLED);
3540 Feld[ELX][ELY] = element | laser.wall_mask;
3544 de = Feld[ELX][ELY];
3545 dm = laser.wall_mask;
3549 int x = ELX, y = ELY;
3550 int wall_mask = laser.wall_mask;
3553 DrawLaser(0, DL_LASER_ENABLED);
3555 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3557 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3558 Store[x][y] = EL_WALL_AMOEBA;
3559 Store2[x][y] = wall_mask;
3565 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3567 DrawLaser(0, DL_LASER_ENABLED);
3569 PlayLevelSound_MM(dx, dy, element, MM_ACTION_GROWING);
3571 for (i = 4; i >= 0; i--)
3573 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3579 DrawLaser(0, DL_LASER_ENABLED);
3584 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3585 laser.stops_inside_element && CT > 75)
3590 if (ABS(XS) > ABS(YS))
3597 for (i = 0; i < 4; i++)
3604 x = ELX + Step[k * 4].x;
3605 y = ELY + Step[k * 4].y;
3607 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3610 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3618 laser.overloaded = (element == EL_BLOCK_STONE);
3623 PlayLevelSound_MM(ELX, ELY, element, MM_ACTION_PUSHING);
3626 Feld[x][y] = element;
3628 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3631 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3633 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3634 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3642 if (element == EL_FUEL_FULL && CT > 10)
3644 for (i = game_mm.energy_left; i <= MAX_LASER_ENERGY; i+=2)
3647 BlitBitmap(pix[PIX_DOOR], drawto,
3648 DOOR_GFX_PAGEX4 + XX_ENERGY,
3649 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3650 ENERGY_XSIZE, i, DX_ENERGY,
3651 DY_ENERGY + ENERGY_YSIZE - i);
3654 redraw_mask |= REDRAW_DOOR_1;
3660 game_mm.energy_left = MAX_LASER_ENERGY;
3661 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3662 DrawField_MM(ELX, ELY);
3664 DrawLaser(0, DL_LASER_ENABLED);
3672 void GameActions_MM(struct MouseActionInfo action, boolean warp_mode)
3674 ClickElement(action.lx, action.ly, action.button);
3676 GameActions_MM_Ext(action, warp_mode);
3682 int mx, my, ox, oy, nx, ny;
3686 if (++p >= game_mm.num_pacman)
3689 game_mm.pacman[p].dir--;
3691 for (l = 1; l < 5; l++)
3693 game_mm.pacman[p].dir++;
3695 if (game_mm.pacman[p].dir > 4)
3696 game_mm.pacman[p].dir = 1;
3698 if (game_mm.pacman[p].dir % 2)
3701 my = game_mm.pacman[p].dir - 2;
3706 mx = 3 - game_mm.pacman[p].dir;
3709 ox = game_mm.pacman[p].x;
3710 oy = game_mm.pacman[p].y;
3713 element = Feld[nx][ny];
3715 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3718 if (!IS_EATABLE4PACMAN(element))
3721 if (ObjHit(nx, ny, HIT_POS_CENTER))
3724 Feld[ox][oy] = EL_EMPTY;
3726 EL_PACMAN_RIGHT - 1 +
3727 (game_mm.pacman[p].dir - 1 +
3728 (game_mm.pacman[p].dir % 2) * 2);
3730 game_mm.pacman[p].x = nx;
3731 game_mm.pacman[p].y = ny;
3733 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3735 if (element != EL_EMPTY)
3737 int graphic = el2gfx(Feld[nx][ny]);
3742 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3745 ox = SX + ox * TILEX;
3746 oy = SY + oy * TILEY;
3748 for (i = 1; i < 33; i += 2)
3749 BlitBitmap(bitmap, window,
3750 src_x, src_y, TILEX, TILEY,
3751 ox + i * mx, oy + i * my);
3752 Ct = Ct + FrameCounter - CT;
3755 DrawField_MM(nx, ny);
3758 if (!laser.fuse_off)
3760 DrawLaser(0, DL_LASER_ENABLED);
3762 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3764 AddDamagedField(nx, ny);
3766 laser.damage[laser.num_damages - 1].edge = 0;
3770 if (element == EL_BOMB)
3771 DeletePacMan(nx, ny);
3773 if (IS_WALL_AMOEBA(element) &&
3774 (LX + 2 * XS) / TILEX == nx &&
3775 (LY + 2 * YS) / TILEY == ny)
3788 boolean raise_level = FALSE;
3791 if (local_player->MovPos)
3794 local_player->LevelSolved = FALSE;
3797 if (game_mm.energy_left)
3799 if (setup.sound_loops)
3800 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3801 SND_CTRL_PLAY_LOOP);
3803 while (game_mm.energy_left > 0)
3805 if (!setup.sound_loops)
3806 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3809 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3810 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3815 game_mm.energy_left--;
3816 if (game_mm.energy_left >= 0)
3819 BlitBitmap(pix[PIX_DOOR], drawto,
3820 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3821 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3822 DX_ENERGY, DY_ENERGY);
3824 redraw_mask |= REDRAW_DOOR_1;
3831 if (setup.sound_loops)
3832 StopSound(SND_SIRR);
3834 else if (native_mm_level.time == 0) /* level without time limit */
3836 if (setup.sound_loops)
3837 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT,
3838 SND_CTRL_PLAY_LOOP);
3840 while (TimePlayed < 999)
3842 if (!setup.sound_loops)
3843 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3844 if (TimePlayed < 999 && !(TimePlayed % 10))
3845 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3846 if (TimePlayed < 900 && !(TimePlayed % 10))
3852 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3859 if (setup.sound_loops)
3860 StopSound(SND_SIRR);
3867 CloseDoor(DOOR_CLOSE_1);
3869 Request("Level solved !", REQ_CONFIRM);
3871 if (level_nr == leveldir_current->handicap_level)
3873 leveldir_current->handicap_level++;
3874 SaveLevelSetup_SeriesInfo();
3877 if (level_editor_test_game)
3878 game_mm.score = -1; /* no highscore when playing from editor */
3879 else if (level_nr < leveldir_current->last_level)
3880 raise_level = TRUE; /* advance to next level */
3882 if ((hi_pos = NewHiScore_MM()) >= 0)
3884 game_status = HALLOFFAME;
3886 // DrawHallOfFame(hi_pos);
3893 game_status = MAINMENU;
3909 // LoadScore(level_nr);
3911 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3912 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3915 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
3917 if (game_mm.score > highscore[k].Score)
3919 /* player has made it to the hall of fame */
3921 if (k < MAX_SCORE_ENTRIES - 1)
3923 int m = MAX_SCORE_ENTRIES - 1;
3926 for (l = k; l < MAX_SCORE_ENTRIES; l++)
3927 if (!strcmp(setup.player_name, highscore[l].Name))
3929 if (m == k) /* player's new highscore overwrites his old one */
3933 for (l = m; l>k; l--)
3935 strcpy(highscore[l].Name, highscore[l - 1].Name);
3936 highscore[l].Score = highscore[l - 1].Score;
3943 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3944 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3945 highscore[k].Score = game_mm.score;
3952 else if (!strncmp(setup.player_name, highscore[k].Name,
3953 MAX_PLAYER_NAME_LEN))
3954 break; /* player already there with a higher score */
3959 // if (position >= 0)
3960 // SaveScore(level_nr);
3965 static void InitMovingField_MM(int x, int y, int direction)
3967 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3968 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3970 MovDir[x][y] = direction;
3971 MovDir[newx][newy] = direction;
3973 if (Feld[newx][newy] == EL_EMPTY)
3974 Feld[newx][newy] = EL_BLOCKED;
3977 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3979 int direction = MovDir[x][y];
3980 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3981 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3987 static void Blocked2Moving_MM(int x, int y,
3988 int *comes_from_x, int *comes_from_y)
3990 int oldx = x, oldy = y;
3991 int direction = MovDir[x][y];
3993 if (direction == MV_LEFT)
3995 else if (direction == MV_RIGHT)
3997 else if (direction == MV_UP)
3999 else if (direction == MV_DOWN)
4002 *comes_from_x = oldx;
4003 *comes_from_y = oldy;
4006 static int MovingOrBlocked2Element_MM(int x, int y)
4008 int element = Feld[x][y];
4010 if (element == EL_BLOCKED)
4014 Blocked2Moving_MM(x, y, &oldx, &oldy);
4016 return Feld[oldx][oldy];
4023 static void RemoveField(int x, int y)
4025 Feld[x][y] = EL_EMPTY;
4032 static void RemoveMovingField_MM(int x, int y)
4034 int oldx = x, oldy = y, newx = x, newy = y;
4036 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
4039 if (IS_MOVING(x, y))
4041 Moving2Blocked_MM(x, y, &newx, &newy);
4042 if (Feld[newx][newy] != EL_BLOCKED)
4045 else if (Feld[x][y] == EL_BLOCKED)
4047 Blocked2Moving_MM(x, y, &oldx, &oldy);
4048 if (!IS_MOVING(oldx, oldy))
4052 Feld[oldx][oldy] = EL_EMPTY;
4053 Feld[newx][newy] = EL_EMPTY;
4054 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
4055 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
4057 DrawLevelField_MM(oldx, oldy);
4058 DrawLevelField_MM(newx, newy);
4061 void PlaySoundLevel(int x, int y, int sound_nr)
4063 int sx = SCREENX(x), sy = SCREENY(y);
4065 int silence_distance = 8;
4067 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
4068 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
4071 if (!IN_LEV_FIELD(x, y) ||
4072 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
4073 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
4076 volume = SOUND_MAX_VOLUME;
4079 stereo = (sx - SCR_FIELDX/2) * 12;
4081 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
4082 if (stereo > SOUND_MAX_RIGHT)
4083 stereo = SOUND_MAX_RIGHT;
4084 if (stereo < SOUND_MAX_LEFT)
4085 stereo = SOUND_MAX_LEFT;
4088 if (!IN_SCR_FIELD(sx, sy))
4090 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
4091 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
4093 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
4096 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
4099 static void RaiseScore_MM(int value)
4101 game_mm.score += value;
4104 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
4109 void RaiseScoreElement_MM(int element)
4114 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
4118 RaiseScore_MM(native_mm_level.score[SC_KEY]);