diff --git a/paper-server/nms-patches/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.patch b/paper-server/nms-patches/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.patch index 1a380a21a3..1a76b79a57 100644 --- a/paper-server/nms-patches/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.patch +++ b/paper-server/nms-patches/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.patch @@ -1,5 +1,16 @@ --- a/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.java +++ b/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructure.java +@@ -64,8 +64,8 @@ + public static final String ENTITY_TAG_NBT = "nbt"; + public static final String SIZE_TAG = "size"; + static final int CHUNK_SIZE = 16; +- private final List palettes = Lists.newArrayList(); +- private final List entityInfoList = Lists.newArrayList(); ++ public final List palettes = Lists.newArrayList(); // PAIL private->public ++ public final List entityInfoList = Lists.newArrayList(); // PAIL private->public + private BaseBlockPosition size; + private String author; + @@ -147,7 +147,7 @@ } diff --git a/paper-server/nms-patches/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructureInfo.patch b/paper-server/nms-patches/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructureInfo.patch new file mode 100644 index 0000000000..ba774c0ea0 --- /dev/null +++ b/paper-server/nms-patches/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructureInfo.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructureInfo.java ++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructureInfo.java +@@ -24,7 +24,7 @@ + @Nullable + private Random random; + @Nullable +- private int palette; ++ public int palette = -1; // CraftBukkit - Set initial value so we know if the palette has been set forcefully + private final List processors; + private boolean knownShape; + private boolean finalizeEntities; +@@ -151,6 +151,13 @@ + + if (i == 0) { + throw new IllegalStateException("No palettes"); ++ // CraftBukkit start ++ } else if (this.palette > 0) { ++ if (this.palette >= i) { ++ throw new IllegalArgumentException("Palette index out of bounds. Got " + this.palette + " where there are only " + i + " palettes available."); ++ } ++ return list.get(this.palette); ++ // CraftBukkit end + } else { + return (DefinedStructure.a) list.get(this.b(blockposition).nextInt(i)); + } diff --git a/paper-server/nms-patches/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructureManager.patch b/paper-server/nms-patches/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructureManager.patch new file mode 100644 index 0000000000..1d87b5496b --- /dev/null +++ b/paper-server/nms-patches/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructureManager.patch @@ -0,0 +1,47 @@ +--- a/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructureManager.java ++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/DefinedStructureManager.java +@@ -34,7 +34,7 @@ + private static final String STRUCTURE_DIRECTORY_NAME = "structures"; + private static final String STRUCTURE_FILE_EXTENSION = ".nbt"; + private static final String STRUCTURE_TEXT_FILE_EXTENSION = ".snbt"; +- private final Map> structureRepository = Maps.newConcurrentMap(); ++ public final Map> structureRepository = Maps.newConcurrentMap(); // PAIL private->public + private final DataFixer fixerUpper; + private IResourceManager resourceManager; + private final Path generatedDir; +@@ -71,7 +71,7 @@ + this.structureRepository.clear(); + } + +- private Optional e(MinecraftKey minecraftkey) { ++ public Optional e(MinecraftKey minecraftkey) { // PAIL private->public + MinecraftKey minecraftkey1 = new MinecraftKey(minecraftkey.getNamespace(), "structures/" + minecraftkey.getKey() + ".nbt"); + + try { +@@ -106,7 +106,7 @@ + } + } + +- private Optional f(MinecraftKey minecraftkey) { ++ public Optional f(MinecraftKey minecraftkey) { // PAIL private->public + if (!this.generatedDir.toFile().isDirectory()) { + return Optional.empty(); + } else { +@@ -140,7 +140,7 @@ + } + } + +- private DefinedStructure a(InputStream inputstream) throws IOException { ++ public DefinedStructure a(InputStream inputstream) throws IOException { //PAIL rename loadFromStream; private -> public + NBTTagCompound nbttagcompound = NBTCompressedStreamTools.a(inputstream); + + return this.a(nbttagcompound); +@@ -214,7 +214,7 @@ + } + } + +- private Path b(MinecraftKey minecraftkey, String s) { ++ public Path b(MinecraftKey minecraftkey, String s) { //PAIL private->public + if (minecraftkey.getKey().contains("//")) { + throw new ResourceKeyInvalidException("Invalid resource path: " + minecraftkey); + } else { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index a483c1c053..26a63be2a4 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -183,6 +183,7 @@ import org.bukkit.craftbukkit.metadata.WorldMetadataStore; import org.bukkit.craftbukkit.potion.CraftPotionBrewer; import org.bukkit.craftbukkit.scheduler.CraftScheduler; import org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager; +import org.bukkit.craftbukkit.structure.CraftStructureManager; import org.bukkit.craftbukkit.tag.CraftBlockTag; import org.bukkit.craftbukkit.tag.CraftEntityTag; import org.bukkit.craftbukkit.tag.CraftFluidTag; @@ -238,6 +239,7 @@ import org.bukkit.plugin.messaging.StandardMessenger; import org.bukkit.potion.Potion; import org.bukkit.potion.PotionEffectType; import org.bukkit.scheduler.BukkitWorker; +import org.bukkit.structure.StructureManager; import org.bukkit.util.StringUtil; import org.bukkit.util.permissions.DefaultPermissions; import org.yaml.snakeyaml.Yaml; @@ -255,6 +257,7 @@ public final class CraftServer implements Server { private final SimpleHelpMap helpMap = new SimpleHelpMap(this); private final StandardMessenger messenger = new StandardMessenger(); private final SimplePluginManager pluginManager = new SimplePluginManager(this, commandMap); + private final StructureManager structureManager; protected final DedicatedServer console; protected final DedicatedPlayerList playerList; private final Map worlds = new LinkedHashMap(); @@ -298,6 +301,7 @@ public final class CraftServer implements Server { } })); this.serverVersion = CraftServer.class.getPackage().getImplementationVersion(); + this.structureManager = new CraftStructureManager(console.getDefinedStructureManager()); Bukkit.setServer(this); @@ -2199,6 +2203,11 @@ public final class CraftServer implements Server { return new ArrayList<>(Lists.transform(nms, (entity) -> entity.getBukkitEntity())); } + @Override + public StructureManager getStructureManager() { + return structureManager; + } + @Deprecated @Override public UnsafeValues getUnsafe() { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/structure/CraftPalette.java b/paper-server/src/main/java/org/bukkit/craftbukkit/structure/CraftPalette.java new file mode 100644 index 0000000000..4402e611ab --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/structure/CraftPalette.java @@ -0,0 +1,31 @@ +package org.bukkit.craftbukkit.structure; + +import java.util.ArrayList; +import java.util.List; +import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructure; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.block.CraftBlockStates; +import org.bukkit.structure.Palette; + +public class CraftPalette implements Palette { + + private final DefinedStructure.a palette; + + public CraftPalette(DefinedStructure.a palette) { + this.palette = palette; + } + + @Override + public List getBlocks() { + List blocks = new ArrayList<>(); + for (DefinedStructure.BlockInfo blockInfo : palette.a()) { + blocks.add(CraftBlockStates.getBlockState(blockInfo.pos, blockInfo.state, blockInfo.nbt)); + } + return blocks; + } + + @Override + public int getBlockCount() { + return palette.a().size(); + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/structure/CraftStructure.java b/paper-server/src/main/java/org/bukkit/craftbukkit/structure/CraftStructure.java new file mode 100644 index 0000000000..315be27a04 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/structure/CraftStructure.java @@ -0,0 +1,128 @@ +package org.bukkit.craftbukkit.structure; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import net.minecraft.core.BlockPosition; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.level.block.EnumBlockMirror; +import net.minecraft.world.level.block.EnumBlockRotation; +import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructure; +import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructureInfo; +import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructureProcessorRotation; +import org.apache.commons.lang3.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.RegionAccessor; +import org.bukkit.World; +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.craftbukkit.CraftRegionAccessor; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.entity.Entity; +import org.bukkit.structure.Palette; +import org.bukkit.structure.Structure; +import org.bukkit.util.BlockVector; + +public class CraftStructure implements Structure { + + private final DefinedStructure structure; + + public CraftStructure(DefinedStructure structure) { + this.structure = structure; + } + + @Override + public void place(Location location, boolean includeEntities, StructureRotation structureRotation, Mirror mirror, int palette, float integrity, Random random) { + location.checkFinite(); + World world = location.getWorld(); + Validate.notNull(world, "location#getWorld() cannot be null"); + + BlockVector blockVector = new BlockVector(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + place(world, blockVector, includeEntities, structureRotation, mirror, palette, integrity, random); + } + + @Override + public void place(RegionAccessor regionAccessor, BlockVector location, boolean includeEntities, StructureRotation structureRotation, Mirror mirror, int palette, float integrity, Random random) { + Validate.notNull(regionAccessor, "regionAccessor can not be null"); + location.checkFinite(); + + if (integrity < 0F || integrity > 1F) { + throw new IllegalArgumentException("Integrity must be between 0 and 1 inclusive. Was \"" + integrity + "\""); + } + + DefinedStructureInfo definedstructureinfo = new DefinedStructureInfo() + .a(EnumBlockMirror.valueOf(mirror.name())) // PAIL rename setMirror + .a(EnumBlockRotation.valueOf(structureRotation.name())) // PAIL rename setRotation + .a(!includeEntities) // PAIL rename setIgnoreEntities + .a(new DefinedStructureProcessorRotation(integrity)) // PAIL rename addStructureProcessor + .a(random); // PAIL rename setRandom + definedstructureinfo.palette = palette; + + BlockPosition blockPosition = new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + structure.a(((CraftRegionAccessor) regionAccessor).getHandle(), blockPosition, blockPosition, definedstructureinfo, random, 2); // PAIL rename placeInWorld + } + + @Override + public void fill(Location corner1, Location corner2, boolean includeEntities) { + Validate.notNull(corner1, "corner1 cannot be null"); + Validate.notNull(corner2, "corner2 cannot be null"); + World world = corner1.getWorld(); + Validate.notNull(world, "corner1#getWorld() cannot be null"); + + Location origin = new Location(world, Math.min(corner1.getBlockX(), corner2.getBlockX()), Math.min(corner1.getBlockY(), corner2.getBlockY()), Math.min(corner1.getBlockZ(), corner2.getBlockZ())); + BlockVector size = new BlockVector(Math.abs(corner1.getBlockX() - corner2.getBlockX()), Math.abs(corner1.getBlockY() - corner2.getBlockY()), Math.abs(corner1.getBlockZ() - corner2.getBlockZ())); + fill(origin, size, includeEntities); + } + + @Override + public void fill(Location origin, BlockVector size, boolean includeEntities) { + Validate.notNull(origin, "origin cannot be null"); + World world = origin.getWorld(); + Validate.notNull(world, "origin#getWorld() cannot be null"); + Validate.notNull(size, "size cannot be null"); + if (size.getBlockX() < 1 || size.getBlockY() < 1 || size.getBlockZ() < 1) { + throw new IllegalArgumentException("Size must be at least 1x1x1 but was " + size.getBlockX() + "x" + size.getBlockY() + "x" + size.getBlockZ()); + } + + structure.a(((CraftWorld) world).getHandle(), new BlockPosition(origin.getBlockX(), origin.getBlockY(), origin.getBlockZ()), new BlockPosition(size.getBlockX(), size.getBlockY(), size.getBlockZ()), includeEntities, null); // PAIL rename fillFromWorld + } + + @Override + public BlockVector getSize() { + return new BlockVector(structure.a().getX(), structure.a().getY(), structure.a().getZ()); + } + + @Override + public List getEntities() { + List entities = new ArrayList<>(); + for (DefinedStructure.EntityInfo entity : structure.entityInfoList) { + EntityTypes.a(entity.nbt, ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle()).ifPresent(dummyEntity -> { + dummyEntity.setPosition(entity.pos.x, entity.pos.y, entity.pos.z); + entities.add(dummyEntity.getBukkitEntity()); + }); + } + return Collections.unmodifiableList(entities); + } + + @Override + public int getEntityCount() { + return structure.entityInfoList.size(); + } + + @Override + public List getPalettes() { + return structure.palettes.stream().map(CraftPalette::new).collect(Collectors.toList()); + } + + @Override + public int getPaletteCount() { + return structure.palettes.size(); + } + + public DefinedStructure getHandle() { + return structure; + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/structure/CraftStructureManager.java b/paper-server/src/main/java/org/bukkit/craftbukkit/structure/CraftStructureManager.java new file mode 100644 index 0000000000..7748f80d32 --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/structure/CraftStructureManager.java @@ -0,0 +1,185 @@ +package org.bukkit.craftbukkit.structure; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import net.minecraft.nbt.NBTCompressedStreamTools; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.resources.MinecraftKey; +import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructure; +import net.minecraft.world.level.levelgen.structure.templatesystem.DefinedStructureManager; +import org.apache.commons.lang3.Validate; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.structure.Structure; +import org.bukkit.structure.StructureManager; + +public class CraftStructureManager implements StructureManager { + + private final DefinedStructureManager structureManager; + + public CraftStructureManager(DefinedStructureManager structureManager) { + this.structureManager = structureManager; + } + + @Override + public Map getStructures() { + Map cachedStructures = new HashMap<>(); + for (Map.Entry> entry : structureManager.structureRepository.entrySet()) { + entry.getValue().ifPresent(definedStructure -> { + cachedStructures.put(CraftNamespacedKey.fromMinecraft(entry.getKey()), new CraftStructure(definedStructure)); + }); + } + return Collections.unmodifiableMap(cachedStructures); + } + + @Override + public Structure getStructure(NamespacedKey structureKey) { + Validate.notNull(structureKey, "structureKey cannot be null"); + + final Optional definedStructure = structureManager.structureRepository.get(CraftNamespacedKey.toMinecraft(structureKey)); + if (definedStructure == null) { + return null; + } + return definedStructure.map(CraftStructure::new).orElse(null); + } + + @Override + public Structure loadStructure(NamespacedKey structureKey, boolean register) { + MinecraftKey minecraftKey = createAndValidateMinecraftStructureKey(structureKey); + + Optional structure = structureManager.structureRepository.get(minecraftKey); + structure = structure == null ? Optional.empty() : structure; + structure = structure.isPresent() ? structure : structureManager.f(minecraftKey); + structure = structure.isPresent() ? structure : structureManager.e(minecraftKey); + + if (register) { + structureManager.structureRepository.put(minecraftKey, structure); + } + + return structure.map(CraftStructure::new).orElse(null); + } + + @Override + public Structure loadStructure(NamespacedKey structureKey) { + return loadStructure(structureKey, true); + } + + @Override + public void saveStructure(NamespacedKey structureKey) { + MinecraftKey minecraftKey = createAndValidateMinecraftStructureKey(structureKey); + + structureManager.c(minecraftKey); // PAIL rename save + } + + @Override + public void saveStructure(NamespacedKey structureKey, Structure structure) throws IOException { + Validate.notNull(structure, "structure cannot be null"); + + File structureFile = getStructureFile(structureKey); + Files.createDirectories(structureFile.toPath().getParent()); + saveStructure(structureFile, structure); + } + + @Override + public Structure registerStructure(NamespacedKey structureKey, Structure structure) { + Validate.notNull(structure, "structure cannot be null"); + MinecraftKey minecraftKey = createAndValidateMinecraftStructureKey(structureKey); + + final Optional optionalDefinedStructure = Optional.of(((CraftStructure) structure).getHandle()); + final Optional previousStructure = structureManager.structureRepository.put(minecraftKey, optionalDefinedStructure); + return previousStructure == null ? null : previousStructure.map(CraftStructure::new).orElse(null); + } + + @Override + public Structure unregisterStructure(NamespacedKey structureKey) { + MinecraftKey minecraftKey = createAndValidateMinecraftStructureKey(structureKey); + + final Optional previousStructure = structureManager.structureRepository.remove(minecraftKey); + return previousStructure == null ? null : previousStructure.map(CraftStructure::new).orElse(null); + } + + @Override + public void deleteStructure(NamespacedKey structureKey) throws IOException { + deleteStructure(structureKey, true); + } + + @Override + public void deleteStructure(NamespacedKey structureKey, boolean unregister) throws IOException { + MinecraftKey key = CraftNamespacedKey.toMinecraft(structureKey); + + if (unregister) { + structureManager.structureRepository.remove(key); + } + Path path = structureManager.b(key, ".nbt"); + Files.deleteIfExists(path); + } + + @Override + public File getStructureFile(NamespacedKey structureKey) { + MinecraftKey minecraftKey = createAndValidateMinecraftStructureKey(structureKey); + return structureManager.b(minecraftKey, ".nbt").toFile(); + } + + @Override + public Structure loadStructure(File file) throws IOException { + Validate.notNull(file, "file cannot be null"); + + FileInputStream fileinputstream = new FileInputStream(file); + return loadStructure(fileinputstream); + } + + @Override + public Structure loadStructure(InputStream inputStream) throws IOException { + Validate.notNull(inputStream, "inputStream cannot be null"); + + return new CraftStructure(structureManager.a(inputStream)); + } + + @Override + public void saveStructure(File file, Structure structure) throws IOException { + Validate.notNull(file, "file cannot be null"); + Validate.notNull(structure, "structure cannot be null"); + + FileOutputStream fileoutputstream = new FileOutputStream(file); + saveStructure(fileoutputstream, structure); + } + + @Override + public void saveStructure(OutputStream outputStream, Structure structure) throws IOException { + Validate.notNull(outputStream, "outputStream cannot be null"); + Validate.notNull(structure, "structure cannot be null"); + + NBTTagCompound nbttagcompound = ((CraftStructure) structure).getHandle().a(new NBTTagCompound()); + NBTCompressedStreamTools.a(nbttagcompound, outputStream); + } + + @Override + public Structure createStructure() { + return new CraftStructure(new DefinedStructure()); + } + + private MinecraftKey createAndValidateMinecraftStructureKey(NamespacedKey structureKey) { + Validate.notNull(structureKey, "structureKey cannot be null"); + + MinecraftKey minecraftkey = CraftNamespacedKey.toMinecraft(structureKey); + if (minecraftkey.getKey().contains("//")) { + throw new IllegalArgumentException("Resource key for Structures can not contain \"//\""); + } + return minecraftkey; + } + + @Override + public Structure copy(Structure structure) { + return new CraftStructure(structureManager.a(((CraftStructure) structure).getHandle().a(new NBTTagCompound()))); + } +}