diff --git a/src/main/java/org/bukkit/RegionAccessor.java b/src/main/java/org/bukkit/RegionAccessor.java index 369b95b1..4c9fd558 100644 --- a/src/main/java/org/bukkit/RegionAccessor.java +++ b/src/main/java/org/bukkit/RegionAccessor.java @@ -279,6 +279,24 @@ public interface RegionAccessor { @NotNull Collection getEntitiesByClasses(@NotNull Class... classes); + /** + * Creates an entity of a specific class at the given {@link Location} but + * does not spawn it in the world. + *

+ * Note: The created entity keeps a reference to the world it was + * created in, care should be taken that the entity does not outlive the + * world instance as this will lead to memory leaks. + * + * @param the class of the {@link Entity} to create + * @param location the {@link Location} to create the entity at + * @param clazz the class of the {@link Entity} to spawn + * @return an instance of the created {@link Entity} + * @see #addEntity(Entity) + * @see Entity#createSnapshot() + */ + @NotNull + T createEntity(@NotNull Location location, @NotNull Class clazz); + /** * Spawn an entity of a specific class at the given {@link Location} * @@ -393,4 +411,15 @@ public interface RegionAccessor { * {@link HeightMap} */ public int getHighestBlockYAt(@NotNull Location location, @NotNull HeightMap heightMap); + + /** + * Spawns a previously created entity in the world.
+ * The provided entity must not have already been spawned in a world. + * + * @param the generic type of the entity that is being added. + * @param entity the entity to add + * @return the entity now in the world + */ + @NotNull + public T addEntity(@NotNull T entity); } diff --git a/src/main/java/org/bukkit/block/CreatureSpawner.java b/src/main/java/org/bukkit/block/CreatureSpawner.java index 8dae9611..c33f6573 100644 --- a/src/main/java/org/bukkit/block/CreatureSpawner.java +++ b/src/main/java/org/bukkit/block/CreatureSpawner.java @@ -1,6 +1,12 @@ package org.bukkit.block; +import java.util.Collection; +import java.util.List; +import org.bukkit.block.spawner.SpawnRule; +import org.bukkit.block.spawner.SpawnerEntry; +import org.bukkit.entity.EntitySnapshot; import org.bukkit.entity.EntityType; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -17,7 +23,8 @@ public interface CreatureSpawner extends TileState { public EntityType getSpawnedType(); /** - * Set the spawner's creature type. + * Set the spawner's creature type.
+ * This will override any entities that have been added with {@link #addPotentialSpawn} * * @param creatureType The creature type or null to clear. */ @@ -199,4 +206,75 @@ public interface CreatureSpawner extends TileState { * @see #getSpawnRange() */ public void setSpawnRange(int spawnRange); + + /** + * Gets the {@link EntitySnapshot} that will be spawned by this spawner or null + * if no entities have been assigned to this spawner.
+ *

+ * All applicable data from the spawner will be copied, such as custom name, + * health, and velocity.
+ * + * @return the entity snapshot or null if no entities have been assigned to this + * spawner. + */ + @Nullable + public EntitySnapshot getSpawnedEntity(); + + /** + * Sets the entity that will be spawned by this spawner.
+ * This will override any previous entries that have been added with + * {@link #addPotentialSpawn} + *

+ * All applicable data from the snapshot will be copied, such as custom name, + * health, and velocity.
+ * + * @param snapshot the entity snapshot + */ + public void setSpawnedEntity(@NotNull EntitySnapshot snapshot); + + /** + * Adds a new {@link EntitySnapshot} to the list of entities this spawner can + * spawn. + *

+ * The weight will determine how often this entry is chosen to spawn, higher + * weighted entries will spawn more often than lower weighted ones.
+ * The {@link SpawnRule} will determine under what conditions this entry can + * spawn, passing null will use the default conditions for the given entity. + * + * @param snapshot the snapshot that will be spawned + * @param weight the weight + * @param spawnRule the spawn rule for this entity, or null + */ + public void addPotentialSpawn(@NotNull EntitySnapshot snapshot, int weight, @Nullable SpawnRule spawnRule); + + /** + * Adds a new {@link SpawnerEntry} to the list of entities this spawner can + * spawn.
+ * + * @param spawnerEntry the spawner entry to use + * @see #addPotentialSpawn(EntitySnapshot, int, SpawnRule) + */ + public void addPotentialSpawn(@NotNull final SpawnerEntry spawnerEntry); + + /** + * Sets the list of {@link SpawnerEntry} this spawner can spawn.
+ * This will override any previous entries added with + * {@link #addPotentialSpawn} + * + * @param entries the list of entries + */ + public void setPotentialSpawns(@NotNull final Collection entries); + + /** + * Gets a list of potential spawns from this spawner or an empty list if no + * entities have been assigned to this spawner.
+ * Changes made to the returned list will not be reflected in the spawner unless + * applied with {@link #setPotentialSpawns} + * + * @return a list of potential spawns from this spawner, or an empty list if no + * entities have been assigned to this spawner + * @see #getSpawnedType() + */ + @NotNull + public List getPotentialSpawns(); } diff --git a/src/main/java/org/bukkit/block/spawner/SpawnRule.java b/src/main/java/org/bukkit/block/spawner/SpawnRule.java new file mode 100644 index 00000000..c15ce91a --- /dev/null +++ b/src/main/java/org/bukkit/block/spawner/SpawnRule.java @@ -0,0 +1,219 @@ +package org.bukkit.block.spawner; + +import com.google.common.base.Preconditions; +import java.util.LinkedHashMap; +import java.util.Map; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a spawn rule that controls what conditions an entity from a + * monster spawner can spawn. + */ +@SerializableAs("SpawnRule") +public class SpawnRule implements Cloneable, ConfigurationSerializable { + + private int minBlockLight; + private int maxBlockLight; + private int minSkyLight; + private int maxSkyLight; + + /** + * Constructs a new SpawnRule. + * + * @param minBlockLight The minimum (inclusive) block light required for + * spawning to succeed. + * @param maxBlockLight The maximum (inclusive) block light required for + * spawning to succeed. + * @param minSkyLight The minimum (inclusive) sky light required for + * spawning to succeed. + * @param maxSkyLight The maximum (inclusive) sky light required for + * spawning to succeed. + */ + public SpawnRule(int minBlockLight, int maxBlockLight, int minSkyLight, int maxSkyLight) { + Preconditions.checkArgument(minBlockLight <= maxBlockLight, "minBlockLight must be <= maxBlockLight (%s <= %s)", minBlockLight, maxBlockLight); + Preconditions.checkArgument(minSkyLight <= maxSkyLight, "minSkyLight must be <= maxSkyLight (%s <= %s)", minSkyLight, maxSkyLight); + Preconditions.checkArgument(minBlockLight >= 0, "minBlockLight must be >= 0 (given %s)", minBlockLight); + Preconditions.checkArgument(maxBlockLight >= 0, "maxBlockLight must be >= 0 (given %s)", maxBlockLight); + Preconditions.checkArgument(minSkyLight >= 0, "minSkyLight must be >= 0 (given %s)", minSkyLight); + Preconditions.checkArgument(maxSkyLight >= 0, "maxSkyLight must be >= 0 (given %s)", maxSkyLight); + + this.minBlockLight = minBlockLight; + this.maxBlockLight = maxBlockLight; + this.minSkyLight = minSkyLight; + this.maxSkyLight = maxSkyLight; + } + + /** + * Gets the minimum (inclusive) block light required for spawning to + * succeed. + * + * @return minimum block light + */ + public int getMinBlockLight() { + return minBlockLight; + } + + /** + * Sets the minimum (inclusive) block light required for spawning to + * succeed. + * + * @param minBlockLight minimum block light + */ + public void setMinBlockLight(int minBlockLight) { + Preconditions.checkArgument(minBlockLight >= 0, "minBlockLight must be >= 0 (given %s)", minBlockLight); + Preconditions.checkArgument(minBlockLight <= maxBlockLight, "minBlockLight must be <= maxBlockLight (%s <= %s)", minBlockLight, maxBlockLight); + + this.minBlockLight = minBlockLight; + } + + /** + * Gets the maximum (inclusive) block light required for spawning to + * succeed. + * + * @return maximum block light + */ + public int getMaxBlockLight() { + return maxBlockLight; + } + + /** + * Sets the maximum (inclusive) block light required for spawning to + * succeed. + * + * @param maxBlockLight maximum block light + */ + public void setMaxBlockLight(int maxBlockLight) { + Preconditions.checkArgument(maxBlockLight >= 0, "maxBlockLight must be >= 0 (given %s)", maxBlockLight); + + this.maxBlockLight = maxBlockLight; + } + + /** + * Gets the minimum (inclusive) sky light required for spawning to succeed. + * + * @return minimum sky light + */ + public int getMinSkyLight() { + return minSkyLight; + } + + /** + * Sets the minimum (inclusive) sky light required for spawning to succeed. + * + * @param minSkyLight minimum sky light + */ + public void setMinSkyLight(int minSkyLight) { + Preconditions.checkArgument(minSkyLight >= 0, "minSkyLight must be >= 0 (given %s)", minSkyLight); + Preconditions.checkArgument(minSkyLight <= maxSkyLight, "minSkyLight must be <= maxSkyLight (%s <= %s)", minSkyLight, maxSkyLight); + + this.minSkyLight = minSkyLight; + } + + /** + * Gets the maximum (inclusive) sky light required for spawning to succeed. + * + * @return maximum sky light + */ + public int getMaxSkyLight() { + return maxSkyLight; + } + + /** + * Sets the maximum (inclusive) sky light required for spawning to succeed. + * + * @param maxSkyLight maximum sky light + */ + public void setMaxSkyLight(int maxSkyLight) { + Preconditions.checkArgument(maxSkyLight >= 0, "maxSkyLight must be >= 0 (given %s)", maxSkyLight); + + this.maxSkyLight = maxSkyLight; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SpawnRule)) { + return false; + } + + SpawnRule other = (SpawnRule) obj; + return minBlockLight == other.minBlockLight && maxBlockLight == other.maxBlockLight && minSkyLight == other.minSkyLight && maxSkyLight == other.maxSkyLight; + } + + @Override + public int hashCode() { + int hash = minBlockLight; + + hash = (hash << 4) + maxBlockLight; + hash = (hash << 4) + minSkyLight; + hash = (hash << 4) + maxSkyLight; + + return hash; + } + + @NotNull + @Override + public SpawnRule clone() { + try { + return (SpawnRule) super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + @Override + @NotNull + public Map serialize() { + Map result = new LinkedHashMap<>(); + Map block = new LinkedHashMap<>(); + Map sky = new LinkedHashMap<>(); + + block.put("min", getMinBlockLight()); + block.put("max", getMaxBlockLight()); + sky.put("min", getMinSkyLight()); + sky.put("max", getMaxSkyLight()); + + result.put("block-light", block); + result.put("sky-light", sky); + + return result; + } + + @NotNull + public static SpawnRule deserialize(@NotNull Map args) { + int minBlock = 0; + int maxBlock = 0; + int minSky = 0; + int maxSky = 0; + + Object block = args.get("block-light"); + Object sky = args.get("sky-light"); + + if (block instanceof Map) { + Map blockMap = (Map) block; + + if (blockMap.containsKey("min")) { + minBlock = (int) blockMap.get("min"); + } + + if (blockMap.containsKey("max")) { + maxBlock = (int) blockMap.get("max"); + } + } + + if (sky instanceof Map) { + Map skyMap = (Map) sky; + + if (skyMap.containsKey("min")) { + minSky = (int) skyMap.get("min"); + } + + if (skyMap.containsKey("max")) { + maxSky = (int) skyMap.get("max"); + } + } + + return new SpawnRule(minBlock, maxBlock, minSky, maxSky); + } +} diff --git a/src/main/java/org/bukkit/block/spawner/SpawnerEntry.java b/src/main/java/org/bukkit/block/spawner/SpawnerEntry.java new file mode 100644 index 00000000..0043276a --- /dev/null +++ b/src/main/java/org/bukkit/block/spawner/SpawnerEntry.java @@ -0,0 +1,85 @@ +package org.bukkit.block.spawner; + +import com.google.common.base.Preconditions; +import org.bukkit.entity.EntitySnapshot; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a weighted spawn potential that can be added to a monster spawner. + */ +public class SpawnerEntry { + + private EntitySnapshot snapshot; + private int spawnWeight; + private SpawnRule spawnRule; + + public SpawnerEntry(@NotNull EntitySnapshot snapshot, int spawnWeight, @Nullable SpawnRule spawnRule) { + Preconditions.checkArgument(snapshot != null, "Snapshot cannot be null"); + + this.snapshot = snapshot; + this.spawnWeight = spawnWeight; + this.spawnRule = spawnRule; + } + + /** + * Gets the {@link EntitySnapshot} for this SpawnerEntry. + * + * @return the snapshot + */ + @NotNull + public EntitySnapshot getSnapshot() { + return snapshot; + } + + /** + * Sets the {@link EntitySnapshot} for this SpawnerEntry. + * + * @param snapshot the snapshot + */ + public void setSnapshot(@NotNull EntitySnapshot snapshot) { + Preconditions.checkArgument(snapshot != null, "Snapshot cannot be null"); + this.snapshot = snapshot; + } + + /** + * Gets the weight for this SpawnerEntry, when added to a spawner entries + * with higher weight will spawn more often. + * + * @return the weight + */ + public int getSpawnWeight() { + return spawnWeight; + } + + /** + * Sets the weight for this SpawnerEntry, when added to a spawner entries + * with higher weight will spawn more often. + * + * @param spawnWeight the new spawn weight + */ + public void setSpawnWeight(int spawnWeight) { + this.spawnWeight = spawnWeight; + } + + /** + * Gets a copy of the {@link SpawnRule} for this SpawnerEntry, or null if + * none has been set. + * + * @return a copy of the spawn rule or null + */ + @Nullable + public SpawnRule getSpawnRule() { + return spawnRule == null ? null : spawnRule.clone(); + } + + /** + * Sets the {@link SpawnRule} for this SpawnerEntry, null may be used to + * clear the current spawn rule. + * + * @param spawnRule the new spawn rule to use or null + */ + public void setSpawnRule(@Nullable SpawnRule spawnRule) { + this.spawnRule = spawnRule; + } +} diff --git a/src/main/java/org/bukkit/block/spawner/package-info.java b/src/main/java/org/bukkit/block/spawner/package-info.java new file mode 100644 index 00000000..9a8e7688 --- /dev/null +++ b/src/main/java/org/bukkit/block/spawner/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes relevant to mob spawners. + */ +package org.bukkit.block.spawner; diff --git a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java index 2c047062..a6497f42 100644 --- a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java +++ b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java @@ -14,6 +14,7 @@ import org.bukkit.FireworkEffect; import org.bukkit.Location; import org.bukkit.attribute.AttributeModifier; import org.bukkit.block.banner.Pattern; +import org.bukkit.block.spawner.SpawnRule; import org.bukkit.configuration.Configuration; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; @@ -42,6 +43,7 @@ public class ConfigurationSerialization { registerClass(Location.class); registerClass(AttributeModifier.class); registerClass(BoundingBox.class); + registerClass(SpawnRule.class); } protected ConfigurationSerialization(@NotNull Class clazz) { diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java index 764c502c..dafb5d28 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java @@ -26,6 +26,9 @@ import org.jetbrains.annotations.Nullable; /** * Represents a base entity in the world + *

+ * Not all methods are guaranteed to work/may have side effects when + * {@link #isInWorld()} is false. */ public interface Entity extends Metadatable, CommandSender, Nameable, PersistentDataHolder { @@ -265,8 +268,8 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent public boolean isDead(); /** - * Returns false if the entity has died or been despawned for some other - * reason. + * Returns false if the entity has died, been despawned for some other + * reason, or has not been added to the world. * * @return True if valid. */ @@ -714,4 +717,43 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ @NotNull SpawnCategory getSpawnCategory(); + + /** + * Checks if this entity has been spawned in a world.
+ * Entities not spawned in a world will not tick, be sent to players, or be + * saved to the server files. + * + * @return whether the entity has been spawned in a world + */ + boolean isInWorld(); + + /** + * Crates an {@link EntitySnapshot} representing the current state of this entity. + * + * @return a snapshot representing this entity or null if one cannot be made + */ + @Nullable + @ApiStatus.Experimental + EntitySnapshot createSnapshot(); + + /** + * Creates a copy of this entity and all its data. Does not spawn the copy in + * the world.
+ * Note: Players cannot be copied. + * + * @return a copy of this entity. + */ + @NotNull + @ApiStatus.Experimental + Entity copy(); + + /** + * Creates a copy of this entity and all its data. Spawns the copy at the given location.
+ * Note: Players cannot be copied. + * @param to the location to copy to + * @return a copy of this entity. + */ + @NotNull + @ApiStatus.Experimental + Entity copy(@NotNull Location to); } diff --git a/src/main/java/org/bukkit/entity/EntitySnapshot.java b/src/main/java/org/bukkit/entity/EntitySnapshot.java new file mode 100644 index 00000000..6f34486a --- /dev/null +++ b/src/main/java/org/bukkit/entity/EntitySnapshot.java @@ -0,0 +1,39 @@ +package org.bukkit.entity; + +import org.bukkit.Location; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an immutable copy of an entity's state. Can be used at any time to + * create an instance of the stored entity. + */ +public interface EntitySnapshot { + + /** + * Creates an entity using this template. Does not spawn the copy in the world. + *
+ * + * @param world the world to create the entity in + * @return a copy of this entity. + */ + @NotNull + Entity createEntity(@NotNull World world); + + /** + * Creates an entity using this template and spawns it at the provided location. + * + * @param to the location to copy to + * @return the new entity. + */ + @NotNull + Entity createEntity(@NotNull Location to); + + /** + * Gets the type of entity this template holds. + * + * @return the type + */ + @NotNull + EntityType getEntityType(); +} diff --git a/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java b/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java index 9ae84de4..b3447e22 100644 --- a/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java +++ b/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java @@ -1,8 +1,10 @@ package org.bukkit.inventory.meta; +import org.bukkit.entity.EntitySnapshot; import org.bukkit.entity.EntityType; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Represents a spawn egg and it's spawned type. @@ -30,6 +32,28 @@ public interface SpawnEggMeta extends ItemMeta { @Contract("_ -> fail") void setSpawnedType(EntityType type); + /** + * Gets the {@link EntitySnapshot} that will be spawned by this spawn egg or null if no entity + * has been set.
+ *

+ * All applicable data from the egg will be copied, such as custom name, health, + * and velocity.
+ * + * @return the entity snapshot or null if no entity has been set + */ + @Nullable + EntitySnapshot getSpawnedEntity(); + + /** + * Sets the {@link EntitySnapshot} that will be spawned by this spawn egg.
+ *

+ * All applicable data from the entity will be copied, such as custom name, + * health, and velocity.
+ * + * @param snapshot the snapshot + */ + void setSpawnedEntity(@NotNull EntitySnapshot snapshot); + @NotNull @Override SpawnEggMeta clone();