From 0f7866a00b84ab8778b75916c53087fb5a3a1181 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 6 Jul 2021 13:41:23 -0700 Subject: [PATCH] Parent/sub-flag support, split up and designate CONTAINER flag as parent flag (#1784) * Split CONTAINER flag into multiple flags CONTAINER split into - CONTAINER (Chest/Minecart Chest) - BARREL (Barrel) - COMPOSTER (Composter) - FLOWER_POT (Flower Pot) - SHULKER_BOX (Shulker Box) - TRAPPED_CHEST (Trapped Chest) Fixes #1777 * Add subflag support * Create container parent flag, chest subflag * Remove extra string from when CHEST was CONTAINER * Fix incorrect flag specified on fired event in IslandToggleClick * Add missing world subflag event firing * Remove extra import --- .../bentobox/bentobox/api/flags/Flag.java | 50 +++++++++++++++++++ .../api/flags/clicklisteners/CycleClick.java | 12 +++++ .../clicklisteners/IslandToggleClick.java | 5 ++ .../clicklisteners/WorldToggleClick.java | 6 +++ .../bentobox/database/objects/Island.java | 50 +++++++++++++++++-- .../protection/BlockInteractionListener.java | 14 ++++-- .../flags/protection/InventoryListener.java | 28 +++++++++++ .../world/bentobox/bentobox/lists/Flags.java | 12 ++++- src/main/resources/locales/en-US.yml | 42 +++++++++++++--- .../BlockInteractionListenerTest.java | 14 +++--- 10 files changed, 213 insertions(+), 20 deletions(-) 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 40927e278..a2273e16f 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -824,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(); } @@ -1132,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/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/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index a9d07fbce..32fce07e7 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -110,7 +110,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 +136,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. diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index ed207d26d..6d9d7cc50 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -788,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" 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);