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> 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))