From 41fb8a0722c18b1668079aa93606d1458a5129fc Mon Sep 17 00:00:00 2001
From: sk89q
Date: Sun, 17 Aug 2014 13:16:45 -0700
Subject: [PATCH] Implement Associables to handle non-player region membership
checks.
---
.../com/sk89q/worldguard/LocalPlayer.java | 18 +-
.../sk89q/worldguard/bukkit/RegionQuery.java | 45 ++-
.../sk89q/worldguard/bukkit/cause/Cause.java | 9 +
.../listener/EventAbstractionListener.java | 51 +++-
.../listener/RegionProtectionListener.java | 257 +++++++++---------
.../util/BlockStateAsBlockFunction.java | 35 +++
.../sk89q/worldguard/domains/Association.java | 31 +++
.../protection/ApplicableRegionSet.java | 77 +++---
.../protection/FlagValueCalculator.java | 73 ++---
.../protection/association/Associables.java | 58 ++++
.../association/ConstantAssociation.java | 38 +++
.../association/RegionAssociable.java | 38 +++
.../association/RegionOverlapAssociation.java | 56 ++++
.../protection/flags/RegionGroup.java | 40 ++-
14 files changed, 590 insertions(+), 236 deletions(-)
create mode 100644 src/main/java/com/sk89q/worldguard/bukkit/util/BlockStateAsBlockFunction.java
create mode 100644 src/main/java/com/sk89q/worldguard/domains/Association.java
create mode 100644 src/main/java/com/sk89q/worldguard/protection/association/Associables.java
create mode 100644 src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java
create mode 100644 src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java
create mode 100644 src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java
diff --git a/src/main/java/com/sk89q/worldguard/LocalPlayer.java b/src/main/java/com/sk89q/worldguard/LocalPlayer.java
index b1964aa9..585acd2f 100644
--- a/src/main/java/com/sk89q/worldguard/LocalPlayer.java
+++ b/src/main/java/com/sk89q/worldguard/LocalPlayer.java
@@ -20,10 +20,13 @@
package com.sk89q.worldguard;
import com.sk89q.worldedit.Vector;
+import com.sk89q.worldguard.domains.Association;
+import com.sk89q.worldguard.protection.association.RegionAssociable;
+import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import java.util.UUID;
-public abstract class LocalPlayer {
+public abstract class LocalPlayer implements RegionAssociable {
/**
* Get this player's name.
@@ -89,7 +92,18 @@ public abstract class LocalPlayer {
* @return Whether this player has {@code perm}
*/
public abstract boolean hasPermission(String perm);
-
+
+ @Override
+ public Association getAssociation(ProtectedRegion region) {
+ if (region.isOwner(this)) {
+ return Association.OWNER;
+ } else if (region.isMember(this)) {
+ return Association.MEMBER;
+ } else {
+ return Association.NON_MEMBER;
+ }
+ }
+
@Override
public boolean equals(Object obj) {
return obj instanceof LocalPlayer && ((LocalPlayer) obj).getName().equals(getName());
diff --git a/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java b/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java
index 5fafb41a..904a1a10 100644
--- a/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java
+++ b/src/main/java/com/sk89q/worldguard/bukkit/RegionQuery.java
@@ -22,6 +22,7 @@
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.GlobalRegionManager;
+import com.sk89q.worldguard.protection.association.RegionAssociable;
import com.sk89q.worldguard.protection.flags.DefaultFlag;
import com.sk89q.worldguard.protection.flags.Flag;
import com.sk89q.worldguard.protection.flags.StateFlag;
@@ -108,26 +109,46 @@ public ApplicableRegionSet getApplicableRegions(Location location) {
* @param player the player
* @param flags zero or more flags
* @return true if permission is granted
- * @see ApplicableRegionSet#testBuild(LocalPlayer, StateFlag...)
+ * @see ApplicableRegionSet#testBuild(RegionAssociable, StateFlag...)
*/
public boolean testBuild(Location location, Player player, StateFlag... flags) {
checkNotNull(location);
checkNotNull(player);
- checkNotNull(flags);
World world = location.getWorld();
- WorldConfiguration worldConfig = config.get(world);
-
- if (!worldConfig.useRegions) {
- return true;
- }
if (player.hasPermission("worldguard.region.bypass." + world.getName())) {
return true;
}
LocalPlayer localPlayer = plugin.wrapPlayer(player);
- return getApplicableRegions(location).testBuild(localPlayer, flags);
+ return testBuild(location, localPlayer, flags);
+ }
+
+ /**
+ * Test whether the given flags evaluate to {@code ALLOW}, implicitly also
+ * considering the {@link DefaultFlag#BUILD} flag.
+ *
+ * This method is equivalent to calling
+ * {@link #testState(Location, Player, StateFlag...)} with
+ * {@code flags} plus the {@code BUILD} flag.
+ *
+ * @param location the location
+ * @param subject the subject
+ * @param flags zero or more flags
+ * @return true if permission is granted
+ * @see ApplicableRegionSet#testBuild(RegionAssociable, StateFlag...)
+ */
+ public boolean testBuild(Location location, RegionAssociable subject, StateFlag... flags) {
+ checkNotNull(location);
+ checkNotNull(subject);
+ checkNotNull(flags);
+
+ World world = location.getWorld();
+ WorldConfiguration worldConfig = config.get(world);
+
+ return !worldConfig.useRegions || getApplicableRegions(location).testBuild(subject, flags);
+
}
/**
@@ -144,7 +165,7 @@ public boolean testBuild(Location location, Player player, StateFlag... flags) {
* @param player an optional player, which would be used to determine the region group to apply
* @param flag the flag
* @return true if the result was {@code ALLOW}
- * @see ApplicableRegionSet#queryValue(LocalPlayer, Flag)
+ * @see ApplicableRegionSet#queryValue(RegionAssociable, Flag)
*/
public boolean testState(Location location, @Nullable Player player, StateFlag... flag) {
return StateFlag.test(queryState(location, player, flag));
@@ -165,7 +186,7 @@ public boolean testState(Location location, @Nullable Player player, StateFlag..
* @param player an optional player, which would be used to determine the region groups that apply
* @param flags a list of flags to check
* @return a state
- * @see ApplicableRegionSet#queryState(LocalPlayer, StateFlag...)
+ * @see ApplicableRegionSet#queryState(RegionAssociable, StateFlag...)
*/
@Nullable
public State queryState(Location location, @Nullable Player player, StateFlag... flags) {
@@ -195,7 +216,7 @@ public State queryState(Location location, @Nullable Player player, StateFlag...
* @param player an optional player, which would be used to determine the region group to apply
* @param flag the flag
* @return a value, which could be {@code null}
- * @see ApplicableRegionSet#queryValue(LocalPlayer, Flag)
+ * @see ApplicableRegionSet#queryValue(RegionAssociable, Flag)
*/
@Nullable
public V queryValue(Location location, @Nullable Player player, Flag flag) {
@@ -218,7 +239,7 @@ public V queryValue(Location location, @Nullable Player player, Flag flag
* @param player an optional player, which would be used to determine the region group to apply
* @param flag the flag
* @return a collection of values
- * @see ApplicableRegionSet#queryAllValues(LocalPlayer, Flag)
+ * @see ApplicableRegionSet#queryAllValues(RegionAssociable, Flag)
*/
public Collection queryAllValues(Location location, @Nullable Player player, Flag flag) {
LocalPlayer localPlayer = player != null ? plugin.wrapPlayer(player) : null;
diff --git a/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java b/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java
index 1047fa2e..c32ed3b5 100644
--- a/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java
+++ b/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java
@@ -71,6 +71,15 @@ public boolean isKnown() {
return !causes.isEmpty();
}
+ @Nullable
+ public Object getRootCause() {
+ if (!causes.isEmpty()) {
+ return causes.get(0);
+ }
+
+ return null;
+ }
+
@Nullable
public Player getPlayerRootCause() {
for (Object object : causes) {
diff --git a/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java b/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java
index 0f1a9134..c259809f 100644
--- a/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java
+++ b/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java
@@ -19,6 +19,7 @@
package com.sk89q.worldguard.bukkit.listener;
+import com.google.common.collect.Lists;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.bukkit.cause.Cause;
import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent;
@@ -28,6 +29,7 @@
import com.sk89q.worldguard.bukkit.event.entity.SpawnEntityEvent;
import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent;
import com.sk89q.worldguard.bukkit.event.inventory.UseItemEvent;
+import com.sk89q.worldguard.bukkit.util.BlockStateAsBlockFunction;
import com.sk89q.worldguard.bukkit.util.Blocks;
import com.sk89q.worldguard.bukkit.util.Events;
import com.sk89q.worldguard.bukkit.util.Materials;
@@ -40,6 +42,7 @@
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.FallingBlock;
+import org.bukkit.entity.Item;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.ThrownPotion;
@@ -84,11 +87,14 @@
import org.bukkit.event.player.PlayerUnleashEntityEvent;
import org.bukkit.event.vehicle.VehicleDamageEvent;
import org.bukkit.event.vehicle.VehicleDestroyEvent;
+import org.bukkit.event.world.StructureGrowEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.Dispenser;
import org.bukkit.material.MaterialData;
+import org.bukkit.util.Vector;
import javax.annotation.Nullable;
+import java.util.List;
import static com.sk89q.worldguard.bukkit.cause.Cause.create;
import static com.sk89q.worldguard.bukkit.util.Materials.isBlockModifiedOnClick;
@@ -138,6 +144,12 @@ public void onBlockBurn(BlockBurnEvent event) {
Events.fireToCancel(event, new UseBlockEvent(event, Cause.unknown(), event.getBlock()));
}
+ @EventHandler
+ public void onStructureGrowEvent(StructureGrowEvent event) {
+ List blockList = Lists.transform(event.getBlocks(), new BlockStateAsBlockFunction());
+ Events.fireBulkEventToCancel(event, new PlaceBlockEvent(event, create(event.getPlayer()), event.getLocation().getWorld(), blockList, Material.AIR));
+ }
+
// TODO: Handle EntityCreatePortalEvent?
@EventHandler
@@ -162,9 +174,20 @@ public void onEntityChangeBlock(EntityChangeBlockEvent event) {
Events.fireToCancel(event, new BreakBlockEvent(event, create(entity), event.getBlock()));
}
} else {
+ boolean wasCancelled = event.isCancelled();
Cause cause = create(entity);
Events.fireToCancel(event, new PlaceBlockEvent(event, cause, event.getBlock().getLocation(), to));
+
+ if (event.isCancelled() && !wasCancelled && entity instanceof FallingBlock) {
+ FallingBlock fallingBlock = (FallingBlock) entity;
+ ItemStack itemStack = new ItemStack(fallingBlock.getMaterial(), 1, fallingBlock.getBlockData());
+ Item item = block.getWorld().dropItem(fallingBlock.getLocation(), itemStack);
+ item.setVelocity(new Vector());
+ if (Events.fireAndTestCancel(new SpawnEntityEvent(event, create(block, entity), item))) {
+ item.remove();
+ }
+ }
}
}
}
@@ -331,23 +354,21 @@ public void onPlayerBucketFill(PlayerBucketFillEvent event) {
@EventHandler
public void onBlockFromTo(BlockFromToEvent event) {
- if (ABSTRACT_FROM_TO_EVENTS) {
- Block from = event.getBlock();
- Block to = event.getToBlock();
+ Block from = event.getBlock();
+ Block to = event.getToBlock();
- // Liquids pass this event when flowing to solid blocks
- if (to.getType().isSolid() && Materials.isLiquid(from.getType())) {
- return;
- }
-
- Cause cause = create(from);
-
- if (from.getType() != Material.AIR) {
- Events.fireToCancel(event, new BreakBlockEvent(event, cause, to));
- }
-
- Events.fireToCancel(event, new PlaceBlockEvent(event, cause, to.getLocation(), from.getType()));
+ // Liquids pass this event when flowing to solid blocks
+ if (to.getType().isSolid() && Materials.isLiquid(from.getType())) {
+ return;
}
+
+ Cause cause = create(from);
+
+ if (from.getType() != Material.AIR) {
+ Events.fireToCancel(event, new BreakBlockEvent(event, cause, to));
+ }
+
+ Events.fireToCancel(event, new PlaceBlockEvent(event, cause, to.getLocation(), from.getType()));
}
//-------------------------------------------------------------------------
diff --git a/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java b/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java
index e62d35a4..6eb1f5bc 100644
--- a/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java
+++ b/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java
@@ -33,12 +33,17 @@
import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent;
import com.sk89q.worldguard.bukkit.util.Entities;
import com.sk89q.worldguard.bukkit.util.Materials;
+import com.sk89q.worldguard.domains.Association;
+import com.sk89q.worldguard.protection.ApplicableRegionSet;
+import com.sk89q.worldguard.protection.association.Associables;
+import com.sk89q.worldguard.protection.association.RegionAssociable;
+import com.sk89q.worldguard.protection.association.RegionOverlapAssociation;
import com.sk89q.worldguard.protection.flags.DefaultFlag;
import com.sk89q.worldguard.protection.flags.StateFlag;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
-import org.bukkit.command.CommandSender;
+import org.bukkit.block.Block;
import org.bukkit.entity.Creeper;
import org.bukkit.entity.EnderDragon;
import org.bukkit.entity.Entity;
@@ -65,11 +70,15 @@ public RegionProtectionListener(WorldGuardPlugin plugin) {
/**
* Tell a sender that s/he cannot do something 'here'.
*
- * @param sender the sender
+ * @param cause the cause
* @param subject the subject that the sender was blocked from touching
*/
- private void tellErrorMessage(CommandSender sender, Object subject) {
- sender.sendMessage(ChatColor.DARK_RED + "Sorry, but you are not allowed to do that here.");
+ private void tellErrorMessage(Cause cause, Object subject) {
+ Object rootCause = cause.getRootCause();
+
+ if (rootCause instanceof Player) {
+ ((Player) rootCause).sendMessage(ChatColor.DARK_RED + "Sorry, but you are not allowed to do that here.");
+ }
}
/**
@@ -98,6 +107,24 @@ public boolean apply(@Nullable Location location) {
};
}
+ private RegionAssociable createRegionAssociable(Cause cause) {
+ Object rootCause = cause.getRootCause();
+
+ if (rootCause instanceof Player) {
+ return getPlugin().wrapPlayer((Player) rootCause);
+ } else if (rootCause instanceof Entity) {
+ RegionQuery query = getPlugin().getRegionContainer().createQuery();
+ ApplicableRegionSet source = query.getApplicableRegions(((Entity) rootCause).getLocation());
+ return new RegionOverlapAssociation(source.getRegions());
+ } else if (rootCause instanceof Block) {
+ RegionQuery query = getPlugin().getRegionContainer().createQuery();
+ ApplicableRegionSet source = query.getApplicableRegions(((Block) rootCause).getLocation());
+ return new RegionOverlapAssociation(source.getRegions());
+ } else {
+ return Associables.constant(Association.NON_MEMBER);
+ }
+ }
+
@EventHandler(ignoreCancelled = true)
public void onPlaceBlock(final PlaceBlockEvent event) {
if (isWhitelisted(event.getCause())) {
@@ -105,36 +132,30 @@ public void onPlaceBlock(final PlaceBlockEvent event) {
}
final Material type = event.getEffectiveMaterial();
+ final RegionQuery query = getPlugin().getRegionContainer().createQuery();
+ final RegionAssociable associable = createRegionAssociable(event.getCause());
- final Player player;
+ event.filterBlocks(new Predicate() {
+ @Override
+ public boolean apply(Location target) {
+ boolean canPlace;
- if ((player = event.getCause().getPlayerRootCause()) != null) {
- final RegionQuery query = getPlugin().getRegionContainer().createQuery();
+ // Flint and steel, fire charge
+ if (type == Material.FIRE) {
+ canPlace = query.testBuild(target, associable, DefaultFlag.LIGHTER);
- event.filterBlocks(new Predicate() {
- @Override
- public boolean apply(Location target) {
- boolean canPlace;
-
- // Flint and steel, fire charge
- if (type == Material.FIRE) {
- canPlace = query.testBuild(target, player, DefaultFlag.LIGHTER);
-
- } else {
- canPlace = query.testBuild(target, player);
- }
-
- if (!canPlace) {
- tellErrorMessage(player, target);
- return false;
- }
-
- return true;
+ } else {
+ canPlace = query.testBuild(target, associable);
}
- });
- } else {
- event.setCancelled(true);
- }
+
+ if (!canPlace) {
+ tellErrorMessage(event.getCause(), target);
+ return false;
+ }
+
+ return true;
+ }
+ });
}
@EventHandler(ignoreCancelled = true)
@@ -143,37 +164,13 @@ public void onBreakBlock(final BreakBlockEvent event) {
return; // Whitelisted cause
}
- final RegionQuery query = getPlugin().getRegionContainer().createQuery();
ConfigurationManager globalConfig = getPlugin().getGlobalStateManager();
WorldConfiguration config = globalConfig.get(event.getWorld());
+ final RegionQuery query = getPlugin().getRegionContainer().createQuery();
- final Player player;
- final Entity entity;
-
- // ====================================================================
- // Player caused
- // ====================================================================
-
- if ((player = event.getCause().getPlayerRootCause()) != null) {
- event.filterBlocks(new Predicate() {
- @Override
- public boolean apply(Location target) {
- boolean canBreak = query.testBuild(target, player);
-
- if (!canBreak) {
- tellErrorMessage(player, target);
- return false;
- }
-
- return true;
- }
- });
-
- // ====================================================================
- // Entity caused
- // ====================================================================
-
- } else if ((entity = event.getCause().getEntityRootCause()) != null) {
+ // TODO: Move this to another event handler
+ Entity entity;
+ if ((entity = event.getCause().getEntityRootCause()) != null) {
// Creeper
if (entity instanceof Creeper) {
event.filterBlocks(createStateTest(query, DefaultFlag.CREEPER_EXPLOSION), config.explosionFlagCancellation);
@@ -187,8 +184,24 @@ public boolean apply(Location target) {
event.filterBlocks(createStateTest(query, DefaultFlag.TNT), config.explosionFlagCancellation);
}
- } else {
- event.setCancelled(true);
+ }
+
+ if (!event.isCancelled()) {
+ final RegionAssociable associable = createRegionAssociable(event.getCause());
+
+ event.filterBlocks(new Predicate() {
+ @Override
+ public boolean apply(Location target) {
+ boolean canBreak = query.testBuild(target, associable);
+
+ if (!canBreak) {
+ tellErrorMessage(event.getCause(), target);
+ return false;
+ }
+
+ return true;
+ }
+ });
}
}
@@ -199,45 +212,39 @@ public void onUseBlock(final UseBlockEvent event) {
}
final Material type = event.getEffectiveMaterial();
+ final RegionQuery query = getPlugin().getRegionContainer().createQuery();
+ final RegionAssociable associable = createRegionAssociable(event.getCause());
- final Player player;
+ event.filterBlocks(new Predicate() {
+ @Override
+ public boolean apply(Location target) {
+ boolean canUse;
- if ((player = event.getCause().getPlayerRootCause()) != null) {
- final RegionQuery query = getPlugin().getRegionContainer().createQuery();
+ // Inventory blocks (CHEST_ACCESS)
+ if (Materials.isInventoryBlock(type)) {
+ canUse = query.testBuild(target, associable, DefaultFlag.USE, DefaultFlag.CHEST_ACCESS);
- event.filterBlocks(new Predicate() {
- @Override
- public boolean apply(Location target) {
- boolean canUse;
+ // Beds (SLEEP)
+ } else if (type == Material.BED) {
+ canUse = query.testBuild(target, associable, DefaultFlag.USE, DefaultFlag.SLEEP);
- // Inventory blocks (CHEST_ACCESS)
- if (Materials.isInventoryBlock(type)) {
- canUse = query.testBuild(target, player, DefaultFlag.USE, DefaultFlag.CHEST_ACCESS);
+ // TNT (TNT)
+ } else if (type == Material.TNT) {
+ canUse = query.testBuild(target, associable, DefaultFlag.TNT);
- // Beds (SLEEP)
- } else if (type == Material.BED) {
- canUse = query.testBuild(target, player, DefaultFlag.USE, DefaultFlag.SLEEP);
-
- // TNT (TNT)
- } else if (type == Material.TNT) {
- canUse = query.testBuild(target, player, DefaultFlag.TNT);
-
- // Everything else
- } else {
- canUse = query.testBuild(target, player, DefaultFlag.USE);
- }
-
- if (!canUse) {
- tellErrorMessage(player, target);
- return false;
- }
-
- return true;
+ // Everything else
+ } else {
+ canUse = query.testBuild(target, associable, DefaultFlag.USE);
}
- });
- } else {
- event.setCancelled(true);
- }
+
+ if (!canUse) {
+ tellErrorMessage(event.getCause(), target);
+ return false;
+ }
+
+ return true;
+ }
+ });
}
@EventHandler(ignoreCancelled = true)
@@ -249,23 +256,19 @@ public void onSpawnEntity(SpawnEntityEvent event) {
Location target = event.getTarget();
EntityType type = event.getEffectiveType();
- Player player;
+ RegionQuery query = getPlugin().getRegionContainer().createQuery();
+ RegionAssociable associable = createRegionAssociable(event.getCause());
- if ((player = event.getCause().getPlayerRootCause()) != null) {
- RegionQuery query = getPlugin().getRegionContainer().createQuery();
- boolean canSpawn;
+ boolean canSpawn;
- if (Entities.isVehicle(type)) {
- canSpawn = query.testBuild(target, player, DefaultFlag.PLACE_VEHICLE);
- } else {
- canSpawn = query.testBuild(target, player);
- }
-
- if (!canSpawn) {
- tellErrorMessage(player, target);
- event.setCancelled(true);
- }
+ if (Entities.isVehicle(type)) {
+ canSpawn = query.testBuild(target, associable, DefaultFlag.PLACE_VEHICLE);
} else {
+ canSpawn = query.testBuild(target, associable);
+ }
+
+ if (!canSpawn) {
+ tellErrorMessage(event.getCause(), target);
event.setCancelled(true);
}
}
@@ -278,24 +281,19 @@ public void onDestroyEntity(DestroyEntityEvent event) {
Location target = event.getTarget();
EntityType type = event.getEntity().getType();
+ RegionAssociable associable = createRegionAssociable(event.getCause());
- Player player;
+ RegionQuery query = getPlugin().getRegionContainer().createQuery();
+ boolean canDestroy;
- if ((player = event.getCause().getPlayerRootCause()) != null) {
- RegionQuery query = getPlugin().getRegionContainer().createQuery();
- boolean canDestroy;
-
- if (Entities.isVehicle(type)) {
- canDestroy = query.testBuild(target, player, DefaultFlag.DESTROY_VEHICLE);
- } else {
- canDestroy = query.testBuild(target, player);
- }
-
- if (!canDestroy) {
- tellErrorMessage(player, target);
- event.setCancelled(true);
- }
+ if (Entities.isVehicle(type)) {
+ canDestroy = query.testBuild(target, associable, DefaultFlag.DESTROY_VEHICLE);
} else {
+ canDestroy = query.testBuild(target, associable);
+ }
+
+ if (!canDestroy) {
+ tellErrorMessage(event.getCause(), target);
event.setCancelled(true);
}
}
@@ -307,18 +305,13 @@ public void onUseEntity(UseEntityEvent event) {
}
Location target = event.getTarget();
+ RegionAssociable associable = createRegionAssociable(event.getCause());
- Player player;
+ RegionQuery query = getPlugin().getRegionContainer().createQuery();
+ boolean canUse = query.testBuild(target, associable, DefaultFlag.USE);
- if ((player = event.getCause().getPlayerRootCause()) != null) {
- RegionQuery query = getPlugin().getRegionContainer().createQuery();
- boolean canUse = query.testBuild(target, player, DefaultFlag.USE);
-
- if (!canUse) {
- tellErrorMessage(player, target);
- event.setCancelled(true);
- }
- } else {
+ if (!canUse) {
+ tellErrorMessage(event.getCause(), target);
event.setCancelled(true);
}
}
diff --git a/src/main/java/com/sk89q/worldguard/bukkit/util/BlockStateAsBlockFunction.java b/src/main/java/com/sk89q/worldguard/bukkit/util/BlockStateAsBlockFunction.java
new file mode 100644
index 00000000..26b42def
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/bukkit/util/BlockStateAsBlockFunction.java
@@ -0,0 +1,35 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.bukkit.util;
+
+import com.google.common.base.Function;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockState;
+
+import javax.annotation.Nullable;
+
+public class BlockStateAsBlockFunction implements Function {
+
+ @Override
+ public Block apply(@Nullable BlockState blockState) {
+ return blockState != null ? blockState.getBlock() : null;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldguard/domains/Association.java b/src/main/java/com/sk89q/worldguard/domains/Association.java
new file mode 100644
index 00000000..d15b60e3
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/domains/Association.java
@@ -0,0 +1,31 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.domains;
+
+/**
+ * Indicates the level of membership.
+ */
+public enum Association {
+
+ OWNER,
+ MEMBER,
+ NON_MEMBER
+
+}
diff --git a/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java b/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java
index 1ec49a7e..f25c0128 100644
--- a/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java
+++ b/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java
@@ -21,6 +21,7 @@
import com.google.common.collect.ObjectArrays;
import com.sk89q.worldguard.LocalPlayer;
+import com.sk89q.worldguard.protection.association.RegionAssociable;
import com.sk89q.worldguard.protection.flags.DefaultFlag;
import com.sk89q.worldguard.protection.flags.Flag;
import com.sk89q.worldguard.protection.flags.RegionGroup;
@@ -34,6 +35,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
+import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -88,11 +90,11 @@ public ApplicableRegionSet(SortedSet applicable, @Nullable Prot
* If there are several relevant flags (i.e. in addition to
* {@code BUILD}, such as {@link DefaultFlag#SLEEP} when the target
* object is a bed), then
- * {@link #testBuild(LocalPlayer, StateFlag...)} should be used.
+ * {@link #testBuild(RegionAssociable, StateFlag...)} should be used.
*
* @param player the player to check
* @return true if permitted
- * @deprecated use {@link #testBuild(LocalPlayer, StateFlag...)}
+ * @deprecated use {@link #testBuild(RegionAssociable, StateFlag...)}
*/
@Deprecated
public boolean canBuild(LocalPlayer player) {
@@ -105,36 +107,36 @@ public boolean canBuild(LocalPlayer player) {
* considering the {@link DefaultFlag#BUILD} flag.
*
* This method is equivalent to calling
- * {@link #testState(LocalPlayer, StateFlag...)} with {@code flags} plus
+ * {@link #testState(RegionAssociable, StateFlag...)} with {@code flags} plus
* the {@code BUILD} flag.
*
- * @param player the player
+ * @param subject the subject
* @param flags zero or more flags
* @return true if permission is granted
- * @see #queryState(LocalPlayer, StateFlag...)
+ * @see #queryState(RegionAssociable, StateFlag...)
*/
- public boolean testBuild(LocalPlayer player, StateFlag... flags) {
- checkNotNull(player);
- return test(flagValueCalculator.queryState(player, ObjectArrays.concat(flags, DefaultFlag.BUILD)));
+ public boolean testBuild(RegionAssociable subject, StateFlag... flags) {
+ checkNotNull(subject);
+ return test(flagValueCalculator.queryState(subject, ObjectArrays.concat(flags, DefaultFlag.BUILD)));
}
/**
* Test whether the (effective) value for a list of state flags equals
* {@code ALLOW}.
*
- * {@code player} can be non-null to satisfy region group requirements,
+ *
{@code subject} can be non-null to satisfy region group requirements,
* otherwise it will be assumed that the caller that is not a member of any
* regions. (Flags on a region can be changed so that they only apply
- * to certain users.) The player argument is required if the
+ * to certain users.) The subject argument is required if the
* {@link DefaultFlag#BUILD} flag is in the list of flags.
*
- * @param player an optional player, which would be used to determine the region groups that apply
+ * @param subject an optional subject, which would be used to determine the region groups that apply
* @param flags a list of flags to check
* @return true if the result was {@code ALLOW}
- * @see #queryState(LocalPlayer, StateFlag...)
+ * @see #queryState(RegionAssociable, StateFlag...)
*/
- public boolean testState(@Nullable LocalPlayer player, StateFlag... flags) {
- return test(flagValueCalculator.queryState(player, flags));
+ public boolean testState(@Nullable RegionAssociable subject, StateFlag... flags) {
+ return test(flagValueCalculator.queryState(subject, flags));
}
/**
@@ -142,19 +144,19 @@ public boolean testState(@Nullable LocalPlayer player, StateFlag... flags) {
* states is observed here; that is, {@code DENY} overrides {@code ALLOW},
* and {@code ALLOW} overrides {@code NONE}. One flag may override another.
*
- * {@code player} can be non-null to satisfy region group requirements,
+ *
{@code subject} can be non-null to satisfy region group requirements,
* otherwise it will be assumed that the caller that is not a member of any
* regions. (Flags on a region can be changed so that they only apply
- * to certain users.) The player argument is required if the
+ * to certain users.) The subject argument is required if the
* {@link DefaultFlag#BUILD} flag is in the list of flags.
*
- * @param player an optional player, which would be used to determine the region groups that apply
+ * @param subject an optional subject, which would be used to determine the region groups that apply
* @param flags a list of flags to check
* @return a state
*/
@Nullable
- public State queryState(@Nullable LocalPlayer player, StateFlag... flags) {
- return flagValueCalculator.queryState(player, flags);
+ public State queryState(@Nullable RegionAssociable subject, StateFlag... flags) {
+ return flagValueCalculator.queryState(subject, flags);
}
/**
@@ -169,19 +171,19 @@ public State queryState(@Nullable LocalPlayer player, StateFlag... flags) {
* type of flag that actually has a strategy for picking a value is the
* {@link StateFlag}.
*
- * {@code player} can be non-null to satisfy region group requirements,
+ *
{@code subject} can be non-null to satisfy region group requirements,
* otherwise it will be assumed that the caller that is not a member of any
* regions. (Flags on a region can be changed so that they only apply
- * to certain users.) The player argument is required if the
+ * to certain users.) The subject argument is required if the
* {@link DefaultFlag#BUILD} flag is the flag being queried.
*
- * @param player an optional player, which would be used to determine the region group to apply
+ * @param subject an optional subject, which would be used to determine the region group to apply
* @param flag the flag
* @return a value, which could be {@code null}
*/
@Nullable
- public V queryValue(@Nullable LocalPlayer player, Flag flag) {
- return flagValueCalculator.queryValue(player, flag);
+ public V queryValue(@Nullable RegionAssociable subject, Flag flag) {
+ return flagValueCalculator.queryValue(subject, flag);
}
/**
@@ -189,18 +191,18 @@ public V queryValue(@Nullable LocalPlayer player, Flag flag) {
* values. It is up to the caller to determine which value, if any,
* from the collection will be used.
*
- * {@code player} can be non-null to satisfy region group requirements,
+ *
{@code subject} can be non-null to satisfy region group requirements,
* otherwise it will be assumed that the caller that is not a member of any
* regions. (Flags on a region can be changed so that they only apply
- * to certain users.) The player argument is required if the
+ * to certain users.) The subject argument is required if the
* {@link DefaultFlag#BUILD} flag is the flag being queried.
*
- * @param player an optional player, which would be used to determine the region group to apply
+ * @param subject an optional subject, which would be used to determine the region group to apply
* @param flag the flag
* @return a collection of values
*/
- public Collection queryAllValues(@Nullable LocalPlayer player, Flag flag) {
- return flagValueCalculator.queryAllValues(player, flag);
+ public Collection queryAllValues(@Nullable RegionAssociable subject, Flag flag) {
+ return flagValueCalculator.queryAllValues(subject, flag);
}
/**
@@ -224,7 +226,7 @@ public boolean canConstruct(LocalPlayer player) {
* @param flag flag to check
* @return whether it is allowed
* @throws IllegalArgumentException if the build flag is given
- * @deprecated use {@link #queryState(LocalPlayer, StateFlag...)} instead
+ * @deprecated use {@link #queryState(RegionAssociable, StateFlag...)} instead
*/
@Deprecated
public boolean allows(StateFlag flag) {
@@ -244,7 +246,7 @@ public boolean allows(StateFlag flag) {
* @param player player (used by some flags)
* @return whether the state is allows for it
* @throws IllegalArgumentException if the build flag is given
- * @deprecated use {@link #queryState(LocalPlayer, StateFlag...)} instead
+ * @deprecated use {@link #queryState(RegionAssociable, StateFlag...)} instead
*/
@Deprecated
public boolean allows(StateFlag flag, @Nullable LocalPlayer player) {
@@ -299,7 +301,7 @@ public boolean isMemberOfAll(LocalPlayer player) {
*
* @param flag the flag to check
* @return value of the flag, which may be null
- * @deprecated Use {@link #queryValue(LocalPlayer, Flag)} instead. There
+ * @deprecated Use {@link #queryValue(RegionAssociable, Flag)} instead. There
* is no difference in functionality.
*/
@Deprecated
@@ -316,7 +318,7 @@ public , V> V getFlag(T flag) {
* @param groupPlayer player to check {@link RegionGroup}s against
* @return value of the flag, which may be null
* @throws IllegalArgumentException if a StateFlag is given
- * @deprecated Use {@link #queryValue(LocalPlayer, Flag)} instead. There
+ * @deprecated Use {@link #queryValue(RegionAssociable, Flag)} instead. There
* is no difference in functionality.
*/
@Deprecated
@@ -334,6 +336,15 @@ public int size() {
return applicable.size();
}
+ /**
+ * Get an immutable set of regions that are included in this set.
+ *
+ * @return a set of regions
+ */
+ public Set getRegions() {
+ return Collections.unmodifiableSet(applicable);
+ }
+
@Override
public Iterator iterator() {
return applicable.iterator();
diff --git a/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java b/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java
index 6b2e1a49..7918c884 100644
--- a/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java
+++ b/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java
@@ -21,11 +21,11 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
-import com.sk89q.worldguard.LocalPlayer;
+import com.sk89q.worldguard.domains.Association;
+import com.sk89q.worldguard.protection.association.RegionAssociable;
import com.sk89q.worldguard.protection.flags.DefaultFlag;
import com.sk89q.worldguard.protection.flags.Flag;
import com.sk89q.worldguard.protection.flags.RegionGroup;
-import com.sk89q.worldguard.protection.flags.RegionGroupFlag;
import com.sk89q.worldguard.protection.flags.StateFlag;
import com.sk89q.worldguard.protection.flags.StateFlag.State;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
@@ -84,9 +84,9 @@ private Iterable getApplicable() {
}
/**
- * Return the membership status of the given player, indicating
+ * Return the membership status of the given subject, indicating
* whether there are no (counted) regions in the list of regions,
- * whether the player is a member of all regions, or whether
+ * whether the subject is a member of all regions, or whether
* the region is not a member of all regions.
*
* A region is "counted" if it doesn't have the
@@ -97,20 +97,20 @@ private Iterable getApplicable() {
* This method is mostly for internal use. It's not particularly
* useful.
*
- * @param player the player
+ * @param subject the subject
* @return the membership result
*/
- public Result getMembership(LocalPlayer player) {
- checkNotNull(player);
+ public Result getMembership(RegionAssociable subject) {
+ checkNotNull(subject);
int minimumPriority = Integer.MIN_VALUE;
boolean foundApplicableRegion = false;
// Say there are two regions in one location: CHILD and PARENT (CHILD
// is a child of PARENT). If there are two overlapping regions in WG, a
- // player has to be a member of /both/ (or flags permit) in order to
+ // subject has to be a member of /both/ (or flags permit) in order to
// build in that location. However, inheritance is supposed
- // to allow building if the player is a member of just CHILD. That
+ // to allow building if the subject is a member of just CHILD. That
// presents a problem.
//
// To rectify this, we keep two sets. When we iterate over the list of
@@ -145,7 +145,7 @@ public Result getMembership(LocalPlayer player) {
}
// If PASSTHROUGH is set, ignore this region
- if (getEffectiveFlag(region, DefaultFlag.PASSTHROUGH, player) == State.ALLOW) {
+ if (getEffectiveFlag(region, DefaultFlag.PASSTHROUGH, subject) == State.ALLOW) {
continue;
}
@@ -153,7 +153,7 @@ public Result getMembership(LocalPlayer player) {
foundApplicableRegion = true;
if (!hasCleared.contains(region)) {
- if (!region.isMember(player)) {
+ if (!RegionGroup.MEMBERS.contains(subject.getAssociation(region))) {
needsClear.add(region);
} else {
// Need to clear all parents
@@ -175,24 +175,24 @@ public Result getMembership(LocalPlayer player) {
* states is observed here; that is, {@code DENY} overrides {@code ALLOW},
* and {@code ALLOW} overrides {@code NONE}.
*
- * A player can be provided that is used to determine whether the value
+ *
A subject can be provided that is used to determine whether the value
* of a flag on a particular region should be used. For example, if a
* flag's region group is set to {@link RegionGroup#MEMBERS} and the given
- * player is not a member, then the region would be skipped when
- * querying that flag. If {@code null} is provided for the player, then
+ * subject is not a member, then the region would be skipped when
+ * querying that flag. If {@code null} is provided for the subject, then
* only flags that use {@link RegionGroup#ALL},
* {@link RegionGroup#NON_MEMBERS}, etc. will apply.
*
- * @param player an optional player, which would be used to determine the region group to apply
+ * @param subject an optional subject, which would be used to determine the region group to apply
* @param flags a list of flags to check
* @return a state
*/
@Nullable
- public State queryState(@Nullable LocalPlayer player, StateFlag... flags) {
+ public State queryState(@Nullable RegionAssociable subject, StateFlag... flags) {
State value = null;
for (StateFlag flag : flags) {
- value = StateFlag.combine(value, queryValue(player, flag));
+ value = StateFlag.combine(value, queryValue(subject, flag));
if (value == State.DENY) {
break;
}
@@ -214,21 +214,21 @@ public State queryState(@Nullable LocalPlayer player, StateFlag... flags) {
* type of flag that can consistently return the same 'best' value is
* {@link StateFlag}.
*
- * A player can be provided that is used to determine whether the value
+ *
A subject can be provided that is used to determine whether the value
* of a flag on a particular region should be used. For example, if a
* flag's region group is set to {@link RegionGroup#MEMBERS} and the given
- * player is not a member, then the region would be skipped when
- * querying that flag. If {@code null} is provided for the player, then
+ * subject is not a member, then the region would be skipped when
+ * querying that flag. If {@code null} is provided for the subject, then
* only flags that use {@link RegionGroup#ALL},
* {@link RegionGroup#NON_MEMBERS}, etc. will apply.
*
- * @param player an optional player, which would be used to determine the region group to apply
+ * @param subject an optional subject, which would be used to determine the region group to apply
* @param flag the flag
* @return a value, which could be {@code null}
*/
@Nullable
- public V queryValue(@Nullable LocalPlayer player, Flag flag) {
- Collection values = queryAllValues(player, flag);
+ public V queryValue(@Nullable RegionAssociable subject, Flag flag) {
+ Collection values = queryAllValues(subject, flag);
return flag.chooseValue(values);
}
@@ -237,20 +237,20 @@ public V queryValue(@Nullable LocalPlayer player, Flag flag) {
* values. It is up to the caller to determine which value, if any,
* from the collection will be used.
*
- * A player can be provided that is used to determine whether the value
+ *
A subject can be provided that is used to determine whether the value
* of a flag on a particular region should be used. For example, if a
* flag's region group is set to {@link RegionGroup#MEMBERS} and the given
- * player is not a member, then the region would be skipped when
- * querying that flag. If {@code null} is provided for the player, then
+ * subject is not a member, then the region would be skipped when
+ * querying that flag. If {@code null} is provided for the subject, then
* only flags that use {@link RegionGroup#ALL},
* {@link RegionGroup#NON_MEMBERS}, etc. will apply.
*
- * @param player an optional player, which would be used to determine the region group to apply
+ * @param subject an optional subject, which would be used to determine the region group to apply
* @param flag the flag
* @return a collection of values
*/
@SuppressWarnings("unchecked")
- public Collection queryAllValues(@Nullable LocalPlayer player, Flag flag) {
+ public Collection queryAllValues(@Nullable RegionAssociable subject, Flag flag) {
checkNotNull(flag);
int minimumPriority = Integer.MIN_VALUE;
@@ -295,7 +295,7 @@ public Collection queryAllValues(@Nullable LocalPlayer player, Flag fl
break;
}
- V value = getEffectiveFlag(region, flag, player);
+ V value = getEffectiveFlag(region, flag, subject);
if (value != null) {
if (!ignoredRegions.contains(region)) {
@@ -314,11 +314,11 @@ public Collection queryAllValues(@Nullable LocalPlayer player, Flag fl
}
if (flag == DefaultFlag.BUILD && consideredValues.isEmpty()) {
- if (player == null) {
- throw new NullPointerException("The BUILD flag is handled in a special fashion and requires a non-null player parameter");
+ if (subject == null) {
+ throw new NullPointerException("The BUILD flag is handled in a special fashion and requires a non-null subject parameter");
}
- switch (getMembership(player)) {
+ switch (getMembership(subject)) {
case FAIL:
return ImmutableList.of();
case SUCCESS:
@@ -359,10 +359,11 @@ public int getPriority(final ProtectedRegion region) {
*
* @param region the region
* @param flag the flag
+ * @param subject an subject object
* @return the value
*/
@SuppressWarnings("unchecked")
- public V getEffectiveFlag(final ProtectedRegion region, Flag flag, @Nullable LocalPlayer player) {
+ public V getEffectiveFlag(final ProtectedRegion region, Flag flag, @Nullable RegionAssociable subject) {
if (region == globalRegion) {
if (flag == DefaultFlag.PASSTHROUGH) {
// Has members/owners -> the global region acts like
@@ -393,7 +394,9 @@ public V getEffectiveFlag(final ProtectedRegion region, Flag flag, @Nulla
group = flag.getRegionGroupFlag().getDefault();
}
- if (!RegionGroupFlag.isMember(region, group, player)) {
+ if (subject == null) {
+ use = group.contains(Association.NON_MEMBER);
+ } else if (!group.contains(subject.getAssociation(region))) {
use = false;
}
}
@@ -448,7 +451,7 @@ private void ignoreValuesOfParents(Map needsClear, Set
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.protection.association;
+
+import com.sk89q.worldguard.domains.Association;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Utility methods to deal with associables.
+ */
+public final class Associables {
+
+ private static final RegionAssociable OWNER_ASSOCIABLE = new ConstantAssociation(Association.OWNER);
+ private static final RegionAssociable MEMBER_ASSOCIABLE = new ConstantAssociation(Association.MEMBER);
+ private static final RegionAssociable NON_MEMBER_ASSOCIABLE = new ConstantAssociation(Association.NON_MEMBER);
+
+ private Associables() {
+ }
+
+ /**
+ * Get an instance that always returns the same association.
+ *
+ * @param association the association
+ * @return the instance
+ */
+ public static RegionAssociable constant(Association association) {
+ checkNotNull(association);
+ switch (association) {
+ case OWNER:
+ return OWNER_ASSOCIABLE;
+ case MEMBER:
+ return MEMBER_ASSOCIABLE;
+ case NON_MEMBER:
+ return NON_MEMBER_ASSOCIABLE;
+ default:
+ return new ConstantAssociation(association);
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java b/src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java
new file mode 100644
index 00000000..95e8d5e9
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java
@@ -0,0 +1,38 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.protection.association;
+
+import com.sk89q.worldguard.domains.Association;
+import com.sk89q.worldguard.protection.regions.ProtectedRegion;
+
+class ConstantAssociation implements RegionAssociable {
+
+ private final Association association;
+
+ ConstantAssociation(Association association) {
+ this.association = association;
+ }
+
+ @Override
+ public Association getAssociation(ProtectedRegion region) {
+ return association;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java b/src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java
new file mode 100644
index 00000000..07e8c3f4
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java
@@ -0,0 +1,38 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.protection.association;
+
+import com.sk89q.worldguard.domains.Association;
+import com.sk89q.worldguard.protection.regions.ProtectedRegion;
+
+/**
+ * An object that can have membership in a region.
+ */
+public interface RegionAssociable {
+
+ /**
+ * Get the most specific association level for the input region.
+ *
+ * @param region the input region
+ * @return the most specific membership level
+ */
+ Association getAssociation(ProtectedRegion region);
+
+}
diff --git a/src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java b/src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java
new file mode 100644
index 00000000..7165c9c2
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java
@@ -0,0 +1,56 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.protection.association;
+
+import com.sk89q.worldguard.domains.Association;
+import com.sk89q.worldguard.protection.regions.ProtectedRegion;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Determines that the association to a region is {@code OWNER} if the input
+ * region is in a set of source regions.
+ */
+public class RegionOverlapAssociation implements RegionAssociable {
+
+ private final Set source;
+
+ /**
+ * Create a new instance.
+ *
+ * @param source set of regions that input regions must be contained within
+ */
+ public RegionOverlapAssociation(Set source) {
+ checkNotNull(source);
+ this.source = source;
+ }
+
+ @Override
+ public Association getAssociation(ProtectedRegion region) {
+ if (source.contains(region)) {
+ return Association.OWNER;
+ } else {
+ return Association.NON_MEMBER;
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java b/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java
index 0d7f6e55..b1f1598f 100644
--- a/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java
+++ b/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java
@@ -19,14 +19,40 @@
package com.sk89q.worldguard.protection.flags;
+import com.google.common.collect.ImmutableSet;
+import com.sk89q.worldguard.domains.Association;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
/**
- * A grouping of region membership types
+ * A grouping of region membership types.
*/
public enum RegionGroup {
- MEMBERS,
- OWNERS,
- NON_MEMBERS,
- NON_OWNERS,
- ALL,
- NONE
+
+ MEMBERS(Association.MEMBER, Association.OWNER),
+ OWNERS(Association.OWNER),
+ NON_MEMBERS(Association.NON_MEMBER),
+ NON_OWNERS(Association.MEMBER, Association.NON_MEMBER),
+ ALL(Association.OWNER, Association.MEMBER, Association.NON_MEMBER),
+ NONE();
+
+ private final Set contained;
+
+ RegionGroup(Association... association) {
+ this.contained = ImmutableSet.copyOf(association);
+ }
+
+ /**
+ * Test whether this group contains the given membership status.
+ *
+ * @param association membership status
+ * @return true if contained
+ */
+ public boolean contains(Association association) {
+ checkNotNull(association);
+ return contained.contains(association);
+ }
+
}