diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java index 3bf23002..e1f7e821 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/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; @@ -451,6 +452,27 @@ public final class Bukkit { public static int getTicksPerWaterAmbientSpawns() { return server.getTicksPerAmbientSpawns(); } + /** + * Gets the default ticks per water underground creature spawns value. + *

+ * Example Usage: + *

+ *

+ * Note: If set to 0, water underground creature spawning will be disabled. + *

+ * Minecraft default: 1. + * + * @return the default ticks per water underground creature spawn value + */ + public static int getTicksPerWaterUndergroundCreatureSpawns() { + return server.getTicksPerWaterUndergroundCreatureSpawns(); + } /** * Gets a player object by the given username. @@ -1234,6 +1256,15 @@ public final class Bukkit { return server.getAmbientSpawnLimit(); } + /** + * Get user-specified limit for number of water creature underground that can spawn + * in a chunk. + * @return the water underground creature limit + */ + public static int getWaterUndergroundCreatureSpawnLimit() { + return server.getWaterUndergroundCreatureSpawnLimit(); + } + /** * Gets user-specified limit for number of ambient mobs that can spawn in * a chunk. @@ -1653,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(); + } + /** * Returns the registry for the given class. *
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java index ab123372..fda80f21 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/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; @@ -361,6 +362,26 @@ public interface Server extends PluginMessageRecipient { */ public int getTicksPerWaterAmbientSpawns(); + /** + * Gets the default ticks per water underground creature spawns value. + *

+ * Example Usage: + *

+ *

+ * Note: If set to 0, water underground creature spawning will be disabled. + *

+ * Minecraft default: 1. + * + * @return the default ticks per water underground creature spawn value + */ + public int getTicksPerWaterUndergroundCreatureSpawns(); + /** * Gets the default ticks per ambient mob spawns value. *

@@ -1035,6 +1056,13 @@ public interface Server extends PluginMessageRecipient { */ int getWaterAmbientSpawnLimit(); + /** + * Get user-specified limit for number of water creature underground that can spawn + * in a chunk. + * @return the water underground creature limit + */ + int getWaterUndergroundCreatureSpawnLimit(); + /** * Gets user-specified limit for number of ambient mobs that can spawn in * a chunk. @@ -1399,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(); + /** * Returns the registry for the given class. *
diff --git a/src/main/java/org/bukkit/Tag.java b/src/main/java/org/bukkit/Tag.java index 53b66c28..c86ac9f4 100644 --- a/src/main/java/org/bukkit/Tag.java +++ b/src/main/java/org/bukkit/Tag.java @@ -1,6 +1,7 @@ package org.bukkit; import java.util.Set; +import org.bukkit.entity.EntityType; import org.jetbrains.annotations.NotNull; /** @@ -585,6 +586,50 @@ public interface Tag extends Keyed { * Vanilla fluid tag representing water and flowing water. */ Tag FLUIDS_WATER = Bukkit.getTag(REGISTRY_FLUIDS, NamespacedKey.minecraft("water"), Fluid.class); + /** + * Key for the built in entity registry. + */ + String REGISTRY_ENTITY_TYPES = "entity_types"; + /** + * Vanilla tag representing skeletons. + */ + Tag ENTITY_TYPES_SKELETONS = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("skeletons"), EntityType.class); + /** + * Vanilla tag representing raiders. + */ + Tag ENTITY_TYPES_RAIDERS = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("raiders"), EntityType.class); + /** + * Vanilla tag representing entities which can live in beehives. + */ + Tag ENTITY_TYPES_BEEHIVE_INHABITORS = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("beehive_inhabitors"), EntityType.class); + /** + * Vanilla tag representing arrows. + */ + Tag ENTITY_TYPES_ARROWS = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("arrows"), EntityType.class); + /** + * Vanilla tag representing projectiles. + */ + Tag ENTITY_TYPES_IMPACT_PROJECTILES = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("impact_projectiles"), EntityType.class); + /** + * Vanilla tag representing mobs which can walk on powder snow. + */ + Tag ENTITY_TYPES_POWDER_SNOW_WALKABLE_MOBS = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("powder_snow_walkable_mobs"), EntityType.class); + /** + * Vanilla tag representing which entities axolotls are always hostile to. + */ + Tag ENTITY_TYPES_AXOLOTL_ALWAYS_HOSTILES = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("axolotl_always_hostiles"), EntityType.class); + /** + * Vanilla tag representing axolotl targets. + */ + Tag ENTITY_TYPES_AXOLOTL_HUNT_TARGETS = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("axolotl_hunt_targets"), EntityType.class); + /** + * Vanilla tag representing entities immune from freezing. + */ + Tag ENTITY_TYPES_FREEZE_IMMUNE_ENTITY_TYPES = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("freeze_immune_entity_types"), EntityType.class); + /** + * Vanilla tag representing entities extra susceptible to freezing. + */ + Tag ENTITY_TYPES_FREEZE_HURTS_EXTRA_TYPES = Bukkit.getTag(REGISTRY_ENTITY_TYPES, NamespacedKey.minecraft("freeze_hurts_extra_types"), EntityType.class); /** * Returns whether or not this tag has an entry for the specified item. diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java index 64062724..20e8e212 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -1805,6 +1805,51 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient */ public void setTicksPerWaterAmbientSpawns(int ticksPerAmbientSpawns); + /** + * Gets the default ticks per water underground creature spawns value. + *

+ * Example Usage: + *

+ *

+ * Note: If set to 0, water underground creature spawning will be disabled. + *

+ * Minecraft default: 1. + * + * @return the default ticks per water underground creature spawn value + */ + public long getTicksPerWaterUndergroundCreatureSpawns(); + + /** + * Sets the world's ticks per water underground creature spawns value + *

+ * This value determines how many ticks there are between attempts to + * spawn water underground creature. + *

+ * Example Usage: + *

+ *

+ * Note: + * If set to 0, water underground creature spawning will be disabled for this world. + *

+ * Minecraft default: 1. + * + * @param ticksPerWaterUndergroundCreatureSpawns the ticks per water underground creature spawns value you + * want to set the world to + */ + public void setTicksPerWaterUndergroundCreatureSpawns(int ticksPerWaterUndergroundCreatureSpawns); + /** * Gets the world's ticks per ambient mob spawns value *

@@ -1911,6 +1956,25 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient */ void setWaterAnimalSpawnLimit(int limit); + /** + * Gets the limit for number of water underground creature that can spawn in a chunk in + * this world + * + * @return The water underground creature spawn limit + */ + int getWaterUndergroundCreatureSpawnLimit(); + + /** + * Sets the limit for number of water underground creature that can spawn in a chunk in + * this world + *

+ * Note: If set to a negative number the world will use the + * server-wide spawn limit instead. + * + * @param limit the new mob limit + */ + void setWaterUndergroundCreatureSpawnLimit(int limit); + /** * Gets user-specified limit for number of water ambient mobs that can spawn * in a chunk. diff --git a/src/main/java/org/bukkit/command/defaults/HelpCommand.java b/src/main/java/org/bukkit/command/defaults/HelpCommand.java index 98027fdd..80ffb719 100644 --- a/src/main/java/org/bukkit/command/defaults/HelpCommand.java +++ b/src/main/java/org/bukkit/command/defaults/HelpCommand.java @@ -122,6 +122,9 @@ public class HelpCommand extends BukkitCommand { List matchedTopics = new ArrayList(); String searchString = args[0]; for (HelpTopic topic : Bukkit.getServer().getHelpMap().getHelpTopics()) { + if (!topic.canSee(sender)) { + continue; + } String trimmedTopic = topic.getName().startsWith("/") ? topic.getName().substring(1) : topic.getName(); if (trimmedTopic.startsWith(searchString)) { diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java index e886e970..cd4599b0 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java @@ -377,6 +377,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void stopSound(@NotNull String sound, @Nullable SoundCategory category); + /** + * Stop all sounds from playing. + */ + public void stopAllSounds(); + /** * Plays an effect to just this player. * diff --git a/src/main/java/org/bukkit/event/block/BlockBreakEvent.java b/src/main/java/org/bukkit/event/block/BlockBreakEvent.java index c05cd9ca..691733a6 100644 --- a/src/main/java/org/bukkit/event/block/BlockBreakEvent.java +++ b/src/main/java/org/bukkit/event/block/BlockBreakEvent.java @@ -49,18 +49,25 @@ public class BlockBreakEvent extends BlockExpEvent implements Cancellable { } /** - * Sets whether or not the block will drop items as it normally would. + * Sets whether or not the block will attempt to drop items as it normally + * would. * - * @param dropItems Whether or not the block will drop items + * If and only if this is false then {@link BlockDropItemEvent} will not be + * called after this event. + * + * @param dropItems Whether or not the block will attempt to drop items */ public void setDropItems(boolean dropItems) { this.dropItems = dropItems; } /** - * Gets whether or not the block will drop items. + * Gets whether or not the block will attempt to drop items. * - * @return Whether or not the block will drop items + * If and only if this is false then {@link BlockDropItemEvent} will not be + * called after this event. + * + * @return Whether or not the block will attempt to drop items */ public boolean isDropItems() { return this.dropItems; diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java index 196e6d2a..07538e1b 100644 --- a/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java +++ b/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java @@ -260,9 +260,10 @@ public class EntityDamageEvent extends EntityEvent implements Cancellable { public enum DamageCause { /** - * Damage caused when an entity contacts a block such as a Cactus. + * Damage caused when an entity contacts a block such as a Cactus, + * Dripstone (Stalagmite) or Berry Bush. *

- * Damage: 1 (Cactus) + * Damage: variable */ CONTACT, /** @@ -351,9 +352,12 @@ public class EntityDamageEvent extends EntityEvent implements Cancellable { */ LIGHTNING, /** - * Damage caused by committing suicide using the command "/kill" + * Damage caused by committing suicide. *

- * Damage: 1000 + * Note: This is currently only used by plugins, default commands + * like /minecraft:kill use {@link #VOID} to damage players. + *

+ * Damage: variable */ SUICIDE, /** diff --git a/src/main/java/org/bukkit/event/inventory/BrewEvent.java b/src/main/java/org/bukkit/event/inventory/BrewEvent.java index 8fbfad91..f37cc5de 100644 --- a/src/main/java/org/bukkit/event/inventory/BrewEvent.java +++ b/src/main/java/org/bukkit/event/inventory/BrewEvent.java @@ -1,10 +1,12 @@ package org.bukkit.event.inventory; +import java.util.List; import org.bukkit.block.Block; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.block.BlockEvent; import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; /** @@ -14,18 +16,23 @@ import org.jetbrains.annotations.NotNull; public class BrewEvent extends BlockEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private BrewerInventory contents; + private final List results; private int fuelLevel; private boolean cancelled; - public BrewEvent(@NotNull Block brewer, @NotNull BrewerInventory contents, int fuelLevel) { + public BrewEvent(@NotNull Block brewer, @NotNull BrewerInventory contents, @NotNull List results, int fuelLevel) { super(brewer); this.contents = contents; + this.results = results; this.fuelLevel = fuelLevel; } /** * Gets the contents of the Brewing Stand. * + * Note: The brewer inventory still holds the items found prior to + * the finalization of the brewing process, e.g. the plain water bottles. + * * @return the contents */ @NotNull @@ -42,6 +49,21 @@ public class BrewEvent extends BlockEvent implements Cancellable { return fuelLevel; } + /** + * Gets the resulting items in the Brewing Stand. + * + * The returned list, in case of a server-created event instance, is + * mutable. Any changes in the returned list will reflect in the brewing + * result if the event is not cancelled. If the size of the list is reduced, + * remaining items will be set to air. + * + * @return List of {@link ItemStack} resulting for this operation + */ + @NotNull + public List getResults() { + return results; + } + @Override public boolean isCancelled() { return cancelled; diff --git a/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java b/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java index a6f91166..57eeeafa 100644 --- a/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java +++ b/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java @@ -8,7 +8,7 @@ import org.jetbrains.annotations.Nullable; /** * Called when a player is about to teleport because it is in contact with a - * portal. + * portal which will generate an exit portal. *

* For other entities see {@link org.bukkit.event.entity.EntityPortalEvent} */ diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java index 993a8c02..6bdd9f1d 100644 --- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java @@ -101,7 +101,12 @@ final class PluginClassLoader extends URLClassLoader { Class loadClass0(@NotNull String name, boolean resolve, boolean checkGlobal, boolean checkLibraries) throws ClassNotFoundException { try { - return super.loadClass(name, resolve); + Class result = super.loadClass(name, resolve); + + // SPIGOT-6749: Library classes will appear in the above, but we don't want to return them to other plugins + if (checkGlobal || result.getClassLoader() == this) { + return result; + } } catch (ClassNotFoundException ex) { } diff --git a/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java b/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java index 3c074079..e4f05975 100644 --- a/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java +++ b/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java @@ -14,7 +14,7 @@ public interface ScoreboardManager { * This Scoreboard is saved by the server, is affected by the /scoreboard * command, and is the scoreboard shown by default to players. * - * @return the default sever scoreboard + * @return the default server scoreboard */ @NotNull Scoreboard getMainScoreboard(); diff --git a/src/main/java/org/bukkit/structure/Palette.java b/src/main/java/org/bukkit/structure/Palette.java new file mode 100644 index 00000000..883189e5 --- /dev/null +++ b/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/src/main/java/org/bukkit/structure/Structure.java b/src/main/java/org/bukkit/structure/Structure.java new file mode 100644 index 00000000..9ac52306 --- /dev/null +++ b/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/src/main/java/org/bukkit/structure/StructureManager.java b/src/main/java/org/bukkit/structure/StructureManager.java new file mode 100644 index 00000000..78fb239e --- /dev/null +++ b/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/src/main/java/org/bukkit/structure/package-info.java b/src/main/java/org/bukkit/structure/package-info.java new file mode 100644 index 00000000..6f1f2352 --- /dev/null +++ b/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; diff --git a/src/main/java/org/bukkit/util/ChatPaginator.java b/src/main/java/org/bukkit/util/ChatPaginator.java index 1f510211..dea3b53c 100644 --- a/src/main/java/org/bukkit/util/ChatPaginator.java +++ b/src/main/java/org/bukkit/util/ChatPaginator.java @@ -133,17 +133,12 @@ public class ChatPaginator { } // Iterate over the wrapped lines, applying the last color from one line to the beginning of the next - if (lines.get(0).length() == 0 || lines.get(0).charAt(0) != ChatColor.COLOR_CHAR) { - lines.set(0, ChatColor.WHITE + lines.get(0)); - } for (int i = 1; i < lines.size(); i++) { final String pLine = lines.get(i - 1); final String subLine = lines.get(i); - char color = pLine.charAt(pLine.lastIndexOf(ChatColor.COLOR_CHAR) + 1); - if (subLine.length() == 0 || subLine.charAt(0) != ChatColor.COLOR_CHAR) { - lines.set(i, ChatColor.getByChar(color) + subLine); - } + String color = ChatColor.getLastColors(pLine); + lines.set(i, color + subLine); } return lines.toArray(new String[lines.size()]); diff --git a/src/test/java/org/bukkit/ChatPaginatorTest.java b/src/test/java/org/bukkit/ChatPaginatorTest.java index d38e64f0..b0384b12 100644 --- a/src/test/java/org/bukkit/ChatPaginatorTest.java +++ b/src/test/java/org/bukkit/ChatPaginatorTest.java @@ -22,8 +22,8 @@ public class ChatPaginatorTest { String[] lines = ChatPaginator.wordWrap(rawString, 22); assertThat(lines.length, is(2)); - assertThat(lines[0], is(ChatColor.WHITE.toString() + "123456789 123456789")); - assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789")); + assertThat(lines[0], is("123456789 123456789")); + assertThat(lines[1], is("123456789")); } @Test @@ -33,8 +33,8 @@ public class ChatPaginatorTest { assertThat(lines.length, is(3)); assertThat(lines[0], is(ChatColor.RED + "123456789")); - assertThat(lines[1], is(ChatColor.RED.toString() + ChatColor.RED + "123456789")); - assertThat(lines[2], is(ChatColor.RED + "123456789")); + assertThat(lines[1], is(ChatColor.RED.toString() + ChatColor.RED + ChatColor.RED + "123456789")); + assertThat(lines[2], is(ChatColor.RED.toString() + ChatColor.RED + "123456789")); } @Test @@ -43,8 +43,8 @@ public class ChatPaginatorTest { String[] lines = ChatPaginator.wordWrap(rawString, 19); assertThat(lines.length, is(2)); - assertThat(lines[0], is(ChatColor.WHITE.toString() + "123456789 123456789")); - assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789 12345")); + assertThat(lines[0], is("123456789 123456789")); + assertThat(lines[1], is("123456789 12345")); } @Test @@ -53,8 +53,8 @@ public class ChatPaginatorTest { String[] lines = ChatPaginator.wordWrap(rawString, 19); assertThat(lines.length, is(2)); - assertThat(lines[0], is(ChatColor.WHITE.toString() + "123456789")); - assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789 123456789")); + assertThat(lines[0], is("123456789")); + assertThat(lines[1], is("123456789 123456789")); } @Test @@ -63,8 +63,8 @@ public class ChatPaginatorTest { String[] lines = ChatPaginator.wordWrap(rawString, 19); assertThat(lines.length, is(2)); - assertThat(lines[0], is(ChatColor.WHITE.toString() + "12345678 23456789")); - assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789")); + assertThat(lines[0], is("12345678 23456789")); + assertThat(lines[1], is("123456789")); } @Test @@ -73,8 +73,8 @@ public class ChatPaginatorTest { String[] lines = ChatPaginator.wordWrap(rawString, 19); assertThat(lines.length, is(2)); - assertThat(lines[0], is(ChatColor.WHITE.toString() + "12345678 23456789")); - assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789")); + assertThat(lines[0], is("12345678 23456789")); + assertThat(lines[1], is("123456789")); } @Test @@ -83,12 +83,12 @@ public class ChatPaginatorTest { String[] lines = ChatPaginator.wordWrap(rawString, 6); assertThat(lines.length, is(6)); - assertThat(lines[0], is(ChatColor.WHITE.toString() + "123456")); - assertThat(lines[1], is(ChatColor.WHITE.toString() + "789")); - assertThat(lines[2], is(ChatColor.WHITE.toString() + "123456")); - assertThat(lines[3], is(ChatColor.WHITE.toString() + "789")); - assertThat(lines[4], is(ChatColor.WHITE.toString() + "123456")); - assertThat(lines[5], is(ChatColor.WHITE.toString() + "789")); + assertThat(lines[0], is("123456")); + assertThat(lines[1], is("789")); + assertThat(lines[2], is("123456")); + assertThat(lines[3], is("789")); + assertThat(lines[4], is("123456")); + assertThat(lines[5], is("789")); } @Test @@ -97,13 +97,13 @@ public class ChatPaginatorTest { String[] lines = ChatPaginator.wordWrap(rawString, 6); assertThat(lines.length, is(7)); - assertThat(lines[0], is(ChatColor.WHITE.toString() + "1234")); - assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456")); - assertThat(lines[2], is(ChatColor.WHITE.toString() + "789")); - assertThat(lines[3], is(ChatColor.WHITE.toString() + "123456")); - assertThat(lines[4], is(ChatColor.WHITE.toString() + "789")); - assertThat(lines[5], is(ChatColor.WHITE.toString() + "123456")); - assertThat(lines[6], is(ChatColor.WHITE.toString() + "789")); + assertThat(lines[0], is("1234")); + assertThat(lines[1], is("123456")); + assertThat(lines[2], is("789")); + assertThat(lines[3], is("123456")); + assertThat(lines[4], is("789")); + assertThat(lines[5], is("123456")); + assertThat(lines[6], is("789")); } @Test @@ -112,8 +112,8 @@ public class ChatPaginatorTest { String[] lines = ChatPaginator.wordWrap(rawString, 19); assertThat(lines.length, is(2)); - assertThat(lines[0], is(ChatColor.WHITE.toString() + "123456789")); - assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789")); + assertThat(lines[0], is("123456789")); + assertThat(lines[1], is("123456789")); } @Test @@ -131,8 +131,19 @@ public class ChatPaginatorTest { String[] lines = ChatPaginator.wordWrap(rawString, 5); assertThat(lines.length, is(2)); - assertThat(lines[0], is(ChatColor.WHITE.toString() + "123 1")); - assertThat(lines[1], is(ChatColor.WHITE.toString() + "123")); + assertThat(lines[0], is("123 1")); + assertThat(lines[1], is("123")); + } + + @Test + public void testWordWrap13() { + String rawString = ChatColor.RED + "123456789 " + ChatColor.RED + ChatColor.BOLD + "123456789 " + ChatColor.RED + "123456789"; + String[] lines = ChatPaginator.wordWrap(rawString, 16); + + assertThat(lines.length, is(3)); + assertThat(lines[0], is(ChatColor.RED + "123456789")); + assertThat(lines[1], is(ChatColor.RED.toString() + ChatColor.RED + ChatColor.BOLD + "123456789")); + assertThat(lines[2], is(ChatColor.RED.toString() + ChatColor.BOLD + ChatColor.RED + "123456789")); } @Test @@ -143,8 +154,8 @@ public class ChatPaginatorTest { assertThat(page.getPageNumber(), is(1)); assertThat(page.getTotalPages(), is(4)); assertThat(page.getLines().length, is(2)); - assertThat(page.getLines()[0], is(ChatColor.WHITE.toString() + "1234")); - assertThat(page.getLines()[1], is(ChatColor.WHITE.toString() + "123456")); + assertThat(page.getLines()[0], is("1234")); + assertThat(page.getLines()[1], is("123456")); } @Test @@ -155,8 +166,8 @@ public class ChatPaginatorTest { assertThat(page.getPageNumber(), is(2)); assertThat(page.getTotalPages(), is(4)); assertThat(page.getLines().length, is(2)); - assertThat(page.getLines()[0], is(ChatColor.WHITE.toString() + "789")); - assertThat(page.getLines()[1], is(ChatColor.WHITE.toString() + "123456")); + assertThat(page.getLines()[0], is("789")); + assertThat(page.getLines()[1], is("123456")); } @Test @@ -167,6 +178,6 @@ public class ChatPaginatorTest { assertThat(page.getPageNumber(), is(4)); assertThat(page.getTotalPages(), is(4)); assertThat(page.getLines().length, is(1)); - assertThat(page.getLines()[0], is(ChatColor.WHITE.toString() + "789")); + assertThat(page.getLines()[0], is("789")); } }