diff --git a/ADDON.md b/ADDON.md index ae5386c67..01f9fc49c 100644 --- a/ADDON.md +++ b/ADDON.md @@ -1,4 +1,5 @@ The following is a list of all addons currently made for BentoBox: +* [**Bank**](https://github.com/BentoBoxWorld/Bank/): Provides an island bank to enable island members to share money. * [**Biomes**](https://github.com/BentoBoxWorld/Biomes/): Enables players to change biomes on islands. * [**Border**](https://github.com/BentoBoxWorld/Border/): Adds a world border around islands. * [**Cauldron Witchery**](https://github.com/BentoBoxWorld/CauldronWitchery/): Allows summoning mobs using some magic! diff --git a/README.md b/README.md index 1806f8064..ea309b227 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Start now to create the server you've dreamed of! These are some popular Gamemodes: * [**AcidIsland**](https://github.com/BentoBoxWorld/AcidIsland): You are marooned in a sea of acid! * [**AOneBlock**](https://github.com/BentoBoxWorld/AOneBlock): Start to play with only 1 magical block. +* [**Boxed**](https://github.com/BentoBoxWorld/Boxed): A game mode where you are boxed into a tiny space that only expands by completing advancements. * [**BSkyBlock**](https://github.com/BentoBoxWorld/BSkyBlock): The successor to the popular ASkyBlock. * [**CaveBlock**](https://github.com/BentoBoxWorld/CaveBlock): Try to live underground! * [**SkyGrid**](https://github.com/BentoBoxWorld/SkyGrid): Survive in world made up of scattered blocks - what an adventure! diff --git a/src/main/java/world/bentobox/bentobox/BStats.java b/src/main/java/world/bentobox/bentobox/BStats.java index dee4a6a86..aedf7c83a 100644 --- a/src/main/java/world/bentobox/bentobox/BStats.java +++ b/src/main/java/world/bentobox/bentobox/BStats.java @@ -1,10 +1,14 @@ package world.bentobox.bentobox; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import java.util.UUID; import org.bstats.bukkit.Metrics; import org.bstats.charts.AdvancedPie; +import org.bstats.charts.SimpleBarChart; import org.bstats.charts.SimplePie; import org.bstats.charts.SingleLineChart; import org.bukkit.Bukkit; @@ -29,6 +33,13 @@ public class BStats { */ private int islandsCreatedCount = 0; + /** + * Contains the amount of connected players since last data send. + * @since 1.17.1 + */ + private final Set connectedPlayerSet = new HashSet<>(); + + BStats(BentoBox plugin) { this.plugin = plugin; } @@ -53,6 +64,11 @@ public class BStats { // Single Line charts registerIslandsCountChart(); registerIslandsCreatedChart(); + + // Bar Charts + registerAddonsBarChart(); + registerGameModeAddonsBarChart(); + registerHooksBarChart(); } private void registerDefaultLanguageChart() { @@ -86,6 +102,15 @@ public class BStats { islandsCreatedCount++; } + /** + * Adds given UUID to the connected player set. + * @param uuid UUID of a player who logins. + * @since 1.17.1 + */ + public void addPlayer(UUID uuid) { + this.connectedPlayerSet.add(uuid); + } + /** * Sends the enabled addons (except GameModeAddons) of this server. * @since 1.1 @@ -132,7 +157,9 @@ public class BStats { */ private void registerPlayersPerServerChart() { metrics.addCustomChart(new SimplePie("playersPerServer", () -> { - int players = Bukkit.getOnlinePlayers().size(); + int players = this.connectedPlayerSet.size(); + this.connectedPlayerSet.clear(); + if (players <= 0) return "0"; else if (players <= 10) return "1-10"; else if (players <= 30) return "11-30"; @@ -164,4 +191,44 @@ public class BStats { return values; })); } + + /** + * Sends the enabled addons (except GameModeAddons) of this server as bar chart. + * @since 1.17.1 + */ + private void registerAddonsBarChart() { + metrics.addCustomChart(new SimpleBarChart("addonsBar", () -> { + Map values = new HashMap<>(); + plugin.getAddonsManager().getEnabledAddons().stream() + .filter(addon -> !(addon instanceof GameModeAddon) && addon.getDescription().isMetrics()) + .forEach(addon -> values.put(addon.getDescription().getName(), 1)); + return values; + })); + } + + /** + * Sends the enabled GameModeAddons of this server as a bar chart. + * @since 1.17.1 + */ + private void registerGameModeAddonsBarChart() { + metrics.addCustomChart(new SimpleBarChart("gameModeAddonsBar", () -> { + Map values = new HashMap<>(); + plugin.getAddonsManager().getGameModeAddons().stream() + .filter(gameModeAddon -> gameModeAddon.getDescription().isMetrics()) + .forEach(gameModeAddon -> values.put(gameModeAddon.getDescription().getName(), 1)); + return values; + })); + } + + /** + * Sends the enabled Hooks of this server as a bar chart. + * @since 1.17.1 + */ + private void registerHooksBarChart() { + metrics.addCustomChart(new SimpleBarChart("hooksBar", () -> { + Map values = new HashMap<>(); + plugin.getHooks().getHooks().forEach(hook -> values.put(hook.getPluginName(), 1)); + return values; + })); + } } diff --git a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java index c56474630..754cdf325 100644 --- a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java +++ b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java @@ -1,5 +1,6 @@ package world.bentobox.bentobox.api.flags; +import java.util.Arrays; import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -131,6 +132,7 @@ public class Flag implements Comparable { private final Addon addon; private final int cooldown; private final Mode mode; + private final Set subflags; private Flag(Builder builder) { this.id = builder.id; @@ -147,6 +149,7 @@ public class Flag implements Comparable { this.cooldown = builder.cooldown; this.addon = builder.addon; this.mode = builder.mode; + this.subflags = builder.subflags; } public String getID() { @@ -200,6 +203,18 @@ public class Flag implements Comparable { .getWorldSettings(world) .getWorldFlags() .put(getID(), setting); + + // Subflag support + if (hasSubflags()) { + subflags.stream() + .filter(subflag -> subflag.getType().equals(Type.WORLD_SETTING) || subflag.getType().equals(Type.PROTECTION)) + .forEach(subflag -> BentoBox.getInstance() + .getIWM() + .getWorldSettings(world) + .getWorldFlags() + .put(subflag.getID(), setting)); + } + // Save config file BentoBox.getInstance().getIWM().getAddon(world).ifPresent(GameModeAddon::saveWorldSettings); } @@ -208,6 +223,7 @@ public class Flag implements Comparable { /** * Set the original status of this flag for locations outside of island spaces. * May be overriden by the the setting for this world. + * Does not affect subflags. * @param defaultSetting - true means it is allowed. false means it is not allowed */ public void setDefaultSetting(boolean defaultSetting) { @@ -217,6 +233,7 @@ public class Flag implements Comparable { /** * Set the status of this flag for locations outside of island spaces for a specific world. * World must exist and be registered before this method can be called. + * Does not affect subflags. * @param defaultSetting - true means it is allowed. false means it is not allowed */ public void setDefaultSetting(World world, boolean defaultSetting) { @@ -435,6 +452,22 @@ public class Flag implements Comparable { return mode; } + /** + * @return whether the flag has subflags (and therefore is a parent flag) + * @since 1.17.0 + */ + public boolean hasSubflags() { + return !subflags.isEmpty(); + } + + /** + * @return the subflags, an empty Set if none + * @since 1.17.0 + */ + public Set getSubflags() { + return subflags; + } + @Override public String toString() { return "Flag [id=" + id + "]"; @@ -480,6 +513,9 @@ public class Flag implements Comparable { // Mode private Mode mode = Mode.EXPERT; + // Subflags + private Set subflags; + /** * Builder for making flags * @param id - a unique id that MUST be the same as the enum of the flag @@ -488,6 +524,7 @@ public class Flag implements Comparable { public Builder(String id, Material icon) { this.id = id; this.icon = icon; + this.subflags = new HashSet<>(); } /** @@ -595,6 +632,19 @@ public class Flag implements Comparable { return this; } + /** + * Add subflags and designate this flag as a parent flag. + * Subflags have their state simultaneously changed with the parent flag. + * Take extra care to ensure that subflags have the same number of possible values as the parent flag. + * @param flags all Flags that are subflags + * @return Builder - flag builder + * @since 1.17.0 + */ + public Builder subflags(Flag... flags) { + this.subflags.addAll(Arrays.asList(flags)); + return this; + } + /** * Build the flag * @return Flag diff --git a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java index 401e760de..922337133 100644 --- a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java +++ b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java @@ -90,6 +90,12 @@ public class CycleClick implements PanelItem.ClickHandler { user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_OFF, 1F, 1F); // Fire event Bukkit.getPluginManager().callEvent(new FlagProtectionChangeEvent(island, user.getUniqueId(), flag, island.getFlag(flag))); + + // Subflag support + if (flag.hasSubflags()) { + // Fire events for all subflags as well + flag.getSubflags().forEach(subflag -> Bukkit.getPluginManager().callEvent(new FlagProtectionChangeEvent(island, user.getUniqueId(), subflag, island.getFlag(subflag)))); + } } else if (click.equals(ClickType.RIGHT)) { if (currentRank <= minRank) { island.setFlag(flag, maxRank); @@ -99,6 +105,12 @@ public class CycleClick implements PanelItem.ClickHandler { user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); // Fire event Bukkit.getPluginManager().callEvent(new FlagProtectionChangeEvent(island, user.getUniqueId(), flag, island.getFlag(flag))); + + // Subflag support + if (flag.hasSubflags()) { + // Fire events for all subflags as well + flag.getSubflags().forEach(subflag -> Bukkit.getPluginManager().callEvent(new FlagProtectionChangeEvent(island, user.getUniqueId(), subflag, island.getFlag(subflag)))); + } } else if (click.equals(ClickType.SHIFT_LEFT) && user.isOp()) { if (!plugin.getIWM().getHiddenFlags(user.getWorld()).contains(flag.getID())) { plugin.getIWM().getHiddenFlags(user.getWorld()).add(flag.getID()); diff --git a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java index 08dbba227..2afcd6b96 100644 --- a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java +++ b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java @@ -77,6 +77,11 @@ public class IslandToggleClick implements ClickHandler { island.setCooldown(flag); // Fire event Bukkit.getPluginManager().callEvent(new FlagSettingChangeEvent(island, user.getUniqueId(), flag, island.isAllowed(flag))); + + if (flag.hasSubflags()) { + // Fire events for all subflags as well + flag.getSubflags().forEach(subflag -> Bukkit.getPluginManager().callEvent(new FlagSettingChangeEvent(island, user.getUniqueId(), subflag, island.isAllowed(subflag)))); + } } }); } else { diff --git a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/WorldToggleClick.java b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/WorldToggleClick.java index e33bbf623..cb8c0a0e1 100644 --- a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/WorldToggleClick.java +++ b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/WorldToggleClick.java @@ -61,6 +61,12 @@ public class WorldToggleClick implements ClickHandler { user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); // Fire event Bukkit.getPluginManager().callEvent(new FlagWorldSettingChangeEvent(user.getWorld(), user.getUniqueId(), flag, flag.isSetForWorld(user.getWorld()))); + + // Subflag support + if (flag.hasSubflags()) { + // Fire events for all subflags as well + flag.getSubflags().forEach(subflag -> Bukkit.getPluginManager().callEvent(new FlagWorldSettingChangeEvent(user.getWorld(), user.getUniqueId(), subflag, subflag.isSetForWorld(user.getWorld())))); + } } // Save world settings 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 57554ada4..a2273e16f 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -1,6 +1,7 @@ package world.bentobox.bentobox.database.objects; import java.io.IOException; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.EnumMap; import java.util.HashMap; @@ -823,11 +824,28 @@ public class Island implements DataObject, MetaDataAble { /** * Set the Island Guard flag rank + * This method affects subflags (if the given flag is a parent flag) * @param flag - flag * @param value - Use RanksManager settings, e.g. RanksManager.MEMBER */ - public void setFlag(Flag flag, int value){ + public void setFlag(Flag flag, int value) { + setFlag(flag, value, true); + } + + /** + * Set the Island Guard flag rank + * Also specify whether subflags are affected by this method call + * @param flag - flag + * @param value - Use RanksManager settings, e.g. RanksManager.MEMBER + * @param doSubflags - whether to set subflags + */ + public void setFlag(Flag flag, int value, boolean doSubflags) { flags.put(flag, value); + // Subflag support + if (doSubflags && flag.hasSubflags()) { + // Ensure that a subflag isn't a subflag of itself or else we're in trouble! + flag.getSubflags().forEach(subflag -> setFlag(subflag, value, true)); + } setChanged(); } @@ -1075,7 +1093,14 @@ public class Island implements DataObject, MetaDataAble { // Fixes #getLastPlayed() returning 0 when it is the owner's first connection. long lastPlayed = (Bukkit.getServer().getOfflinePlayer(getOwner()).getLastPlayed() != 0) ? Bukkit.getServer().getOfflinePlayer(getOwner()).getLastPlayed() : Bukkit.getServer().getOfflinePlayer(getOwner()).getFirstPlayed(); - user.sendMessage("commands.admin.info.last-login","[date]", new Date(lastPlayed).toString()); + String formattedDate; + try { + String dateTimeFormat = plugin.getLocalesManager().get("commands.admin.info.last-login-date-time-format"); + formattedDate = new SimpleDateFormat(dateTimeFormat).format(new Date(lastPlayed)); + } catch (NullPointerException | IllegalArgumentException ignored) { + formattedDate = new Date(lastPlayed).toString(); + } + user.sendMessage("commands.admin.info.last-login","[date]", formattedDate); user.sendMessage("commands.admin.info.deaths", "[number]", String.valueOf(plugin.getPlayers().getDeaths(getWorld(), getOwner()))); String resets = String.valueOf(plugin.getPlayers().getResets(getWorld(), getOwner())); @@ -1124,23 +1149,50 @@ public class Island implements DataObject, MetaDataAble { /** * Toggles a settings flag + * This method affects subflags (if the given flag is a parent flag) * @param flag - flag */ public void toggleFlag(Flag flag) { + toggleFlag(flag, true); + } + + /** + * Toggles a settings flag + * Also specify whether subflags are affected by this method call + * @param flag - flag + */ + public void toggleFlag(Flag flag, boolean doSubflags) { + boolean newToggleValue = !isAllowed(flag); // Use for subflags if (flag.getType().equals(Flag.Type.SETTING) || flag.getType().equals(Flag.Type.WORLD_SETTING)) { - setSettingsFlag(flag, !isAllowed(flag)); + setSettingsFlag(flag, newToggleValue, doSubflags); } setChanged(); } /** * Sets the state of a settings flag + * This method affects subflags (if the given flag is a parent flag) * @param flag - flag * @param state - true or false */ public void setSettingsFlag(Flag flag, boolean state) { + setSettingsFlag(flag, state, true); + } + + /** + * Sets the state of a settings flag + * Also specify whether subflags are affected by this method call + * @param flag - flag + * @param state - true or false + */ + public void setSettingsFlag(Flag flag, boolean state, boolean doSubflags) { + int newState = state ? 1 : -1; if (flag.getType().equals(Flag.Type.SETTING) || flag.getType().equals(Flag.Type.WORLD_SETTING)) { - flags.put(flag, state ? 1 : -1); + flags.put(flag, newState); + if (doSubflags && flag.hasSubflags()) { + // If we have circular subflags or a flag is a subflag of itself we are in trouble! + flag.getSubflags().forEach(subflag -> setSettingsFlag(subflag, state, true)); + } } setChanged(); } diff --git a/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java b/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java index 29882ce21..bac2aa6b1 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java @@ -102,6 +102,9 @@ public class JoinLeaveListener implements Listener { plugin.getIslands().getMaxMembers(i, RanksManager.TRUSTED_RANK); plugin.getIslands().getMaxHomes(i); }); + + // Add a player to the bStats cache. + plugin.getMetrics().ifPresent(bStats -> bStats.addPlayer(playerUUID)); } 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 1fbd2e2af..faf4408ad 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 @@ -95,7 +95,7 @@ public class BlockInteractionListener extends FlagListener { private void checkClickedBlock(Event e, Player player, Location loc, Material type) { // Handle pots if (type.name().startsWith("POTTED")) { - checkIsland(e, player, loc, Flags.CONTAINER); + checkIsland(e, player, loc, Flags.FLOWER_POT); return; } if (Tag.ANVIL.isTagged(type)) { @@ -115,7 +115,7 @@ public class BlockInteractionListener extends FlagListener { return; } if (Tag.SHULKER_BOXES.isTagged(type)) { - checkIsland(e, player, loc, Flags.CONTAINER); + checkIsland(e, player, loc, Flags.SHULKER_BOX); return; } if (Tag.TRAPDOORS.isTagged(type)) { @@ -136,12 +136,20 @@ public class BlockInteractionListener extends FlagListener { checkIsland(e, player, loc, Flags.HIVE); break; case BARREL: + checkIsland(e, player, loc, Flags.BARREL); + break; case CHEST: case CHEST_MINECART: + checkIsland(e, player, loc, Flags.CHEST); + break; case TRAPPED_CHEST: + checkIsland(e, player, loc, Flags.TRAPPED_CHEST); + break; case FLOWER_POT: + checkIsland(e, player, loc, Flags.FLOWER_POT); + break; case COMPOSTER: - checkIsland(e, player, loc, Flags.CONTAINER); + checkIsland(e, player, loc, Flags.COMPOSTER); break; case DISPENSER: checkIsland(e, player, loc, Flags.DISPENSER); diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java index 2cd31f7a2..ef7e103c2 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java @@ -1,15 +1,20 @@ package world.bentobox.bentobox.listeners.flags.protection; +import org.bukkit.Material; +import org.bukkit.block.Barrel; import org.bukkit.block.Beacon; import org.bukkit.block.BrewingStand; +import org.bukkit.block.Chest; import org.bukkit.block.Dispenser; import org.bukkit.block.Dropper; import org.bukkit.block.Furnace; import org.bukkit.block.Hopper; +import org.bukkit.block.ShulkerBox; import org.bukkit.entity.Animals; import org.bukkit.entity.NPC; import org.bukkit.entity.Player; import org.bukkit.entity.minecart.HopperMinecart; +import org.bukkit.entity.minecart.StorageMinecart; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.inventory.InventoryClickEvent; @@ -61,6 +66,29 @@ public class InventoryListener extends FlagListener { else if (inventoryHolder instanceof NPC) { checkIsland(e, player, e.getInventory().getLocation(), Flags.TRADING); } + else if (inventoryHolder instanceof Barrel) { + checkIsland(e, player, e.getInventory().getLocation(), Flags.BARREL); + } + else if (inventoryHolder instanceof ShulkerBox) { + checkIsland(e, player, e.getInventory().getLocation(), Flags.SHULKER_BOX); + } + else if (inventoryHolder instanceof Chest) { + // To differentiate between a Chest and a Trapped Chest we need to get the Block corresponding to the inventory + Chest chestInventoryHolder = (Chest) inventoryHolder; + try { + if (chestInventoryHolder.getBlock().getType() == Material.TRAPPED_CHEST) { + checkIsland(e, player, e.getInventory().getLocation(), Flags.TRAPPED_CHEST); + } else { + checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST); + } + } catch (IllegalStateException ignored) { + // Thrown if the Chest corresponds to a block that isn't placed (how did we get here?) + checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST); + } + } + else if (inventoryHolder instanceof StorageMinecart) { + checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST); + } else if (!(inventoryHolder instanceof Player)) { // All other containers checkIsland(e, player, e.getInventory().getLocation(), Flags.CONTAINER); diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorKeepInventoryListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorKeepInventoryListener.java new file mode 100644 index 000000000..e6ef8b142 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorKeepInventoryListener.java @@ -0,0 +1,39 @@ +package world.bentobox.bentobox.listeners.flags.worldsettings; + +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.PlayerDeathEvent; +import world.bentobox.bentobox.api.flags.FlagListener; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.util.Util; + +import java.util.Optional; + +/** + * Prevents visitors from losing their items if they + * die on an island in which they are a visitor. + * Handles {@link world.bentobox.bentobox.lists.Flags#VISITOR_KEEP_INVENTORY}. + * @author jstnf + * @since 1.17.0 + */ +public class VisitorKeepInventoryListener extends FlagListener { + + @EventHandler (priority = EventPriority.LOW, ignoreCancelled = true) + public void onVisitorDeath(PlayerDeathEvent e) { + World world = Util.getWorld(e.getEntity().getWorld()); + if (!getIWM().inWorld(world) || !Flags.VISITOR_KEEP_INVENTORY.isSetForWorld(world)) { + // If the player dies outside of the island world, don't do anything + return; + } + + Optional island = getIslands().getProtectedIslandAt(e.getEntity().getLocation()); + if (island.isPresent() && !island.get().getMemberSet().contains(e.getEntity().getUniqueId())) { + e.setKeepInventory(true); + e.setKeepLevel(true); + e.getDrops().clear(); + e.setDroppedExp(0); + } + } +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index a9d07fbce..057082afe 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -62,6 +62,7 @@ import world.bentobox.bentobox.listeners.flags.worldsettings.PistonPushListener; import world.bentobox.bentobox.listeners.flags.worldsettings.RemoveMobsListener; import world.bentobox.bentobox.listeners.flags.worldsettings.SpawnerSpawnEggsListener; import world.bentobox.bentobox.listeners.flags.worldsettings.TreesGrowingOutsideRangeListener; +import world.bentobox.bentobox.listeners.flags.worldsettings.VisitorKeepInventoryListener; import world.bentobox.bentobox.listeners.flags.worldsettings.WitherListener; import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; @@ -110,7 +111,14 @@ public final class Flags { public static final Flag BEACON = new Flag.Builder("BEACON", Material.BEACON).build(); public static final Flag BED = new Flag.Builder("BED", Material.RED_BED).build(); public static final Flag BREWING = new Flag.Builder("BREWING", Material.BREWING_STAND).mode(Flag.Mode.ADVANCED).build(); - public static final Flag CONTAINER = new Flag.Builder("CONTAINER", Material.CHEST).mode(Flag.Mode.BASIC).build(); + // START CONTAINER split + public static final Flag CHEST = new Flag.Builder("CHEST", Material.CHEST).mode(Flag.Mode.ADVANCED).build(); + public static final Flag BARREL = new Flag.Builder("BARREL", Material.BARREL).mode(Flag.Mode.ADVANCED).build(); + public static final Flag COMPOSTER = new Flag.Builder("COMPOSTER", Material.COMPOSTER).mode(Flag.Mode.ADVANCED).build(); + public static final Flag FLOWER_POT = new Flag.Builder("FLOWER_POT", Material.FLOWER_POT).mode(Flag.Mode.ADVANCED).build(); + public static final Flag SHULKER_BOX = new Flag.Builder("SHULKER_BOX", Material.SHULKER_BOX).mode(Flag.Mode.ADVANCED).build(); + public static final Flag TRAPPED_CHEST = new Flag.Builder("TRAPPED_CHEST", Material.TRAPPED_CHEST).mode(Flag.Mode.ADVANCED).build(); + // END CONTAINER split public static final Flag DISPENSER = new Flag.Builder("DISPENSER", Material.DISPENSER).mode(Flag.Mode.ADVANCED).build(); public static final Flag DROPPER = new Flag.Builder("DROPPER", Material.DROPPER).mode(Flag.Mode.ADVANCED).build(); public static final Flag HOPPER = new Flag.Builder("HOPPER", Material.HOPPER).mode(Flag.Mode.ADVANCED).build(); @@ -129,6 +137,9 @@ public final class Flags { public static final Flag ITEM_FRAME = new Flag.Builder("ITEM_FRAME", Material.ITEM_FRAME).mode(Flag.Mode.ADVANCED).build(); public static final Flag CAKE = new Flag.Builder("CAKE", Material.CAKE).build(); public static final Flag HIVE = new Flag.Builder("HIVE", Material.HONEY_BOTTLE).type(Type.PROTECTION).build(); + public static final Flag CONTAINER = new Flag.Builder("CONTAINER", Material.CHEST).mode(Flag.Mode.BASIC) + .subflags(BREWING, BARREL, CHEST, COMPOSTER, FLOWER_POT, SHULKER_BOX, TRAPPED_CHEST, FURNACE, JUKEBOX, DISPENSER, DROPPER, HOPPER, ITEM_FRAME, HIVE) + .build(); /** * Prevents players from interacting with the Dragon Egg. @@ -526,6 +537,13 @@ public final class Flags { */ public static final Flag PETS_STAY_AT_HOME = new Flag.Builder("PETS_STAY_AT_HOME", Material.TROPICAL_FISH).listener(new PetTeleportListener()).type(Type.WORLD_SETTING).defaultSetting(true).build(); + /** + * Toggles whether island visitors keep their items if they die on another player's island. + * @since 1.17.0 + * @see VisitorKeepInventoryListener + */ + public static final Flag VISITOR_KEEP_INVENTORY = new Flag.Builder("VISITOR_KEEP_INVENTORY", Material.TOTEM_OF_UNDYING).listener(new VisitorKeepInventoryListener()).type(Type.WORLD_SETTING).defaultSetting(false).build(); + /** * Provides a list of all the Flag instances contained in this class using reflection. * Deprecated Flags are ignored. diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 10578d84e..9dc3d7cee 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -188,6 +188,7 @@ commands: island-uuid: "UUID: [uuid]" owner: "Owner: [owner] ([uuid])" last-login: "Last login: [date]" + last-login-date-time-format: "EEE MMM dd HH:mm:ss zzz yyyy" deaths: "Deaths: [number]" resets-left: "Resets: [number] (Max: [total])" team-members-title: "Team members:" @@ -787,15 +788,45 @@ protection: name: "Cakes" hint: "Cake eating disabled" CONTAINER: - name: "Containers" + name: "All containers" description: |- - &a Toggle interaction with chests, - &a shulker boxes and flower pots, - &a composters and barrels. + &a Toggle interaction with all containers. + &a Includes: Barrel, bee hive, brewing stand, + &a chest, composter, dispenser, dropper, + &a flower pot, furnace, hopper, item frame, + &a jukebox, minecart chest, shulker box, + &a trapped chest. - &7 Other containers are handled - &7 by dedicated flags. + &7 Changing individual settings overrides + &7 this flag. hint: "Container access disabled" + CHEST: + name: "Chests and minecart chests" + description: |- + &a Toggle interaction with chests + &a and chest minecarts. + &a (does not include trapped chests) + hint: "Chest access disabled" + BARREL: + name: "Barrels" + description: "Toggle barrel interaction" + hint: "Barrel access disabled" + COMPOSTER: + name: "Composters" + description: "Toggle composter interaction" + hint: "Composter interaction disabled" + FLOWER_POT: + name: "Flower pots" + description: "Toggle flower pot interaction" + hint: "Flower pot interaction disabled" + SHULKER_BOX: + name: "Shulker boxes" + description: "Toggle shulker box interaction" + hint: "Shulker box access disabled" + TRAPPED_CHEST: + name: "Trapped chests" + description: "Toggle trapped chest interaction" + hint: "Trapped chest access disabled" DISPENSER: name: "Dispensers" description: "Toggle dispenser interaction" @@ -1271,6 +1302,15 @@ protection: &a back to their island using commands &a if they are falling. hint: "&c You cannot do that while falling." + VISITOR_KEEP_INVENTORY: + name: "Visitors keep inventory on death" + description: |- + &a Prevent players from losing their + &a items and experience if they die on + &a an island in which they are a visitor. + &a + &a Island members still lose their items + &a if they die on their own island! WITHER_DAMAGE: name: "Toggle wither damage" description: |- diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a07ee8626..f080ab77f 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -24,6 +24,7 @@ softdepend: - LangUtils - WildStacker - LuckPerms + - HolographicDisplays permissions: bentobox.admin: diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java index c86dae48a..ad3cac559 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java @@ -70,14 +70,14 @@ public class BlockInteractionListenerTest extends AbstractCommonSetup { when(Tag.BEDS.isTagged(Material.WHITE_BED)).thenReturn(true); clickedBlocks.put(Material.BREWING_STAND, Flags.BREWING); clickedBlocks.put(Material.CAULDRON, Flags.BREWING); - clickedBlocks.put(Material.BARREL, Flags.CONTAINER); - clickedBlocks.put(Material.CHEST, Flags.CONTAINER); - clickedBlocks.put(Material.CHEST_MINECART, Flags.CONTAINER); - clickedBlocks.put(Material.TRAPPED_CHEST, Flags.CONTAINER); - clickedBlocks.put(Material.SHULKER_BOX, Flags.CONTAINER); + clickedBlocks.put(Material.BARREL, Flags.BARREL); + clickedBlocks.put(Material.CHEST, Flags.CHEST); + clickedBlocks.put(Material.CHEST_MINECART, Flags.CHEST); + clickedBlocks.put(Material.TRAPPED_CHEST, Flags.TRAPPED_CHEST); + clickedBlocks.put(Material.SHULKER_BOX, Flags.SHULKER_BOX); when(Tag.SHULKER_BOXES.isTagged(Material.SHULKER_BOX)).thenReturn(true); - clickedBlocks.put(Material.FLOWER_POT, Flags.CONTAINER); - clickedBlocks.put(Material.COMPOSTER, Flags.CONTAINER); + clickedBlocks.put(Material.FLOWER_POT, Flags.FLOWER_POT); + clickedBlocks.put(Material.COMPOSTER, Flags.COMPOSTER); clickedBlocks.put(Material.DISPENSER, Flags.DISPENSER); clickedBlocks.put(Material.DROPPER, Flags.DROPPER); clickedBlocks.put(Material.HOPPER, Flags.HOPPER);