diff --git a/pom.xml b/pom.xml
index d932b91ca..e29d7d061 100644
--- a/pom.xml
+++ b/pom.xml
@@ -198,6 +198,12 @@
FancyPlugins Repositoryhttps://repo.fancyplugins.de/releases
+
+
+ pyr-snapshots
+ Pyr's Repo
+ https://repo.pyr.lol/snapshots
+
@@ -406,6 +412,13 @@
2.4.0provided
+
+
+ lol.pyr
+ znpcsplus-api
+ 2.0.0-SNAPSHOT
+ provided
+
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 org.eclipse.jdt.annotation.Nullable;
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.MyWorldsHook;
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 @@ public class BentoBox extends JavaPlugin implements Listener {
// FancyNpcs
hooksManager.registerHook(new FancyNpcsHook());
+ // ZNPCsPlus
+ hooksManager.registerHook(new ZNPCsPlusHook());
// MythicMobs
hooksManager.registerHook(new MythicMobsHook());
diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java
index dc10da175..6d678bd08 100644
--- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java
+++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java
@@ -45,6 +45,7 @@ import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
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
@@ -71,6 +72,7 @@ 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 class BlueprintClipboard {
}
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,9 @@ 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));
}
+ if (znpc.isPresent()) {
+ bpEntities.putAll(znpc.get().getNpcsInArea(world, vectorsToCopy, origin));
+ }
// Repeating copy task
copyTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
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 @@ public class BlueprintPaster {
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/hooks/ZNPCsPlusHook.java b/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java
new file mode 100644
index 000000000..d9a31dfef
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/hooks/ZNPCsPlusHook.java
@@ -0,0 +1,104 @@
+package world.bentobox.bentobox.hooks;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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.Hook;
+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 Hook {
+
+ private static final String VERSION = "2.0.0-SNAPSHOT"; // Minimum version required
+
+ public ZNPCsPlusHook() {
+ super("ZNPCsPlus", Material.PLAYER_HEAD);
+ }
+
+ public String serializeNPC(NpcEntry entry, Vector origin) {
+ String result = NpcApiProvider.get().getNpcSerializerRegistry().getSerializer(YamlConfiguration.class)
+ .serialize(entry)
+ .saveToString();
+ return result;
+ }
+
+ 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();
+ }
+
+ 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;
+ }
+}
diff --git a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
index 86ee2e070..2e6083026 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.blueprints.dataobjects.BlueprintEntity;
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 class DefaultPasteUtil {
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,7 @@ public class DefaultPasteUtil {
* @return true if Bukkit entity spawned, false another plugin entity spawned
*/
static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island island) {
- // Npc entity
+ // FancyNpc entity
if (k.getNpc() != null
&& plugin.getHooks().getHook("FancyNpcs").filter(mmh -> mmh instanceof FancyNpcsHook).map(mmh -> {
try {
@@ -201,6 +202,19 @@ public class DefaultPasteUtil {
// 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 +224,10 @@ public class DefaultPasteUtil {
// 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..3cf2fac55 100644
--- a/src/main/java/world/bentobox/bentobox/util/Util.java
+++ b/src/main/java/world/bentobox/bentobox/util/Util.java
@@ -514,6 +514,54 @@ public class Util {
return PaperLib.getMinecraftPatchVersion();
}
+ /**
+ * Checks if the given version is compatible with the required version.
+ *
+ *
+ * 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.
+ *
+ *