Merge pull request #2573 from BentoBoxWorld/develop

Release 3.1.1
This commit is contained in:
tastybento 2024-12-21 12:35:17 -08:00 committed by GitHub
commit f0729ccafe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 912 additions and 293 deletions

46
.github/workflows/modrinth-publish.yml vendored Normal file
View File

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

21
pom.xml
View File

@ -84,7 +84,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>3.0.1</build.version>
<build.version>3.1.1</build.version>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<server.jars>${project.basedir}/lib</server.jars>
@ -192,6 +192,12 @@
<id>clojars</id>
<url>https://repo.clojars.org/</url>
</repository>
<!-- FancyNPC -->
<repository>
<id>fancyplugins-releases</id>
<name>FancyPlugins Repository</name>
<url>https://repo.fancyplugins.de/releases</url>
</repository>
</repositories>
<dependencies>
@ -234,6 +240,12 @@
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc......</groupId>
<artifactId>spigot</artifactId>
<version>1.21.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc.....</groupId>
<artifactId>spigot</artifactId>
@ -387,6 +399,13 @@
<version>1.1.13</version>
<scope>compile</scope>
</dependency>
<!-- FancyNPCs -->
<dependency>
<groupId>de.oliver</groupId>
<artifactId>FancyNpcs</artifactId>
<version>2.4.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View File

@ -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());

View File

@ -1,12 +1,20 @@
/**
* This package covers all addon-specific API
* <p>
* The Addon class and the associated Pladdon are like Bukkit plugins
* but contain extra API specific for BentoBox games.
* </p>
* 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;

View File

@ -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.
* <p>
* 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.
* </p>
* @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;

View File

@ -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;

View File

@ -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
*
*/

View File

@ -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;

View File

@ -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<Vector, BlueprintBlock> bpBlocks = new LinkedHashMap<>();
private final BentoBox plugin = BentoBox.getInstance();
private Optional<MythicMobsHook> mmh;
private Optional<FancyNpcsHook> 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<Vector> 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<LivingEntity> ents = world.getLivingEntities().stream()
List<Entity> 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<LivingEntity> entities) {
private boolean copyBlock(Location l, boolean copyAir, boolean copyBiome, List<Entity> 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<BlueprintEntity> bpEnts = setEntities(entities);
List<BlueprintEntity> 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<BlueprintEntity> setEntities(Collection<LivingEntity> 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<BlueprintEntity> setEntities(List<Entity> ents) {
List<BlueprintEntity> 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);

View File

@ -124,12 +124,38 @@ public class BlueprintPaster {
location.setY(y);
}
private record Bits(Map<Vector, BlueprintBlock> 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<Vector, BlueprintBlock> blocks,
/**
* Attached blocks
*/
Map<Vector, BlueprintBlock> attached,
/**
* Entities to be pasted
*/
Map<Vector, List<BlueprintEntity>> entities,
/**
* Basic block pasting iterator
*/
Iterator<Entry<Vector, BlueprintBlock>> it,
/**
* Attached block pasting iterator
*/
Iterator<Entry<Vector, BlueprintBlock>> it2,
/**
* Entity pasting iterator
*/
Iterator<Entry<Vector, List<BlueprintEntity>>> it3,
/**
* Paste speed
*/
int pasteSpeed) {}
/**

View File

@ -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 : "") + "]";
}
}

View File

@ -477,7 +477,7 @@ public class Island implements DataObject, MetaDataAble {
* @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 class Island implements DataObject, MetaDataAble {
* @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 class Island implements DataObject, MetaDataAble {
* @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 class Island implements DataObject, MetaDataAble {
* @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 class Island implements DataObject, MetaDataAble {
|| 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);
}
/**

View File

@ -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<NpcEquipmentSlot, org.bukkit.inventory.ItemStack> 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<ActionTrigger, List<NpcAction.NpcActionData>> 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", "<empty>");
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<ActionTrigger, List<NpcAction.NpcActionData>> 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<NpcAction.NpcActionData> 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<NpcAttribute, String> 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<BlueprintEntity>> getNpcsInArea(World world, List<Vector> vectorsToCopy,
@Nullable Vector origin) {
Map<Vector, List<BlueprintEntity>> 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<BlueprintEntity> 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;
}
}

View File

@ -940,7 +940,6 @@ public class IslandsManager {
*/
@NonNull
public Map<String, Location> getHomeLocations(@NonNull Island island) {
island.getHomes().forEach((n, l) -> BentoBox.getInstance().logDebug(n));
return island.getHomes();
}

View File

@ -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<Void> 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());
}
});
}
}

View File

@ -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);
}
}

View File

@ -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<Void> 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());
}
});
}
}

View File

@ -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);
}
}

View File

@ -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<Void> 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());
}
});
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<Void> setEntity(Island island, Location location, List<BlueprintEntity> 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))

View File

@ -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!
* </p>
*
*
* 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
*
*/

View File

@ -197,23 +197,23 @@ public class ServerCompatibility {
/**
* @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 class ServerCompatibility {
/**
* @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;

View File

@ -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.

View File

@ -2,6 +2,7 @@ package world.bentobox.bentobox.database.objects;
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.verify;
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.Material;
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 class IslandTest {
// 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 class IslandTest {
// 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<BonusRangeRecord> 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<BonusRangeRecord> 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<UUID> 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)}.
*/