diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureNBT.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureNBT.java index 3b062ac6..454be563 100644 --- a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureNBT.java +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureNBT.java @@ -27,6 +27,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.*; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -111,13 +112,13 @@ public class StructureNBT { return READER_CACHE.computeIfAbsent(resource, r -> _readStructureFromJar(r)); } - private static StructureTemplate _readStructureFromJar(ResourceLocation resource) { - String ns = resource.getNamespace(); - String nm = resource.getPath(); + private static String getStructurePath(ResourceLocation resource) { + return "data/" + resource.getNamespace() + "/structures/" + resource.getPath(); + } - allResourcesFrom(resource); + private static StructureTemplate _readStructureFromJar(ResourceLocation resource) { try { - InputStream inputstream = MinecraftServer.class.getResourceAsStream("/data/" + ns + "/structures/" + nm + ".nbt"); + InputStream inputstream = MinecraftServer.class.getResourceAsStream("/" + getStructurePath(resource) + ".nbt"); return readStructureFromStream(inputstream); } catch (IOException e) { e.printStackTrace(); @@ -126,11 +127,19 @@ public class StructureNBT { return null; } - public static List allResourcesFrom(ResourceLocation resource) { + /** + * Returns a list of all structures found at the given resource location. + * + * @param resource The resource location to search. + * @param recursionDepth The maximum recursion depth or 0 to indicate no limitation + * @return A list of all structures found at the given resource location. + */ + public static List createResourcesFrom(ResourceLocation resource, int recursionDepth) { String ns = resource.getNamespace(); String nm = resource.getPath(); - final URL url = MinecraftServer.class.getClassLoader().getResource("data/" + ns + "/structures/" + nm); + final String resourceFolder = getStructurePath(resource); + final URL url = MinecraftServer.class.getClassLoader().getResource(resourceFolder); if (url != null) { final URI uri; try { @@ -143,26 +152,38 @@ public class StructureNBT { if (uri.getScheme().equals("jar")) { FileSystem fileSystem = null; try { - fileSystem = FileSystems.newFileSystem(uri, new HashMap<>()); - } catch (IOException e) { - BCLib.LOGGER.error("Unable to load Resources: ", e); - return null; + fileSystem = FileSystems.getFileSystem(uri); + } catch (FileSystemNotFoundException notLoaded) { + try { + fileSystem = FileSystems.newFileSystem(uri, new HashMap<>()); + } catch (IOException e) { + BCLib.LOGGER.error("Unable to load Filesystem: ", e); + return null; + } } - myPath = fileSystem.getPath("/resources"); + + myPath = fileSystem.getPath(resourceFolder); } else { myPath = Paths.get(uri); } - if (myPath.toFile().isDirectory()) { + if (Files.isDirectory(myPath)) { try { - return Files.walk(myPath, 1) - .filter(p -> p.toFile().isFile()) - .map(p -> p.getFileName().toFile()) - .filter(f -> f.toString().endsWith(".nbt")) - .map(f -> f.toString()) + // /bclib place nbt minecraft:village/plains 0 southOf 0 -60 -0 controller + return Files.walk(myPath, recursionDepth <= 0 ? Integer.MAX_VALUE : recursionDepth) + .filter(p -> Files.isRegularFile(p)) + .map(p -> { + if (p.isAbsolute()) + return Path.of(uri).relativize(p).toString(); + else { + return p.toString().replace(resourceFolder, "").replaceAll("^/+", ""); + } + }) + .filter(s -> s.endsWith(".nbt")) .map(s -> new ResourceLocation( ns, (nm.isEmpty() ? "" : (nm + "/")) + s.substring(0, s.length() - 4) )) + .sorted(Comparator.comparing(ResourceLocation::toString)) .map(r -> { BCLib.LOGGER.info("Loading Structure: " + r); try { @@ -171,7 +192,8 @@ public class StructureNBT { BCLib.LOGGER.error("Unable to load Structure " + r, e); } return null; - }).toList(); + }) + .toList(); } catch (IOException e) { BCLib.LOGGER.error("Unable to load Resources: ", e); return null; diff --git a/src/main/java/org/betterx/bclib/commands/PlaceCommand.java b/src/main/java/org/betterx/bclib/commands/PlaceCommand.java index 02c599dd..38329579 100644 --- a/src/main/java/org/betterx/bclib/commands/PlaceCommand.java +++ b/src/main/java/org/betterx/bclib/commands/PlaceCommand.java @@ -3,12 +3,14 @@ package org.betterx.bclib.commands; import de.ambertation.wunderlib.math.Bounds; import de.ambertation.wunderlib.math.Float3; import org.betterx.bclib.api.v2.levelgen.structures.StructureNBT; +import org.betterx.bclib.commands.arguments.ConnectorArgument; import org.betterx.bclib.commands.arguments.Float3ArgumentType; import org.betterx.bclib.commands.arguments.PlacementDirections; import org.betterx.bclib.commands.arguments.TemplatePlacementArgument; import org.betterx.bclib.util.BlocksHelper; import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; @@ -17,28 +19,34 @@ import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.ResourceKeyArgument; import net.minecraft.commands.arguments.ResourceLocationArgument; import net.minecraft.commands.arguments.blocks.BlockInput; import net.minecraft.commands.arguments.blocks.BlockStateArgument; +import net.minecraft.commands.arguments.blocks.BlockStateParser; import net.minecraft.commands.arguments.coordinates.BlockPosArgument; import net.minecraft.commands.arguments.coordinates.Coordinates; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.Vec3i; +import net.minecraft.core.*; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.TagParser; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.*; -import net.minecraft.world.level.block.entity.CommandBlockEntity; -import net.minecraft.world.level.block.entity.StructureBlockEntity; +import net.minecraft.world.level.block.entity.*; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.AttachFace; import net.minecraft.world.level.block.state.properties.StructureMode; import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; +import java.util.List; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; @@ -49,8 +57,16 @@ class PlaceCommandBuilder { public static final String EMPTY = "empty"; public static final String PLACEMENT = "placement"; public static final String POS = "pos"; + public static final String RECURSION_DEPTH = "recursion_depth"; public static final String SPAN = "span"; public static final String BORDER = "border"; + public static final String ADD_CONTROLL_BLOCKS = "controller"; + public static final String FILL_VOID = "replaceair"; + public static final String JIGSAW = "jigsaw"; + public static final String CONNECTOR_NAME = "connector_name"; + public static final String REPLACE_WITH = "replace_with"; + public static final String ROLLABLE = "rollable"; + public static final String REPLACE_FROM_WORLD = "fromWorld"; public void register( CommandBuildContext ctx, @@ -68,17 +84,25 @@ class PlaceCommandBuilder { POS, BlockPosArgument.blockPos() ); + final Supplier> recursionDepth = () -> Commands.argument( + RECURSION_DEPTH, + IntegerArgumentType.integer(0, 16) + ); + final Function> placeIt = (hasRecursionArg) -> placement + .get() + .then( + addOptionalsAndExecute( + ctx, + pos.get(), + hasRecursionArg, + PlaceCommandBuilder::placeNBT + ) + ); final var nbtTree = Commands.literal(NBT).then( - path.get().then( - placement.get().then( - addOptionalsAndExecute( - ctx, - pos.get(), - PlaceCommandBuilder::placeNBT - ) - ) - ) + path.get() + .then(recursionDepth.get().then(placeIt.apply(true))) + .then(placeIt.apply(false)) ); final var emptyTree = Commands.literal(EMPTY).then( @@ -88,92 +112,169 @@ class PlaceCommandBuilder { addOptionalsAndExecute( ctx, Commands.argument(SPAN, Float3ArgumentType.int3(0, 64)), + false, PlaceCommandBuilder::placeEmpty ) ) ) ) ); + final Supplier> replace = () -> Commands.argument( + REPLACE_WITH, + BlockStateArgument.block(ctx) + ); + final Supplier> replace_source = () -> Commands.literal( + REPLACE_FROM_WORLD); + final Supplier> rotate = () -> Commands.literal(ROLLABLE); + + final var jigsawTree = Commands.literal(JIGSAW).then( + Commands.argument(PlaceCommand.POOL, ResourceKeyArgument.key(Registries.TEMPLATE_POOL)).then( + Commands.argument(CONNECTOR_NAME, ConnectorArgument.id()) + .then(replace.get() + .then(rotate.get() + .then(pos.get() + .executes(cc -> placeJigsaw(cc, true, true, false)))) + .then(pos.get().executes(cc -> placeJigsaw(cc, true, false, false))) + ) + .then(replace_source.get() + .then(rotate.get() + .then(pos.get() + .executes(cc -> placeJigsaw( + cc, + false, + true, + true + )))) + .then(pos.get().executes(cc -> placeJigsaw(cc, false, false, true))) + ) + .then(rotate.get() + .then(pos.get().executes(cc -> placeJigsaw(cc, true, true, false)))) + .then( + pos.get().executes(cc -> placeJigsaw(cc, false, false, false)) + ) + + ) + ); + + + final var testSpaner = Commands.literal("spawner") + .then(pos.get().executes(cc -> placeSpawner(cc))); command .then(nbtTree) - .then(emptyTree); + .then(emptyTree) + .then(jigsawTree) + .then(testSpaner); } private RequiredArgumentBuilder addOptionalsAndExecute( CommandBuildContext commandBuildContext, RequiredArgumentBuilder root, + boolean hasRecursionArgs, Executor runner ) { - final Supplier> addControllers = () -> Commands.literal("controller"); + final Supplier> addControllers = () -> Commands.literal( + ADD_CONTROLL_BLOCKS); + final Supplier> replaceAir = () -> Commands.literal(FILL_VOID); final Supplier> addBorder = () -> Commands.argument( BORDER, BlockStateArgument.block(commandBuildContext) ); return root - .executes(c -> runner.exec(c, false, false)) + .executes(c -> runner.exec(c, false, false, false, hasRecursionArgs)) .then(addBorder.get() - .executes(c -> runner.exec(c, true, false)) + .executes(c -> runner.exec(c, true, false, false, hasRecursionArgs)) .then(addControllers.get() - .executes(c -> runner.exec(c, true, true))) + .executes(c -> runner.exec(c, true, true, false, hasRecursionArgs))) + .then(addControllers.get() + .then(replaceAir.get() + .executes(c -> runner.exec( + c, + true, + true, + true, + hasRecursionArgs + ))) + ) ) - .then(addControllers.get().executes(c -> runner.exec(c, false, true))); + .then(addControllers.get().executes(c -> runner.exec(c, false, true, false, hasRecursionArgs))) + .then(addControllers.get() + .then(replaceAir.get() + .executes(c -> runner.exec( + c, + false, + true, + true, + hasRecursionArgs + )))); } interface Executor { int exec( CommandContext ctx, - boolean border, - boolean controlBlocks + boolean hasBorderArg, + boolean controlBlocks, + boolean replaceAir, + boolean hasRecursionArg ) throws CommandSyntaxException; } // /bclib place nbt betternether:city southOf 0 -59 0 controller + // /bclib place nbt minecraft:village/plains 0 southOf 0 -59 0 controller protected static int placeNBT( CommandContext ctx, - boolean border, - boolean controlBlocks + boolean hasBorderArg, + boolean controlBlocks, + boolean replaceAir, + boolean hasRecursionArg ) throws CommandSyntaxException { final ResourceLocation id = ResourceLocationArgument.getId(ctx, PATH); final PlacementDirections searchDir = TemplatePlacementArgument.getPlacement(ctx, PLACEMENT); - final BlockInput blockInput = border ? BlockStateArgument.getBlock(ctx, BORDER) : null; + final BlockInput blockInput = hasBorderArg ? BlockStateArgument.getBlock(ctx, BORDER) : null; final BlockPos pos = BlockPosArgument.getLoadedBlockPos(ctx, POS); - var structures = StructureNBT.allResourcesFrom(id); + final int recursionDepth = hasRecursionArg ? IntegerArgumentType.getInteger(ctx, RECURSION_DEPTH) : 1; + final List structures = StructureNBT.createResourcesFrom(id, recursionDepth); if (structures != null) { Bounds b = Bounds.of(pos); Bounds all = Bounds.of(pos); BlockPos pNew = pos; + BlockPos rowStart = pos; + String lastPrefix = null; for (var s : structures) { - Bounds bb = Bounds.of(PlaceCommand.placeBlocks( + String prefix = s.location.getPath().contains("/") + ? s.location.getPath().replaceAll("/[^/]*$", "") + : ""; + if (lastPrefix != null && !lastPrefix.equals(prefix)) { + pNew = searchDir.resetStart(b, pNew, 10); + rowStart = pNew; + b = Bounds.of(pNew); + } + lastPrefix = prefix; + + Bounds bb = Bounds.of(Objects.requireNonNull(PlaceCommand.placeBlocks( ctx.getSource(), - pNew, + rowStart, searchDir.getOffset(), blockInput, controlBlocks, + replaceAir, s.location, (p) -> s.getBoundingBox(p, Rotation.NONE, Mirror.NONE), (level, p) -> s.generateAt(level, p, Rotation.NONE, Mirror.NONE) - )); + ))); + rowStart = searchDir.advanceStart(bb, rowStart); + ; + b = b.encapsulate(bb); all = all.encapsulate(bb); - if (searchDir == PlacementDirections.NORTH_OF || searchDir == PlacementDirections.SOUTH_OF) { - if (b.getSize().z > 10 * 16) { - pNew = new BlockPos((int) b.max.x + 3, pNew.getY(), pNew.getZ()); - b = Bounds.of(pNew); - } - } else if (searchDir == PlacementDirections.EAST_OF || searchDir == PlacementDirections.WEST_OF) { - if (b.getSize().x > 10 * 16) { - pNew = new BlockPos(pNew.getX(), pNew.getY(), (int) b.max.z + 3); - b = Bounds.of(pNew); - } - } else { - if (b.getSize().y > 10 * 16) { - pNew = new BlockPos(pNew.getX(), pNew.getY(), (int) b.max.z + 3); - b = Bounds.of(pNew); - } - } + + if (searchDir.sizeInDirection(b) > 10 * 16) { + pNew = searchDir.resetStart(b, pNew); + rowStart = pNew; + b = Bounds.of(pNew); + } } Bounds finalAll = all; ctx.getSource() @@ -191,6 +292,7 @@ class PlaceCommandBuilder { searchDir.getOffset(), blockInput, controlBlocks, + replaceAir, structureNBT.location, (p) -> structureNBT.getBoundingBox(p, Rotation.NONE, Mirror.NONE), (level, p) -> structureNBT.generateAt(level, p, Rotation.NONE, Mirror.NONE) @@ -200,12 +302,14 @@ class PlaceCommandBuilder { protected static int placeEmpty( CommandContext ctx, - boolean border, - boolean controlBlocks + boolean hasBorderArg, + boolean controlBlocks, + boolean replaceAir, + boolean hasRecursionArg ) throws CommandSyntaxException { final ResourceLocation id = ResourceLocationArgument.getId(ctx, PATH); final PlacementDirections searchDir = TemplatePlacementArgument.getPlacement(ctx, PLACEMENT); - final BlockInput blockInput = border ? BlockStateArgument.getBlock(ctx, BORDER) : null; + final BlockInput blockInput = hasBorderArg ? BlockStateArgument.getBlock(ctx, BORDER) : null; final BlockPos span = Float3ArgumentType.getFloat3(ctx, SPAN).toBlockPos(); return PlaceCommand.placeBlocks( @@ -214,6 +318,7 @@ class PlaceCommandBuilder { searchDir == null || searchDir.dir == Float3.ZERO ? null : searchDir.dir.toBlockPos(), blockInput, controlBlocks, + replaceAir, id, (p) -> BoundingBox.fromCorners(p, p.offset(span)), (level, p) -> { @@ -232,9 +337,90 @@ class PlaceCommandBuilder { } ) == null ? Command.SINGLE_SUCCESS : -1; } + + public static int placeJigsaw( + CommandContext ctx, + boolean hasReplaceArg, + boolean rotate, + boolean replaceFromWorld + ) throws CommandSyntaxException { + // /bclib place jigsaw betterend:village/center_piece betterend:entrance fromWorld rollable 1235 -60 42 + final BlockPos pos = BlockPosArgument.getLoadedBlockPos(ctx, POS); + final Holder.Reference pool = ResourceKeyArgument.getStructureTemplatePool( + ctx, + PlaceCommand.POOL + ); + ResourceLocation connector = ResourceLocationArgument.getId(ctx, CONNECTOR_NAME); + if (connector.getNamespace().equals("-")) { + connector = new ResourceLocation(pool.key().location().getNamespace(), connector.getPath()); + } + BlockState replaceWith = hasReplaceArg + ? BlockStateArgument.getBlock(ctx, REPLACE_WITH).getState() + : Blocks.AIR.defaultBlockState(); + + final ServerLevel level = ctx.getSource().getLevel(); + final ServerPlayer player = ctx.getSource().getPlayer(); + final int deltaY = player.getBlockY() - pos.getY(); + + BlockState state = Blocks.JIGSAW.defaultBlockState(); + + + if (deltaY < 2 && deltaY > -2 && !rotate) { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(player.getDirection().getOpposite(), Direction.UP) + ); + } else if (deltaY < 0) { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(Direction.DOWN, player.getDirection().getOpposite()) + ); + } else { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(Direction.UP, player.getDirection().getOpposite()) + ); + } + + if (replaceFromWorld) { + replaceWith = level.getBlockState(pos); + } + level.setBlock(pos, state, BlocksHelper.SET_SILENT); + if (level.getBlockEntity(pos) instanceof JigsawBlockEntity entity) { + entity.setName(connector); + entity.setTarget(connector); + entity.setPool(pool.key()); + entity.setFinalState(BlockStateParser.serialize(replaceWith)); + if (rotate) { + entity.setJoint(JigsawBlockEntity.JointType.ROLLABLE); + } else { + entity.setJoint(JigsawBlockEntity.JointType.ALIGNED); + } + } + return Command.SINGLE_SUCCESS; + } + + public static int placeSpawner(CommandContext ctx) throws CommandSyntaxException { + final BlockPos pos = BlockPosArgument.getLoadedBlockPos(ctx, POS); + final ServerLevel level = ctx.getSource().getLevel(); + + level.setBlock(pos, Blocks.SPAWNER.defaultBlockState(), BlocksHelper.SET_SILENT); + + if (level.getBlockEntity(pos) instanceof SpawnerBlockEntity entity) { + CompoundTag tag = TagParser.parseTag( + "{SpawnData:{entity:{id:wither_skeleton,PersistenceRequired:1,HandItems:[{Count:1,id:netherite_sword},{Count:1,id:shield}],ArmorItems:[{Count:1,id:netherite_boots,tag:{Enchantments:[{id:protection,lvl:1}]}},{Count:1,id:netherite_leggings,tag:{Enchantments:[{id:protection,lvl:1}]}},{Count:1,id:netherite_chestplate,tag:{Enchantments:[{id:protection,lvl:1},{id:thorns,lvl:3}]}},{Count:1,id:netherite_helmet,tag:{Enchantments:[{id:protection,lvl:1}]}}],HandDropChances:[0.0f,0.0f],ArmorDropChances:[0.0f,0.0f,0.0f,0.0f]}, custom_spawn_rules:{sky_light_limit:{max_inclusive:13},block_light_limit:{max_inclusive:11}}},SpawnRange:4,SpawnCount:8,MaxNearbyEntities:18,Delay:499,MinSpawnDelay:300,MaxSpawnDelay:1600,RequiredPlayerRange:20}"); + entity.load(tag); + } + + return Command.SINGLE_SUCCESS; + } } public class PlaceCommand { + + public static final String PLACE_COMMAND = "place"; + public static final String POOL = "pool"; + public PlaceCommand() { } @@ -243,7 +429,7 @@ public class PlaceCommand { CommandBuildContext commandBuildContext ) { final var command = Commands - .literal("place") + .literal(PLACE_COMMAND) .requires(commandSourceStack -> commandSourceStack.hasPermission(2)); new PlaceCommandBuilder().register(commandBuildContext, command); @@ -264,23 +450,23 @@ public class PlaceCommand { } private static void replaceAir(Level level, BoundingBox bb) { - for (int x = bb.minX(); x <= bb.maxX(); x++) - for (int y = bb.minY(); y <= bb.maxY(); y++) - for (int z = bb.minZ(); z <= bb.maxZ(); z++) { - BlockPos bp = new BlockPos(x, y, z); - if (level.getBlockState(bp).is(Blocks.AIR)) { - level.setBlock(bp, Blocks.STRUCTURE_VOID.defaultBlockState(), BlocksHelper.SET_OBSERV); - } - } + BlocksHelper.forAllInBounds(bb, (bp) -> { + if (level.getBlockState(bp).is(Blocks.AIR)) { + level.setBlock(bp, Blocks.STRUCTURE_VOID.defaultBlockState(), BlocksHelper.SET_OBSERV); + } + }); + } + + private static void removeLootTableSeed(Level level, BoundingBox bb) { + BlocksHelper.forAllInBounds(bb, (bp) -> { + if (level.getBlockEntity(bp) instanceof RandomizableContainerBlockEntity rnd) { + rnd.setLootTable(null, 0); + } + }); } static void fill(Level level, BoundingBox bb, BlockState blockState) { - for (int x = bb.minX(); x <= bb.maxX(); x++) - for (int y = bb.minY(); y <= bb.maxY(); y++) - for (int z = bb.minZ(); z <= bb.maxZ(); z++) { - BlockPos bp = new BlockPos(x, y, z); - level.setBlock(bp, blockState, BlocksHelper.SET_OBSERV); - } + BlocksHelper.forAllInBounds(bb, (bp) -> level.setBlock(bp, blockState, BlocksHelper.SET_OBSERV)); } static void fillStructureVoid(Level level, BoundingBox bb) { @@ -289,24 +475,7 @@ public class PlaceCommand { //Draws a border around the bounding box private static void outline(Level level, BoundingBox bb, BlockState outlineState) { - for (int x = bb.minX(); x <= bb.maxX(); x++) { - level.setBlock(new BlockPos(x, bb.minY(), bb.minZ()), outlineState, BlocksHelper.SET_OBSERV); - level.setBlock(new BlockPos(x, bb.maxY(), bb.minZ()), outlineState, BlocksHelper.SET_OBSERV); - level.setBlock(new BlockPos(x, bb.minY(), bb.maxZ()), outlineState, BlocksHelper.SET_OBSERV); - level.setBlock(new BlockPos(x, bb.maxY(), bb.maxZ()), outlineState, BlocksHelper.SET_OBSERV); - } - for (int y = bb.minY(); y <= bb.maxY(); y++) { - level.setBlock(new BlockPos(bb.minX(), y, bb.minZ()), outlineState, BlocksHelper.SET_OBSERV); - level.setBlock(new BlockPos(bb.maxX(), y, bb.minZ()), outlineState, BlocksHelper.SET_OBSERV); - level.setBlock(new BlockPos(bb.minX(), y, bb.maxZ()), outlineState, BlocksHelper.SET_OBSERV); - level.setBlock(new BlockPos(bb.maxX(), y, bb.maxZ()), outlineState, BlocksHelper.SET_OBSERV); - } - for (int z = bb.minZ(); z <= bb.maxZ(); z++) { - level.setBlock(new BlockPos(bb.minX(), bb.minY(), z), outlineState, BlocksHelper.SET_OBSERV); - level.setBlock(new BlockPos(bb.maxX(), bb.minY(), z), outlineState, BlocksHelper.SET_OBSERV); - level.setBlock(new BlockPos(bb.minX(), bb.maxY(), z), outlineState, BlocksHelper.SET_OBSERV); - level.setBlock(new BlockPos(bb.maxX(), bb.maxY(), z), outlineState, BlocksHelper.SET_OBSERV); - } + BlocksHelper.forOutlineInBounds(bb, (bp) -> level.setBlock(bp, outlineState, BlocksHelper.SET_OBSERV)); } private static BoundingBox adapt(BoundingBox bb, boolean border, boolean structureBlock) { @@ -314,12 +483,12 @@ public class PlaceCommand { return bb.inflatedBy(1); } else if (structureBlock) { return new BoundingBox( - bb.minX() - 1, - bb.minY() - 1, - bb.minZ() - 1, - bb.maxX(), - bb.maxY(), - bb.maxZ() + bb.minX(), + bb.minY(), + bb.minZ(), + bb.maxX() + 1, + bb.maxY() + 1, + bb.maxZ() + 1 ); } return bb; @@ -374,6 +543,7 @@ public class PlaceCommand { BlockPos searchDir, BlockInput blockInput, boolean structureBlock, + boolean replaceAir, ResourceLocation location, Function getBounds, BiConsumer generate @@ -399,10 +569,13 @@ public class PlaceCommand { } } + if (structureBlock) { + pos = pos.offset(1, 0, 1); + } final BoundingBox bbNBT = getBounds.apply(pos); final BoundingBox bb; if (blockInput != null) { - bb = bbNBT.inflatedBy(1); + bb = adapt(bbNBT, true, structureBlock); outline(stack.getLevel(), bb, blockInput.getState()); stack.sendSuccess(() -> Component.literal("Placed border: " + bb.toString()) .setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN)), true); @@ -411,7 +584,10 @@ public class PlaceCommand { } generate.accept(stack.getLevel(), pos); - replaceAir(stack.getLevel(), bbNBT); + if (replaceAir) { + replaceAir(stack.getLevel(), bbNBT); + } + removeLootTableSeed(stack.getLevel(), bbNBT); stack.sendSuccess(() -> Component.literal("Placed NBT: " + bbNBT.toString()) .setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN)), true); diff --git a/src/main/java/org/betterx/bclib/util/BlocksHelper.java b/src/main/java/org/betterx/bclib/util/BlocksHelper.java index cd27ee3d..86abbf86 100644 --- a/src/main/java/org/betterx/bclib/util/BlocksHelper.java +++ b/src/main/java/org/betterx/bclib/util/BlocksHelper.java @@ -13,6 +13,7 @@ import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.*; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.material.LavaFluid; import net.minecraft.world.level.material.PushReaction; @@ -21,6 +22,7 @@ import com.google.common.collect.Maps; import java.util.Map; import java.util.Optional; import java.util.Random; +import java.util.function.Consumer; import java.util.function.Predicate; public class BlocksHelper { @@ -364,7 +366,37 @@ public class BlocksHelper { ) { return true; } - + return state.canBeReplaced(); } + + public static void forAllInBounds(BoundingBox bb, Consumer worker) { + for (int x = bb.minX(); x <= bb.maxX(); x++) + for (int y = bb.minY(); y <= bb.maxY(); y++) + for (int z = bb.minZ(); z <= bb.maxZ(); z++) { + BlockPos bp = new BlockPos(x, y, z); + worker.accept(bp); + } + } + + public static void forOutlineInBounds(BoundingBox bb, Consumer worker) { + for (int x = bb.minX(); x <= bb.maxX(); x++) { + worker.accept(new BlockPos(x, bb.minY(), bb.minZ())); + worker.accept(new BlockPos(x, bb.maxY(), bb.minZ())); + worker.accept(new BlockPos(x, bb.minY(), bb.maxZ())); + worker.accept(new BlockPos(x, bb.maxY(), bb.maxZ())); + } + for (int y = bb.minY(); y <= bb.maxY(); y++) { + worker.accept(new BlockPos(bb.minX(), y, bb.minZ())); + worker.accept(new BlockPos(bb.maxX(), y, bb.minZ())); + worker.accept(new BlockPos(bb.minX(), y, bb.maxZ())); + worker.accept(new BlockPos(bb.maxX(), y, bb.maxZ())); + } + for (int z = bb.minZ(); z <= bb.maxZ(); z++) { + worker.accept(new BlockPos(bb.minX(), bb.minY(), z)); + worker.accept(new BlockPos(bb.maxX(), bb.minY(), z)); + worker.accept(new BlockPos(bb.minX(), bb.maxY(), z)); + worker.accept(new BlockPos(bb.maxX(), bb.maxY(), z)); + } + } }