package net.minecraft.server.commands; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.datafixers.util.Either; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.ResourceArgument; import net.minecraft.commands.arguments.ResourceOrTagArgument; import net.minecraft.commands.arguments.coordinates.BlockPosArgument; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.QuartPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeResolver; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.levelgen.structure.BoundingBox; import org.apache.commons.lang3.mutable.MutableInt; public class FillBiomeCommand { public static final SimpleCommandExceptionType ERROR_NOT_LOADED = new SimpleCommandExceptionType(Component.translatable("argument.pos.unloaded")); private static final Dynamic2CommandExceptionType ERROR_VOLUME_TOO_LARGE = new Dynamic2CommandExceptionType( (object, object2) -> Component.translatableEscape("commands.fillbiome.toobig", object, object2) ); public static void register(CommandDispatcher dispatcher, CommandBuildContext context) { dispatcher.register( Commands.literal("fillbiome") .requires(commandSourceStack -> commandSourceStack.hasPermission(2)) .then( Commands.argument("from", BlockPosArgument.blockPos()) .then( Commands.argument("to", BlockPosArgument.blockPos()) .then( Commands.argument("biome", ResourceArgument.resource(context, Registries.BIOME)) .executes( commandContext -> fill( commandContext.getSource(), BlockPosArgument.getLoadedBlockPos(commandContext, "from"), BlockPosArgument.getLoadedBlockPos(commandContext, "to"), ResourceArgument.getResource(commandContext, "biome", Registries.BIOME), holder -> true ) ) .then( Commands.literal("replace") .then( Commands.argument("filter", ResourceOrTagArgument.resourceOrTag(context, Registries.BIOME)) .executes( commandContext -> fill( commandContext.getSource(), BlockPosArgument.getLoadedBlockPos(commandContext, "from"), BlockPosArgument.getLoadedBlockPos(commandContext, "to"), ResourceArgument.getResource(commandContext, "biome", Registries.BIOME), ResourceOrTagArgument.getResourceOrTag(commandContext, "filter", Registries.BIOME)::test ) ) ) ) ) ) ) ); } private static int quantize(int value) { return QuartPos.toBlock(QuartPos.fromBlock(value)); } private static BlockPos quantize(BlockPos pos) { return new BlockPos(quantize(pos.getX()), quantize(pos.getY()), quantize(pos.getZ())); } private static BiomeResolver makeResolver( MutableInt biomeEntries, ChunkAccess chunk, BoundingBox targetRegion, Holder replacementBiome, Predicate> filter ) { return (i, j, k, sampler) -> { int l = QuartPos.toBlock(i); int m = QuartPos.toBlock(j); int n = QuartPos.toBlock(k); Holder holder2 = chunk.getNoiseBiome(i, j, k); if (targetRegion.isInside(l, m, n) && filter.test(holder2)) { biomeEntries.increment(); return replacementBiome; } else { return holder2; } }; } public static Either fill(ServerLevel level, BlockPos from, BlockPos to, Holder biome) { return fill(level, from, to, biome, holder -> true, supplier -> {}); } public static Either fill( ServerLevel level, BlockPos from, BlockPos to, Holder biome, Predicate> filter, Consumer> messageOutput ) { BlockPos blockPos = quantize(from); BlockPos blockPos2 = quantize(to); BoundingBox boundingBox = BoundingBox.fromCorners(blockPos, blockPos2); int i = boundingBox.getXSpan() * boundingBox.getYSpan() * boundingBox.getZSpan(); int j = level.getGameRules().getInt(GameRules.RULE_COMMAND_MODIFICATION_BLOCK_LIMIT); if (i > j) { return Either.right(ERROR_VOLUME_TOO_LARGE.create(j, i)); } else { List list = new ArrayList(); for (int k = SectionPos.blockToSectionCoord(boundingBox.minZ()); k <= SectionPos.blockToSectionCoord(boundingBox.maxZ()); k++) { for (int l = SectionPos.blockToSectionCoord(boundingBox.minX()); l <= SectionPos.blockToSectionCoord(boundingBox.maxX()); l++) { ChunkAccess chunkAccess = level.getChunk(l, k, ChunkStatus.FULL, false); if (chunkAccess == null) { return Either.right(ERROR_NOT_LOADED.create()); } list.add(chunkAccess); } } MutableInt mutableInt = new MutableInt(0); for (ChunkAccess chunkAccess : list) { chunkAccess.fillBiomesFromNoise(makeResolver(mutableInt, chunkAccess, boundingBox, biome, filter), level.getChunkSource().randomState().sampler()); chunkAccess.markUnsaved(); } level.getChunkSource().chunkMap.resendBiomesForChunks(list); messageOutput.accept( (Supplier)() -> Component.translatable( "commands.fillbiome.success.count", mutableInt.getValue(), boundingBox.minX(), boundingBox.minY(), boundingBox.minZ(), boundingBox.maxX(), boundingBox.maxY(), boundingBox.maxZ() ) ); return Either.left(mutableInt.getValue()); } } private static int fill(CommandSourceStack source, BlockPos from, BlockPos to, Holder.Reference biome, Predicate> filter) throws CommandSyntaxException { Either either = fill(source.getLevel(), from, to, biome, filter, supplier -> source.sendSuccess(supplier, true)); Optional optional = either.right(); if (optional.isPresent()) { throw (CommandSyntaxException)optional.get(); } else { return (Integer)either.left().get(); } } }