Skip to content

Commit

Permalink
Updated FlagValueCalculator to treat global regions a lowest priority…
Browse files Browse the repository at this point in the history
… region.
  • Loading branch information
sk89q committed Aug 16, 2014
1 parent 4d43ef5 commit 7481acb
Show file tree
Hide file tree
Showing 6 changed files with 526 additions and 749 deletions.
293 changes: 8 additions & 285 deletions src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,18 @@
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.*;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;

import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.sk89q.worldguard.protection.flags.StateFlag.*;
import static com.sk89q.worldguard.protection.flags.StateFlag.test;

/**
* Represents the effective set of flags, owners, and members for a given
Expand All @@ -61,6 +56,7 @@ public class ApplicableRegionSet implements Iterable<ProtectedRegion> {
private final SortedSet<ProtectedRegion> applicable;
@Nullable
private final ProtectedRegion globalRegion;
private final FlagValueCalculator flagValueCalculator;

/**
* Construct the object.
Expand All @@ -84,6 +80,7 @@ public ApplicableRegionSet(SortedSet<ProtectedRegion> applicable, @Nullable Prot
checkNotNull(applicable);
this.applicable = applicable;
this.globalRegion = globalRegion;
this.flagValueCalculator = new FlagValueCalculator(applicable, globalRegion);
}

/**
Expand All @@ -94,7 +91,7 @@ public ApplicableRegionSet(SortedSet<ProtectedRegion> applicable, @Nullable Prot
*/
public boolean canBuild(LocalPlayer player) {
checkNotNull(player);
return test(calculateState(DefaultFlag.BUILD, new RegionMemberTest(player), null));
return test(flagValueCalculator.testPermission(player, DefaultFlag.BUILD));
}

/**
Expand Down Expand Up @@ -123,7 +120,7 @@ public boolean allows(StateFlag flag) {
throw new IllegalArgumentException("Can't use build flag with allows()");
}

return test(calculateState(flag, null, null));
return test(flagValueCalculator.queryState(null, flag));
}

/**
Expand All @@ -140,7 +137,8 @@ public boolean allows(StateFlag flag, @Nullable LocalPlayer player) {
if (flag == DefaultFlag.BUILD) {
throw new IllegalArgumentException("Can't use build flag with allows()");
}
return test(calculateState(flag, null, player));

return test(flagValueCalculator.queryState(player, flag));
}

/**
Expand Down Expand Up @@ -179,206 +177,6 @@ public boolean isMemberOfAll(LocalPlayer player) {
return true;
}

/**
* Calculate the effective value of a flag based on the regions
* in this set, membership, the global region (if set), and the default
* value of a flag {@link StateFlag#getDefault()}.
*
* @param flag the flag to check
* @param membershipTest null to perform a "wilderness check" or a predicate
* returns true if a the subject is a member of the
* region passed
* @param groupPlayer a player to use for the group flag check
* @return the allow/deny state for the flag
*/
private State calculateState(StateFlag flag, @Nullable Predicate<ProtectedRegion> membershipTest, @Nullable LocalPlayer groupPlayer) {
checkNotNull(flag);

// This method works in two modes:
//
// 1) Membership mode (if membershipTest != null):
// a) Regions in this set -> Check membership + Check region flags
// a) No regions -> Use global region + default value
// 1) Flag mode:
// a) Regions in this set -> Use global region + default value
// a) No regions -> Use global region + default value

int minimumPriority = Integer.MIN_VALUE;
boolean regionsThatCountExistHere = false; // We can't do a application.isEmpty() because
// PASSTHROUGH regions have to be skipped
// (in some cases)
State state = null; // Start with NONE

// 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
// build in that location. However, inheritance is supposed
// to allow building if the player is a member of just CHILD. That
// presents a problem.
//
// To rectify this, we keep two sets. When we iterate over the list of
// regions, there are two scenarios that we may encounter:
//
// 1) PARENT first, CHILD later:
// a) When the loop reaches PARENT, PARENT is added to needsClear.
// b) When the loop reaches CHILD, parents of CHILD (which includes
// PARENT) are removed from needsClear.
// c) needsClear is empty again.
//
// 2) CHILD first, PARENT later:
// a) When the loop reaches CHILD, CHILD's parents (i.e. PARENT) are
// added to hasCleared.
// b) When the loop reaches PARENT, since PARENT is already in
// hasCleared, it does not add PARENT to needsClear.
// c) needsClear stays empty.
//
// As long as the process ends with needsClear being empty, then
// we have satisfied all membership requirements.

Set<ProtectedRegion> needsClear = new HashSet<ProtectedRegion>();
Set<ProtectedRegion> hasCleared = new HashSet<ProtectedRegion>();

for (ProtectedRegion region : applicable) {
// Don't consider lower priorities below minimumPriority
// (which starts at Integer.MIN_VALUE). A region that "counts"
// (has the flag set OR has members) will raise minimumPriority
// to its own priority.
if (region.getPriority() < minimumPriority) {
break;
}

// If PASSTHROUGH is set and we are checking to see if a player
// is a member, then skip this region
if (membershipTest != null && getStateFlagIncludingParents(region, DefaultFlag.PASSTHROUGH) == State.ALLOW) {
continue;
}

// If the flag has a group set on to it, skip this region if
// the group does not match our (group) player
if (groupPlayer != null && flag.getRegionGroupFlag() != null) {
RegionGroup group = region.getFlag(flag.getRegionGroupFlag());
if (group == null) {
group = flag.getRegionGroupFlag().getDefault();
}

if (!RegionGroupFlag.isMember(region, group, groupPlayer)) {
continue;
}
}

regionsThatCountExistHere = true;

State v = getStateFlagIncludingParents(region, flag);

// DENY overrides everything
if (v == State.DENY) {
state = State.DENY;
break; // No need to process any more regions

// ALLOW means we don't care about membership
} else if (v == State.ALLOW) {
state = State.ALLOW;
minimumPriority = region.getPriority();

} else {
if (membershipTest != null) {
minimumPriority = region.getPriority();

if (!hasCleared.contains(region)) {
if (!membershipTest.apply(region)) {
needsClear.add(region);
} else {
// Need to clear all parents
clearParents(needsClear, hasCleared, region);
}
}
}
}
}

if (membershipTest != null) {
State fallback;

if (regionsThatCountExistHere) {
fallback = allowOrNone(needsClear.isEmpty());
} else {
fallback = getDefault(flag, membershipTest);
}

return combine(state, fallback);
} else {
return combine(state, getDefault(flag, null));
}
}

@Nullable
private State getDefault(StateFlag flag, @Nullable Predicate<ProtectedRegion> membershipTest) {
boolean allowed = flag.getDefault() == State.ALLOW;

// Handle defaults
if (globalRegion != null) {
State globalState = globalRegion.getFlag(flag);

// The global region has this flag set
if (globalState != null) {
// Build flag is very special
if (membershipTest != null && globalRegion.hasMembersOrOwners()) {
allowed = membershipTest.apply(globalRegion) && (globalState == State.ALLOW);
} else {
allowed = (globalState == State.ALLOW);
}
} else {
// Build flag is very special
if (membershipTest != null && globalRegion.hasMembersOrOwners()) {
allowed = membershipTest.apply(globalRegion);
}
}
}

return allowed ? State.ALLOW : null;
}

/**
* Clear a region's parents for isFlagAllowed().
*
* @param needsClear the regions that should be cleared
* @param hasCleared the regions already cleared
* @param region the region to start from
*/
private void clearParents(Set<ProtectedRegion> needsClear, Set<ProtectedRegion> hasCleared, ProtectedRegion region) {
ProtectedRegion parent = region.getParent();

while (parent != null) {
if (!needsClear.remove(parent)) {
hasCleared.add(parent);
}

parent = parent.getParent();
}
}

/**
* Get a region's state flag, checking parent regions until a value for the
* flag can be found (if one even exists).
*
* @param region the region
* @param flag the flag
* @return the value
*/
private static State getStateFlagIncludingParents(ProtectedRegion region, StateFlag flag) {
while (region != null) {
State value = region.getFlag(flag);

if (value != null) {
return value;
}

region = region.getParent();
}

return null;
}

/**
* Gets the value of a flag. Do not use this for state flags
* (use {@link #allows(StateFlag, LocalPlayer)} for that).
Expand All @@ -402,82 +200,7 @@ public <T extends Flag<V>, V> V getFlag(T flag) {
*/
@Nullable
public <T extends Flag<V>, V> V getFlag(T flag, @Nullable LocalPlayer groupPlayer) {
checkNotNull(flag);

/*
if (flag instanceof StateFlag) {
throw new IllegalArgumentException("Cannot use StateFlag with getFlag()");
}
*/

int lastPriority = 0;
boolean found = false;

Map<ProtectedRegion, V> needsClear = new HashMap<ProtectedRegion, V>();
Set<ProtectedRegion> hasCleared = new HashSet<ProtectedRegion>();

for (ProtectedRegion region : applicable) {
// Ignore lower priority regions
if (found && region.getPriority() < lastPriority) {
break;
}

// Check group permissions
if (groupPlayer != null && flag.getRegionGroupFlag() != null) {
RegionGroup group = region.getFlag(flag.getRegionGroupFlag());

if (group == null) {
group = flag.getRegionGroupFlag().getDefault();
}

if (!RegionGroupFlag.isMember(region, group, groupPlayer)) {
continue;
}
}

//noinspection StatementWithEmptyBody
if (hasCleared.contains(region)) {
// Already cleared, so do nothing
} else if (region.getFlag(flag) != null) {
clearParents(needsClear, hasCleared, region);

needsClear.put(region, region.getFlag(flag));

found = true;
}

lastPriority = region.getPriority();
}

if (!needsClear.isEmpty()) {
return needsClear.values().iterator().next();
} else {
if (globalRegion != null) {
V gFlag = globalRegion.getFlag(flag);
if (gFlag != null) return gFlag;
}
return null;
}
}

/**
* Clear a region's parents for getFlag().
*
* @param needsClear The regions that should be cleared
* @param hasCleared The regions already cleared
* @param region The region to start from
*/
private void clearParents(Map<ProtectedRegion, ?> needsClear,
Set<ProtectedRegion> hasCleared, ProtectedRegion region) {
ProtectedRegion parent = region.getParent();

while (parent != null) {
if (needsClear.remove(parent) == null) {
hasCleared.add(parent);
}

parent = parent.getParent();
}
return flagValueCalculator.queryValue(groupPlayer, flag);
}

/**
Expand Down
Loading

0 comments on commit 7481acb

Please sign in to comment.