From 6882afca7b2f38398ee72d5f84f8a8f446877aa3 Mon Sep 17 00:00:00 2001 From: Bukkit/Spigot Date: Tue, 5 Oct 2021 11:55:20 +1100 Subject: [PATCH] SPIGOT-2903: Add Structure API By: Sander Knauff --- .../src/main/java/org/bukkit/Bukkit.java | 11 + .../src/main/java/org/bukkit/Server.java | 9 + .../java/org/bukkit/structure/Palette.java | 33 +++ .../java/org/bukkit/structure/Structure.java | 146 +++++++++++++ .../bukkit/structure/StructureManager.java | 205 ++++++++++++++++++ .../org/bukkit/structure/package-info.java | 6 + 6 files changed, 410 insertions(+) create mode 100644 paper-api/src/main/java/org/bukkit/structure/Palette.java create mode 100644 paper-api/src/main/java/org/bukkit/structure/Structure.java create mode 100644 paper-api/src/main/java/org/bukkit/structure/StructureManager.java create mode 100644 paper-api/src/main/java/org/bukkit/structure/package-info.java diff --git a/paper-api/src/main/java/org/bukkit/Bukkit.java b/paper-api/src/main/java/org/bukkit/Bukkit.java index 6baaecacef..7fd1eb5bb1 100644 --- a/paper-api/src/main/java/org/bukkit/Bukkit.java +++ b/paper-api/src/main/java/org/bukkit/Bukkit.java @@ -46,6 +46,7 @@ import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.messaging.Messenger; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scoreboard.ScoreboardManager; +import org.bukkit.structure.StructureManager; import org.bukkit.util.CachedServerIcon; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -1683,6 +1684,16 @@ public final class Bukkit { return server.selectEntities(sender, selector); } + /** + * Gets the structure manager for loading and saving structures. + * + * @return the structure manager + */ + @NotNull + public static StructureManager getStructureManager() { + return server.getStructureManager(); + } + /** * @return the unsafe values instance * @see UnsafeValues diff --git a/paper-api/src/main/java/org/bukkit/Server.java b/paper-api/src/main/java/org/bukkit/Server.java index 1b53f54854..3fdabd26dc 100644 --- a/paper-api/src/main/java/org/bukkit/Server.java +++ b/paper-api/src/main/java/org/bukkit/Server.java @@ -47,6 +47,7 @@ import org.bukkit.plugin.messaging.Messenger; import org.bukkit.plugin.messaging.PluginMessageRecipient; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scoreboard.ScoreboardManager; +import org.bukkit.structure.StructureManager; import org.bukkit.util.CachedServerIcon; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -1426,6 +1427,14 @@ public interface Server extends PluginMessageRecipient { @NotNull List selectEntities(@NotNull CommandSender sender, @NotNull String selector) throws IllegalArgumentException; + /** + * Gets the structure manager for loading and saving structures. + * + * @return the structure manager + */ + @NotNull + StructureManager getStructureManager(); + /** * @return the unsafe values instance * @see UnsafeValues diff --git a/paper-api/src/main/java/org/bukkit/structure/Palette.java b/paper-api/src/main/java/org/bukkit/structure/Palette.java new file mode 100644 index 0000000000..883189e548 --- /dev/null +++ b/paper-api/src/main/java/org/bukkit/structure/Palette.java @@ -0,0 +1,33 @@ +package org.bukkit.structure; + +import java.util.List; +import org.bukkit.block.BlockState; +import org.jetbrains.annotations.NotNull; + +/** + * Represent a variation of a structure. + * + * Most structures, like the ones generated with structure blocks, only have a + * single variant. + */ +public interface Palette { + + /** + * Gets a copy of the blocks this Palette is made of. + * + * The {@link BlockState#getLocation() positions} of the returned block + * states are offsets relative to the structure's position that is provided + * once the structure is placed into the world. + * + * @return The blocks in this palette + */ + @NotNull + List getBlocks(); + + /** + * Gets the number of blocks stored in this palette. + * + * @return The number of blocks in this palette + */ + int getBlockCount(); +} diff --git a/paper-api/src/main/java/org/bukkit/structure/Structure.java b/paper-api/src/main/java/org/bukkit/structure/Structure.java new file mode 100644 index 0000000000..9ac52306c7 --- /dev/null +++ b/paper-api/src/main/java/org/bukkit/structure/Structure.java @@ -0,0 +1,146 @@ +package org.bukkit.structure; + +import java.util.List; +import java.util.Random; +import org.bukkit.Location; +import org.bukkit.RegionAccessor; +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.entity.Entity; +import org.bukkit.util.BlockVector; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a structure. + *

+ * A structure is a mutable template of captured blocks and entities that can be + * copied back into the world. The {@link StructureManager}, retrieved via + * {@link org.bukkit.Server#getStructureManager()}, allows you to create new + * structures, load existing structures, and save structures. + *

+ * In order for a structure to be usable by structure blocks, it needs to be + * null {@link StructureManager#registerStructure(org.bukkit.NamespacedKey, Structure) + * registered} with the {@link StructureManager}, or located in the primary + * world folder, a DataPack, or the server's own default resources, so that the + * StructureManager can find it. + */ +public interface Structure { + + /** + * Gets the current size of the structure. + *

+ * The size of the structure may not be fixed. + * + * @return A new vector that represents the size of the structure along each + * axis. + */ + @NotNull + BlockVector getSize(); + + /** + * Gets a list of available block palettes. + * + * @return a list of available variants of this structure. + */ + @NotNull + List getPalettes(); + + /** + * Gets the number of palettes in this structure. + * + * @return The number of palettes in this structure + */ + int getPaletteCount(); + + /** + * Gets a list of entities that have been included in the Structure. + * + * The entity positions are offsets relative to the structure's position + * that is provided once the structure is placed into the world. + * + * @return a list of Entities included in the Structure. + */ + @NotNull + List getEntities(); + + /** + * Gets the number of entities in this structure. + * + * @return The number of entities in this structure + */ + int getEntityCount(); + + /** + * Place a structure in the world. + * + * @param location The location to place the structure at. + * @param includeEntities If the entities present in the structure should be + * spawned. + * @param structureRotation The rotation of the structure. + * @param mirror The mirror settings of the structure. + * @param palette The palette index of the structure to use, starting at + * {@code 0}, or {@code -1} to pick a random palette. + * @param integrity Determines how damaged the building should look by + * randomly skipping blocks to place. This value can range from 0 to 1. With + * 0 removing all blocks and 1 spawning the structure in pristine condition. + * @param random The randomizer used for setting the structure's + * {@link org.bukkit.loot.LootTable}s and integrity. + */ + void place(@NotNull Location location, boolean includeEntities, @NotNull StructureRotation structureRotation, @NotNull Mirror mirror, int palette, float integrity, @NotNull Random random); + + /** + * Place a structure in the world. + * + * @param regionAccessor The world to place the structure in. + * @param location The location to place the structure at. + * @param includeEntities If the entities present in the structure should be + * spawned. + * @param structureRotation The rotation of the structure. + * @param mirror The mirror settings of the structure. + * @param palette The palette index of the structure to use, starting at + * {@code 0}, or {@code -1} to pick a random palette. + * @param integrity Determines how damaged the building should look by + * randomly skipping blocks to place. This value can range from 0 to 1. With + * 0 removing all blocks and 1 spawning the structure in pristine condition. + * @param random The randomizer used for setting the structure's + * {@link org.bukkit.loot.LootTable}s and integrity. + */ + void place(@NotNull RegionAccessor regionAccessor, @NotNull BlockVector location, boolean includeEntities, @NotNull StructureRotation structureRotation, @NotNull Mirror mirror, int palette, float integrity, @NotNull Random random); + + /** + * Fills the structure from an area in a world. The origin and size will be + * calculated automatically from the two corners provided. + *

+ * Be careful as this will override the current data of the structure. + *

+ * Be aware that this method allows for creating structures larger than the + * 48x48x48 size that Minecraft's Structure blocks support. Any structures + * saved this way can not be loaded by using a structure block. Using the + * API however will still work. + * + * @param corner1 A corner of the structure. + * @param corner2 The corner opposite from corner1. + * @param includeEntities true if entities should be included in the saved + * structure. + */ + void fill(@NotNull Location corner1, @NotNull Location corner2, boolean includeEntities); + + /** + * Fills the Structure from an area in a world, starting at the specified + * origin and extending in each axis according to the specified size vector. + *

+ * Be careful as this will override the current data of the structure. + *

+ * Be aware that this method allows for saving structures larger than the + * 48x48x48 size that Minecraft's Structure blocks support. Any structures + * saved this way can not be loaded by using a structure block. Using the + * API however will still work. + * + * @param origin The origin of the structure. + * @param size The size of the structure, must be at least 1x1x1. + * @param includeEntities true if entities should be included in the saved + * structure. + * @throws IllegalArgumentException Thrown if size is smaller than 1x1x1 + */ + void fill(@NotNull Location origin, @NotNull BlockVector size, boolean includeEntities); +} diff --git a/paper-api/src/main/java/org/bukkit/structure/StructureManager.java b/paper-api/src/main/java/org/bukkit/structure/StructureManager.java new file mode 100644 index 0000000000..78fb239ebc --- /dev/null +++ b/paper-api/src/main/java/org/bukkit/structure/StructureManager.java @@ -0,0 +1,205 @@ +package org.bukkit.structure; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface StructureManager { + + /** + * Gets the currently registered structures. + *

+ * These are the currently loaded structures that the StructureManager is + * aware of. When a structure block refers to a structure, these structures + * are checked first. If the specified structure is not found among the + * currently registered structures, the StructureManager may dynamically + * read the structure from the primary world folder, DataPacks, or the + * server's own resources. Structures can be registered via {@link + * #registerStructure(NamespacedKey, Structure)} + * + * @return an unmodifiable shallow copy of the currently registered + * structures + */ + @NotNull + Map getStructures(); + + /** + * Gets a registered Structure. + * + * @param structureKey The key for which to get the structure + * @return The structure that belongs to the structureKey or + * null if there is none registered for that key. + */ + @Nullable + Structure getStructure(@NotNull NamespacedKey structureKey); + + /** + * Registers the given structure. See {@link #getStructures()}. + * + * @param structureKey The key for which to register the structure + * @param structure The structure to register + * @return The structure for the specified key, or null if the + * structure could not be found. + */ + @Nullable + Structure registerStructure(@NotNull NamespacedKey structureKey, @NotNull Structure structure); + + /** + * Unregisters a structure. Unregisters the specified structure. If the + * structure still exists in the primary world folder, a DataPack, or is + * part of the server's own resources, it may be loaded and registered again + * when it is requested by a plugin or the server itself. + * + * @param structureKey The key for which to save the structure for + * @return The structure that was registered for that key or + * null if there was none + */ + @Nullable + Structure unregisterStructure(@NotNull NamespacedKey structureKey); + + /** + * Loads a structure for the specified key and optionally {@link + * #registerStructure(NamespacedKey, Structure) registers} it. + *

+ * This will first check the already loaded {@link #getStructures() + * registered structures}, and otherwise load the structure from the primary + * world folder, DataPacks, and the server's own resources (in this order). + *

+ * When loading the structure from the primary world folder, the given key + * is translated to a file as specified by + * {@link #getStructureFile(NamespacedKey)}. + * + * @param structureKey The key for which to load the structure + * @param register true to register the loaded structure. + * @return The structure, or null if no structure was found for + * the specified key + */ + @Nullable + Structure loadStructure(@NotNull NamespacedKey structureKey, boolean register); + + /** + * Loads the structure for the specified key and automatically registers it. + * See {@link #loadStructure(NamespacedKey, boolean)}. + * + * @param structureKey The key for which to load the structure + * @return The structure for the specified key, or null if the + * structure could not be found. + */ + @Nullable + Structure loadStructure(@NotNull NamespacedKey structureKey); + + /** + * Saves the currently {@link #getStructures() registered structure} for the + * specified {@link NamespacedKey key} to the primary world folder as + * specified by {#getStructureFile(NamespacedKey}. + * + * @param structureKey The key for which to save the structure for + */ + void saveStructure(@NotNull NamespacedKey structureKey); + + /** + * Saves a structure with a given key to the primary world folder. + * + * @param structureKey The key for which to save the structure for + * @param structure The structure to save for this structureKey + */ + void saveStructure(@NotNull NamespacedKey structureKey, @NotNull Structure structure) throws IOException; + + /** + * Unregisters the specified structure and deletes its {@link + * #getStructureFile(NamespacedKey) structure file} from the primary world + * folder. Note that this method cannot be used to delete vanilla Minecraft + * structures, or structures from DataPacks. Unregistering these structures + * will however work fine. + * + * @param structureKey The key of the structure to remove + * @throws IOException If the file could not be removed for some reason. + */ + void deleteStructure(@NotNull NamespacedKey structureKey) throws IOException; + + /** + * Deletes the {@link #getStructureFile(NamespacedKey) structure file} for + * the specified structure from the primary world folder. Note that this + * method cannot be used to delete vanilla Minecraft structures, or + * structures from DataPacks. Unregistering these structures will however + * work fine. + * + * @param structureKey The key of the structure to remove + * @param unregister Whether to also unregister the specified structure if + * it is currently loaded. + * @throws IOException If the file could not be removed for some reason. + */ + void deleteStructure(@NotNull NamespacedKey structureKey, boolean unregister) throws IOException; + + /** + * Gets the location where a structure file would exist in the primary world + * directory based on the NamespacedKey using the format + * world/generated/{NAMESPACE}/structures/{KEY}.nbt. This method will always + * return a file, even if none exists at the moment. + * + * @param structureKey The key to build the filepath from. + * @return The location where a file with this key would be. + */ + @NotNull + File getStructureFile(@NotNull NamespacedKey structureKey); + + /** + * Reads a Structure from disk. + * + * @param file The file of the structure + * @return The read structure + * @throws IOException when the given file can not be read from + */ + @NotNull + Structure loadStructure(@NotNull File file) throws IOException; + + /** + * Reads a Structure from a stream. + * + * @param inputStream The file of the structure + * @return The read Structure + */ + @NotNull + Structure loadStructure(@NotNull InputStream inputStream) throws IOException; + + /** + * Save a structure to a file. This will overwrite a file if it already + * exists. + * + * @param file the target to save to. + * @param structure the Structure to save. + * @throws IOException when the given file can not be written to. + */ + void saveStructure(@NotNull File file, @NotNull Structure structure) throws IOException; + + /** + * Save a structure to a stream. + * + * @param outputStream the stream to write to. + * @param structure the Structure to save. + * @throws IOException when the given file can not be written to. + */ + void saveStructure(@NotNull OutputStream outputStream, @NotNull Structure structure) throws IOException; + + /** + * Creates a new empty structure. + * + * @return an empty structure. + */ + @NotNull + Structure createStructure(); + + /** + * Creates a copy of this structure. + * + * @param structure The structure to copy + * @return a copy of the structure + */ + @NotNull + Structure copy(@NotNull Structure structure); +} diff --git a/paper-api/src/main/java/org/bukkit/structure/package-info.java b/paper-api/src/main/java/org/bukkit/structure/package-info.java new file mode 100644 index 0000000000..6f1f235276 --- /dev/null +++ b/paper-api/src/main/java/org/bukkit/structure/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes related to creating or using {@link org.bukkit.structure.Structure + * structures} without creating {@link org.bukkit.block.Structure Structure + * blocks} in the world. + */ +package org.bukkit.structure;