diff --git a/pom.xml b/pom.xml
index 243d6dfbe..c8ed60cb8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -84,7 +84,7 @@
-LOCAL
- 3.0.1
+ 3.1.0
bentobox-world
https://sonarcloud.io
${project.basedir}/lib
@@ -192,6 +192,12 @@
clojars
https://repo.clojars.org/
+
+
+ fancyplugins-releases
+ FancyPlugins Repository
+ https://repo.fancyplugins.de/releases
+
@@ -393,6 +399,13 @@
1.1.13
compile
+
+
+ de.oliver
+ FancyNpcs
+ 2.4.0
+ provided
+
diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java
index daa0b91dd..ff315d7de 100644
--- a/src/main/java/world/bentobox/bentobox/BentoBox.java
+++ b/src/main/java/world/bentobox/bentobox/BentoBox.java
@@ -19,11 +19,13 @@ 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;
import world.bentobox.bentobox.commands.BentoBoxCommand;
import world.bentobox.bentobox.database.DatabaseSetup;
+import world.bentobox.bentobox.hooks.FancyNpcsHook;
import world.bentobox.bentobox.hooks.ItemsAdderHook;
import world.bentobox.bentobox.hooks.MultipaperHook;
import world.bentobox.bentobox.hooks.MultiverseCoreHook;
@@ -192,6 +194,9 @@ public class BentoBox extends JavaPlugin implements Listener {
hooksManager.registerHook(new VaultHook());
+ // FancyNpcs
+ hooksManager.registerHook(new FancyNpcsHook());
+
// 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 d189f2df0..dc10da175 100644
--- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java
+++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java
@@ -2,7 +2,6 @@ package world.bentobox.bentobox.blueprints;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -23,8 +22,8 @@ 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.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.Villager;
@@ -44,6 +43,7 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
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;
/**
@@ -70,20 +70,22 @@ public class BlueprintClipboard {
private final Map bpBlocks = new LinkedHashMap<>();
private final BentoBox plugin = BentoBox.getInstance();
private Optional mmh;
+ private Optional npc;
/**
* Create a clipboard for blueprint
* @param blueprint - the blueprint to load into the clipboard
*/
public BlueprintClipboard(@NonNull Blueprint blueprint) {
+ this();
this.blueprint = blueprint;
- // MythicMobs
- mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance)
- .map(MythicMobsHook.class::cast);
}
public BlueprintClipboard() {
- // MythicMobs
+ // Citizens 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);
}
@@ -136,13 +138,20 @@ public class BlueprintClipboard {
private void copyAsync(World world, User user, List vectorsToCopy, int speed, boolean copyAir, boolean copyBiome) {
copying = false;
+ // FancyNpcs
+ if (npc.isPresent()) {
+ // Add all the citizens for the area in one go. This is pretty fast.
+ bpEntities.putAll(npc.get().getNpcsInArea(world, vectorsToCopy, origin));
+ }
+
+ // Repeating copy task
copyTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
if (copying) {
return;
}
copying = true;
vectorsToCopy.stream().skip(index).limit(speed).forEach(v -> {
- List ents = world.getLivingEntities().stream()
+ List ents = world.getEntities().stream()
.filter(Objects::nonNull)
.filter(e -> !(e instanceof Player))
.filter(e -> new Vector(Math.rint(e.getLocation().getX()),
@@ -153,6 +162,7 @@ public class BlueprintClipboard {
count++;
}
});
+
index += speed;
int percent = (int)(index * 100 / (double)vectorsToCopy.size());
if (percent != lastPercentage && percent % 10 == 0) {
@@ -189,9 +199,9 @@ public class BlueprintClipboard {
return r;
}
- private boolean copyBlock(Location l, boolean copyAir, boolean copyBiome, Collection entities) {
+ private boolean copyBlock(Location l, boolean copyAir, boolean copyBiome, List ents) {
Block block = l.getBlock();
- if (!copyAir && block.getType().equals(Material.AIR) && entities.isEmpty()) {
+ if (!copyAir && block.getType().equals(Material.AIR) && ents.isEmpty()) {
return false;
}
// Create position
@@ -202,14 +212,14 @@ public class BlueprintClipboard {
Vector pos = new Vector(x, y, z);
// Set entities
- List bpEnts = setEntities(entities);
+ List bpEnts = setEntities(ents);
// Store
if (!bpEnts.isEmpty()) {
bpEntities.put(pos, bpEnts);
}
// Return if this is just air block
- if (!copyAir && block.getType().equals(Material.AIR) && !entities.isEmpty()) {
+ if (!copyAir && block.getType().equals(Material.AIR) && !ents.isEmpty()) {
return true;
}
@@ -291,9 +301,14 @@ public class BlueprintClipboard {
return cs;
}
- private List setEntities(Collection entities) {
+ /**
+ * Deals with any entities that are in this block. Technically, this could be more than one, but is usually one.
+ * @param ents collection of entities
+ * @return Serialized list of entities
+ */
+ private List setEntities(List ents) {
List bpEnts = new ArrayList<>();
- for (LivingEntity entity: entities) {
+ for (Entity entity : ents) {
BlueprintEntity bpe = new BlueprintEntity();
bpe.setType(entity.getType());
@@ -329,6 +344,7 @@ public class BlueprintClipboard {
bpe.setStyle(horse.getStyle());
}
+ // Mythic mob check
mmh.filter(mm -> mm.isMythicMob(entity)).map(mm -> mm.getMythicMob(entity))
.ifPresent(bpe::setMythicMobsRecord);
diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java
index d62f8807d..8ba1cb498 100644
--- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java
+++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java
@@ -124,12 +124,38 @@ public class BlueprintPaster {
location.setY(y);
}
- private record Bits(Map blocks,
+ /**
+ * A record of all the "bits" of the blueprint that need to be pasted
+ * Consists of blocks, attached blocks, entities, iterators for the blocks and a speed
+ */
+ private record Bits(
+ /**
+ * Basic blocks to the pasted (not attached blocks)
+ */
+ Map blocks,
+ /**
+ * Attached blocks
+ */
Map attached,
+ /**
+ * Entities to be pasted
+ */
Map> entities,
+ /**
+ * Basic block pasting iterator
+ */
Iterator> it,
+ /**
+ * Attached block pasting iterator
+ */
Iterator> it2,
+ /**
+ * Entity pasting iterator
+ */
Iterator>> it3,
+ /**
+ * Paste speed
+ */
int pasteSpeed) {}
/**
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 4c4eb1de1..2ce285fee 100644
--- a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java
+++ b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java
@@ -24,6 +24,11 @@ import com.google.gson.annotations.Expose;
*/
public class BlueprintEntity {
+ // Npc storage
+ @Expose
+ private String npc;
+
+ // MythicMobs storage
public record MythicMobRecord(String type, String displayName, double level, float power, String stance) {
}
@@ -303,5 +308,38 @@ public class BlueprintEntity {
this.MMStance = mmr.stance();
this.MMpower = mmr.power();
}
+
+ /**
+ * @return the npc
+ */
+ public String getNpc() {
+ return npc;
+ }
+
+ /**
+ * @param citizen the citizen to set
+ */
+ public void setNpc(String citizen) {
+ this.npc = citizen;
+ }
+
+ @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 : "") + "]";
+ }
}
diff --git a/src/main/java/world/bentobox/bentobox/hooks/FancyNpcsHook.java b/src/main/java/world/bentobox/bentobox/hooks/FancyNpcsHook.java
new file mode 100644
index 000000000..acc5ba7d8
--- /dev/null
+++ b/src/main/java/world/bentobox/bentobox/hooks/FancyNpcsHook.java
@@ -0,0 +1,293 @@
+package world.bentobox.bentobox.hooks;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.entity.EntityType;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.util.Vector;
+import org.eclipse.jdt.annotation.Nullable;
+
+import de.oliver.fancynpcs.api.FancyNpcsPlugin;
+import de.oliver.fancynpcs.api.Npc;
+import de.oliver.fancynpcs.api.NpcAttribute;
+import de.oliver.fancynpcs.api.NpcData;
+import de.oliver.fancynpcs.api.actions.ActionTrigger;
+import de.oliver.fancynpcs.api.actions.NpcAction;
+import de.oliver.fancynpcs.api.utils.NpcEquipmentSlot;
+import de.oliver.fancynpcs.api.utils.SkinFetcher;
+import net.kyori.adventure.text.format.NamedTextColor;
+import world.bentobox.bentobox.BentoBox;
+import world.bentobox.bentobox.api.hooks.Hook;
+import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
+
+/**
+ * Provides copy and pasting of FancyNPCs in blueprints
+ *
+ * @author tastybento
+ * @since 3.1.0
+ */
+public class FancyNpcsHook extends Hook {
+
+ public FancyNpcsHook() {
+ super("FancyNpcs", Material.PLAYER_HEAD);
+ }
+
+ public String serializeNPC(Npc npc, Vector origin) {
+ if (npc == null) {
+ throw new IllegalArgumentException("NPC cannot be null.");
+ }
+ YamlConfiguration npcConfig = new YamlConfiguration();
+ NpcData data = npc.getData();
+ npcConfig.set("name", data.getName()); // Stored just for reference
+ npcConfig.set("creator", data.getCreator().toString());
+ npcConfig.set("displayName", data.getDisplayName());
+ npcConfig.set("type", data.getType().name());
+ npcConfig.set("location.world", data.getLocation().getWorld().getName()); // This will not be used
+ // Location is stored relative to the origin, and just stored for reference. x,y,z are not used
+ npcConfig.set("location.x", data.getLocation().getX() - origin.getBlockX());
+ npcConfig.set("location.y", data.getLocation().getY() - origin.getBlockY());
+ npcConfig.set("location.z", data.getLocation().getZ() - origin.getBlockZ());
+ // Only yaw and pitch are used
+ npcConfig.set("location.yaw", data.getLocation().getYaw());
+ npcConfig.set("location.pitch", data.getLocation().getPitch());
+ npcConfig.set("showInTab", data.isShowInTab());
+ npcConfig.set("spawnEntity", data.isSpawnEntity());
+ npcConfig.set("collidable", data.isCollidable());
+ npcConfig.set("glowing", data.isGlowing());
+ npcConfig.set("glowingColor", data.getGlowingColor().toString());
+ npcConfig.set("turnToPlayer", data.isTurnToPlayer());
+ npcConfig.set("messages", null);
+ npcConfig.set("playerCommands", null);
+ npcConfig.set("serverCommands", null);
+ npcConfig.set("sendMessagesRandomly", null);
+ npcConfig.set("interactionCooldown", data.getInteractionCooldown());
+ npcConfig.set("scale", data.getScale());
+
+ if (data.getSkin() != null) {
+ npcConfig.set("skin.identifier", data.getSkin().identifier());
+ } else {
+ npcConfig.set("skin.identifier", null);
+ }
+ npcConfig.set("skin.mirrorSkin", data.isMirrorSkin());
+
+ if (data.getEquipment() != null) {
+ for (Entry entry : data.getEquipment().entrySet()) {
+ npcConfig.set("equipment." + entry.getKey().name(), entry.getValue());
+ }
+ }
+
+ for (NpcAttribute attribute : FancyNpcsPlugin.get().getAttributeManager()
+ .getAllAttributesForEntityType(data.getType())) {
+ String value = data.getAttributes().getOrDefault(attribute, null);
+ npcConfig.set("attributes." + attribute.getName(), value);
+ }
+
+ npcConfig.set("actions", null);
+ for (Map.Entry> entry : npc.getData().getActions().entrySet()) {
+ for (NpcAction.NpcActionData actionData : entry.getValue()) {
+ if (actionData == null) {
+ continue;
+ }
+
+ npcConfig.set("actions." + entry.getKey().name() + "." + actionData.order() + ".action",
+ actionData.action().getName());
+ npcConfig.set("actions." + entry.getKey().name() + "." + actionData.order() + ".value",
+ actionData.value());
+ }
+ }
+
+ return npcConfig.saveToString();
+ }
+
+ public boolean spawnNpc(String yaml, Location pos) throws InvalidConfigurationException {
+ YamlConfiguration npcConfig = new YamlConfiguration();
+ npcConfig.loadFromString(yaml);
+
+ String name = UUID.randomUUID().toString(); // Create a unique name
+
+ UUID creator = UUID.randomUUID(); // Random creator
+
+ String displayName = npcConfig.getString("displayName", "");
+ EntityType type = EntityType.valueOf(npcConfig.getString("type", "PLAYER").toUpperCase(Locale.ENGLISH));
+
+ // Create the spawn location
+ Location location = null;
+ double x = pos.getBlockX();
+ double y = pos.getBlockY();
+ double z = pos.getBlockZ();
+ // Add in the yaw and pitch
+ float yaw = (float) npcConfig.getDouble("location.yaw");
+ float pitch = (float) npcConfig.getDouble("location.pitch");
+
+ location = new Location(pos.getWorld(), x, y, z, yaw, pitch);
+
+
+ String skinIdentifier = npcConfig.getString("skin.identifier", npcConfig.getString("skin.uuid", ""));
+ SkinFetcher.SkinData skin = null;
+ if (!skinIdentifier.isEmpty()) {
+ skin = new SkinFetcher.SkinData(skinIdentifier, "", "");
+ }
+
+ if (npcConfig.isSet("skin.value") && npcConfig.isSet("skin.signature")) {
+
+ String value = npcConfig.getString("skin.value");
+ String signature = npcConfig.getString("skin.signature");
+
+ if (value != null && !value.isEmpty() && signature != null && !signature.isEmpty()) {
+ skin = new SkinFetcher.SkinData(skinIdentifier, value, signature);
+ SkinFetcher.SkinData oldSkinData = new SkinFetcher.SkinData(skinIdentifier, value, signature);
+ SkinFetcher.skinCache.put(skinIdentifier, oldSkinData);
+ FancyNpcsPlugin.get().getSkinCache().upsert(new SkinFetcher.SkinCacheData(oldSkinData,
+ System.currentTimeMillis(), 1000 * 60 * 60 * 24));
+ }
+ }
+
+ boolean mirrorSkin = npcConfig.getBoolean("skin.mirrorSkin");
+
+ boolean showInTab = npcConfig.getBoolean("showInTab");
+ boolean spawnEntity = npcConfig.getBoolean("spawnEntity");
+ boolean collidable = npcConfig.getBoolean("collidable", true);
+ boolean glowing = npcConfig.getBoolean("glowing");
+ NamedTextColor glowingColor = NamedTextColor.NAMES
+ .value(npcConfig.getString("glowingColor", "white"));
+ boolean turnToPlayer = npcConfig.getBoolean("turnToPlayer");
+
+ Map> actions = new ConcurrentHashMap<>();
+
+ ConfigurationSection actiontriggerSection = npcConfig.getConfigurationSection("actions");
+ if (actiontriggerSection != null) {
+ actiontriggerSection.getKeys(false).forEach(trigger -> {
+ ActionTrigger actionTrigger = ActionTrigger.getByName(trigger);
+ if (actionTrigger == null) {
+ BentoBox.getInstance().logWarning("Could not find action trigger: " + trigger);
+ return;
+ }
+
+ List actionList = new ArrayList<>();
+ ConfigurationSection actionsSection = npcConfig.getConfigurationSection("actions." + trigger);
+ if (actionsSection != null) {
+ actionsSection.getKeys(false).forEach(order -> {
+ String actionName = npcConfig
+ .getString("actions." + trigger + "." + order + ".action");
+ String value = npcConfig.getString("actions." + trigger + "." + order + ".value");
+ NpcAction action = FancyNpcsPlugin.get().getActionManager().getActionByName(actionName);
+ if (action == null) {
+ BentoBox.getInstance().logWarning("Could not find action: " + actionName);
+ return;
+ }
+
+ try {
+ actionList.add(new NpcAction.NpcActionData(Integer.parseInt(order), action, value));
+ } catch (NumberFormatException e) {
+ BentoBox.getInstance().logWarning("Could not parse order: " + order);
+ }
+ });
+
+ actions.put(actionTrigger, actionList);
+ }
+ });
+ }
+
+ float interactionCooldown = (float) npcConfig.getDouble("interactionCooldown", 0);
+ float scale = (float) npcConfig.getDouble("scale", 1);
+
+ Map attributes = new HashMap<>();
+ if (npcConfig.isConfigurationSection("attributes")) {
+ for (String attrName : npcConfig.getConfigurationSection("attributes").getKeys(false)) {
+ NpcAttribute attribute = FancyNpcsPlugin.get().getAttributeManager().getAttributeByName(type,
+ attrName);
+ if (attribute == null) {
+ BentoBox.getInstance().logWarning("Could not find attribute: " + attrName);
+ continue;
+ }
+
+ String value = npcConfig.getString("attributes." + attrName);
+ if (!attribute.isValidValue(value)) {
+ BentoBox.getInstance().logWarning("Invalid value for attribute: " + attrName);
+ continue;
+ }
+
+ attributes.put(attribute, value);
+ }
+ }
+
+ FancyNpcsPlugin.get().getNpcManager().getNpc(name);
+
+ // When we make a copy, we need to use a new ID
+ String newId = UUID.randomUUID().toString();
+ NpcData data = new NpcData(newId, name, creator, displayName, skin, location, showInTab, spawnEntity,
+ collidable, glowing, glowingColor, type, new HashMap<>(), turnToPlayer, null, actions,
+ interactionCooldown, scale, attributes, mirrorSkin);
+ Npc npc = FancyNpcsPlugin.get().getNpcAdapter().apply(data);
+
+ if (npcConfig.isConfigurationSection("equipment")) {
+ for (String equipmentSlotStr : npcConfig.getConfigurationSection("equipment").getKeys(false)) {
+ NpcEquipmentSlot equipmentSlot = NpcEquipmentSlot.parse(equipmentSlotStr);
+ ItemStack item = npcConfig.getItemStack("equipment." + equipmentSlotStr);
+ npc.getData().addEquipment(equipmentSlot, item);
+ }
+ }
+
+ Bukkit.getScheduler().runTask(getPlugin(), () -> {
+ FancyNpcsPlugin.get().getNpcManager().registerNpc(npc);
+ npc.create();
+ npc.spawnForAll();
+ });
+
+ return true;
+ }
+
+ @Override
+ public boolean hook() {
+ boolean hooked = this.isPluginAvailable();
+ if (!hooked) {
+ BentoBox.getInstance().logError("Could not hook into FancyNpcs");
+ }
+ return hooked; // The hook process shouldn't fail
+ }
+
+ @Override
+ public String getFailureCause() {
+ return null; // The hook process shouldn't fail
+ }
+
+ public Map extends Vector, ? extends List> getNpcsInArea(World world, List vectorsToCopy,
+ @Nullable Vector origin) {
+ Map> bpEntities = new HashMap<>();
+ for (Npc npc : FancyNpcsPlugin.get().getNpcManager().getAllNpcs()) {
+ Location npcLocation = npc.getData().getLocation();
+ Vector spot = new Vector(npcLocation.getBlockX(), npcLocation.getBlockY(), npcLocation.getBlockZ());
+ if (npcLocation.getWorld().equals(world) && vectorsToCopy.contains(spot)) {
+ BlueprintEntity cit = new BlueprintEntity();
+ cit.setType(npc.getData().getType());
+ cit.setNpc(this.serializeNPC(npc, origin));
+ // Retrieve or create the list, then add the entity
+ List entities = bpEntities.getOrDefault(spot, new ArrayList<>());
+ entities.add(cit);
+ // Create position
+ Vector origin2 = origin == null ? new Vector(0, 0, 0) : origin;
+ int x = spot.getBlockX() - origin2.getBlockX();
+ int y = spot.getBlockY() - origin2.getBlockY();
+ int z = spot.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/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
index 681fd69e7..53c25b4ba 100644
--- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
+++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java
@@ -940,7 +940,6 @@ public class IslandsManager {
*/
@NonNull
public Map getHomeLocations(@NonNull Island island) {
- island.getHomes().forEach((n, l) -> BentoBox.getInstance().logDebug(n));
return island.getHomes();
}
diff --git a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
index 76fb68be9..86ee2e070 100644
--- a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
+++ b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java
@@ -21,6 +21,7 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
+import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
@@ -33,6 +34,7 @@ import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
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.nms.PasteHandler;
@@ -169,6 +171,7 @@ public class DefaultPasteUtil {
* @param island - island
* @param location - location
* @param list - blueprint entities
+ * @return future boolean - true if Bukkit entity spawned, false another plugin entity spawned
*/
public static CompletableFuture setEntity(Island island, Location location, List list) {
World world = location.getWorld();
@@ -180,11 +183,26 @@ public class DefaultPasteUtil {
/**
* Spawn an entity
* @param k the blueprint entity definition
- * @param location location
+ * @param location location to paste the entity
* @param island island
- * @return true if Bukkit entity spawned, false if MythicMob entity spawned
+ * @return true if Bukkit entity spawned, false another plugin entity spawned
*/
static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island island) {
+ // Npc entity
+ if (k.getNpc() != null
+ && plugin.getHooks().getHook("FancyNpcs").filter(mmh -> mmh instanceof FancyNpcsHook).map(mmh -> {
+ try {
+ return ((FancyNpcsHook) mmh).spawnNpc(k.getNpc(), location);
+ } catch (InvalidConfigurationException e) {
+ plugin.logError("FancyNpc loading failed in blueprint.");
+ return false;
+ }
+ }).orElse(false)) {
+ // Npc has spawned.
+ return false;
+ }
+
+ // Mythic Mobs entity
if (k.getMythicMobsRecord() != null && plugin.getHooks().getHook("MythicMobs")
.filter(mmh -> mmh instanceof MythicMobsHook)
.map(mmh -> ((MythicMobsHook) mmh).spawnMythicMob(k.getMythicMobsRecord(), location))