From c6efda75a0db1eb8052a580b557d077576a9df78 Mon Sep 17 00:00:00 2001 From: Matt <4009945+MattBDev@users.noreply.github.com> Date: Thu, 4 May 2023 22:01:32 -0400 Subject: [PATCH 1/2] Added upstream OffsetsMask classes --- .../parser/mask/RichOffsetMaskParser.java | 4 +- .../factory/parser/mask/OffsetMaskParser.java | 4 +- .../worldedit/function/mask/OffsetMask.java | 9 +- .../worldedit/function/mask/OffsetMask2D.java | 5 +- .../worldedit/function/mask/OffsetsMask.java | 292 ++++++++++++++++++ .../function/mask/OffsetsMask2D.java | 240 ++++++++++++++ 6 files changed, 546 insertions(+), 8 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask2D.java diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/RichOffsetMaskParser.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/RichOffsetMaskParser.java index f6a21ef6fb..7033bc83dd 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/RichOffsetMaskParser.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/RichOffsetMaskParser.java @@ -6,7 +6,7 @@ import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.function.mask.Mask; -import com.sk89q.worldedit.function.mask.OffsetMask; +import com.sk89q.worldedit.function.mask.OffsetsMask; import com.sk89q.worldedit.math.BlockVector3; import javax.annotation.Nonnull; @@ -43,7 +43,7 @@ protected Mask parseFromInput(@Nonnull String[] arguments, ParserContext context int y = Integer.parseInt(arguments[1]); int z = Integer.parseInt(arguments[2]); Mask submask = worldEdit.getMaskFactory().parseFromInput(arguments[3], context); - return new OffsetMask(submask, BlockVector3.at(x, y, z), context.getMinY(), context.getMaxY()); + return OffsetsMask.single(submask, BlockVector3.at(x, y, z), context.getMinY(), context.getMaxY()); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java index a9c12235c1..5484f3d430 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java @@ -26,7 +26,7 @@ import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.function.mask.ExistingBlockMask; import com.sk89q.worldedit.function.mask.Mask; -import com.sk89q.worldedit.function.mask.OffsetMask; +import com.sk89q.worldedit.function.mask.OffsetsMask; import com.sk89q.worldedit.internal.registry.InputParser; import com.sk89q.worldedit.math.BlockVector3; @@ -69,7 +69,7 @@ public Mask parseFromInput(String input, ParserContext context) throws InputPars submask = new ExistingBlockMask(context.requireExtent()); } //FAWE start - OffsetMask > OffsetsMask - return new OffsetMask(submask, BlockVector3.at(0, firstChar == '>' ? -1 : 1, 0), context.getMinY(), context.getMaxY()); + return OffsetsMask.single(submask, BlockVector3.at(0, firstChar == '>' ? -1 : 1, 0), context.getMinY(), context.getMaxY()); //FAWE end } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetMask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetMask.java index 2c809ada43..a799363384 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetMask.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetMask.java @@ -30,7 +30,10 @@ /** * Checks whether another mask tests true for a position that is offset * a given vector. + * + * @deprecated Use {@link OffsetsMask#single} */ +@Deprecated public class OffsetMask extends AbstractMask { //FAWE start - ignore resultant position outside world height range @@ -71,8 +74,8 @@ public OffsetMask(Mask mask, BlockVector3 offset, int minY, int maxY) { this.offset = offset; this.minY = minY; this.maxY = maxY; - //FAWE end } + //FAWE end /** * Get the mask. @@ -128,7 +131,7 @@ public boolean test(BlockVector3 vector) { public Mask2D toMask2D() { Mask2D childMask = getMask().toMask2D(); if (childMask != null) { - return new OffsetMask2D(childMask, getOffset().toBlockVector2()); + return OffsetsMask2D.single(childMask, getOffset().toBlockVector2()); } else { return null; } @@ -137,7 +140,7 @@ public Mask2D toMask2D() { //FAWE start @Override public Mask copy() { - return new OffsetMask(mask.copy(), offset.toImmutable(), minY, maxY); + return OffsetsMask.single(mask.copy(), offset.toImmutable(), minY, maxY); } //FAWE end diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetMask2D.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetMask2D.java index 3e70c9ddd5..0e09ed0fe3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetMask2D.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetMask2D.java @@ -27,7 +27,10 @@ /** * Checks whether another mask tests true for a position that is offset * a given vector. + * + * @deprecated Use {@link OffsetsMask2D#single} */ +@Deprecated public class OffsetMask2D extends AbstractMask2D { private Mask2D mask; @@ -95,7 +98,7 @@ public boolean test(BlockVector2 vector) { @Override public Mask2D copy2D() { - return new OffsetMask2D(mask.copy2D(), BlockVector2.at(offset.getX(), offset.getZ())); + return OffsetsMask2D.single(mask.copy2D(), BlockVector2.at(offset.getX(), offset.getZ())); } //FAWE end diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask.java new file mode 100644 index 0000000000..7402ddf035 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask.java @@ -0,0 +1,292 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.mask; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.sk89q.worldedit.EditSessionBuilder; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.Direction.Flag; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Checks whether any face of the given offsets to a block match a given mask. + */ +public class OffsetsMask extends AbstractMask { + + private static final ImmutableSet OFFSET_LIST = + Direction.valuesOf(Flag.CARDINAL | Flag.UPRIGHT) + .stream() + .map(Direction::toBlockVector) + .collect(ImmutableSet.toImmutableSet()); + + //FAWE start + /** + * Create an offsets mask for a single offset. + * + * @param mask the mask to use + * @param offset the offset + * @param minY + * @param maxY + * @return the new offsets mask + */ + public static OffsetsMask single(Mask mask, BlockVector3 offset, int minY, int maxY) { + return builder(mask).maxMatches(1).offsets(ImmutableList.of(offset)).minY(minY).maxY(maxY).build(); + } + //FAWE end + + /** + * Create a new builder, using the given mask. + * @param mask the mask to use + * @return the builder + */ + public static Builder builder(Mask mask) { + return new Builder().mask(mask); + } + + /** + * A builder for an {@link OffsetsMask}. + */ + public static final class Builder { + private Mask mask; + private boolean excludeSelf; + private int minMatches = 1; + private int maxMatches = Integer.MAX_VALUE; + private ImmutableSet offsets = OFFSET_LIST; + //FAWE start - ignore resultant position outside world height range + private int minY; + private int maxY; + //FAWE end + + private Builder() { + } + + /** + * Set the mask to test. + * @param mask the mask to test + * @return this builder, for chaining + */ + public Builder mask(Mask mask) { + this.mask = mask; + return this; + } + + /** + * Set whether the mask should fail if the original position matches. Defaults to + * {@code false}. + * + * @param excludeSelf {@code true} to exclude the original position if it matches + * @return this builder, for chaining + */ + public Builder excludeSelf(boolean excludeSelf) { + this.excludeSelf = excludeSelf; + return this; + } + + /** + * Set the minimum amount of matches required. Defaults to {@code 1}. Must be smaller than + * or equal to the {@linkplain #maxMatches(int) max matches} and the {@link #offsets} size, + * and greater than or equal to {@code 0}. + * + * @param minMatches the minimum amount of matches required + * @return this builder, for chaining + */ + public Builder minMatches(int minMatches) { + this.minMatches = minMatches; + return this; + } + + /** + * Set the maximum amount of matches allowed. Defaults to {@link Integer#MAX_VALUE}. Must + * be greater than or equal to {@linkplain #minMatches(int)}. + * + * @param maxMatches the maximum amount of matches allowed + * @return this builder, for chaining + */ + public Builder maxMatches(int maxMatches) { + this.maxMatches = maxMatches; + return this; + } + + /** + * Set the offsets to test. Defaults to all {@linkplain Flag#CARDINAL cardinal} + * and {@linkplain Flag#UPRIGHT upright} directions. + * + * @param offsets the offsets to test + * @return this builder, for chaining + */ + public Builder offsets(Iterable offsets) { + this.offsets = ImmutableSet.copyOf(offsets); + return this; + } + + //FAWE start + public Builder minY(int minY) { + this.minY = minY; + return null; + } + + public Builder maxY(int maxY) { + this.maxY = maxY; + return this; + } + //FAWE end + + /** + * Build an offsets mask. + * + * @return the new mask + */ + public OffsetsMask build() { + return new OffsetsMask(mask, excludeSelf, minMatches, maxMatches, offsets, minY, maxY); + } + } + + //FAWE start - ignore resultant position outside world height range + private final int minY; + private final int maxY; + //FAWE end + + private final Mask mask; + private final boolean excludeSelf; + private final int minMatches; + private final int maxMatches; + private final ImmutableSet offsets; + + //FAWE start - ignore resultant position outside world height range + private OffsetsMask(Mask mask, boolean excludeSelf, int minMatches, int maxMatches, ImmutableSet offsets, int minY, int maxY) { + checkNotNull(mask); + checkNotNull(offsets); + // Validate match args. No need to test maxMatches as it must be >=0 based on the conditions here. + checkArgument(minMatches <= maxMatches, "minMatches must be less than or equal to maxMatches"); + checkArgument(minMatches >= 0, "minMatches must be greater than or equal to 0"); + checkArgument(minMatches <= offsets.size(), "minMatches must be less than or equal to the number of offsets"); + checkArgument(offsets.size() > 0, "offsets must have at least one element"); + this.minY = minY; + this.maxY = maxY; + this.mask = mask; + this.excludeSelf = excludeSelf; + this.minMatches = minMatches; + this.maxMatches = maxMatches; + this.offsets = offsets; + } + //FAWE end + + /** + * Get the mask. + * + * @return the mask + */ + public Mask getMask() { + return mask; + } + + /** + * Get the flag determining if matching the current block should fail the mask. + * + * @return if it should exclude self-matches + */ + public boolean getExcludeSelf() { + return this.excludeSelf; + } + + /** + * Gets the minimum number of matches to pass. + * + * @return the minimum number of matches + */ + public int getMinMatches() { + return this.minMatches; + } + + /** + * Gets the maximum number of matches to pass. + * + * @return the maximum number of matches + */ + public int getMaxMatches() { + return this.maxMatches; + } + + /** + * Get the offsets. + * + * @return the offsets + */ + public ImmutableSet getOffsets() { + return this.offsets; + } + + // + @Override + public boolean test(BlockVector3 vector) { + if (excludeSelf && getMask().test(vector)) { + return false; + } + + int matches = 0; + + //FAWE start - ignore resultant position outside world height range + for (BlockVector3 offset : offsets) { + BlockVector3 testPos = vector.add(offset); + if (testPos.getBlockY() < minY || testPos.getBlockY() > maxY) { + continue; + } + if (getMask().test(testPos)) { + matches++; + if (matches > maxMatches) { + return false; + } + } + } + //FAWE end + + return minMatches <= matches && matches <= maxMatches; + } + + @Nullable + @Override + public Mask2D toMask2D() { + Mask2D childMask = getMask().toMask2D(); + if (childMask != null) { + return OffsetsMask2D.builder(childMask) + .excludeSelf(excludeSelf) + .minMatches(minMatches) + .maxMatches(maxMatches) + .offsets(Iterables.transform(offsets, BlockVector3::toBlockVector2)) + .build(); + } else { + return null; + } + } + + //FAWE start + @Override + public Mask copy() { + return new OffsetsMask(mask.copy(),excludeSelf,minMatches,maxMatches,ImmutableSet.copyOf(this.offsets),minY,maxY); + } + //FAWE end +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask2D.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask2D.java new file mode 100644 index 0000000000..e3a97df7c7 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask2D.java @@ -0,0 +1,240 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.mask; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.Direction.Flag; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Checks whether any face of the given offsets to a block match a given mask. + */ +public class OffsetsMask2D extends AbstractMask2D { + + private static final ImmutableSet OFFSET_LIST = + Direction.valuesOf(Flag.CARDINAL) + .stream() + .map(Direction::toBlockVector) + .map(BlockVector3::toBlockVector2) + .collect(ImmutableSet.toImmutableSet()); + + /** + * Create an offsets mask for a single offset. + * + * @param mask the mask to use + * @param offset the offset + * @return the new offsets mask + */ + public static OffsetsMask2D single(Mask2D mask, BlockVector2 offset) { + return builder(mask).maxMatches(1).offsets(ImmutableList.of(offset)).build(); + } + + /** + * Create a new builder, using the given mask. + * @param mask the mask to use + * @return the builder + */ + public static Builder builder(Mask2D mask) { + return new Builder().mask(mask); + } + + /** + * A builder for an {@link OffsetsMask}. + */ + public static final class Builder { + private Mask2D mask; + private boolean excludeSelf; + private int minMatches = 1; + private int maxMatches = Integer.MAX_VALUE; + private ImmutableSet offsets = OFFSET_LIST; + + private Builder() { + } + + /** + * Set the mask to test. + * @param mask the mask to test + * @return this builder, for chaining + */ + public Builder mask(Mask2D mask) { + this.mask = mask; + return this; + } + + /** + * Set whether the mask should fail if the original position matches. Defaults to + * {@code false}. + * + * @param excludeSelf {@code true} to exclude the original position if it matches + * @return this builder, for chaining + */ + public Builder excludeSelf(boolean excludeSelf) { + this.excludeSelf = excludeSelf; + return this; + } + + /** + * Set the minimum amount of matches required. Defaults to {@code 1}. Must be smaller than + * or equal to the {@linkplain #maxMatches(int) max matches} and the {@link #offsets} size, + * and greater than or equal to {@code 0}. + * + * @param minMatches the minimum amount of matches required + * @return this builder, for chaining + */ + public Builder minMatches(int minMatches) { + this.minMatches = minMatches; + return this; + } + + /** + * Set the maximum amount of matches allowed. Defaults to {@link Integer#MAX_VALUE}. Must + * be greater than or equal to {@linkplain #minMatches(int)}. + * + * @param maxMatches the maximum amount of matches allowed + * @return this builder, for chaining + */ + public Builder maxMatches(int maxMatches) { + this.maxMatches = maxMatches; + return this; + } + + /** + * Set the offsets to test. Defaults to all {@linkplain Flag#CARDINAL cardinal} + * directions. + * + * @param offsets the offsets to test + * @return this builder, for chaining + */ + public Builder offsets(Iterable offsets) { + this.offsets = ImmutableSet.copyOf(offsets); + return this; + } + + /** + * Build an offsets mask. + * + * @return the new mask + */ + public OffsetsMask2D build() { + return new OffsetsMask2D(mask, excludeSelf, minMatches, maxMatches, offsets); + } + } + + private final Mask2D mask; + private final boolean excludeSelf; + private final int minMatches; + private final int maxMatches; + private final ImmutableSet offsets; + + private OffsetsMask2D(Mask2D mask, boolean excludeSelf, int minMatches, int maxMatches, ImmutableSet offsets) { + checkNotNull(mask); + checkNotNull(offsets); + // Validate match args. No need to test maxMatches as it must be >=0 based on the conditions here. + checkArgument(minMatches <= maxMatches, "minMatches must be less than or equal to maxMatches"); + checkArgument(minMatches >= 0, "minMatches must be greater than or equal to 0"); + checkArgument(minMatches <= offsets.size(), "minMatches must be less than or equal to the number of offsets"); + checkArgument(offsets.size() > 0, "offsets must have at least one element"); + + this.mask = mask; + this.excludeSelf = excludeSelf; + this.minMatches = minMatches; + this.maxMatches = maxMatches; + this.offsets = offsets; + } + + /** + * Get the mask. + * + * @return the mask + */ + public Mask2D getMask() { + return mask; + } + + /** + * Get the flag determining if matching the current block should fail the mask. + * + * @return if it should exclude self-matches + */ + public boolean getExcludeSelf() { + return this.excludeSelf; + } + + /** + * Gets the minimum number of matches to pass. + * + * @return the minimum number of matches + */ + public int getMinMatches() { + return this.minMatches; + } + + /** + * Gets the maximum number of matches to pass. + * + * @return the maximum number of matches + */ + public int getMaxMatches() { + return this.maxMatches; + } + + /** + * Get the offsets. + * + * @return the offsets + */ + public ImmutableSet getOffsets() { + return this.offsets; + } + + @Override + public boolean test(BlockVector2 vector) { + if (excludeSelf && mask.test(vector)) { + return false; + } + + int matches = 0; + + for (BlockVector2 offset : offsets) { + if (mask.test(vector.add(offset))) { + matches++; + if (matches > maxMatches) { + return false; + } + } + } + + return minMatches <= matches && matches <= maxMatches; + } + + //FAWE start + @Override + public Mask2D copy2D() { + return new OffsetsMask2D(mask.copy2D(), excludeSelf, minMatches, maxMatches, ImmutableSet.copyOf(offsets)); + } + //FAWE end + +} From e29c1cf29b9ca7db7dd23c53e500d61fc070d327 Mon Sep 17 00:00:00 2001 From: Matt <4009945+MattBDev@users.noreply.github.com> Date: Thu, 4 May 2023 22:14:40 -0400 Subject: [PATCH 2/2] Fixed some missed API/ABI issues --- .../worldedit/function/mask/OffsetsMask.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask.java index 7402ddf035..3d5b414b71 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/OffsetsMask.java @@ -22,7 +22,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import com.sk89q.worldedit.EditSessionBuilder; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.Direction.Flag; @@ -43,6 +44,17 @@ public class OffsetsMask extends AbstractMask { .map(Direction::toBlockVector) .collect(ImmutableSet.toImmutableSet()); + /** + * Create an offsets mask for a single offset. + * + * @param mask the mask to use + * @param offset the offset + * @return the new offsets mask + */ + public static OffsetsMask single(Mask mask, BlockVector3 offset) { + return builder(mask).maxMatches(1).offsets(ImmutableList.of(offset)).build(); + } + //FAWE start /** * Create an offsets mask for a single offset. @@ -58,6 +70,7 @@ public static OffsetsMask single(Mask mask, BlockVector3 offset, int minY, int m } //FAWE end + /** * Create a new builder, using the given mask. * @param mask the mask to use @@ -176,7 +189,14 @@ public OffsetsMask build() { private final int maxMatches; private final ImmutableSet offsets; + //FAWE start - ignore resultant position outside world height range + private OffsetsMask(Mask mask, boolean excludeSelf, int minMatches, int maxMatches, ImmutableSet offsets) { + this(mask, excludeSelf, minMatches, maxMatches, offsets, + WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).versionMinY(), + WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).versionMaxY()); + } + private OffsetsMask(Mask mask, boolean excludeSelf, int minMatches, int maxMatches, ImmutableSet offsets, int minY, int maxY) { checkNotNull(mask); checkNotNull(offsets);