diff --git a/pom.xml b/pom.xml
index e29d7d061..3198bfad0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -419,6 +419,13 @@
2.0.0-SNAPSHOT
provided
+
+
+ de.oliver
+ FancyHolograms
+ 2.4.1
+ provided
+
diff --git a/src/main/java/world/bentobox/bentobox/api/hooks/NPCHook.java b/src/main/java/world/bentobox/bentobox/api/hooks/NPCHook.java
new file mode 100644
index 000000000..6a848215f
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/api/hooks/NPCHook.java
@@ -0,0 +1,41 @@
+package world.bentobox.bentobox.api.hooks;
+
+import java.util.List;
+import java.util.Map;
+
+import org.bukkit.Chunk;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.util.Vector;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import de.oliver.fancynpcs.api.Npc;
+import lol.pyr.znpcsplus.api.npc.NpcEntry;
+import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
+
+/**
+ * NPC Hooks
+ * @author tastybento
+ * @since 3.2.0
+ */
+public abstract class NPCHook extends Hook {
+
+ protected NPCHook(@NonNull String pluginName, @NonNull Material icon) {
+ super(pluginName, icon);
+ }
+
+ public abstract boolean spawnNpc(String yaml, Location pos) throws InvalidConfigurationException;
+
+ public abstract Map extends Vector, ? extends List> getNpcsInArea(World world,
+ List vectorsToCopy, @Nullable Vector origin);
+
+ /**
+ * Remove all NPCs in chunk
+ * @param chunk chunk
+ */
+ public abstract void removeNPCsInChunk(Chunk chunk);
+
+}
diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java
index 6d678bd08..5522ea867 100644
--- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java
+++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java
@@ -12,25 +12,20 @@ import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.block.Banner;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.Sign;
+import org.bukkit.block.data.Attachable;
import org.bukkit.block.sign.Side;
-import org.bukkit.entity.AbstractHorse;
-import org.bukkit.entity.Ageable;
-import org.bukkit.entity.ChestedHorse;
import org.bukkit.entity.Entity;
-import org.bukkit.entity.Horse;
import org.bukkit.entity.Player;
-import org.bukkit.entity.Tameable;
-import org.bukkit.entity.Villager;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
-import org.bukkit.material.Attachable;
-import org.bukkit.material.Colorable;
+import org.bukkit.persistence.PersistentDataType;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
@@ -57,6 +52,10 @@ import world.bentobox.bentobox.hooks.ZNPCsPlusHook;
*/
public class BlueprintClipboard {
+ /**
+ * Used to filter out hidden DisplayEntity armor stands when copying
+ */
+ private static final NamespacedKey KEY = new NamespacedKey(BentoBox.getInstance(), "associatedDisplayEntity");
private @Nullable Blueprint blueprint;
private @Nullable Location pos1;
private @Nullable Location pos2;
@@ -74,6 +73,7 @@ public class BlueprintClipboard {
private Optional npc;
private Optional znpc;
+
/**
* Create a clipboard for blueprint
* @param blueprint - the blueprint to load into the clipboard
@@ -148,6 +148,7 @@ public class BlueprintClipboard {
// Add all the citizens for the area in one go. This is pretty fast.
bpEntities.putAll(npc.get().getNpcsInArea(world, vectorsToCopy, origin));
}
+ // ZNPCsPlus NPCs
if (znpc.isPresent()) {
bpEntities.putAll(znpc.get().getNpcsInArea(world, vectorsToCopy, origin));
}
@@ -162,9 +163,9 @@ public class BlueprintClipboard {
List ents = world.getEntities().stream()
.filter(Objects::nonNull)
.filter(e -> !(e instanceof Player))
- .filter(e -> new Vector(Math.rint(e.getLocation().getX()),
- Math.rint(e.getLocation().getY()),
- Math.rint(e.getLocation().getZ())).equals(v))
+ .filter(e -> !e.getPersistentDataContainer().has(KEY, PersistentDataType.STRING)) // Do not copy hidden display entities
+ .filter(e -> new Vector(e.getLocation().getBlockX(), e.getLocation().getBlockY(),
+ e.getLocation().getBlockZ()).equals(v))
.toList();
if (copyBlock(v.toLocation(world), copyAir, copyBiome, ents)) {
count++;
@@ -230,7 +231,6 @@ public class BlueprintClipboard {
if (!copyAir && block.getType().equals(Material.AIR) && !ents.isEmpty()) {
return true;
}
-
BlueprintBlock b = bluePrintBlock(pos, block, copyBiome);
if (b != null) {
this.bpBlocks.put(pos, b);
@@ -256,7 +256,7 @@ public class BlueprintClipboard {
}
}
// Set block data
- if (blockState.getData() instanceof Attachable) {
+ if (blockState.getBlockData() instanceof Attachable) {
// Placeholder for attachment
bpBlocks.put(pos, new BlueprintBlock("minecraft:air"));
bpAttachable.put(pos, b);
@@ -273,7 +273,6 @@ public class BlueprintClipboard {
}
}
}
-
// Chests
if (blockState instanceof InventoryHolder ih) {
b.setInventory(new HashMap<>());
@@ -284,11 +283,9 @@ public class BlueprintClipboard {
}
}
}
-
if (blockState instanceof CreatureSpawner spawner) {
b.setCreatureSpawner(getSpawner(spawner));
}
-
// Banners
if (blockState instanceof Banner banner) {
b.setBannerPatterns(banner.getPatterns());
@@ -317,62 +314,15 @@ public class BlueprintClipboard {
private List setEntities(List ents) {
List bpEnts = new ArrayList<>();
for (Entity entity : ents) {
- BlueprintEntity bpe = new BlueprintEntity();
-
- bpe.setType(entity.getType());
- bpe.setCustomName(entity.getCustomName());
- if (entity instanceof Villager villager) {
- setVillager(villager, bpe);
- }
- if (entity instanceof Colorable c && c.getColor() != null) {
- bpe.setColor(c.getColor());
- }
- if (entity instanceof Tameable tameable) {
- bpe.setTamed(tameable.isTamed());
- }
- if (entity instanceof ChestedHorse chestedHorse) {
- bpe.setChest(chestedHorse.isCarryingChest());
- }
- // Only set if child. Most animals are adults
- if (entity instanceof Ageable ageable && !ageable.isAdult()) {
- bpe.setAdult(false);
- }
- if (entity instanceof AbstractHorse horse) {
- bpe.setDomestication(horse.getDomestication());
- bpe.setInventory(new HashMap<>());
- for (int i = 0; i < horse.getInventory().getSize(); i++) {
- ItemStack item = horse.getInventory().getItem(i);
- if (item != null) {
- bpe.getInventory().put(i, item);
- }
- }
- }
-
- if (entity instanceof Horse horse) {
- bpe.setStyle(horse.getStyle());
- }
-
+ BlueprintEntity bpe = new BlueprintEntity(entity);
// Mythic mob check
mmh.filter(mm -> mm.isMythicMob(entity)).map(mm -> mm.getMythicMob(entity))
.ifPresent(bpe::setMythicMobsRecord);
-
bpEnts.add(bpe);
}
return bpEnts;
}
- /**
- * Set the villager stats
- * @param v - villager
- * @param bpe - Blueprint Entity
- */
- private void setVillager(Villager v, BlueprintEntity bpe) {
- bpe.setExperience(v.getVillagerExperience());
- bpe.setLevel(v.getVillagerLevel());
- bpe.setProfession(v.getProfession());
- bpe.setVillagerType(v.getVillagerType());
- }
-
/**
* @return the origin
*/
diff --git a/src/main/java/world/bentobox/bentobox/blueprints/DisplayListener.java b/src/main/java/world/bentobox/bentobox/blueprints/DisplayListener.java
new file mode 100644
index 000000000..efa1dccf3
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/blueprints/DisplayListener.java
@@ -0,0 +1,45 @@
+package world.bentobox.bentobox.blueprints;
+
+import java.util.UUID;
+
+import org.bukkit.NamespacedKey;
+import org.bukkit.Sound;
+import org.bukkit.World;
+import org.bukkit.entity.ArmorStand;
+import org.bukkit.entity.Display;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerInteractAtEntityEvent;
+import org.bukkit.persistence.PersistentDataType;
+
+import world.bentobox.bentobox.BentoBox;
+
+/**
+ * Provides a listener for the Display Objects pasted when a hologram is interacted with
+ * https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/event/player/PlayerInteractAtEntityEvent.html
+ */
+public class DisplayListener implements Listener {
+
+ @EventHandler
+ public void onPlayerInteractEntity(PlayerInteractAtEntityEvent event) {
+ if (event.getRightClicked() instanceof ArmorStand) {
+ ArmorStand armorStand = (ArmorStand) event.getRightClicked();
+ NamespacedKey key = new NamespacedKey(BentoBox.getInstance(), "associatedDisplayEntity");
+
+ if (armorStand.getPersistentDataContainer().has(key, PersistentDataType.STRING)) {
+ String displayEntityUUID = armorStand.getPersistentDataContainer().get(key, PersistentDataType.STRING);
+
+ // Fetch the associated DisplayEntity by UUID
+ World world = armorStand.getWorld();
+ world.getEntitiesByClass(Display.class).stream()
+ .filter(e -> e.getUniqueId().equals(UUID.fromString(displayEntityUUID))).findFirst()
+ .ifPresent(e -> {
+ event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.BLOCK_GLASS_BREAK, 1F,
+ 1F);
+ e.remove();
+
+ });
+ }
+ }
+ }
+}
diff --git a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java
index 2ce285fee..e41ebcc9b 100644
--- a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java
+++ b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java
@@ -1,78 +1,234 @@
package world.bentobox.bentobox.blueprints.dataobjects;
+import java.util.HashMap;
import java.util.Map;
+import org.bukkit.Bukkit;
+import org.bukkit.Color;
import org.bukkit.DyeColor;
+import org.bukkit.Location;
+import org.bukkit.NamespacedKey;
+import org.bukkit.World;
+import org.bukkit.block.BlockFace;
+import org.bukkit.block.data.BlockData;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Ageable;
+import org.bukkit.entity.ArmorStand;
+import org.bukkit.entity.BlockDisplay;
import org.bukkit.entity.ChestedHorse;
+import org.bukkit.entity.Display;
+import org.bukkit.entity.Display.Billboard;
+import org.bukkit.entity.Display.Brightness;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Horse;
import org.bukkit.entity.Horse.Style;
+import org.bukkit.entity.ItemDisplay;
+import org.bukkit.entity.ItemDisplay.ItemDisplayTransform;
import org.bukkit.entity.Tameable;
+import org.bukkit.entity.TextDisplay;
+import org.bukkit.entity.TextDisplay.TextAlignment;
import org.bukkit.entity.Villager;
import org.bukkit.entity.Villager.Profession;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.Colorable;
+import org.bukkit.persistence.PersistentDataType;
+import org.bukkit.util.Transformation;
+import org.bukkit.util.Vector;
import com.google.gson.annotations.Expose;
+import world.bentobox.bentobox.BentoBox;
+
/**
* @author tastybento
* @since 1.5.0
*/
public class BlueprintEntity {
- // Npc storage
- @Expose
- private String npc;
-
// MythicMobs storage
public record MythicMobRecord(String type, String displayName, double level, float power, String stance) {
}
- // GSON can serialize records, but the record class needs to be know in advance. So this breaks out the record entries
- @Expose
- String MMtype;
- @Expose
- Double MMLevel;
- @Expose
- String MMStance;
- @Expose
- Float MMpower;
+ /**
+ * Item Display Entity store
+ * @since 3.2.0
+ */
+ public record ItemDispRec(@Expose ItemStack item, @Expose ItemDisplayTransform itemDispTrans) {}
- @Expose
- private DyeColor color;
- @Expose
- private EntityType type;
- @Expose
- private String customName;
- @Expose
- private Boolean tamed;
- @Expose
- private Boolean chest;
+ /**
+ * Display Entity store
+ * @since 3.2.0
+ */
+ public record DisplayRec(@Expose Billboard billboard, @Expose Brightness brightness, @Expose float height,
+ @Expose float width, @Expose Color glowColorOverride, @Expose int interpolationDelay,
+ @Expose int interpolationDuration, @Expose float shadowRadius, @Expose float shadowStrength,
+ @Expose int teleportDuration, @Expose Transformation transformation, @Expose float range) {
+ }
+
+ /**
+ * TextDisplay entity store
+ * @since 3.2.0
+ */
+ public record TextDisplayRec(@Expose String text, @Expose TextAlignment alignment, @Expose Color bgColor,
+ @Expose BlockFace face, @Expose int lWidth, @Expose byte opacity, @Expose boolean isShadowed,
+ @Expose boolean isSeeThrough, @Expose boolean isDefaultBg) {
+ }
@Expose
private Boolean adult;
@Expose
+ public BlueprintBlock blockDisp;
+
+ @Expose
+ private Boolean chest;
+ @Expose
+ private DyeColor color;
+ @Expose
+ private String customName;
+ @Expose
+ public DisplayRec displayRec;
+ @Expose
private Integer domestication;
@Expose
- private Map inventory;
- @Expose
- private Style style;
- @Expose
- private Integer level;
- @Expose
- private Profession profession;
- @Expose
private Integer experience;
@Expose
+ private Map inventory;
+ @Expose
+ public ItemDispRec itemDisp;
+ @Expose
+ private Integer level;
+ @Expose
+ Double MMLevel;
+ @Expose
+ Float MMpower;
+ @Expose
+ String MMStance;
+ // GSON can serialize records, but the record class needs to be know in advance. So this breaks out the record entries
+ @Expose
+ String MMtype;
+ // Npc storage
+ @Expose
+ private String npc;
+ @Expose
+ private Profession profession;
+ @Expose
+ private Style style;
+
+ @Expose
+ private Boolean tamed;
+
+ @Expose
+ public TextDisplayRec textDisp;
+
+ @Expose
+ private EntityType type;
+ @Expose
private Villager.Type villagerType;
+ // Position within the block
+ @Expose
+ private double x;
+ @Expose
+ private double y;
+ @Expose
+ private double z;
+ @Expose
+ private boolean glowing;
+ @Expose
+ private boolean gravity;
+ @Expose
+ private boolean visualFire;
+ @Expose
+ private boolean silent;
+ @Expose
+ private boolean invulnerable;
+ @Expose
+ private int fireTicks;
/**
+ * Serializes an entity to a Blueprint Entity
+ * @param entity entity to serialize
+ * @since 3.2.0
+ */
+ public BlueprintEntity(Entity entity) {
+ this.setType(entity.getType());
+ this.setCustomName(entity.getCustomName());
+ this.setGlowing(entity.isGlowing());
+ this.setGravity(entity.hasGravity());
+ this.setVisualFire(entity.isVisualFire());
+ this.setSilent(entity.isSilent());
+ this.setInvulnerable(entity.isInvulnerable());
+ this.setFireTicks(entity.getFireTicks());
+
+ if (entity instanceof Villager villager) {
+ configVillager(villager);
+ }
+ if (entity instanceof Colorable c && c.getColor() != null) {
+ this.setColor(c.getColor());
+ }
+ if (entity instanceof Tameable tameable) {
+ this.setTamed(tameable.isTamed());
+ }
+ if (entity instanceof ChestedHorse chestedHorse) {
+ this.setChest(chestedHorse.isCarryingChest());
+ }
+ // Only set if child. Most animals are adults
+ if (entity instanceof Ageable ageable && !ageable.isAdult()) {
+ this.setAdult(false);
+ }
+ if (entity instanceof AbstractHorse horse) {
+ this.setDomestication(horse.getDomestication());
+ this.setInventory(new HashMap<>());
+ for (int i = 0; i < horse.getInventory().getSize(); i++) {
+ ItemStack item = horse.getInventory().getItem(i);
+ if (item != null) {
+ this.getInventory().put(i, item);
+ }
+ }
+ }
+
+ if (entity instanceof Horse horse) {
+ this.setStyle(horse.getStyle());
+ }
+
+ // Display entities
+ if (entity instanceof Display disp) {
+ this.storeDisplay(disp);
+ }
+
+ }
+
+ /**
+ * Makes a blank BlueprintEntity
+ */
+ public BlueprintEntity() {
+ // Blank constructor
+ }
+
+ /**
+ * Set the villager stats
+ * @param v - villager
+ * @param bpe - Blueprint Entity
+ */
+ private void configVillager(Villager v) {
+ this.setExperience(v.getVillagerExperience());
+ this.setLevel(v.getVillagerLevel());
+ this.setProfession(v.getProfession());
+ this.setVillagerType(v.getVillagerType());
+ }
+
+ /**
+ * Adjusts the entity according to how it was stored
* @since 1.8.0
*/
public void configureEntity(Entity e) {
+ // Set the general states
+ e.setGlowing(glowing);
+ e.setGravity(gravity);
+ e.setVisualFire(visualFire);
+ e.setSilent(silent);
+ e.setInvulnerable(invulnerable);
+ e.setFireTicks(fireTicks);
+
if (e instanceof Villager villager) {
setVillager(villager);
}
@@ -102,78 +258,8 @@ public class BlueprintEntity {
if (style != null && e instanceof Horse horse) {
horse.setStyle(style);
}
- }
-
- /**
- * @param v - villager
- * @since 1.16.0
- */
- private void setVillager(Villager v) {
- v.setProfession(profession == null ? Profession.NONE : profession);
- v.setVillagerExperience(experience == null ? 0 : experience);
- v.setVillagerLevel(level == null ? 0 : level);
- v.setVillagerType(villagerType == null ? Villager.Type.PLAINS : villagerType);
- }
-
- /**
- * @return the color
- */
- public DyeColor getColor() {
- return color;
- }
- /**
- * @param color the color to set
- */
- public void setColor(DyeColor color) {
- this.color = color;
- }
- /**
- * @return the type
- */
- public EntityType getType() {
- return type;
- }
- /**
- * @param type the type to set
- */
- public void setType(EntityType type) {
- this.type = type;
- }
- /**
- * @return the customName
- */
- public String getCustomName() {
- return customName;
- }
- /**
- * @param customName the customName to set
- */
- public void setCustomName(String customName) {
- this.customName = customName;
- }
- /**
- * @return the tamed
- */
- public Boolean getTamed() {
- return tamed;
- }
- /**
- * @param tamed the tamed to set
- */
- public void setTamed(Boolean tamed) {
- this.tamed = tamed;
- }
- /**
- * @return the chest
- */
- public Boolean getChest() {
- return chest;
- }
- /**
- * @param chest the chest to set
- */
- public void setChest(Boolean chest) {
- this.chest = chest;
+ // Shift to the in-block location (remove the 0.5 that the location serializer used)
+ e.getLocation().add(new Vector(x - 0.5D, y, z - 0.5D));
}
/**
* @return the adult
@@ -182,10 +268,22 @@ public class BlueprintEntity {
return adult;
}
/**
- * @param adult the adult to set
+ * @return the chest
*/
- public void setAdult(Boolean adult) {
- this.adult = adult;
+ public Boolean getChest() {
+ return chest;
+ }
+ /**
+ * @return the color
+ */
+ public DyeColor getColor() {
+ return color;
+ }
+ /**
+ * @return the customName
+ */
+ public String getCustomName() {
+ return customName;
}
/**
* @return the domestication
@@ -194,10 +292,10 @@ public class BlueprintEntity {
return domestication;
}
/**
- * @param domestication the domestication to set
+ * @return the experience
*/
- public void setDomestication(int domestication) {
- this.domestication = domestication;
+ public Integer getExperience() {
+ return experience;
}
/**
* @return the inventory
@@ -206,10 +304,31 @@ public class BlueprintEntity {
return inventory;
}
/**
- * @param inventory the inventory to set
+ * @return the level
*/
- public void setInventory(Map inventory) {
- this.inventory = inventory;
+ public Integer getLevel() {
+ return level;
+ }
+ /**
+ * @return the mythicMobsRecord
+ */
+ public MythicMobRecord getMythicMobsRecord() {
+ if (this.MMtype == null || this.MMLevel == null || this.MMpower == null || this.MMStance == null) {
+ return null;
+ }
+ return new MythicMobRecord(this.MMtype, this.getCustomName(), this.MMLevel, this.MMpower, this.MMStance);
+ }
+ /**
+ * @return the npc
+ */
+ public String getNpc() {
+ return npc;
+ }
+ /**
+ * @return the profession
+ */
+ public Profession getProfession() {
+ return profession;
}
/**
* @return the style
@@ -217,53 +336,19 @@ public class BlueprintEntity {
public Style getStyle() {
return style;
}
+
/**
- * @param style the style to set
+ * @return the tamed
*/
- public void setStyle(Style style) {
- this.style = style;
+ public Boolean getTamed() {
+ return tamed;
}
/**
- * @return the level
+ * @return the type
*/
- public Integer getLevel() {
- return level;
- }
-
- /**
- * @param level the level to set
- */
- public void setLevel(Integer level) {
- this.level = level;
- }
-
- /**
- * @return the profession
- */
- public Profession getProfession() {
- return profession;
- }
-
- /**
- * @param profession the profession to set
- */
- public void setProfession(Profession profession) {
- this.profession = profession;
- }
-
- /**
- * @return the experience
- */
- public Integer getExperience() {
- return experience;
- }
-
- /**
- * @param experience the experience to set
- */
- public void setExperience(Integer experience) {
- this.experience = experience;
+ public EntityType getType() {
+ return type;
}
/**
@@ -274,10 +359,93 @@ public class BlueprintEntity {
}
/**
- * @param villagerType the villagerType to set
+ * @param adult the adult to set
*/
- public void setVillagerType(Villager.Type villagerType) {
- this.villagerType = villagerType;
+ public void setAdult(Boolean adult) {
+ this.adult = adult;
+ }
+
+ /**
+ * @param chest the chest to set
+ */
+ public void setChest(Boolean chest) {
+ this.chest = chest;
+ }
+
+ /**
+ * @param color the color to set
+ */
+ public void setColor(DyeColor color) {
+ this.color = color;
+ }
+
+ /**
+ * @param customName the customName to set
+ */
+ public void setCustomName(String customName) {
+ this.customName = customName;
+ }
+
+ /**
+ * Sets any display entity properties to the location, e.g. holograms
+ * @param pos location
+ */
+ public void setDisplay(Location pos) {
+ World world = pos.getWorld();
+ Location newPos = pos.clone().add(new Vector(x - 0.5D, y, z - 0.5D));
+ Display d = null;
+ if (this.blockDisp != null) {
+ // Block Display
+ d = world.spawn(newPos, BlockDisplay.class);
+ BlockData bd = Bukkit.createBlockData(this.blockDisp.getBlockData());
+ ((BlockDisplay) d).setBlock(bd);
+ } else if (this.itemDisp != null) {
+ // Item Display
+ d = world.spawn(newPos, ItemDisplay.class);
+ ((ItemDisplay) d).setItemStack(itemDisp.item());
+ ((ItemDisplay) d).setItemDisplayTransform(itemDisp.itemDispTrans());
+ } else if (this.textDisp != null) {
+ // Text Display
+ d = world.spawn(newPos, TextDisplay.class);
+ ((TextDisplay) d).setText(textDisp.text());
+ ((TextDisplay) d).setAlignment(textDisp.alignment());
+ ((TextDisplay) d).setBackgroundColor(textDisp.bgColor());
+ ((TextDisplay) d).setLineWidth(textDisp.lWidth());
+ ((TextDisplay) d).setTextOpacity(textDisp.opacity());
+ ((TextDisplay) d).setShadowed(textDisp.isShadowed());
+ ((TextDisplay) d).setSeeThrough(textDisp.isSeeThrough());
+ ((TextDisplay) d).setBackgroundColor(textDisp.bgColor());
+ }
+ if (d != null && this.displayRec != null) {
+ d.setCustomName(getCustomName());
+ d.setBillboard(displayRec.billboard());
+ d.setBrightness(displayRec.brightness());
+ d.setDisplayHeight(displayRec.height());
+ d.setDisplayWidth(displayRec.width());
+ d.setGlowColorOverride(displayRec.glowColorOverride());
+ d.setInterpolationDelay(displayRec.interpolationDelay());
+ d.setInterpolationDuration(displayRec.interpolationDuration());
+ d.setShadowRadius(displayRec.shadowRadius());
+ d.setShadowStrength(displayRec.shadowStrength());
+ d.setTeleportDuration(displayRec.teleportDuration());
+ d.setTransformation(displayRec.transformation());
+ d.setViewRange(displayRec.range());
+
+ // Spawn an armor stand here so that we have a way to detect if a player interacts with the item
+ ArmorStand armorStand = (ArmorStand) world.spawnEntity(newPos, EntityType.ARMOR_STAND);
+ armorStand.setSmall(true); // Reduces size
+ armorStand.setGravity(false); // Prevents falling
+ armorStand.setInvisible(true);
+ NamespacedKey key = new NamespacedKey(BentoBox.getInstance(), "associatedDisplayEntity");
+ armorStand.getPersistentDataContainer().set(key, PersistentDataType.STRING, d.getUniqueId().toString());
+ }
+ }
+
+ /**
+ * @param domestication the domestication to set
+ */
+ public void setDomestication(int domestication) {
+ this.domestication = domestication;
}
/**
@@ -288,13 +456,24 @@ public class BlueprintEntity {
}
/**
- * @return the mythicMobsRecord
+ * @param experience the experience to set
*/
- public MythicMobRecord getMythicMobsRecord() {
- if (this.MMtype == null || this.MMLevel == null || this.MMpower == null || this.MMStance == null) {
- return null;
- }
- return new MythicMobRecord(this.MMtype, this.getCustomName(), this.MMLevel, this.MMpower, this.MMStance);
+ public void setExperience(Integer experience) {
+ this.experience = experience;
+ }
+
+ /**
+ * @param inventory the inventory to set
+ */
+ public void setInventory(Map inventory) {
+ this.inventory = inventory;
+ }
+
+ /**
+ * @param level the level to set
+ */
+ public void setLevel(Integer level) {
+ this.level = level;
}
/**
@@ -308,38 +487,164 @@ public class BlueprintEntity {
this.MMStance = mmr.stance();
this.MMpower = mmr.power();
}
-
/**
- * @return the npc
+ * @param npc the citizen to set
*/
- public String getNpc() {
- return npc;
+ public void setNpc(String npc) {
+ this.npc = npc;
+ }
+ /**
+ * @param profession the profession to set
+ */
+ public void setProfession(Profession profession) {
+ this.profession = profession;
+ }
+ /**
+ * @param style the style to set
+ */
+ public void setStyle(Style style) {
+ this.style = style;
}
/**
- * @param citizen the citizen to set
+ * @param tamed the tamed to set
*/
- public void setNpc(String citizen) {
- this.npc = citizen;
+ public void setTamed(Boolean tamed) {
+ this.tamed = tamed;
}
- @Override
- public String toString() {
- return "BlueprintEntity [" + (npc != null ? "npc=" + npc + ", " : "")
- + (MMtype != null ? "MMtype=" + MMtype + ", " : "")
- + (MMLevel != null ? "MMLevel=" + MMLevel + ", " : "")
- + (MMStance != null ? "MMStance=" + MMStance + ", " : "")
- + (MMpower != null ? "MMpower=" + MMpower + ", " : "") + (color != null ? "color=" + color + ", " : "")
- + (type != null ? "type=" + type + ", " : "")
- + (customName != null ? "customName=" + customName + ", " : "")
- + (tamed != null ? "tamed=" + tamed + ", " : "") + (chest != null ? "chest=" + chest + ", " : "")
- + (adult != null ? "adult=" + adult + ", " : "")
- + (domestication != null ? "domestication=" + domestication + ", " : "")
- + (inventory != null ? "inventory=" + inventory + ", " : "")
- + (style != null ? "style=" + style + ", " : "") + (level != null ? "level=" + level + ", " : "")
- + (profession != null ? "profession=" + profession + ", " : "")
- + (experience != null ? "experience=" + experience + ", " : "")
- + (villagerType != null ? "villagerType=" + villagerType : "") + "]";
+ /**
+ * @param type the type to set
+ */
+ public void setType(EntityType type) {
+ this.type = type;
+ }
+
+ /**
+ * @param v - villager
+ * @since 1.16.0
+ */
+ private void setVillager(Villager v) {
+ v.setProfession(profession == null ? Profession.NONE : profession);
+ v.setVillagerExperience(experience == null ? 0 : experience);
+ v.setVillagerLevel(level == null ? 0 : level);
+ v.setVillagerType(villagerType == null ? Villager.Type.PLAINS : villagerType);
+ }
+
+ /**
+ * @param villagerType the villagerType to set
+ */
+ public void setVillagerType(Villager.Type villagerType) {
+ this.villagerType = villagerType;
+ }
+
+ /**
+ * BlockDisplay, ItemDisplay, TextDisplay
+ * @param disp display entity
+ */
+ public void storeDisplay(Display disp) {
+ // Generic items
+ displayRec = new DisplayRec(disp.getBillboard(), disp.getBrightness(), disp.getDisplayHeight(),
+ disp.getDisplayWidth(), disp.getGlowColorOverride(), disp.getInterpolationDelay(),
+ disp.getInterpolationDuration(), disp.getShadowRadius(), disp.getShadowStrength(),
+ disp.getTeleportDuration(), disp.getTransformation(), disp.getViewRange());
+ // Class specific items
+ if (disp instanceof BlockDisplay bd) {
+ this.blockDisp = new BlueprintBlock(bd.getBlock().getAsString());
+ } else if (disp instanceof ItemDisplay id) {
+ itemDisp = new ItemDispRec(id.getItemStack(), id.getItemDisplayTransform());
+ } else if (disp instanceof TextDisplay td) {
+ textDisp = new TextDisplayRec(td.getText(), td.getAlignment(), td.getBackgroundColor(),
+ td.getFacing(), td.getLineWidth(), td.getTextOpacity(), td.isShadowed(), td.isSeeThrough(),
+ td.isDefaultBackground());
+ }
+ // Store location within block
+ x = disp.getLocation().getX() - disp.getLocation().getBlockX();
+ y = disp.getLocation().getY() - disp.getLocation().getBlockY();
+ z = disp.getLocation().getZ() - disp.getLocation().getBlockZ();
+ }
+
+ /**
+ * @return the glowing
+ */
+ public boolean isGlowing() {
+ return glowing;
+ }
+
+ /**
+ * @param glowing the glowing to set
+ */
+ public void setGlowing(boolean glowing) {
+ this.glowing = glowing;
+ }
+
+ /**
+ * @return the gravity
+ */
+ public boolean isGravity() {
+ return gravity;
+ }
+
+ /**
+ * @param gravity the gravity to set
+ */
+ public void setGravity(boolean gravity) {
+ this.gravity = gravity;
+ }
+
+ /**
+ * @return the visualFire
+ */
+ public boolean isVisualFire() {
+ return visualFire;
+ }
+
+ /**
+ * @param visualFire the visualFire to set
+ */
+ public void setVisualFire(boolean visualFire) {
+ this.visualFire = visualFire;
+ }
+
+ /**
+ * @return the silent
+ */
+ public boolean isSilent() {
+ return silent;
+ }
+
+ /**
+ * @param silent the silent to set
+ */
+ public void setSilent(boolean silent) {
+ this.silent = silent;
+ }
+
+ /**
+ * @return the invulnerable
+ */
+ public boolean isInvulnerable() {
+ return invulnerable;
+ }
+
+ /**
+ * @param invulnerable the invulnerable to set
+ */
+ public void setInvulnerable(boolean invulnerable) {
+ this.invulnerable = invulnerable;
+ }
+
+ /**
+ * @return the fireTicks
+ */
+ public int getFireTicks() {
+ return fireTicks;
+ }
+
+ /**
+ * @param fireTicks the fireTicks to set
+ */
+ public void setFireTicks(int fireTicks) {
+ this.fireTicks = fireTicks;
}
-
}
diff --git a/src/main/java/world/bentobox/bentobox/database/json/adapters/MaterialTypeAdapter.java b/src/main/java/world/bentobox/bentobox/database/json/adapters/MaterialTypeAdapter.java
index e5efa70a9..13e5d3401 100644
--- a/src/main/java/world/bentobox/bentobox/database/json/adapters/MaterialTypeAdapter.java
+++ b/src/main/java/world/bentobox/bentobox/database/json/adapters/MaterialTypeAdapter.java
@@ -17,7 +17,7 @@ import com.google.gson.stream.JsonWriter;
/**
* Minecraft 1.20 changed GRASS to SHORT_GRASS. This class provides and backwards compatibility when loading
- * databased files stored with previous versions. It can be extended in the future if further enum changes are made.
+ * database files stored with previous versions. It can be extended in the future if further enum changes are made.
* @author tastybento
* @since 2.0.0
*/
diff --git a/src/main/java/world/bentobox/bentobox/hooks/FancyNpcsHook.java b/src/main/java/world/bentobox/bentobox/hooks/FancyNpcsHook.java
index acc5ba7d8..c8c375f12 100644
--- a/src/main/java/world/bentobox/bentobox/hooks/FancyNpcsHook.java
+++ b/src/main/java/world/bentobox/bentobox/hooks/FancyNpcsHook.java
@@ -10,6 +10,7 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
+import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
@@ -29,9 +30,10 @@ import de.oliver.fancynpcs.api.actions.ActionTrigger;
import de.oliver.fancynpcs.api.actions.NpcAction;
import de.oliver.fancynpcs.api.utils.NpcEquipmentSlot;
import de.oliver.fancynpcs.api.utils.SkinFetcher;
+import lol.pyr.znpcsplus.api.npc.NpcEntry;
import net.kyori.adventure.text.format.NamedTextColor;
import world.bentobox.bentobox.BentoBox;
-import world.bentobox.bentobox.api.hooks.Hook;
+import world.bentobox.bentobox.api.hooks.NPCHook;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
/**
@@ -40,13 +42,13 @@ import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
* @author tastybento
* @since 3.1.0
*/
-public class FancyNpcsHook extends Hook {
+public class FancyNpcsHook extends NPCHook {
public FancyNpcsHook() {
super("FancyNpcs", Material.PLAYER_HEAD);
}
- public String serializeNPC(Npc npc, Vector origin) {
+ String serializeNPC(Npc npc, Vector origin) {
if (npc == null) {
throw new IllegalArgumentException("NPC cannot be null.");
}
@@ -265,6 +267,26 @@ public class FancyNpcsHook extends Hook {
return null; // The hook process shouldn't fail
}
+ /**
+ * Return all NPCs in the chunk
+ * @param chunk chunk
+ * @return list of NPCs
+ */
+ public List getNPCsInChunk(Chunk chunk) {
+ return FancyNpcsPlugin.get().getNpcManager().getAllNpcs().stream()
+ .filter(npc -> npc.getData().getLocation().getChunk().equals(chunk)).toList();
+ }
+
+ /**
+ * Remove all NPCs in chunk
+ * @param chunk chunk
+ */
+ @Override
+ public void removeNPCsInChunk(Chunk chunk) {
+ getNPCsInChunk(chunk).forEach(npc -> npc.removeForAll());
+ }
+
+ @Override
public Map extends Vector, ? extends List> getNpcsInArea(World world, List vectorsToCopy,
@Nullable Vector origin) {
Map> bpEntities = new HashMap<>();
@@ -290,4 +312,5 @@ public class FancyNpcsHook extends Hook {
}
return bpEntities;
}
+
}
diff --git a/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java b/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java
index d9a31dfef..9a538d7a5 100644
--- a/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java
+++ b/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java
@@ -5,6 +5,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
@@ -17,7 +18,7 @@ import lol.pyr.znpcsplus.api.NpcApiProvider;
import lol.pyr.znpcsplus.api.npc.NpcEntry;
import lol.pyr.znpcsplus.util.NpcLocation;
import world.bentobox.bentobox.BentoBox;
-import world.bentobox.bentobox.api.hooks.Hook;
+import world.bentobox.bentobox.api.hooks.NPCHook;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.util.Util;
@@ -27,7 +28,7 @@ import world.bentobox.bentobox.util.Util;
* @author tastybento
* @since 3.2.0
*/
-public class ZNPCsPlusHook extends Hook {
+public class ZNPCsPlusHook extends NPCHook {
private static final String VERSION = "2.0.0-SNAPSHOT"; // Minimum version required
@@ -35,13 +36,20 @@ public class ZNPCsPlusHook extends Hook {
super("ZNPCsPlus", Material.PLAYER_HEAD);
}
- public String serializeNPC(NpcEntry entry, Vector origin) {
+ /**
+ * Serialize a NpcEntry
+ * @param entry NPC entry
+ * @param origin origin point of blueprint
+ * @return string serializing the NPC Entry
+ */
+ String serializeNPC(NpcEntry entry, Vector origin) {
String result = NpcApiProvider.get().getNpcSerializerRegistry().getSerializer(YamlConfiguration.class)
.serialize(entry)
.saveToString();
return result;
}
+ @Override
public boolean spawnNpc(String yaml, Location pos) throws InvalidConfigurationException {
YamlConfiguration yaml2 = new YamlConfiguration();
yaml2.loadFromString(yaml);
@@ -75,6 +83,7 @@ public class ZNPCsPlusHook extends Hook {
+ this.getPlugin().getDescription().getVersion();
}
+ @Override
public Map extends Vector, ? extends List> getNpcsInArea(World world, List vectorsToCopy,
@Nullable Vector origin) {
Map> bpEntities = new HashMap<>();
@@ -101,4 +110,23 @@ public class ZNPCsPlusHook extends Hook {
}
return bpEntities;
}
+
+ /**
+ * Get a list of all the NPC IDs in this chunk
+ * @param chunk chunk
+ * @return list of NPC IDs
+ */
+ public List getNPCsInChunk(Chunk chunk) {
+ return NpcApiProvider.get().getNpcRegistry().getAll().stream()
+ .filter(npc -> npc.getNpc().getWorld().equals(chunk.getWorld())) // Only NPCs in this world
+ .filter(npc -> npc.getNpc().getLocation().toBukkitLocation(chunk.getWorld()).getChunk().equals(chunk)) // Only in this chunk
+ .map(npc -> npc.getId()) // IDs
+ .toList();
+ }
+
+ @Override
+ public void removeNPCsInChunk(Chunk chunk) {
+ getNPCsInChunk(chunk).forEach(NpcApiProvider.get().getNpcRegistry()::delete);
+ }
+
}
diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java
index d13332ed5..ca6a6cef6 100644
--- a/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java
+++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java
@@ -44,6 +44,7 @@ import world.bentobox.bentobox.api.metadata.MetaDataValue;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.BlueprintPaster;
+import world.bentobox.bentobox.blueprints.DisplayListener;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBundle;
import world.bentobox.bentobox.database.json.BentoboxTypeAdapterFactory;
@@ -113,6 +114,8 @@ public class BlueprintsManager {
gson = builder.create();
// Loaded tracker
blueprintsLoaded = new HashSet<>();
+ // Register Display listeners
+ Bukkit.getPluginManager().registerEvents(new DisplayListener(), plugin);
}
/**
diff --git a/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java b/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java
index c3ac17183..4be154439 100644
--- a/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java
+++ b/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java
@@ -43,8 +43,10 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.hooks.Hook;
import world.bentobox.bentobox.database.objects.IslandDeletion;
+import world.bentobox.bentobox.hooks.FancyNpcsHook;
import world.bentobox.bentobox.hooks.ItemsAdderHook;
import world.bentobox.bentobox.hooks.SlimefunHook;
+import world.bentobox.bentobox.hooks.ZNPCsPlusHook;
import world.bentobox.bentobox.util.MyBiomeGrid;
/**
@@ -56,9 +58,18 @@ import world.bentobox.bentobox.util.MyBiomeGrid;
public abstract class CopyWorldRegenerator implements WorldRegenerator {
private final BentoBox plugin;
+ private Optional npc;
+ private Optional znpc;
protected CopyWorldRegenerator() {
this.plugin = BentoBox.getInstance();
+ // Fancy NPCs Hook
+ npc = plugin.getHooks().getHook("FancyNpcs").filter(FancyNpcsHook.class::isInstance)
+ .map(FancyNpcsHook.class::cast);
+ // ZNPCs Plus Hook
+ znpc = plugin.getHooks().getHook("ZNPCsPlus").filter(ZNPCsPlusHook.class::isInstance)
+ .map(ZNPCsPlusHook.class::cast);
+
}
/**
@@ -179,11 +190,20 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
);
// Similarly, when the chunk is loaded, remove all the entities in the chunk apart from players
- CompletableFuture entitiesFuture = chunkFuture.thenAccept(chunk ->
- // Remove all entities in chunk, including any dropped items as a result of clearing the blocks above
- Arrays.stream(chunk.getEntities())
- .filter(e -> !(e instanceof Player) && di.inBounds(e.getLocation().getBlockX(), e.getLocation().getBlockZ()))
- .forEach(Entity::remove));
+ CompletableFuture entitiesFuture = chunkFuture.thenAccept(chunk -> {
+ // Remove all entities in chunk, including any dropped items as a result of clearing the blocks above
+ Arrays.stream(chunk.getEntities())
+ .filter(e -> !(e instanceof Player)
+ && di.inBounds(e.getLocation().getBlockX(), e.getLocation().getBlockZ()))
+ .forEach(Entity::remove);
+ // Remove any NPCs
+ // Fancy NPCs Hook
+ npc.ifPresent(hook -> hook.removeNPCsInChunk(chunk));
+ // ZNPCs Plus Hook
+ znpc.ifPresent(hook -> hook.removeNPCsInChunk(chunk));
+
+ });
+
return CompletableFuture.allOf(invFuture, entitiesFuture);
}
@@ -310,6 +330,10 @@ public abstract class CopyWorldRegenerator implements WorldRegenerator {
public CompletableFuture regenerateSimple(GameModeAddon gm, IslandDeletion di, World world) {
CompletableFuture bigFuture = new CompletableFuture<>();
+ if (world == null) {
+ bigFuture.complete(null);
+ return bigFuture;
+ }
new BukkitRunnable() {
private int chunkX = di.getMinXChunk();
private int chunkZ = di.getMinZChunk();
diff --git a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
index 2e6083026..2de5ae600 100644
--- a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
+++ b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
@@ -189,6 +189,8 @@ public class DefaultPasteUtil {
* @return true if Bukkit entity spawned, false another plugin entity spawned
*/
static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island island) {
+ // Display Entity (holograms, etc.)
+ k.setDisplay(location);
// FancyNpc entity
if (k.getNpc() != null
&& plugin.getHooks().getHook("FancyNpcs").filter(mmh -> mmh instanceof FancyNpcsHook).map(mmh -> {
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 9ddec656a..d314626f3 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -25,6 +25,8 @@ softdepend:
- EconomyPlus
- MythicMobs
- ZNPCsPlus
+ - FancyNpcs
+ - FancyHolograms
libraries:
- mysql:mysql-connector-java:${mysql.version}
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCopyCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCopyCommandTest.java
index cdcbe9bea..af3d086cd 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCopyCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCopyCommandTest.java
@@ -47,13 +47,15 @@ import world.bentobox.bentobox.managers.LocalesManager;
@PrepareForTest({Bukkit.class, BentoBox.class, User.class })
public class AdminBlueprintCopyCommandTest {
+ @Mock
+ private BentoBox plugin;
@Mock
private AdminBlueprintCommand ac;
@Mock
private GameModeAddon addon;
@Mock
private User user;
- @Mock
+
private BlueprintClipboard clip;
private UUID uuid = UUID.randomUUID();
@Mock
@@ -64,10 +66,12 @@ public class AdminBlueprintCopyCommandTest {
*/
@Before
public void setUp() throws Exception {
- // Set up plugin
- BentoBox plugin = mock(BentoBox.class);
+ // Set up plugin // Set up plugin
+ // Required for NamespacedKey
+ when(plugin.getName()).thenReturn("BentoBox");
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
+ clip = mock(BlueprintClipboard.class);
// Blueprints Manager
when(plugin.getBlueprintsManager()).thenReturn(bm);
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommandTest.java
index 284028a36..64c5963bc 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommandTest.java
@@ -76,6 +76,8 @@ public class AdminBlueprintLoadCommandTest {
@Before
public void setUp() throws Exception {
+ // Required for NamespacedKey
+ when(plugin.getName()).thenReturn("BentoBox");
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommandTest.java
index ac5386a6c..a8d382fc4 100644
--- a/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommandTest.java
+++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommandTest.java
@@ -55,6 +55,8 @@ import world.bentobox.bentobox.mocks.ServerMocks;
@PrepareForTest({Bukkit.class, BentoBox.class, User.class })
public class AdminBlueprintSaveCommandTest {
+ @Mock
+ private BentoBox plugin;
private AdminBlueprintSaveCommand absc;
@Mock
private AdminBlueprintCommand ac;
@@ -76,8 +78,8 @@ public class AdminBlueprintSaveCommandTest {
@Before
public void setUp() throws Exception {
- // Set up plugin
- BentoBox plugin = mock(BentoBox.class);
+ // Required for NamespacedKey
+ when(plugin.getName()).thenReturn("BentoBox");
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
// Hooks
HooksManager hooksManager = mock(HooksManager.class);
diff --git a/src/test/java/world/bentobox/bentobox/blueprints/BlueprintClipboardTest.java b/src/test/java/world/bentobox/bentobox/blueprints/BlueprintClipboardTest.java
index 861b0f8be..b951aa4e8 100644
--- a/src/test/java/world/bentobox/bentobox/blueprints/BlueprintClipboardTest.java
+++ b/src/test/java/world/bentobox/bentobox/blueprints/BlueprintClipboardTest.java
@@ -58,6 +58,8 @@ public class BlueprintClipboardTest {
*/
@Before
public void setUp() throws Exception {
+ // Required for NamespacedKey
+ when(plugin.getName()).thenReturn("BentoBox");
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
// Hooks
diff --git a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java
index d6e4fc354..390257852 100644
--- a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java
+++ b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java
@@ -57,7 +57,7 @@ public class BlueprintClipboardManagerTest {
@Mock
private BentoBox plugin;
- @Mock
+
private BlueprintClipboard clipboard;
private File blueprintFolder;
@@ -129,15 +129,19 @@ public class BlueprintClipboardManagerTest {
*/
@Before
public void setUp() throws Exception {
+ // Set up plugin
+ // Required for NamespacedKey
+ when(plugin.getName()).thenReturn("BentoBox");
+ Whitebox.setInternalState(BentoBox.class, "instance", plugin);
+
+ clipboard = mock(BlueprintClipboard.class);
+
server = ServerMocks.newServer();
PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
blueprintFolder = new File("blueprints");
// Clear any residual files
tearDown();
- // Set up plugin
- BentoBox plugin = mock(BentoBox.class);
- Whitebox.setInternalState(BentoBox.class, "instance", plugin);
// Hooks
HooksManager hooksManager = mock(HooksManager.class);
when(hooksManager.getHook(anyString())).thenReturn(Optional.empty());