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
This commit is contained in:
Justin 2021-07-06 13:41:23 -07:00 committed by GitHub
parent f88b8d4d6d
commit 0f7866a00b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 213 additions and 20 deletions

View File

@ -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<Flag> {
private final Addon addon;
private final int cooldown;
private final Mode mode;
private final Set<Flag> subflags;
private Flag(Builder builder) {
this.id = builder.id;
@ -147,6 +149,7 @@ public class Flag implements Comparable<Flag> {
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<Flag> {
.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<Flag> {
/**
* 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<Flag> {
/**
* 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<Flag> {
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<Flag> getSubflags() {
return subflags;
}
@Override
public String toString() {
return "Flag [id=" + id + "]";
@ -480,6 +513,9 @@ public class Flag implements Comparable<Flag> {
// Mode
private Mode mode = Mode.EXPERT;
// Subflags
private Set<Flag> 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<Flag> {
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<Flag> {
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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