From 5800c0bc966492a653b513d83742d8cebcd76133 Mon Sep 17 00:00:00 2001 From: Hannes Greule Date: Wed, 7 Oct 2020 15:32:05 +0200 Subject: [PATCH 1/7] Fix copypaste brush (#688) --- .../com/boydti/fawe/object/changeset/AbstractChangeSet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/changeset/AbstractChangeSet.java b/worldedit-core/src/main/java/com/boydti/fawe/object/changeset/AbstractChangeSet.java index ee9b3b7c6..88d9a923d 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/changeset/AbstractChangeSet.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/changeset/AbstractChangeSet.java @@ -303,8 +303,8 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { MainUtil.setPosition(nbt, x, y, z); addTileCreate(nbt); } - int combinedFrom = from.getInternalId(); - int combinedTo = to.getInternalId(); + int combinedFrom = from.getOrdinal(); + int combinedTo = to.getOrdinal(); add(x, y, z, combinedFrom, combinedTo); } catch (Exception e) { From def986710f99fe426f9a6fef673c5707c49f6c8d Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Wed, 7 Oct 2020 15:52:48 +0100 Subject: [PATCH 2/7] Remove deep synchronisation that is a very plausible cause of circular locking. This may be a fix for #685 --- .../com/boydti/fawe/beta/implementation/blocks/CharBlocks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java index 834fca3ae..ffd2b4466 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java @@ -17,7 +17,7 @@ public abstract class CharBlocks implements IBlocks { }; public static final Section EMPTY = new Section() { @Override - public synchronized final char[] get(CharBlocks blocks, int layer) { + public final char[] get(CharBlocks blocks, int layer) { char[] arr = blocks.blocks[layer]; if (arr == null) { arr = blocks.blocks[layer] = blocks.update(layer, null); From a64a1ab09e328ac9df7d8852f32ef59220483759 Mon Sep 17 00:00:00 2001 From: weaondara Date: Wed, 7 Oct 2020 21:11:57 +0200 Subject: [PATCH 3/7] Fix schem loading of sponge schems with dataversion == 1 (#690) * fix sponge schem loading with schem version 1 --- .../extent/clipboard/io/FastSchematicReader.java | 11 ++++++++++- .../extent/clipboard/io/SchematicReader.java | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicReader.java index 6433ce318..c0c413689 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicReader.java @@ -32,10 +32,13 @@ import com.sk89q.jnbt.IntTag; import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.DataFixer; @@ -99,6 +102,7 @@ public class FastSchematicReader extends NBTSchematicReader { public FastSchematicReader(NBTInputStream inputStream) { checkNotNull(inputStream); this.inputStream = inputStream; + this.fixer = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataFixer(); } private String fix(String palettePart) { @@ -133,7 +137,12 @@ public class FastSchematicReader extends NBTSchematicReader { StreamDelegate root = new StreamDelegate(); StreamDelegate schematic = root.add("Schematic"); schematic.add("DataVersion").withInt((i, v) -> dataVersion = v); - schematic.add("Version").withInt((i, v) -> version = v); + schematic.add("Version").withInt((i, v) -> { + version = v; + if (v == 1 && dataVersion == -1) { // DataVersion might not be present, assume 1.13.2 + dataVersion = Constants.DATA_VERSION_MC_1_13_2; + } + }); schematic.add("Width").withInt((i, v) -> width = v); schematic.add("Height").withInt((i, v) -> height = v); schematic.add("Length").withInt((i, v) -> length = v); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java index 44ac3c8bf..14f363009 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java @@ -33,6 +33,8 @@ import com.sk89q.jnbt.NBTInputStream; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.legacycompat.BannerBlockCompatibilityHandler; +import com.sk89q.worldedit.extent.clipboard.io.legacycompat.BedBlockCompatibilityHandler; import com.sk89q.worldedit.extent.clipboard.io.legacycompat.EntityNBTCompatibilityHandler; import com.sk89q.worldedit.extent.clipboard.io.legacycompat.FlowerPotCompatibilityHandler; import com.sk89q.worldedit.extent.clipboard.io.legacycompat.NBTCompatibilityHandler; @@ -86,7 +88,9 @@ public class SchematicReader implements ClipboardReader { new SignCompatibilityHandler(), new FlowerPotCompatibilityHandler(), new NoteBlockCompatibilityHandler(), - new SkullBlockCompatibilityHandler() + new SkullBlockCompatibilityHandler(), + new BannerBlockCompatibilityHandler(), + new BedBlockCompatibilityHandler() }; private static final EntityNBTCompatibilityHandler[] ENTITY_COMPATIBILITY_HANDLERS = { new Pre13HangingCompatibilityHandler() From 2c27b9c02aad4cebfc78b4f39c1354f712bb4cce Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Thu, 8 Oct 2020 10:53:57 +0100 Subject: [PATCH 4/7] Correctly queue edits Synchronising on the LocalSession ends up being dangerous as it's a craftbukkit thread, leading to blocking issues if something goes wrong in an edit, made worse by the fact craftbukkit threads like to interfere with each other sometimes, and also cause OOMs and hanging when there are too many of them. --- .../worldedit/extension/platform/AbstractPlayerActor.java | 4 ++-- .../com/sk89q/worldedit/extension/platform/Actor.java | 8 ++++---- .../extension/platform/PlatformCommandManager.java | 5 ++--- .../worldedit/extension/platform/PlatformManager.java | 4 ++-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java index d180ef31a..b61589c33 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java @@ -79,8 +79,8 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable { private final Map meta; // Queue for async tasks - private AtomicInteger runningCount = new AtomicInteger(); - private AsyncNotifyQueue asyncNotifyQueue = new AsyncNotifyQueue( + private final AtomicInteger runningCount = new AtomicInteger(); + private final AsyncNotifyQueue asyncNotifyQueue = new AsyncNotifyQueue( (thread, throwable) -> { while (throwable.getCause() != null) { throwable = throwable.getCause(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java index 7761aaf9f..5a222f0cf 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java @@ -189,11 +189,11 @@ public interface Actor extends Identifiable, SessionOwner, Subject, MapMetadatab */ default boolean confirm() { InterruptableCondition confirm = deleteMeta("cmdConfirm"); - if (confirm != null) { - confirm.signal();; - return true; + if (confirm == null) { + return false; } - return false; + queueAction(confirm::signal); + return true; } /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java index 50ec0c47c..3a66dc276 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java @@ -649,13 +649,12 @@ public final class PlatformCommandManager { } else { actor.decline(); } - LocalSession session = worldEdit.getSessionManager().get(actor); - synchronized (session) { + actor.runAction(() -> { SessionKey key = actor.getSessionKey(); if (key.isActive()) { PlatformCommandManager.this.handleCommandOnCurrentThread(event); } - } + }, false, true); }, Fawe.isMainThread()); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java index 762c462d4..62910011c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java @@ -437,7 +437,7 @@ public class PlatformManager { Tool tool = session.getTool(player); if (tool instanceof DoubleActionTraceTool && tool.canUse(player)) { player.runAsyncIfFree(() -> reset((DoubleActionTraceTool) tool).actSecondary(queryCapability(Capability.WORLD_EDITING), - getConfiguration(), player, session)); + getConfiguration(), player, session)); event.setCancelled(true); return; } @@ -450,7 +450,7 @@ public class PlatformManager { if (tool instanceof TraceTool && tool.canUse(player)) { //todo this needs to be fixed so the event is canceled after actPrimary is used and returns true player.runAction(() -> reset((TraceTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), - getConfiguration(), player, session), false, true); + getConfiguration(), player, session), false, true); event.setCancelled(true); return; } From 116c5d17c5bb68d51b99c221ed05aecfe8aa403f Mon Sep 17 00:00:00 2001 From: weaondara Date: Thu, 8 Oct 2020 17:38:31 +0200 Subject: [PATCH 5/7] Fix //jumpto (#689) * fix jumpto command * imports * Formatting Co-authored-by: Aurora Co-authored-by: Aurora <21148213+aurorasmiles@users.noreply.github.com> Co-authored-by: dordsor21 --- .../command/argument/LocationConverter.java | 88 +++++++++++++++++++ .../command/argument/WorldConverter.java | 6 +- .../platform/PlatformCommandManager.java | 2 + 3 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/LocationConverter.java diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/LocationConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/LocationConverter.java new file mode 100644 index 000000000..5cf84a152 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/LocationConverter.java @@ -0,0 +1,88 @@ +/* + * 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.command.argument; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.world.World; +import org.enginehub.piston.CommandManager; +import org.enginehub.piston.converter.ArgumentConverter; +import org.enginehub.piston.converter.ConversionResult; +import org.enginehub.piston.converter.FailedConversion; +import org.enginehub.piston.converter.SuccessfulConversion; +import org.enginehub.piston.inject.InjectedValueAccess; +import org.enginehub.piston.inject.Key; + +import java.util.Collections; +import java.util.List; + +public class LocationConverter implements ArgumentConverter { + + public static void register(CommandManager commandManager) { + commandManager.registerConverter(Key.of(Location.class), LOCATION_CONVERTER); + } + + public static final LocationConverter LOCATION_CONVERTER = new LocationConverter(); + + private final WorldConverter worldConverter = WorldConverter.WORLD_CONVERTER; + private final VectorConverter vectorConverter = VectorConverter.BLOCK_VECTOR_3_CONVERTER; + private final Component desc = TextComponent.of("any world, x, y, and z"); + + private LocationConverter() { + } + + @Override + public Component describeAcceptableArguments() { + return desc; + } + + @Override + public List getSuggestions(String input, InjectedValueAccess context) { + if (input.contains(",")) { + return Collections.emptyList(); + } + return worldConverter.getSuggestions(input, context); + } + + @Override + public ConversionResult convert(String s, InjectedValueAccess injectedValueAccess) { + String[] split4 = s.split(",", 4); + if (split4.length != 4) { + return FailedConversion.from(new IllegalArgumentException( + "Must have exactly 1 world and 3 vector components")); + } + + String[] split2 = s.split(",", 2); + + ConversionResult world = worldConverter.convert(split2[0], injectedValueAccess); + if (!world.isSuccessful()) { + return (FailedConversion) world; + } + ConversionResult vector = vectorConverter.convert(split2[1], injectedValueAccess); + if (!vector.isSuccessful()) { + return (FailedConversion) vector; + } + + Location location = new Location(world.get().iterator().next(), vector.get().iterator().next().toVector3()); + return SuccessfulConversion.fromSingle(location); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/WorldConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/WorldConverter.java index af6ce8b41..d9243cba4 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/WorldConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/WorldConverter.java @@ -39,10 +39,10 @@ import java.util.stream.Stream; public class WorldConverter implements ArgumentConverter { public static void register(CommandManager commandManager) { - commandManager.registerConverter(Key.of(World.class), - new WorldConverter() - ); + commandManager.registerConverter(Key.of(World.class), WORLD_CONVERTER); } + + public static final WorldConverter WORLD_CONVERTER = new WorldConverter(); private final TextComponent choices; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java index 3a66dc276..81df98434 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java @@ -90,6 +90,7 @@ import com.sk89q.worldedit.command.argument.EntityRemoverConverter; import com.sk89q.worldedit.command.argument.EnumConverter; import com.sk89q.worldedit.command.argument.ExpressionConverter; import com.sk89q.worldedit.command.argument.FactoryConverter; +import com.sk89q.worldedit.command.argument.LocationConverter; import com.sk89q.worldedit.command.argument.RegionFactoryConverter; import com.sk89q.worldedit.command.argument.RegistryConverter; import com.sk89q.worldedit.command.argument.SideEffectConverter; @@ -256,6 +257,7 @@ public final class PlatformCommandManager { EntityRemoverConverter.register(commandManager); RegionFactoryConverter.register(commandManager); WorldConverter.register(commandManager); + LocationConverter.register(commandManager); ExpressionConverter.register(commandManager); SideEffectConverter.register(commandManager); From 440774921972cd5585e0309d152265fa3dc265f1 Mon Sep 17 00:00:00 2001 From: Hannes Greule Date: Thu, 8 Oct 2020 17:39:15 +0200 Subject: [PATCH 6/7] Reimplement l2d, l3d, existing and buffer pattern (#684) * Reimplement l2d, l3d, existing and buffer pattern Also smaller translation improvements and cleanup * Allow aliases in RichParser --- .../object/pattern/Linear2DBlockPattern.java | 10 +- .../object/pattern/Linear3DBlockPattern.java | 14 +- .../command/util/SuggestionHelper.java | 55 ++++++++ .../extension/factory/PatternFactory.java | 8 ++ .../extension/factory/parser/RichParser.java | 123 ++++++++---------- .../parser/mask/AdjacentMaskParser.java | 3 +- .../factory/parser/mask/AngleMaskParser.java | 10 +- .../parser/mask/ExtremaMaskParser.java | 10 +- .../parser/mask/ROCAngleMaskParser.java | 10 +- .../factory/parser/mask/RadiusMaskParser.java | 3 +- .../parser/mask/SimplexMaskParser.java | 3 +- .../parser/pattern/BufferedPatternParser.java | 43 ++++++ .../parser/pattern/ExistingPatternParser.java | 29 +++++ .../parser/pattern/Linear2DPatternParser.java | 71 ++++++++++ .../parser/pattern/Linear3DPatternParser.java | 77 +++++++++++ .../parser/pattern/NoisePatternParser.java | 13 +- .../src/main/resources/lang/strings.json | 2 + 17 files changed, 395 insertions(+), 89 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BufferedPatternParser.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/ExistingPatternParser.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/Linear2DPatternParser.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/Linear3DPatternParser.java diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/pattern/Linear2DBlockPattern.java b/worldedit-core/src/main/java/com/boydti/fawe/object/pattern/Linear2DBlockPattern.java index 5442510f9..0bed3a376 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/pattern/Linear2DBlockPattern.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/pattern/Linear2DBlockPattern.java @@ -10,14 +10,18 @@ import com.sk89q.worldedit.world.block.BaseBlock; public class Linear2DBlockPattern extends AbstractPattern { private final Pattern[] patternsArray; + private final int xScale; + private final int zScale; - public Linear2DBlockPattern(Pattern[] patterns) { + public Linear2DBlockPattern(Pattern[] patterns, int xScale, int zScale) { this.patternsArray = patterns; + this.xScale = xScale; + this.zScale = zScale; } @Override public BaseBlock apply(BlockVector3 position) { - int index = (position.getBlockX() + position.getBlockZ()) % patternsArray.length; + int index = (position.getBlockX() / this.xScale + position.getBlockZ() / this.zScale) % patternsArray.length; if (index < 0) { index += patternsArray.length; } @@ -26,7 +30,7 @@ public class Linear2DBlockPattern extends AbstractPattern { @Override public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { - int index = (get.getBlockX() + get.getBlockZ()) % patternsArray.length; + int index = (get.getBlockX() / this.xScale + get.getBlockZ() / this.zScale) % patternsArray.length; if (index < 0) { index += patternsArray.length; } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/pattern/Linear3DBlockPattern.java b/worldedit-core/src/main/java/com/boydti/fawe/object/pattern/Linear3DBlockPattern.java index cbf7d0002..9f70debe9 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/pattern/Linear3DBlockPattern.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/pattern/Linear3DBlockPattern.java @@ -10,14 +10,21 @@ import com.sk89q.worldedit.world.block.BaseBlock; public class Linear3DBlockPattern extends AbstractPattern { private final Pattern[] patternsArray; + private final int xScale; + private final int yScale; + private final int zScale; - public Linear3DBlockPattern(Pattern[] patterns) { + public Linear3DBlockPattern(Pattern[] patterns, int xScale, int yScale, int zScale) { this.patternsArray = patterns; + this.xScale = xScale; + this.yScale = yScale; + this.zScale = zScale; } @Override public BaseBlock apply(BlockVector3 position) { - int index = (position.getBlockX() + position.getBlockY() + position.getBlockZ()) % patternsArray.length; + int index = (position.getBlockX() / this.xScale + + position.getBlockY() / this.yScale + position.getBlockZ() / this.zScale) % patternsArray.length; if (index < 0) { index += patternsArray.length; } @@ -26,7 +33,8 @@ public class Linear3DBlockPattern extends AbstractPattern { @Override public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { - int index = (get.getBlockX() + get.getBlockY() + get.getBlockZ()) % patternsArray.length; + int index = (get.getBlockX() / this.xScale + + get.getBlockY() / this.yScale + get.getBlockZ() / this.zScale) % patternsArray.length; if (index < 0) { index += patternsArray.length; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SuggestionHelper.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SuggestionHelper.java index 87ea41d68..28bbea434 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SuggestionHelper.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/SuggestionHelper.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.command.util; +import com.boydti.fawe.util.MathMan; import com.sk89q.worldedit.registry.Keyed; import com.sk89q.worldedit.registry.NamespacedRegistry; import com.sk89q.worldedit.registry.Registry; @@ -35,6 +36,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import static org.enginehub.piston.converter.SuggestionHelper.byPrefix; @@ -173,4 +175,57 @@ public final class SuggestionHelper { Predicate search = byPrefix(input.toLowerCase(Locale.ROOT)); return registry.keySet().stream().filter(search); } + + /** + * Returns a stream of suggestions for positive doubles. + * + * @param argumentInput the given input to filter with. + * @return a stream of suggestions. + */ + public static Stream suggestPositiveDoubles(String argumentInput) { + if (argumentInput.isEmpty()) { + return Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9"); + } + // if already a valid number, suggest more digits + if (isDouble(argumentInput)) { + Stream numbers = Stream.of("", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); + if (argumentInput.indexOf('.') == -1) { + numbers = Stream.concat(numbers, Stream.of(".")); + } + return numbers.map(s -> argumentInput + s); + } + // no valid input anymore + return Stream.empty(); + } + + /** + * Returns a stream of suggestions for positive integers. + * + * @param argumentInput the given input to filter with. + * @return a stream of suggestions. + */ + public static Stream suggestPositiveIntegers(String argumentInput) { + if (argumentInput.isEmpty()) { + return IntStream.rangeClosed(1, 9).mapToObj(Integer::toString); + } + if (MathMan.isInteger(argumentInput)) { + return IntStream.rangeClosed(0, 9).mapToObj(Integer::toString).map(s -> argumentInput + s); + } + // no valid input anymore + return Stream.empty(); + } + + private static boolean isDouble(String input) { + boolean point = false; + for (char c : input.toCharArray()) { + if (!Character.isDigit(c)) { + if (c == '.' && !point) { + point = true; + } else { + return false; + } + } + } + return true; + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java index e8bad9814..3a392b8d8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java @@ -22,7 +22,11 @@ package com.sk89q.worldedit.extension.factory; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.factory.parser.pattern.BiomePatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.BlockCategoryPatternParser; +import com.sk89q.worldedit.extension.factory.parser.pattern.BufferedPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.ClipboardPatternParser; +import com.sk89q.worldedit.extension.factory.parser.pattern.ExistingPatternParser; +import com.sk89q.worldedit.extension.factory.parser.pattern.Linear2DPatternParser; +import com.sk89q.worldedit.extension.factory.parser.pattern.Linear3DPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.PerlinPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.RandomStatePatternParser; @@ -66,6 +70,10 @@ public final class PatternFactory extends AbstractFactory { register(new PerlinPatternParser(worldEdit)); register(new RidgedMultiFractalPatternParser(worldEdit)); register(new BiomePatternParser(worldEdit)); + register(new Linear2DPatternParser(worldEdit)); + register(new Linear3DPatternParser(worldEdit)); + register(new BufferedPatternParser(worldEdit)); + register(new ExistingPatternParser(worldEdit)); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/RichParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/RichParser.java index 66fe34d98..fae4b740d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/RichParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/RichParser.java @@ -1,5 +1,6 @@ package com.sk89q.worldedit.extension.factory.parser; +import com.google.common.base.Preconditions; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; @@ -7,8 +8,10 @@ import com.sk89q.worldedit.internal.registry.InputParser; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.StringJoiner; +import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Stream; /** @@ -18,51 +21,75 @@ import java.util.stream.Stream; * @param the parse result. */ public abstract class RichParser extends InputParser { - private final String prefix; - private final String required; + private final String[] prefixes; /** * Create a new rich parser with a defined prefix for the result, e.g. {@code #simplex}. * * @param worldEdit the worldedit instance. - * @param prefix the prefix of this parser result. + * @param aliases the prefix of this parser result. */ - protected RichParser(WorldEdit worldEdit, String prefix) { + protected RichParser(WorldEdit worldEdit, String... aliases) { super(worldEdit); - this.prefix = prefix; - this.required = prefix + "["; + Preconditions.checkArgument(aliases.length >= 1, "Aliases may not be empty"); + this.prefixes = aliases; + } + + @NotNull + private static Predicate validPrefix(String other) { + return prefix -> { + if (prefix.length() > other.length()) { + return prefix.startsWith(other); + } + return other.startsWith(prefix); + }; + } + + @NotNull + private Function> extractArguments(String input) { + return prefix -> { + if (input.length() > prefix.length()) { + // input already contains argument(s) -> extract them + String[] strings = extractArguments(input.substring(prefix.length()), false); + // rebuild the argument string without the last argument + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < strings.length - 1; i++) { + builder.append('[').append(strings[i]).append(']'); + } + String previous = prefix + builder; + // read the suggestions for the last argument + return getSuggestions(strings[strings.length - 1], strings.length - 1) + .map(suggestion -> previous + "[" + suggestion); + } else { + return Stream.of(prefix); + } + }; + } + + public String getPrefix() { + return this.prefixes[0]; } @Override public Stream getSuggestions(String input) { - // we don't even want to start suggesting if it's not meant to be this parser result - if (input.length() >= this.required.length() && !input.startsWith(this.required)) { - return Stream.empty(); - } - // suggest until the first [ as long as it isn't fully typed - if (input.length() < this.required.length()) { - return Stream.of(this.required).filter(s -> s.startsWith(input)); - } - // we know that it is at least "" - String[] strings = extractArguments(input.substring(this.prefix.length()), false); - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < strings.length - 1; i++) { - builder.append('[').append(strings[i]).append(']'); - } - String previous = this.prefix + builder; - return getSuggestions(strings[strings.length - 1], strings.length - 1).map(s -> previous + "[" + s + "]"); + return Arrays.stream(this.prefixes) + .filter(validPrefix(input)) + .flatMap(extractArguments(input)); } @Override public E parseFromInput(String input, ParserContext context) throws InputParseException { - if (!input.startsWith(this.prefix)) { - return null; + for (String prefix : this.prefixes) { + if (!input.startsWith(prefix)) { + continue; + } + if (input.length() < prefix.length()) { + continue; + } + String[] arguments = extractArguments(input.substring(prefix.length()), true); + return parseFromInput(arguments, context); } - if (input.length() < this.prefix.length()) { - return null; - } - String[] arguments = extractArguments(input.substring(prefix.length()), true); - return parseFromInput(arguments, context); + return null; } /** @@ -118,40 +145,4 @@ public abstract class RichParser extends InputParser { } return arguments.toArray(new String[0]); } - - /** - * Returns a stream of suggestions for positive doubles. - * - * @param argumentInput the given input to filter with. - * @return a stream of suggestions. - */ - protected Stream suggestPositiveDoubles(String argumentInput) { - if (argumentInput.isEmpty()) { - return Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9"); - } - // if already a valid number, suggest more digits - if (isDouble(argumentInput)) { - Stream numbers = Stream.of("", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); - if (argumentInput.indexOf('.') == -1) { - numbers = Stream.concat(numbers, Stream.of(".")); - } - return numbers.map(s -> argumentInput + s); - } - // no valid input anymore - return Stream.empty(); - } - - private static boolean isDouble(String input) { - boolean point = false; - for (char c : input.toCharArray()) { - if (!Character.isDigit(c)) { - if (c == '.' && !point) { - point = true; - } else { - return false; - } - } - } - return true; - } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/AdjacentMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/AdjacentMaskParser.java index c9d24e1a8..d60ef0d9a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/AdjacentMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/AdjacentMaskParser.java @@ -3,6 +3,7 @@ package com.sk89q.worldedit.extension.factory.parser.mask; import com.boydti.fawe.object.mask.AdjacentAnyMask; import com.boydti.fawe.object.mask.AdjacentMask; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.SuggestionHelper; import com.sk89q.worldedit.extension.factory.parser.RichParser; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; @@ -22,7 +23,7 @@ public class AdjacentMaskParser extends RichParser { if (index == 0) { return worldEdit.getMaskFactory().getSuggestions(argumentInput).stream(); } else if (index == 1 || index == 2) { - return this.suggestPositiveDoubles(argumentInput); + return SuggestionHelper.suggestPositiveDoubles(argumentInput); } return Stream.empty(); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/AngleMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/AngleMaskParser.java index 0c7acf214..f2d9867ee 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/AngleMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/AngleMaskParser.java @@ -2,10 +2,13 @@ package com.sk89q.worldedit.extension.factory.parser.mask; import com.boydti.fawe.object.mask.AngleMask; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.SuggestionHelper; import com.sk89q.worldedit.extension.factory.parser.RichParser; 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.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import org.jetbrains.annotations.NotNull; import java.util.stream.Stream; @@ -21,7 +24,7 @@ public class AngleMaskParser extends RichParser { @Override protected Stream getSuggestions(String argumentInput, int index) { if (index == 0 || index == 1) { - return suggestPositiveDoubles(argumentInput).flatMap(s -> Stream.of(s, s + "d")); + return SuggestionHelper.suggestPositiveDoubles(argumentInput).flatMap(s -> Stream.of(s, s + "d")); } else if (index > 1 && index <= 1 + flags.length) { return Stream.of(flags); } @@ -37,7 +40,7 @@ public class AngleMaskParser extends RichParser { String maxArg = arguments[1]; boolean degree = minArg.endsWith("d"); if (degree ^ maxArg.endsWith("d")) { - throw new InputParseException("Cannot combine degree with block-step"); + throw new InputParseException(TranslatableComponent.of("fawe.error.mask.angle")); } boolean overlay = false; if (arguments.length > 2) { @@ -46,7 +49,8 @@ public class AngleMaskParser extends RichParser { if (flag.equals("-o")) { overlay = true; } else { - throw new InputParseException("The flag " + flag + " is not applicable for this mask!"); + throw new InputParseException(TranslatableComponent.of("fawe.error.invalid-flag", + TextComponent.of(flag))); } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExtremaMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExtremaMaskParser.java index 50fa9b849..479b6f71c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExtremaMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExtremaMaskParser.java @@ -2,10 +2,13 @@ package com.sk89q.worldedit.extension.factory.parser.mask; import com.boydti.fawe.object.mask.ExtremaMask; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.SuggestionHelper; import com.sk89q.worldedit.extension.factory.parser.RichParser; 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.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import org.jetbrains.annotations.NotNull; import java.util.stream.Stream; @@ -21,7 +24,7 @@ public class ExtremaMaskParser extends RichParser { @Override protected Stream getSuggestions(String argumentInput, int index) { if (index == 0 || index == 1) { - return suggestPositiveDoubles(argumentInput).flatMap(s -> Stream.of(s, s + "d")); + return SuggestionHelper.suggestPositiveDoubles(argumentInput).flatMap(s -> Stream.of(s, s + "d")); } else if (index > 1 && index <= 1 + flags.length) { return Stream.of(flags); } @@ -37,7 +40,7 @@ public class ExtremaMaskParser extends RichParser { String maxArg = arguments[1]; boolean degree = minArg.endsWith("d"); if (degree ^ maxArg.endsWith("d")) { - throw new InputParseException("Cannot combine degree with block-step"); + throw new InputParseException(TranslatableComponent.of("fawe.error.mask.angle")); } double min; double max; @@ -48,7 +51,8 @@ public class ExtremaMaskParser extends RichParser { if (flag.equals("-o")) { overlay = true; } else { - throw new InputParseException("The flag " + flag + " is not applicable for this mask!"); + throw new InputParseException(TranslatableComponent.of("fawe.error.invalid-flag", + TextComponent.of(flag))); } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ROCAngleMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ROCAngleMaskParser.java index fd4672ae7..b99274084 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ROCAngleMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ROCAngleMaskParser.java @@ -2,10 +2,13 @@ package com.sk89q.worldedit.extension.factory.parser.mask; import com.boydti.fawe.object.mask.ROCAngleMask; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.SuggestionHelper; import com.sk89q.worldedit.extension.factory.parser.RichParser; 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.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import org.jetbrains.annotations.NotNull; import java.util.stream.Stream; @@ -21,7 +24,7 @@ public class ROCAngleMaskParser extends RichParser { @Override protected Stream getSuggestions(String argumentInput, int index) { if (index == 0 || index == 1) { - return suggestPositiveDoubles(argumentInput).flatMap(s -> Stream.of(s, s + "d")); + return SuggestionHelper.suggestPositiveDoubles(argumentInput).flatMap(s -> Stream.of(s, s + "d")); } else if (index > 1 && index <= 1 + flags.length) { return Stream.of(flags); } @@ -37,7 +40,7 @@ public class ROCAngleMaskParser extends RichParser { String maxArg = arguments[1]; boolean degree = minArg.endsWith("d"); if (degree ^ maxArg.endsWith("d")) { - throw new InputParseException("Cannot combine degree with block-step"); + throw new InputParseException(TranslatableComponent.of("fawe.error.mask.angle")); } double min; double max; @@ -48,7 +51,8 @@ public class ROCAngleMaskParser extends RichParser { if (flag.equals("-o")) { overlay = true; } else { - throw new InputParseException("The flag " + flag + " is not applicable for this mask!"); + throw new InputParseException(TranslatableComponent.of("fawe.error.invalid-flag", + TextComponent.of(flag))); } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/RadiusMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/RadiusMaskParser.java index 914159218..ae0f7182e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/RadiusMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/RadiusMaskParser.java @@ -2,6 +2,7 @@ package com.sk89q.worldedit.extension.factory.parser.mask; import com.boydti.fawe.object.mask.RadiusMask; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.SuggestionHelper; import com.sk89q.worldedit.extension.factory.parser.RichParser; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; @@ -19,7 +20,7 @@ public class RadiusMaskParser extends RichParser { @Override protected Stream getSuggestions(String argumentInput, int index) { if (index == 0 || index == 1) { - return suggestPositiveDoubles(argumentInput); + return SuggestionHelper.suggestPositiveDoubles(argumentInput); } return Stream.empty(); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SimplexMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SimplexMaskParser.java index 09424b6c8..4e96690f0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SimplexMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SimplexMaskParser.java @@ -2,6 +2,7 @@ package com.sk89q.worldedit.extension.factory.parser.mask; import com.boydti.fawe.object.mask.SimplexMask; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.SuggestionHelper; import com.sk89q.worldedit.extension.factory.parser.RichParser; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; @@ -20,7 +21,7 @@ public class SimplexMaskParser extends RichParser { @Override protected Stream getSuggestions(String argumentInput, int index) { if (index < 3) { - suggestPositiveDoubles(argumentInput); + return SuggestionHelper.suggestPositiveDoubles(argumentInput); } return Stream.empty(); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BufferedPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BufferedPatternParser.java new file mode 100644 index 000000000..d7e48ccf8 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BufferedPatternParser.java @@ -0,0 +1,43 @@ +package com.sk89q.worldedit.extension.factory.parser.pattern; + +import com.boydti.fawe.object.pattern.BufferedPattern; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.factory.parser.RichParser; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import org.jetbrains.annotations.NotNull; + +import java.util.stream.Stream; + +public class BufferedPatternParser extends RichParser { + + /** + * Create a new rich parser with a defined prefix for the result, e.g. {@code #simplex}. + * + * @param worldEdit the worldedit instance. + */ + public BufferedPatternParser(WorldEdit worldEdit) { + super(worldEdit, "#buffer"); + } + + @Override + protected Stream getSuggestions(String argumentInput, int index) { + if (index == 0) { + return this.worldEdit.getPatternFactory().getSuggestions(argumentInput).stream(); + } + return Stream.empty(); + } + + @Override + protected Pattern parseFromInput(@NotNull String[] arguments, ParserContext context) throws InputParseException { + if (arguments.length != 1) { + throw new InputParseException(TranslatableComponent.of("fawe.error.command.syntax", + TextComponent.of(getPrefix() + "[pattern] (e.g. " + getPrefix() + "[stone,dirt])"))); + } + Pattern inner = this.worldEdit.getPatternFactory().parseFromInput(arguments[0], context); + return new BufferedPattern(context.requireActor(), inner); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/ExistingPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/ExistingPatternParser.java new file mode 100644 index 000000000..f69981b9a --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/ExistingPatternParser.java @@ -0,0 +1,29 @@ +package com.sk89q.worldedit.extension.factory.parser.pattern; + +import com.boydti.fawe.object.pattern.ExistingPattern; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.internal.registry.SimpleInputParser; + +import java.util.Collections; +import java.util.List; + +public class ExistingPatternParser extends SimpleInputParser { + private final List aliases = Collections.singletonList("#existing"); + + public ExistingPatternParser(WorldEdit worldEdit) { + super(worldEdit); + } + + @Override + public List getMatchedAliases() { + return this.aliases; + } + + @Override + public Pattern parseFromSimpleInput(String input, ParserContext context) throws InputParseException { + return new ExistingPattern(context.requireExtent()); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/Linear2DPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/Linear2DPatternParser.java new file mode 100644 index 000000000..34922b994 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/Linear2DPatternParser.java @@ -0,0 +1,71 @@ +package com.sk89q.worldedit.extension.factory.parser.pattern; + +import com.boydti.fawe.object.pattern.Linear2DBlockPattern; +import com.google.common.base.Preconditions; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.SuggestionHelper; +import com.sk89q.worldedit.extension.factory.parser.RichParser; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.function.pattern.RandomPattern; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; +import java.util.stream.Stream; + +public class Linear2DPatternParser extends RichParser { + + /** + * Create a new rich parser with a defined prefix for the result, e.g. {@code #simplex}. + * + * @param worldEdit the worldedit instance. + */ + public Linear2DPatternParser(WorldEdit worldEdit) { + super(worldEdit, "#linear2d", "#l2d"); + } + + @Override + protected Stream getSuggestions(String argumentInput, int index) { + switch (index) { + case 0: + return this.worldEdit.getPatternFactory().getSuggestions(argumentInput).stream(); + case 1: + case 2: + return SuggestionHelper.suggestPositiveIntegers(argumentInput); + default: + return Stream.empty(); + } + } + + @Override + protected Pattern parseFromInput(@NotNull String[] arguments, ParserContext context) throws InputParseException { + if (arguments.length == 0 || arguments.length > 3) { + throw new InputParseException(TranslatableComponent.of("fawe.error.command.syntax", + TextComponent.of(getPrefix() + "[pattern] (e.g. " + getPrefix() + "[stone,dirt])"))); + } + Pattern inner = this.worldEdit.getPatternFactory().parseFromInput(arguments[0], context); + if (inner instanceof BlockStateHolder) { + return inner; + } + int xScale = 1; + int zScale = 1; + if (arguments.length > 1) { + xScale = Integer.parseInt(arguments[1]); + Preconditions.checkArgument(xScale != 0); + } + if (arguments.length > 2) { + zScale = Integer.parseInt(arguments[2]); + Preconditions.checkArgument(zScale != 0); + } + if (inner instanceof RandomPattern) { + Set patterns = ((RandomPattern) inner).getPatterns(); + return new Linear2DBlockPattern(patterns.toArray(new Pattern[0]), xScale, zScale); + } + throw new InputParseException(TextComponent.of("Pattern " + inner.getClass().getSimpleName() + + " cannot be used with " + getPrefix())); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/Linear3DPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/Linear3DPatternParser.java new file mode 100644 index 000000000..72b8480ea --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/Linear3DPatternParser.java @@ -0,0 +1,77 @@ +package com.sk89q.worldedit.extension.factory.parser.pattern; + +import com.boydti.fawe.object.pattern.Linear3DBlockPattern; +import com.google.common.base.Preconditions; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.SuggestionHelper; +import com.sk89q.worldedit.extension.factory.parser.RichParser; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.function.pattern.RandomPattern; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; +import java.util.stream.Stream; + +public class Linear3DPatternParser extends RichParser { + + /** + * Create a new rich parser with a defined prefix for the result, e.g. {@code #simplex}. + * + * @param worldEdit the worldedit instance. + */ + public Linear3DPatternParser(WorldEdit worldEdit) { + super(worldEdit, "#linear3d", "#l3d"); + } + + @Override + protected Stream getSuggestions(String argumentInput, int index) { + switch (index) { + case 0: + return this.worldEdit.getPatternFactory().getSuggestions(argumentInput).stream(); + case 1: + case 2: + case 3: + return SuggestionHelper.suggestPositiveIntegers(argumentInput); + default: + return Stream.empty(); + } + } + + @Override + protected Pattern parseFromInput(@NotNull String[] arguments, ParserContext context) throws InputParseException { + if (arguments.length == 0 || arguments.length > 4) { + throw new InputParseException(TranslatableComponent.of("fawe.error.command.syntax", + TextComponent.of(getPrefix() + "[pattern] (e.g. " + getPrefix() + "[stone,dirt])"))); + } + Pattern inner = this.worldEdit.getPatternFactory().parseFromInput(arguments[0], context); + if (inner instanceof BlockStateHolder) { + return inner; + } + int xScale = 1; + int yScale = 1; + int zScale = 1; + if (arguments.length > 1) { + xScale = Integer.parseInt(arguments[1]); + Preconditions.checkArgument(xScale != 0); + } + if (arguments.length > 2) { + yScale = Integer.parseInt(arguments[2]); + Preconditions.checkArgument(yScale != 0); + } + if (arguments.length > 3) { + zScale = Integer.parseInt(arguments[3]); + Preconditions.checkArgument(zScale != 0); + } + if (inner instanceof RandomPattern) { + Set patterns = ((RandomPattern) inner).getPatterns(); + return new Linear3DBlockPattern(patterns.toArray(new Pattern[0]), xScale, yScale,zScale); + } + throw new InputParseException(TextComponent.of("Pattern " + inner.getClass().getSimpleName() + + " cannot be used with " + getPrefix())); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/NoisePatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/NoisePatternParser.java index e8b2a0e30..d9b0ee56a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/NoisePatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/NoisePatternParser.java @@ -2,12 +2,15 @@ package com.sk89q.worldedit.extension.factory.parser.pattern; import com.boydti.fawe.object.random.NoiseRandom; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.SuggestionHelper; import com.sk89q.worldedit.extension.factory.parser.RichParser; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.RandomPattern; import com.sk89q.worldedit.math.noise.NoiseGenerator; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.block.BlockStateHolder; import org.jetbrains.annotations.NotNull; @@ -35,7 +38,7 @@ public abstract class NoisePatternParser extends RichParser { @Override protected Stream getSuggestions(String argumentInput, int index) { if (index == 0) { - return suggestPositiveDoubles(argumentInput); + return SuggestionHelper.suggestPositiveDoubles(argumentInput); } if (index == 1) { return worldEdit.getPatternFactory().getSuggestions(argumentInput).stream(); @@ -46,8 +49,8 @@ public abstract class NoisePatternParser extends RichParser { @Override protected Pattern parseFromInput(@NotNull String[] arguments, ParserContext context) { if (arguments.length != 2) { - throw new InputParseException(this.name + " requires a scale and a pattern, e.g. #" - + this.name + "[5][dirt,stone]"); + throw new InputParseException(TranslatableComponent.of("fawe.error.command.syntax", + TextComponent.of(getPrefix() + "[scale][pattern] (e.g. " + getPrefix() + "[5][dirt,stone])"))); } double scale = parseScale(arguments[0]); Pattern inner = worldEdit.getPatternFactory().parseFromInput(arguments[1], context); @@ -56,8 +59,8 @@ public abstract class NoisePatternParser extends RichParser { } else if (inner instanceof BlockStateHolder) { return inner; // single blocks won't have any impact on how a noise behaves } else { - throw new InputParseException("Pattern " + inner.getClass().getSimpleName() - + " cannot be used with #" + this.name); + throw new InputParseException(TextComponent.of("Pattern " + inner.getClass().getSimpleName() + + " cannot be used with #" + this.name)); } } diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index b86e09a95..de060a1ed 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -102,6 +102,8 @@ "fawe.error.player.not.found": "Player not found: {0}", "fawe.error.worldedit.some.fails": "{0} blocks weren't placed because they were outside your allowed region.", "fawe.error.worldedit.some.fails.blockbag": "Missing blocks: {0}", + "fawe.error.mask.angle": "Cannot combine degree with block-step", + "fawe.error.invalid-flag": "The flag {0} is not applicable here", "fawe.cancel.worldedit.cancel.count": "Cancelled {0} edits.", "fawe.cancel.worldedit.cancel.reason.confirm": "Use //confirm to execute {2}", From 5b97c0abcd59c079691718ce250701dc48c9ff7e Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Thu, 8 Oct 2020 21:15:05 +0100 Subject: [PATCH 7/7] Several fixes to actual, probable and possible synchronocity issues (#691) * Several fixes to actual, probable and possible synchronocity issues - Ensure that all edits are queued onto the same AsyncNotifyQueue by actually delegating to the parent player in PlayerProxy - Ensure that the order editsessions are being remembered on the LocalSession is being respected by using a fair ReentrentLock - Ensure a chunk cannot be called when an update is being called * Don't add locks to GetBlocks --- .../com/boydti/fawe/bukkit/FaweBukkit.java | 1 - .../mc1_15_2/BukkitGetBlocks_1_15_2_Copy.java | 2 +- .../mc1_16_1/BukkitGetBlocks_1_16_1_Copy.java | 2 +- .../mc1_16_2/BukkitGetBlocks_1_16_2_Copy.java | 2 +- .../com/sk89q/worldedit/LocalSession.java | 196 +++++++++--------- .../platform/AbstractPlayerActor.java | 1 + .../extension/platform/PlayerProxy.java | 5 + 7 files changed, 111 insertions(+), 98 deletions(-) diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java index 9357e693f..14977e160 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java @@ -18,7 +18,6 @@ import com.boydti.fawe.bukkit.regions.TownyFeature; import com.boydti.fawe.bukkit.regions.Worldguard; import com.boydti.fawe.bukkit.util.BukkitTaskMan; import com.boydti.fawe.bukkit.util.ItemUtil; -import com.boydti.fawe.bukkit.util.VaultUtil; import com.boydti.fawe.bukkit.util.image.BukkitImageViewer; import com.boydti.fawe.config.Settings; import com.boydti.fawe.regions.FaweMaskManager; diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2_Copy.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2_Copy.java index 07b06075c..a9aef70f4 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2_Copy.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2_Copy.java @@ -106,7 +106,7 @@ public class BukkitGetBlocks_1_15_2_Copy extends BukkitGetBlocks_1_15_2 { } protected void storeSection(int layer) { - update(layer, blocks[layer]); + blocks[layer] = update(layer, null).clone(); } @Override diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1_Copy.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1_Copy.java index 42364ed89..9b9dc3697 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1_Copy.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_1/BukkitGetBlocks_1_16_1_Copy.java @@ -107,7 +107,7 @@ public class BukkitGetBlocks_1_16_1_Copy extends BukkitGetBlocks_1_16_1 { } protected void storeSection(int layer) { - update(layer, blocks[layer]); + blocks[layer] = update(layer, null).clone(); } @Override diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitGetBlocks_1_16_2_Copy.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitGetBlocks_1_16_2_Copy.java index 6981d63a7..ac80fe3f1 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitGetBlocks_1_16_2_Copy.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_2/BukkitGetBlocks_1_16_2_Copy.java @@ -107,7 +107,7 @@ public class BukkitGetBlocks_1_16_2_Copy extends BukkitGetBlocks_1_16_2 { } protected void storeSection(int layer) { - update(layer, blocks[layer]); + blocks[layer] = update(layer, null).clone(); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java index 4b8345798..5094f4827 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -92,9 +92,12 @@ import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.Random; import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -116,7 +119,7 @@ public class LocalSession implements TextureHolder { // Session related private transient RegionSelector selector = new CuboidRegionSelector(); private transient boolean placeAtPos1 = false; - private transient List history = Collections.synchronizedList(new LinkedList() { + private final transient List history = Collections.synchronizedList(new LinkedList() { @Override public Object get(int index) { Object value = super.get(index); @@ -135,6 +138,7 @@ public class LocalSession implements TextureHolder { private transient volatile Integer historyNegativeIndex; private transient ClipboardHolder clipboard; private transient final Object clipboardLock = new Object(); + private transient final Lock historyWriteLock = new ReentrantLock(true); private transient boolean superPickaxe = false; private transient BlockTool pickaxeMode = new SinglePickaxe(); private final transient Int2ObjectOpenHashMap tools = new Int2ObjectOpenHashMap<>(0); @@ -439,109 +443,113 @@ public class LocalSession implements TextureHolder { return null; } - public synchronized void remember(Identifiable player, World world, ChangeSet changeSet, FaweLimit limit) { - if (Settings.IMP.HISTORY.USE_DISK) { - LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE; - } - if (changeSet.size() == 0) { - return; - } - loadSessionHistoryFromDisk(player.getUniqueId(), world); - if (changeSet instanceof ChangeSet) { - ListIterator iter = history.listIterator(); - int i = 0; - int cutoffIndex = history.size() - getHistoryNegativeIndex(); - while (iter.hasNext()) { - Object item = iter.next(); - if (++i > cutoffIndex) { - ChangeSet oldChangeSet; - if (item instanceof ChangeSet) { - oldChangeSet = (ChangeSet) item; - } else { - oldChangeSet = getChangeSet(item); + public void remember(Identifiable player, World world, ChangeSet changeSet, FaweLimit limit) { + historyWriteLock.lock(); + try { + if (Settings.IMP.HISTORY.USE_DISK) { + LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE; + } + if (changeSet.size() == 0) { + return; + } + loadSessionHistoryFromDisk(player.getUniqueId(), world); + if (changeSet instanceof ChangeSet) { + ListIterator iter = history.listIterator(); + int i = 0; + int cutoffIndex = history.size() - getHistoryNegativeIndex(); + while (iter.hasNext()) { + Object item = iter.next(); + if (++i > cutoffIndex) { + ChangeSet oldChangeSet; + if (item instanceof ChangeSet) { + oldChangeSet = (ChangeSet) item; + } else { + oldChangeSet = getChangeSet(item); + } + historySize -= MainUtil.getSize(oldChangeSet); + iter.remove(); } - historySize -= MainUtil.getSize(oldChangeSet); - iter.remove(); } } + historySize += MainUtil.getSize(changeSet); + history.add(changeSet); + if (getHistoryNegativeIndex() != 0) { + setDirty(); + historyNegativeIndex = 0; + } + if (limit != null) { + int limitMb = limit.MAX_HISTORY; + while (((!Settings.IMP.HISTORY.USE_DISK && history.size() > MAX_HISTORY_SIZE) || (historySize >> 20) > limitMb) && history.size() > 1) { + ChangeSet item = (ChangeSet) history.remove(0); + item.delete(); + long size = MainUtil.getSize(item); + historySize -= size; + } + } + } finally { + historyWriteLock.unlock(); } - historySize += MainUtil.getSize(changeSet); - history.add(changeSet); - if (getHistoryNegativeIndex() != 0) { - setDirty(); - historyNegativeIndex = 0; - } - if (limit != null) { - int limitMb = limit.MAX_HISTORY; + } + + public void remember(EditSession editSession, boolean append, int limitMb) { + historyWriteLock.lock(); + try { + if (Settings.IMP.HISTORY.USE_DISK) { + LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE; + } + // It should have already been flushed, but just in case! + editSession.flushQueue(); + if (editSession.getChangeSet() == null || limitMb == 0 || historySize >> 20 > limitMb && !append) { + return; + } + + ChangeSet changeSet = editSession.getChangeSet(); + if (changeSet.isEmpty()) { + return; + } + + Player player = editSession.getPlayer(); + if (player != null) { + loadSessionHistoryFromDisk(player.getUniqueId(), editSession.getWorld()); + } + // Destroy any sessions after this undo point + if (append) { + ListIterator iter = history.listIterator(); + int i = 0; + int cutoffIndex = history.size() - getHistoryNegativeIndex(); + while (iter.hasNext()) { + Object item = iter.next(); + if (++i > cutoffIndex) { + ChangeSet oldChangeSet; + if (item instanceof ChangeSet) { + oldChangeSet = (ChangeSet) item; + } else { + oldChangeSet = getChangeSet(item); + } + historySize -= MainUtil.getSize(oldChangeSet); + iter.remove(); + } + } + } + + historySize += MainUtil.getSize(changeSet); + if (append) { + history.add(changeSet); + if (getHistoryNegativeIndex() != 0) { + setDirty(); + historyNegativeIndex = 0; + } + } else { + history.add(0, changeSet); + } while (((!Settings.IMP.HISTORY.USE_DISK && history.size() > MAX_HISTORY_SIZE) || (historySize >> 20) > limitMb) && history.size() > 1) { ChangeSet item = (ChangeSet) history.remove(0); item.delete(); long size = MainUtil.getSize(item); historySize -= size; } - } - } - - public synchronized void remember(EditSession editSession, boolean append, int limitMb) { - if (Settings.IMP.HISTORY.USE_DISK) { - LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE; - } - // It should have already been flushed, but just in case! - editSession.flushQueue(); - if (editSession.getChangeSet() == null || limitMb == 0 || historySize >> 20 > limitMb && !append) { - return; - } - /* - // Don't store anything if no changes were made - if (editSession.size() == 0) { - return; - } - */ - - ChangeSet changeSet = editSession.getChangeSet(); - if (changeSet.isEmpty()) { - return; - } - - Player player = editSession.getPlayer(); - if (player != null) { - loadSessionHistoryFromDisk(player.getUniqueId(), editSession.getWorld()); - } - // Destroy any sessions after this undo point - if (append) { - ListIterator iter = history.listIterator(); - int i = 0; - int cutoffIndex = history.size() - getHistoryNegativeIndex(); - while (iter.hasNext()) { - Object item = iter.next(); - if (++i > cutoffIndex) { - ChangeSet oldChangeSet; - if (item instanceof ChangeSet) { - oldChangeSet = (ChangeSet) item; - } else { - oldChangeSet = getChangeSet(item); - } - historySize -= MainUtil.getSize(oldChangeSet); - iter.remove(); - } - } - } - - historySize += MainUtil.getSize(changeSet); - if (append) { - history.add(changeSet); - if (getHistoryNegativeIndex() != 0) { - setDirty(); - historyNegativeIndex = 0; - } - } else { - history.add(0, changeSet); - } - while (((!Settings.IMP.HISTORY.USE_DISK && history.size() > MAX_HISTORY_SIZE) || (historySize >> 20) > limitMb) && history.size() > 1) { - ChangeSet item = (ChangeSet) history.remove(0); - item.delete(); - long size = MainUtil.getSize(item); - historySize -= size; + } finally { + historyWriteLock.unlock(); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java index b61589c33..ce8be7bbd 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java @@ -684,6 +684,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable { * @param async TODO description * @return false if the task was ran or queued */ + @Override public boolean runAction(Runnable ifFree, boolean checkFree, boolean async) { if (checkFree) { if (runningCount.get() != 0) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java index c3a2dad04..3a8732255 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java @@ -83,6 +83,11 @@ public class PlayerProxy extends AbstractPlayerActor { return basePlayer.getBlockInHand(handSide); } + @Override + public boolean runAction(Runnable ifFree, boolean checkFree, boolean async) { + return basePlayer.runAction(ifFree, checkFree, async); + } + @Override public UUID getUniqueId() { return basePlayer.getUniqueId();