Add configurable disallowed states for limits, global disallowed blocks (#1312)

This commit is contained in:
dordsor21 2021-10-09 12:02:26 +01:00 committed by GitHub
parent d641e21dfc
commit 6839fa5567
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 575 additions and 48 deletions

View File

@ -1,13 +1,21 @@
package com.fastasyncworldedit.core.configuration;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.limit.PropertyRemap;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Settings extends Config {
@ -192,7 +200,32 @@ public class Settings extends Config {
"List of nbt tags to strip from blocks, e.g. Items",
})
public List<String> STRIP_NBT = new ArrayList<>();
@Comment({
"If the disallowed blocks listed in config-legacy.yml should be disallowed in all edits,",
"not just where blocks patterns are used.",
" - Can prevent blocks being pasted from clipboards, etc.",
" - If fast-placement is disabled, this may cause edits to be slower."
})
public boolean UNIVERSAL_DISALLOWED_BLOCKS = true;
@Comment({
"List of blocks to deny use of. Can be either an entire block type or a block with a specific property value.",
"Where block properties are specified, any blockstate with the property will be disallowed (i.g. all directions",
"of a waterlogged fence). For blocking/remapping of all occurence of a property like waterlogged, see",
"remap-properties below.",
"Example block property blocking:",
" - \"minecraft:conduit[waterlogged=true]\"",
" - \"minecraft:piston[extended=false,facing=west]\"",
" - \"minecraft:wheat[age=7]\""
})
public List<String> DISALLOWED_BLOCKS = Arrays.asList("\"minecraft:wheat\"", "\"minecraft:fire\"", "\"minecraft:redstone_wire\"");
@Comment({
"List of block properties that should be remapped if used in an edit. Entries should take the form",
"\"property_name[value1_old:value1_new,value2_old:value2_new]\". For example:",
" - \"waterlogged[true:false]\"",
" - \"age[7:4,6:4,5:4]\"",
" - \"extended[true:false]\""
})
public List<String> REMAP_PROPERTIES = new ArrayList<>();
}
public static class HISTORY {
@ -399,7 +432,7 @@ public class Settings extends Config {
@Comment({
"[SAFE] Keep entities that are positioned in non-air blocks when editing an area",
"Might cause client-side FPS lagg in some situations"
"Might cause client-side FPS lag in some situations"
})
public boolean KEEP_ENTITIES_IN_BLOCKS = false;
@ -607,6 +640,84 @@ public class Settings extends Config {
limit.STRIP_NBT = Collections.emptySet();
}
}
limit.UNIVERSAL_DISALLOWED_BLOCKS &= newLimit.UNIVERSAL_DISALLOWED_BLOCKS;
if (limit.DISALLOWED_BLOCKS == null) {
limit.DISALLOWED_BLOCKS = newLimit.DISALLOWED_BLOCKS.isEmpty() ? Collections.emptySet() : new HashSet<>(
newLimit.DISALLOWED_BLOCKS);
} else if (limit.DISALLOWED_BLOCKS.isEmpty() || newLimit.DISALLOWED_BLOCKS.isEmpty()) {
limit.DISALLOWED_BLOCKS = Collections.emptySet();
} else {
limit.DISALLOWED_BLOCKS = new HashSet<>(limit.DISALLOWED_BLOCKS);
limit.DISALLOWED_BLOCKS.retainAll(newLimit.DISALLOWED_BLOCKS
.stream()
.map(s -> s.contains(":") ? s.toLowerCase(Locale.ROOT) : ("minecraft:" + s).toLowerCase(Locale.ROOT))
.collect(Collectors.toSet()));
if (limit.DISALLOWED_BLOCKS.isEmpty()) {
limit.DISALLOWED_BLOCKS = Collections.emptySet();
}
}
if (limit.REMAP_PROPERTIES == null) {
limit.REMAP_PROPERTIES = newLimit.REMAP_PROPERTIES.isEmpty() ? Collections.emptySet() :
newLimit.REMAP_PROPERTIES.stream().flatMap(s -> {
String propertyStr = s.substring(0, s.indexOf('['));
List<Property<?>> properties =
BlockTypesCache.getAllProperties().get(propertyStr.toLowerCase(Locale.ROOT));
if (properties == null || properties.isEmpty()) {
return Stream.empty();
}
String[] mappings = s.substring(s.indexOf('[') + 1, s.indexOf(']')).split(",");
Set<PropertyRemap<?>> remaps = new HashSet<>();
for (Property<?> property : properties) {
for (String mapping : mappings) {
try {
String[] fromTo = mapping.split(":");
remaps.add(property.getRemap(
property.getValueFor(fromTo[0]),
property.getValueFor(fromTo[1])
));
} catch (IllegalArgumentException ignored) {
// This property is unlikely to be the one being targeted.
break;
}
}
}
return remaps.stream();
}).collect(Collectors.toSet());
} else if (limit.REMAP_PROPERTIES.isEmpty() || newLimit.REMAP_PROPERTIES.isEmpty()) {
limit.REMAP_PROPERTIES = Collections.emptySet();
} else {
limit.REMAP_PROPERTIES = new HashSet<>(limit.REMAP_PROPERTIES);
limit.REMAP_PROPERTIES.retainAll(newLimit.REMAP_PROPERTIES.stream().flatMap(s -> {
String propertyStr = s.substring(0, s.indexOf('['));
List<Property<?>> properties =
BlockTypesCache.getAllProperties().get(propertyStr.toLowerCase(Locale.ROOT));
if (properties == null || properties.isEmpty()) {
return Stream.empty();
}
String[] mappings = s.substring(s.indexOf('[') + 1, s.indexOf(']')).split(",");
Set<PropertyRemap<?>> remaps = new HashSet<>();
for (Property<?> property : properties) {
for (String mapping : mappings) {
try {
String[] fromTo = mapping.split(":");
remaps.add(property.getRemap(
property.getValueFor(fromTo[0]),
property.getValueFor(fromTo[1])
));
} catch (IllegalArgumentException ignored) {
// This property is unlikely to be the one being targeted.
break;
}
}
}
return remaps.stream();
}).collect(Collectors.toSet()));
if (limit.REMAP_PROPERTIES.isEmpty()) {
limit.REMAP_PROPERTIES = Collections.emptySet();
}
}
}
}
return limit;

View File

@ -0,0 +1,188 @@
package com.fastasyncworldedit.core.extent;
import com.fastasyncworldedit.core.extent.processor.ProcessorScope;
import com.fastasyncworldedit.core.limit.PropertyRemap;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.fastasyncworldedit.core.queue.IChunk;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.IChunkSet;
import com.fastasyncworldedit.core.util.ExtentTraverser;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extension.factory.parser.DefaultBlockParser;
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.block.FuzzyBlockState;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import static com.sk89q.worldedit.world.block.BlockTypesCache.states;
public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IBatchProcessor {
private static final BlockState RESERVED = BlockTypes.__RESERVED__.getDefaultState();
private final Set<PropertyRemap<?>> remaps;
private Set<FuzzyBlockState> blockedStates = null;
private Set<String> blockedBlocks = null;
/**
* Create a new instance.
*
* @param extent the extent
* @param blockedBlocks block types to disallow
* @param remaps property remaps to apply, e.g. waterlogged true -> false
*/
public DisallowedBlocksExtent(Extent extent, Set<String> blockedBlocks, Set<PropertyRemap<?>> remaps) {
super(extent);
this.remaps = remaps;
if (blockedBlocks != null && !blockedBlocks.isEmpty()) {
blockedBlocks = blockedBlocks.stream()
.map(s -> s.contains(":") ? s.toLowerCase(Locale.ROOT) : ("minecraft:" + s).toLowerCase(Locale.ROOT))
.collect(Collectors.toSet());
this.blockedBlocks = new HashSet<>();
for (String block : blockedBlocks) {
if (block.indexOf('[') == -1 || block.indexOf(']') == -1) {
blockedBlocks.add(block);
continue;
}
String[] properties = block.substring(block.indexOf('[') + 1, block.indexOf(']')).split(",");
if (properties.length == 0) {
continue;
}
BlockType type = BlockTypes.get(block.substring(0, block.indexOf('[')));
Map<Property<?>, Object> values =
DefaultBlockParser.parseProperties(type, properties, null, true);
if (values == null || values.isEmpty()) {
continue;
}
if (blockedStates == null) {
blockedStates = new HashSet<>();
}
blockedStates.add(new FuzzyBlockState(type.getDefaultState(), values));
}
}
}
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 location, B block) throws WorldEditException {
if (block instanceof BaseBlock || block instanceof BlockState) {
B newBlock = checkBlock(block);
if (newBlock.getBlockType() == BlockTypes.__RESERVED__) {
return false;
}
return super.setBlock(location, newBlock);
}
return super.setBlock(location, block);
}
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block) throws WorldEditException {
if (block instanceof BaseBlock || block instanceof BlockState) {
B newBlock = checkBlock(block);
if (newBlock.getBlockType() == BlockTypes.__RESERVED__) {
return false;
}
return super.setBlock(x, y, z, newBlock);
}
return super.setBlock(x, y, z, block);
}
private <B extends BlockStateHolder<B>> B checkBlock(B block) {
if (blockedBlocks != null) {
if (blockedBlocks.contains(block.getBlockType().getId())) {
return (B) (block instanceof BlockState ? RESERVED : RESERVED.toBaseBlock()); // set to reserved/empty
}
}
if (blockedStates == null) {
return block;
}
for (FuzzyBlockState state : blockedStates) {
if (state.equalsFuzzy(block)) {
return (B) (block instanceof BlockState ? RESERVED : RESERVED.toBaseBlock());
}
}
if (remaps == null || remaps.isEmpty()) {
return block;
}
for (PropertyRemap<?> remap : remaps) {
block = remap.apply(block);
}
return block;
}
@Override
public IChunkSet processSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
if (blockedStates == null && blockedBlocks == null) { // Shouldn't be possible, but make sure
return set;
}
for (int layer = set.getMinSectionPosition(); layer <= set.getMaxSectionPosition(); layer++) {
if (!set.hasSection(layer)) {
continue;
}
char[] blocks = Objects.requireNonNull(set.loadIfPresent(layer));
it:
for (int i = 0; i < blocks.length; i++) {
char block = blocks[i];
BlockState state = states[block];
if (blockedBlocks != null) {
if (blockedBlocks.contains(state.getBlockType().getId())) {
blocks[i] = 0;
continue;
}
}
if (blockedStates == null) {
continue;
}
for (FuzzyBlockState fuzzy : blockedStates) {
if (fuzzy.equalsFuzzy(state)) {
blocks[i] = 0;
continue it;
}
}
if (remaps == null || remaps.isEmpty()) {
blocks[i] = block;
continue;
}
for (PropertyRemap<?> remap : remaps) {
state = remap.apply(state);
}
blocks[i] = state.getOrdinalChar();
}
}
return set;
}
@Override
public Future<IChunkSet> postProcessSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
return CompletableFuture.completedFuture(set);
}
@Nullable
@Override
public Extent construct(final Extent child) {
if (getExtent() != child) {
new ExtentTraverser<Extent>(this).setNext(child);
}
return this;
}
@Override
public ProcessorScope getScope() {
return ProcessorScope.CHANGING_BLOCKS;
}
}

View File

@ -2,7 +2,7 @@ package com.fastasyncworldedit.core.extent;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.extent.processor.ProcessorScope;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.fastasyncworldedit.core.util.ExtentTraverser;
import com.fastasyncworldedit.core.util.WEManager;

View File

@ -1,6 +1,6 @@
package com.fastasyncworldedit.core.extent;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.queue.IChunk;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.IChunkSet;

View File

@ -4,7 +4,7 @@ import com.fastasyncworldedit.core.extent.filter.block.ExtentFilterBlock;
import com.fastasyncworldedit.core.function.generator.GenBase;
import com.fastasyncworldedit.core.function.generator.Resource;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.queue.Filter;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.MaxChangedBlocksException;

View File

@ -1,6 +1,6 @@
package com.fastasyncworldedit.core.extent;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.queue.IChunk;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.IChunkSet;

View File

@ -5,7 +5,7 @@ import com.fastasyncworldedit.core.extent.processor.ProcessorScope;
import com.fastasyncworldedit.core.function.generator.GenBase;
import com.fastasyncworldedit.core.function.generator.Resource;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.fastasyncworldedit.core.queue.IChunk;
import com.fastasyncworldedit.core.queue.IChunkGet;

View File

@ -1,7 +1,7 @@
package com.fastasyncworldedit.core.extent;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.util.WEManager;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.entity.BaseEntity;

View File

@ -1,6 +1,6 @@
package com.fastasyncworldedit.core.extent;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.queue.IChunk;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.IChunkSet;

View File

@ -1,4 +1,4 @@
package com.fastasyncworldedit.core.object;
package com.fastasyncworldedit.core.limit;
import com.fastasyncworldedit.core.FaweCache;
@ -21,6 +21,9 @@ public class FaweLimit {
public boolean CONFIRM_LARGE = true;
public boolean RESTRICT_HISTORY_TO_REGIONS = true;
public Set<String> STRIP_NBT = null;
public boolean UNIVERSAL_DISALLOWED_BLOCKS = true;
public Set<String> DISALLOWED_BLOCKS = null;
public Set<PropertyRemap<?>> REMAP_PROPERTIES = null;
public static FaweLimit MAX;
@ -112,6 +115,9 @@ public class FaweLimit {
MAX.CONFIRM_LARGE = true;
MAX.RESTRICT_HISTORY_TO_REGIONS = false;
MAX.STRIP_NBT = null;
MAX.UNIVERSAL_DISALLOWED_BLOCKS = false;
MAX.DISALLOWED_BLOCKS = null;
MAX.REMAP_PROPERTIES = null;
}
public boolean MAX_CHANGES() {
@ -234,7 +240,10 @@ public class FaweLimit {
&& SPEED_REDUCTION == 0
&& FAST_PLACEMENT
&& !RESTRICT_HISTORY_TO_REGIONS
&& (STRIP_NBT == null || STRIP_NBT.isEmpty());
&& (STRIP_NBT == null || STRIP_NBT.isEmpty())
&& !UNIVERSAL_DISALLOWED_BLOCKS
&& (DISALLOWED_BLOCKS == null || DISALLOWED_BLOCKS.isEmpty())
&& (REMAP_PROPERTIES == null || REMAP_PROPERTIES.isEmpty());
}
public void set(FaweLimit limit) {
@ -252,6 +261,9 @@ public class FaweLimit {
CONFIRM_LARGE = limit.CONFIRM_LARGE;
RESTRICT_HISTORY_TO_REGIONS = limit.RESTRICT_HISTORY_TO_REGIONS;
STRIP_NBT = limit.STRIP_NBT;
UNIVERSAL_DISALLOWED_BLOCKS = limit.UNIVERSAL_DISALLOWED_BLOCKS;
DISALLOWED_BLOCKS = limit.DISALLOWED_BLOCKS;
REMAP_PROPERTIES = limit.REMAP_PROPERTIES;
}
public FaweLimit copy() {
@ -270,6 +282,9 @@ public class FaweLimit {
limit.CONFIRM_LARGE = CONFIRM_LARGE;
limit.RESTRICT_HISTORY_TO_REGIONS = RESTRICT_HISTORY_TO_REGIONS;
limit.STRIP_NBT = STRIP_NBT;
limit.UNIVERSAL_DISALLOWED_BLOCKS = UNIVERSAL_DISALLOWED_BLOCKS;
limit.DISALLOWED_BLOCKS = DISALLOWED_BLOCKS;
limit.REMAP_PROPERTIES = REMAP_PROPERTIES;
return limit;
}

View File

@ -0,0 +1,57 @@
package com.fastasyncworldedit.core.limit;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
public class PropertyRemap<T> {
private final Property<T> property;
private final T oldValue;
private final T newValue;
/**
* New instance
*
* @param property property to remap values for
* @param oldValue value to remap from
* @param newValue value to remap to
*/
public PropertyRemap(Property<T> property, T oldValue, T newValue) {
this.property = property;
this.oldValue = oldValue;
this.newValue = newValue;
}
/**
* Apply remapping to a state. Will return original state if property is not present.
*
* @param state Block to apply remapping to
* @return new state
*/
public <B extends BlockStateHolder<B>> B apply(B state) {
if (!state.getBlockType().hasProperty(property.getKey())) {
return state;
}
T current = state.getState(property);
if (current == oldValue) {
state = state.with(property.getKey(), newValue);
}
return state;
}
/**
* Apply remapping to a given value if the given block type has the property associated with this remap instance.
*
* @param type block type to check
* @param value value to remap
* @return new value
*/
public T apply(BlockType type, T value) {
if (type.hasProperty(property.getKey())) {
return value == oldValue ? newValue : value;
}
return value;
}
}

View File

@ -23,7 +23,7 @@ import com.fastasyncworldedit.core.history.RollbackOptimizedHistory;
import com.fastasyncworldedit.core.history.changeset.AbstractChangeSet;
import com.fastasyncworldedit.core.history.changeset.BlockBagChangeSet;
import com.fastasyncworldedit.core.history.changeset.NullChangeSet;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent;

View File

@ -50,7 +50,7 @@ import com.fastasyncworldedit.core.math.MutableBlockVector2;
import com.fastasyncworldedit.core.math.MutableBlockVector3;
import com.fastasyncworldedit.core.math.MutableVector3;
import com.fastasyncworldedit.core.math.random.SimplexNoise;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.queue.implementation.preloader.Preloader;
import com.fastasyncworldedit.core.regions.RegionWrapper;
import com.fastasyncworldedit.core.util.ExtentTraverser;

View File

@ -23,6 +23,7 @@ import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.DisallowedBlocksExtent;
import com.fastasyncworldedit.core.extent.FaweRegionExtent;
import com.fastasyncworldedit.core.extent.HistoryExtent;
import com.fastasyncworldedit.core.extent.LimitExtent;
@ -42,7 +43,8 @@ import com.fastasyncworldedit.core.history.RollbackOptimizedHistory;
import com.fastasyncworldedit.core.history.changeset.AbstractChangeSet;
import com.fastasyncworldedit.core.history.changeset.BlockBagChangeSet;
import com.fastasyncworldedit.core.history.changeset.NullChangeSet;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.limit.PropertyRemap;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent;
@ -68,7 +70,9 @@ import org.apache.logging.log4j.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
/**
@ -551,6 +555,23 @@ public final class EditSessionBuilder {
this.extent = new StripNBTExtent(this.extent, this.limit.STRIP_NBT);
}
}
if (this.limit != null && !this.limit.isUnlimited()) {
Set<String> limitBlocks = new HashSet<>();
if ((getActor() == null || getActor().hasPermission("worldedit.anyblock") && this.limit.UNIVERSAL_DISALLOWED_BLOCKS)) {
limitBlocks.addAll(WorldEdit.getInstance().getConfiguration().disallowedBlocks);
}
if (this.limit.DISALLOWED_BLOCKS != null && !this.limit.DISALLOWED_BLOCKS.isEmpty()) {
limitBlocks.addAll(this.limit.DISALLOWED_BLOCKS);
}
Set<PropertyRemap<?>> remaps = this.limit.REMAP_PROPERTIES;
if (!limitBlocks.isEmpty() || (remaps != null && !remaps.isEmpty())) {
if (placeChunks) {
extent.addProcessor(new DisallowedBlocksExtent(this.extent, limitBlocks, remaps));
} else {
this.extent = new DisallowedBlocksExtent(this.extent, limitBlocks, remaps);
}
}
}
this.extent = wrapExtent(this.extent, eventBus, event, EditSession.Stage.BEFORE_HISTORY);
}
return this;

View File

@ -27,7 +27,7 @@ import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
import com.fastasyncworldedit.core.history.DiskStorageHistory;
import com.fastasyncworldedit.core.internal.io.FaweInputStream;
import com.fastasyncworldedit.core.internal.io.FaweOutputStream;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.util.BrushCache;
import com.fastasyncworldedit.core.util.MainUtil;
import com.fastasyncworldedit.core.util.StringMan;

View File

@ -54,7 +54,7 @@ import com.fastasyncworldedit.core.function.mask.IdMask;
import com.fastasyncworldedit.core.function.mask.SingleBlockTypeMask;
import com.fastasyncworldedit.core.math.heightmap.ScalableHeightMap;
import com.fastasyncworldedit.core.math.heightmap.ScalableHeightMap.Shape;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.util.MainUtil;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.StringMan;

View File

@ -29,7 +29,7 @@ import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard;
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.util.ImgurUtility;
import com.fastasyncworldedit.core.util.MainUtil;
import com.fastasyncworldedit.core.util.MaskTraverser;
@ -45,7 +45,6 @@ import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
import com.sk89q.worldedit.command.util.Logging;
import com.sk89q.worldedit.command.util.annotation.Confirm;
import com.sk89q.worldedit.command.util.annotation.Preload;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;

View File

@ -23,7 +23,7 @@ import com.fastasyncworldedit.core.FaweAPI;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.util.MaskTraverser;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.EditSession;

View File

@ -23,6 +23,8 @@ import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.extent.inventory.SlottableBlockBag;
import com.fastasyncworldedit.core.jnbt.JSON2NBT;
import com.fastasyncworldedit.core.jnbt.NBTException;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.limit.PropertyRemap;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.StringMan;
import com.fastasyncworldedit.core.world.block.BlanketBaseBlock;
@ -66,6 +68,8 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@ -171,19 +175,57 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
}
}
private static Map<Property<?>, Object> parseProperties(
//FAWE start - make public
public static Map<Property<?>, Object> parseProperties(
//FAWE end
BlockType type,
String[] stateProperties,
ParserContext context
ParserContext context,
//FAWE start - if null should be returned instead of throwing an error
boolean nullNotError
//FAWE end
) throws NoMatchException {
Map<Property<?>, Object> blockStates = new HashMap<>();
//FAWE start - disallowed states
if (context != null && context.getActor() != null && !context.getActor().getLimit().isUnlimited()) {
for (String input : context.getActor().getLimit().DISALLOWED_BLOCKS) {
if (input.indexOf('[') == -1 && input.indexOf(']') == -1) {
continue;
}
if (!type.getId().equalsIgnoreCase(input.substring(0, input.indexOf('[')))) {
continue;
}
String[] properties = input.substring(input.indexOf('[') + 1, input.indexOf(']')).split(",");
Set<String> blocked = Arrays.stream(properties).filter(s -> {
for (String in : stateProperties) {
if (in.equalsIgnoreCase(s)) {
return true;
}
}
return false;
}).collect(Collectors.toSet());
if (!blocked.isEmpty()) {
throw new DisallowedUsageException(Caption.of(
"fawe.error.limit.disallowed-block",
TextComponent.of(input)
));
}
}
}
//FAWE end
if (stateProperties.length > 0) { // Block data not yet detected
// Parse the block data (optional)
for (String parseableData : stateProperties) {
try {
String[] parts = parseableData.split("=");
if (parts.length != 2) {
//FAWE start - if null should be returned instead of throwing an error
if (nullNotError) {
return null;
}
//FAWE end
throw new InputParseException(
Caption.of(
"worldedit.error.parser.bad-state-format",
@ -195,7 +237,14 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
@SuppressWarnings("unchecked")
Property<Object> propertyKey = (Property<Object>) type.getPropertyMap().get(parts[0]);
if (propertyKey == null) {
if (context.getActor() != null) {
//FAWE start - nullable context
if (context != null && context.getActor() != null) {
//FAWE end
//FAWE start - if null should be returned instead of throwing an error
if (nullNotError) {
return null;
}
//FAWE end
throw new NoMatchException(Caption.of(
"worldedit.error.parser.unknown-property",
TextComponent.of(parts[0]),
@ -207,6 +256,11 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
return Maps.newHashMap();
}
if (blockStates.containsKey(propertyKey)) {
//FAWE start - if null should be returned instead of throwing an error
if (nullNotError) {
return null;
}
//FAWE end
throw new InputParseException(Caption.of(
"worldedit.error.parser.duplicate-property",
TextComponent.of(parts[0])
@ -216,6 +270,11 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
try {
value = propertyKey.getValueFor(parts[1]);
} catch (IllegalArgumentException e) {
//FAWE start - if null should be returned instead of throwing an error
if (nullNotError) {
return null;
}
//FAWE end
throw new NoMatchException(Caption.of(
"worldedit.error.parser.unknown-value",
TextComponent.of(parts[1]),
@ -223,10 +282,30 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
));
}
//FAWE start - blocked states
if (context != null && context.getActor() != null && !context.getActor().getLimit().isUnlimited()) {
if (context.getActor().getLimit().REMAP_PROPERTIES != null
&& !context.getActor().getLimit().REMAP_PROPERTIES.isEmpty()) {
for (PropertyRemap remap : context.getActor().getLimit().REMAP_PROPERTIES) {
Object newValue = remap.apply(type, value);
if (newValue != value) {
value = newValue;
break;
}
}
}
}
//FAWE end
blockStates.put(propertyKey, value);
} catch (NoMatchException e) {
} catch (NoMatchException | DisallowedUsageException e) {
throw e; // Pass-through
} catch (Exception e) {
//FAWE start - if null should be returned instead of throwing an error
if (nullNotError) {
return null;
}
//FAWE end
throw new InputParseException(Caption.of(
"worldedit.error.parser.bad-state-format",
TextComponent.of(parseableData)
@ -387,7 +466,9 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
}
//FAWE end
blockStates.putAll(parseProperties(state.getBlockType(), stateProperties, context));
//FAWE start - Not null if nullNotError false.
blockStates.putAll(parseProperties(state.getBlockType(), stateProperties, context, false));
//FAWE end
if (context.isPreferringWildcard()) {
if (stateString == null || stateString.isEmpty()) {
state = new FuzzyBlockState(state);
@ -428,10 +509,23 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
if (context.isRestricted()) {
Actor actor = context.requireActor();
if (actor != null && !actor.hasPermission("worldedit.anyblock")
&& worldEdit.getConfiguration().disallowedBlocks.contains(blockType.getId())) {
throw new DisallowedUsageException(Caption.of("worldedit.error.disallowed-block", TextComponent.of(input)));
//FAWE start - per-limit disallowed blocks
if (actor != null) {
if (!actor.hasPermission("worldedit.anyblock")
&& worldEdit.getConfiguration().disallowedBlocks.contains(blockType.getId().toLowerCase(Locale.ROOT))) {
throw new DisallowedUsageException(Caption.of("worldedit.error.disallowed-block", TextComponent.of(blockType.getId())));
}
FaweLimit limit = actor.getLimit();
if (!limit.isUnlimited()) {
// No need to account for blocked states/properties as it will simply return false in the equality check
// during contains.
if (limit.DISALLOWED_BLOCKS.contains(blockType.getId().toLowerCase(Locale.ROOT))) {
throw new DisallowedUsageException(Caption.of("fawe.error.limit.disallowed-block",
TextComponent.of(blockType.getId())));
}
}
}
//FAWE end
}
if (nbt != null) {

View File

@ -22,7 +22,7 @@ package com.sk89q.worldedit.extension.platform;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.entity.MapMetadatable;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.util.task.InterruptableCondition;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.internal.cui.CUIEvent;

View File

@ -22,7 +22,7 @@ package com.sk89q.worldedit.regions;
import com.fastasyncworldedit.core.extent.SingleRegionExtent;
import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock;
import com.fastasyncworldedit.core.extent.processor.ProcessorScope;
import com.fastasyncworldedit.core.object.FaweLimit;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.queue.Filter;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.fastasyncworldedit.core.queue.IChunk;

View File

@ -19,10 +19,12 @@
package com.sk89q.worldedit.registry.state;
import com.fastasyncworldedit.core.limit.PropertyRemap;
import com.fastasyncworldedit.core.registry.state.PropertyKey;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Objects;
/**
* Describes a state property of a block.
@ -57,16 +59,45 @@ public interface Property<T> {
T getValueFor(String string) throws IllegalArgumentException;
//FAWE start
/**
* Get the index of the given value in the list of values
*
* @param value value to get index for
* @throws IllegalArgumentException if value not applicable to this property
*/
default int getIndex(T value) {
return getValues().indexOf(value);
}
default int getIndexFor(CharSequence string) throws IllegalArgumentException {
return getIndex(getValueFor(string.toString()));
/**
* Get the index of the given value in the list of values
*
* @param value value to get index for
* @throws IllegalArgumentException if value not applicable to this property
*/
default int getIndexFor(CharSequence value) throws IllegalArgumentException {
return getIndex(getValueFor(value.toString()));
}
/**
* Get the {@link PropertyKey} associated with this property.
*/
default PropertyKey getKey() {
return PropertyKey.getOrCreate(getName());
}
/**
* Get a {@link PropertyRemap} instance for this property with the given remap.
*
* @param from value to remap from
* @param to value to remap to
* @return new {@link PropertyRemap} instance
*/
default PropertyRemap<T> getRemap(Object from, Object to) {
Objects.requireNonNull(from);
Objects.requireNonNull(to);
return new PropertyRemap<T>(this, (T) from, (T) to);
}
//FAWE end
}

View File

@ -42,6 +42,7 @@ import java.util.HashSet;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Simple LocalConfiguration that loads settings using
@ -92,7 +93,10 @@ public class PropertiesConfiguration extends LocalConfiguration {
profile = getBool("profile", profile);
traceUnflushedSessions = getBool("trace-unflushed-sessions", traceUnflushedSessions);
disallowedBlocks = getStringSet("disallowed-blocks", getDefaultDisallowedBlocks());
disallowedBlocks = getStringSet("disallowed-blocks", getDefaultDisallowedBlocks())
.stream()
.map(s -> s.contains(":") ? s.toLowerCase(Locale.ROOT) : ("minecraft:" + s).toLowerCase(Locale.ROOT))
.collect(Collectors.toSet());
defaultChangeLimit = getInt("default-max-changed-blocks", defaultChangeLimit);
maxChangeLimit = getInt("max-changed-blocks", maxChangeLimit);
defaultVerticalHeight = getInt("default-vertical-height", defaultVerticalHeight);

View File

@ -30,6 +30,7 @@ import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.HashSet;
import java.util.Locale;
import java.util.stream.Collectors;
/**
* A less simple implementation of {@link LocalConfiguration}
@ -94,10 +95,10 @@ public class YAMLConfiguration extends LocalConfiguration {
butcherDefaultRadius = Math.max(-1, config.getInt("limits.butcher-radius.default", butcherDefaultRadius));
butcherMaxRadius = Math.max(-1, config.getInt("limits.butcher-radius.maximum", butcherMaxRadius));
disallowedBlocks = new HashSet<>(config.getStringList(
"limits.disallowed-blocks",
Lists.newArrayList(getDefaultDisallowedBlocks())
));
disallowedBlocks = config.getStringList("limits.disallowed-blocks", Lists.newArrayList(getDefaultDisallowedBlocks()))
.stream()
.map(s -> s.contains(":") ? s.toLowerCase(Locale.ROOT) : ("minecraft:" + s).toLowerCase(Locale.ROOT))
.collect(Collectors.toSet());
allowedDataCycleBlocks = new HashSet<>(config.getStringList("limits.allowed-data-cycle-blocks", null));
registerHelp = config.getBoolean("register-help", true);

View File

@ -152,8 +152,8 @@ public class BlockState implements BlockStateHolder<BlockState>, Pattern {
String input = key.toString();
throw new SuggestInputParseException("Does not match a valid block type: " + input, input, () -> Stream.of(
BlockTypesCache.values)
.filter(b -> StringMan.blockStateMatches(input, b.getId()))
.map(BlockType::getId)
.filter(id -> StringMan.blockStateMatches(input, id))
.sorted(StringMan.blockStateComparator(input))
.collect(Collectors.toList())
);
@ -163,7 +163,7 @@ public class BlockState implements BlockStateHolder<BlockState>, Pattern {
return type.getDefaultState();
}
List<? extends Property> propList = type.getProperties();
List<? extends Property<?>> propList = type.getProperties();
if (state.charAt(state.length() - 1) != ']') {
state = state + "]";
@ -172,7 +172,7 @@ public class BlockState implements BlockStateHolder<BlockState>, Pattern {
charSequence.setString(state);
if (propList.size() == 1) {
AbstractProperty property = (AbstractProperty) propList.get(0);
AbstractProperty<?> property = (AbstractProperty<?>) propList.get(0);
String name = property.getName();
charSequence.setSubstring(propStrStart + name.length() + 2, state.length() - 1);
@ -188,7 +188,7 @@ public class BlockState implements BlockStateHolder<BlockState>, Pattern {
stateId = type.getDefaultState().getInternalId();
}
int length = state.length();
AbstractProperty property = null;
AbstractProperty<?> property = null;
int last = propStrStart + 1;
for (int i = last; i < length; i++) {
@ -200,7 +200,7 @@ public class BlockState implements BlockStateHolder<BlockState>, Pattern {
if (property != null) {
int index = property.getIndexFor(charSequence);
if (index == -1) {
throw SuggestInputParseException.of(charSequence.toString(), property.getValues());
throw SuggestInputParseException.of(charSequence.toString(), (List<Object>) property.getValues());
}
stateId = property.modifyIndex(stateId, index);
} else {
@ -288,7 +288,7 @@ public class BlockState implements BlockStateHolder<BlockState>, Pattern {
public <V> BlockState with(final Property<V> property, final V value) {
try {
BlockType type = getBlockType();
int newState = ((AbstractProperty) property).modify(this.getInternalId(), value);
int newState = ((AbstractProperty<V>) property).modify(this.getInternalId(), value);
return newState != this.getInternalId() ? type.withStateId(newState) : this;
} catch (ClassCastException e) {
throw new IllegalArgumentException("Property not found: " + property);
@ -298,7 +298,7 @@ public class BlockState implements BlockStateHolder<BlockState>, Pattern {
@Override
public <V> V getState(final Property<V> property) {
try {
AbstractProperty ap = (AbstractProperty) property;
AbstractProperty<V> ap = (AbstractProperty<V>) property;
return (V) ap.getValue(this.getInternalId());
} catch (ClassCastException e) {
throw new IllegalArgumentException("Property not found: " + property);
@ -309,7 +309,11 @@ public class BlockState implements BlockStateHolder<BlockState>, Pattern {
public <V> BlockState with(final PropertyKey property, final V value) {
try {
BlockType type = getBlockType();
int newState = ((AbstractProperty) type.getProperty(property)).modify(this.getInternalId(), value);
AbstractProperty<V> abstractProperty = ((AbstractProperty<V>) type.getProperty(property));
if (abstractProperty == null) {
return this;
}
int newState = abstractProperty.modify(this.getInternalId(), value);
return newState != this.getInternalId() ? type.withStateId(newState) : this;
} catch (ClassCastException e) {
throw new IllegalArgumentException("Property not found: " + property);
@ -340,7 +344,7 @@ public class BlockState implements BlockStateHolder<BlockState>, Pattern {
//FAWE end
BlockType type = this.getBlockType();
// Lazily initialize the map
Map<? extends Property, Object> map = Maps.asMap(type.getPropertiesSet(), (Function<Property, Object>) this::getState);
Map<? extends Property<?>, Object> map = Maps.asMap(type.getPropertiesSet(), (Function<Property<?>, Object>) this::getState);
//noinspection RedundantCast - This is required for compilation, etc.
return Collections.unmodifiableMap((Map<Property<?>, Object>) map);
//FAWE end

View File

@ -52,8 +52,8 @@ public class FuzzyBlockState extends BlockState {
}
//FAWE end
private FuzzyBlockState(BlockState state, Map<Property<?>, Object> values) {
//FAWE start - use internal ids
//FAWE start - use internal ids, public constructor
public FuzzyBlockState(BlockState state, Map<Property<?>, Object> values) {
super(state.getBlockType(), state.getInternalId(), state.getOrdinal());
if (values == null || values.isEmpty()) {
props = Collections.emptyMap();

View File

@ -148,6 +148,8 @@
"fawe.error.clipboard.invalid.info": "File: {0} (len: {1})",
"fawe.error.clipboard.load.failure": "Could not load clipboard. Possible that the clipboard is still being written to from another server?!",
"fawe.error.clipboard.on.disk.version.mismatch": "Clipboard version mismatch. Please delete your clipboards folder and restart the server.",
"fawe.error.limit.disallowed-block": "Your limit disallows use of block '{0}'",
"fawe.error.limit.disallowed-property": "Your limit disallows use of property '{0}'",
"fawe.cancel.count": "Cancelled {0} edits.",
"fawe.cancel.reason.confirm": "Use //confirm to execute {0}",