diff --git a/.github/workflows/modrinth-publish.yml b/.github/workflows/modrinth-publish.yml new file mode 100644 index 000000000..8244fe239 --- /dev/null +++ b/.github/workflows/modrinth-publish.yml @@ -0,0 +1,46 @@ +name: Publish + +on: + release: + types: [published] + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # !!! Make sure to select the correct Java version for your project !!! + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: adopt + 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: Build and package with Maven + run: mvn -B clean package --file pom.xml + + - name: Upload to Modrinth + uses: cloudnode-pro/modrinth-publish@2.0.0 + with: + # Configure the action + # api-domain: staging-api.modrinth.com + token: ${{ secrets.MODRINTH_TOKEN }} + project: aBVLHiAW + name: ${{ github.event.release.name }} + version: ${{ github.event.release.tag_name }} + changelog: ${{ github.event.release.body }} + loaders: |- + paper + spigot + game-versions: |- + 1.21.3 + 1.21.4 + files: target/BentoBox-${{ github.event.release.tag_name }}.jar diff --git a/pom.xml b/pom.xml index 90bf01ed9..ffb166b74 100644 --- a/pom.xml +++ b/pom.xml @@ -84,7 +84,7 @@ -LOCAL - 3.0.1 + 3.1.1 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 + @@ -234,6 +240,12 @@ ${spigot.version} provided + + org.spigotmc...... + spigot + 1.21.4-R0.1-SNAPSHOT + provided + org.spigotmc..... spigot @@ -387,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 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 @@ private void completeSetup(long loadTime) { hooksManager.registerHook(new VaultHook()); + // FancyNpcs + hooksManager.registerHook(new FancyNpcsHook()); + // MythicMobs hooksManager.registerHook(new MythicMobsHook()); diff --git a/src/main/java/world/bentobox/bentobox/api/addons/package-info.java b/src/main/java/world/bentobox/bentobox/api/addons/package-info.java index 4d239fd0d..abf00000b 100644 --- a/src/main/java/world/bentobox/bentobox/api/addons/package-info.java +++ b/src/main/java/world/bentobox/bentobox/api/addons/package-info.java @@ -1,12 +1,20 @@ /** - * This package covers all addon-specific API - *

- * The Addon class and the associated Pladdon are like Bukkit plugins - * but contain extra API specific for BentoBox games. - *

+ * This package contains classes and interfaces related to BentoBox addons. + * + * Addons are modular extensions that enhance BentoBox functionality. Game-specific + * addons (e.g., BSkyBlock, AcidIsland) as well as generic addons (e.g., Challenges, Warps) + * are supported by this system. Developers can create custom addons to introduce + * new features or gamemodes. + * + * Since BentoBox was created, server tech has changed and code remapping is done and that + * is usually only applied when a Plugin is loaded, so developers should use Pladdons + * which are a wrapper for Addons in a Plugin. + * + * Key components: + * - AddonLoader: Manages the lifecycle of addons. + * - AddonConfig: Handles addon-specific configurations. * * @since 1.0 * @author tastybento - * */ package world.bentobox.bentobox.api.addons; \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/api/commands/package-info.java b/src/main/java/world/bentobox/bentobox/api/commands/package-info.java index 7c4212842..2c2306515 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/package-info.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/package-info.java @@ -1,7 +1,9 @@ /** - * API for BentoBox commands - */ -/** + * This package contains classes and handlers for BentoBox commands. + * + * Commands allow players and administrators to interact with BentoBox, including + * managing islands, settings, and other in-game features. This package ensures + * smooth integration and execution of commands within the plugin. *

* The workhorse class is the abstract class CompositeCommand. It provides all the functionality for * a command including automatic help, sub-commands, convenience methods, etc. See examples of how to use @@ -13,6 +15,10 @@ * their own custom help if required, but most of the time it is not. *

* @author tastybento - * + * + * Key features: + * - Command registration and parsing. + * - Support for custom addon-specific commands. + * - Error handling and permission validation. */ package world.bentobox.bentobox.api.commands; \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/api/configuration/package-info.java b/src/main/java/world/bentobox/bentobox/api/configuration/package-info.java index 75ae81960..2beff6941 100644 --- a/src/main/java/world/bentobox/bentobox/api/configuration/package-info.java +++ b/src/main/java/world/bentobox/bentobox/api/configuration/package-info.java @@ -1,4 +1,6 @@ /** - * Contains API related to configurations. + * Provides classes and interfaces for managing configuration settings within the BentoBox API. + * This package enables the definition, access, and manipulation of configuration options, + * facilitating the customization and extension of BentoBox's functionality. */ package world.bentobox.bentobox.api.configuration; \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/api/events/package-info.java b/src/main/java/world/bentobox/bentobox/api/events/package-info.java index 326a59c8c..8709a133b 100644 --- a/src/main/java/world/bentobox/bentobox/api/events/package-info.java +++ b/src/main/java/world/bentobox/bentobox/api/events/package-info.java @@ -1,7 +1,14 @@ /** - * API for all the events that BentoBox generates - */ -/** + * This package defines events used within the BentoBox framework. + * + * Events are triggered during key gameplay actions, such as island creation, + * deletion, or player interactions. Developers can use these events to customize + * behaviors or respond to actions in their addons. + * + * Key features: + * - Custom event classes (e.g., IslandCreateEvent, PlayerJoinEvent). + * - Integration with Bukkit's event system. + * * @author tastybento * */ diff --git a/src/main/java/world/bentobox/bentobox/api/package-info.java b/src/main/java/world/bentobox/bentobox/api/package-info.java new file mode 100644 index 000000000..19f31387b --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/package-info.java @@ -0,0 +1,10 @@ +/** + * This package provides the core API for interacting with the BentoBox framework. + * + * It enables developers to integrate their custom plugins or addons with BentoBox, + * offering simplified access to common functionalities like: + * - Island management (creation, deletion, permissions). + * - Player interactions within island-based games. + * - Events and hooks for extending core behavior. + */ +package world.bentobox.bentobox.api; \ No newline at end of file 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 @@ 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.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.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 boolean copy(User user, boolean copyAir, boolean copyBiome) { 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 @@ private void copyAsync(World world, User user, List vectorsToCopy, int s count++; } }); + index += speed; int percent = (int)(index * 100 / (double)vectorsToCopy.size()); if (percent != lastPercentage && percent % 10 == 0) { @@ -189,9 +199,9 @@ protected List getVectors(BoundingBox b) { 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 @@ private boolean copyBlock(Location l, boolean copyAir, boolean copyBiome, Collec 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 @@ private BlueprintCreatureSpawner getSpawner(CreatureSpawner spawner) { 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 @@ private List setEntities(Collection entities) { 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 BlueprintPaster(@NonNull BentoBox plugin, @NonNull Blueprint bp, World wo 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 @@ */ 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 void setMythicMobsRecord(MythicMobRecord mmr) { 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/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index 663c31cf0..317a5fbc6 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -477,7 +477,7 @@ public ImmutableSet getMemberSet() { * @return the minProtectedX */ public int getMinProtectedX() { - return Math.max(getMinX(), getProtectionCenter().getBlockX() - protectionRange); + return Math.max(getMinX(), getProtectionCenter().getBlockX() - this.getProtectionRange()); } /** @@ -488,7 +488,7 @@ public int getMinProtectedX() { * @since 1.5.2 */ public int getMaxProtectedX() { - return Math.min(getMaxX(), getProtectionCenter().getBlockX() + protectionRange); + return Math.min(getMaxX(), getProtectionCenter().getBlockX() + this.getProtectionRange()); } /** @@ -498,7 +498,7 @@ public int getMaxProtectedX() { * @return the minProtectedZ */ public int getMinProtectedZ() { - return Math.max(getMinZ(), getProtectionCenter().getBlockZ() - protectionRange); + return Math.max(getMinZ(), getProtectionCenter().getBlockZ() - this.getProtectionRange()); } /** @@ -509,7 +509,7 @@ public int getMinProtectedZ() { * @since 1.5.2 */ public int getMaxProtectedZ() { - return Math.min(getMaxZ(), getProtectionCenter().getBlockZ() + protectionRange); + return Math.min(getMaxZ(), getProtectionCenter().getBlockZ() + this.getProtectionRange()); } /** @@ -957,9 +957,9 @@ public boolean onIsland(@NonNull Location target) { || this.getPlugin().getIWM().isIslandNether(target.getWorld()) || this.getPlugin().getIWM().isIslandEnd(target.getWorld())) && target.getBlockX() >= this.getMinProtectedX() - && target.getBlockX() < (this.getMinProtectedX() + this.protectionRange * 2) + && target.getBlockX() < (this.getMinProtectedX() + this.getProtectionRange() * 2) && target.getBlockZ() >= this.getMinProtectedZ() - && target.getBlockZ() < (this.getMinProtectedZ() + this.protectionRange * 2); + && target.getBlockZ() < (this.getMinProtectedZ() + this.getProtectionRange() * 2); } /** 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 boolean renameHomeLocation(@NonNull Island island, @NonNull String oldNam */ @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/nms/v1_20_0_R0_1_SNAPSHOT/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/PasteHandlerImpl.java deleted file mode 100644 index d9cc8f9f2..000000000 --- a/src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/PasteHandlerImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -package world.bentobox.bentobox.nms.v1_20_0_R0_1_SNAPSHOT; - -import java.util.concurrent.CompletableFuture; - -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData; - -import net.minecraft.core.BlockPosition; -import net.minecraft.world.level.block.state.IBlockData; -import net.minecraft.world.level.chunk.Chunk; -import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.nms.PasteHandler; -import world.bentobox.bentobox.util.DefaultPasteUtil; -import world.bentobox.bentobox.util.Util; - -public class PasteHandlerImpl implements PasteHandler { - - - protected static final IBlockData AIR = ((CraftBlockData) AIR_BLOCKDATA).getState(); - - /** - * Set the block to the location - * - * @param island - island - * @param location - location - * @param bpBlock - blueprint block - */ - public CompletableFuture setBlock(Island island, Location location, BlueprintBlock bpBlock) { - return Util.getChunkAtAsync(location).thenRun(() -> { - Block block = location.getBlock(); - // Set the block data - default is AIR - BlockData bd = DefaultPasteUtil.createBlockData(bpBlock); - CraftBlockData craft = (CraftBlockData) bd; - net.minecraft.world.level.World nmsWorld = ((CraftWorld) location.getWorld()).getHandle(); - Chunk nmsChunk = nmsWorld.d(location.getBlockX() >> 4, location.getBlockZ() >> 4); - BlockPosition bp = new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ()); - // Setting the block to air before setting to another state prevents some console errors - nmsChunk.a(bp, AIR, false); - nmsChunk.a(bp, craft.getState(), false); - block.setBlockData(bd, false); - DefaultPasteUtil.setBlockState(island, block, bpBlock); - // Set biome - if (bpBlock.getBiome() != null) { - block.setBiome(bpBlock.getBiome()); - } - }); - } -} diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/WorldRegeneratorImpl.java deleted file mode 100644 index 6b85d7272..000000000 --- a/src/main/java/world/bentobox/bentobox/nms/v1_20_0_R0_1_SNAPSHOT/WorldRegeneratorImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package world.bentobox.bentobox.nms.v1_20_0_R0_1_SNAPSHOT; - -import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData; - -import net.minecraft.core.BlockPosition; -import net.minecraft.world.level.World; -import net.minecraft.world.level.chunk.Chunk; -import world.bentobox.bentobox.nms.CopyWorldRegenerator; - -public class WorldRegeneratorImpl extends CopyWorldRegenerator { - - @Override - public void setBlockInNativeChunk(org.bukkit.Chunk chunk, int x, int y, int z, BlockData blockData, - boolean applyPhysics) { - CraftBlockData craft = (CraftBlockData) blockData; - World nmsWorld = ((CraftWorld) chunk.getWorld()).getHandle(); - Chunk nmsChunk = nmsWorld.d(chunk.getX(), chunk.getZ()); - BlockPosition bp = new BlockPosition((chunk.getX() << 4) + x, y, (chunk.getZ() << 4) + z); - // Setting the block to air before setting to another state prevents some console errors - nmsChunk.a(bp, PasteHandlerImpl.AIR, applyPhysics); - nmsChunk.a(bp, craft.getState(), applyPhysics); - } - -} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/PasteHandlerImpl.java deleted file mode 100644 index 483025c52..000000000 --- a/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/PasteHandlerImpl.java +++ /dev/null @@ -1,51 +0,0 @@ -package world.bentobox.bentobox.nms.v1_20_1_R0_1_SNAPSHOT; - -import java.util.concurrent.CompletableFuture; - -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData; - -import net.minecraft.core.BlockPosition; -import net.minecraft.world.level.block.state.IBlockData; -import net.minecraft.world.level.chunk.Chunk; -import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.nms.PasteHandler; -import world.bentobox.bentobox.util.DefaultPasteUtil; -import world.bentobox.bentobox.util.Util; - -public class PasteHandlerImpl implements PasteHandler { - - protected static final IBlockData AIR = ((CraftBlockData) AIR_BLOCKDATA).getState(); - - /** - * Set the block to the location - * - * @param island - island - * @param location - location - * @param bpBlock - blueprint block - */ - public CompletableFuture setBlock(Island island, Location location, BlueprintBlock bpBlock) { - return Util.getChunkAtAsync(location).thenRun(() -> { - Block block = location.getBlock(); - // Set the block data - default is AIR - BlockData bd = DefaultPasteUtil.createBlockData(bpBlock); - CraftBlockData craft = (CraftBlockData) bd; - net.minecraft.world.level.World nmsWorld = ((CraftWorld) location.getWorld()).getHandle(); - Chunk nmsChunk = nmsWorld.d(location.getBlockX() >> 4, location.getBlockZ() >> 4); - BlockPosition bp = new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ()); - // Setting the block to air before setting to another state prevents some console errors - nmsChunk.a(bp, AIR, false); - nmsChunk.a(bp, craft.getState(), false); - block.setBlockData(bd, false); - DefaultPasteUtil.setBlockState(island, block, bpBlock); - // Set biome - if (bpBlock.getBiome() != null) { - block.setBiome(bpBlock.getBiome()); - } - }); - } -} diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/WorldRegeneratorImpl.java deleted file mode 100644 index d15d4809b..000000000 --- a/src/main/java/world/bentobox/bentobox/nms/v1_20_1_R0_1_SNAPSHOT/WorldRegeneratorImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package world.bentobox.bentobox.nms.v1_20_1_R0_1_SNAPSHOT; - -import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData; - -import net.minecraft.core.BlockPosition; -import net.minecraft.world.level.World; -import net.minecraft.world.level.chunk.Chunk; -import world.bentobox.bentobox.nms.CopyWorldRegenerator; - -public class WorldRegeneratorImpl extends CopyWorldRegenerator { - - @Override - public void setBlockInNativeChunk(org.bukkit.Chunk chunk, int x, int y, int z, BlockData blockData, - boolean applyPhysics) { - CraftBlockData craft = (CraftBlockData) blockData; - World nmsWorld = ((CraftWorld) chunk.getWorld()).getHandle(); - Chunk nmsChunk = nmsWorld.d(chunk.getX(), chunk.getZ()); - BlockPosition bp = new BlockPosition((chunk.getX() << 4) + x, y, (chunk.getZ() << 4) + z); - // Setting the block to air before setting to another state prevents some console errors - nmsChunk.a(bp, PasteHandlerImpl.AIR, applyPhysics); - nmsChunk.a(bp, craft.getState(), applyPhysics); - } - -} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/PasteHandlerImpl.java deleted file mode 100644 index 49efdc370..000000000 --- a/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/PasteHandlerImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -package world.bentobox.bentobox.nms.v1_20_6_R0_1_SNAPSHOT; - -import java.util.concurrent.CompletableFuture; - -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_20_R4.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData; - -import net.minecraft.core.BlockPosition; -import net.minecraft.world.level.block.state.IBlockData; -import net.minecraft.world.level.chunk.Chunk; -import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.nms.PasteHandler; -import world.bentobox.bentobox.util.DefaultPasteUtil; -import world.bentobox.bentobox.util.Util; - -public class PasteHandlerImpl implements PasteHandler { - - protected static final IBlockData AIR = ((CraftBlockData) AIR_BLOCKDATA).getState(); - - /** - * Set the block to the location - * - * @param island - island - * @param location - location - * @param bpBlock - blueprint block - */ - @Override - public CompletableFuture setBlock(Island island, Location location, BlueprintBlock bpBlock) { - return Util.getChunkAtAsync(location).thenRun(() -> { - Block block = location.getBlock(); - // Set the block data - default is AIR - BlockData bd = DefaultPasteUtil.createBlockData(bpBlock); - CraftBlockData craft = (CraftBlockData) bd; - net.minecraft.world.level.World nmsWorld = ((CraftWorld) location.getWorld()).getHandle(); - Chunk nmsChunk = nmsWorld.d(location.getBlockX() >> 4, location.getBlockZ() >> 4); - BlockPosition bp = new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ()); - // Setting the block to air before setting to another state prevents some console errors - nmsChunk.a(bp, AIR, false); - nmsChunk.a(bp, craft.getState(), false); - block.setBlockData(bd, false); - DefaultPasteUtil.setBlockState(island, block, bpBlock); - // Set biome - if (bpBlock.getBiome() != null) { - block.setBiome(bpBlock.getBiome()); - } - }); - } -} diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/WorldRegeneratorImpl.java deleted file mode 100644 index fbff31665..000000000 --- a/src/main/java/world/bentobox/bentobox/nms/v1_20_6_R0_1_SNAPSHOT/WorldRegeneratorImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package world.bentobox.bentobox.nms.v1_20_6_R0_1_SNAPSHOT; - -import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_20_R4.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData; - -import net.minecraft.core.BlockPosition; -import net.minecraft.world.level.World; -import net.minecraft.world.level.chunk.Chunk; -import world.bentobox.bentobox.nms.CopyWorldRegenerator; - -public class WorldRegeneratorImpl extends CopyWorldRegenerator { - - @Override - public void setBlockInNativeChunk(org.bukkit.Chunk chunk, int x, int y, int z, BlockData blockData, - boolean applyPhysics) { - CraftBlockData craft = (CraftBlockData) blockData; - World nmsWorld = ((CraftWorld) chunk.getWorld()).getHandle(); - Chunk nmsChunk = nmsWorld.d(chunk.getX(), chunk.getZ()); - BlockPosition bp = new BlockPosition((chunk.getX() << 4) + x, y, (chunk.getZ() << 4) + z); - // Setting the block to air before setting to another state prevents some console errors - nmsChunk.a(bp, PasteHandlerImpl.AIR, applyPhysics); - nmsChunk.a(bp, craft.getState(), applyPhysics); - } - -} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_21_4_R0_1_SNAPSHOT/PasteHandlerImpl.java similarity index 92% rename from src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/PasteHandlerImpl.java rename to src/main/java/world/bentobox/bentobox/nms/v1_21_4_R0_1_SNAPSHOT/PasteHandlerImpl.java index af6a83d7a..e0096f298 100644 --- a/src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/PasteHandlerImpl.java +++ b/src/main/java/world/bentobox/bentobox/nms/v1_21_4_R0_1_SNAPSHOT/PasteHandlerImpl.java @@ -1,12 +1,12 @@ -package world.bentobox.bentobox.nms.v1_20_4_R0_1_SNAPSHOT; +package world.bentobox.bentobox.nms.v1_21_4_R0_1_SNAPSHOT; import java.util.concurrent.CompletableFuture; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_21_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R3.block.data.CraftBlockData; import net.minecraft.core.BlockPosition; import net.minecraft.world.level.block.state.IBlockData; diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_21_4_R0_1_SNAPSHOT/WorldRegeneratorImpl.java similarity index 84% rename from src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/WorldRegeneratorImpl.java rename to src/main/java/world/bentobox/bentobox/nms/v1_21_4_R0_1_SNAPSHOT/WorldRegeneratorImpl.java index f259a92bb..ca4215070 100644 --- a/src/main/java/world/bentobox/bentobox/nms/v1_20_4_R0_1_SNAPSHOT/WorldRegeneratorImpl.java +++ b/src/main/java/world/bentobox/bentobox/nms/v1_21_4_R0_1_SNAPSHOT/WorldRegeneratorImpl.java @@ -1,8 +1,8 @@ -package world.bentobox.bentobox.nms.v1_20_4_R0_1_SNAPSHOT; +package world.bentobox.bentobox.nms.v1_21_4_R0_1_SNAPSHOT; import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_21_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R3.block.data.CraftBlockData; import net.minecraft.core.BlockPosition; import net.minecraft.world.level.World; diff --git a/src/main/java/world/bentobox/bentobox/package-info.java b/src/main/java/world/bentobox/bentobox/package-info.java new file mode 100644 index 000000000..128bc2f0f --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/package-info.java @@ -0,0 +1,14 @@ +/** + * The core package for the BentoBox plugin. + * + * This package provides the foundational framework for island-based games like SkyBlock, + * AcidIsland, and others. It manages core plugin features such as the addon system, + * configuration, and APIs used by developers to create custom addons or extend + * the functionality of BentoBox. + * + * Key features: + * - A modular addon system to mix and match features and game modes. + * - Comprehensive APIs for island protection, GUIs, team management, and more. + * - Cross-compatibility with various game modes. + */ +package world.bentobox.bentobox; \ No newline at end of file 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.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.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 static void setSpawner(CreatureSpawner spawner, BlueprintCreatureSpawner * @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 static CompletableFuture setEntity(Island island, Location location /** * 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)) diff --git a/src/main/java/world/bentobox/bentobox/util/package-info.java b/src/main/java/world/bentobox/bentobox/util/package-info.java index 3b6d6e5c3..787a062db 100644 --- a/src/main/java/world/bentobox/bentobox/util/package-info.java +++ b/src/main/java/world/bentobox/bentobox/util/package-info.java @@ -5,7 +5,13 @@ * Look here before you write your own utility function. If it isn't here, but would be useful * the submit a PR so others can avoid duplicating code! *

- * + * + * This package provides utility classes and helpers for common tasks within BentoBox. + * + * These utilities simplify repetitive tasks like file handling, configuration management, + * and mathematical calculations. They are used throughout the framework and can be + * leveraged by addon developers. + * * @author various * */ diff --git a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java index 2b356e31e..a5566b07a 100644 --- a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java +++ b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java @@ -197,23 +197,23 @@ public enum ServerVersion { /** * @since 1.21.0 */ - V1_19(Compatibility.COMPATIBLE), + V1_19(Compatibility.INCOMPATIBLE), /** * @since 1.21.0 */ - V1_19_1(Compatibility.COMPATIBLE), + V1_19_1(Compatibility.INCOMPATIBLE), /** * @since 1.21.0 */ - V1_19_2(Compatibility.COMPATIBLE), + V1_19_2(Compatibility.INCOMPATIBLE), /** * @since 1.22.0 */ - V1_19_3(Compatibility.COMPATIBLE), + V1_19_3(Compatibility.INCOMPATIBLE), /** * @since 1.22.1 */ - V1_19_4(Compatibility.COMPATIBLE), + V1_19_4(Compatibility.INCOMPATIBLE), /** * @since 1.24.0 */ @@ -221,40 +221,50 @@ public enum ServerVersion { /** * @since 1.24.0 */ - V1_20_1(Compatibility.COMPATIBLE), + V1_20_1(Compatibility.INCOMPATIBLE), /** * @since 2.0.0 */ - V1_20_2(Compatibility.COMPATIBLE), + V1_20_2(Compatibility.INCOMPATIBLE), /** * @since 2.0.0 */ - V1_20_3(Compatibility.COMPATIBLE), + V1_20_3(Compatibility.INCOMPATIBLE), /** * @since 2.0.0 */ - V1_20_4(Compatibility.COMPATIBLE), + V1_20_4(Compatibility.INCOMPATIBLE), /** * @since 2.4.0 */ - V1_20_5(Compatibility.COMPATIBLE), + V1_20_5(Compatibility.INCOMPATIBLE), /** * @since 2.4.0 */ - V1_20_6(Compatibility.COMPATIBLE), + V1_20_6(Compatibility.INCOMPATIBLE), /** * @since 2.4.0 */ - V1_21(Compatibility.COMPATIBLE), + V1_21(Compatibility.INCOMPATIBLE), /** * @since 2.5.0 */ - V1_21_1(Compatibility.COMPATIBLE), + V1_21_1(Compatibility.INCOMPATIBLE), /** * @since 2.7.0 */ - V1_21_2(Compatibility.INCOMPATIBLE), V1_21_3(Compatibility.COMPATIBLE); + V1_21_2(Compatibility.INCOMPATIBLE), + + /** + * @since 3.0.0 + */ + V1_21_3(Compatibility.COMPATIBLE), + + /** + * @since 3.0.1 + */ + V1_21_4(Compatibility.COMPATIBLE),; private final Compatibility compatibility; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 84e8787f6..20e8b492e 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -93,7 +93,7 @@ panel: # Mojang API sometime may be slow and may limit requests to the player data, so this will allow to # get player heads a bit faster then Mojang API. # Added since 1.16.0. - use-cache-server: true + use-cache-server: false # Defines how long player skin texture link is stored into local cache before it is requested again. # Defined value is in the minutes. # Value 0 will not clear cache until server restart. diff --git a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java index b2b149abc..eb0e1abb4 100644 --- a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java +++ b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -12,10 +13,14 @@ import static org.mockito.Mockito.when; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; @@ -24,6 +29,7 @@ import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.block.Block; +import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; import org.junit.Before; @@ -68,12 +74,22 @@ public class IslandTest { private BentoBox plugin; @Mock private IslandWorldManager iwm; + @Mock private World world; @Mock + private World netherWorld; + @Mock + private World endWorld; + @Mock private User user; @Mock private CommandsManager cm; + private String uniqueId1; + private String uniqueId2; + private Island island1; + private Island island2; + private Island island3; @Before public void setUp() throws Exception { @@ -95,6 +111,12 @@ public void setUp() throws Exception { // User when(user.getUniqueId()).thenReturn(uuid); + // Nether and End worlds + when(iwm.getNetherWorld(world)).thenReturn(netherWorld); + when(iwm.getEndWorld(world)).thenReturn(endWorld); + when(netherWorld.getName()).thenReturn("bskyblock_nether"); + when(endWorld.getName()).thenReturn("bskyblock_end"); + // Bukkit PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); when(Bukkit.getOnlinePlayers()).thenReturn(Collections.emptyList()); @@ -109,9 +131,316 @@ public void setUp() throws Exception { // Islands Manager PowerMockito.mockStatic(IslandsManager.class, Mockito.RETURNS_MOCKS); + // Initialize unique IDs for test objects + uniqueId1 = UUID.randomUUID().toString(); + uniqueId2 = UUID.randomUUID().toString(); + + // Create Island instances + island1 = new Island(); + island1.setUniqueId(uniqueId1); + + island2 = new Island(); + island2.setUniqueId(uniqueId1); + + island3 = new Island(); + island3.setUniqueId(uniqueId2); + i = new Island(new Island(location, uuid, 100)); } + /** + * Test equals method: two objects with the same uniqueId should be equal. + */ + @Test + public void testEquals_SameUniqueId() { + assertTrue("island1 should equal island2", island1.equals(island2)); + } + + /** + * Test equals method: objects with different uniqueId should not be equal. + */ + @Test + public void testEquals_DifferentUniqueId() { + assertFalse("island1 should not equal island3", island1.equals(island3)); + } + + /** + * Test equals method: object compared with itself should be equal. + */ + @Test + public void testEquals_SameObject() { + assertTrue("island1 should equal itself", island1.equals(island1)); + } + + /** + * Test equals method: object compared with null should return false. + */ + @Test + public void testEquals_NullObject() { + assertFalse("island1 should not equal null", island1.equals(null)); + } + + /** + * Test equals method: object compared with a different class should return false. + */ + @SuppressWarnings("unlikely-arg-type") + @Test + public void testEquals_DifferentClass() { + assertFalse("island1 should not equal a string", island1.equals("someString")); + } + + /** + * Test hashCode: two objects with the same uniqueId should have the same hashCode. + */ + @Test + public void testHashCode_SameUniqueId() { + assertEquals("island1 and island2 should have the same hashCode", island1.hashCode(), island2.hashCode()); + } + + /** + * Test hashCode: objects with different uniqueId should have different hashCode. + */ + @Test + public void testHashCode_DifferentUniqueId() { + assertNotEquals("island1 and island3 should have different hashCodes", island1.hashCode(), island3.hashCode()); + } + + /** + * Test method for {@link world.bentobox.bentobox.database.objects.Island#Island()}. + */ + @Test + public void testIsland() { + Island island = new Island(); + assertNotNull("Island instance should not be null", island); + assertNotNull("Unique ID should be initialized", island.getUniqueId()); + } + + /** + * Test method for {@link world.bentobox.bentobox.database.objects.Island#getRawProtectionRange()}. + */ + @Test + public void testGetRawProtectionRange() { + int range = 100; + Island island = new Island(location, uuid, range); + assertEquals("Raw protection range should match", range, island.getRawProtectionRange()); + } + + /** + * Test method for {@link world.bentobox.bentobox.database.objects.Island#getNetherWorld()}. + */ + @Test + public void testGetNetherWorld() { + Island island = new Island(location, uuid, 100); + when(plugin.getIWM().isNetherGenerate(world)).thenReturn(true); + when(plugin.getIWM().isNetherIslands(world)).thenReturn(true); + when(plugin.getIWM().getNetherWorld(world)).thenReturn(netherWorld); + assertEquals("Nether world should be returned", netherWorld, island.getNetherWorld()); + } + + /** + * Test method for {@link world.bentobox.bentobox.database.objects.Island#getEndWorld()}. + */ + @Test + public void testGetEndWorld() { + Island island = new Island(location, uuid, 100); + when(plugin.getIWM().getEndWorld(world)).thenReturn(endWorld); + when(plugin.getIWM().isEndGenerate(world)).thenReturn(true); + when(plugin.getIWM().isEndIslands(world)).thenReturn(true); + assertEquals("End world should be returned", endWorld, island.getEndWorld()); + } + + /** + * Test method for {@link world.bentobox.bentobox.database.objects.Island#getWorld(org.bukkit.World.Environment)}. + */ + @Test + public void testGetWorldEnvironment() { + Island island = new Island(location, uuid, 100); + when(plugin.getIWM().getEndWorld(world)).thenReturn(endWorld); + when(plugin.getIWM().isEndGenerate(world)).thenReturn(true); + when(plugin.getIWM().isEndIslands(world)).thenReturn(true); + when(plugin.getIWM().isNetherGenerate(world)).thenReturn(true); + when(plugin.getIWM().isNetherIslands(world)).thenReturn(true); + when(plugin.getIWM().getNetherWorld(world)).thenReturn(netherWorld); + assertEquals("Normal world should be returned", world, island.getWorld(Environment.NORMAL)); + assertEquals("Nether world should be returned", netherWorld, island.getWorld(Environment.NETHER)); + assertEquals("End world should be returned", endWorld, island.getWorld(Environment.THE_END)); + } + + /** + * Test method for {@link world.bentobox.bentobox.database.objects.Island#getBoundingBox(org.bukkit.World.Environment)}. + */ + @Test + public void testGetBoundingBoxEnvironment() { + Island island = new Island(location, uuid, 100); + int dist = iwm.getIslandDistance(world); + BoundingBox expected = new BoundingBox(-dist, world.getMinHeight(), -dist, dist, world.getMaxHeight(), dist); + BoundingBox result = island.getBoundingBox(Environment.NORMAL); + assertEquals("BoundingBox should match", expected, result); + } + + @Test + public void testGetProtectionBoundingBoxEnvironment() { + Island island = new Island(location, uuid, 100); + when(world.getMinHeight()).thenReturn(0); + when(world.getMaxHeight()).thenReturn(256); + BoundingBox expected = new BoundingBox(-100, world.getMinHeight(), -100, 100, world.getMaxHeight(), 100); + + BoundingBox result = island.getProtectionBoundingBox(Environment.NORMAL); + assertEquals("Protection bounding box should match", expected, result); + } + + @Test + public void testIsNetherIslandEnabled() { + Island island = new Island(location, uuid, 100); + when(plugin.getIWM().isNetherGenerate(world)).thenReturn(true); + when(plugin.getIWM().isNetherIslands(world)).thenReturn(true); + + assertTrue("Nether island should be enabled", island.isNetherIslandEnabled()); + } + + @Test + public void testIsEndIslandEnabled() { + Island island = new Island(location, uuid, 100); + when(plugin.getIWM().isEndGenerate(world)).thenReturn(true); + when(plugin.getIWM().isEndIslands(world)).thenReturn(true); + + assertTrue("End island should be enabled", island.isEndIslandEnabled()); + } + + @Test + public void testClearChanged() { + Island island = new Island(location, uuid, 100); + island.setChanged(); + assertTrue("Island should be marked as changed", island.isChanged()); + + island.clearChanged(); + assertFalse("Island should not be marked as changed", island.isChanged()); + } + + @Test + public void testRemoveHomes() { + Island island = new Island(location, uuid, 100); + island.addHome("home1", location); + island.addHome("home2", location); + assertEquals("Island should have two homes", 2, island.getHomes().size()); + + island.removeHomes(); + assertEquals("Only the default home should remain", 0, island.getHomes().size()); + } + + @Test + public void testGetBonusRanges() { + Island island = new Island(location, uuid, 100); + assertNotNull("Bonus ranges should not be null", island.getBonusRanges()); + assertTrue("Bonus ranges should initially be empty", island.getBonusRanges().isEmpty()); + } + + @Test + public void testSetBonusRanges() { + Island island = new Island(location, uuid, 100); + List bonusRanges = Arrays.asList(new BonusRangeRecord("id1", 10, "Bonus 1"), + new BonusRangeRecord("id2", 20, "Bonus 2")); + island.setBonusRanges(bonusRanges); + assertEquals("Bonus ranges should match", bonusRanges, island.getBonusRanges()); + assertEquals("Protection range should match", 100 + 10 + 20, island.getProtectionRange()); + } + + @Test + public void testGetBonusRange() { + Island island = new Island(location, uuid, 100); + island.addBonusRange("id1", 10, "Bonus 1"); + assertEquals("Bonus range should match", 10, island.getBonusRange("id1")); + } + + @Test + public void testGetBonusRangeRecord() { + Island island = new Island(location, uuid, 100); + island.addBonusRange("id1", 10, "Bonus 1"); + Optional record = island.getBonusRangeRecord("id1"); + assertTrue("Bonus range record should exist", record.isPresent()); + assertEquals("Bonus range ID should match", "id1", record.get().getUniqueId()); + } + + @Test + public void testAddBonusRange() { + Island island = new Island(location, uuid, 100); + island.addBonusRange("id1", 10, "Bonus 1"); + assertEquals("Bonus range should match", 10, island.getBonusRange("id1")); + assertEquals("Protection range should match", 100 + 10, island.getProtectionRange()); + } + + @Test + public void testClearBonusRange() { + Island island = new Island(location, uuid, 100); + island.addBonusRange("id1", 10, "Bonus 1"); + island.clearBonusRange("id1"); + assertEquals("Bonus range should be cleared", 0, island.getBonusRange("id1")); + assertEquals("Protection range should match", 100, island.getProtectionRange()); + } + + @Test + public void testClearAllBonusRanges() { + Island island = new Island(location, uuid, 100); + island.addBonusRange("id1", 10, "Bonus 1"); + island.addBonusRange("id2", 20, "Bonus 2"); + assertEquals("Protection range should match", 100 + 10 + 20, island.getProtectionRange()); + island.clearAllBonusRanges(); + assertTrue("All bonus ranges should be cleared", island.getBonusRanges().isEmpty()); + assertEquals("Protection range should match", 100, island.getProtectionRange()); + } + + @Test + public void testIsPrimary() { + Island island = new Island(location, uuid, 100); + island.setPrimary(uuid); + assertTrue("User should be primary", island.isPrimary(uuid)); + } + + @Test + public void testSetPrimary() { + Island island = new Island(location, uuid, 100); + island.setPrimary(uuid); + assertTrue("User should be primary", island.isPrimary(uuid)); + } + + @Test + public void testRemovePrimary() { + Island island = new Island(location, uuid, 100); + island.setPrimary(uuid); + island.removePrimary(uuid); + assertFalse("User should not be primary", island.isPrimary(uuid)); + } + + @Test + public void testInTeam() { + Island island = new Island(location, uuid, 100); + island.addMember(uuid); + assertTrue("User should be in team", island.inTeam(uuid)); + } + + @Test + public void testHasTeam() { + Island island = new Island(location, uuid, 100); + assertFalse("Island should not have a team initially", island.hasTeam()); + island.addMember(UUID.randomUUID()); + assertTrue("Island should have a team", island.hasTeam()); + } + + @Test + public void testGetPrimaries() { + Island island = new Island(location, uuid, 100); + island.setPrimary(uuid); + assertTrue("Primaries should contain user", island.getPrimaries().contains(uuid)); + } + + @Test + public void testSetPrimaries() { + Island island = new Island(location, uuid, 100); + Set primaries = new HashSet<>(Collections.singletonList(uuid)); + island.setPrimaries(primaries); + assertEquals("Primaries should match", primaries, island.getPrimaries()); + } + /** * Test method for {@link world.bentobox.bentobox.database.objects.Island#Island(org.bukkit.Location, java.util.UUID, int)}. */