package net.minecraft.world.item; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultiset; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.core.SectionPos; import net.minecraft.core.component.DataComponents; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.BiomeTags; import net.minecraft.tags.BlockTags; import net.minecraft.util.Mth; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.component.MapPostProcessing; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.MapColor; import net.minecraft.world.level.material.MapColor.Brightness; import net.minecraft.world.level.saveddata.maps.MapId; import net.minecraft.world.level.saveddata.maps.MapItemSavedData; import org.jetbrains.annotations.Nullable; public class MapItem extends Item { public static final int IMAGE_WIDTH = 128; public static final int IMAGE_HEIGHT = 128; public MapItem(Item.Properties properties) { super(properties); } public static ItemStack create(ServerLevel level, int x, int z, byte scale, boolean trackingPosition, boolean unlimitedTracking) { ItemStack itemStack = new ItemStack(Items.FILLED_MAP); MapId mapId = createNewSavedData(level, x, z, scale, trackingPosition, unlimitedTracking, level.dimension()); itemStack.set(DataComponents.MAP_ID, mapId); return itemStack; } @Nullable public static MapItemSavedData getSavedData(@Nullable MapId mapId, Level level) { return mapId == null ? null : level.getMapData(mapId); } @Nullable public static MapItemSavedData getSavedData(ItemStack stack, Level level) { MapId mapId = stack.get(DataComponents.MAP_ID); return getSavedData(mapId, level); } private static MapId createNewSavedData( ServerLevel level, int x, int z, int scale, boolean trackingPosition, boolean unlimitedTracking, ResourceKey dimension ) { MapItemSavedData mapItemSavedData = MapItemSavedData.createFresh(x, z, (byte)scale, trackingPosition, unlimitedTracking, dimension); MapId mapId = level.getFreeMapId(); level.setMapData(mapId, mapItemSavedData); return mapId; } public void update(Level level, Entity viewer, MapItemSavedData data) { if (level.dimension() == data.dimension && viewer instanceof Player) { int i = 1 << data.scale; int j = data.centerX; int k = data.centerZ; int l = Mth.floor(viewer.getX() - j) / i + 64; int m = Mth.floor(viewer.getZ() - k) / i + 64; int n = 128 / i; if (level.dimensionType().hasCeiling()) { n /= 2; } MapItemSavedData.HoldingPlayer holdingPlayer = data.getHoldingPlayer((Player)viewer); holdingPlayer.step++; BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); BlockPos.MutableBlockPos mutableBlockPos2 = new BlockPos.MutableBlockPos(); boolean bl = false; for (int o = l - n + 1; o < l + n; o++) { if ((o & 15) == (holdingPlayer.step & 15) || bl) { bl = false; double d = 0.0; for (int p = m - n - 1; p < m + n; p++) { if (o >= 0 && p >= -1 && o < 128 && p < 128) { int q = Mth.square(o - l) + Mth.square(p - m); boolean bl2 = q > (n - 2) * (n - 2); int r = (j / i + o - 64) * i; int s = (k / i + p - 64) * i; Multiset multiset = LinkedHashMultiset.create(); LevelChunk levelChunk = level.getChunk(SectionPos.blockToSectionCoord(r), SectionPos.blockToSectionCoord(s)); if (!levelChunk.isEmpty()) { int t = 0; double e = 0.0; if (level.dimensionType().hasCeiling()) { int u = r + s * 231871; u = u * u * 31287121 + u * 11; if ((u >> 20 & 1) == 0) { multiset.add(Blocks.DIRT.defaultBlockState().getMapColor(level, BlockPos.ZERO), 10); } else { multiset.add(Blocks.STONE.defaultBlockState().getMapColor(level, BlockPos.ZERO), 100); } e = 100.0; } else { for (int u = 0; u < i; u++) { for (int v = 0; v < i; v++) { mutableBlockPos.set(r + u, 0, s + v); int w = levelChunk.getHeight(Heightmap.Types.WORLD_SURFACE, mutableBlockPos.getX(), mutableBlockPos.getZ()) + 1; BlockState blockState; if (w <= level.getMinY()) { blockState = Blocks.BEDROCK.defaultBlockState(); } else { do { mutableBlockPos.setY(--w); blockState = levelChunk.getBlockState(mutableBlockPos); } while (blockState.getMapColor(level, mutableBlockPos) == MapColor.NONE && w > level.getMinY()); if (w > level.getMinY() && !blockState.getFluidState().isEmpty()) { int x = w - 1; mutableBlockPos2.set(mutableBlockPos); BlockState blockState2; do { mutableBlockPos2.setY(x--); blockState2 = levelChunk.getBlockState(mutableBlockPos2); t++; } while (x > level.getMinY() && !blockState2.getFluidState().isEmpty()); blockState = this.getCorrectStateForFluidBlock(level, blockState, mutableBlockPos); } } data.checkBanners(level, mutableBlockPos.getX(), mutableBlockPos.getZ()); e += (double)w / (i * i); multiset.add(blockState.getMapColor(level, mutableBlockPos)); } } } t /= i * i; MapColor mapColor = Iterables.getFirst(Multisets.copyHighestCountFirst(multiset), MapColor.NONE); Brightness brightness; if (mapColor == MapColor.WATER) { double f = t * 0.1 + (o + p & 1) * 0.2; if (f < 0.5) { brightness = Brightness.HIGH; } else if (f > 0.9) { brightness = Brightness.LOW; } else { brightness = Brightness.NORMAL; } } else { double f = (e - d) * 4.0 / (i + 4) + ((o + p & 1) - 0.5) * 0.4; if (f > 0.6) { brightness = Brightness.HIGH; } else if (f < -0.6) { brightness = Brightness.LOW; } else { brightness = Brightness.NORMAL; } } d = e; if (p >= 0 && q < n * n && (!bl2 || (o + p & 1) != 0)) { bl |= data.updateColor(o, p, mapColor.getPackedId(brightness)); } } } } } } } } private BlockState getCorrectStateForFluidBlock(Level level, BlockState state, BlockPos pos) { FluidState fluidState = state.getFluidState(); return !fluidState.isEmpty() && !state.isFaceSturdy(level, pos, Direction.UP) ? fluidState.createLegacyBlock() : state; } private static boolean isBiomeWatery(boolean[] wateryMap, int xSample, int zSample) { return wateryMap[zSample * 128 + xSample]; } public static void renderBiomePreviewMap(ServerLevel serverLevel, ItemStack stack) { MapItemSavedData mapItemSavedData = getSavedData(stack, serverLevel); if (mapItemSavedData != null) { if (serverLevel.dimension() == mapItemSavedData.dimension) { int i = 1 << mapItemSavedData.scale; int j = mapItemSavedData.centerX; int k = mapItemSavedData.centerZ; boolean[] bls = new boolean[16384]; int l = j / i - 64; int m = k / i - 64; BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); for (int n = 0; n < 128; n++) { for (int o = 0; o < 128; o++) { Holder holder = serverLevel.getBiome(mutableBlockPos.set((l + o) * i, 0, (m + n) * i)); bls[n * 128 + o] = holder.is(BiomeTags.WATER_ON_MAP_OUTLINES); } } for (int n = 1; n < 127; n++) { for (int o = 1; o < 127; o++) { int p = 0; for (int q = -1; q < 2; q++) { for (int r = -1; r < 2; r++) { if ((q != 0 || r != 0) && isBiomeWatery(bls, n + q, o + r)) { p++; } } } Brightness brightness = Brightness.LOWEST; MapColor mapColor = MapColor.NONE; if (isBiomeWatery(bls, n, o)) { mapColor = MapColor.COLOR_ORANGE; if (p > 7 && o % 2 == 0) { switch ((n + (int)(Mth.sin(o + 0.0F) * 7.0F)) / 8 % 5) { case 0: case 4: brightness = Brightness.LOW; break; case 1: case 3: brightness = Brightness.NORMAL; break; case 2: brightness = Brightness.HIGH; } } else if (p > 7) { mapColor = MapColor.NONE; } else if (p > 5) { brightness = Brightness.NORMAL; } else if (p > 3) { brightness = Brightness.LOW; } else if (p > 1) { brightness = Brightness.LOW; } } else if (p > 0) { mapColor = MapColor.COLOR_BROWN; if (p > 3) { brightness = Brightness.NORMAL; } else { brightness = Brightness.LOWEST; } } if (mapColor != MapColor.NONE) { mapItemSavedData.setColor(n, o, mapColor.getPackedId(brightness)); } } } } } } @Override public void inventoryTick(ItemStack stack, ServerLevel level, Entity entity, @Nullable EquipmentSlot slot) { MapItemSavedData mapItemSavedData = getSavedData(stack, level); if (mapItemSavedData != null) { if (entity instanceof Player player) { mapItemSavedData.tickCarriedBy(player, stack); } if (!mapItemSavedData.locked && slot != null && slot.getType() == EquipmentSlot.Type.HAND) { this.update(level, entity, mapItemSavedData); } } } @Override public void onCraftedPostProcess(ItemStack stack, Level level) { MapPostProcessing mapPostProcessing = stack.remove(DataComponents.MAP_POST_PROCESSING); if (mapPostProcessing != null) { if (level instanceof ServerLevel serverLevel) { switch (mapPostProcessing) { case LOCK: lockMap(stack, serverLevel); break; case SCALE: scaleMap(stack, serverLevel); } } } } private static void scaleMap(ItemStack stack, ServerLevel level) { MapItemSavedData mapItemSavedData = getSavedData(stack, level); if (mapItemSavedData != null) { MapId mapId = level.getFreeMapId(); level.setMapData(mapId, mapItemSavedData.scaled()); stack.set(DataComponents.MAP_ID, mapId); } } private static void lockMap(ItemStack stack, ServerLevel level) { MapItemSavedData mapItemSavedData = getSavedData(stack, level); if (mapItemSavedData != null) { MapId mapId = level.getFreeMapId(); MapItemSavedData mapItemSavedData2 = mapItemSavedData.locked(); level.setMapData(mapId, mapItemSavedData2); stack.set(DataComponents.MAP_ID, mapId); } } @Override public InteractionResult useOn(UseOnContext context) { BlockState blockState = context.getLevel().getBlockState(context.getClickedPos()); if (blockState.is(BlockTags.BANNERS)) { if (!context.getLevel().isClientSide) { MapItemSavedData mapItemSavedData = getSavedData(context.getItemInHand(), context.getLevel()); if (mapItemSavedData != null && !mapItemSavedData.toggleBanner(context.getLevel(), context.getClickedPos())) { return InteractionResult.FAIL; } } return InteractionResult.SUCCESS; } else { return super.useOn(context); } } }