diff --git a/.github/workflows/modrinth-publish.yml b/.github/workflows/modrinth-publish.yml
index 8244fe239..9f8b505a8 100644
--- a/.github/workflows/modrinth-publish.yml
+++ b/.github/workflows/modrinth-publish.yml
@@ -21,8 +21,8 @@ jobs:
cache: maven
# This step will take the version tag from the release and replace it in `pom.xml` before building.
- - name: Set version from release tag
- run: mvn -B versions:set -DnewVersion=${{ github.event.release.tag_name }} -DgenerateBackupPoms=false
+ #- name: Set version from release tag
+ # run: mvn -B versions:set -DnewVersion=${{ github.event.release.tag_name }} -DgenerateBackupPoms=false
- name: Build and package with Maven
run: mvn -B clean package --file pom.xml
diff --git a/pom.xml b/pom.xml
index ffb166b74..1e46541fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,7 +59,7 @@
UTF-8
UTF-8
- 17
+ 21
2.0.9
@@ -84,7 +84,7 @@
-LOCAL
- 3.1.1
+ 3.2.0
bentobox-world
https://sonarcloud.io
${project.basedir}/lib
@@ -161,12 +161,6 @@
papermc
https://repo.papermc.io/repository/maven-public/
-
-
- maven-snapshots
- https://repository.apache.org/content/repositories/snapshots/
-
minecraft-repo
https://libraries.minecraft.net/
@@ -198,6 +192,12 @@
FancyPlugins Repository
https://repo.fancyplugins.de/releases
+
+
+ pyr-snapshots
+ Pyr's Repo
+ https://repo.pyr.lol/snapshots
+
@@ -233,6 +233,13 @@
4.2.2
test
+
+
+ io.papermc.paper
+ paper-api
+ ${paper.version}
+ provided
+
org.spigotmc
@@ -357,13 +364,6 @@
org.eclipse.jdt.annotation
2.2.600
-
-
- io.papermc
- paperlib
- 1.0.6
- compile
-
com.github.apachezy
@@ -406,6 +406,20 @@
2.4.0
provided
+
+
+ lol.pyr
+ znpcsplus-api
+ 2.0.0-SNAPSHOT
+ provided
+
+
+
+ de.oliver
+ FancyHolograms
+ 2.4.1
+ provided
+
@@ -454,7 +468,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.0.0-M5
+ 3.5.2
@@ -528,7 +542,7 @@
org.apache.maven.plugins
maven-shade-plugin
- 3.4.0
+ 3.6.0
true
${project.build.directory}/dependency-reduced-pom.xml
diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java
index ff315d7de..1cb28844a 100644
--- a/src/main/java/world/bentobox/bentobox/BentoBox.java
+++ b/src/main/java/world/bentobox/bentobox/BentoBox.java
@@ -19,7 +19,6 @@
import world.bentobox.bentobox.api.configuration.Config;
import world.bentobox.bentobox.api.events.BentoBoxReadyEvent;
-import world.bentobox.bentobox.api.hooks.Hook;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.Notifier;
import world.bentobox.bentobox.api.user.User;
@@ -33,6 +32,7 @@
import world.bentobox.bentobox.hooks.MythicMobsHook;
import world.bentobox.bentobox.hooks.SlimefunHook;
import world.bentobox.bentobox.hooks.VaultHook;
+import world.bentobox.bentobox.hooks.ZNPCsPlusHook;
import world.bentobox.bentobox.hooks.placeholders.PlaceholderAPIHook;
import world.bentobox.bentobox.listeners.BannedCommands;
import world.bentobox.bentobox.listeners.BlockEndDragon;
@@ -196,6 +196,8 @@ private void completeSetup(long loadTime) {
// FancyNpcs
hooksManager.registerHook(new FancyNpcsHook());
+ // ZNPCsPlus
+ hooksManager.registerHook(new ZNPCsPlusHook());
// MythicMobs
hooksManager.registerHook(new MythicMobsHook());
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..fdcbca181
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/api/hooks/NPCHook.java
@@ -0,0 +1,39 @@
+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 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 dc10da175..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 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;
@@ -45,6 +40,7 @@
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.hooks.FancyNpcsHook;
import world.bentobox.bentobox.hooks.MythicMobsHook;
+import world.bentobox.bentobox.hooks.ZNPCsPlusHook;
/**
* The clipboard provides the holding spot for an active blueprint that is being
@@ -56,6 +52,10 @@
*/
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;
@@ -71,6 +71,8 @@ public class BlueprintClipboard {
private final BentoBox plugin = BentoBox.getInstance();
private Optional mmh;
private Optional npc;
+ private Optional znpc;
+
/**
* Create a clipboard for blueprint
@@ -82,12 +84,15 @@ public BlueprintClipboard(@NonNull Blueprint blueprint) {
}
public BlueprintClipboard() {
- // Citizens Hook
+ // Fancy NPCs Hook
npc = plugin.getHooks().getHook("FancyNpcs").filter(FancyNpcsHook.class::isInstance)
.map(FancyNpcsHook.class::cast);
// MythicMobs Hook
mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance)
.map(MythicMobsHook.class::cast);
+ // ZNPCs Plus Hook
+ znpc = plugin.getHooks().getHook("ZNPCsPlus").filter(ZNPCsPlusHook.class::isInstance)
+ .map(ZNPCsPlusHook.class::cast);
}
/**
@@ -143,6 +148,10 @@ private void copyAsync(World world, User user, List vectorsToCopy, int s
// 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));
+ }
// Repeating copy task
copyTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
@@ -154,9 +163,9 @@ private void copyAsync(World world, User user, List vectorsToCopy, int s
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++;
@@ -222,7 +231,6 @@ private boolean copyBlock(Location l, boolean copyAir, boolean copyBiome, List());
@@ -276,11 +283,9 @@ private BlueprintBlock bluePrintBlock(Vector pos, Block block, boolean copyBiome
}
}
}
-
if (blockState instanceof CreatureSpawner spawner) {
b.setCreatureSpawner(getSpawner(spawner));
}
-
// Banners
if (blockState instanceof Banner banner) {
b.setBannerPatterns(banner.getPatterns());
@@ -309,62 +314,15 @@ private BlueprintCreatureSpawner getSpawner(CreatureSpawner spawner) {
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/BlueprintPaster.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java
index 8ba1cb498..06a53e40a 100644
--- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java
+++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java
@@ -240,7 +240,7 @@ private void pasteEntities(Bits bits, int count, Optional owner, int paste
int x = location.getBlockX() + entry.getKey().getBlockX();
int y = location.getBlockY() + entry.getKey().getBlockY();
int z = location.getBlockZ() + entry.getKey().getBlockZ();
- Location center = new Location(world, x, y, z).add(new Vector(0.5, 0.5, 0.5));
+ Location center = new Location(world, x, y, z).add(new Vector(0.5, 0D, 0.5));
List entities = entry.getValue();
entityMap.put(center, entities);
count++;
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;
+ /**
+ * Item Display Entity store
+ * @since 3.2.0
+ */
+ public record ItemDispRec(@Expose ItemStack item, @Expose ItemDisplayTransform itemDispTrans) {}
+
+ /**
+ * 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
- String MMStance;
+ private Boolean adult;
@Expose
- Float MMpower;
+ public BlueprintBlock blockDisp;
@Expose
- private DyeColor color;
+ private Boolean chest;
@Expose
- private EntityType type;
+ private DyeColor color;
@Expose
private String customName;
@Expose
- private Boolean tamed;
- @Expose
- private Boolean chest;
- @Expose
- private Boolean adult;
+ public DisplayRec displayRec;
@Expose
private Integer domestication;
@Expose
+ private Integer experience;
+ @Expose
private Map inventory;
@Expose
- private Style style;
+ 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 Integer experience;
+ 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,19 +258,21 @@ public void configureEntity(Entity e) {
if (style != null && e instanceof Horse horse) {
horse.setStyle(style);
}
+ // 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));
}
-
/**
- * @param v - villager
- * @since 1.16.0
+ * @return the adult
*/
- 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);
+ public Boolean getAdult() {
+ return adult;
+ }
+ /**
+ * @return the chest
+ */
+ public Boolean getChest() {
+ return chest;
}
-
/**
* @return the color
*/
@@ -122,100 +280,224 @@ public DyeColor getColor() {
return color;
}
/**
- * @param color the color to set
+ * @return the customName
*/
- public void setColor(DyeColor color) {
- this.color = color;
+ public String getCustomName() {
+ return customName;
}
/**
- * @return the type
+ * @return the domestication
*/
- public EntityType getType() {
- return type;
+ public Integer getDomestication() {
+ return domestication;
}
/**
- * @param type the type to set
+ * @return the experience
*/
- public void setType(EntityType type) {
- this.type = type;
+ public Integer getExperience() {
+ return experience;
}
/**
- * @return the customName
+ * @return the inventory
*/
- public String getCustomName() {
- return customName;
+ public Map getInventory() {
+ return inventory;
}
/**
- * @param customName the customName to set
+ * @return the level
*/
- public void setCustomName(String customName) {
- this.customName = customName;
+ 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
+ */
+ public Style getStyle() {
+ return style;
}
+
/**
* @return the tamed
*/
public Boolean getTamed() {
return tamed;
}
+
/**
- * @param tamed the tamed to set
+ * @return the type
*/
- public void setTamed(Boolean tamed) {
- this.tamed = tamed;
+ public EntityType getType() {
+ return type;
}
+
/**
- * @return the chest
+ * @return the villagerType
*/
- public Boolean getChest() {
- return chest;
+ public Villager.Type getVillagerType() {
+ return villagerType;
+ }
+
+ /**
+ * @param adult the adult to set
+ */
+ public void setAdult(Boolean adult) {
+ this.adult = adult;
}
+
/**
* @param chest the chest to set
*/
public void setChest(Boolean chest) {
this.chest = chest;
}
+
/**
- * @return the adult
+ * @param color the color to set
*/
- public Boolean getAdult() {
- return adult;
+ public void setColor(DyeColor color) {
+ this.color = color;
}
+
/**
- * @param adult the adult to set
+ * @param customName the customName to set
*/
- public void setAdult(Boolean adult) {
- this.adult = adult;
+ public void setCustomName(String customName) {
+ this.customName = customName;
}
+
/**
- * @return the domestication
- */
- public Integer getDomestication() {
- return domestication;
+ * 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;
}
+
/**
- * @return the inventory
+ * @param domestication the domestication to set
*/
- public Map getInventory() {
- return inventory;
+ public void setDomestication(Integer domestication) {
+ this.domestication = domestication;
+ }
+
+ /**
+ * @param experience the experience to set
+ */
+ public void setExperience(Integer experience) {
+ this.experience = experience;
}
+
/**
* @param inventory the inventory to set
*/
public void setInventory(Map inventory) {
this.inventory = inventory;
}
+
/**
- * @return the style
+ * @param level the level to set
*/
- public Style getStyle() {
- return style;
+ public void setLevel(Integer level) {
+ this.level = level;
+ }
+
+ /**
+ * @param mmr the mythicMobsRecord to set
+ * @since 2.1.0
+ */
+ public void setMythicMobsRecord(MythicMobRecord mmr) {
+ this.setCustomName(mmr.displayName());
+ this.MMtype = mmr.type();
+ this.MMLevel = mmr.level();
+ this.MMStance = mmr.stance();
+ this.MMpower = mmr.power();
+ }
+ /**
+ * @param npc the citizen to set
+ */
+ 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
@@ -225,121 +507,144 @@ public void setStyle(Style style) {
}
/**
- * @return the level
+ * @param tamed the tamed to set
*/
- public Integer getLevel() {
- return level;
+ public void setTamed(Boolean tamed) {
+ this.tamed = tamed;
}
/**
- * @param level the level to set
+ * @param type the type to set
*/
- public void setLevel(Integer level) {
- this.level = level;
+ public void setType(EntityType type) {
+ this.type = type;
}
/**
- * @return the profession
+ * @param v - villager
+ * @since 1.16.0
*/
- public Profession getProfession() {
- return profession;
+ 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 profession the profession to set
+ * @param villagerType the villagerType to set
*/
- public void setProfession(Profession profession) {
- this.profession = profession;
+ public void setVillagerType(Villager.Type villagerType) {
+ this.villagerType = villagerType;
}
/**
- * @return the experience
+ * 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 Integer getExperience() {
- return experience;
+ public boolean isGlowing() {
+ return glowing;
}
/**
- * @param experience the experience to set
+ * @param glowing the glowing to set
*/
- public void setExperience(Integer experience) {
- this.experience = experience;
+ public void setGlowing(boolean glowing) {
+ this.glowing = glowing;
}
/**
- * @return the villagerType
+ * @return the gravity
*/
- public Villager.Type getVillagerType() {
- return villagerType;
+ public boolean isGravity() {
+ return gravity;
}
/**
- * @param villagerType the villagerType to set
+ * @param gravity the gravity to set
*/
- public void setVillagerType(Villager.Type villagerType) {
- this.villagerType = villagerType;
+ public void setGravity(boolean gravity) {
+ this.gravity = gravity;
}
/**
- * @param domestication the domestication to set
+ * @return the visualFire
*/
- public void setDomestication(Integer domestication) {
- this.domestication = domestication;
+ public boolean isVisualFire() {
+ return visualFire;
}
/**
- * @return the mythicMobsRecord
+ * @param visualFire the visualFire 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 setVisualFire(boolean visualFire) {
+ this.visualFire = visualFire;
}
/**
- * @param mmr the mythicMobsRecord to set
- * @since 2.1.0
+ * @return the silent
*/
- public void setMythicMobsRecord(MythicMobRecord mmr) {
- this.setCustomName(mmr.displayName());
- this.MMtype = mmr.type();
- this.MMLevel = mmr.level();
- this.MMStance = mmr.stance();
- this.MMpower = mmr.power();
+ public boolean isSilent() {
+ return silent;
}
/**
- * @return the npc
+ * @param silent the silent to set
*/
- public String getNpc() {
- return npc;
+ public void setSilent(boolean silent) {
+ this.silent = silent;
}
/**
- * @param citizen the citizen to set
+ * @return the invulnerable
*/
- public void setNpc(String citizen) {
- this.npc = citizen;
+ public boolean isInvulnerable() {
+ return invulnerable;
}
- @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 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/Database.java b/src/main/java/world/bentobox/bentobox/database/Database.java
index eca983ac2..8b0c4aea3 100644
--- a/src/main/java/world/bentobox/bentobox/database/Database.java
+++ b/src/main/java/world/bentobox/bentobox/database/Database.java
@@ -167,8 +167,8 @@ public static Set> getDataobjects() {
}
/**
- * Load all objects async
- * @return CompletableFuture>
+ * Load all objects asynchronously.
+ * @return {@code CompletableFuture>}
*/
public @NonNull CompletableFuture> loadObjectsASync() {
return handler.loadObjectsASync();
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 @@
/**
* 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..528289e24 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.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
+import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
@@ -31,7 +32,7 @@
import de.oliver.fancynpcs.api.utils.SkinFetcher;
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 +41,13 @@
* @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 +266,26 @@ public String getFailureCause() {
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 +311,5 @@ public String getFailureCause() {
}
return bpEntities;
}
+
}
diff --git a/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java b/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java
new file mode 100644
index 000000000..9a538d7a5
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java
@@ -0,0 +1,132 @@
+package world.bentobox.bentobox.hooks;
+
+import java.util.ArrayList;
+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;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.util.Vector;
+import org.eclipse.jdt.annotation.Nullable;
+
+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.NPCHook;
+import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
+import world.bentobox.bentobox.util.Util;
+
+/**
+ * Provides copy and pasting of ZNPCS Plus in blueprints https://github.com/Pyrbu/ZNPCsPlus
+ *
+ * @author tastybento
+ * @since 3.2.0
+ */
+public class ZNPCsPlusHook extends NPCHook {
+
+ private static final String VERSION = "2.0.0-SNAPSHOT"; // Minimum version required
+
+ public ZNPCsPlusHook() {
+ super("ZNPCsPlus", Material.PLAYER_HEAD);
+ }
+
+ /**
+ * 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);
+ NpcEntry entry = NpcApiProvider.get().getNpcSerializerRegistry().getSerializer(YamlConfiguration.class)
+ .deserialize(yaml2);
+ NpcLocation loc = new NpcLocation(pos);
+ entry.getNpc().setLocation(loc);
+ NpcApiProvider.get().getNpcRegistry().register(entry);
+
+ return true;
+ }
+
+ @Override
+ public boolean hook() {
+ boolean hooked = this.isPluginAvailable();
+ // Check version
+ String version = this.getPlugin().getDescription().getVersion();
+ if (!Util.isVersionCompatible(version, VERSION)) {
+ return false;
+ }
+ if (!hooked) {
+ BentoBox.getInstance().logError("Could not hook into FancyNpcs");
+ }
+ return hooked;
+ }
+
+ @Override
+ public String getFailureCause() {
+ // The only failure is wrong version
+ return "ZNPCsPlus version " + VERSION + " required or later. You are running "
+ + this.getPlugin().getDescription().getVersion();
+ }
+
+ @Override
+ public Map extends Vector, ? extends List> getNpcsInArea(World world, List vectorsToCopy,
+ @Nullable Vector origin) {
+ Map> bpEntities = new HashMap<>();
+
+ for (NpcEntry npcEntry : NpcApiProvider.get().getNpcRegistry().getAll()) {
+ NpcLocation npcLocation = npcEntry.getNpc().getLocation();
+ Vector loc = new Vector(npcLocation.getBlockX(), npcLocation.getBlockY(), npcLocation.getBlockZ());
+ if (npcEntry.getNpc().getWorld().equals(world) && vectorsToCopy.contains(loc)) {
+ // Put the NPC into a BlueprintEntity - serialize it
+ BlueprintEntity cit = new BlueprintEntity();
+ cit.setNpc(this.serializeNPC(npcEntry, origin));
+ // Retrieve or create the list of entities and add this one
+ List entities = bpEntities.getOrDefault(loc, new ArrayList<>());
+ entities.add(cit);
+ // Create the position where this entity will be pasted relative to the location
+ Vector origin2 = origin == null ? new Vector(0, 0, 0) : origin;
+ int x = loc.getBlockX() - origin2.getBlockX();
+ int y = loc.getBlockY() - origin2.getBlockY();
+ int z = loc.getBlockZ() - origin2.getBlockZ();
+ Vector pos = new Vector(x, y, z);
+ // Store
+ bpEntities.put(pos, entities); // Update the map
+ }
+ }
+ 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/listeners/flags/protection/LockAndBanListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java
index 6a41cb2ca..9295eb8a7 100644
--- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java
+++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java
@@ -13,11 +13,11 @@
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
-import io.papermc.lib.PaperLib;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.lists.Flags;
+import world.bentobox.bentobox.util.Util;
/**
* Listener for the lock flag
@@ -164,7 +164,7 @@ private void eject(Player player) {
// We'll try to teleport him to the spawn...
Location l = player.getWorld().getSpawnLocation();
if (l != null) {
- PaperLib.teleportAsync(player, l);
+ Util.teleportAsync(player, l);
}
// Switch him back to the default gamemode. He may die, sorry :(
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.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 BlueprintsManager(@NonNull BentoBox plugin) {
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/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
index 53c25b4ba..30037e739 100644
--- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
+++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
@@ -40,7 +40,6 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import io.papermc.lib.PaperLib;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent;
@@ -1080,7 +1079,7 @@ private CompletableFuture homeTeleportAsync(@NonNull World world, @NonN
.ifFail(() -> goingHome.remove(user.getUniqueId())).buildFuture().thenAccept(result::complete);
return;
}
- PaperLib.teleportAsync(Objects.requireNonNull(player), home).thenAccept(b -> {
+ Util.teleportAsync(Objects.requireNonNull(player), home).thenAccept(b -> {
// Only run the commands if the player is successfully teleported
if (Boolean.TRUE.equals(b)) {
teleported(world, user, name, newIsland, island);
@@ -1192,7 +1191,6 @@ public void spawnTeleport(@NonNull World world, @NonNull Player player) {
*
* @param player player
*/
- @SuppressWarnings("deprecation")
private void readyPlayer(@NonNull Player player) {
// Stop any gliding
player.setGliding(false);
@@ -1469,7 +1467,7 @@ public void removePlayersFromIsland(Island island) {
} else {
// Move player to spawn
getSpawn(w).map(i -> i.getSpawnPoint(w.getEnvironment())).filter(Objects::nonNull)
- .ifPresentOrElse(sp -> PaperLib.teleportAsync(p, sp),
+ .ifPresentOrElse(sp -> Util.teleportAsync(p, sp),
() -> plugin.logWarning("Spawn exists but its location is null!"));
}
diff --git a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java
index 198e8950c..59aec708d 100644
--- a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java
+++ b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java
@@ -15,9 +15,9 @@
import world.bentobox.bentobox.api.events.island.IslandCreateEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
+import world.bentobox.bentobox.api.events.island.IslandResetEvent;
import world.bentobox.bentobox.api.logs.LogEntry;
import world.bentobox.bentobox.api.logs.LogEntry.LogType;
-import world.bentobox.bentobox.api.events.island.IslandResetEvent;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.BlueprintsManager;
diff --git a/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java b/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java
index c3ac17183..dec80a8e3 100644
--- a/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java
+++ b/src/main/java/world/bentobox/bentobox/nms/CopyWorldRegenerator.java
@@ -38,14 +38,16 @@
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import io.papermc.lib.PaperLib;
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;
+import world.bentobox.bentobox.util.Util;
/**
* Regenerates by using a seed world. The seed world is created using the same generator as the game
@@ -56,9 +58,18 @@
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);
+
}
/**
@@ -137,7 +148,7 @@ private CompletableFuture regenerateChunk(@Nullable IslandDeletion di, @No
CompletableFuture seedWorldFuture = getSeedWorldChunk(world, chunkX, chunkZ);
// Set up a future to get the chunk requests using Paper's Lib. If Paper is used, this should be done async
- CompletableFuture chunkFuture = PaperLib.getChunkAtAsync(world, chunkX, chunkZ);
+ CompletableFuture chunkFuture = Util.getChunkAtAsync(world, chunkX, chunkZ);
// If there is no island, do not clean chunk
CompletableFuture cleanFuture = di != null ? cleanChunk(chunkFuture, di) : CompletableFuture.completedFuture(null);
@@ -161,7 +172,7 @@ private CompletableFuture regenerateChunk(@Nullable IslandDeletion di, @No
private CompletableFuture getSeedWorldChunk(World world, int chunkX, int chunkZ) {
World seed = Bukkit.getWorld(world.getName() + "/bentobox");
if (seed == null) return CompletableFuture.completedFuture(null);
- return PaperLib.getChunkAtAsync(seed, chunkX, chunkZ);
+ return Util.getChunkAtAsync(seed, chunkX, chunkZ);
}
/**
@@ -179,11 +190,20 @@ private CompletableFuture cleanChunk(CompletableFuture chunkFuture,
);
// 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);
}
@@ -227,6 +247,7 @@ private void copyChunkDataToChunk(Chunk toChunk, Chunk fromChunk, BoundingBox li
Arrays.stream(fromChunk.getTileEntities()).forEach(bs -> processTileEntity(bs.getBlock(), bs.getLocation().toVector().toLocation(toChunk.getWorld()).getBlock()));
}
+ @SuppressWarnings("deprecation")
private void processEntity(Entity entity, Location location) {
Entity bpe = location.getWorld().spawnEntity(location, entity.getType());
bpe.setCustomName(entity.getCustomName());
@@ -310,6 +331,10 @@ private void writeSign(Sign fromSign, Sign toSign, Side side) {
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();
@@ -350,7 +375,7 @@ private boolean isEnded(int chunkX) {
@SuppressWarnings("deprecation")
private CompletableFuture regenerateChunk(GameModeAddon gm, IslandDeletion di, @Nonnull World world,
int chunkX, int chunkZ) {
- CompletableFuture chunkFuture = PaperLib.getChunkAtAsync(world, chunkX, chunkZ);
+ CompletableFuture chunkFuture = Util.getChunkAtAsync(world, chunkX, chunkZ);
CompletableFuture invFuture = chunkFuture.thenAccept(chunk ->
Arrays.stream(chunk.getTileEntities()).filter(InventoryHolder.class::isInstance)
.filter(te -> di.inBounds(te.getLocation().getBlockX(), te.getLocation().getBlockZ()))
diff --git a/src/main/java/world/bentobox/bentobox/panels/customizable/IslandHomesPanel.java b/src/main/java/world/bentobox/bentobox/panels/customizable/IslandHomesPanel.java
index c760a21d8..84e9898a3 100644
--- a/src/main/java/world/bentobox/bentobox/panels/customizable/IslandHomesPanel.java
+++ b/src/main/java/world/bentobox/bentobox/panels/customizable/IslandHomesPanel.java
@@ -60,7 +60,6 @@ public class IslandHomesPanel extends AbstractPanel
*
* @param command CompositeCommand
* @param user User who opens panel
- * @param islandMap map of island names and IslandInfo
*/
private IslandHomesPanel(@NonNull CompositeCommand command, @NonNull User user)
{
diff --git a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
index 86ee2e070..2de5ae600 100644
--- a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
+++ b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
@@ -36,6 +36,7 @@
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.hooks.FancyNpcsHook;
import world.bentobox.bentobox.hooks.MythicMobsHook;
+import world.bentobox.bentobox.hooks.ZNPCsPlusHook;
import world.bentobox.bentobox.nms.PasteHandler;
/**
@@ -176,8 +177,8 @@ public static void setSpawner(CreatureSpawner spawner, BlueprintCreatureSpawner
public static CompletableFuture setEntity(Island island, Location location, List list) {
World world = location.getWorld();
assert world != null;
- return Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null)
- .forEach(k -> spawnBlueprintEntity(k, location, island)));
+ return Util.getChunkAtAsync(location)
+ .thenRun(() -> list.stream().forEach(k -> spawnBlueprintEntity(k, location, island)));
}
/**
@@ -188,7 +189,9 @@ public static CompletableFuture setEntity(Island island, Location location
* @return true if Bukkit entity spawned, false another plugin entity spawned
*/
static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island island) {
- // Npc entity
+ // Display Entity (holograms, etc.)
+ k.setDisplay(location);
+ // FancyNpc entity
if (k.getNpc() != null
&& plugin.getHooks().getHook("FancyNpcs").filter(mmh -> mmh instanceof FancyNpcsHook).map(mmh -> {
try {
@@ -201,6 +204,19 @@ static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island
// Npc has spawned.
return false;
}
+ // ZNPCsPlus
+ if (k.getNpc() != null
+ && plugin.getHooks().getHook("ZNPCsPlus").filter(mmh -> mmh instanceof ZNPCsPlusHook).map(znpch -> {
+ try {
+ return ((ZNPCsPlusHook) znpch).spawnNpc(k.getNpc(), location);
+ } catch (InvalidConfigurationException e) {
+ plugin.logError("ZNPCsPlus loading failed in blueprint.");
+ return false;
+ }
+ }).orElse(false)) {
+ // Npc has spawned.
+ return false;
+ }
// Mythic Mobs entity
if (k.getMythicMobsRecord() != null && plugin.getHooks().getHook("MythicMobs")
@@ -210,6 +226,10 @@ static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island
// MythicMob has spawned.
return false;
}
+ if (k.getType() == null) {
+ // Nothing
+ return false;
+ }
LivingEntity e = (LivingEntity) location.getWorld().spawnEntity(location, k.getType());
if (k.getCustomName() != null) {
String customName = k.getCustomName();
diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java
index 00434d260..4aa1ccc1f 100644
--- a/src/main/java/world/bentobox/bentobox/util/Util.java
+++ b/src/main/java/world/bentobox/bentobox/util/Util.java
@@ -1,6 +1,7 @@
package world.bentobox.bentobox.util;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -26,7 +27,6 @@
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.attribute.Attribute;
-import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Allay;
import org.bukkit.entity.Animals;
@@ -51,8 +51,6 @@
import com.google.common.base.Enums;
import com.google.common.base.Optional;
-import io.papermc.lib.PaperLib;
-import io.papermc.lib.features.blockstatesnapshot.BlockStateSnapshotResult;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.nms.PasteHandler;
@@ -382,7 +380,7 @@ public static boolean isTamableEntity(Entity entity) {
*/
@NonNull
public static CompletableFuture teleportAsync(@Nonnull Entity entity, @Nonnull Location location) {
- return PaperLib.teleportAsync(entity, location);
+ return teleportAsync(entity, location, TeleportCause.UNKNOWN);
}
/**
@@ -392,12 +390,26 @@ public static CompletableFuture teleportAsync(@Nonnull Entity entity, @
* @param cause The cause for the teleportation
* @return Future that completes with the result of the teleport
*/
+ @SuppressWarnings("unchecked")
@NonNull
public static CompletableFuture teleportAsync(@Nonnull Entity entity, @Nonnull Location location,
TeleportCause cause) {
- return PaperLib.teleportAsync(entity, location, cause);
+ try {
+ // Use reflection to check if the method exists
+ Method method = Entity.class.getMethod("teleportAsync", Location.class, TeleportCause.class);
+ if (method != null) {
+ // Invoke the method using reflection on the entity instance
+ return (CompletableFuture) method.invoke(entity, location, cause);
+ }
+ } catch (NoSuchMethodException e) {
+ // Method does not exist, fallback to Spigot behavior
+ } catch (Exception e) {
+ plugin.logStacktrace(e); // Report other exceptions
+ }
+ // Fallback for Spigot servers
+ entity.teleport(location, cause);
+ return CompletableFuture.completedFuture(true);
}
-
/**
* Gets the chunk at the target location, loading it asynchronously if needed.
* @param loc Location to get chunk for
@@ -440,9 +452,24 @@ public static CompletableFuture getChunkAtAsync(@Nonnull World world, int
* @param gen Should the chunk generate or not. Only respected on some MC versions, 1.13 for CB, 1.12 for Paper
* @return Future that completes with the chunk, or null if the chunk did not exists and generation was not requested.
*/
+ @SuppressWarnings("unchecked")
@NonNull
public static CompletableFuture getChunkAtAsync(@Nonnull World world, int x, int z, boolean gen) {
- return PaperLib.getChunkAtAsync(world, x, z, gen);
+ try {
+ // Use reflection to check if the method exists
+ Method method = World.class.getMethod("getChunkAtAsync", int.class, int.class, boolean.class);
+ if (method != null) {
+ // Invoke the method using reflection
+ return (CompletableFuture) method.invoke(world, x, z, gen);
+ }
+ } catch (NoSuchMethodException e) {
+ // Method does not exist, fallback to Spigot behavior
+ } catch (Exception e) {
+ e.printStackTrace(); // Handle other exceptions (optional)
+ }
+ // Fallback for Spigot servers
+ return CompletableFuture.completedFuture(world.getChunkAt(x, z, gen));
+
}
/**
@@ -462,64 +489,55 @@ public static boolean isChunkGenerated(@NonNull Location loc) {
* @return If the chunk is generated or not
*/
public static boolean isChunkGenerated(@Nonnull World world, int x, int z) {
- return PaperLib.isChunkGenerated(world, x, z);
- }
-
- /**
- * Get's a BlockState, optionally not using a snapshot
- * @param block The block to get a State of
- * @param useSnapshot Whether or not to use a snapshot when supported
- * @return The BlockState
- */
- @NonNull
- public static BlockStateSnapshotResult getBlockState(@Nonnull Block block, boolean useSnapshot) {
- return PaperLib.getBlockState(block, useSnapshot);
+ return world.isChunkGenerated(x, z);
}
/**
- * Detects if the current MC version is at least the following version.
+ * Checks if the given version is compatible with the required version.
+ *
*
- * Assumes 0 patch version.
- *
- * @param minor Min Minor Version
- * @return Meets the version requested
- */
- public static boolean isVersion(int minor) {
- return PaperLib.isVersion(minor);
- }
-
- /**
- * Detects if the current MC version is at least the following version.
- * @param minor Min Minor Version
- * @param patch Min Patch Version
- * @return Meets the version requested
- */
- public static boolean isVersion(int minor, int patch) {
- return PaperLib.isVersion(minor, patch);
- }
-
- /**
- * Gets the current Minecraft Minor version. IE: 1.13.1 returns 13
- * @return The Minor Version
- */
- public static int getMinecraftVersion() {
- return PaperLib.getMinecraftVersion();
- }
+ * A version is considered compatible if:
+ *
+ * - The major, minor, and patch components of the given version are greater than or equal to those of the required version.
+ * - If the numeric components are equal, the absence of "-SNAPSHOT" in the given version takes precedence (i.e., release versions are considered more compatible than SNAPSHOT versions).
+ *
+ *
+ *
+ * @param version the version to check, in the format "major.minor.patch[-SNAPSHOT]".
+ * @param requiredVersion the required version, in the format "major.minor.patch[-SNAPSHOT]".
+ * @return {@code true} if the given version is compatible with the required version; {@code false} otherwise.
+ *
+ *
+ * Examples:
+ *
+ * - {@code isVersionCompatible("2.1.0", "2.0.0-SNAPSHOT")} returns {@code true}
+ * - {@code isVersionCompatible("2.0.0", "2.0.0-SNAPSHOT")} returns {@code true}
+ * - {@code isVersionCompatible("2.0.0-SNAPSHOT", "2.0.0")} returns {@code false}
+ * - {@code isVersionCompatible("1.9.9", "2.0.0-SNAPSHOT")} returns {@code false}
+ *
+ *
+ */
+ public static boolean isVersionCompatible(String version, String requiredVersion) {
+ String[] versionParts = version.replace("-SNAPSHOT", "").split("\\.");
+ String[] requiredVersionParts = requiredVersion.replace("-SNAPSHOT", "").split("\\.");
+
+ for (int i = 0; i < Math.max(versionParts.length, requiredVersionParts.length); i++) {
+ int vPart = i < versionParts.length ? Integer.parseInt(versionParts[i]) : 0;
+ int rPart = i < requiredVersionParts.length ? Integer.parseInt(requiredVersionParts[i]) : 0;
+
+ if (vPart > rPart) {
+ return true;
+ } else if (vPart < rPart) {
+ return false;
+ }
+ }
- /**
- * Gets the current Minecraft Patch version. IE: 1.13.1 returns 1
- * @return The Patch Version
- */
- public static int getMinecraftPatchVersion() {
- return PaperLib.getMinecraftPatchVersion();
- }
+ // If numeric parts are equal, prioritize SNAPSHOT as lower precedence
+ boolean isVersionSnapshot = version.contains("-SNAPSHOT");
+ boolean isRequiredSnapshot = requiredVersion.contains("-SNAPSHOT");
- /**
- * Check if the server has access to the Spigot API
- * @return True for Spigot and Paper environments
- */
- public static boolean isSpigot() {
- return PaperLib.isSpigot();
+ // If required version is a full release but current version is SNAPSHOT, it's incompatible
+ return !(!isRequiredSnapshot && isVersionSnapshot);
}
/**
@@ -527,29 +545,21 @@ public static boolean isSpigot() {
* @return True for Paper environments
*/
public static boolean isPaper() {
- return !isJUnitTest() && PaperLib.isPaper();
- }
-
- /**
- * I don't like doing this, but otherwise we need to set a flag in every test
- */
- private static boolean isJUnitTest() {
- StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
- for (StackTraceElement element : stackTrace) {
- if (element.getClassName().startsWith("org.junit.")) {
- return true;
- }
+ try {
+ Class.forName("com.destroystokyo.paper.PaperConfig");
+ return true; // Paper-specific class exists
+ } catch (ClassNotFoundException e) {
+ return false; // Not a Paper server
}
- return false;
}
-
/**
* This method translates color codes in given string and strips whitespace after them.
* This code parses both: hex and old color codes.
* @param textToColor Text which color codes must be parsed.
* @return String text with parsed colors and stripped whitespaces after them.
*/
+ @SuppressWarnings("deprecation")
@NonNull
public static String translateColorCodes(@NonNull String textToColor) {
// Use matcher to find hex patterns in given text.
@@ -712,8 +722,14 @@ public static void runCommands(User user, String ownerName, @NonNull List ser;
+ @Mock
+ private NpcRegistry registry;
+ @Mock
+ private Npc npc;
+ @Mock
+ private NpcLocation npcLoc;
+
+ /**
+ * @throws java.lang.Exception
+ */
+ @Before
+ public void setUp() throws Exception {
+ // Set up plugin
+ plugin = mock(BentoBox.class);
+ Whitebox.setInternalState(BentoBox.class, "instance", plugin);
+ // Bukkit
+ PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
+ when(Bukkit.getPluginManager()).thenReturn(pim);
+ when(npcPlugin.getDescription()).thenReturn(new PluginDescriptionFile("ZNPCsPlus", "2.0.0-SNAPSHOT", "main"));
+ when(pim.getPlugin("ZNPCsPlus")).thenReturn(npcPlugin);
+ // Location
+ when(world.getName()).thenReturn("bskyblock");
+ when(location.getWorld()).thenReturn(world);
+ // NpcApiProvider
+ PowerMockito.mockStatic(NpcApiProvider.class, Mockito.RETURNS_MOCKS);
+ when(NpcApiProvider.get()).thenReturn(npcApi);
+
+ when(registry.getAll()).thenAnswer(invocation -> List.of(entry));
+
+ when(npcLoc.getBlockX()).thenReturn(0);
+ when(npcLoc.getBlockY()).thenReturn(0);
+ when(npcLoc.getBlockZ()).thenReturn(0);
+ when(npc.getWorld()).thenReturn(world);
+
+ when(npc.getLocation()).thenReturn(npcLoc);
+
+ when(npcApi.getNpcRegistry()).thenReturn(registry);
+ when(npcApi.getNpcSerializerRegistry()).thenReturn(npcSerReg);
+ when(npcSerReg.getSerializer(any())).thenReturn(ser);
+ YamlConfiguration yaml = new YamlConfiguration();
+ yaml.set("test", "test");
+ when(ser.serialize(any())).thenReturn(yaml);
+ when(entry.getNpc()).thenReturn(npc);
+ when(ser.deserialize(any())).thenReturn(entry);
+
+
+ hook = new ZNPCsPlusHook();
+ }
+
+
+ /**
+ * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#hook()}.
+ */
+ @Test
+ public void testHook() {
+ // Not hooked
+ assertFalse(hook.hook());
+ }
+
+ /**
+ * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#getFailureCause()}.
+ */
+ @Test
+ public void testGetFailureCause() {
+ when(npcPlugin.getDescription()).thenReturn(new PluginDescriptionFile("ZNPCsPlus", "1.0.0", "main"));
+ assertEquals("ZNPCsPlus version 2.0.0-SNAPSHOT required or later. You are running 1.0.0",
+ hook.getFailureCause());
+ }
+
+ /**
+ * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#ZNPCsPlusHook()}.
+ */
+ @Test
+ public void testZNPCsPlusHook() {
+ assertNotNull(hook);
+ assertEquals(Material.PLAYER_HEAD, hook.getIcon());
+ }
+
+ /**
+ * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#serializeNPC(lol.pyr.znpcsplus.api.npc.NpcEntry, org.bukkit.util.Vector)}.
+ */
+ @Test
+ public void testSerializeNPC() {
+ assertEquals("test: test\n", hook.serializeNPC(entry, new Vector(1, 1, 1)));
+ }
+
+ /**
+ * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#spawnNpc(java.lang.String, org.bukkit.Location)}.
+ */
+ @Test
+ public void testSpawnNpc() {
+ try {
+ assertTrue(hook.spawnNpc("", location));
+ } catch (InvalidConfigurationException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Test method for {@link world.bentobox.bentobox.hooks.ZNPCsPlusHook#getNpcsInArea(org.bukkit.World, java.util.List, org.bukkit.util.Vector)}.
+ */
+ @Test
+ public void testGetNpcsInArea() {
+ hook.getNpcsInArea(world, List.of(new Vector(0, 0, 0)), new Vector(0, 0, 0));
+ }
+
+}
diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListenerTest.java
index 6a14ef448..c85479552 100644
--- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListenerTest.java
+++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListenerTest.java
@@ -338,7 +338,7 @@ public void testOnVehicleDamageEventNotAllowedBoat() {
when(island.isAllowed(any(), any())).thenReturn(false);
Vehicle vehicle = mock(Vehicle.class);
when(vehicle.getLocation()).thenReturn(location);
- when(vehicle.getType()).thenReturn(EntityType.OAK_BOAT);
+ when(vehicle.getType()).thenReturn(EntityType.BOAT);
VehicleDamageEvent e = new VehicleDamageEvent(vehicle, mockPlayer, 10);
bbl.onVehicleDamageEvent(e);
assertTrue(e.isCancelled());
diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListenerTest.java
index fc7181d3f..a4a1139b3 100644
--- a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListenerTest.java
+++ b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListenerTest.java
@@ -181,7 +181,7 @@ public void setUp() throws Exception {
when(top.getSize()).thenReturn(9);
when(panel.getInventory()).thenReturn(top);
- when(Bukkit.createInventory(any(), anyInt(), any())).thenReturn(top);
+ when(Bukkit.createInventory(any(), anyInt(), anyString())).thenReturn(top);
}
@After
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 @@ private void zip(File targetFile) throws IOException {
*/
@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());
diff --git a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java
index c64cbf844..74f6cc514 100644
--- a/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java
+++ b/src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java
@@ -74,9 +74,6 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
-import io.papermc.lib.PaperLib;
-import io.papermc.lib.environments.CraftBukkitEnvironment;
-import io.papermc.lib.environments.Environment;
import world.bentobox.bentobox.AbstractCommonSetup;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
@@ -148,8 +145,6 @@ public class IslandsManagerTest extends AbstractCommonSetup {
private Material sign;
private Material wallSign;
- private Environment env;
-
// Class under test
IslandsManager im;
@@ -170,7 +165,7 @@ public static void beforeClass() throws IllegalAccessException, InvocationTarget
}
@Override
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({ "unchecked", "deprecation" })
@Before
public void setUp() throws Exception {
super.setUp();
@@ -350,10 +345,6 @@ public void setUp() throws Exception {
sign = Material.BIRCH_SIGN;
wallSign = Material.ACACIA_WALL_SIGN;
- // PaperLib
- env = new CraftBukkitEnvironment();
- PaperLib.setCustomEnvironment(env);
-
// Util strip spaces
when(Util.stripSpaceAfterColorCodes(anyString())).thenCallRealMethod();
@@ -423,7 +414,6 @@ public void testIsSafeLocationSubmerged() {
assertFalse(im.isSafeLocation(location));
}
- @SuppressWarnings("deprecation")
@Test
@Ignore("Material#isSolid() cannot be tested")
public void testCheckIfSafeTrapdoor() {
diff --git a/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java
index d550ca413..1716a5394 100644
--- a/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java
+++ b/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java
@@ -181,7 +181,7 @@ public void setUp() throws Exception {
when(p.getUniqueId()).thenReturn(uuid);
AttributeInstance at = mock(AttributeInstance.class);
when(at.getValue()).thenReturn(20D);
- when(p.getAttribute(Attribute.MAX_HEALTH)).thenReturn(at);
+ when(p.getAttribute(Attribute.GENERIC_MAX_HEALTH)).thenReturn(at);
when(p.getName()).thenReturn("tastybento");
User.getInstance(p);
diff --git a/src/test/java/world/bentobox/bentobox/mocks/ServerMocks.java b/src/test/java/world/bentobox/bentobox/mocks/ServerMocks.java
index 8fd039318..282caceba 100644
--- a/src/test/java/world/bentobox/bentobox/mocks/ServerMocks.java
+++ b/src/test/java/world/bentobox/bentobox/mocks/ServerMocks.java
@@ -1,5 +1,6 @@
package world.bentobox.bentobox.mocks;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
@@ -21,10 +22,26 @@
import org.bukkit.Tag;
import org.bukkit.UnsafeValues;
import org.eclipse.jdt.annotation.NonNull;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import io.papermc.paper.ServerBuildInfo;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(ServerBuildInfo.class)
public final class ServerMocks {
+ @Mock
+ private static ServerBuildInfo sbi;
public static @NonNull Server newServer() {
+ PowerMockito.mockStatic(ServerBuildInfo.class, Mockito.RETURNS_MOCKS);
+ when(sbi.asString(any())).thenReturn("Mock server version");
+ when(ServerBuildInfo.buildInfo()).thenReturn(sbi);
+
Server mock = mock(Server.class);
Logger noOp = mock(Logger.class);
@@ -66,7 +83,7 @@ public final class ServerMocks {
doReturn(key).when(keyed).getKey();
return keyed;
});
- }).when(registry).get(notNull());
+ }).when(registry).get((NamespacedKey) notNull());
return registry;
})).when(mock).getRegistry(notNull());
diff --git a/src/test/java/world/bentobox/bentobox/panels/BlueprintManagementPanelTest.java b/src/test/java/world/bentobox/bentobox/panels/BlueprintManagementPanelTest.java
index aa0b06016..f0101694b 100644
--- a/src/test/java/world/bentobox/bentobox/panels/BlueprintManagementPanelTest.java
+++ b/src/test/java/world/bentobox/bentobox/panels/BlueprintManagementPanelTest.java
@@ -80,7 +80,7 @@ public void setUp() throws Exception {
ItemFactory itemFac = mock(ItemFactory.class);
when(Bukkit.getItemFactory()).thenReturn(itemFac);
// Panel inventory
- when(Bukkit.createInventory(any(), Mockito.anyInt(), any())).thenReturn(inv);
+ when(Bukkit.createInventory(any(), Mockito.anyInt(), anyString())).thenReturn(inv);
// Player
Player player = mock(Player.class);
diff --git a/src/test/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanelTest.java b/src/test/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanelTest.java
index 0f8bb9433..c94653483 100644
--- a/src/test/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanelTest.java
+++ b/src/test/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanelTest.java
@@ -37,6 +37,7 @@
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
+import io.papermc.paper.ServerBuildInfo;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.addons.GameModeAddon;
@@ -48,13 +49,14 @@
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.PlayersManager;
+import world.bentobox.bentobox.mocks.ServerMocks;
/**
* @author tastybento
*
*/
@RunWith(PowerMockRunner.class)
-@PrepareForTest({ Bukkit.class, BentoBox.class })
+@PrepareForTest({ Bukkit.class, BentoBox.class, ServerBuildInfo.class })
public class IslandCreationPanelTest {
@Mock
@@ -89,6 +91,8 @@ public class IslandCreationPanelTest {
*/
@Before
public void setUp() throws Exception {
+ ServerMocks.newServer();
+
PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
// Set up plugin
@@ -160,7 +164,8 @@ public void setUp() throws Exception {
when(plugin.getIWM()).thenReturn(iwm);
// Panel inventory
- when(Bukkit.createInventory(any(), Mockito.anyInt(), any())).thenReturn(inv);
+
+ when(Bukkit.createInventory(any(), Mockito.anyInt(), anyString())).thenReturn(inv);
// Item Factory (needed for ItemStack)
ItemFactory itemF = mock(ItemFactory.class);
@@ -202,6 +207,7 @@ public void setUp() throws Exception {
public void tearDown() {
User.clearUsers();
Mockito.framework().clearInlineMocks();
+ ServerMocks.unsetBukkitServer();
}
/**
diff --git a/src/test/java/world/bentobox/bentobox/panels/customizable/LanguagePanelTest.java b/src/test/java/world/bentobox/bentobox/panels/customizable/LanguagePanelTest.java
index 2a2ad5526..7b8639bda 100644
--- a/src/test/java/world/bentobox/bentobox/panels/customizable/LanguagePanelTest.java
+++ b/src/test/java/world/bentobox/bentobox/panels/customizable/LanguagePanelTest.java
@@ -131,7 +131,7 @@ public void setUp() throws Exception {
// Panel
PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
- when(Bukkit.createInventory(any(), Mockito.anyInt(), any())).thenReturn(inv);
+ when(Bukkit.createInventory(any(), Mockito.anyInt(), anyString())).thenReturn(inv);
// Item Factory (needed for ItemStack)
ItemFactory itemF = mock(ItemFactory.class);
diff --git a/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java b/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java
index 38135c66d..768b1ad32 100644
--- a/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java
+++ b/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java
@@ -141,6 +141,9 @@ public void setUp() throws Exception {
when(plugin.getHooks()).thenReturn(hooksManager);
when(plugin.getPlayers()).thenReturn(pm);
+
+ // Blueprint Entity
+ when(blueprintEntity.getType()).thenReturn(EntityType.PLAYER);
}
/**
diff --git a/src/test/java/world/bentobox/bentobox/util/UtilTest.java b/src/test/java/world/bentobox/bentobox/util/UtilTest.java
index 4745b6ce3..d6162354d 100644
--- a/src/test/java/world/bentobox/bentobox/util/UtilTest.java
+++ b/src/test/java/world/bentobox/bentobox/util/UtilTest.java
@@ -494,4 +494,112 @@ public void testTranslateColorCodesHex() {
assertEquals("§x§f§f§0§0§0§0full hex", Util.translateColorCodes("ff0000 full hex"));
assertEquals("ggg outside hex range", Util.translateColorCodes("ggg outside hex range"));
}
-}
+
+ /**
+ * Tests if the method returns true for identical versions without SNAPSHOT.
+ */
+ @Test
+ public void testVersionIsCompatible_SameVersion() {
+ assertTrue("Same versions should be compatible", Util.isVersionCompatible("2.0.0", "2.0.0"));
+ }
+
+ /**
+ * Tests if the method returns true for identical SNAPSHOT versions.
+ */
+ @Test
+ public void testVersionIsCompatible_SnapshotToSnapshot() {
+ assertTrue("Same SNAPSHOT versions should be compatible",
+ Util.isVersionCompatible("2.0.0-SNAPSHOT", "2.0.0-SNAPSHOT"));
+ }
+
+ /**
+ * Tests if the method considers release versions compatible with their SNAPSHOT equivalents.
+ */
+ @Test
+ public void testVersionIsCompatible_ReleaseGreaterThanSnapshot() {
+ assertTrue("Release version should be compatible with SNAPSHOT of the same version",
+ Util.isVersionCompatible("2.0.0", "2.0.0-SNAPSHOT"));
+ }
+
+ /**
+ * Tests if the method considers SNAPSHOT versions less compatible than release versions.
+ */
+ @Test
+ public void testVersionIsCompatible_SnapshotLessThanRelease() {
+ assertFalse("SNAPSHOT version should not be compatible with release of the same version",
+ Util.isVersionCompatible("2.0.0-SNAPSHOT", "2.0.0"));
+ }
+
+ /**
+ * Tests if the method correctly identifies compatibility for a higher major version.
+ */
+ @Test
+ public void testVersionIsCompatible_MajorVersionGreater() {
+ assertTrue("Higher major version should be compatible", Util.isVersionCompatible("3.0.0", "2.0.0"));
+ }
+
+ /**
+ * Tests if the method correctly identifies incompatibility for a lower major version.
+ */
+ @Test
+ public void testVersionIsCompatible_MajorVersionLower() {
+ assertFalse("Lower major version should not be compatible", Util.isVersionCompatible("1.9.9", "2.0.0"));
+ }
+
+ /**
+ * Tests if the method correctly identifies compatibility for a higher minor version.
+ */
+ @Test
+ public void testVersionIsCompatible_MinorVersionGreater() {
+ assertTrue("Higher minor version should be compatible", Util.isVersionCompatible("2.1.0", "2.0.0"));
+ }
+
+ /**
+ * Tests if the method correctly identifies incompatibility for a lower minor version.
+ */
+ @Test
+ public void testVersionIsCompatible_MinorVersionLower() {
+ assertFalse("Lower minor version should not be compatible", Util.isVersionCompatible("2.0.0", "2.1.0"));
+ }
+
+ /**
+ * Tests if the method correctly identifies compatibility for a higher patch version.
+ */
+ @Test
+ public void testVersionIsCompatible_PatchVersionGreater() {
+ assertTrue("Higher patch version should be compatible", Util.isVersionCompatible("2.0.1", "2.0.0"));
+ }
+
+ /**
+ * Tests if the method correctly identifies incompatibility for a lower patch version.
+ */
+ @Test
+ public void testVersionIsCompatible_PatchVersionLower() {
+ assertFalse("Lower patch version should not be compatible", Util.isVersionCompatible("2.0.0", "2.0.1"));
+ }
+
+ /**
+ * Tests if the method correctly handles compatibility when both versions have a SNAPSHOT suffix.
+ */
+ @Test
+ public void testVersionIsCompatible_HandlesSnapshotSuffix() {
+ assertTrue("Higher patch version (SNAPSHOT) should be compatible with lower patch version (SNAPSHOT)",
+ Util.isVersionCompatible("2.0.1-SNAPSHOT", "2.0.0-SNAPSHOT"));
+ }
+
+ /**
+ * Tests if the method throws an exception for an empty version string.
+ */
+ @Test(expected = NumberFormatException.class)
+ public void testVersionIsCompatible_EmptyVersion() {
+ Util.isVersionCompatible("", "2.0.0");
+ }
+
+ /**
+ * Tests if the method throws an exception for a null version string.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testVersionIsCompatible_NullVersion() {
+ Util.isVersionCompatible(null, "2.0.0");
+ }
+}
\ No newline at end of file