diff --git a/pom.xml b/pom.xml index da107d0bd..89e22cc51 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ -LOCAL - 2.4.1 + 2.4.2 bentobox-world https://sonarcloud.io ${project.basedir}/lib diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java index 4fba9e558..c84727767 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java @@ -219,7 +219,8 @@ public class IslandTeamInviteGUI { return true; } if (clickType.equals(ClickType.LEFT)) { - user.closeInventory(); + // Close inventory after one tick to allow the no pickup click return to occur + Bukkit.getScheduler().runTask(plugin, () -> user.closeInventory()); if (itic.canExecute(user, itic.getLabel(), List.of(player.getName()))) { plugin.log("Invite sent to: " + player.getName() + " by " + user.getName() + " to join island in " + itc.getWorld().getName()); @@ -229,7 +230,8 @@ public class IslandTeamInviteGUI { + itc.getWorld().getName()); } } else if (clickType.equals(ClickType.RIGHT)) { - user.closeInventory(); + // Close inventory after one tick to allow the no pickup click return to occur + Bukkit.getScheduler().runTask(plugin, () -> user.closeInventory()); if (this.itc.getCoopCommand().canExecute(user, itic.getLabel(), List.of(player.getName()))) { plugin.log("Coop: " + player.getName() + " cooped " + user.getName() + " to island in " + itc.getWorld().getName()); @@ -240,15 +242,15 @@ public class IslandTeamInviteGUI { + itc.getWorld().getName()); } } else if (clickType.equals(ClickType.SHIFT_LEFT)) { - user.closeInventory(); + // Close inventory after one tick to allow the no pickup click return to occur + Bukkit.getScheduler().runTask(plugin, () -> user.closeInventory()); if (this.itc.getTrustCommand().canExecute(user, itic.getLabel(), List.of(player.getName()))) { plugin.log("Trust: " + player.getName() + " trusted " + user.getName() + " to island in " + itc.getWorld().getName()); this.itc.getTrustCommand().execute(user, itic.getLabel(), List.of(player.getName())); } else { plugin.log("Trust failed: " + player.getName() + "'s trust failed for " + user.getName() - + " for island in " - + itc.getWorld().getName()); + + " for island in " + itc.getWorld().getName()); } } return true; @@ -272,8 +274,8 @@ public class IslandTeamInviteGUI { public Prompt acceptInput(@NonNull ConversationContext context, String input) { if (itic.canExecute(user, itic.getLabel(), List.of(input)) && itic.execute(user, itic.getLabel(), List.of(input))) { - return Prompt.END_OF_CONVERSATION; - } + return Prompt.END_OF_CONVERSATION; + } // Set the search item to what was entered searchName = input; // Return to the GUI but give a second for the error to show diff --git a/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java b/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java index e7aca2f8b..6615ced50 100644 --- a/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java +++ b/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java @@ -9,6 +9,8 @@ import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Biome; import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.entity.Villager; +import org.bukkit.entity.Villager.Profession; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffectType; import org.bukkit.util.Vector; @@ -29,7 +31,9 @@ import world.bentobox.bentobox.database.json.adapters.LocationTypeAdapter; import world.bentobox.bentobox.database.json.adapters.MaterialTypeAdapter; import world.bentobox.bentobox.database.json.adapters.PairTypeAdapter; import world.bentobox.bentobox.database.json.adapters.PotionEffectTypeAdapter; +import world.bentobox.bentobox.database.json.adapters.ProfessionTypeAdapter; import world.bentobox.bentobox.database.json.adapters.VectorTypeAdapter; +import world.bentobox.bentobox.database.json.adapters.VillagerTypeAdapter; import world.bentobox.bentobox.database.json.adapters.WorldTypeAdapter; import world.bentobox.bentobox.util.Pair; @@ -78,6 +82,10 @@ public class BentoboxTypeAdapterFactory implements TypeAdapterFactory { return (TypeAdapter) new WorldTypeAdapter(); } else if (Vector.class.isAssignableFrom(rawType)) { return (TypeAdapter) new VectorTypeAdapter(); + } else if (Profession.class.isAssignableFrom(rawType)) { + return (TypeAdapter) new ProfessionTypeAdapter(); + } else if (Villager.Type.class.isAssignableFrom(rawType)) { + return (TypeAdapter) new VillagerTypeAdapter(); } else if (Pair.class.isAssignableFrom(rawType)) { // Add Pair handling here with type safety Type pairType = type.getType(); diff --git a/src/main/java/world/bentobox/bentobox/database/json/adapters/ProfessionTypeAdapter.java b/src/main/java/world/bentobox/bentobox/database/json/adapters/ProfessionTypeAdapter.java new file mode 100644 index 000000000..bd40494ec --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/database/json/adapters/ProfessionTypeAdapter.java @@ -0,0 +1,34 @@ +package world.bentobox.bentobox.database.json.adapters; + +import java.io.IOException; + +import org.bukkit.entity.Villager.Profession; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +public class ProfessionTypeAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, Profession profession) throws IOException { + out.value(profession.name()); + } + + @Override + public Profession read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + String id = reader.nextString(); + try { + return Profession.valueOf(id); + } catch (Exception e) { + // Do nothing + } + return Profession.NONE; + + } +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/database/json/adapters/VillagerTypeAdapter.java b/src/main/java/world/bentobox/bentobox/database/json/adapters/VillagerTypeAdapter.java new file mode 100644 index 000000000..050142ad6 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/database/json/adapters/VillagerTypeAdapter.java @@ -0,0 +1,34 @@ +package world.bentobox.bentobox.database.json.adapters; + +import java.io.IOException; + +import org.bukkit.entity.Villager; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +public class VillagerTypeAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, Villager.Type type) throws IOException { + out.value(type.name()); + } + + @Override + public Villager.Type read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + String id = reader.nextString(); + try { + return Villager.Type.valueOf(id); + } catch (Exception e) { + // Do nothing + } + return Villager.Type.PLAINS; + + } +} \ No newline at end of file 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 7f26e3180..848b98863 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -501,7 +501,7 @@ public class Island implements DataObject, MetaDataAble { /** * Get the maximum protected Z block coordinate based on the island location. It - * will never be more than {@link #getMinZ()} + * will never be more than {@link #getMaxZ()} * * @return the maxProtectedZ * @since 1.5.2 diff --git a/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java b/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java index 5a6b9ae70..d1ded8cea 100644 --- a/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java +++ b/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java @@ -363,16 +363,23 @@ public class LangUtilsHook extends Hook { * @param user The user's language will be used for translation. * @return Return the translation result. */ + @SuppressWarnings("deprecation") public static String getPotionBaseEffectName(PotionType potionType, User user) { if (hooked) { return LanguageHelper.getPotionBaseEffectName(potionType, getUserLocale(user)); } - List effects = potionType.getPotionEffects(); - if (effects.isEmpty()) { - return "No Effects"; + try { + List effects = potionType.getPotionEffects(); + + if (effects.isEmpty()) { + return "No Effects"; + } + return effects.stream().map(effect -> Util.prettifyText(effect.getType().getKey().getKey())) + .collect(Collectors.joining(", ")); + } catch (Exception e) { + // Older versions of Spigot pre-1.20.4 don't have getPotionEffects() + return Util.prettifyText(potionType.getEffectType().getKey().getKey()); } - return effects.stream().map(effect -> Util.prettifyText(effect.getType().getKey().getKey())) - .collect(Collectors.joining(", ")); } /** diff --git a/src/main/java/world/bentobox/bentobox/listeners/PanelListenerManager.java b/src/main/java/world/bentobox/bentobox/listeners/PanelListenerManager.java index 6cb57fd45..ff5149f52 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/PanelListenerManager.java +++ b/src/main/java/world/bentobox/bentobox/listeners/PanelListenerManager.java @@ -26,7 +26,7 @@ public class PanelListenerManager implements Listener { private static final HashMap openPanels = new HashMap<>(); - @EventHandler(priority = EventPriority.HIGHEST) + @EventHandler(priority = EventPriority.LOW) public void onInventoryClick(InventoryClickEvent event) { User user = User.getInstance(event.getWhoClicked()); // The player that clicked the item InventoryView view = event.getView(); diff --git a/src/main/java/world/bentobox/bentobox/listeners/SeedWorldMakerListener.java b/src/main/java/world/bentobox/bentobox/listeners/SeedWorldMakerListener.java index 72e42adae..56dc79cb8 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/SeedWorldMakerListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/SeedWorldMakerListener.java @@ -39,7 +39,7 @@ public class SeedWorldMakerListener implements Listener { @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onChunkLoad(ChunkLoadEvent e) { - if (!ready || !e.getChunk().isGenerated()) { + if (!ready) { return; } World world = e.getWorld(); @@ -47,7 +47,7 @@ public class SeedWorldMakerListener implements Listener { World seed = Bukkit.getWorld(world.getName() + "/bentobox"); int x = e.getChunk().getX(); int z = e.getChunk().getZ(); - if (seed != null && !seed.getChunkAt(x, z, false).isGenerated()) { + if (seed != null) { Util.getChunkAtAsync(seed, x, z, true); } }); diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java index aecaf82c0..496ae5238 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java @@ -100,9 +100,11 @@ public class BlockInteractionListener extends FlagListener switch (type) { case BEACON -> this.checkIsland(e, player, loc, Flags.BEACON); + case BELL -> this.checkIsland(e, player, loc, Flags.BELL_RINGING); case BREWING_STAND -> this.checkIsland(e, player, loc, Flags.BREWING); case BEEHIVE, BEE_NEST -> this.checkIsland(e, player, loc, Flags.HIVE); case BARREL -> this.checkIsland(e, player, loc, Flags.BARREL); + case CANDLE -> this.checkIsland(e, player, loc, Flags.CANDLES); case CHEST, CHEST_MINECART -> this.checkIsland(e, player, loc, Flags.CHEST); case TRAPPED_CHEST -> this.checkIsland(e, player, loc, Flags.TRAPPED_CHEST); case FLOWER_POT -> this.checkIsland(e, player, loc, Flags.FLOWER_POT); diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java index b40c99b05..bf8ec1cab 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java @@ -21,6 +21,8 @@ import org.bukkit.event.hanging.HangingBreakByEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.vehicle.VehicleDamageEvent; +import com.google.common.base.Enums; + import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; @@ -101,6 +103,10 @@ public class BreakBlocksListener extends FlagListener { { return; } + if (Enums.getIfPresent(Material.class, "TRIAL_SPAWNER").isPresent() && m.equals(Material.TRIAL_SPAWNER)) { + this.checkIsland(e, p, l, Flags.BREAK_SPAWNERS); + return; + } switch (m) { case CAKE -> this.checkIsland(e, p, l, Flags.BREAK_BLOCKS); @@ -182,7 +188,7 @@ public class BreakBlocksListener extends FlagListener { @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onProjectileHitBreakBlock(ProjectileHitEvent e) { // We want to make sure this is an actual projectile (arrow or trident) - if (!(e.getEntity() instanceof AbstractArrow)) { + if (!(e.getEntity() instanceof Projectile)) { return; } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/CandleListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/CandleListener.java new file mode 100644 index 000000000..bd8f92c0b --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/CandleListener.java @@ -0,0 +1,33 @@ +package world.bentobox.bentobox.listeners.flags.protection; + +import org.bukkit.Tag; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerInteractEvent; + +import world.bentobox.bentobox.api.flags.FlagListener; +import world.bentobox.bentobox.lists.Flags; + +/** + * Protects candles + * @author tastybento + * @since 2.4.2 + */ +public class CandleListener extends FlagListener { + + /** + * Prevent dying signs. + * @param e - event + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onCandleInteract(final PlayerInteractEvent e) { + if (e.getClickedBlock() == null) { + return; + } + + if (Tag.CANDLES.isTagged(e.getClickedBlock().getType()) + || Tag.CANDLE_CAKES.isTagged(e.getClickedBlock().getType())) { + this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.CANDLES); + } + } +} diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java similarity index 99% rename from src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java rename to src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java index f0878c8ce..d000eea7f 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/TNTListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/ExplosionListener.java @@ -27,7 +27,7 @@ import world.bentobox.bentobox.lists.Flags; * Protects islands from visitors blowing things up * @author tastybento */ -public class TNTListener extends FlagListener { +public class ExplosionListener extends FlagListener { /** * Contains {@link EntityType}s that generates an explosion. * @since 1.5.0 diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListener.java index bef997cd8..0580802be 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListener.java @@ -1,15 +1,23 @@ package world.bentobox.bentobox.listeners.flags.protection; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + import org.bukkit.Material; import org.bukkit.Tag; +import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; +import org.bukkit.event.Event; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityInteractEvent; import org.bukkit.event.player.PlayerInteractEvent; +import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; @@ -55,24 +63,47 @@ public class PhysicalInteractionListener extends FlagListener @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onProjectileHit(EntityInteractEvent e) { - if (!(e.getEntity() instanceof Projectile p)) + if (e.getEntity() instanceof Projectile p && p.getShooter() instanceof Player player) { - return; - } - - if (p.getShooter() instanceof Player player) - { - if (Tag.WOODEN_BUTTONS.isTagged(e.getBlock().getType())) - { - this.checkIsland(e, player, e.getBlock().getLocation(), Flags.BUTTON); - return; - } - - if (Tag.PRESSURE_PLATES.isTagged(e.getBlock().getType())) - { - // Pressure plates - this.checkIsland(e, player, e.getBlock().getLocation(), Flags.PRESSURE_PLATE); - } + checkBlocks(e, player, e.getBlock()); } } + + private boolean checkBlocks(Event e, Player player, Block block) { + Map, Flag> TAG_TO_FLAG = Map.of(Tag.WOODEN_BUTTONS, Flags.BUTTON, Tag.PRESSURE_PLATES, + Flags.PRESSURE_PLATE, Tag.FENCE_GATES, Flags.GATE, Tag.DOORS, Flags.DOOR, Tag.CANDLE_CAKES, + Flags.CANDLES, Tag.CANDLES, Flags.CANDLES); + Map MAT_TO_FLAG = Map.of(Material.LEVER, Flags.LEVER, Material.TRIPWIRE, Flags.REDSTONE, + Material.TARGET, Flags.REDSTONE, Material.DECORATED_POT, Flags.BREAK_BLOCKS); + boolean result = TAG_TO_FLAG.entrySet().stream().filter(entry -> entry.getKey().isTagged(block.getType())) + .findFirst().map(entry -> this.checkIsland(e, player, block.getLocation(), entry.getValue())) + .orElse(true); + if (result && MAT_TO_FLAG.containsKey(block.getType())) { + result = this.checkIsland(e, player, block.getLocation(), MAT_TO_FLAG.get(block.getType())); + + } + + return result; + } + + /** + * Protects buttons and plates, etc. from being activated by projectiles that explode + * @param e - event + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onProjectileExplode(EntityExplodeEvent e) { + if (e.getEntity() instanceof Projectile p && p.getShooter() instanceof Player player) { + List blocksToRemove = new ArrayList<>(); + + for (Block b : e.blockList()) { + if (!this.checkBlocks(e, player, b)) { + blocksToRemove.add(b); + } + } + + e.blockList().removeAll(blocksToRemove); + } + } + + } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/SpawnerSpawnEggsListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/SpawnerSpawnEggsListener.java index 09c5d2e5c..34a7baac6 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/SpawnerSpawnEggsListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/SpawnerSpawnEggsListener.java @@ -1,6 +1,5 @@ package world.bentobox.bentobox.listeners.flags.worldsettings; -import org.bukkit.Material; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.player.PlayerInteractEvent; @@ -21,8 +20,8 @@ public class SpawnerSpawnEggsListener extends FlagListener { public void onSpawnerChange(final PlayerInteractEvent e) { User user = User.getInstance(e.getPlayer()); // Checking if the clicked block is a spawner and the item in hand is a mob egg - if (e.getClickedBlock() != null && e.getClickedBlock().getType().equals(Material.SPAWNER) - && e.getItem() != null && e.getItem().getType().toString().endsWith("_SPAWN_EGG") + if (e.getClickedBlock() != null && e.getClickedBlock().getType().name().endsWith("_SPAWNER") + && e.getItem() != null && e.getItem().getType().name().endsWith("_SPAWN_EGG") && getIWM().inWorld(e.getClickedBlock().getWorld()) && !(user.hasPermission(getIWM().getPermissionPrefix(e.getClickedBlock().getWorld()) + "mod.bypass." + Flags.SPAWNER_SPAWN_EGGS.getID() + ".everywhere") || user.hasPermission(getIWM().getPermissionPrefix(e.getClickedBlock().getWorld()) + "mod.bypassprotect")) diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index d0f5c75b5..15eb623d7 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -19,6 +19,7 @@ import world.bentobox.bentobox.listeners.flags.protection.BlockInteractionListen import world.bentobox.bentobox.listeners.flags.protection.BreakBlocksListener; import world.bentobox.bentobox.listeners.flags.protection.BreedingListener; import world.bentobox.bentobox.listeners.flags.protection.BucketListener; +import world.bentobox.bentobox.listeners.flags.protection.CandleListener; import world.bentobox.bentobox.listeners.flags.protection.DyeListener; import world.bentobox.bentobox.listeners.flags.protection.EggListener; import world.bentobox.bentobox.listeners.flags.protection.ElytraListener; @@ -38,7 +39,7 @@ import world.bentobox.bentobox.listeners.flags.protection.PortalListener; import world.bentobox.bentobox.listeners.flags.protection.SculkSensorListener; import world.bentobox.bentobox.listeners.flags.protection.SculkShriekerListener; import world.bentobox.bentobox.listeners.flags.protection.ShearingListener; -import world.bentobox.bentobox.listeners.flags.protection.TNTListener; +import world.bentobox.bentobox.listeners.flags.protection.ExplosionListener; import world.bentobox.bentobox.listeners.flags.protection.TeleportationListener; import world.bentobox.bentobox.listeners.flags.protection.ThrowingListener; import world.bentobox.bentobox.listeners.flags.settings.DecayListener; @@ -266,9 +267,9 @@ public final class Flags { * Prevents players from priming TNT. * @since 1.5.0 * - * @see TNTListener + * @see ExplosionListener */ - public static final Flag TNT_PRIMING = new Flag.Builder("TNT_PRIMING", Material.TNT).listener(new TNTListener()).build(); + public static final Flag TNT_PRIMING = new Flag.Builder("TNT_PRIMING", Material.TNT).listener(new ExplosionListener()).build(); /** * Prevents players from extinguishing fires. @@ -461,7 +462,7 @@ public final class Flags { /** * If {@code false}, prevents TNT from breaking blocks and damaging nearby entities. * @since 1.5.0 - * @see TNTListener + * @see ExplosionListener */ public static final Flag TNT_DAMAGE = new Flag.Builder("TNT_DAMAGE", Material.TNT).type(Type.SETTING) .mode(Flag.Mode.ADVANCED).build(); @@ -469,7 +470,7 @@ public final class Flags { /** * If {@code false}, prevents Block Explode from breaking blocks and damaging nearby entities. * @since 1.19.1 - * @see TNTListener + * @see ExplosionListener */ public static final Flag BLOCK_EXPLODE_DAMAGE = new Flag.Builder("BLOCK_EXPLODE_DAMAGE", Material.TNT_MINECART).type(Type.SETTING) .mode(Flag.Mode.ADVANCED).build(); @@ -477,7 +478,7 @@ public final class Flags { /** * If {@code false}, prevents TNT from breaking blocks and damaging nearby entities outside of island boundaries. * @since 1.15.3 - * @see TNTListener + * @see ExplosionListener */ public static final Flag WORLD_TNT_DAMAGE = new Flag.Builder("WORLD_TNT_DAMAGE", Material.TNT) .type(Type.WORLD_SETTING) @@ -486,7 +487,7 @@ public final class Flags { /** * If {@code false}, prevents Block Explode from breaking blocks and damaging nearby entities outside of island boundaries. * @since 1.19.1 - * @see TNTListener + * @see ExplosionListener */ public static final Flag WORLD_BLOCK_EXPLODE_DAMAGE = new Flag.Builder("WORLD_BLOCK_EXPLODE_DAMAGE", Material.TNT_MINECART) .type(Type.WORLD_SETTING) @@ -687,6 +688,23 @@ public final class Flags { */ public static final Flag SIGN_EDITING = new Flag.Builder("SIGN_EDITING", Material.DARK_OAK_SIGN).mode(Flag.Mode.BASIC).type(Type.PROTECTION).build(); + /** + * Bell ringing protection + * Listeners are {@link BlockInteractionListener} and {@link PhysicalInteractionListener} + * @since 2.4.2 + */ + public static final Flag BELL_RINGING = new Flag.Builder("BELL_RINGING", Material.BELL).mode(Flag.Mode.EXPERT) + .type(Type.PROTECTION).build(); + + /** + * Candle protection + * Listener is {@link CandleListener} + * @since 2.4.2 + */ + public static final Flag CANDLES = new Flag.Builder("CANDLES", Material.CANDLE).mode(Flag.Mode.EXPERT) + .listener(new CandleListener()) + .type(Type.PROTECTION).build(); + /** * Provides a list of all the Flag instances contained in this class using reflection. * Deprecated Flags are ignored. diff --git a/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java b/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java index d472ba78d..5677b3c89 100644 --- a/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java +++ b/src/main/java/world/bentobox/bentobox/lists/GameModePlaceholder.java @@ -125,6 +125,22 @@ public enum GameModePlaceholder { */ ISLAND_MEMBERS_LIST("island_members_list", (addon, user, island) -> island == null ? "" : island.getMemberSet(RanksManager.MEMBER_RANK).stream() .map(addon.getPlayers()::getName).collect(Collectors.joining(","))), + /** + * Returns a comma separated list of player names that are at least TRUSTED on this island. + * @since 2.4.2 + */ + ISLAND_TRUSTED_LIST("island_trusted_list", + (addon, user, island) -> island == null ? "" + : island.getMemberSet(RanksManager.TRUSTED_RANK, false).stream().map(addon.getPlayers()::getName) + .collect(Collectors.joining(","))), + /** + * Returns a comma separated list of player names that are at least COOP on this island. + * @since 2.4.2 + */ + ISLAND_COOP_LIST("island_coop_list", + (addon, user, island) -> island == null ? "" + : island.getMemberSet(RanksManager.COOP_RANK, false).stream().map(addon.getPlayers()::getName) + .collect(Collectors.joining(","))), /** * Returns the amount of players that are TRUSTED on this island. * @since 1.5.0 @@ -244,6 +260,20 @@ public enum GameModePlaceholder { VISITED_ISLAND_MEMBERS_LIST("visited_island_members_list", (addon, user, island) -> getVisitedIsland(addon, user).map(value -> value.getMemberSet(RanksManager.MEMBER_RANK).stream() .map(addon.getPlayers()::getName).collect(Collectors.joining(","))).orElse("")), + /** + * Returns a comma separated list of player names that are at TRUSTED on the island the player is standing on. + * @since 2.4.2 + */ + VISITED_ISLAND_TRUSTED_LIST("visited_island_trusted_list", (addon, user, + island) -> getVisitedIsland(addon, user).map(value -> value.getMemberSet(RanksManager.TRUSTED_RANK, false) + .stream().map(addon.getPlayers()::getName).collect(Collectors.joining(","))).orElse("")), + /** + * Returns a comma separated list of player names that are COOP on the island the player is standing on. + * @since 2.4.2 + */ + VISITED_ISLAND_COOP_LIST("visited_island_coop_list", (addon, user, + island) -> getVisitedIsland(addon, user).map(value -> value.getMemberSet(RanksManager.COOP_RANK, false) + .stream().map(addon.getPlayers()::getName).collect(Collectors.joining(","))).orElse("")), /** * Returns the amount of players that are at least MEMBER on the island the player is standing on. * @since 1.5.2 diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java index 4a5e66c33..fdcbddee3 100644 --- a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java @@ -108,7 +108,9 @@ public class BlueprintClipboardManager { bp = gson.fromJson(fr, Blueprint.class); } catch (Exception e) { plugin.logError("Blueprint has JSON error: " + zipFile.getName()); + plugin.logStacktrace(e); throw new IOException("Blueprint has JSON error: " + zipFile.getName()); + } Files.delete(file.toPath()); // Bedrock check and set diff --git a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java index 47c27bea9..dd5dd9bb0 100644 --- a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java @@ -84,9 +84,11 @@ public class PlayersManager { Objects.requireNonNull(playerUUID, "Player UUID must not be null"); // If the player exists in the database, load it; otherwise, create and save a new player - Players player = loadPlayer(playerUUID); - if (player != null) { - return player; + if (handler.objectExists(playerUUID.toString())) { + Players p = loadPlayer(playerUUID); + if (p != null) { + return p; + } } Players newPlayer = new Players(plugin, playerUUID); handler.saveObjectAsync(newPlayer); diff --git a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java index f5edc3c8f..e6f91066d 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java @@ -38,7 +38,7 @@ public class IslandCache { * Map of all islands with island uniqueId as key */ @NonNull - private final Map<@NonNull String, @NonNull Island> islandsById; + private final Map<@NonNull String, Island> islandsById; /** * Every player who is associated with an island is in this map. Key is player * UUID, value is a set of islands diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 7989f41ed..47c5f27bc 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -912,6 +912,10 @@ protection: description: Toggle interaction name: Beacons hint: Beacon use disabled + BELL_RINGING: + description: Toggle interaction + name: Allow bell ringing + hint: Bell ringing disabled BED: description: Toggle interaction name: Beds @@ -960,6 +964,10 @@ protection: description: Toggle button use name: Buttons hint: Button use disabled + CANDLES: + description: Toggle candle interaction + name: Candles + hint: Candle interaction disabled CAKE: description: Toggle cake interaction name: Cakes diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/CandleListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/CandleListenerTest.java new file mode 100644 index 000000000..6d709b421 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/CandleListenerTest.java @@ -0,0 +1,110 @@ +package world.bentobox.bentobox.listeners.flags.protection; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.Event.Result; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.listeners.flags.AbstractCommonSetup; +import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.util.Util; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ BentoBox.class, Flags.class, Util.class, Bukkit.class }) +public class CandleListenerTest extends AbstractCommonSetup { + + private CandleListener l; + @Mock + private Block block; + + /** + */ + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + // Island manager + // Default is that everything is allowed + when(island.isAllowed(any(), any())).thenReturn(true); + + when(block.getLocation()).thenReturn(location); + + // Tags + when(Tag.CANDLES.isTagged(any(Material.class))).thenReturn(true); + when(Tag.CANDLE_CAKES.isTagged(any(Material.class))).thenReturn(true); + + // Listener + l = new CandleListener(); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.protection.CandleListener#onCandleInteract(org.bukkit.event.player.PlayerInteractEvent)}. + */ + @Test + public void testOnCandleInteract() { + // Block + when(block.getType()).thenReturn(Material.CANDLE); + PlayerInteractEvent e = new PlayerInteractEvent(mockPlayer, Action.LEFT_CLICK_BLOCK, null, block, BlockFace.UP); + l.onCandleInteract(e); + assertEquals(Result.ALLOW, e.useInteractedBlock()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.protection.CandleListener#onCandleInteract(org.bukkit.event.player.PlayerInteractEvent)}. + */ + @Test + public void testOnCandleCakeInteract() { + // Block + when(block.getType()).thenReturn(Material.CANDLE_CAKE); + PlayerInteractEvent e = new PlayerInteractEvent(mockPlayer, Action.LEFT_CLICK_BLOCK, null, block, BlockFace.UP); + l.onCandleInteract(e); + assertEquals(Result.ALLOW, e.useInteractedBlock()); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.protection.CandleListener#onCandleInteract(org.bukkit.event.player.PlayerInteractEvent)}. + */ + @Test + public void testOnCandleInteractFail() { + when(island.isAllowed(any(), any())).thenReturn(false); + // Block + when(block.getType()).thenReturn(Material.CANDLE); + PlayerInteractEvent e = new PlayerInteractEvent(mockPlayer, Action.LEFT_CLICK_BLOCK, null, block, BlockFace.UP); + l.onCandleInteract(e); + assertEquals(Result.DENY, e.useInteractedBlock()); + verify(notifier).notify(any(), eq("protection.protected")); + } + + /** + * Test method for {@link world.bentobox.bentobox.listeners.flags.protection.CandleListener#onCandleInteract(org.bukkit.event.player.PlayerInteractEvent)}. + */ + @Test + public void testOnCandleCakeInteractFail() { + when(island.isAllowed(any(), any())).thenReturn(false); + // Block + when(block.getType()).thenReturn(Material.CANDLE_CAKE); + PlayerInteractEvent e = new PlayerInteractEvent(mockPlayer, Action.LEFT_CLICK_BLOCK, null, block, BlockFace.UP); + l.onCandleInteract(e); + assertEquals(Result.DENY, e.useInteractedBlock()); + verify(notifier).notify(any(), eq("protection.protected")); + } + +} diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListenerTest.java index 0db36a177..883d07019 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListenerTest.java @@ -7,10 +7,13 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -25,6 +28,7 @@ import org.bukkit.entity.Slime; import org.bukkit.entity.Zombie; import org.bukkit.event.Event.Result; import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityInteractEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; @@ -228,4 +232,61 @@ public class PhysicalInteractionListenerTest extends AbstractCommonSetup { assertTrue(p.name() +" failed", e.isCancelled()); }); } + + /** + * Test method for {@link PhysicalInteractionListener#onProjectileExplode(org.bukkit.event.entity.EntityExplodeEvent)}. + */ + @Test + public void testOnProjectileExplodeNotProjectile() { + Entity entity = mock(Entity.class); + List blocks = new ArrayList<>(); + EntityExplodeEvent e = new EntityExplodeEvent(entity, location, blocks, 0); + PhysicalInteractionListener i = new PhysicalInteractionListener(); + i.onProjectileExplode(e); + assertFalse(e.isCancelled()); + } + + /** + * Test method for {@link PhysicalInteractionListener#onProjectileExplode(org.bukkit.event.entity.EntityExplodeEvent)}. + */ + @Test + public void testOnProjectileExplodeProjectileNoPlayer() { + Projectile entity = mock(Projectile.class); + ProjectileSource source = mock(Creeper.class); + when(entity.getShooter()).thenReturn(source); + List blocks = new ArrayList<>(); + EntityExplodeEvent e = new EntityExplodeEvent(entity, location, blocks, 0); + PhysicalInteractionListener i = new PhysicalInteractionListener(); + i.onProjectileExplode(e); + assertFalse(e.isCancelled()); + } + + /** + * Test method for {@link PhysicalInteractionListener#onProjectileExplode(org.bukkit.event.entity.EntityExplodeEvent)}. + */ + @Test + public void testOnProjectileExplodeProjectilePlayer() { + Projectile entity = mock(Projectile.class); + when(entity.getShooter()).thenReturn(mockPlayer); + List blocks = new ArrayList<>(); + Block block1 = mock(Block.class); + Block block2 = mock(Block.class); + when(block1.getLocation()).thenReturn(location); + when(block2.getLocation()).thenReturn(location); + blocks.add(block1); + blocks.add(block2); + + EntityExplodeEvent e = new EntityExplodeEvent(entity, location, blocks, 0); + PhysicalInteractionListener i = new PhysicalInteractionListener(); + + // Test with wooden button + when(block1.getType()).thenReturn(Material.OAK_BUTTON); + when(Tag.WOODEN_BUTTONS.isTagged(Material.OAK_BUTTON)).thenReturn(true); + // Test with pressure plate + when(block2.getType()).thenReturn(Material.STONE_PRESSURE_PLATE); + when(Tag.PRESSURE_PLATES.isTagged(Material.STONE_PRESSURE_PLATE)).thenReturn(true); + + i.onProjectileExplode(e); + verify(notifier, times(2)).notify(any(), eq("protection.protected")); + } } diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/TNTListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/TNTListenerTest.java index 1aff866eb..846ddc34c 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/TNTListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/TNTListenerTest.java @@ -58,7 +58,7 @@ public class TNTListenerTest extends AbstractCommonSetup { private Entity entity; // Class under test - private TNTListener listener; + private ExplosionListener listener; @Override @Before @@ -85,7 +85,7 @@ public class TNTListenerTest extends AbstractCommonSetup { // Util when(Util.findFirstMatchingEnum(any(), anyString())).thenCallRealMethod(); - listener = new TNTListener(); + listener = new ExplosionListener(); listener.setPlugin(plugin); } diff --git a/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java index 2cc674d87..c9f53635d 100644 --- a/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java @@ -46,7 +46,7 @@ public class FlagsManagerTest { /** * Update this value if the number of registered listeners changes */ - private static final int NUMBER_OF_LISTENERS = 54; + private static final int NUMBER_OF_LISTENERS = 55; @Mock private BentoBox plugin; @Mock