mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2025-06-11 20:13:55 +00:00
Merge remote-tracking branch 'upstream/master' into merge
This commit is contained in:
@ -404,4 +404,4 @@ public class SimplexNoise {
|
||||
this.w = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* The {@code TAG_Int_Array} tag.
|
||||
*/
|
||||
|
@ -23,6 +23,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* The {@code TAG_Long_Array} tag.
|
||||
*/
|
||||
|
@ -21,4 +21,4 @@
|
||||
* This package contains the old command system. It is no longer in use. Please switch
|
||||
* to Piston, Intake, ACF, or similar systems.
|
||||
*/
|
||||
package com.sk89q.minecraft.util.commands;
|
||||
package com.sk89q.minecraft.util.commands;
|
@ -43,6 +43,8 @@ import com.boydti.fawe.util.TaskManager;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.event.extent.EditSessionEvent;
|
||||
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
|
||||
import com.sk89q.worldedit.extension.platform.Capability;
|
||||
import com.sk89q.worldedit.extension.platform.Watchdog;
|
||||
import com.sk89q.worldedit.extent.ChangeSetExtent;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.extent.MaskingExtent;
|
||||
@ -60,6 +62,7 @@ import com.sk89q.worldedit.extent.world.FastModeExtent;
|
||||
import com.sk89q.worldedit.extent.world.SurvivalModeExtent;
|
||||
import com.sk89q.worldedit.extent.world.WatchdogTickingExtent;
|
||||
import com.sk89q.worldedit.function.GroundFunction;
|
||||
import com.sk89q.worldedit.function.biome.BiomeReplace;
|
||||
import com.sk89q.worldedit.function.RegionFunction;
|
||||
import com.sk89q.worldedit.function.block.BlockReplace;
|
||||
import com.sk89q.worldedit.function.block.Naturalizer;
|
||||
@ -94,11 +97,11 @@ import com.sk89q.worldedit.history.changeset.ChangeSet;
|
||||
import com.sk89q.worldedit.internal.expression.Expression;
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.ExpressionTimeoutException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.RValue;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.math.MathUtils;
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionTimeoutException;
|
||||
import com.sk89q.worldedit.internal.expression.LocalSlot.Variable;
|
||||
import com.sk89q.worldedit.math.MutableBlockVector2;
|
||||
import com.sk89q.worldedit.math.MutableBlockVector3;
|
||||
import com.sk89q.worldedit.math.Vector2;
|
||||
@ -319,6 +322,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
public Extent getBypassHistory() {
|
||||
return bypassHistory;
|
||||
}
|
||||
private final List<WatchdogTickingExtent> watchdogExtents = new ArrayList<>(2);
|
||||
|
||||
public void setExtent(AbstractDelegateExtent extent) {
|
||||
new ExtentTraverser<>(getExtent()).setNext(extent);
|
||||
@ -1164,6 +1168,18 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
return this.changes = visitor.getAffected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of blocks of a list of types in a region.
|
||||
*
|
||||
* @param region the region
|
||||
* @param searchBlocks the list of blocks to search
|
||||
* @return the number of blocks that matched the block
|
||||
*/
|
||||
public int countBlocks(Region region, Set<BaseBlock> searchBlocks) {
|
||||
BlockMask mask = new BlockMask(this, searchBlocks);
|
||||
return countBlocks(region, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cuboid above the given position with a given apothem and a given height.
|
||||
*
|
||||
@ -1182,8 +1198,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
getWorld(), // Causes clamping of Y range
|
||||
position.add(-apothem + 1, 0, -apothem + 1),
|
||||
position.add(apothem - 1, height - 1, apothem - 1));
|
||||
Pattern pattern = BlockTypes.AIR.getDefaultState();
|
||||
return setBlocks(region, pattern);
|
||||
return setBlocks(region, BlockTypes.AIR.getDefaultState());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1231,16 +1246,15 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
* Remove blocks of a certain type nearby a given position.
|
||||
*
|
||||
* @param position center position of cuboid
|
||||
* @param blockType the block type to match
|
||||
* @param mask the mask to match
|
||||
* @param apothem an apothem of the cuboid, where the minimum is 1
|
||||
* @return number of blocks affected
|
||||
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||
*/
|
||||
public int removeNear(BlockVector3 position, BlockType blockType, int apothem) throws MaxChangedBlocksException {
|
||||
public int removeNear(BlockVector3 position, Mask mask, int apothem) throws MaxChangedBlocksException {
|
||||
checkNotNull(position);
|
||||
checkArgument(apothem >= 1, "apothem >= 1");
|
||||
|
||||
Mask mask = new SingleBlockTypeMask(this, blockType);
|
||||
BlockVector3 adjustment = BlockVector3.ONE.multiply(apothem - 1);
|
||||
Region region = new CuboidRegion(
|
||||
getWorld(), // Causes clamping of Y range
|
||||
@ -1450,7 +1464,9 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
* @param region the region to stack
|
||||
* @param dir the direction to stack
|
||||
* @param count the number of times to stack
|
||||
* @param copyAir true to also copy air blocks
|
||||
* @param copyEntities true to copy entities
|
||||
* @param copyBiomes true to copy biomes
|
||||
* @param mask source mask for the operation (only matching blocks are copied)
|
||||
* @return number of blocks affected
|
||||
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||
*/
|
||||
@ -1485,10 +1501,13 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
* @param region the region to move
|
||||
* @param dir the direction
|
||||
* @param distance the distance to move
|
||||
* @param copyAir true to copy air blocks
|
||||
* @param moveEntities true to move entities
|
||||
* @param copyBiomes true to copy biomes (source biome is unchanged)
|
||||
* @param mask source mask for the operation (only matching blocks are moved)
|
||||
* @param replacement the replacement pattern to fill in after moving, or null to use air
|
||||
* @return number of blocks moved
|
||||
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||
* @throws IllegalArgumentException thrown if the region is not a flat region, but copyBiomes is true
|
||||
*/
|
||||
public int moveRegion(Region region, BlockVector3 dir, int distance, boolean copyAir,
|
||||
boolean moveEntities, boolean copyBiomes, Pattern replacement) throws MaxChangedBlocksException {
|
||||
@ -1563,7 +1582,7 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
/**
|
||||
* Drain nearby pools of water or lava, optionally removed waterlogged states from blocks.
|
||||
*
|
||||
* @param origin the origin to drain from, which will search a 3×3 area
|
||||
* @param origin the origin to drain from, which will search a 3x3 area
|
||||
* @param radius the radius of the removal, where a value should be 0 or greater
|
||||
* @param waterlogged true to make waterlogged blocks non-waterlogged as well
|
||||
* @return number of blocks affected
|
||||
@ -1592,14 +1611,14 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
}
|
||||
RecursiveVisitor visitor = new RecursiveVisitor(mask, replace, (int) (radius * 2 + 1));
|
||||
|
||||
// Around the origin in a 3×3 block
|
||||
// Around the origin in a 3x3 block
|
||||
for (BlockVector3 position : CuboidRegion.fromCenter(origin, 1)) {
|
||||
if (mask.test(position)) {
|
||||
visitor.visit(position);
|
||||
}
|
||||
}
|
||||
|
||||
Operations.completeBlindly(visitor);
|
||||
Operations.completeLegacy(visitor);
|
||||
|
||||
return this.changes = visitor.getAffected();
|
||||
}
|
||||
@ -1680,6 +1699,21 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
return makeCylinder(pos, block, radiusX, radiusZ, height, thickness, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stack a cuboid region. For compatibility, entities are copied by biomes are not.
|
||||
* Use {@link #stackCuboidRegion(Region, BlockVector3, int, boolean, boolean, Mask)} to fine tune.
|
||||
*
|
||||
* @param region the region to stack
|
||||
* @param dir the direction to stack
|
||||
* @param count the number of times to stack
|
||||
* @param copyAir true to also copy air blocks
|
||||
* @return number of blocks affected
|
||||
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||
*/
|
||||
public int stackCuboidRegion(Region region, BlockVector3 dir, int count, boolean copyAir) throws MaxChangedBlocksException {
|
||||
return stackCuboidRegion(region, dir, count, true, false, copyAir ? null : new ExistingBlockMask(this));
|
||||
}
|
||||
|
||||
private int makeCylinder(BlockVector3 pos, Pattern block, double radiusX, double radiusZ, int height, double thickness, boolean filled) throws MaxChangedBlocksException {
|
||||
int affected = 0;
|
||||
|
||||
@ -1790,6 +1824,21 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
return this.changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the blocks in a region a certain direction.
|
||||
*
|
||||
* @param region the region to move
|
||||
* @param dir the direction
|
||||
* @param distance the distance to move
|
||||
* @param copyAir true to copy air blocks
|
||||
* @param replacement the replacement pattern to fill in after moving, or null to use air
|
||||
* @return number of blocks moved
|
||||
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||
*/
|
||||
public int moveRegion(Region region, BlockVector3 dir, int distance, boolean copyAir, Pattern replacement) throws MaxChangedBlocksException {
|
||||
return moveRegion(region, dir, distance, true, false, copyAir ? new ExistingBlockMask(this) : null, replacement);
|
||||
}
|
||||
|
||||
public int makeCircle(BlockVector3 pos, final Pattern block, double radiusX, double radiusY, double radiusZ, boolean filled, Vector3 normal) throws MaxChangedBlocksException {
|
||||
radiusX += 0.5;
|
||||
radiusY += 0.5;
|
||||
@ -2322,8 +2371,10 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
final Expression expression = Expression.compile(expressionString, "x", "y", "z", "type", "data");
|
||||
expression.optimize();
|
||||
|
||||
final RValue typeVariable = expression.getVariable("type", false);
|
||||
final RValue dataVariable = expression.getVariable("data", false);
|
||||
final Variable typeVariable = expression.getSlots().getVariable("type")
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
final Variable dataVariable = expression.getSlots().getVariable("data")
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
|
||||
final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, unit, zero);
|
||||
expression.setEnvironment(environment);
|
||||
@ -2371,9 +2422,12 @@ public class EditSession extends PassthroughExtent implements AutoCloseable {
|
||||
final Expression expression = Expression.compile(expressionString, "x", "y", "z");
|
||||
expression.optimize();
|
||||
|
||||
final RValue x = expression.getVariable("x", false).optimize();
|
||||
final RValue y = expression.getVariable("y", false).optimize();
|
||||
final RValue z = expression.getVariable("z", false).optimize();
|
||||
final Variable x = expression.getSlots().getVariable("x")
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
final Variable y = expression.getSlots().getVariable("y")
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
final Variable z = expression.getSlots().getVariable("z")
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
|
||||
final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, unit, zero);
|
||||
expression.setEnvironment(environment);
|
||||
|
@ -20,8 +20,6 @@
|
||||
package com.sk89q.worldedit;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.event.extent.EditSessionEvent;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.extent.inventory.BlockBag;
|
||||
|
@ -78,7 +78,6 @@ import com.sk89q.worldedit.world.World;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.item.ItemType;
|
||||
import com.sk89q.worldedit.world.item.ItemTypes;
|
||||
import com.sk89q.worldedit.world.snapshot.Snapshot;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import java.io.File;
|
||||
@ -148,6 +147,7 @@ public class LocalSession implements TextureHolder {
|
||||
private transient ResettableExtent transform = null;
|
||||
private transient ZoneId timezone = ZoneId.systemDefault();
|
||||
private transient World currentWorld;
|
||||
private transient boolean tickingWatchdog = false;
|
||||
private transient UUID uuid;
|
||||
private transient volatile long historySize = 0;
|
||||
|
||||
@ -157,8 +157,6 @@ public class LocalSession implements TextureHolder {
|
||||
private transient World worldOverride;
|
||||
private transient boolean tickingWatchdog = false;
|
||||
|
||||
private transient boolean loadDefaults = true;
|
||||
|
||||
// Saved properties
|
||||
private String lastScript;
|
||||
private RegionSelectorType defaultSelector;
|
||||
@ -1104,6 +1102,14 @@ public class LocalSession implements TextureHolder {
|
||||
}
|
||||
}
|
||||
|
||||
public void setPlaceAtPos1(boolean placeAtPos1) {
|
||||
this.placeAtPos1 = placeAtPos1;
|
||||
}
|
||||
|
||||
public boolean isPlaceAtPos1() {
|
||||
return placeAtPos1;
|
||||
}
|
||||
|
||||
public void setTool(BaseItem item, @Nullable Tool tool, Player player) throws InvalidToolBindException {
|
||||
ItemType type = item.getType();
|
||||
if (type.hasBlockType() && type.getBlockType().getMaterial().isAir()) {
|
||||
@ -1603,4 +1609,13 @@ public class LocalSession implements TextureHolder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareEditingExtents(EditSession editSession, Actor actor) {
|
||||
editSession.setFastMode(fastMode);
|
||||
editSession.setReorderMode(reorderMode);
|
||||
if (editSession.getSurvivalExtent() != null) {
|
||||
editSession.getSurvivalExtent().setStripNbt(!actor.hasPermission("worldedit.setnbt"));
|
||||
}
|
||||
editSession.setTickingWatchdog(tickingWatchdog);
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.blocks.metadata;
|
||||
|
||||
/**
|
||||
* Represents the possible types of mobs.
|
||||
*/
|
||||
public enum MobType {
|
||||
BAT("Bat"),
|
||||
BLAZE("Blaze"),
|
||||
CAVE_SPIDER("CaveSpider"),
|
||||
CHICKEN("Chicken"),
|
||||
COW("Cow"),
|
||||
CREEPER("Creeper"),
|
||||
ENDERDRAGON("EnderDragon"),
|
||||
ENDERMAN("Enderman"),
|
||||
GHAST("Ghast"),
|
||||
GIANT("Giant"),
|
||||
VILLAGER_GOLEM("VillagerGolem"),
|
||||
HORSE("EntityHorse"),
|
||||
MAGMA_CUBE("LavaSlime"),
|
||||
MOOSHROOM("MushroomCow"),
|
||||
OCELOT("Ozelot"),
|
||||
PIG("Pig"),
|
||||
PIG_ZOMBIE("PigZombie"),
|
||||
SHEEP("Sheep"),
|
||||
SILVERFISH("Silverfish"),
|
||||
SKELETON("Skeleton"),
|
||||
SLIME("Slime"),
|
||||
SNOWMAN("SnowMan"),
|
||||
SPIDER("Spider"),
|
||||
SQUID("Squid"),
|
||||
VILLAGER("Villager"),
|
||||
WITCH("Witch"),
|
||||
WITHER("WitherBoss"),
|
||||
WOLF("Wolf"),
|
||||
ZOMBIE("Zombie");
|
||||
|
||||
private final String name;
|
||||
|
||||
MobType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
@ -27,6 +27,7 @@ import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.command.util.CommandPermissions;
|
||||
import com.sk89q.worldedit.command.util.WorldEditAsyncCommandBuilder;
|
||||
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
|
||||
import com.sk89q.worldedit.command.util.Logging;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
@ -81,6 +82,7 @@ public class BiomeCommands {
|
||||
public void biomeList(Actor actor,
|
||||
@ArgFlag(name = 'p', desc = "Page number.", def = "1")
|
||||
int page) {
|
||||
WorldEditAsyncCommandBuilder.createAndSendMessage(actor, () -> {
|
||||
BiomeRegistry biomeRegistry = WorldEdit.getInstance().getPlatformManager()
|
||||
.queryCapability(Capability.GAME_HOOKS).getRegistries().getBiomeRegistry();
|
||||
|
||||
@ -97,7 +99,8 @@ public class BiomeCommands {
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList()));
|
||||
actor.print(paginationBox.create(page));
|
||||
return paginationBox.create(page);
|
||||
}, null);
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -180,7 +183,8 @@ public class BiomeCommands {
|
||||
Mask2D mask2d = mask != null ? mask.toMask2D() : null;
|
||||
|
||||
if (atPosition) {
|
||||
region = new CuboidRegion(player.getLocation().toVector().toBlockPoint(), player.getLocation().toVector().toBlockPoint());
|
||||
final BlockVector3 pos = player.getLocation().toVector().toBlockPoint();
|
||||
region = new CuboidRegion(pos, pos);
|
||||
} else {
|
||||
region = session.getSelection(world);
|
||||
}
|
||||
|
@ -19,8 +19,6 @@
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.config.Settings;
|
||||
@ -67,6 +65,7 @@ import com.sk89q.worldedit.EmptyClipboardException;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.command.factory.ReplaceFactory;
|
||||
import com.sk89q.worldedit.blocks.BaseItem;
|
||||
import com.sk89q.worldedit.command.argument.Arguments;
|
||||
import com.sk89q.worldedit.command.factory.TreeGeneratorFactory;
|
||||
@ -127,6 +126,7 @@ import java.nio.file.FileSystems;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import org.enginehub.piston.annotation.Command;
|
||||
import com.sk89q.worldedit.function.factory.Apply;
|
||||
import org.enginehub.piston.annotation.CommandContainer;
|
||||
import org.enginehub.piston.annotation.param.Arg;
|
||||
import org.enginehub.piston.annotation.param.ArgFlag;
|
||||
@ -134,6 +134,8 @@ import org.enginehub.piston.annotation.param.Switch;
|
||||
import org.enginehub.piston.inject.InjectedValueAccess;
|
||||
import org.enginehub.piston.inject.Key;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Commands to set brush shape.
|
||||
*/
|
||||
@ -152,6 +154,15 @@ public class BrushCommands {
|
||||
this.worldEdit = worldEdit;
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "none",
|
||||
aliases = "unbind",
|
||||
desc = "Unbind a bound brush from your current item"
|
||||
)
|
||||
void none(Player player, LocalSession session) throws WorldEditException {
|
||||
ToolCommands.setToolNone(player, session, "Brush");
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "blendball",
|
||||
aliases = {"bb", "blend"},
|
||||
@ -644,18 +655,18 @@ public class BrushCommands {
|
||||
)
|
||||
@Deprecated
|
||||
@CommandPermissions("worldedit.brush.clipboard")
|
||||
public void clipboardBrush(LocalSession session, InjectedValueAccess context,
|
||||
@Switch(name = 'a', desc = "Don't paste air from the clipboard")
|
||||
boolean ignoreAir,
|
||||
@Switch(name = 'o', desc = "Paste starting at the target location, instead of centering on it")
|
||||
boolean usingOrigin,
|
||||
@Switch(name = 'e', desc = "Skip paste entities if available")
|
||||
boolean skipEntities,
|
||||
@Switch(name = 'b', desc = "Paste biomes if available")
|
||||
boolean pasteBiomes,
|
||||
@ArgFlag(name = 'm', desc = "Skip blocks matching this mask in the clipboard", def = "")
|
||||
@ClipboardMask
|
||||
Mask sourceMask) throws WorldEditException {
|
||||
public void clipboardBrush(Player player, LocalSession session,
|
||||
@Switch(name = 'a', desc = "Don't paste air from the clipboard")
|
||||
boolean ignoreAir,
|
||||
@Switch(name = 'o', desc = "Paste starting at the target location, instead of centering on it")
|
||||
boolean usingOrigin,
|
||||
@Switch(name = 'e', desc = "Paste entities if available")
|
||||
boolean pasteEntities,
|
||||
@Switch(name = 'b', desc = "Paste biomes if available")
|
||||
boolean pasteBiomes,
|
||||
@ArgFlag(name = 'm', desc = "Skip blocks matching this mask in the clipboard", def = "")
|
||||
@ClipboardMask
|
||||
Mask sourceMask) throws WorldEditException {
|
||||
ClipboardHolder holder = session.getClipboard();
|
||||
|
||||
Clipboard clipboard = holder.getClipboard();
|
||||
@ -678,13 +689,13 @@ public class BrushCommands {
|
||||
descFooter = "Example: '/brush smooth 2 4 grass_block,dirt,stone'"
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.smooth")
|
||||
public void smoothBrush(Player player, InjectedValueAccess context, EditSession editSession,
|
||||
@Arg(desc = "The radius to sample for softening", def = "2")
|
||||
Expression radius,
|
||||
@Arg(desc = "The number of iterations to perform", def = "4")
|
||||
int iterations,
|
||||
@Arg(desc = "The mask of blocks to use for the heightmap", def = "")
|
||||
Mask maskOpt) throws WorldEditException {
|
||||
public void smoothBrush(Player player, LocalSession session,
|
||||
@Arg(desc = "The radius to sample for softening", def = "2")
|
||||
Expression radius,
|
||||
@Arg(desc = "The number of iterations to perform", def = "4")
|
||||
int iterations,
|
||||
@Arg(desc = "The mask of blocks to use for the heightmap", def = "")
|
||||
Mask maskOpt) throws WorldEditException {
|
||||
worldEdit.checkMaxBrushRadius(radius);
|
||||
|
||||
FaweLimit limit = Settings.IMP.getLimit(player);
|
||||
@ -1089,4 +1100,106 @@ public class BrushCommands {
|
||||
|
||||
player.print("Set brush to " + factory);
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "deform",
|
||||
desc = "Deform brush, applies an expression to an area"
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.deform")
|
||||
public void deform(Player player, LocalSession localSession,
|
||||
@Arg(desc = "The shape of the region")
|
||||
RegionFactory shape,
|
||||
@Arg(desc = "The size of the brush", def = "5")
|
||||
double radius,
|
||||
@Arg(desc = "Expression to apply", def = "y-=0.2")
|
||||
String expression,
|
||||
@Switch(name = 'r', desc = "Use the game's coordinate origin")
|
||||
boolean useRawCoords,
|
||||
@Switch(name = 'o', desc = "Use the placement position as the origin")
|
||||
boolean usePlacement) throws WorldEditException {
|
||||
Deform deform = new Deform(expression);
|
||||
if (useRawCoords) {
|
||||
deform.setMode(Deform.Mode.RAW_COORD);
|
||||
} else if (usePlacement) {
|
||||
deform.setMode(Deform.Mode.OFFSET);
|
||||
deform.setOffset(localSession.getPlacementPosition(player).toVector3());
|
||||
}
|
||||
setOperationBasedBrush(player, localSession, radius,
|
||||
deform, shape, "worldedit.brush.deform");
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "set",
|
||||
desc = "Set brush, sets all blocks in the area"
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.set")
|
||||
public void set(Player player, LocalSession localSession,
|
||||
@Arg(desc = "The shape of the region")
|
||||
RegionFactory shape,
|
||||
@Arg(desc = "The size of the brush", def = "5")
|
||||
double radius,
|
||||
@Arg(desc = "The pattern of blocks to set")
|
||||
Pattern pattern) throws WorldEditException {
|
||||
setOperationBasedBrush(player, localSession, radius,
|
||||
new Apply(new ReplaceFactory(pattern)), shape, "worldedit.brush.set");
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "forest",
|
||||
desc = "Forest brush, creates a forest in the area"
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.forest")
|
||||
public void forest(Player player, LocalSession localSession,
|
||||
@Arg(desc = "The shape of the region")
|
||||
RegionFactory shape,
|
||||
@Arg(desc = "The size of the brush", def = "5")
|
||||
double radius,
|
||||
@Arg(desc = "The density of the brush", def = "20")
|
||||
double density,
|
||||
@Arg(desc = "The type of tree to use")
|
||||
TreeGenerator.TreeType type) throws WorldEditException {
|
||||
setOperationBasedBrush(player, localSession, radius,
|
||||
new Paint(new TreeGeneratorFactory(type), density / 100), shape, "worldedit.brush.forest");
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "raise",
|
||||
desc = "Raise brush, raise all blocks by one"
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.raise")
|
||||
public void raise(Player player, LocalSession localSession,
|
||||
@Arg(desc = "The shape of the region")
|
||||
RegionFactory shape,
|
||||
@Arg(desc = "The size of the brush", def = "5")
|
||||
double radius) throws WorldEditException {
|
||||
setOperationBasedBrush(player, localSession, radius,
|
||||
new Deform("y-=1"), shape, "worldedit.brush.raise");
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "lower",
|
||||
desc = "Lower brush, lower all blocks by one"
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.lower")
|
||||
public void lower(Player player, LocalSession localSession,
|
||||
@Arg(desc = "The shape of the region")
|
||||
RegionFactory shape,
|
||||
@Arg(desc = "The size of the brush", def = "5")
|
||||
double radius) throws WorldEditException {
|
||||
setOperationBasedBrush(player, localSession, radius,
|
||||
new Deform("y+=1"), shape, "worldedit.brush.lower");
|
||||
}
|
||||
|
||||
static void setOperationBasedBrush(Player player, LocalSession session, double radius,
|
||||
Contextual<? extends Operation> factory,
|
||||
RegionFactory shape,
|
||||
String permission) throws WorldEditException {
|
||||
WorldEdit.getInstance().checkMaxBrushRadius(radius);
|
||||
BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType());
|
||||
tool.setSize(radius);
|
||||
tool.setFill(null);
|
||||
tool.setBrush(new OperationFactoryBrush(factory, shape, session), permission);
|
||||
|
||||
player.print("Set brush to " + factory);
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,6 @@
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
||||
import static com.sk89q.worldedit.internal.anvil.ChunkDeleter.DELCHUNKS_FILE_NAME;
|
||||
|
||||
@ -39,6 +37,7 @@ import com.sk89q.worldedit.regions.CuboidRegion;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
import com.sk89q.worldedit.util.formatting.component.PaginationBox;
|
||||
import com.sk89q.worldedit.command.util.WorldEditAsyncCommandBuilder;
|
||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||
import com.sk89q.worldedit.util.formatting.text.event.ClickEvent;
|
||||
@ -46,9 +45,10 @@ import com.sk89q.worldedit.util.formatting.text.format.TextColor;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import com.sk89q.worldedit.world.storage.LegacyChunkStore;
|
||||
import com.sk89q.worldedit.world.storage.McRegionChunkStore;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
@ -59,6 +59,8 @@ import org.enginehub.piston.annotation.CommandContainer;
|
||||
import org.enginehub.piston.annotation.param.ArgFlag;
|
||||
import org.enginehub.piston.exception.StopExecutionException;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Commands for working with chunks.
|
||||
*/
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.boydti.fawe.FaweAPI;
|
||||
import com.boydti.fawe.FaweCache;
|
||||
import com.boydti.fawe.config.BBC;
|
||||
@ -37,12 +39,10 @@ import com.boydti.fawe.util.MaskTraverser;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.LocalConfiguration;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.command.util.CommandPermissions;
|
||||
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
|
||||
import com.sk89q.worldedit.command.util.Logging;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.event.extent.PasteEvent;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
@ -53,6 +53,8 @@ import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
|
||||
import com.sk89q.worldedit.function.block.BlockReplace;
|
||||
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.PLACEMENT;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.function.mask.Masks;
|
||||
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
|
||||
@ -91,11 +93,10 @@ import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.PLACEMENT;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Clipboard commands.
|
||||
@ -103,18 +104,6 @@ import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
||||
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
|
||||
public class ClipboardCommands {
|
||||
|
||||
private WorldEdit worldEdit;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*
|
||||
* @param worldEdit reference to WorldEdit
|
||||
*/
|
||||
public ClipboardCommands(WorldEdit worldEdit) {
|
||||
checkNotNull(worldEdit);
|
||||
this.worldEdit = worldEdit;
|
||||
}
|
||||
|
||||
|
||||
@Command(
|
||||
name = "/copy",
|
||||
@ -442,6 +431,8 @@ public class ClipboardCommands {
|
||||
boolean atOrigin,
|
||||
@Switch(name = 's', desc = "Select the region after pasting")
|
||||
boolean selectPasted,
|
||||
@Switch(name = 'n', desc = "No paste, select only. (Implies -s)")
|
||||
boolean onlySelect,
|
||||
@Switch(name = 'e', desc = "Paste entities if available")
|
||||
boolean pasteEntities,
|
||||
@Switch(name = 'b', desc = "Paste biomes if available")
|
||||
@ -457,6 +448,7 @@ public class ClipboardCommands {
|
||||
}
|
||||
Clipboard clipboard = holder.getClipboard();
|
||||
Region region = clipboard.getRegion();
|
||||
List<String> messages = Lists.newArrayList();
|
||||
|
||||
BlockVector3 to = atOrigin ? clipboard.getOrigin() : session.getPlacementPosition(actor);
|
||||
checkPaste(actor, editSession, to, holder, clipboard);
|
||||
@ -471,7 +463,7 @@ public class ClipboardCommands {
|
||||
.build();
|
||||
Operations.completeLegacy(operation);
|
||||
|
||||
if (selectPasted) {
|
||||
if (selectPasted || onlySelect) {
|
||||
BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
|
||||
Vector3 realTo = to.toVector3().add(holder.getTransform().apply(clipboardOffset.toVector3()));
|
||||
Vector3 max = realTo.add(holder.getTransform().apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3()));
|
||||
@ -578,7 +570,7 @@ public class ClipboardCommands {
|
||||
AffineTransform transform = new AffineTransform();
|
||||
transform = transform.scale(direction.abs().multiply(-2).add(1, 1, 1).toVector3());
|
||||
holder.setTransform(holder.getTransform().combine(transform));
|
||||
actor.print(BBC.COMMAND_FLIPPED.s());
|
||||
actor.print("The clipboard copy has been flipped.");
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -588,6 +580,6 @@ public class ClipboardCommands {
|
||||
@CommandPermissions("worldedit.clipboard.clear")
|
||||
public void clearClipboard(Actor actor, LocalSession session) throws WorldEditException {
|
||||
session.setClipboard(null);
|
||||
actor.print(BBC.CLIPBOARD_CLEARED.s());
|
||||
actor.print("Clipboard cleared.");
|
||||
}
|
||||
}
|
||||
|
@ -19,9 +19,6 @@
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
||||
import static com.sk89q.worldedit.internal.command.CommandUtil.requireIV;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.sk89q.worldedit.IncompleteRegionException;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
@ -39,7 +36,6 @@ import com.sk89q.worldedit.regions.RegionOperationException;
|
||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import java.util.List;
|
||||
import org.enginehub.piston.Command;
|
||||
import org.enginehub.piston.CommandManager;
|
||||
import org.enginehub.piston.CommandManagerService;
|
||||
@ -48,6 +44,11 @@ import org.enginehub.piston.annotation.param.Arg;
|
||||
import org.enginehub.piston.inject.Key;
|
||||
import org.enginehub.piston.part.SubCommandPart;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
||||
import static com.sk89q.worldedit.internal.command.CommandUtil.requireIV;
|
||||
|
||||
/**
|
||||
* Extracted from {@link SelectionCommands} to allow importing of {@link Command}.
|
||||
*/
|
||||
|
@ -19,8 +19,6 @@
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.object.extent.ResettableExtent;
|
||||
@ -44,6 +42,8 @@ import com.sk89q.worldedit.extension.input.DisallowedUsageException;
|
||||
import com.sk89q.worldedit.extension.input.InputParseException;
|
||||
import com.sk89q.worldedit.extension.input.ParserContext;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.extension.platform.Capability;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
@ -52,7 +52,6 @@ import com.sk89q.worldedit.util.formatting.text.Component;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import com.sk89q.worldedit.world.item.ItemType;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -64,6 +63,8 @@ import org.enginehub.piston.annotation.param.Arg;
|
||||
import org.enginehub.piston.annotation.param.ArgFlag;
|
||||
import org.enginehub.piston.annotation.param.Switch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* General WorldEdit commands.
|
||||
*/
|
||||
@ -132,8 +133,8 @@ public class GeneralCommands {
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "/fast",
|
||||
desc = "Toggle fast mode"
|
||||
name = "/fast",
|
||||
desc = "Toggle fast mode"
|
||||
)
|
||||
@CommandPermissions("worldedit.fast")
|
||||
public void fast(Actor actor, LocalSession session,
|
||||
@ -144,12 +145,13 @@ public class GeneralCommands {
|
||||
actor.printError("Fast mode already " + (fastMode ? "enabled" : "disabled") + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasFastMode) {
|
||||
session.setFastMode(false);
|
||||
actor.print(BBC.FAST_DISABLED.s());
|
||||
actor.print("Fast mode disabled.");
|
||||
} else {
|
||||
session.setFastMode(true);
|
||||
actor.print(BBC.FAST_ENABLED.s());
|
||||
actor.print("Fast mode enabled. Lighting in the affected chunks may be wrong and/or you may need to rejoin to see changes.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,20 +198,20 @@ public class GeneralCommands {
|
||||
}
|
||||
}
|
||||
|
||||
// @Command(
|
||||
// name = "/world",
|
||||
// desc = "Sets the world override"
|
||||
// )
|
||||
// @CommandPermissions("worldedit.world")
|
||||
// public void worldOverride(Actor actor, LocalSession session,
|
||||
// @Arg(desc = "The world override", def = "") World world) {
|
||||
// session.setWorldOverride(world);
|
||||
// if (world == null) {
|
||||
// actor.print("Removed world override.");
|
||||
// } else {
|
||||
// actor.print("Set the world override to " + world.getId() + ". (Use //world to go back to default)");
|
||||
// }
|
||||
// }
|
||||
@Command(
|
||||
name = "/world",
|
||||
desc = "Sets the world override"
|
||||
)
|
||||
@CommandPermissions("worldedit.world")
|
||||
public void world(Actor actor, LocalSession session,
|
||||
@Arg(desc = "The world override", def = "") World world) {
|
||||
session.setWorldOverride(world);
|
||||
if (world == null) {
|
||||
actor.print("Removed world override.");
|
||||
} else {
|
||||
actor.print("Set the world override to " + world.getId() + ". (Use //world to go back to default)");
|
||||
}
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "/watchdog",
|
||||
@ -237,16 +239,18 @@ public class GeneralCommands {
|
||||
@Command(
|
||||
name = "gmask",
|
||||
aliases = {"/gmask"},
|
||||
descFooter = "The global destination mask applies to all edits you do and masks based on the destination blocks (i.e., the blocks in the world).",
|
||||
desc = "Set the global mask"
|
||||
)
|
||||
@CommandPermissions({"worldedit.global-mask", "worldedit.mask.global"})
|
||||
public void gmask(Actor actor, LocalSession session, @Arg(desc = "The mask to set", def = "") Mask mask) {
|
||||
session.setMask(mask);
|
||||
@CommandPermissions("worldedit.global-mask")
|
||||
public void gmask(Actor actor, LocalSession session,
|
||||
@Arg(desc = "The mask to set", def = "")
|
||||
Mask mask) {
|
||||
if (mask == null) {
|
||||
actor.print(BBC.MASK_DISABLED.s());
|
||||
session.setMask(null);
|
||||
actor.print("Global mask disabled.");
|
||||
} else {
|
||||
actor.print(BBC.MASK.s());
|
||||
session.setMask(mask);
|
||||
actor.print("Global mask set.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,7 +295,7 @@ public class GeneralCommands {
|
||||
actor.print(new ItemSearcher(search, blocksOnly, itemsOnly, page).call());
|
||||
}
|
||||
|
||||
public static class ItemSearcher implements Callable<Component> {
|
||||
private static class ItemSearcher implements Callable<Component> {
|
||||
private final boolean blocksOnly;
|
||||
private final boolean itemsOnly;
|
||||
private final String search;
|
||||
|
@ -18,8 +18,6 @@
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.sk89q.worldedit.command.MethodCommands.getArguments;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.ALL;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.PLACEMENT;
|
||||
@ -42,10 +40,13 @@ import com.sk89q.worldedit.command.util.Logging;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.function.generator.CavesGen;
|
||||
|
||||
import java.util.List;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.function.operation.Operations;
|
||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.function.visitor.RegionVisitor;
|
||||
import com.sk89q.worldedit.internal.annotation.Radii;
|
||||
import com.sk89q.worldedit.internal.annotation.Range;
|
||||
import com.sk89q.worldedit.internal.annotation.Selection;
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
@ -61,11 +62,12 @@ import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import org.enginehub.piston.annotation.Command;
|
||||
import org.enginehub.piston.annotation.CommandContainer;
|
||||
import org.enginehub.piston.annotation.param.Arg;
|
||||
import org.enginehub.piston.annotation.param.Switch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import org.enginehub.piston.inject.InjectedValueAccess;
|
||||
|
||||
/**
|
||||
@ -184,20 +186,15 @@ public class GenerationCommands {
|
||||
)
|
||||
@CommandPermissions("worldedit.generation.cylinder")
|
||||
@Logging(PLACEMENT)
|
||||
public void hcyl(Actor actor, LocalSession session, EditSession editSession,
|
||||
public int hcyl(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Arg(desc = "The pattern of blocks to generate")
|
||||
Pattern pattern,
|
||||
@Arg(desc = "The radii of the cylinder. Order is N/S, E/W") BlockVector2 radius,
|
||||
Pattern pattern,
|
||||
@Arg(desc = "The radii of the cylinder. 1st is N/S, 2nd is E/W")
|
||||
@Radii(2)
|
||||
List<Double> radii,
|
||||
@Arg(desc = "The height of the cylinder", def = "1")
|
||||
int height,
|
||||
@Range(min = 1) @Arg(desc = "double", def = "1") double thickness, InjectedValueAccess context) throws WorldEditException {
|
||||
double max = MathMan.max(radius.getBlockX(), radius.getBlockZ());
|
||||
worldEdit.checkMaxRadius(max);
|
||||
BlockVector3 pos = session.getPlacementPosition(actor);
|
||||
actor.checkConfirmationRadius(() -> {
|
||||
int affected = editSession.makeHollowCylinder(pos, pattern, radius.getX(), radius.getZ(), Math.min(256, height), thickness - 1);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
}, "/hcyl", (int) max, context);
|
||||
int height) throws WorldEditException {
|
||||
return cyl(actor, session, editSession, pattern, radii, height, true);
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -278,9 +275,10 @@ public class GenerationCommands {
|
||||
int size,
|
||||
@Arg(desc = "The type of forest", def = "tree")
|
||||
TreeType type,
|
||||
@Range(min = 0, max = 100) @Arg(desc = "The density of the forest, between 0 and 100", def = "5")
|
||||
@Arg(desc = "The density of the forest, between 0 and 100", def = "5")
|
||||
double density) throws WorldEditException {
|
||||
checkCommandArgument(0 <= density && density <= 100, "Density must be between 0 and 100");
|
||||
worldEdit.checkMaxRadius(size);
|
||||
density /= 100;
|
||||
int affected = editSession.makeForest(session.getPlacementPosition(actor), size, density, type);
|
||||
actor.print(affected + " trees created.");
|
||||
@ -295,14 +293,10 @@ public class GenerationCommands {
|
||||
@Logging(POSITION)
|
||||
public int pumpkins(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Arg(desc = "The size of the patch", def = "10")
|
||||
int size,
|
||||
@Arg(desc = "//TODO", def = "10")
|
||||
int apothem,
|
||||
@Arg(desc = "//TODO ", def = "0.02")
|
||||
double density) throws WorldEditException {
|
||||
checkCommandArgument(0 <= density && density <= 100, "Density must be between 0 and 100");
|
||||
int affected = editSession.makePumpkinPatches(session.getPlacementPosition(actor), apothem, density);
|
||||
BBC.COMMAND_PUMPKIN.send(actor, affected);
|
||||
int size) throws WorldEditException {
|
||||
worldEdit.checkMaxRadius(size);
|
||||
int affected = editSession.makePumpkinPatches(session.getPlacementPosition(actor), size);
|
||||
actor.print(affected + " pumpkin patches created.");
|
||||
return affected;
|
||||
}
|
||||
|
||||
|
@ -19,8 +19,6 @@
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.FaweAPI;
|
||||
import com.boydti.fawe.config.BBC;
|
||||
@ -57,6 +55,8 @@ import org.enginehub.piston.annotation.param.Arg;
|
||||
import org.enginehub.piston.annotation.param.Switch;
|
||||
import org.enginehub.piston.inject.InjectedValueAccess;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Commands to undo, redo, and clear history.
|
||||
*/
|
||||
@ -223,6 +223,16 @@ public class HistoryCommands {
|
||||
aliases = { "/un", "/ud", "undo" },
|
||||
desc = "Undoes the last action (from history)"
|
||||
)
|
||||
} else {
|
||||
undoSession = session;
|
||||
}
|
||||
int finalTimes = times;
|
||||
player.checkConfirmation(() -> {
|
||||
EditSession undone = null;
|
||||
int i = 0;
|
||||
for (; i < finalTimes; ++i) {
|
||||
undone = undoSession.undo(undoSession.getBlockBag(player), player);
|
||||
if (undone == null) break;
|
||||
@CommandPermissions({"worldedit.history.undo", "worldedit.history.undo.self"})
|
||||
public void undo(Player player, LocalSession session,
|
||||
@Range(min = 1) @Arg(desc = "Number of undoes to perform", def = "1")
|
||||
@ -243,16 +253,6 @@ public class HistoryCommands {
|
||||
BBC.COMMAND_HISTORY_OTHER_ERROR.send(player, playerName);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
undoSession = session;
|
||||
}
|
||||
int finalTimes = times;
|
||||
player.checkConfirmation(() -> {
|
||||
EditSession undone = null;
|
||||
int i = 0;
|
||||
for (; i < finalTimes; ++i) {
|
||||
undone = undoSession.undo(undoSession.getBlockBag(player), player);
|
||||
if (undone == null) break;
|
||||
worldEdit.flushBlockBag(player, undone);
|
||||
}
|
||||
if (undone == null) i--;
|
||||
@ -311,7 +311,7 @@ public class HistoryCommands {
|
||||
@CommandPermissions("worldedit.history.clear")
|
||||
public void clearHistory(Actor actor, LocalSession session) {
|
||||
session.clearHistory();
|
||||
actor.print(BBC.COMMAND_HISTORY_CLEAR.s());
|
||||
actor.print("History cleared.");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,8 +18,6 @@
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.POSITION;
|
||||
|
||||
import com.boydti.fawe.config.BBC;
|
||||
@ -36,6 +34,8 @@ import org.enginehub.piston.annotation.CommandContainer;
|
||||
import org.enginehub.piston.annotation.param.Arg;
|
||||
import org.enginehub.piston.annotation.param.Switch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Commands for moving the player around.
|
||||
*/
|
||||
@ -62,7 +62,7 @@ public class NavigationCommands {
|
||||
@CommandPermissions("worldedit.navigation.unstuck")
|
||||
public void unstuck(Player player) throws WorldEditException {
|
||||
player.findFreePosition();
|
||||
player.print(BBC.UNSTUCK.s());
|
||||
player.print("There you go!");
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -84,11 +84,7 @@ public class NavigationCommands {
|
||||
if (ascentLevels == 0) {
|
||||
player.printError(BBC.ASCEND_FAIL.s());
|
||||
} else {
|
||||
if (ascentLevels == 1) {
|
||||
player.print(BBC.ASCENDED_SINGULAR.s());
|
||||
} else {
|
||||
BBC.ASCENDED_PLURAL.send(player, ascentLevels);
|
||||
}
|
||||
player.print((ascentLevels != 1) ? "Ascended " + ascentLevels + " levels." : "Ascended a level.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +109,7 @@ public class NavigationCommands {
|
||||
} else if (descentLevels == 1) {
|
||||
player.print(BBC.DESCEND_SINGULAR.s());
|
||||
} else {
|
||||
BBC.DESCEND_PLURAL.send(player, descentLevels);
|
||||
player.print((descentLevels != 1) ? "Descended " + descentLevels + " levels." : "Descended a level.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,19 +19,23 @@
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import com.boydti.fawe.FaweAPI;
|
||||
import com.boydti.fawe.FaweCache;
|
||||
import com.boydti.fawe.beta.implementation.processors.ChunkSendProcessor;
|
||||
import com.boydti.fawe.beta.implementation.processors.NullProcessor;
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.object.FaweLimit;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.command.util.CommandPermissions;
|
||||
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
|
||||
import com.sk89q.worldedit.function.RegionFunction;
|
||||
import com.sk89q.worldedit.function.block.BlockReplace;
|
||||
import com.sk89q.worldedit.command.util.Logging;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
@ -39,9 +43,11 @@ import com.sk89q.worldedit.function.FlatRegionFunction;
|
||||
import com.sk89q.worldedit.function.GroundFunction;
|
||||
import com.sk89q.worldedit.function.biome.BiomeReplace;
|
||||
import com.sk89q.worldedit.function.generator.FloraGenerator;
|
||||
import com.sk89q.worldedit.function.mask.MaskIntersection;
|
||||
import com.sk89q.worldedit.function.mask.ExistingBlockMask;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.function.mask.NoiseFilter2D;
|
||||
import com.sk89q.worldedit.function.visitor.RegionVisitor;
|
||||
import com.sk89q.worldedit.function.mask.SolidBlockMask;
|
||||
import com.sk89q.worldedit.function.operation.Operations;
|
||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
@ -63,6 +69,8 @@ import com.sk89q.worldedit.regions.CuboidRegion;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
import com.sk89q.worldedit.regions.RegionOperationException;
|
||||
import com.sk89q.worldedit.regions.Regions;
|
||||
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.ALL;
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
import com.sk89q.worldedit.util.TreeGenerator.TreeType;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
@ -81,10 +89,7 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.sk89q.worldedit.command.MethodCommands.getArguments;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.ALL;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.ORIENTATION_REGION;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
||||
import static com.sk89q.worldedit.internal.command.CommandUtil.checkCommandArgument;
|
||||
@ -98,16 +103,35 @@ import static com.sk89q.worldedit.regions.Regions.minimumBlockY;
|
||||
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
|
||||
public class RegionCommands {
|
||||
|
||||
private final WorldEdit worldEdit;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*
|
||||
* @param worldEdit reference to WorldEdit
|
||||
*/
|
||||
public RegionCommands(WorldEdit worldEdit) {
|
||||
checkNotNull(worldEdit);
|
||||
this.worldEdit = worldEdit;
|
||||
public RegionCommands() {
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "/set",
|
||||
desc = "Sets all the blocks in the region"
|
||||
)
|
||||
@CommandPermissions("worldedit.region.set")
|
||||
@Logging(REGION)
|
||||
public int set(Actor actor, EditSession editSession,
|
||||
@Selection Region region,
|
||||
@Arg(desc = "The pattern of blocks to set")
|
||||
Pattern pattern) {
|
||||
RegionFunction set = new BlockReplace(editSession, pattern);
|
||||
RegionVisitor visitor = new RegionVisitor(region, set);
|
||||
|
||||
Operations.completeBlindly(visitor);
|
||||
List<String> messages = Lists.newArrayList();
|
||||
visitor.addStatusMessages(messages);
|
||||
if (messages.isEmpty()) {
|
||||
actor.print("Operation completed.");
|
||||
} else {
|
||||
actor.print("Operation completed (" + Joiner.on(", ").join(messages) + ").");
|
||||
}
|
||||
|
||||
return visitor.getAffected();
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -254,7 +278,7 @@ public class RegionCommands {
|
||||
@Selection Region region,
|
||||
@Arg(desc = "The pattern of blocks to place")
|
||||
Pattern pattern,
|
||||
@Range(min = 1) @Arg(desc = "The thickness of the line", def = "0")
|
||||
@Arg(desc = "The thickness of the line", def = "0")
|
||||
int thickness,
|
||||
@Switch(name = 'h', desc = "Generate only a shell")
|
||||
boolean shell) throws WorldEditException {
|
||||
@ -269,7 +293,7 @@ public class RegionCommands {
|
||||
BlockVector3 pos2 = cuboidregion.getPos2();
|
||||
int blocksChanged = editSession.drawLine(pattern, pos1, pos2, thickness, !shell);
|
||||
|
||||
BBC.VISITOR_BLOCK.send(actor, blocksChanged);
|
||||
actor.print(blocksChanged + " block(s) have been changed.");
|
||||
return blocksChanged;
|
||||
}
|
||||
|
||||
@ -290,7 +314,7 @@ public class RegionCommands {
|
||||
boolean shell, InjectedValueAccess context) throws WorldEditException {
|
||||
if (!(region instanceof ConvexPolyhedralRegion)) {
|
||||
actor.printError("//curve only works with convex polyhedral selections");
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
checkCommandArgument(thickness >= 0, "Thickness must be >= 0");
|
||||
|
||||
@ -300,7 +324,7 @@ public class RegionCommands {
|
||||
|
||||
int blocksChanged = editSession.drawSpline(pattern, vectors, 0, 0, 0, 10, thickness, !shell);
|
||||
|
||||
BBC.VISITOR_BLOCK.send(actor, blocksChanged);
|
||||
actor.print(blocksChanged + " block(s) have been changed.");
|
||||
}, getArguments(context), region, context);
|
||||
}
|
||||
|
||||
@ -321,14 +345,8 @@ public class RegionCommands {
|
||||
}
|
||||
Mask finalFrom = from;
|
||||
actor.checkConfirmationRegion(() -> {
|
||||
int affected = editSession.replaceBlocks(region, finalFrom, to);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
if (!actor.hasPermission("fawe.tips")) {
|
||||
BBC.TIP_REPLACE_ID
|
||||
.or(BBC.TIP_REPLACE_LIGHT, BBC.TIP_REPLACE_MARKER, BBC.TIP_TAB_COMPLETE,
|
||||
BBC.TIP_REPLACE_REGEX, BBC.TIP_REPLACE_REGEX_2, BBC.TIP_REPLACE_REGEX_3,
|
||||
BBC.TIP_REPLACE_REGEX_4, BBC.TIP_REPLACE_REGEX_5).send(actor);
|
||||
}
|
||||
int affected = editSession.replaceBlocks(region, finalFrom, to);
|
||||
actor.print(affected + " block(s) have been replaced.");
|
||||
}, getArguments(context), region, context);
|
||||
}
|
||||
|
||||
@ -342,8 +360,8 @@ public class RegionCommands {
|
||||
@Arg(desc = "The pattern of blocks to overlay")
|
||||
Pattern pattern, InjectedValueAccess context) throws WorldEditException {
|
||||
actor.checkConfirmationRegion(() -> {
|
||||
int affected = editSession.overlayCuboidBlocks(region, pattern);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
int affected = editSession.overlayCuboidBlocks(region, pattern);
|
||||
actor.print(affected + " block(s) have been overlaid.");
|
||||
}, getArguments(context), region, context);
|
||||
}
|
||||
|
||||
@ -380,11 +398,12 @@ public class RegionCommands {
|
||||
)
|
||||
@Logging(REGION)
|
||||
@CommandPermissions("worldedit.region.center")
|
||||
public void center(Actor actor, EditSession editSession, @Selection Region region,
|
||||
public int center(Actor actor, EditSession editSession, @Selection Region region,
|
||||
@Arg(desc = "The pattern of blocks to set")
|
||||
Pattern pattern) throws WorldEditException {
|
||||
int affected = editSession.center(region, pattern);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print("Center set (" + affected + " block(s) changed)");
|
||||
return affected;
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -396,7 +415,7 @@ public class RegionCommands {
|
||||
public void naturalize(Actor actor, EditSession editSession, @Selection Region region, InjectedValueAccess context) throws WorldEditException {
|
||||
actor.checkConfirmationRegion(() -> {
|
||||
int affected = editSession.naturalizeCuboidBlocks(region);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been made to look more natural.");
|
||||
}, getArguments(context), region, context);
|
||||
}
|
||||
|
||||
@ -411,7 +430,7 @@ public class RegionCommands {
|
||||
Pattern pattern, InjectedValueAccess context) throws WorldEditException {
|
||||
actor.checkConfirmationRegion(() -> {
|
||||
int affected = editSession.makeWalls(region, pattern);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been changed.");
|
||||
}, getArguments(context), region, context);
|
||||
}
|
||||
|
||||
@ -427,7 +446,7 @@ public class RegionCommands {
|
||||
Pattern pattern, InjectedValueAccess context) throws WorldEditException {
|
||||
actor.checkConfirmationRegion(() -> {
|
||||
int affected = editSession.makeCuboidFaces(region, pattern);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been changed.");
|
||||
}, getArguments(context), region, context);
|
||||
}
|
||||
|
||||
@ -456,7 +475,7 @@ public class RegionCommands {
|
||||
HeightMap heightMap = new HeightMap(editSession, region, mask, snow);
|
||||
HeightMapFilter filter = new HeightMapFilter(new GaussianKernel(5, 1.0));
|
||||
int affected = heightMap.applyFilter(filter, iterations);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print("Terrain's height map smoothed. " + affected + " block(s) changed.");
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -522,8 +541,20 @@ public class RegionCommands {
|
||||
boolean copyBiomes,
|
||||
InjectedValueAccess context) throws WorldEditException {
|
||||
checkCommandArgument(count >= 1, "Count must be >= 1");
|
||||
|
||||
Mask combinedMask;
|
||||
if (ignoreAirBlocks) {
|
||||
if (mask == null) {
|
||||
combinedMask = new ExistingBlockMask(editSession);
|
||||
} else {
|
||||
combinedMask = new MaskIntersection(mask, new ExistingBlockMask(editSession));
|
||||
}
|
||||
} else {
|
||||
combinedMask = mask;
|
||||
}
|
||||
|
||||
actor.checkConfirmationRegion(() -> {
|
||||
int affected = editSession.moveRegion(region, direction, count, !ignoreAirBlocks, !skipEntities, copyBiomes, replace);
|
||||
int affected = editSession.moveRegion(region, direction, count, !ignoreAirBlocks, !skipEntities, copyBiomes, combinedMask, replace);
|
||||
|
||||
if (moveSelection) {
|
||||
try {
|
||||
@ -583,17 +614,29 @@ public class RegionCommands {
|
||||
@ArgFlag(name = 'm', desc = "Source mask", def="")
|
||||
Mask sourceMask,
|
||||
InjectedValueAccess context) throws WorldEditException {
|
||||
|
||||
Mask combinedMask;
|
||||
if (ignoreAirBlocks) {
|
||||
if (mask == null) {
|
||||
combinedMask = new ExistingBlockMask(editSession);
|
||||
} else {
|
||||
combinedMask = new MaskIntersection(mask, new ExistingBlockMask(editSession));
|
||||
}
|
||||
} else {
|
||||
combinedMask = mask;
|
||||
}
|
||||
|
||||
actor.checkConfirmationStack(() -> {
|
||||
if (sourceMask != null) {
|
||||
editSession.addSourceMask(sourceMask);
|
||||
}
|
||||
int affected = editSession.stackCuboidRegion(region, direction, count, !ignoreAirBlocks, !skipEntities, copyBiomes);
|
||||
int affected = editSession.stackCuboidRegion(region, direction, count, !skipEntities, copyBiomes, combinedMask);
|
||||
|
||||
if (moveSelection) {
|
||||
try {
|
||||
final BlockVector3 size = region.getMaximumPoint().subtract(region.getMinimumPoint());
|
||||
final BlockVector3 size = region.getMaximumPoint().subtract(region.getMinimumPoint()).add(1, 1, 1);
|
||||
|
||||
final BlockVector3 shiftVector = direction.toVector3().multiply(count * (Math.abs(direction.dot(size)) + 1)).toBlockPoint();
|
||||
final BlockVector3 shiftVector = direction.multiply(size).multiply(count);
|
||||
region.shift(shiftVector);
|
||||
|
||||
session.getRegionSelector(world).learnChanges();
|
||||
@ -652,7 +695,7 @@ public class RegionCommands {
|
||||
if (actor instanceof Player) {
|
||||
((Player) actor).findFreePosition();
|
||||
}
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been deformed.");
|
||||
} catch (ExpressionException e) {
|
||||
actor.printError(e.getMessage());
|
||||
}
|
||||
@ -718,7 +761,7 @@ public class RegionCommands {
|
||||
Mask finalMask = mask == null ? new SolidBlockMask(editSession) : mask;
|
||||
actor.checkConfirmationRegion(() -> {
|
||||
int affected = editSession.hollowOutRegion(region, thickness, pattern, finalMask);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been changed.");
|
||||
}, getArguments(context), region, context);
|
||||
}
|
||||
|
||||
@ -735,7 +778,7 @@ public class RegionCommands {
|
||||
double density) throws WorldEditException {
|
||||
checkCommandArgument(0 <= density && density <= 100, "Density must be in [0, 100]");
|
||||
int affected = editSession.makeForest(region, density / 100, type);
|
||||
BBC.COMMAND_TREE.send(actor, affected);
|
||||
actor.print(affected + " trees created.");
|
||||
return affected;
|
||||
}
|
||||
|
||||
@ -756,7 +799,7 @@ public class RegionCommands {
|
||||
visitor.setMask(new NoiseFilter2D(new RandomNoise(), density / 100));
|
||||
Operations.completeLegacy(visitor);
|
||||
|
||||
BBC.COMMAND_FLORA.send(actor, ground.getAffected());
|
||||
actor.print(affected + " flora created.");
|
||||
}, "/flora", region, context);
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import static com.boydti.fawe.util.ReflectionUtils.as;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.boydti.fawe.config.BBC;
|
||||
@ -41,6 +40,7 @@ import com.sk89q.worldedit.command.argument.Arguments;
|
||||
import com.sk89q.worldedit.command.util.AsyncCommandBuilder;
|
||||
import com.sk89q.worldedit.command.util.CommandPermissions;
|
||||
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
|
||||
import com.sk89q.worldedit.command.util.WorldEditAsyncCommandBuilder;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.event.extent.ActorSaveClipboardEvent;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
@ -60,6 +60,7 @@ import com.sk89q.worldedit.util.formatting.text.Component;
|
||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||
import com.sk89q.worldedit.util.formatting.text.event.ClickEvent;
|
||||
import com.sk89q.worldedit.util.formatting.text.event.HoverEvent;
|
||||
import com.sk89q.worldedit.util.formatting.component.CodeFormat;
|
||||
import com.sk89q.worldedit.util.formatting.text.format.TextColor;
|
||||
import com.sk89q.worldedit.util.io.Closer;
|
||||
import com.sk89q.worldedit.util.io.file.FilenameException;
|
||||
@ -70,6 +71,8 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
@ -843,6 +846,24 @@ public class SchematicCommands {
|
||||
return fileList;
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "delete",
|
||||
aliases = {"d"},
|
||||
desc = "Delete a saved schematic"
|
||||
)
|
||||
@CommandPermissions("worldedit.schematic.delete")
|
||||
public void delete(Actor actor,
|
||||
@Arg(desc = "File name.")
|
||||
String filename) throws WorldEditException {
|
||||
LocalConfiguration config = worldEdit.getConfiguration();
|
||||
File dir = worldEdit.getWorkingDirectoryFile(config.saveDir);
|
||||
|
||||
File f = worldEdit.getSafeOpenFile(actor instanceof Player ? ((Player) actor) : null,
|
||||
dir, filename, "schematic", ClipboardFormats.getFileExtensionArray());
|
||||
|
||||
if (!f.exists()) {
|
||||
actor.printError("Schematic " + filename + " does not exist!");
|
||||
return;
|
||||
private boolean delete(File file) {
|
||||
if (file.delete()) {
|
||||
new File(file.getParentFile(), "." + file.getName() + ".cached").delete();
|
||||
|
@ -18,8 +18,6 @@
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.ALL;
|
||||
|
||||
import com.boydti.fawe.config.BBC;
|
||||
@ -39,6 +37,8 @@ import org.enginehub.piston.annotation.CommandContainer;
|
||||
import org.enginehub.piston.annotation.param.Arg;
|
||||
import org.enginehub.piston.inject.InjectedValueAccess;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
/**
|
||||
* Commands related to scripting.
|
||||
@ -68,8 +68,8 @@ public class ScriptingCommands {
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "cs",
|
||||
desc = "Execute a CraftScript"
|
||||
name = "cs",
|
||||
desc = "Execute a CraftScript"
|
||||
)
|
||||
@CommandPermissions("worldedit.scripting.execute")
|
||||
@Logging(ALL)
|
||||
@ -77,9 +77,9 @@ public class ScriptingCommands {
|
||||
@Arg(desc = "Filename of the CraftScript to load")
|
||||
String filename,
|
||||
@Arg(desc = "Arguments to the CraftScript", def = "", variable = true)
|
||||
List<String> commandStr) throws WorldEditException {
|
||||
List<String> args) throws WorldEditException {
|
||||
if (!player.hasPermission("worldedit.scripting.execute." + filename)) {
|
||||
player.printError(BBC.SCRIPTING_NO_PERM.s());
|
||||
player.printError("You don't have permission to use that script.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -88,19 +88,19 @@ public class ScriptingCommands {
|
||||
File dir = worldEdit.getWorkingDirectoryFile(worldEdit.getConfiguration().scriptsDir);
|
||||
File f = worldEdit.getSafeOpenFile(player, dir, filename, "js", "js");
|
||||
|
||||
worldEdit.runScript(player, f, Stream.concat(Stream.of(filename), commandStr.stream())
|
||||
.toArray(String[]::new));
|
||||
worldEdit.runScript(player, f, Stream.concat(Stream.of(filename), args.stream())
|
||||
.toArray(String[]::new));
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = ".s",
|
||||
desc = "Execute last CraftScript"
|
||||
name = ".s",
|
||||
desc = "Execute last CraftScript"
|
||||
)
|
||||
@CommandPermissions("worldedit.scripting.execute")
|
||||
@Logging(ALL)
|
||||
public void executeLast(Player player, LocalSession session,
|
||||
@Arg(desc = "Arguments to the CraftScript", def = "", variable = true)
|
||||
List<String> commandStr) throws WorldEditException {
|
||||
List<String> args) throws WorldEditException {
|
||||
|
||||
String lastScript = session.getLastScript();
|
||||
|
||||
@ -117,7 +117,7 @@ public class ScriptingCommands {
|
||||
File dir = worldEdit.getWorkingDirectoryFile(worldEdit.getConfiguration().scriptsDir);
|
||||
File f = worldEdit.getSafeOpenFile(player, dir, lastScript, "js", "js");
|
||||
|
||||
worldEdit.runScript(player, f, Stream.concat(Stream.of(lastScript), commandStr.stream())
|
||||
.toArray(String[]::new));
|
||||
worldEdit.runScript(player, f, Stream.concat(Stream.of(lastScript), args.stream())
|
||||
.toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.POSITION;
|
||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
||||
|
||||
@ -44,6 +46,7 @@ import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.extension.platform.Locatable;
|
||||
import com.sk89q.worldedit.extension.platform.permission.ActorSelectorLimits;
|
||||
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
|
||||
import com.sk89q.worldedit.command.util.WorldEditAsyncCommandBuilder;
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.internal.annotation.Direction;
|
||||
@ -77,12 +80,19 @@ import com.sk89q.worldedit.world.storage.ChunkStore;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import com.sk89q.worldedit.util.formatting.component.PaginationBox;
|
||||
import com.sk89q.worldedit.util.formatting.component.InvalidComponentException;
|
||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||
import java.util.Optional;
|
||||
import com.sk89q.worldedit.util.formatting.text.event.HoverEvent;
|
||||
import java.util.stream.Stream;
|
||||
import org.enginehub.piston.annotation.Command;
|
||||
import org.enginehub.piston.annotation.CommandContainer;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
import org.enginehub.piston.annotation.param.Arg;
|
||||
import org.enginehub.piston.annotation.param.Switch;
|
||||
import org.enginehub.piston.annotation.param.ArgFlag;
|
||||
import org.enginehub.piston.exception.StopExecutionException;
|
||||
|
||||
/**
|
||||
* Selection commands.
|
||||
@ -98,7 +108,6 @@ public class SelectionCommands {
|
||||
|
||||
@Command(
|
||||
name = "/pos1",
|
||||
aliases = "/1",
|
||||
desc = "Set position 1"
|
||||
)
|
||||
@Logging(POSITION)
|
||||
@ -116,18 +125,17 @@ public class SelectionCommands {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session.getRegionSelector(world).selectPrimary(pos.toBlockPoint(), ActorSelectorLimits.forActor(actor))) {
|
||||
actor.printError(BBC.SELECTOR_ALREADY_SET.s());
|
||||
if (!session.getRegionSelector(world).selectPrimary(pos.toVector().toBlockPoint(), ActorSelectorLimits.forActor(actor))) {
|
||||
actor.printError("Position already set.");
|
||||
return;
|
||||
}
|
||||
|
||||
session.getRegionSelector(world)
|
||||
.explainPrimarySelection(actor, session, pos.toBlockPoint());
|
||||
.explainPrimarySelection(actor, session, pos.toVector().toBlockPoint());
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "/pos2",
|
||||
aliases = "/2",
|
||||
desc = "Set position 2"
|
||||
)
|
||||
@Logging(POSITION)
|
||||
@ -145,13 +153,13 @@ public class SelectionCommands {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session.getRegionSelector(world).selectSecondary(pos.toBlockPoint(), ActorSelectorLimits.forActor(actor))) {
|
||||
actor.printError(BBC.SELECTOR_ALREADY_SET.s());
|
||||
if (!session.getRegionSelector(world).selectSecondary(pos.toVector().toBlockPoint(), ActorSelectorLimits.forActor(actor))) {
|
||||
actor.printError("Position already set.");
|
||||
return;
|
||||
}
|
||||
|
||||
session.getRegionSelector(world)
|
||||
.explainSecondarySelection(actor, session, pos.toBlockPoint());
|
||||
.explainSecondarySelection(actor, session, pos.toVector().toBlockPoint());
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -204,7 +212,7 @@ public class SelectionCommands {
|
||||
)
|
||||
@Logging(POSITION)
|
||||
@CommandPermissions("worldedit.selection.chunk")
|
||||
public void chunk(Player player, LocalSession session,
|
||||
public void chunk(Actor actor, World world, LocalSession session,
|
||||
@Arg(desc = "The chunk to select", def = "")
|
||||
BlockVector2 coordinates,
|
||||
@Switch(name = 's', desc = "Expand your selection to encompass all chunks that are part of it")
|
||||
@ -213,7 +221,6 @@ public class SelectionCommands {
|
||||
boolean useChunkCoordinates) throws WorldEditException {
|
||||
final BlockVector3 min;
|
||||
final BlockVector3 max;
|
||||
final World world = player.getWorld();
|
||||
if (expandSelection) {
|
||||
Region region = session.getSelection(world);
|
||||
|
||||
@ -223,7 +230,9 @@ public class SelectionCommands {
|
||||
min = BlockVector3.at(min2D.getBlockX() * 16, 0, min2D.getBlockZ() * 16);
|
||||
max = BlockVector3.at(max2D.getBlockX() * 16 + 15, world.getMaxY(), max2D.getBlockZ() * 16 + 15);
|
||||
|
||||
BBC.SELECTION_CHUNKS.send(player, min2D.getBlockX() + ", " + min2D.getBlockZ(), max2D.getBlockX() + ", " + max2D.getBlockZ());
|
||||
actor.print("Chunks selected: ("
|
||||
+ min2D.getBlockX() + ", " + min2D.getBlockZ() + ") - ("
|
||||
+ max2D.getBlockX() + ", " + max2D.getBlockZ() + ")");
|
||||
} else {
|
||||
final BlockVector2 min2D;
|
||||
if (coordinates != null) {
|
||||
@ -233,13 +242,18 @@ public class SelectionCommands {
|
||||
: ChunkStore.toChunk(coordinates.toBlockVector3());
|
||||
} else {
|
||||
// use player loc
|
||||
min2D = ChunkStore.toChunk(player.getBlockLocation().toBlockPoint());
|
||||
if (actor instanceof Locatable) {
|
||||
min2D = ChunkStore.toChunk(((Locatable) actor).getBlockLocation().toVector().toBlockPoint());
|
||||
} else {
|
||||
throw new StopExecutionException(TextComponent.of("A player or coordinates are required."));
|
||||
}
|
||||
}
|
||||
|
||||
min = BlockVector3.at(min2D.getBlockX() * 16, 0, min2D.getBlockZ() * 16);
|
||||
max = min.add(15, world.getMaxY(), 15);
|
||||
|
||||
BBC.SELECTION_CHUNK.send(player, min2D.getBlockX() + ", " + min2D.getBlockZ());
|
||||
actor.print("Chunk selected: "
|
||||
+ min2D.getBlockX() + ", " + min2D.getBlockZ());
|
||||
}
|
||||
|
||||
final CuboidRegionSelector selector;
|
||||
@ -248,11 +262,11 @@ public class SelectionCommands {
|
||||
} else {
|
||||
selector = new CuboidRegionSelector(world);
|
||||
}
|
||||
selector.selectPrimary(min, ActorSelectorLimits.forActor(player));
|
||||
selector.selectSecondary(max, ActorSelectorLimits.forActor(player));
|
||||
selector.selectPrimary(min, ActorSelectorLimits.forActor(actor));
|
||||
selector.selectSecondary(max, ActorSelectorLimits.forActor(actor));
|
||||
session.setRegionSelector(world, selector);
|
||||
|
||||
session.dispatchCUISelection(player);
|
||||
session.dispatchCUISelection(actor);
|
||||
|
||||
}
|
||||
|
||||
@ -333,8 +347,7 @@ public class SelectionCommands {
|
||||
|
||||
session.getRegionSelector(world).explainRegionAdjust(actor, session);
|
||||
|
||||
|
||||
BBC.SELECTION_CONTRACT.send(actor, (oldSize - newSize));
|
||||
actor.print("Region contracted " + (oldSize - newSize) + " blocks.");
|
||||
} catch (RegionOperationException e) {
|
||||
actor.printError(e.getMessage());
|
||||
}
|
||||
@ -363,7 +376,7 @@ public class SelectionCommands {
|
||||
|
||||
session.getRegionSelector(world).explainRegionAdjust(actor, session);
|
||||
|
||||
actor.print(BBC.SELECTION_SHIFT.s());
|
||||
actor.print("Region shifted.");
|
||||
} catch (RegionOperationException e) {
|
||||
actor.printError(e.getMessage());
|
||||
}
|
||||
@ -386,7 +399,7 @@ public class SelectionCommands {
|
||||
region.expand(getChangesForEachDir(amount, onlyHorizontal, onlyVertical));
|
||||
session.getRegionSelector(world).learnChanges();
|
||||
session.getRegionSelector(world).explainRegionAdjust(actor, session);
|
||||
actor.print(BBC.SELECTION_OUTSET.s());
|
||||
actor.print("Region outset.");
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -496,11 +509,11 @@ public class SelectionCommands {
|
||||
desc = "Counts the number of blocks matching a mask"
|
||||
)
|
||||
@CommandPermissions("worldedit.analysis.count")
|
||||
public void count(Player player, LocalSession session, EditSession editSession,
|
||||
public void count(Actor actor, World world, LocalSession session, EditSession editSession,
|
||||
@Arg(desc = "The mask of blocks to match")
|
||||
Mask mask) throws WorldEditException {
|
||||
int count = editSession.countBlocks(session.getSelection(player.getWorld()), mask);
|
||||
BBC.SELECTION_COUNT.send(player, count);
|
||||
int count = editSession.countBlocks(session.getSelection(world), mask);
|
||||
actor.print("Counted: " + count);
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -531,7 +544,7 @@ public class SelectionCommands {
|
||||
|
||||
|
||||
if (distribution.isEmpty()) { // *Should* always be false
|
||||
player.printError("No blocks counted.");
|
||||
actor.printError("No blocks counted.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -549,6 +562,65 @@ public class SelectionCommands {
|
||||
}
|
||||
}
|
||||
|
||||
private static class BlockDistributionResult extends PaginationBox {
|
||||
|
||||
private final List<Countable<BlockState>> distribution;
|
||||
private final int totalBlocks;
|
||||
private final boolean separateStates;
|
||||
|
||||
BlockDistributionResult(List<Countable<BlockState>> distribution, boolean separateStates) {
|
||||
super("Block Distribution", "//distr -p %page%" + (separateStates ? " -d" : ""));
|
||||
this.distribution = distribution;
|
||||
// note: doing things like region.getArea is inaccurate for non-cuboids.
|
||||
this.totalBlocks = distribution.stream().mapToInt(Countable::getAmount).sum();
|
||||
this.separateStates = separateStates;
|
||||
setComponentsPerPage(7);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getComponent(int number) {
|
||||
Countable<BlockState> c = distribution.get(number);
|
||||
TextComponent.Builder line = TextComponent.builder();
|
||||
|
||||
final int count = c.getAmount();
|
||||
|
||||
final double perc = count / (double) totalBlocks * 100;
|
||||
final int maxDigits = (int) (Math.log10(totalBlocks) + 1);
|
||||
final int curDigits = (int) (Math.log10(count) + 1);
|
||||
line.append(String.format("%s%.3f%% ", perc < 10 ? " " : "", perc), TextColor.GOLD);
|
||||
final int space = maxDigits - curDigits;
|
||||
String pad = Strings.repeat(" ", space == 0 ? 2 : 2 * space + 1);
|
||||
line.append(String.format("%s%s", count, pad), TextColor.YELLOW);
|
||||
|
||||
final BlockState state = c.getID();
|
||||
final BlockType blockType = state.getBlockType();
|
||||
TextComponent blockName = TextComponent.of(blockType.getName(), TextColor.LIGHT_PURPLE);
|
||||
TextComponent toolTip;
|
||||
if (separateStates && state != blockType.getDefaultState()) {
|
||||
toolTip = TextComponent.of(state.getAsString(), TextColor.GRAY);
|
||||
blockName = blockName.append(TextComponent.of("*", TextColor.LIGHT_PURPLE));
|
||||
} else {
|
||||
toolTip = TextComponent.of(blockType.getId(), TextColor.GRAY);
|
||||
}
|
||||
blockName = blockName.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, toolTip));
|
||||
line.append(blockName);
|
||||
|
||||
return line.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getComponentsSize() {
|
||||
return distribution.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component create(int page) throws InvalidComponentException {
|
||||
super.getContents().append(TextComponent.of("Total Block Count: " + totalBlocks, TextColor.GRAY))
|
||||
.append(TextComponent.newline());
|
||||
return super.create(page);
|
||||
}
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "/sel",
|
||||
aliases = { ";", "/desel", "/deselect" },
|
||||
|
@ -76,7 +76,7 @@ public class SnapshotCommands {
|
||||
LocalConfiguration config = we.getConfiguration();
|
||||
|
||||
if (config.snapshotRepo == null) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_CONFIGURED.s());
|
||||
actor.printError("Snapshot/backup restore is not configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ public class SnapshotCommands {
|
||||
if (!snapshots.isEmpty()) {
|
||||
actor.print(new SnapshotListBox(world.getName(), snapshots).create(page));
|
||||
} else {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_AVAILABLE.s());
|
||||
actor.printError("No snapshots are available. See console for details.");
|
||||
|
||||
// Okay, let's toss some debugging information!
|
||||
File dir = config.snapshotRepo.getDirectory();
|
||||
@ -101,7 +101,7 @@ public class SnapshotCommands {
|
||||
}
|
||||
}
|
||||
} catch (MissingWorldException ex) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_FOUND_WORLD.s());
|
||||
actor.printError("No snapshots were found for this world.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ public class SnapshotCommands {
|
||||
LocalConfiguration config = we.getConfiguration();
|
||||
|
||||
if (config.snapshotRepo == null) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_CONFIGURED.s());
|
||||
actor.printError("Snapshot/backup restore is not configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -128,19 +128,19 @@ public class SnapshotCommands {
|
||||
|
||||
if (snapshot != null) {
|
||||
session.setSnapshot(null);
|
||||
actor.print(BBC.SNAPSHOT_NEWEST.s());
|
||||
actor.print("Now using newest snapshot.");
|
||||
} else {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_FOUND.s());
|
||||
actor.printError("No snapshots were found.");
|
||||
}
|
||||
} catch (MissingWorldException ex) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_FOUND_WORLD.s());
|
||||
actor.printError("No snapshots were found for this world.");
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
session.setSnapshot(config.snapshotRepo.getSnapshot(name));
|
||||
BBC.SNAPSHOT_SET.send(actor, name);
|
||||
actor.print("Snapshot set to: " + name);
|
||||
} catch (InvalidSnapshotException e) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_AVAILABLE.s());
|
||||
actor.printError("That snapshot does not exist or is not available.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -156,12 +156,12 @@ public class SnapshotCommands {
|
||||
LocalConfiguration config = we.getConfiguration();
|
||||
|
||||
if (config.snapshotRepo == null) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_CONFIGURED.s());
|
||||
actor.printError("Snapshot/backup restore is not configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 1) {
|
||||
actor.printError(BBC.SNAPSHOT_INVALID_INDEX.s());
|
||||
actor.printError("Invalid index, must be equal or higher then 1.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -173,13 +173,13 @@ public class SnapshotCommands {
|
||||
}
|
||||
Snapshot snapshot = snapshots.get(index - 1);
|
||||
if (snapshot == null) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_AVAILABLE.s());
|
||||
actor.printError("That snapshot does not exist or is not available.");
|
||||
return;
|
||||
}
|
||||
session.setSnapshot(snapshot);
|
||||
BBC.SNAPSHOT_SET.send(actor, snapshot.getName());
|
||||
actor.print("Snapshot set to: " + snapshot.getName());
|
||||
} catch (MissingWorldException e) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_FOUND_WORLD.s());
|
||||
actor.printError("No snapshots were found for this world.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,7 +195,7 @@ public class SnapshotCommands {
|
||||
LocalConfiguration config = we.getConfiguration();
|
||||
|
||||
if (config.snapshotRepo == null) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_CONFIGURED.s());
|
||||
actor.printError("Snapshot/backup restore is not configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -207,10 +207,10 @@ public class SnapshotCommands {
|
||||
+ dateFormat.withZone(session.getTimeZone()).format(date) + ".");
|
||||
} else {
|
||||
session.setSnapshot(snapshot);
|
||||
BBC.SNAPSHOT_SET.send(actor, snapshot.getName());
|
||||
actor.print("Snapshot set to: " + snapshot.getName());
|
||||
}
|
||||
} catch (MissingWorldException ex) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_FOUND_WORLD.s());
|
||||
actor.printError("No snapshots were found for this world.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,7 +226,7 @@ public class SnapshotCommands {
|
||||
LocalConfiguration config = we.getConfiguration();
|
||||
|
||||
if (config.snapshotRepo == null) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_CONFIGURED.s());
|
||||
actor.printError("Snapshot/backup restore is not configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -240,7 +240,7 @@ public class SnapshotCommands {
|
||||
actor.print("Snapshot set to: " + snapshot.getName());
|
||||
}
|
||||
} catch (MissingWorldException ex) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_FOUND_WORLD.s());
|
||||
actor.printError("No snapshots were found for this world.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ public class SnapshotUtilCommands {
|
||||
LocalConfiguration config = we.getConfiguration();
|
||||
|
||||
if (config.snapshotRepo == null) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_CONFIGURED.s());
|
||||
actor.printError("Snapshot/backup restore is not configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ public class SnapshotUtilCommands {
|
||||
try {
|
||||
snapshot = config.snapshotRepo.getSnapshot(snapshotName);
|
||||
} catch (InvalidSnapshotException e) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_AVAILABLE.s());
|
||||
actor.printError("That snapshot does not exist or is not available.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@ -92,7 +92,7 @@ public class SnapshotUtilCommands {
|
||||
snapshot = config.snapshotRepo.getDefaultSnapshot(world.getName());
|
||||
|
||||
if (snapshot == null) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_AVAILABLE.s());
|
||||
actor.printError("No snapshots were found. See console for details.");
|
||||
|
||||
// Okay, let's toss some debugging information!
|
||||
File dir = config.snapshotRepo.getDirectory();
|
||||
@ -109,15 +109,21 @@ public class SnapshotUtilCommands {
|
||||
return;
|
||||
}
|
||||
} catch (MissingWorldException ex) {
|
||||
actor.printError(BBC.SNAPSHOT_NOT_FOUND_WORLD.s());
|
||||
actor.printError("No snapshots were found for this world.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ChunkStore chunkStore;
|
||||
|
||||
// Load chunk store
|
||||
try (ChunkStore chunkStore = snapshot.getChunkStore()) {
|
||||
BBC.SNAPSHOT_LOADED.send(actor, snapshot.getName());
|
||||
try {
|
||||
chunkStore = snapshot.getChunkStore();
|
||||
actor.print("Snapshot '" + snapshot.getName() + "' loaded; now restoring...");
|
||||
} catch (DataException | IOException e) {
|
||||
actor.printError("Failed to load snapshot: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore snapshot
|
||||
SnapshotRestore restore = new SnapshotRestore(chunkStore, editSession, region);
|
||||
@ -128,12 +134,12 @@ public class SnapshotUtilCommands {
|
||||
if (restore.hadTotalFailure()) {
|
||||
String error = restore.getLastErrorMessage();
|
||||
if (!restore.getMissingChunks().isEmpty()) {
|
||||
actor.printError(BBC.SNAPSHOT_ERROR_RESTORE.s());
|
||||
actor.printError("Chunks were not present in snapshot.");
|
||||
} else if (error != null) {
|
||||
actor.printError("Errors prevented any blocks from being restored.");
|
||||
actor.printError("Last error: " + error);
|
||||
} else {
|
||||
actor.printError(BBC.SNAPSHOT_ERROR_RESTORE_CHUNKS.s());
|
||||
actor.printError("No chunks could be loaded. (Bad archive?)");
|
||||
}
|
||||
} else {
|
||||
actor.print(String.format("Restored; %d "
|
||||
@ -143,6 +149,6 @@ public class SnapshotUtilCommands {
|
||||
}
|
||||
} catch (DataException | IOException e) {
|
||||
actor.printError("Failed to load snapshot: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.object.brush.InspectBrush;
|
||||
import com.sk89q.worldedit.LocalConfiguration;
|
||||
@ -28,6 +30,7 @@ import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.blocks.BaseItemStack;
|
||||
import com.sk89q.worldedit.command.tool.BlockDataCyler;
|
||||
import com.sk89q.worldedit.command.tool.BlockReplacer;
|
||||
import com.sk89q.worldedit.command.tool.InvalidToolBindException;
|
||||
import com.sk89q.worldedit.command.tool.DistanceWand;
|
||||
import com.sk89q.worldedit.command.tool.FloatingTreeRemover;
|
||||
import com.sk89q.worldedit.command.tool.FloodFillTool;
|
||||
@ -44,27 +47,95 @@ import com.sk89q.worldedit.util.HandSide;
|
||||
import com.sk89q.worldedit.util.TreeGenerator;
|
||||
import com.sk89q.worldedit.world.item.ItemType;
|
||||
import org.enginehub.piston.annotation.Command;
|
||||
import com.sk89q.worldedit.internal.command.CommandRegistrationHandler;
|
||||
import com.sk89q.worldedit.internal.command.CommandUtil;
|
||||
import org.enginehub.piston.annotation.CommandContainer;
|
||||
import org.enginehub.piston.annotation.param.Arg;
|
||||
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.enginehub.piston.CommandManager;
|
||||
import org.enginehub.piston.CommandManagerService;
|
||||
import org.enginehub.piston.CommandMetadata;
|
||||
import org.enginehub.piston.CommandParameters;
|
||||
import org.enginehub.piston.part.SubCommandPart;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
|
||||
public class ToolCommands {
|
||||
|
||||
public static void register(CommandRegistrationHandler registration,
|
||||
CommandManager commandManager,
|
||||
CommandManagerService commandManagerService,
|
||||
WorldEdit worldEdit) {
|
||||
// Collect the tool commands
|
||||
CommandManager collect = commandManagerService.newCommandManager();
|
||||
|
||||
registration.register(
|
||||
collect,
|
||||
ToolCommandsRegistration.builder(),
|
||||
new ToolCommands(worldEdit)
|
||||
);
|
||||
|
||||
// Register deprecated global commands
|
||||
Set<org.enginehub.piston.Command> commands = collect.getAllCommands()
|
||||
.collect(Collectors.toSet());
|
||||
for (org.enginehub.piston.Command command : commands) {
|
||||
if (command.getAliases().contains("unbind")) {
|
||||
// Don't register new /tool unbind alias
|
||||
command = command.toBuilder().aliases(
|
||||
Collections2.filter(command.getAliases(), alias -> !"unbind".equals(alias))
|
||||
).build();
|
||||
}
|
||||
commandManager.register(CommandUtil.deprecate(
|
||||
command, "Global tool names cause conflicts " +
|
||||
"and will be removed in WorldEdit 8", ToolCommands::asNonGlobal
|
||||
));
|
||||
}
|
||||
|
||||
// Remove aliases with / in them, since it doesn't make sense for sub-commands.
|
||||
Set<org.enginehub.piston.Command> nonGlobalCommands = commands.stream()
|
||||
.map(command ->
|
||||
command.toBuilder().aliases(
|
||||
Collections2.filter(command.getAliases(), alias -> !alias.startsWith("/"))
|
||||
).build()
|
||||
)
|
||||
.collect(Collectors.toSet());
|
||||
commandManager.register("tool", command -> {
|
||||
command.addPart(SubCommandPart.builder(
|
||||
TranslatableComponent.of("tool"),
|
||||
TextComponent.of("The tool to bind")
|
||||
)
|
||||
.withCommands(nonGlobalCommands)
|
||||
.required()
|
||||
.build());
|
||||
command.description(TextComponent.of("Binds a tool to the item in your hand"));
|
||||
});
|
||||
}
|
||||
|
||||
private static String asNonGlobal(org.enginehub.piston.Command oldCommand,
|
||||
CommandParameters oldParameters) {
|
||||
String name = Optional.ofNullable(oldParameters.getMetadata())
|
||||
.map(CommandMetadata::getCalledName)
|
||||
.filter(n -> !n.startsWith("/"))
|
||||
.orElseGet(oldCommand::getName);
|
||||
return "/tool " + name;
|
||||
}
|
||||
|
||||
static void setToolNone(Player player, LocalSession session, String type)
|
||||
throws InvalidToolBindException {
|
||||
session.setTool(player.getItemInHand(HandSide.MAIN_HAND).getType(), null);
|
||||
player.print(type + " unbound from your current item.");
|
||||
}
|
||||
private final WorldEdit we;
|
||||
|
||||
public ToolCommands(WorldEdit we) {
|
||||
this.we = we;
|
||||
}
|
||||
|
||||
// @Command(
|
||||
// name = "none",
|
||||
// desc = "Unbind a bound tool from your current item"
|
||||
// )
|
||||
// public void none(Player player, LocalSession session) throws WorldEditException {
|
||||
//
|
||||
// session.setTool(player.getItemInHand(HandSide.MAIN_HAND).getType(), null);
|
||||
// player.print("Tool unbound from your current item.");
|
||||
// }
|
||||
|
||||
@Command(
|
||||
name = "selwand",
|
||||
aliases = "/selwand",
|
||||
@ -74,7 +145,7 @@ public class ToolCommands {
|
||||
public void selwand(Player player, LocalSession session) throws WorldEditException {
|
||||
|
||||
final ItemType itemType = player.getItemInHand(HandSide.MAIN_HAND).getType();
|
||||
session.setTool(player, SelectionWand.INSTANCE);
|
||||
session.setTool(itemType, new SelectionWand());
|
||||
player.print("Selection wand bound to " + itemType.getName() + ".");
|
||||
}
|
||||
|
||||
@ -215,8 +286,17 @@ public class ToolCommands {
|
||||
Pattern secondary) throws WorldEditException {
|
||||
BaseItemStack itemStack = player.getItemInHand(HandSide.MAIN_HAND);
|
||||
|
||||
session.setTool(player, new LongRangeBuildTool(primary, secondary));
|
||||
BBC.TOOL_LRBUILD_BOUND.send(player, itemStack.getType().getName());
|
||||
BBC.TOOL_LRBUILD_INFO.send(player, secondary, primary);
|
||||
session.setTool(itemStack.getType(), new LongRangeBuildTool(primary, secondary));
|
||||
player.print("Long-range building tool bound to " + itemStack.getType().getName() + ".");
|
||||
String primaryName = "pattern";
|
||||
String secondaryName = "pattern";
|
||||
if (primary instanceof BlockStateHolder) {
|
||||
primaryName = ((BlockStateHolder<?>) primary).getBlockType().getName();
|
||||
}
|
||||
if (secondary instanceof BlockStateHolder) {
|
||||
secondaryName = ((BlockStateHolder<?>) secondary).getBlockType().getName();
|
||||
}
|
||||
player.print("Left-click set to " + primaryName + "; right-click set to "
|
||||
+ secondaryName + ".");
|
||||
}
|
||||
}
|
||||
|
@ -82,16 +82,16 @@ public class ToolUtilCommands {
|
||||
return;
|
||||
}
|
||||
if (maskOpt == null) {
|
||||
player.print(BBC.BRUSH_MASK_DISABLED.s());
|
||||
player.print("Brush mask disabled.");
|
||||
tool.setMask(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
BrushSettings settings = offHand ? tool.getOffHand() : tool.getContext();
|
||||
String lastArg = Iterables.getLast(CommandArgParser.spaceSplit(arguments.get())).getSubstring();
|
||||
settings.addSetting(BrushSettings.SettingType.MASK, lastArg);
|
||||
settings.setMask(maskOpt);
|
||||
tool.update();
|
||||
player.print(BBC.BRUSH_MASK.s());
|
||||
player.print("Brush mask set.");
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -110,7 +110,7 @@ public class ToolUtilCommands {
|
||||
if (tool == null) {
|
||||
player.print(BBC.BRUSH_NONE.s());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pattern == null) {
|
||||
player.print(BBC.BRUSH_MATERIAL.s());
|
||||
tool.setFill(null);
|
||||
@ -125,61 +125,29 @@ public class ToolUtilCommands {
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "range",
|
||||
desc = "Set the brush range"
|
||||
)
|
||||
name = "range",
|
||||
desc = "Set the brush range"
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.options.range")
|
||||
public void range(Player player, LocalSession session,
|
||||
@Arg(desc = "The range of the brush")
|
||||
int range) throws WorldEditException {
|
||||
range = Math.max(0, Math.min(256, range));
|
||||
BrushTool tool = session.getBrushTool(player, false);
|
||||
if (tool == null) {
|
||||
player.print(BBC.BRUSH_NONE.s());
|
||||
return;
|
||||
}
|
||||
tool.setRange(range);
|
||||
player.print(BBC.BRUSH_RANGE.s());
|
||||
int range) throws WorldEditException {
|
||||
session.getBrushTool(player, false).setRange(range);
|
||||
player.print("Brush range set.");
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "size",
|
||||
desc = "Set the brush size"
|
||||
name = "size",
|
||||
desc = "Set the brush size"
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.options.size")
|
||||
public void size(Player player, LocalSession session,
|
||||
@Arg(desc = "The size of the brush", def = "5")
|
||||
int size,
|
||||
@Switch(name = 'h', desc = "TODO")
|
||||
boolean offHand) throws WorldEditException {
|
||||
@Arg(desc = "The size of the brush")
|
||||
int size) throws WorldEditException {
|
||||
we.checkMaxBrushRadius(size);
|
||||
BrushTool tool = session.getBrushTool(player, false);
|
||||
if (tool == null) {
|
||||
player.print(BBC.BRUSH_NONE.s());
|
||||
return;
|
||||
}
|
||||
BrushSettings settings = offHand ? tool.getOffHand() : tool.getContext();
|
||||
settings.setSize(size);
|
||||
tool.update();
|
||||
player.print(BBC.BRUSH_SIZE.s());
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "tracemask",
|
||||
aliases = {"tarmask", "tm", "targetmask"},
|
||||
desc = "Set the mask used to stop tool traces"
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.options.tracemask")
|
||||
public void traceMask(Player player, LocalSession session,
|
||||
@Arg(desc = "The trace mask to set", def = "")
|
||||
Mask maskOpt) throws WorldEditException {
|
||||
BrushTool tool = session.getBrushTool(player, false);
|
||||
if (tool == null) {
|
||||
player.print(BBC.BRUSH_NONE.s());
|
||||
return;
|
||||
}
|
||||
tool.setTraceMask(maskOpt);
|
||||
BBC.BRUSH_TARGET_MASK_SET.send(player, maskOpt.toString());
|
||||
session.getBrushTool(player, false).setSize(size);
|
||||
player.print("Brush size set.");
|
||||
}
|
||||
|
||||
//todo none should be moved to the same class where it is in upstream
|
||||
@ -194,28 +162,42 @@ public class ToolUtilCommands {
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "/superpickaxe",
|
||||
aliases = {",", "/sp", "/pickaxe"},
|
||||
desc = "Toggle the super pickaxe function"
|
||||
name = "tracemask",
|
||||
aliases = {"tarmask", "tm", "targetmask"},
|
||||
desc = "Set the mask used to stop tool traces"
|
||||
)
|
||||
@CommandPermissions("worldedit.brush.options.tracemask")
|
||||
public void traceMask(Player player, LocalSession session,
|
||||
@Arg(desc = "The trace mask to set", def = "")
|
||||
Mask maskOpt) throws WorldEditException {
|
||||
session.getBrushTool(player, false).setTraceMask(maskOpt);
|
||||
if (maskOpt == null) {
|
||||
player.print("Trace mask disabled.");
|
||||
} else {
|
||||
player.print("Trace mask set.");
|
||||
}
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "/",
|
||||
aliases = { "," },
|
||||
desc = "Toggle the super pickaxe function"
|
||||
)
|
||||
@CommandPermissions("worldedit.superpickaxe")
|
||||
public void togglePickaxe(Player player, LocalSession session,
|
||||
@Arg(desc = "state", def = "on") String state) throws WorldEditException {
|
||||
if (session.hasSuperPickAxe()) {
|
||||
if ("on".equals(state)) {
|
||||
player.print(BBC.SUPERPICKAXE_ENABLED.s());
|
||||
@Arg(desc = "The new super pickaxe state", def = "")
|
||||
Boolean superPickaxe) {
|
||||
boolean hasSuperPickAxe = session.hasSuperPickAxe();
|
||||
if (superPickaxe != null && superPickaxe == hasSuperPickAxe) {
|
||||
player.printError("Super pickaxe already " + (superPickaxe ? "enabled" : "disabled") + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasSuperPickAxe) {
|
||||
session.disableSuperPickAxe();
|
||||
player.print(BBC.SUPERPICKAXE_DISABLED.s());
|
||||
player.print("Super pickaxe disabled.");
|
||||
} else {
|
||||
if ("off".equals(state)) {
|
||||
player.print(BBC.SUPERPICKAXE_DISABLED.s());
|
||||
return;
|
||||
}
|
||||
session.enableSuperPickAxe();
|
||||
player.print(BBC.SUPERPICKAXE_ENABLED.s());
|
||||
player.print("Super pickaxe enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,6 @@ import com.boydti.fawe.util.MainUtil;
|
||||
import com.boydti.fawe.util.image.ImageUtil;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.sk89q.minecraft.util.commands.CommandException;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.IncompleteRegionException;
|
||||
import com.sk89q.worldedit.LocalConfiguration;
|
||||
@ -46,21 +45,23 @@ import com.sk89q.worldedit.command.util.Logging;
|
||||
import com.sk89q.worldedit.command.util.PrintCommandHelp;
|
||||
import com.sk89q.worldedit.command.util.SkipQueue;
|
||||
import com.sk89q.worldedit.entity.Entity;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
|
||||
import com.sk89q.worldedit.function.EntityFunction;
|
||||
import com.sk89q.worldedit.function.mask.ExistingBlockMask;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.command.util.WorldEditAsyncCommandBuilder;
|
||||
import com.sk89q.worldedit.function.operation.Operations;
|
||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.function.mask.BlockTypeMask;
|
||||
import com.sk89q.worldedit.function.visitor.EntityVisitor;
|
||||
import com.sk89q.worldedit.internal.annotation.Direction;
|
||||
import com.sk89q.worldedit.internal.annotation.Range;
|
||||
import com.sk89q.worldedit.internal.expression.Expression;
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||
import com.sk89q.worldedit.regions.CylinderRegion;
|
||||
@ -77,7 +78,6 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
@ -99,7 +99,8 @@ import org.enginehub.piston.annotation.param.Switch;
|
||||
/**
|
||||
* Utility commands.
|
||||
*/
|
||||
@CommandContainer(superTypes = {
|
||||
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
|
||||
public class UtilityCommands {
|
||||
// CommandQueuedConditionGenerator.Registration.class,
|
||||
CommandPermissionsConditionGenerator.Registration.class // TODO NOT IMPLEMENTED - Piston doesn't seem to work with multiple conditions???
|
||||
})
|
||||
@ -193,6 +194,7 @@ public class UtilityCommands {
|
||||
@Command(
|
||||
name = "/fill",
|
||||
desc = "Fill a hole"
|
||||
|
||||
)
|
||||
@CommandPermissions("worldedit.fill")
|
||||
@Logging(PLACEMENT)
|
||||
@ -218,6 +220,8 @@ public class UtilityCommands {
|
||||
|
||||
/*
|
||||
@Command(
|
||||
name = "/fillr",
|
||||
desc = "Fill a hole recursively"
|
||||
name = "patterns",
|
||||
desc = "View help about patterns",
|
||||
descFooter = "Patterns determine what blocks are placed\n" +
|
||||
@ -296,10 +300,10 @@ public class UtilityCommands {
|
||||
public int fillr(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Arg(desc = "The blocks to fill with")
|
||||
Pattern pattern,
|
||||
@Range(min=1) @Arg(desc = "The radius to fill in")
|
||||
@Arg(desc = "The radius to fill in")
|
||||
Expression radiusExp,
|
||||
@Arg(desc = "The depth to fill", def = "")
|
||||
Integer depth) throws WorldEditException, EvaluationException {
|
||||
Integer depth) throws WorldEditException {
|
||||
double radius = radiusExp.evaluate();
|
||||
radius = Math.max(1, radius);
|
||||
we.checkMaxRadius(radius);
|
||||
@ -319,10 +323,10 @@ public class UtilityCommands {
|
||||
@CommandPermissions("worldedit.drain")
|
||||
@Logging(PLACEMENT)
|
||||
public int drain(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Range(min=0) @Arg(desc = "The radius to drain")
|
||||
@Arg(desc = "The radius to drain")
|
||||
Expression radiusExp,
|
||||
@Switch(name = 'w', desc = "Also un-waterlog blocks")
|
||||
boolean waterlogged) throws WorldEditException, EvaluationException {
|
||||
boolean waterlogged) throws WorldEditException {
|
||||
double radius = radiusExp.evaluate();
|
||||
radius = Math.max(0, radius);
|
||||
we.checkMaxRadius(radius);
|
||||
@ -340,9 +344,8 @@ public class UtilityCommands {
|
||||
@CommandPermissions("worldedit.fixlava")
|
||||
@Logging(PLACEMENT)
|
||||
public int fixLava(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Range(min=0) @Arg(desc = "The radius to fix in")
|
||||
Expression radiusExp) throws WorldEditException, EvaluationException {
|
||||
double radius = radiusExp.evaluate();
|
||||
@Arg(desc = "The radius to fix in")
|
||||
double radius) throws WorldEditException {
|
||||
radius = Math.max(0, radius);
|
||||
we.checkMaxRadius(radius);
|
||||
int affected = editSession.fixLiquid(session.getPlacementPosition(actor), radius, BlockTypes.LAVA);
|
||||
@ -358,13 +361,12 @@ public class UtilityCommands {
|
||||
@CommandPermissions("worldedit.fixwater")
|
||||
@Logging(PLACEMENT)
|
||||
public int fixWater(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Range(min=0) @Arg(desc = "The radius to fix in")
|
||||
Expression radiusExp) throws WorldEditException, EvaluationException {
|
||||
double radius = radiusExp.evaluate();
|
||||
@Arg(desc = "The radius to fix in")
|
||||
double radius) throws WorldEditException {
|
||||
radius = Math.max(0, radius);
|
||||
we.checkMaxRadius(radius);
|
||||
int affected = editSession.fixLiquid(session.getPlacementPosition(actor), radius, BlockTypes.WATER);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been changed.");
|
||||
return affected;
|
||||
}
|
||||
|
||||
@ -376,15 +378,16 @@ public class UtilityCommands {
|
||||
@CommandPermissions("worldedit.removeabove")
|
||||
@Logging(PLACEMENT)
|
||||
public int removeAbove(Actor actor, World world, LocalSession session, EditSession editSession,
|
||||
@Range(min=1) @Arg(name = "size", desc = "The apothem of the square to remove from", def = "1")
|
||||
@Arg(desc = "The apothem of the square to remove from", def = "1")
|
||||
int size,
|
||||
@Arg(desc = "The maximum height above you to remove from", def = "")
|
||||
Integer height) throws WorldEditException {
|
||||
size = Math.max(1, size);
|
||||
we.checkMaxRadius(size);
|
||||
height = height != null ? Math.min((world.getMaxY() + 1), height + 1) : (world.getMaxY() + 1);
|
||||
|
||||
int affected = editSession.removeAbove(session.getPlacementPosition(actor), size, height);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been removed.");
|
||||
return affected;
|
||||
}
|
||||
|
||||
@ -396,7 +399,7 @@ public class UtilityCommands {
|
||||
@CommandPermissions("worldedit.removebelow")
|
||||
@Logging(PLACEMENT)
|
||||
public int removeBelow(Actor actor, World world, LocalSession session, EditSession editSession,
|
||||
@Arg(name = "size", desc = "The apothem of the square to remove from", def = "1")
|
||||
@Arg(desc = "The apothem of the square to remove from", def = "1")
|
||||
int size,
|
||||
@Arg(desc = "The maximum height below you to remove from", def = "")
|
||||
Integer height) throws WorldEditException {
|
||||
@ -405,7 +408,7 @@ public class UtilityCommands {
|
||||
height = height != null ? Math.min((world.getMaxY() + 1), height + 1) : (world.getMaxY() + 1);
|
||||
|
||||
int affected = editSession.removeBelow(session.getPlacementPosition(actor), size, height);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been removed.");
|
||||
return affected;
|
||||
}
|
||||
|
||||
@ -419,25 +422,25 @@ public class UtilityCommands {
|
||||
public int removeNear(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Arg(desc = "The mask of blocks to remove")
|
||||
Mask mask,
|
||||
@Range(min=1) @Arg(desc = "The radius of the square to remove from", def = "50")
|
||||
@Arg(desc = "The radius of the square to remove from", def = "50")
|
||||
int radius) throws WorldEditException {
|
||||
radius = Math.max(1, radius);
|
||||
we.checkMaxRadius(radius);
|
||||
|
||||
int affected = editSession.removeNear(session.getPlacementPosition(actor), mask, radius);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been removed.");
|
||||
return affected;
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "replacenear",
|
||||
aliases = { "/replacenear", "/rn" },
|
||||
aliases = { "/replacenear" },
|
||||
desc = "Replace nearby blocks"
|
||||
)
|
||||
@CommandPermissions("worldedit.replacenear")
|
||||
@Logging(PLACEMENT)
|
||||
public int replaceNear(Actor actor, World world, LocalSession session, EditSession editSession,
|
||||
@Range(min=1) @Arg(desc = "The radius of the square to remove in")
|
||||
@Arg(desc = "The radius of the square to remove in")
|
||||
int radius,
|
||||
@Arg(desc = "The mask matching blocks to remove", def = "")
|
||||
Mask from,
|
||||
@ -456,7 +459,7 @@ public class UtilityCommands {
|
||||
}
|
||||
|
||||
int affected = editSession.replaceBlocks(region, from, to);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been replaced.");
|
||||
return affected;
|
||||
}
|
||||
|
||||
@ -468,7 +471,7 @@ public class UtilityCommands {
|
||||
@CommandPermissions("worldedit.snow")
|
||||
@Logging(PLACEMENT)
|
||||
public int snow(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Range(min=1) @Arg(desc = "The radius of the circle to snow in", def = "10")
|
||||
@Arg(desc = "The radius of the circle to snow in", def = "10")
|
||||
double size) throws WorldEditException {
|
||||
size = Math.max(1, size);
|
||||
we.checkMaxRadius(size);
|
||||
@ -486,7 +489,7 @@ public class UtilityCommands {
|
||||
@CommandPermissions("worldedit.thaw")
|
||||
@Logging(PLACEMENT)
|
||||
public int thaw(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Range(min=1) @Arg(desc = "The radius of the circle to thaw in", def = "10")
|
||||
@Arg(desc = "The radius of the circle to thaw in", def = "10")
|
||||
double size) throws WorldEditException {
|
||||
size = Math.max(1, size);
|
||||
we.checkMaxRadius(size);
|
||||
@ -504,7 +507,7 @@ public class UtilityCommands {
|
||||
@CommandPermissions("worldedit.green")
|
||||
@Logging(PLACEMENT)
|
||||
public int green(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Range(min=1) @Arg(desc = "The radius of the circle to convert in", def = "10")
|
||||
@Arg(desc = "The radius of the circle to convert in", def = "10")
|
||||
double size,
|
||||
@Switch(name = 'f', desc = "Also convert coarse dirt")
|
||||
boolean convertCoarse) throws WorldEditException {
|
||||
@ -513,10 +516,37 @@ public class UtilityCommands {
|
||||
final boolean onlyNormalDirt = !convertCoarse;
|
||||
|
||||
final int affected = editSession.green(session.getPlacementPosition(actor), size, onlyNormalDirt);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " surface(s) greened.");
|
||||
return affected;
|
||||
}
|
||||
|
||||
private int killMatchingEntities(Integer radius, Actor actor, Supplier<EntityFunction> func) throws IncompleteRegionException,
|
||||
MaxChangedBlocksException {
|
||||
List<EntityVisitor> visitors = new ArrayList<>();
|
||||
|
||||
LocalSession session = we.getSessionManager().get(actor);
|
||||
BlockVector3 center = session.getPlacementPosition(actor);
|
||||
EditSession editSession = session.createEditSession(actor);
|
||||
List<? extends Entity> entities;
|
||||
if (radius >= 0) {
|
||||
CylinderRegion region = CylinderRegion.createRadius(editSession, center, radius);
|
||||
entities = editSession.getEntities(region);
|
||||
} else {
|
||||
entities = editSession.getEntities();
|
||||
}
|
||||
visitors.add(new EntityVisitor(entities.iterator(), func.get()));
|
||||
|
||||
int killed = 0;
|
||||
for (EntityVisitor visitor : visitors) {
|
||||
Operations.completeLegacy(visitor);
|
||||
killed += visitor.getAffected();
|
||||
}
|
||||
|
||||
session.remember(editSession);
|
||||
editSession.flushSession();
|
||||
return killed;
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "extinguish",
|
||||
aliases = { "/ex", "/ext", "/extinguish", "ex", "ext" },
|
||||
@ -525,7 +555,7 @@ public class UtilityCommands {
|
||||
@CommandPermissions("worldedit.extinguish")
|
||||
@Logging(PLACEMENT)
|
||||
public void extinguish(Actor actor, LocalSession session, EditSession editSession,
|
||||
@Range(min=1) @Arg(desc = "The radius of the square to remove in", def = "")
|
||||
@Arg(desc = "The radius of the square to remove in", def = "")
|
||||
Integer radius) throws WorldEditException {
|
||||
|
||||
LocalConfiguration config = we.getConfiguration();
|
||||
@ -534,9 +564,9 @@ public class UtilityCommands {
|
||||
int size = radius != null ? Math.max(1, radius) : defaultRadius;
|
||||
we.checkMaxRadius(size);
|
||||
|
||||
Mask mask = BlockTypes.FIRE.toMask();
|
||||
Mask mask = new BlockTypeMask(editSession, BlockTypes.FIRE);
|
||||
int affected = editSession.removeNear(session.getPlacementPosition(actor), mask, size);
|
||||
BBC.VISITOR_BLOCK.send(actor, affected);
|
||||
actor.print(affected + " block(s) have been removed.");
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -597,6 +627,22 @@ public class UtilityCommands {
|
||||
return killed;
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "/help",
|
||||
desc = "Displays help for WorldEdit commands"
|
||||
)
|
||||
@CommandPermissions("worldedit.help")
|
||||
public void help(Actor actor,
|
||||
@Switch(name = 's', desc = "List sub-commands of the given command, if applicable")
|
||||
boolean listSubCommands,
|
||||
@ArgFlag(name = 'p', desc = "The page to retrieve", def = "1")
|
||||
int page,
|
||||
@Arg(desc = "The command to retrieve help for", def = "", variable = true)
|
||||
List<String> command) throws WorldEditException {
|
||||
PrintCommandHelp.help(command, page, listSubCommands,
|
||||
we.getPlatformManager().getPlatformCommandManager().getCommandManager(), actor, "//help");
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "remove",
|
||||
aliases = { "rem", "rement" },
|
||||
@ -607,9 +653,8 @@ public class UtilityCommands {
|
||||
public int remove(Actor actor,
|
||||
@Arg(desc = "The type of entity to remove")
|
||||
EntityRemover remover,
|
||||
@Range(min=-1) @Arg(desc = "The radius of the cuboid to remove from")
|
||||
@Arg(desc = "The radius of the cuboid to remove from")
|
||||
int radius) throws WorldEditException {
|
||||
|
||||
if (radius < -1) {
|
||||
actor.printError("Use -1 to remove all entities in loaded chunks");
|
||||
return 0;
|
||||
@ -621,34 +666,6 @@ public class UtilityCommands {
|
||||
return removed;
|
||||
}
|
||||
|
||||
private int killMatchingEntities(Integer radius, Actor actor, Supplier<EntityFunction> func) throws IncompleteRegionException, MaxChangedBlocksException {
|
||||
List<EntityVisitor> visitors = new ArrayList<>();
|
||||
|
||||
LocalSession session = we.getSessionManager().get(actor);
|
||||
BlockVector3 center = session.getPlacementPosition(actor);
|
||||
EditSession editSession = session.createEditSession(actor);
|
||||
List<? extends Entity> entities;
|
||||
if (radius >= 0) {
|
||||
CylinderRegion region = CylinderRegion.createRadius(editSession, center, radius);
|
||||
entities = editSession.getEntities(region);
|
||||
} else {
|
||||
entities = editSession.getEntities();
|
||||
}
|
||||
visitors.add(new EntityVisitor(entities.iterator(), func.get()));
|
||||
|
||||
int killed = 0;
|
||||
for (EntityVisitor visitor : visitors) {
|
||||
Operations.completeLegacy(visitor);
|
||||
killed += visitor.getAffected();
|
||||
}
|
||||
|
||||
BBC.KILL_SUCCESS.send(actor, killed, radius);
|
||||
|
||||
session.remember(editSession);
|
||||
editSession.flushSession();
|
||||
return killed;
|
||||
}
|
||||
|
||||
// get the formatter with the system locale. in the future, if we can get a local from a player, we can use that
|
||||
private static final DecimalFormat formatter = (DecimalFormat) NumberFormat.getInstance(Locale.getDefault());
|
||||
static {
|
||||
@ -663,7 +680,7 @@ public class UtilityCommands {
|
||||
@CommandPermissions("worldedit.calc")
|
||||
public void calc(Actor actor,
|
||||
@Arg(desc = "Expression to evaluate", variable = true)
|
||||
List<String> input) throws EvaluationException {
|
||||
List<String> input) {
|
||||
Expression expression;
|
||||
try {
|
||||
expression = Expression.compile(String.join(" ", input));
|
||||
@ -672,11 +689,12 @@ public class UtilityCommands {
|
||||
"'%s' could not be parsed as a valid expression", input));
|
||||
return;
|
||||
}
|
||||
double result = expression.evaluate(
|
||||
new double[]{}, WorldEdit.getInstance().getSessionManager().get(actor).getTimeout());
|
||||
String formatted = Double.isNaN(result) ? "NaN" : formatter.format(result);
|
||||
TextComponent msg = SubtleFormat.wrap(input + " = ").append(TextComponent.of(formatted, TextColor.LIGHT_PURPLE));
|
||||
actor.print(msg);
|
||||
WorldEditAsyncCommandBuilder.createAndSendMessage(actor, () -> {
|
||||
double result = expression.evaluate(
|
||||
new double[]{}, WorldEdit.getInstance().getSessionManager().get(actor).getTimeout());
|
||||
String formatted = Double.isNaN(result) ? "NaN" : formatter.format(result);
|
||||
return SubtleFormat.wrap(input + " = ").append(TextComponent.of(formatted, TextColor.LIGHT_PURPLE));
|
||||
}, null);
|
||||
}
|
||||
|
||||
@Command(
|
||||
@ -690,22 +708,6 @@ public class UtilityCommands {
|
||||
}
|
||||
}
|
||||
|
||||
@Command(
|
||||
name = "/help",
|
||||
desc = "Displays help for WorldEdit commands"
|
||||
)
|
||||
@CommandPermissions("worldedit.help")
|
||||
public void help(Actor actor,
|
||||
@Switch(name = 's', desc = "List sub-commands of the given command, if applicable")
|
||||
boolean listSubCommands,
|
||||
@ArgFlag(name = 'p', desc = "The page to retrieve", def = "1")
|
||||
int page,
|
||||
@Arg(desc = "The command to retrieve help for", def = "", variable = true)
|
||||
List<String> commandStr) throws WorldEditException {
|
||||
PrintCommandHelp.help(commandStr, page, listSubCommands,
|
||||
we.getPlatformManager().getPlatformCommandManager().getCommandManager(), actor, "//help");
|
||||
}
|
||||
|
||||
public static List<Map.Entry<URI, String>> filesToEntry(final File root, final List<File> files, final UUID uuid) {
|
||||
return Lists.transform(files, input -> { // Keep this functional, as transform is evaluated lazily
|
||||
URI uri = input.toURI();
|
||||
|
@ -56,11 +56,9 @@ import org.enginehub.piston.annotation.param.Arg;
|
||||
import org.enginehub.piston.annotation.param.ArgFlag;
|
||||
import org.enginehub.piston.annotation.param.Switch;
|
||||
|
||||
@CommandContainer(superTypes = {CommandPermissionsConditionGenerator.Registration.class, CommandQueuedConditionGenerator.Registration.class})
|
||||
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
|
||||
public class WorldEditCommands {
|
||||
|
||||
private static final DateTimeFormatter dateFormat = DateTimeFormatter
|
||||
.ofPattern("yyyy-MM-dd HH:mm:ss z");
|
||||
private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
|
||||
|
||||
private final WorldEdit we;
|
||||
|
||||
@ -174,11 +172,10 @@ public class WorldEditCommands {
|
||||
try {
|
||||
ZoneId tz = ZoneId.of(timezone);
|
||||
session.setTimezone(tz);
|
||||
BBC.TIMEZONE_SET.send(actor, tz.getDisplayName(
|
||||
TextStyle.FULL, Locale.ENGLISH
|
||||
actor.print("Timezone set for this session to: " + tz.getDisplayName(
|
||||
TextStyle.FULL, Locale.ENGLISH
|
||||
));
|
||||
BBC.TIMEZONE_DISPLAY
|
||||
.send(actor, dateFormat.format(ZonedDateTime.now(tz)));
|
||||
actor.print("The current time in that timezone is: " + dateFormat.format(ZonedDateTime.now(tz)));
|
||||
} catch (ZoneRulesException e) {
|
||||
actor.printError("Invalid timezone");
|
||||
}
|
||||
@ -186,7 +183,7 @@ public class WorldEditCommands {
|
||||
|
||||
@Command(
|
||||
name = "help",
|
||||
desc = "Displays help for FAWE commands"
|
||||
desc = "Displays help for WorldEdit commands"
|
||||
)
|
||||
@SkipQueue
|
||||
@CommandPermissions("worldedit.help")
|
||||
@ -196,8 +193,8 @@ public class WorldEditCommands {
|
||||
@ArgFlag(name = 'p', desc = "The page to retrieve", def = "1")
|
||||
int page,
|
||||
@Arg(desc = "The command to retrieve help for", def = "", variable = true)
|
||||
List<String> commandStr) throws WorldEditException {
|
||||
PrintCommandHelp.help(commandStr, page, listSubCommands,
|
||||
we.getPlatformManager().getPlatformCommandManager().getCommandManager(), actor, "/worldedit help");
|
||||
List<String> command) throws WorldEditException {
|
||||
PrintCommandHelp.help(command, page, listSubCommands,
|
||||
we.getPlatformManager().getPlatformCommandManager().getCommandManager(), actor, "/worldedit help");
|
||||
}
|
||||
}
|
||||
|
@ -19,20 +19,21 @@
|
||||
|
||||
package com.sk89q.worldedit.command.argument;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.sk89q.worldedit.util.formatting.text.TextComponent.space;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||
import java.util.List;
|
||||
import org.enginehub.piston.converter.ArgumentConverter;
|
||||
import org.enginehub.piston.converter.ConversionResult;
|
||||
import org.enginehub.piston.converter.SuccessfulConversion;
|
||||
import org.enginehub.piston.inject.InjectedValueAccess;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.sk89q.worldedit.util.formatting.text.TextComponent.space;
|
||||
|
||||
public class CommaSeparatedValuesConverter<T> implements ArgumentConverter<T> {
|
||||
|
||||
public static <T> CommaSeparatedValuesConverter<T> wrap(ArgumentConverter<T> delegate) {
|
||||
|
@ -28,12 +28,13 @@ 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 static org.enginehub.piston.converter.SuggestionHelper.limitByPrefix;
|
||||
import org.enginehub.piston.inject.InjectedValueAccess;
|
||||
import org.enginehub.piston.inject.Key;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.enginehub.piston.converter.SuggestionHelper.limitByPrefix;
|
||||
|
||||
public class EntityRemoverConverter implements ArgumentConverter<EntityRemover> {
|
||||
|
||||
public static void register(CommandManager commandManager) {
|
||||
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.command.factory;
|
||||
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.function.Contextual;
|
||||
import com.sk89q.worldedit.function.EditContext;
|
||||
import com.sk89q.worldedit.function.generator.ForestGenerator;
|
||||
import com.sk89q.worldedit.util.TreeGenerator;
|
||||
|
||||
public final class TreeGeneratorFactory implements Contextual<ForestGenerator> {
|
||||
private final TreeGenerator.TreeType type;
|
||||
|
||||
public TreeGeneratorFactory(TreeGenerator.TreeType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForestGenerator createFromContext(EditContext input) {
|
||||
return new ForestGenerator((EditSession) input.getDestination(), type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "tree of type " + type;
|
||||
}
|
||||
}
|
@ -66,8 +66,8 @@ public class AreaPickaxe implements BlockTool {
|
||||
|
||||
try {
|
||||
for (int x = ox - range; x <= ox + range; ++x) {
|
||||
for (int z = oz - range; z <= oz + range; ++z) {
|
||||
for (int y = oy + range; y >= oy - range; --y) {
|
||||
for (int y = oy - range; y <= oy + range; ++y) {
|
||||
for (int z = oz - range; z <= oz + range; ++z) {
|
||||
if (initialType.equals(editSession.getBlock(x, y, z))) {
|
||||
continue;
|
||||
}
|
||||
|
@ -70,7 +70,6 @@ import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.internal.expression.Expression;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.math.Vector3;
|
||||
import com.sk89q.worldedit.session.request.Request;
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
@ -437,7 +436,7 @@ public class BrushTool implements DoubleActionTraceTool, ScrollTool, MovableTool
|
||||
@Override
|
||||
public boolean actPrimary(Platform server, LocalConfiguration config, Player player, LocalSession session) {
|
||||
return act(BrushAction.PRIMARY, player, session);
|
||||
}
|
||||
}
|
||||
|
||||
public BlockVector3 getPosition(EditSession editSession, Player player) {
|
||||
Location loc = player.getLocation();
|
||||
|
@ -23,7 +23,6 @@ import com.boydti.fawe.config.BBC;
|
||||
import com.sk89q.worldedit.LocalConfiguration;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.extension.platform.Platform;
|
||||
import com.sk89q.worldedit.extension.platform.permission.ActorSelectorLimits;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
@ -40,11 +39,6 @@ public class DistanceWand extends BrushTool implements DoubleActionTraceTool {
|
||||
super("worldedit.selection.pos");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse(Actor player) {
|
||||
return player.hasPermission("worldedit.wand");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean actSecondary(Platform server, LocalConfiguration config, Player player, LocalSession session) {
|
||||
Location target = getTarget(player);
|
||||
@ -74,8 +68,7 @@ public class DistanceWand extends BrushTool implements DoubleActionTraceTool {
|
||||
private Location getTarget(Player player) {
|
||||
Location target;
|
||||
Mask mask = getTraceMask();
|
||||
int range = getRange();
|
||||
if (range < MAX_RANGE) {
|
||||
if (this.range > -1) {
|
||||
target = player.getBlockTrace(getRange(), true, mask);
|
||||
} else {
|
||||
target = player.getBlockTrace(MAX_RANGE, false, mask);
|
||||
|
@ -118,7 +118,7 @@ public class FloatingTreeRemover implements BlockTool {
|
||||
* @param origin any point contained in the floating tree
|
||||
* @return a set containing all blocks in the tree/shroom or null if this is not a floating tree/shroom.
|
||||
*/
|
||||
private Set<BlockVector3> bfs(World world, BlockVector3 origin) throws MaxChangedBlocksException {
|
||||
private Set<BlockVector3> bfs(World world, BlockVector3 origin) {
|
||||
final LocalBlockVectorSet visited = new LocalBlockVectorSet();
|
||||
final LocalBlockVectorSet queue = new LocalBlockVectorSet();
|
||||
|
||||
|
@ -27,6 +27,7 @@ import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.extension.platform.Platform;
|
||||
import com.sk89q.worldedit.extent.inventory.BlockBag;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
@ -56,39 +57,56 @@ public class LongRangeBuildTool extends BrushTool implements DoubleActionTraceTo
|
||||
public boolean actSecondary(Platform server, LocalConfiguration config, Player player, LocalSession session) {
|
||||
Location pos = getTargetFace(player);
|
||||
if (pos == null) return false;
|
||||
try (EditSession eS = session.createEditSession(player)) {
|
||||
BlockBag bag = session.getBlockBag(player);
|
||||
|
||||
try (EditSession editSession = session.createEditSession(player)) {
|
||||
try {
|
||||
editSession.disableBuffering();
|
||||
BlockVector3 blockPoint = pos.toVector().toBlockPoint();
|
||||
BaseBlock applied = secondary.apply(blockPoint);
|
||||
if (applied.getBlockType().getMaterial().isAir()) {
|
||||
eS.setBlock(blockPoint, secondary);
|
||||
editSession.setBlock(blockPoint, secondary);
|
||||
} else {
|
||||
eS.setBlock(pos.toVector().subtract(pos.getDirection()).toBlockPoint(), secondary);
|
||||
editSession.setBlock(pos.toVector().subtract(pos.getDirection()).toBlockPoint(), secondary);
|
||||
}
|
||||
} catch (MaxChangedBlocksException ignored) {
|
||||
} finally {
|
||||
session.remember(editSession);
|
||||
}
|
||||
return true;
|
||||
} catch (MaxChangedBlocksException ignored) {
|
||||
// one block? eat it
|
||||
} finally {
|
||||
if (bag != null) {
|
||||
bag.flushChanges();
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean actPrimary(Platform server, LocalConfiguration config, Player player, LocalSession session) {
|
||||
Location pos = getTargetFace(player);
|
||||
if (pos == null) return false;
|
||||
try (EditSession eS = session.createEditSession(player)) {
|
||||
BlockBag bag = session.getBlockBag(player);
|
||||
|
||||
try (EditSession editSession = session.createEditSession(player)) {
|
||||
try {
|
||||
editSession.disableBuffering();
|
||||
BlockVector3 blockPoint = pos.toVector().toBlockPoint();
|
||||
BaseBlock applied = primary.apply(blockPoint);
|
||||
if (applied.getBlockType().getMaterial().isAir()) {
|
||||
eS.setBlock(blockPoint, primary);
|
||||
editSession.setBlock(blockPoint, primary);
|
||||
} else {
|
||||
eS.setBlock(pos.toVector().subtract(pos.getDirection()).toBlockPoint(), primary);
|
||||
editSession.setBlock(pos.toVector().subtract(pos.getDirection()).toBlockPoint(), primary);
|
||||
}
|
||||
} catch (MaxChangedBlocksException ignored) {
|
||||
} finally {
|
||||
session.remember(editSession);
|
||||
}
|
||||
return true;
|
||||
} catch (MaxChangedBlocksException ignored) {
|
||||
// one block? eat it
|
||||
} finally {
|
||||
if (bag != null) {
|
||||
bag.flushChanges();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Location getTargetFace(Player player) {
|
||||
@ -99,6 +117,7 @@ public class LongRangeBuildTool extends BrushTool implements DoubleActionTraceTo
|
||||
} else {
|
||||
target = player.getBlockTrace(MAX_RANGE, false, mask);
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
player.printError(BBC.NO_BLOCK.s());
|
||||
return null;
|
||||
|
@ -24,6 +24,7 @@ import com.sk89q.worldedit.LocalConfiguration;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||
import com.sk89q.worldedit.extension.platform.Platform;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
@ -33,6 +34,8 @@ import com.sk89q.worldedit.util.formatting.text.format.TextColor;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
/**
|
||||
* Looks up information about a block.
|
||||
*/
|
||||
|
@ -84,4 +84,36 @@ public class RecursivePickaxe implements BlockTool {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void recurse(Platform server, EditSession editSession, World world, BlockVector3 pos,
|
||||
BlockVector3 origin, double size, BlockType initialType, Set<BlockVector3> visited) throws MaxChangedBlocksException {
|
||||
|
||||
final double distanceSq = origin.distanceSq(pos);
|
||||
if (distanceSq > size*size || visited.contains(pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
visited.add(pos);
|
||||
|
||||
if (editSession.getBlock(pos).getBlockType() != initialType) {
|
||||
return;
|
||||
}
|
||||
|
||||
editSession.setBlock(pos, BlockTypes.AIR.getDefaultState());
|
||||
|
||||
world.queueBlockBreakEffect(server, pos, initialType, distanceSq);
|
||||
|
||||
recurse(server, editSession, world, pos.add(1, 0, 0),
|
||||
origin, size, initialType, visited);
|
||||
recurse(server, editSession, world, pos.add(-1, 0, 0),
|
||||
origin, size, initialType, visited);
|
||||
recurse(server, editSession, world, pos.add(0, 0, 1),
|
||||
origin, size, initialType, visited);
|
||||
recurse(server, editSession, world, pos.add(0, 0, -1),
|
||||
origin, size, initialType, visited);
|
||||
recurse(server, editSession, world, pos.add(0, 1, 0),
|
||||
origin, size, initialType, visited);
|
||||
recurse(server, editSession, world, pos.add(0, -1, 0),
|
||||
origin, size, initialType, visited);
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ public class ClipboardBrush implements Brush {
|
||||
this.ignoreAirBlocks = ignoreAirBlocks;
|
||||
this.usingOrigin = usingOrigin;
|
||||
this.pasteBiomes = false;
|
||||
this.pasteEntities = true;
|
||||
this.pasteEntities = false;
|
||||
this.sourceMask = null;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,12 @@ import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.util.LocatedBlock;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.util.collection.LocatedBlockList;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
|
||||
public class GravityBrush implements Brush {
|
||||
@ -48,12 +53,15 @@ public class GravityBrush implements Brush {
|
||||
if (y != freeSpot) {
|
||||
editSession.setBlock((int)x, (int)y, (int)z, BlockTypes.AIR.getDefaultState());
|
||||
editSession.setBlock((int)x, (int)freeSpot, (int)z, block);
|
||||
}
|
||||
}
|
||||
freeSpot = y + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
column.clear();
|
||||
removedBlocks.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,8 +19,6 @@
|
||||
|
||||
package com.sk89q.worldedit.command.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
@ -42,6 +40,9 @@ import javax.annotation.Nullable;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public final class AsyncCommandBuilder<T> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AsyncCommandBuilder.class);
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
package com.sk89q.worldedit.command.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.enginehub.piston.Command;
|
||||
import org.enginehub.piston.gen.CommandConditionGenerator;
|
||||
@ -28,6 +27,8 @@ import org.enginehub.piston.util.NonnullByDefault;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@NonnullByDefault
|
||||
public final class CommandPermissionsConditionGenerator implements CommandConditionGenerator {
|
||||
|
||||
|
@ -20,12 +20,12 @@
|
||||
package com.sk89q.worldedit.command.util;
|
||||
|
||||
import com.boydti.fawe.util.TaskManager;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import com.sk89q.worldedit.entity.metadata.EntityProperties;
|
||||
import com.sk89q.worldedit.function.EntityFunction;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
|
@ -24,8 +24,6 @@ import org.enginehub.piston.Command;
|
||||
import org.enginehub.piston.inject.InjectedValueAccess;
|
||||
import org.enginehub.piston.inject.Key;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class PermissionCondition implements Command.Condition {
|
||||
|
@ -303,14 +303,6 @@ public interface Player extends Entity, Actor {
|
||||
*/
|
||||
void setPosition(Vector3 pos, float pitch, float yaw);
|
||||
|
||||
/**
|
||||
* Move the player.
|
||||
*
|
||||
* @param pos where to move them
|
||||
*/
|
||||
@Override
|
||||
void setPosition(Vector3 pos);
|
||||
|
||||
/**
|
||||
* Sends a fake block to the client.
|
||||
*
|
||||
|
@ -18,8 +18,6 @@
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.event.platform;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import com.sk89q.worldedit.event.Event;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.internal.util.Substring;
|
||||
@ -27,6 +25,8 @@ import com.sk89q.worldedit.internal.util.Substring;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Posted when suggestions for auto-completion are requested for command input.
|
||||
*/
|
||||
|
@ -32,6 +32,7 @@ import com.sk89q.worldedit.internal.registry.InputParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A registry of known {@link Mask}s. Provides methods to instantiate
|
||||
@ -48,19 +49,36 @@ public final class MaskFactory extends AbstractFactory<Mask> {
|
||||
* @param worldEdit the WorldEdit instance
|
||||
*/
|
||||
public MaskFactory(WorldEdit worldEdit) {
|
||||
super(worldEdit, new DefaultMaskParser(worldEdit));
|
||||
super(worldEdit, new DefaultMaskParser(worldEdit));
|
||||
/*
|
||||
super(worldEdit, new BlocksMaskParser(worldEdit));
|
||||
|
||||
// register(new ExistingMaskParser(worldEdit));
|
||||
// register(new SolidMaskParser(worldEdit));
|
||||
// register(new LazyRegionMaskParser(worldEdit));
|
||||
// register(new RegionMaskParser(worldEdit));
|
||||
// register(new OffsetMaskParser(worldEdit));
|
||||
// register(new NoiseMaskParser(worldEdit));
|
||||
// register(new BlockStateMaskParser(worldEdit));
|
||||
// register(new NegateMaskParser(worldEdit));
|
||||
// register(new ExpressionMaskParser(worldEdit));
|
||||
register(new BlockCategoryMaskParser(worldEdit)); // TODO implement in DefaultMaskParser
|
||||
// register(new BiomeMaskParser(worldEdit));
|
||||
register(new ExistingMaskParser(worldEdit));
|
||||
register(new SolidMaskParser(worldEdit));
|
||||
register(new LazyRegionMaskParser(worldEdit));
|
||||
register(new RegionMaskParser(worldEdit));
|
||||
register(new OffsetMaskParser(worldEdit));
|
||||
register(new NoiseMaskParser(worldEdit));
|
||||
register(new BlockStateMaskParser(worldEdit));
|
||||
register(new NegateMaskParser(worldEdit));
|
||||
register(new ExpressionMaskParser(worldEdit));
|
||||
*/
|
||||
register(new BlockCategoryMaskParser(worldEdit));
|
||||
/*
|
||||
register(new BiomeMaskParser(worldEdit));
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSuggestions(String input) {
|
||||
final String[] split = input.split(" ");
|
||||
if (split.length > 1) {
|
||||
String prev = input.substring(0, input.lastIndexOf(" ")) + " ";
|
||||
return super.getSuggestions(split[split.length -1]).stream().map(s -> prev + s).collect(Collectors.toList());
|
||||
}
|
||||
return super.getSuggestions(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -40,16 +40,19 @@ public final class PatternFactory extends AbstractFactory<Pattern> {
|
||||
* @param worldEdit the WorldEdit instance
|
||||
*/
|
||||
public PatternFactory(WorldEdit worldEdit) {
|
||||
super(worldEdit, new DefaultPatternParser(worldEdit));
|
||||
super(worldEdit, new DefaultPatternParser(worldEdit));
|
||||
/*
|
||||
super(worldEdit, new SingleBlockPatternParser(worldEdit));
|
||||
|
||||
// split and parse each sub-pattern
|
||||
// register(new RandomPatternParser(worldEdit));
|
||||
register(new RandomPatternParser(worldEdit));
|
||||
|
||||
// individual patterns
|
||||
// register(new ClipboardPatternParser(worldEdit));
|
||||
// register(new TypeOrStateApplyingPatternParser(worldEdit));
|
||||
// register(new RandomStatePatternParser(worldEdit));
|
||||
register(new BlockCategoryPatternParser(worldEdit)); // TODO implement in pattern parser
|
||||
register(new ClipboardPatternParser(worldEdit));
|
||||
register(new TypeOrStateApplyingPatternParser(worldEdit));
|
||||
register(new RandomStatePatternParser(worldEdit));
|
||||
*/
|
||||
register(new BlockCategoryPatternParser(worldEdit));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
package com.sk89q.worldedit.extension.factory.parser;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.boydti.fawe.command.SuggestInputParseException;
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.jnbt.JSON2NBT;
|
||||
@ -35,7 +37,7 @@ import com.sk89q.worldedit.blocks.BaseItem;
|
||||
import com.sk89q.worldedit.blocks.MobSpawnerBlock;
|
||||
import com.sk89q.worldedit.blocks.SignBlock;
|
||||
import com.sk89q.worldedit.blocks.SkullBlock;
|
||||
import com.sk89q.worldedit.blocks.metadata.MobType;
|
||||
import com.sk89q.worldedit.command.util.SuggestionHelper;
|
||||
import com.sk89q.worldedit.entity.Player;
|
||||
import com.sk89q.worldedit.extension.input.DisallowedUsageException;
|
||||
import com.sk89q.worldedit.extension.input.InputParseException;
|
||||
@ -61,6 +63,8 @@ import com.sk89q.worldedit.world.registry.LegacyMapper;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import com.sk89q.worldedit.world.entity.EntityType;
|
||||
import com.sk89q.worldedit.world.entity.EntityTypes;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@ -111,6 +115,8 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] EMPTY_STRING_ARRAY = {};
|
||||
|
||||
/**
|
||||
* Backwards compatibility for wool colours in block syntax.
|
||||
*
|
||||
@ -162,6 +168,71 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<Property<?>, Object> parseProperties(BlockType type, String[] stateProperties, ParserContext context) throws NoMatchException {
|
||||
Map<Property<?>, Object> blockStates = new HashMap<>();
|
||||
|
||||
if (stateProperties.length > 0) { // Block data not yet detected
|
||||
// Parse the block data (optional)
|
||||
for (String parseableData : stateProperties) {
|
||||
try {
|
||||
String[] parts = parseableData.split("=");
|
||||
if (parts.length != 2) {
|
||||
throw new NoMatchException("Bad state format in " + parseableData);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Property<Object> propertyKey = (Property<Object>) type.getPropertyMap().get(parts[0]);
|
||||
if (propertyKey == null) {
|
||||
if (context.getActor() != null) {
|
||||
throw new NoMatchException("Unknown property " + parts[0] + " for block " + type.getId());
|
||||
} else {
|
||||
WorldEdit.logger.warn("Unknown property " + parts[0] + " for block " + type.getId());
|
||||
}
|
||||
return Maps.newHashMap();
|
||||
}
|
||||
if (blockStates.containsKey(propertyKey)) {
|
||||
throw new NoMatchException("Duplicate property " + parts[0]);
|
||||
}
|
||||
Object value;
|
||||
try {
|
||||
value = propertyKey.getValueFor(parts[1]);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new NoMatchException("Unknown value " + parts[1] + " for state " + parts[0]);
|
||||
}
|
||||
|
||||
blockStates.put(propertyKey, value);
|
||||
} catch (NoMatchException e) {
|
||||
throw e; // Pass-through
|
||||
} catch (Exception e) {
|
||||
WorldEdit.logger.warn("Unknown state '" + parseableData + "'", e);
|
||||
throw new NoMatchException("Unknown state '" + parseableData + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blockStates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getSuggestions(String input) {
|
||||
final int idx = input.lastIndexOf('[');
|
||||
if (idx < 0) {
|
||||
return SuggestionHelper.getNamespacedRegistrySuggestions(BlockType.REGISTRY, input);
|
||||
}
|
||||
String blockType = input.substring(0, idx);
|
||||
BlockType type = BlockTypes.get(blockType.toLowerCase(Locale.ROOT));
|
||||
if (type == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
String props = input.substring(idx + 1);
|
||||
if (props.isEmpty()) {
|
||||
return type.getProperties().stream().map(p -> input + p.getName() + "=");
|
||||
}
|
||||
|
||||
return SuggestionHelper.getBlockPropertySuggestions(blockType, props);
|
||||
}
|
||||
|
||||
private BaseBlock parseLogic(String input, ParserContext context) throws InputParseException {
|
||||
String[] blockAndExtraData = input.trim().split("\\|", 2);
|
||||
blockAndExtraData[0] = woolMapper(blockAndExtraData[0]);
|
||||
@ -193,7 +264,7 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
|
||||
state = LegacyMapper.getInstance().getBlockFromLegacy(type.getLegacyCombinedId() >> 4, data);
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,6 +277,13 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
|
||||
typeString = blockAndExtraData[0];
|
||||
} else {
|
||||
typeString = blockAndExtraData[0].substring(0, stateStart);
|
||||
if (stateStart + 1 >= blockAndExtraData[0].length()) {
|
||||
throw new InputParseException("Invalid format. Hanging bracket @ " + stateStart + ".");
|
||||
}
|
||||
int stateEnd = blockAndExtraData[0].lastIndexOf(']');
|
||||
if (stateEnd < 0) {
|
||||
throw new InputParseException("Invalid format. Unclosed property.");
|
||||
}
|
||||
stateString = blockAndExtraData[0].substring(stateStart + 1, blockAndExtraData[0].length() - 1);
|
||||
}
|
||||
if (typeString.isEmpty()) {
|
||||
@ -288,6 +366,10 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// this should be impossible but IntelliJ isn't that smart
|
||||
if (blockType == null) {
|
||||
throw new NoMatchException("Does not match a valid block type: '" + input + "'");
|
||||
}
|
||||
|
||||
if (blockAndExtraData.length > 1 && blockAndExtraData[1].startsWith("{")) {
|
||||
String joined = StringMan.join(Arrays.copyOfRange(blockAndExtraData, 1, blockAndExtraData.length), "|");
|
||||
@ -322,6 +404,7 @@ public class DefaultBlockParser extends InputParser<BaseBlock> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mobName = ent.getId();
|
||||
if (!worldEdit.getPlatformManager().queryCapability(Capability.USER_COMMANDS).isValidMobType(mobName)) {
|
||||
String finalMobName = mobName.toLowerCase(Locale.ROOT);
|
||||
throw new SuggestInputParseException("Unknown mob type '" + mobName + "'", mobName, () -> Stream.of(MobType.values())
|
||||
|
@ -28,7 +28,6 @@ import com.sk89q.worldedit.function.mask.BiomeMask2D;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.function.mask.Masks;
|
||||
import com.sk89q.worldedit.internal.registry.InputParser;
|
||||
import com.sk89q.worldedit.session.request.RequestExtent;
|
||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -26,7 +26,8 @@ import com.sk89q.worldedit.extension.input.ParserContext;
|
||||
import com.sk89q.worldedit.function.mask.BlockStateMask;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.internal.registry.InputParser;
|
||||
import com.sk89q.worldedit.session.request.RequestExtent;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -25,7 +25,6 @@ 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.internal.registry.SimpleInputParser;
|
||||
import com.sk89q.worldedit.session.request.RequestExtent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -29,7 +29,8 @@ import com.sk89q.worldedit.function.mask.Masks;
|
||||
import com.sk89q.worldedit.function.mask.OffsetMask;
|
||||
import com.sk89q.worldedit.internal.registry.InputParser;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.session.request.RequestExtent;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -25,7 +25,6 @@ import com.sk89q.worldedit.extension.input.ParserContext;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.function.mask.SolidBlockMask;
|
||||
import com.sk89q.worldedit.internal.registry.SimpleInputParser;
|
||||
import com.sk89q.worldedit.session.request.RequestExtent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -30,6 +30,7 @@ import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.function.pattern.StateApplyingPattern;
|
||||
import com.sk89q.worldedit.function.pattern.TypeApplyingPattern;
|
||||
import com.sk89q.worldedit.internal.registry.InputParser;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -19,45 +19,12 @@
|
||||
|
||||
package com.sk89q.worldedit.extension.platform;
|
||||
|
||||
import com.boydti.fawe.object.exception.FaweException;
|
||||
import com.boydti.fawe.object.task.SimpleAsyncNotifyQueue;
|
||||
import com.boydti.fawe.util.TaskManager;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.internal.cui.CUIEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public abstract class AbstractNonPlayerActor implements Actor {
|
||||
|
||||
private final ConcurrentHashMap<String, Object> meta = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getRawMeta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
// Queue for async tasks
|
||||
private AtomicInteger runningCount = new AtomicInteger();
|
||||
private SimpleAsyncNotifyQueue asyncNotifyQueue = new SimpleAsyncNotifyQueue(
|
||||
(thread, throwable) -> {
|
||||
while (throwable.getCause() != null) {
|
||||
throwable = throwable.getCause();
|
||||
}
|
||||
if (throwable instanceof WorldEditException) {
|
||||
printError(throwable.getLocalizedMessage());
|
||||
} else {
|
||||
FaweException fe = FaweException.get(throwable);
|
||||
if (fe != null) {
|
||||
printError(fe.getMessage());
|
||||
} else {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public boolean canDestroyBedrock() {
|
||||
return true;
|
||||
@ -81,35 +48,4 @@ public abstract class AbstractNonPlayerActor implements Actor {
|
||||
@Override
|
||||
public void dispatchCUIEvent(CUIEvent event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task either async, or on the current thread
|
||||
*
|
||||
* @param ifFree
|
||||
* @param checkFree Whether to first check if a task is running
|
||||
* @param async
|
||||
* @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) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Runnable wrapped = () -> {
|
||||
try {
|
||||
runningCount.addAndGet(1);
|
||||
ifFree.run();
|
||||
} finally {
|
||||
runningCount.decrementAndGet();
|
||||
}
|
||||
};
|
||||
if (async) {
|
||||
asyncNotifyQueue.queue(wrapped);
|
||||
} else {
|
||||
TaskManager.IMP.taskNow(wrapped, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -19,13 +19,14 @@
|
||||
|
||||
package com.sk89q.worldedit.extension.platform;
|
||||
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.object.exception.FaweException;
|
||||
import com.boydti.fawe.object.task.SimpleAsyncNotifyQueue;
|
||||
import com.boydti.fawe.regions.FaweMaskManager;
|
||||
import com.boydti.fawe.util.TaskManager;
|
||||
import com.boydti.fawe.util.WEManager;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
@ -45,6 +46,8 @@ import com.sk89q.worldedit.regions.Region;
|
||||
import com.sk89q.worldedit.regions.RegionOperationException;
|
||||
import com.sk89q.worldedit.regions.RegionSelector;
|
||||
import com.sk89q.worldedit.regions.selector.ConvexPolyhedralRegionSelector;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
|
||||
import com.sk89q.worldedit.regions.selector.CylinderRegionSelector;
|
||||
import com.sk89q.worldedit.regions.selector.Polygonal2DRegionSelector;
|
||||
@ -70,7 +73,6 @@ import java.text.NumberFormat;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.annotation.Nullable;
|
||||
import org.enginehub.piston.inject.InjectedValueAccess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -249,7 +251,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
||||
if (!lastState) {
|
||||
lastState = BlockTypeUtil.centralBottomLimit(state) != 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (freeStart == -1) {
|
||||
freeStart = level + BlockTypeUtil.centralTopLimit(state);
|
||||
} else {
|
||||
@ -257,13 +259,13 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
||||
double space = level + bottomLimit - freeStart;
|
||||
if (space >= height) {
|
||||
setPosition(Vector3.at(x + 0.5, freeStart, z + 0.5));
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Not enough room, reset the free position
|
||||
if (bottomLimit != 1) {
|
||||
freeStart = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
freeStart = -1;
|
||||
lastState = true;
|
||||
@ -414,7 +416,7 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
||||
session.setBlock(spot, BlockTypes.GLASS.getDefaultState());
|
||||
} catch (MaxChangedBlocksException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setFlying(true);
|
||||
}
|
||||
@ -477,6 +479,23 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
||||
return getBlockTrace(range, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the block target block until the current block is a free
|
||||
* @return true if a free spot is found
|
||||
*/
|
||||
private boolean advanceToFree(TargetBlock hitBlox) {
|
||||
Location curBlock;
|
||||
while ((curBlock = hitBlox.getCurrentBlock()) != null) {
|
||||
if (canPassThroughBlock(curBlock)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
hitBlox.getNextBlock();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getSolidBlockTrace(int range) {
|
||||
TargetBlock tb = new TargetBlock(this, range, 0.2);
|
||||
@ -541,33 +560,17 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Advances the block target block until the current block is a free
|
||||
* @return true if a free spot is found
|
||||
*/
|
||||
private boolean advanceToFree(TargetBlock hitBlox) {
|
||||
Location curBlock;
|
||||
while ((curBlock = hitBlox.getCurrentBlock()) != null) {
|
||||
if (canPassThroughBlock(curBlock)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
hitBlox.getNextBlock();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passThroughForwardWall(int range) {
|
||||
TargetBlock hitBlox = new TargetBlock(this, range, 0.2);
|
||||
|
||||
if (!advanceToWall(hitBlox)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!advanceToFree(hitBlox)) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
Location foundBlock = hitBlox.getCurrentBlock();
|
||||
@ -576,7 +579,6 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,11 @@
|
||||
|
||||
package com.sk89q.worldedit.extension.platform;
|
||||
|
||||
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
import com.sk89q.worldedit.world.registry.BlockRegistry;
|
||||
|
||||
/**
|
||||
* A collection of capabilities that a {@link Platform} may support.
|
||||
*/
|
||||
@ -73,7 +78,22 @@ public enum Capability {
|
||||
/**
|
||||
* The capability of a platform to perform modifications to a world.
|
||||
*/
|
||||
WORLD_EDITING;
|
||||
WORLD_EDITING {
|
||||
@Override
|
||||
void initialize(PlatformManager platformManager, Platform platform) {
|
||||
BlockRegistry blockRegistry = platform.getRegistries().getBlockRegistry();
|
||||
for (BlockType type : BlockType.REGISTRY) {
|
||||
for (BlockState state : type.getAllStates()) {
|
||||
BlockStateIdAccess.register(state, blockRegistry.getInternalBlockStateId(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void unload(PlatformManager platformManager, Platform platform) {
|
||||
BlockStateIdAccess.clear();
|
||||
}
|
||||
};
|
||||
|
||||
void initialize(PlatformManager platformManager, Platform platform) {
|
||||
|
||||
|
@ -321,45 +321,45 @@ public class PlatformManager {
|
||||
if (!(actor instanceof Player)) {
|
||||
return;
|
||||
}
|
||||
Player player = (Player) actor;
|
||||
LocalSession session = worldEdit.getSessionManager().get(actor);
|
||||
Player player = (Player) actor;
|
||||
LocalSession session = worldEdit.getSessionManager().get(actor);
|
||||
|
||||
Request.reset();
|
||||
Request.request().setSession(session);
|
||||
Request.request().setWorld(player.getWorld());
|
||||
Request.reset();
|
||||
Request.request().setSession(session);
|
||||
Request.request().setWorld(player.getWorld());
|
||||
|
||||
try {
|
||||
try {
|
||||
Vector3 vector = location.toVector();
|
||||
|
||||
VirtualWorld virtual = session.getVirtualWorld();
|
||||
if (virtual != null) {
|
||||
virtual.handleBlockInteract(player, vector.toBlockPoint(), event);
|
||||
if (event.isCancelled()) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getType() == Interaction.HIT) {
|
||||
// superpickaxe is special because its primary interaction is a left click, not a right click
|
||||
// in addition, it is implicitly bound to all pickaxe items, not just a single tool item
|
||||
if (session.hasSuperPickAxe() && player.isHoldingPickAxe()) {
|
||||
final BlockTool superPickaxe = session.getSuperPickaxe();
|
||||
if (superPickaxe != null && superPickaxe.canUse(player)) {
|
||||
final BlockTool superPickaxe = session.getSuperPickaxe();
|
||||
if (superPickaxe != null && superPickaxe.canUse(player)) {
|
||||
player.runAction(() -> reset(superPickaxe)
|
||||
.actPrimary(queryCapability(Capability.WORLD_EDITING),
|
||||
getConfiguration(), player, session, location), false, true);
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tool tool = session.getTool(player);
|
||||
if (tool instanceof DoubleActionBlockTool && tool.canUse(player)) {
|
||||
player.runAction(() -> reset(((DoubleActionBlockTool) tool))
|
||||
.actSecondary(queryCapability(Capability.WORLD_EDITING),
|
||||
getConfiguration(), player, session, location), false, true);
|
||||
event.setCancelled(true);
|
||||
}
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
} else if (event.getType() == Interaction.OPEN) {
|
||||
} else if (event.getType() == Interaction.OPEN) {
|
||||
Tool tool = session.getTool(player);
|
||||
if (tool instanceof BlockTool && tool.canUse(player)) {
|
||||
if (player.checkAction()) {
|
||||
@ -367,20 +367,20 @@ public class PlatformManager {
|
||||
BlockTool blockTool = (BlockTool) tool;
|
||||
if (!(tool instanceof BrushTool)) {
|
||||
blockTool = reset(blockTool);
|
||||
}
|
||||
}
|
||||
blockTool.actPrimary(queryCapability(Capability.WORLD_EDITING),
|
||||
getConfiguration(), player, session, location);
|
||||
}, false, true);
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
handleThrowable(e, actor);
|
||||
} finally {
|
||||
Request.reset();
|
||||
} finally {
|
||||
Request.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleThrowable(Throwable e, Actor actor) {
|
||||
FaweException faweException = FaweException.get(e);
|
||||
@ -409,53 +409,29 @@ public class PlatformManager {
|
||||
try {
|
||||
switch (event.getInputType()) {
|
||||
case PRIMARY: {
|
||||
if (getConfiguration().navigationWandMaxDistance > 0 && player.getItemInHand(HandSide.MAIN_HAND).getType().getId().equals(getConfiguration().navigationWand)) {
|
||||
if (!player.hasPermission("worldedit.navigation.jumpto.tool")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location pos = player.getSolidBlockTrace(getConfiguration().navigationWandMaxDistance);
|
||||
if (pos != null) {
|
||||
player.findFreePosition(pos);
|
||||
} else {
|
||||
player.printError(BBC.NO_BLOCK.s());
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
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));
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SECONDARY: {
|
||||
if (getConfiguration().navigationWandMaxDistance > 0 && player.getItemInHand(HandSide.MAIN_HAND).getType().getId().equals(getConfiguration().navigationWand)) {
|
||||
if (!player.hasPermission("worldedit.navigation.thru.tool")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.passThroughForwardWall(40)) {
|
||||
player.printError(BBC.NAVIGATION_WAND_ERROR.s());
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
Tool tool = session.getTool(player);
|
||||
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);
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -221,4 +221,9 @@ public class PlayerProxy extends AbstractPlayerActor {
|
||||
public Player getBasePlayer() {
|
||||
return basePlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void floatAt(int x, int y, int z, boolean alwaysGlass) {
|
||||
basePlayer.floatAt(x, y, z, alwaysGlass);
|
||||
}
|
||||
}
|
||||
|
@ -572,7 +572,7 @@ public interface Extent extends InputExtent, OutputExtent {
|
||||
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||
*/
|
||||
default <B extends BlockStateHolder<B>> int replaceBlocks(Region region, Set<BaseBlock> filter, B replacement) throws MaxChangedBlocksException {
|
||||
return replaceBlocks(region, filter, new BlockPattern(replacement));
|
||||
return replaceBlocks(region, filter, (Pattern) replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,7 +29,6 @@ import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.function.mask.Masks;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@ -74,26 +73,6 @@ public class ExtentBuffer extends AbstractBufferingExtent {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlock(BlockVector3 position) {
|
||||
if (mask.test(position)) {
|
||||
return getOrDefault(position).toImmutableState();
|
||||
}
|
||||
return super.getBlock(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock getFullBlock(BlockVector3 position) {
|
||||
if (mask.test(position)) {
|
||||
return getOrDefault(position);
|
||||
}
|
||||
return super.getFullBlock(position);
|
||||
}
|
||||
|
||||
private BaseBlock getOrDefault(BlockVector3 position) {
|
||||
return buffer.computeIfAbsent(position, (pos -> getExtent().getFullBlock(pos)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends BlockStateHolder<T>> boolean setBlock(BlockVector3 location, T block) throws WorldEditException {
|
||||
if (mask.test(location)) {
|
||||
|
@ -31,12 +31,12 @@ import com.sk89q.worldedit.function.pattern.BiomePattern;
|
||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.regions.AbstractRegion;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
import com.sk89q.worldedit.regions.RegionOperationException;
|
||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||
import com.sk89q.worldedit.world.biome.BiomeTypes;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.regions.AbstractFlatRegion;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
import java.util.Iterator;
|
||||
@ -84,7 +84,6 @@ public class ForgetfulExtentBuffer extends AbstractDelegateExtent implements Pat
|
||||
*/
|
||||
public ForgetfulExtentBuffer(Extent delegate, Mask mask) {
|
||||
super(delegate);
|
||||
checkNotNull(delegate);
|
||||
checkNotNull(mask);
|
||||
this.mask = mask;
|
||||
Mask2D bmask = mask.toMask2D();
|
||||
@ -189,7 +188,7 @@ public class ForgetfulExtentBuffer extends AbstractDelegateExtent implements Pat
|
||||
* @return a region
|
||||
*/
|
||||
public Region asRegion() {
|
||||
return new AbstractRegion(null) {
|
||||
return new AbstractFlatRegion(null) {
|
||||
@Override
|
||||
public BlockVector3 getMinimumPoint() {
|
||||
return min != null ? min : BlockVector3.ZERO;
|
||||
@ -220,6 +219,7 @@ public class ForgetfulExtentBuffer extends AbstractDelegateExtent implements Pat
|
||||
return buffer.keySet().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<BlockVector2> asFlatRegion() {
|
||||
return biomeBuffer.keySet();
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
@ -44,6 +45,15 @@ public interface ClipboardReader extends Closeable {
|
||||
return read(UUID.randomUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DataVersion from a file (if possible).
|
||||
*
|
||||
* @return The data version, or empty
|
||||
*/
|
||||
default OptionalInt getDataVersion() {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
default Clipboard read(UUID uuid) throws IOException {
|
||||
return read(uuid, DiskOptimizedClipboard::new);
|
||||
}
|
||||
|
@ -19,8 +19,9 @@
|
||||
|
||||
package com.sk89q.worldedit.extent.clipboard.io;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import com.sk89q.jnbt.ByteArrayTag;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.IntTag;
|
||||
@ -59,6 +60,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Reads schematic files that are compatible with MCEdit and other editors.
|
||||
@ -176,7 +179,8 @@ public class MCEditSchematicReader extends NBTSchematicReader {
|
||||
}
|
||||
|
||||
// Need to pull out tile entities
|
||||
List<Tag> tileEntities = requireTag(schematic, "TileEntities", ListTag.class).getValue();
|
||||
final ListTag tileEntityTag = getTag(schematic, "TileEntities", ListTag.class);
|
||||
List<Tag> tileEntities = tileEntityTag == null ? new ArrayList<>() : tileEntityTag.getValue();
|
||||
Map<BlockVector3, Map<String, Tag>> tileEntitiesMap = new HashMap<>();
|
||||
Map<BlockVector3, BlockState> blockStates = new HashMap<>();
|
||||
|
||||
@ -206,6 +210,11 @@ public class MCEditSchematicReader extends NBTSchematicReader {
|
||||
if (values.isEmpty()) {
|
||||
t = null;
|
||||
}
|
||||
if (values.isEmpty()) {
|
||||
t = null;
|
||||
} else {
|
||||
t = new CompoundTag(values);
|
||||
}
|
||||
|
||||
if (fixer != null && t != null) {
|
||||
t = fixer.fixUp(DataFixer.FixTypes.BLOCK_ENTITY, t, -1);
|
||||
@ -221,9 +230,8 @@ public class MCEditSchematicReader extends NBTSchematicReader {
|
||||
BlockArrayClipboard clipboard = new BlockArrayClipboard(region);
|
||||
clipboard.setOrigin(origin);
|
||||
|
||||
// Don't log a torrent of errors
|
||||
int failedBlockSets = 0;
|
||||
|
||||
Set<Integer> unknownBlocks = new HashSet<>();
|
||||
for (int x = 0; x < width; ++x) {
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int z = 0; z < length; ++z) {
|
||||
@ -242,18 +250,7 @@ public class MCEditSchematicReader extends NBTSchematicReader {
|
||||
log.warn("Unknown block when pasting schematic: "
|
||||
+ blocks[index] + ":" + blockData[index] + ". Please report this issue.");
|
||||
}
|
||||
} catch (WorldEditException e) {
|
||||
switch (failedBlockSets) {
|
||||
case 0:
|
||||
log.warn("Failed to set block on a Clipboard", e);
|
||||
break;
|
||||
case 1:
|
||||
log.warn("Failed to set block on a Clipboard (again) -- no more messages will be logged", e);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
failedBlockSets++;
|
||||
} catch (WorldEditException ignored) { // BlockArrayClipboard won't throw this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,8 @@ import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||
import com.sk89q.worldedit.world.entity.EntityType;
|
||||
import com.sk89q.worldedit.world.entity.EntityTypes;
|
||||
import com.sk89q.worldedit.extension.platform.Platform;
|
||||
import com.sk89q.worldedit.extension.platform.Capability;
|
||||
import net.jpountz.lz4.LZ4BlockInputStream;
|
||||
import net.jpountz.lz4.LZ4BlockOutputStream;
|
||||
import org.slf4j.Logger;
|
||||
@ -63,7 +65,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -72,6 +73,8 @@ import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import com.sk89q.worldedit.world.storage.NBTConversions;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
/**
|
||||
* Reads schematic files using the Sponge Schematic Specification.
|
||||
@ -96,6 +99,7 @@ public class SpongeSchematicReader extends NBTSchematicReader {
|
||||
private int offsetX, offsetY, offsetZ;
|
||||
private char[] palette, biomePalette;
|
||||
private BlockVector3 min = BlockVector3.ZERO;
|
||||
private int schematicVersion = -1;
|
||||
|
||||
|
||||
/**
|
||||
@ -210,7 +214,58 @@ public class SpongeSchematicReader extends NBTSchematicReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clipboard read(UUID uuid, Function<BlockVector3, Clipboard> createOutput) throws IOException {
|
||||
public Clipboard read() throws IOException {
|
||||
CompoundTag schematicTag = getBaseTag();
|
||||
Map<String, Tag> schematic = schematicTag.getValue();
|
||||
|
||||
final Platform platform = WorldEdit.getInstance().getPlatformManager()
|
||||
.queryCapability(Capability.WORLD_EDITING);
|
||||
int liveDataVersion = platform.getDataVersion();
|
||||
|
||||
if (schematicVersion == 1) {
|
||||
dataVersion = 1631; // this is a relatively safe assumption unless someone imports a schematic from 1.12, e.g. sponge 7.1-
|
||||
fixer = platform.getDataFixer();
|
||||
return readVersion1(schematicTag);
|
||||
} else if (schematicVersion == 2) {
|
||||
dataVersion = requireTag(schematic, "DataVersion", IntTag.class).getValue();
|
||||
if (dataVersion > liveDataVersion) {
|
||||
log.warn("Schematic was made in a newer Minecraft version ({} > {}). Data may be incompatible.",
|
||||
dataVersion, liveDataVersion);
|
||||
} else if (dataVersion < liveDataVersion) {
|
||||
fixer = platform.getDataFixer();
|
||||
if (fixer != null) {
|
||||
log.debug("Schematic was made in an older Minecraft version ({} < {}), will attempt DFU.",
|
||||
dataVersion, liveDataVersion);
|
||||
} else {
|
||||
log.info("Schematic was made in an older Minecraft version ({} < {}), but DFU is not available. Data may be incompatible.",
|
||||
dataVersion, liveDataVersion);
|
||||
}
|
||||
}
|
||||
|
||||
BlockArrayClipboard clip = readVersion1(schematicTag);
|
||||
return readVersion2(clip, schematicTag);
|
||||
}
|
||||
throw new IOException("This schematic version is currently not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt getDataVersion() {
|
||||
try {
|
||||
CompoundTag schematicTag = getBaseTag();
|
||||
Map<String, Tag> schematic = schematicTag.getValue();
|
||||
if (schematicVersion == 1) {
|
||||
return OptionalInt.of(1631);
|
||||
} else if (schematicVersion == 2) {
|
||||
return OptionalInt.of(requireTag(schematic, "DataVersion", IntTag.class).getValue());
|
||||
}
|
||||
return OptionalInt.empty();
|
||||
} catch (IOException e) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clipboard getBaseTag(UUID uuid, Function<BlockVector3, Clipboard> createOutput) throws IOException {
|
||||
StreamDelegate root = createDelegate();
|
||||
inputStream.readNamedTagLazy(root);
|
||||
if (blocks != null) blocks.close();
|
||||
@ -349,6 +404,106 @@ public class SpongeSchematicReader extends NBTSchematicReader {
|
||||
return clipboard;
|
||||
}
|
||||
|
||||
private Clipboard readVersion2(BlockArrayClipboard version1, CompoundTag schematicTag) throws IOException {
|
||||
Map<String, Tag> schematic = schematicTag.getValue();
|
||||
if (schematic.containsKey("BiomeData")) {
|
||||
readBiomes(version1, schematic);
|
||||
}
|
||||
if (schematic.containsKey("Entities")) {
|
||||
readEntities(version1, schematic);
|
||||
}
|
||||
return version1;
|
||||
}
|
||||
|
||||
private void readBiomes(BlockArrayClipboard clipboard, Map<String, Tag> schematic) throws IOException {
|
||||
ByteArrayTag dataTag = requireTag(schematic, "BiomeData", ByteArrayTag.class);
|
||||
IntTag maxTag = requireTag(schematic, "BiomePaletteMax", IntTag.class);
|
||||
CompoundTag paletteTag = requireTag(schematic, "BiomePalette", CompoundTag.class);
|
||||
|
||||
Map<Integer, BiomeType> palette = new HashMap<>();
|
||||
if (maxTag.getValue() != paletteTag.getValue().size()) {
|
||||
throw new IOException("Biome palette size does not match expected size.");
|
||||
}
|
||||
|
||||
for (Entry<String, Tag> palettePart : paletteTag.getValue().entrySet()) {
|
||||
String key = palettePart.getKey();
|
||||
if (fixer != null) {
|
||||
key = fixer.fixUp(DataFixer.FixTypes.BIOME, key, dataVersion);
|
||||
}
|
||||
BiomeType biome = BiomeTypes.get(key);
|
||||
if (biome == null) {
|
||||
log.warn("Unknown biome type :" + key +
|
||||
" in palette. Are you missing a mod or using a schematic made in a newer version of Minecraft?");
|
||||
}
|
||||
Tag idTag = palettePart.getValue();
|
||||
if (!(idTag instanceof IntTag)) {
|
||||
throw new IOException("Biome mapped to non-Int tag.");
|
||||
}
|
||||
palette.put(((IntTag) idTag).getValue(), biome);
|
||||
}
|
||||
|
||||
int width = clipboard.getDimensions().getX();
|
||||
|
||||
byte[] biomes = dataTag.getValue();
|
||||
int biomeIndex = 0;
|
||||
int biomeJ = 0;
|
||||
int bVal;
|
||||
int varIntLength;
|
||||
BlockVector2 min = clipboard.getMinimumPoint().toBlockVector2();
|
||||
while (biomeJ < biomes.length) {
|
||||
bVal = 0;
|
||||
varIntLength = 0;
|
||||
|
||||
while (true) {
|
||||
bVal |= (biomes[biomeJ] & 127) << (varIntLength++ * 7);
|
||||
if (varIntLength > 5) {
|
||||
throw new IOException("VarInt too big (probably corrupted data)");
|
||||
}
|
||||
if (((biomes[biomeJ] & 128) != 128)) {
|
||||
biomeJ++;
|
||||
break;
|
||||
}
|
||||
biomeJ++;
|
||||
}
|
||||
int z = biomeIndex / width;
|
||||
int x = biomeIndex % width;
|
||||
BiomeType type = palette.get(bVal);
|
||||
clipboard.setBiome(min.add(x, z), type);
|
||||
biomeIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private void readEntities(BlockArrayClipboard clipboard, Map<String, Tag> schematic) throws IOException {
|
||||
List<Tag> entList = requireTag(schematic, "Entities", ListTag.class).getValue();
|
||||
if (entList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Tag et : entList) {
|
||||
if (!(et instanceof CompoundTag)) {
|
||||
continue;
|
||||
}
|
||||
CompoundTag entityTag = (CompoundTag) et;
|
||||
Map<String, Tag> tags = entityTag.getValue();
|
||||
String id = requireTag(tags, "Id", StringTag.class).getValue();
|
||||
entityTag = entityTag.createBuilder().putString("id", id).remove("Id").build();
|
||||
|
||||
if (fixer != null) {
|
||||
entityTag = fixer.fixUp(DataFixer.FixTypes.ENTITY, entityTag, dataVersion);
|
||||
}
|
||||
|
||||
EntityType entityType = EntityTypes.get(id);
|
||||
if (entityType != null) {
|
||||
Location location = NBTConversions.toLocation(clipboard,
|
||||
requireTag(tags, "Pos", ListTag.class),
|
||||
requireTag(tags, "Rotation", ListTag.class));
|
||||
BaseEntity state = new BaseEntity(entityType, entityTag);
|
||||
clipboard.createEntity(location, state);
|
||||
} else {
|
||||
log.warn("Unknown entity when pasting schematic: " + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
inputStream.close();
|
||||
|
@ -20,13 +20,16 @@
|
||||
package com.sk89q.worldedit.extent.clipboard.io;
|
||||
|
||||
import com.boydti.fawe.jnbt.streamer.IntValueReader;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.boydti.fawe.object.FaweOutputStream;
|
||||
import com.boydti.fawe.object.clipboard.LinearClipboard;
|
||||
import com.boydti.fawe.util.IOUtil;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.IntArrayTag;
|
||||
import com.sk89q.jnbt.ListTag;
|
||||
import com.sk89q.jnbt.FloatTag;
|
||||
import com.sk89q.jnbt.DoubleTag;
|
||||
import com.sk89q.jnbt.NBTConstants;
|
||||
import com.sk89q.jnbt.NBTOutputStream;
|
||||
import com.sk89q.jnbt.StringTag;
|
||||
@ -61,7 +64,9 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.sk89q.worldedit.math.Vector3;
|
||||
import java.util.Objects;
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
@ -334,6 +339,90 @@ public class SpongeSchematicWriter implements ClipboardWriter {
|
||||
schematic.writeNamedTag("Entities", new ListTag(CompoundTag.class, entities));
|
||||
}
|
||||
|
||||
private void writeBiomes(Clipboard clipboard, Map<String, Tag> schematic) {
|
||||
BlockVector3 min = clipboard.getMinimumPoint();
|
||||
int width = clipboard.getRegion().getWidth();
|
||||
int length = clipboard.getRegion().getLength();
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(width * length);
|
||||
|
||||
int paletteMax = 0;
|
||||
Map<String, Integer> palette = new HashMap<>();
|
||||
|
||||
for (int z = 0; z < length; z++) {
|
||||
int z0 = min.getBlockZ() + z;
|
||||
for (int x = 0; x < width; x++) {
|
||||
int x0 = min.getBlockX() + x;
|
||||
BlockVector2 pt = BlockVector2.at(x0, z0);
|
||||
BiomeType biome = clipboard.getBiome(pt);
|
||||
|
||||
String biomeKey = biome.getId();
|
||||
int biomeId;
|
||||
if (palette.containsKey(biomeKey)) {
|
||||
biomeId = palette.get(biomeKey);
|
||||
} else {
|
||||
biomeId = paletteMax;
|
||||
palette.put(biomeKey, biomeId);
|
||||
paletteMax++;
|
||||
}
|
||||
|
||||
while ((biomeId & -128) != 0) {
|
||||
buffer.write(biomeId & 127 | 128);
|
||||
biomeId >>>= 7;
|
||||
}
|
||||
buffer.write(biomeId);
|
||||
}
|
||||
}
|
||||
|
||||
schematic.put("BiomePaletteMax", new IntTag(paletteMax));
|
||||
|
||||
Map<String, Tag> paletteTag = new HashMap<>();
|
||||
palette.forEach((key, value) -> paletteTag.put(key, new IntTag(value)));
|
||||
|
||||
schematic.put("BiomePalette", new CompoundTag(paletteTag));
|
||||
schematic.put("BiomeData", new ByteArrayTag(buffer.toByteArray()));
|
||||
}
|
||||
|
||||
private void writeEntities(Clipboard clipboard, Map<String, Tag> schematic) {
|
||||
List<CompoundTag> entities = clipboard.getEntities().stream().map(e -> {
|
||||
BaseEntity state = e.getState();
|
||||
if (state == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Tag> values = Maps.newHashMap();
|
||||
CompoundTag rawData = state.getNbtData();
|
||||
if (rawData != null) {
|
||||
values.putAll(rawData.getValue());
|
||||
}
|
||||
values.remove("id");
|
||||
values.put("Id", new StringTag(state.getType().getId()));
|
||||
final Location location = e.getLocation();
|
||||
values.put("Pos", writeVector(location.toVector()));
|
||||
values.put("Rotation", writeRotation(location));
|
||||
|
||||
return new CompoundTag(values);
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
if (entities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
schematic.put("Entities", new ListTag(CompoundTag.class, entities));
|
||||
}
|
||||
|
||||
private Tag writeVector(Vector3 vector) {
|
||||
List<DoubleTag> list = new ArrayList<>();
|
||||
list.add(new DoubleTag(vector.getX()));
|
||||
list.add(new DoubleTag(vector.getY()));
|
||||
list.add(new DoubleTag(vector.getZ()));
|
||||
return new ListTag(DoubleTag.class, list);
|
||||
}
|
||||
|
||||
private Tag writeRotation(Location location) {
|
||||
List<FloatTag> list = new ArrayList<>();
|
||||
list.add(new FloatTag(location.getYaw()));
|
||||
list.add(new FloatTag(location.getPitch()));
|
||||
return new ListTag(FloatTag.class, list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
outputStream.close();
|
||||
|
@ -25,7 +25,6 @@ import com.sk89q.worldedit.extent.AbstractBufferingExtent;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.function.operation.Operation;
|
||||
import com.sk89q.worldedit.function.operation.RunContext;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.math.RegionOptimizedComparator;
|
||||
import com.sk89q.worldedit.util.collection.BlockMap;
|
||||
|
@ -18,8 +18,6 @@
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.extent.transform;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.sk89q.worldedit.util.Direction.ASCENDING_EAST;
|
||||
import static com.sk89q.worldedit.util.Direction.ASCENDING_NORTH;
|
||||
import static com.sk89q.worldedit.util.Direction.ASCENDING_SOUTH;
|
||||
@ -69,8 +67,11 @@ import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||
import java.util.HashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Transforms blocks themselves (but not their position) according to a
|
||||
* given transform.
|
||||
|
@ -24,8 +24,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
|
||||
/**
|
||||
@ -67,4 +69,12 @@ public class ChunkLoadingExtent extends AbstractDelegateExtent {
|
||||
}
|
||||
return super.setBlock(location, block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBiome(BlockVector2 position, BiomeType biome) {
|
||||
if (enabled) {
|
||||
world.checkLoadedChunk(position.toBlockVector3());
|
||||
}
|
||||
return super.setBiome(position, biome);
|
||||
}
|
||||
}
|
||||
|
@ -59,23 +59,23 @@ public class ForestGenerator implements RegionFunction {
|
||||
case BlockID.DIRT:
|
||||
case BlockID.PODZOL:
|
||||
case BlockID.COARSE_DIRT:
|
||||
return treeType.generate(editSession, position.add(0, 1, 0));
|
||||
return treeType.generate(editSession, position.add(0, 1, 0));
|
||||
default:
|
||||
if (t.getMaterial().isReplacedDuringPlacement()) {
|
||||
// since the implementation's tree generators generally don't generate in non-air spots,
|
||||
// we trick editsession history here in the first call
|
||||
editSession.setBlock(position, BlockTypes.AIR.getDefaultState());
|
||||
// and then trick the generator here by directly setting into the world
|
||||
editSession.getWorld().setBlock(position, BlockTypes.AIR.getDefaultState());
|
||||
// so that now the generator can generate the tree
|
||||
boolean success = treeType.generate(editSession, position);
|
||||
if (!success) {
|
||||
editSession.setBlock(position, block); // restore on failure
|
||||
}
|
||||
return success;
|
||||
} else { // Trees won't grow on this!
|
||||
return false;
|
||||
if (t.getMaterial().isReplacedDuringPlacement()) {
|
||||
// since the implementation's tree generators generally don't generate in non-air spots,
|
||||
// we trick editsession history here in the first call
|
||||
editSession.setBlock(position, BlockTypes.AIR.getDefaultState());
|
||||
// and then trick the generator here by directly setting into the world
|
||||
editSession.getWorld().setBlock(position, BlockTypes.AIR.getDefaultState());
|
||||
// so that now the generator can generate the tree
|
||||
boolean success = treeType.generate(editSession, position);
|
||||
if (!success) {
|
||||
editSession.setBlock(position, block); // restore on failure
|
||||
}
|
||||
return success;
|
||||
} else { // Trees won't grow on this!
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ import com.sk89q.worldedit.function.RegionFunction;
|
||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
|
||||
import java.util.Random;
|
||||
@ -39,6 +38,7 @@ public class GardenPatchGenerator implements RegionFunction {
|
||||
private final Random random = new Random();
|
||||
private final EditSession editSession;
|
||||
private Pattern plant = getPumpkinPattern();
|
||||
private Pattern leafPattern = BlockTypes.OAK_LEAVES.getDefaultState().with(BlockTypes.OAK_LEAVES.getProperty("persistent"), true);
|
||||
private int affected;
|
||||
|
||||
/**
|
||||
@ -96,7 +96,7 @@ public class GardenPatchGenerator implements RegionFunction {
|
||||
}
|
||||
}
|
||||
|
||||
setBlockIfAir(editSession, pos, BlockTypes.OAK_LEAVES.getDefaultState());
|
||||
setBlockIfAir(editSession, pos, leafPattern);
|
||||
affected++;
|
||||
|
||||
int t = random.nextInt(4);
|
||||
@ -166,10 +166,9 @@ public class GardenPatchGenerator implements RegionFunction {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockState leavesBlock = BlockTypes.OAK_LEAVES.getDefaultState();
|
||||
|
||||
if (editSession.getBlock(position).getBlockType().getMaterial().isAir()) {
|
||||
editSession.setBlock(position, leavesBlock);
|
||||
editSession.setBlock(position, leafPattern);
|
||||
}
|
||||
|
||||
placeVine(position, position.add(0, 0, 1));
|
||||
@ -193,12 +192,12 @@ public class GardenPatchGenerator implements RegionFunction {
|
||||
* Set a block only if there's no block already there.
|
||||
*
|
||||
* @param position the position
|
||||
* @param block the block to set
|
||||
* @param pattern the pattern to set
|
||||
* @return if block was changed
|
||||
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||
*/
|
||||
private static <B extends BlockStateHolder<B>> boolean setBlockIfAir(EditSession session, BlockVector3 position, B block) throws MaxChangedBlocksException {
|
||||
return session.getBlock(position).getBlockType().getMaterial().isAir() && session.setBlock(position, block);
|
||||
private static boolean setBlockIfAir(EditSession session, BlockVector3 position, Pattern pattern) throws MaxChangedBlocksException {
|
||||
return session.getBlock(position).getBlockType().getMaterial().isAir() && session.setBlock(position, pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,8 +22,8 @@ package com.sk89q.worldedit.function.mask;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.Expression;
|
||||
import com.sk89q.worldedit.internal.expression.EvaluationException;
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
|
||||
|
||||
|
@ -22,8 +22,8 @@ package com.sk89q.worldedit.function.mask;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.Expression;
|
||||
import com.sk89q.worldedit.internal.expression.EvaluationException;
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import java.util.function.IntSupplier;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -36,16 +36,21 @@ import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.function.CombinedRegionFunction;
|
||||
import com.sk89q.worldedit.function.FlatRegionFunction;
|
||||
import com.sk89q.worldedit.function.FlatRegionMaskingFilter;
|
||||
import com.sk89q.worldedit.function.RegionFunction;
|
||||
import com.sk89q.worldedit.function.RegionMaskTestFunction;
|
||||
import com.sk89q.worldedit.function.biome.ExtentBiomeCopy;
|
||||
import com.sk89q.worldedit.function.RegionMaskingFilter;
|
||||
import com.sk89q.worldedit.function.entity.ExtentEntityCopy;
|
||||
import com.sk89q.worldedit.function.mask.Mask;
|
||||
import com.sk89q.worldedit.function.mask.Masks;
|
||||
import com.sk89q.worldedit.function.mask.Mask2D;
|
||||
import com.sk89q.worldedit.function.visitor.EntityVisitor;
|
||||
import com.sk89q.worldedit.function.visitor.IntersectRegionFunction;
|
||||
import com.sk89q.worldedit.function.visitor.RegionVisitor;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.function.visitor.FlatRegionVisitor;
|
||||
import com.sk89q.worldedit.math.transform.AffineTransform;
|
||||
import com.sk89q.worldedit.math.transform.Identity;
|
||||
import com.sk89q.worldedit.math.transform.Transform;
|
||||
@ -78,7 +83,7 @@ public class ForwardExtentCopy implements Operation {
|
||||
private RegionFunction sourceFunction = null;
|
||||
private Transform transform = new Identity();
|
||||
private Transform currentTransform = null;
|
||||
private int affected;
|
||||
private int affectedBlocks;
|
||||
private RegionFunction filterFunction;
|
||||
|
||||
/**
|
||||
@ -94,6 +99,10 @@ public class ForwardExtentCopy implements Operation {
|
||||
public ForwardExtentCopy(Extent source, Region region, Extent destination, BlockVector3 to) {
|
||||
this(source, region, region.getMinimumPoint(), destination, to);
|
||||
}
|
||||
private FlatRegionVisitor lastBiomeVisitor;
|
||||
private EntityVisitor lastEntityVisitor;
|
||||
private int affectedBiomeCols;
|
||||
private int affectedEntities;
|
||||
|
||||
/**
|
||||
* Create a new copy.
|
||||
@ -267,7 +276,7 @@ public class ForwardExtentCopy implements Operation {
|
||||
* @return the number of affected
|
||||
*/
|
||||
public int getAffected() {
|
||||
return affected;
|
||||
return affectedBlocks + affectedBiomeCols + affectedEntities;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -275,6 +284,14 @@ public class ForwardExtentCopy implements Operation {
|
||||
if (currentTransform == null) {
|
||||
currentTransform = transform;
|
||||
}
|
||||
if (lastBiomeVisitor != null) {
|
||||
affectedBiomeCols += lastBiomeVisitor.getAffected();
|
||||
lastBiomeVisitor = null;
|
||||
}
|
||||
if (lastEntityVisitor != null) {
|
||||
affectedEntities += lastEntityVisitor.getAffected();
|
||||
lastEntityVisitor = null;
|
||||
}
|
||||
|
||||
Extent finalDest = destination;
|
||||
BlockVector3 translation = to.subtract(from);
|
||||
@ -405,7 +422,21 @@ public class ForwardExtentCopy implements Operation {
|
||||
@Override
|
||||
public void addStatusMessages(List<String> messages) {
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.append(affected).append(" objects(s)");
|
||||
msg.append(affectedBlocks).append(" block(s)");
|
||||
if (affectedBiomeCols > 0) {
|
||||
if (affectedEntities > 0) {
|
||||
msg.append(", ");
|
||||
} else {
|
||||
msg.append(" and ");
|
||||
}
|
||||
msg.append(affectedBiomeCols).append(" biome(s)");
|
||||
}
|
||||
if (affectedEntities > 0) {
|
||||
if (affectedBiomeCols > 0) {
|
||||
msg.append(",");
|
||||
}
|
||||
msg.append(" and ").append(affectedEntities).append(" entities(s)");
|
||||
}
|
||||
msg.append(" affected.");
|
||||
messages.add(msg.toString());
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specifies a range of values for numbers.
|
||||
*
|
||||
* @see PrimitiveBindings a user of this annotation as a modifier
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface Range {
|
||||
|
||||
/**
|
||||
* The minimum value that the number can be at, inclusive.
|
||||
*
|
||||
* @return the minimum value
|
||||
*/
|
||||
double min() default Double.MIN_VALUE;
|
||||
|
||||
/**
|
||||
* The maximum value that the number can be at, inclusive.
|
||||
*
|
||||
* @return the maximum value
|
||||
*/
|
||||
double max() default Double.MAX_VALUE;
|
||||
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Used to validate a string.
|
||||
*
|
||||
* @see PrimitiveBindings where this validation is used
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER, ElementType.METHOD})
|
||||
public @interface Validate {
|
||||
|
||||
/**
|
||||
* An optional regular expression that must match the string.
|
||||
*
|
||||
* @see Pattern regular expression class
|
||||
* @return the pattern
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
}
|
@ -19,8 +19,6 @@
|
||||
|
||||
package com.sk89q.worldedit.internal.command.exception;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.sk89q.worldedit.DisallowedItemException;
|
||||
import com.sk89q.worldedit.EmptyClipboardException;
|
||||
@ -29,6 +27,7 @@ import com.sk89q.worldedit.InvalidItemException;
|
||||
import com.sk89q.worldedit.MaxBrushRadiusException;
|
||||
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||
import com.sk89q.worldedit.MaxRadiusException;
|
||||
import com.sk89q.worldedit.MissingWorldException;
|
||||
import com.sk89q.worldedit.UnknownDirectionException;
|
||||
import com.sk89q.worldedit.UnknownItemException;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
@ -47,6 +46,8 @@ import java.util.regex.Pattern;
|
||||
import org.enginehub.piston.exception.CommandException;
|
||||
import org.enginehub.piston.exception.UsageException;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* converts WorldEdit exceptions and converts them into {@link CommandException}s.
|
||||
*/
|
||||
|
@ -17,18 +17,19 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.runtime;
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
/**
|
||||
* Thrown when a break or continue is encountered.
|
||||
* Loop constructs catch this exception.
|
||||
*/
|
||||
public class BreakException extends EvaluationException {
|
||||
public class BreakException extends RuntimeException {
|
||||
|
||||
final boolean doContinue;
|
||||
public final boolean doContinue;
|
||||
|
||||
public BreakException(boolean doContinue) {
|
||||
super(-1, doContinue ? "'continue' encountered outside a loop" : "'break' encountered outside a loop");
|
||||
super(doContinue ? "'continue' encountered outside a loop" : "'break' encountered outside a loop",
|
||||
null, true, false);
|
||||
|
||||
this.doContinue = doContinue;
|
||||
}
|
@ -0,0 +1,625 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.sk89q.worldedit.antlr.ExpressionBaseVisitor;
|
||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
||||
import it.unimi.dsi.fastutil.doubles.Double2ObjectLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.doubles.Double2ObjectMaps;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.RuleNode;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.DoubleBinaryOperator;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.ASSIGN;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.DIVIDE;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.DIVIDE_ASSIGN;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.EQUAL;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.EXCLAMATION_MARK;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.GREATER_THAN;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.GREATER_THAN_OR_EQUAL;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.INCREMENT;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.LEFT_SHIFT;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.LESS_THAN;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.LESS_THAN_OR_EQUAL;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.MINUS;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.MINUS_ASSIGN;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.MODULO;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.MODULO_ASSIGN;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.NEAR;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.NOT_EQUAL;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.PLUS;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.PLUS_ASSIGN;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.POWER_ASSIGN;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.RIGHT_SHIFT;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.TIMES;
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.TIMES_ASSIGN;
|
||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.WRAPPED_CONSTANT;
|
||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.check;
|
||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.checkIterations;
|
||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.checkTimeout;
|
||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.evalException;
|
||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.getArgumentHandleName;
|
||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.resolveFunction;
|
||||
|
||||
class EvaluatingVisitor extends ExpressionBaseVisitor<Double> {
|
||||
|
||||
private final SlotTable slots;
|
||||
private final SetMultimap<String, MethodHandle> functions;
|
||||
|
||||
EvaluatingVisitor(SlotTable slots,
|
||||
SetMultimap<String, MethodHandle> functions) {
|
||||
this.slots = slots;
|
||||
this.functions = functions;
|
||||
}
|
||||
|
||||
private LocalSlot.Variable initVariable(String name, ParserRuleContext ctx) {
|
||||
return slots.initVariable(name)
|
||||
.orElseThrow(() -> evalException(ctx, "Cannot overwrite non-variable '" + name + "'"));
|
||||
}
|
||||
|
||||
private Supplier<EvaluationException> varNotInitException(String name, ParserRuleContext ctx) {
|
||||
return () -> evalException(ctx, "'" + name + "' is not initialized yet");
|
||||
}
|
||||
|
||||
private LocalSlot.Variable getVariable(String name, ParserRuleContext ctx) {
|
||||
LocalSlot slot = slots.getSlot(name)
|
||||
.orElseThrow(varNotInitException(name, ctx));
|
||||
check(slot instanceof LocalSlot.Variable, ctx, "'" + name + "' is not a variable");
|
||||
return (LocalSlot.Variable) slot;
|
||||
}
|
||||
|
||||
private double getSlotValue(String name, ParserRuleContext ctx) {
|
||||
return slots.getSlotValue(name)
|
||||
.orElseThrow(varNotInitException(name, ctx));
|
||||
}
|
||||
|
||||
private Token extractToken(ParserRuleContext ctx) {
|
||||
List<TerminalNode> children = ctx.children.stream()
|
||||
.filter(TerminalNode.class::isInstance)
|
||||
.map(TerminalNode.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
check(children.size() == 1, ctx, "Expected exactly one token, got " + children.size());
|
||||
return children.get(0).getSymbol();
|
||||
}
|
||||
|
||||
private Double evaluate(ParserRuleContext ctx) {
|
||||
return ctx.accept(this);
|
||||
}
|
||||
|
||||
private double evaluateForValue(ParserRuleContext ctx) {
|
||||
Double result = evaluate(ctx);
|
||||
check(result != null, ctx, "Invalid expression for a value");
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean evaluateBoolean(ParserRuleContext boolExpression) {
|
||||
Double bool = evaluate(boolExpression);
|
||||
check(bool != null, boolExpression, "Invalid expression for boolean");
|
||||
return doubleToBool(bool);
|
||||
}
|
||||
|
||||
private boolean doubleToBool(double bool) {
|
||||
return bool > 0;
|
||||
}
|
||||
|
||||
private double boolToDouble(boolean bool) {
|
||||
return bool ? 1 : 0;
|
||||
}
|
||||
|
||||
private Double evaluateConditional(ParserRuleContext condition,
|
||||
ParserRuleContext trueBranch,
|
||||
ParserRuleContext falseBranch) {
|
||||
ParserRuleContext ctx = evaluateBoolean(condition) ? trueBranch : falseBranch;
|
||||
return ctx == null ? null : evaluate(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitIfStatement(ExpressionParser.IfStatementContext ctx) {
|
||||
return evaluateConditional(ctx.condition, ctx.trueBranch, ctx.falseBranch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitTernaryExpr(ExpressionParser.TernaryExprContext ctx) {
|
||||
return evaluateConditional(ctx.condition, ctx.trueBranch, ctx.falseBranch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitWhileStatement(ExpressionParser.WhileStatementContext ctx) {
|
||||
Double result = defaultResult();
|
||||
int iterations = 0;
|
||||
while (evaluateBoolean(ctx.condition)) {
|
||||
checkIterations(iterations, ctx.body);
|
||||
checkTimeout();
|
||||
iterations++;
|
||||
try {
|
||||
result = evaluate(ctx.body);
|
||||
} catch (BreakException ex) {
|
||||
if (!ex.doContinue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitDoStatement(ExpressionParser.DoStatementContext ctx) {
|
||||
Double result = defaultResult();
|
||||
int iterations = 0;
|
||||
do {
|
||||
checkIterations(iterations, ctx.body);
|
||||
checkTimeout();
|
||||
iterations++;
|
||||
try {
|
||||
result = evaluate(ctx.body);
|
||||
} catch (BreakException ex) {
|
||||
if (!ex.doContinue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (evaluateBoolean(ctx.condition));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitForStatement(ExpressionParser.ForStatementContext ctx) {
|
||||
Double result = defaultResult();
|
||||
int iterations = 0;
|
||||
evaluate(ctx.init);
|
||||
while (evaluateBoolean(ctx.condition)) {
|
||||
checkIterations(iterations, ctx.body);
|
||||
checkTimeout();
|
||||
iterations++;
|
||||
try {
|
||||
result = evaluate(ctx.body);
|
||||
} catch (BreakException ex) {
|
||||
if (!ex.doContinue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
evaluate(ctx.update);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitSimpleForStatement(ExpressionParser.SimpleForStatementContext ctx) {
|
||||
Double result = defaultResult();
|
||||
int iterations = 0;
|
||||
double first = evaluateForValue(ctx.first);
|
||||
double last = evaluateForValue(ctx.last);
|
||||
String counter = ctx.counter.getText();
|
||||
LocalSlot.Variable variable = initVariable(counter, ctx);
|
||||
for (double i = first; i <= last; i++) {
|
||||
checkIterations(iterations, ctx.body);
|
||||
checkTimeout();
|
||||
iterations++;
|
||||
variable.setValue(i);
|
||||
try {
|
||||
result = evaluate(ctx.body);
|
||||
} catch (BreakException ex) {
|
||||
if (!ex.doContinue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitBreakStatement(ExpressionParser.BreakStatementContext ctx) {
|
||||
throw new BreakException(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitContinueStatement(ExpressionParser.ContinueStatementContext ctx) {
|
||||
throw new BreakException(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitReturnStatement(ExpressionParser.ReturnStatementContext ctx) {
|
||||
if (ctx.value != null) {
|
||||
return evaluate(ctx.value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitSwitchStatement(ExpressionParser.SwitchStatementContext ctx) {
|
||||
Double2ObjectMap<ParserRuleContext> cases = new Double2ObjectLinkedOpenHashMap<>(ctx.labels.size());
|
||||
ParserRuleContext defaultCase = null;
|
||||
for (int i = 0; i < ctx.labels.size(); i++) {
|
||||
ExpressionParser.SwitchLabelContext label = ctx.labels.get(i);
|
||||
ExpressionParser.StatementsContext body = ctx.bodies.get(i);
|
||||
if (label instanceof ExpressionParser.CaseContext) {
|
||||
ExpressionParser.CaseContext caseContext = (ExpressionParser.CaseContext) label;
|
||||
double key = evaluateForValue(caseContext.constant);
|
||||
check(!cases.containsKey(key), body, "Duplicate cases detected.");
|
||||
cases.put(key, body);
|
||||
} else {
|
||||
check(defaultCase == null, body, "Duplicate default cases detected.");
|
||||
defaultCase = body;
|
||||
}
|
||||
}
|
||||
double value = evaluateForValue(ctx.target);
|
||||
boolean matched = false;
|
||||
Double evaluated = null;
|
||||
boolean falling = false;
|
||||
for (Double2ObjectMap.Entry<ParserRuleContext> entry : Double2ObjectMaps.fastIterable(cases)) {
|
||||
if (falling || entry.getDoubleKey() == value) {
|
||||
matched = true;
|
||||
try {
|
||||
evaluated = evaluate(entry.getValue());
|
||||
falling = true;
|
||||
} catch (BreakException brk) {
|
||||
check(!brk.doContinue, entry.getValue(), "Cannot continue in a switch");
|
||||
falling = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// This if is like the one in the loop, default's "case" is `!matched` & present
|
||||
if ((falling || !matched) && defaultCase != null) {
|
||||
try {
|
||||
evaluated = evaluate(defaultCase);
|
||||
} catch (BreakException brk) {
|
||||
check(!brk.doContinue, defaultCase, "Cannot continue in a switch");
|
||||
}
|
||||
}
|
||||
return evaluated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitExpressionStatement(ExpressionParser.ExpressionStatementContext ctx) {
|
||||
return evaluate(ctx.expression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitPostCrementExpr(ExpressionParser.PostCrementExprContext ctx) {
|
||||
String target = ctx.target.getText();
|
||||
LocalSlot.Variable variable = getVariable(target, ctx);
|
||||
double value = variable.getValue();
|
||||
if (ctx.op.getType() == INCREMENT) {
|
||||
value++;
|
||||
} else {
|
||||
value--;
|
||||
}
|
||||
variable.setValue(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitPreCrementExpr(ExpressionParser.PreCrementExprContext ctx) {
|
||||
String target = ctx.target.getText();
|
||||
LocalSlot.Variable variable = getVariable(target, ctx);
|
||||
double value = variable.getValue();
|
||||
double result = value;
|
||||
if (ctx.op.getType() == INCREMENT) {
|
||||
value++;
|
||||
} else {
|
||||
value--;
|
||||
}
|
||||
variable.setValue(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitPlusMinusExpr(ExpressionParser.PlusMinusExprContext ctx) {
|
||||
double value = evaluateForValue(ctx.expr);
|
||||
switch (ctx.op.getType()) {
|
||||
case PLUS:
|
||||
return +value;
|
||||
case MINUS:
|
||||
return -value;
|
||||
}
|
||||
throw evalException(ctx, "Invalid text for plus/minus expr: " + ctx.op.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitNotExpr(ExpressionParser.NotExprContext ctx) {
|
||||
return boolToDouble(!evaluateBoolean(ctx.expr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitComplementExpr(ExpressionParser.ComplementExprContext ctx) {
|
||||
return (double) ~(long) evaluateForValue(ctx.expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitConditionalAndExpr(ExpressionParser.ConditionalAndExprContext ctx) {
|
||||
if (!evaluateBoolean(ctx.left)) {
|
||||
return boolToDouble(false);
|
||||
}
|
||||
return evaluateForValue(ctx.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitConditionalOrExpr(ExpressionParser.ConditionalOrExprContext ctx) {
|
||||
double left = evaluateForValue(ctx.left);
|
||||
if (doubleToBool(left)) {
|
||||
return left;
|
||||
}
|
||||
return evaluateForValue(ctx.right);
|
||||
}
|
||||
|
||||
private double evaluateBinary(ParserRuleContext left,
|
||||
ParserRuleContext right,
|
||||
DoubleBinaryOperator op) {
|
||||
return op.applyAsDouble(evaluateForValue(left), evaluateForValue(right));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitPowerExpr(ExpressionParser.PowerExprContext ctx) {
|
||||
return evaluateBinary(ctx.left, ctx.right, Math::pow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitMultiplicativeExpr(ExpressionParser.MultiplicativeExprContext ctx) {
|
||||
return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
|
||||
switch (ctx.op.getType()) {
|
||||
case TIMES:
|
||||
return l * r;
|
||||
case DIVIDE:
|
||||
return l / r;
|
||||
case MODULO:
|
||||
return l % r;
|
||||
}
|
||||
throw evalException(ctx, "Invalid text for multiplicative expr: " + ctx.op.getText());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitAddExpr(ExpressionParser.AddExprContext ctx) {
|
||||
return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
|
||||
switch (ctx.op.getType()) {
|
||||
case PLUS:
|
||||
return l + r;
|
||||
case MINUS:
|
||||
return l - r;
|
||||
}
|
||||
throw evalException(ctx, "Invalid text for additive expr: " + ctx.op.getText());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitShiftExpr(ExpressionParser.ShiftExprContext ctx) {
|
||||
return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
|
||||
switch (ctx.op.getType()) {
|
||||
case LEFT_SHIFT:
|
||||
return (double) ((long) l << (long) r);
|
||||
case RIGHT_SHIFT:
|
||||
return (double) ((long) l >> (long) r);
|
||||
}
|
||||
throw evalException(ctx, "Invalid text for shift expr: " + ctx.op.getText());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitRelationalExpr(ExpressionParser.RelationalExprContext ctx) {
|
||||
return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
|
||||
switch (ctx.op.getType()) {
|
||||
case LESS_THAN:
|
||||
return boolToDouble(l < r);
|
||||
case LESS_THAN_OR_EQUAL:
|
||||
return boolToDouble(l <= r);
|
||||
case GREATER_THAN:
|
||||
return boolToDouble(l > r);
|
||||
case GREATER_THAN_OR_EQUAL:
|
||||
return boolToDouble(l >= r);
|
||||
}
|
||||
throw evalException(ctx, "Invalid text for relational expr: " + ctx.op.getText());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitEqualityExpr(ExpressionParser.EqualityExprContext ctx) {
|
||||
return evaluateBinary(ctx.left, ctx.right, (l, r) -> {
|
||||
switch (ctx.op.getType()) {
|
||||
case EQUAL:
|
||||
return boolToDouble(l == r);
|
||||
case NOT_EQUAL:
|
||||
return boolToDouble(l != r);
|
||||
case NEAR:
|
||||
return boolToDouble(almostEqual2sComplement(l, r, 450359963L));
|
||||
case GREATER_THAN_OR_EQUAL:
|
||||
return boolToDouble(l >= r);
|
||||
}
|
||||
throw evalException(ctx, "Invalid text for equality expr: " + ctx.op.getText());
|
||||
});
|
||||
}
|
||||
|
||||
// Usable AlmostEqual function, based on http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
|
||||
private static boolean almostEqual2sComplement(double a, double b, long maxUlps) {
|
||||
// Make sure maxUlps is non-negative and small enough that the
|
||||
// default NAN won't compare as equal to anything.
|
||||
//assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); // this is for floats, not doubles
|
||||
|
||||
long aLong = Double.doubleToRawLongBits(a);
|
||||
// Make aLong lexicographically ordered as a twos-complement long
|
||||
if (aLong < 0) aLong = 0x8000000000000000L - aLong;
|
||||
|
||||
long bLong = Double.doubleToRawLongBits(b);
|
||||
// Make bLong lexicographically ordered as a twos-complement long
|
||||
if (bLong < 0) bLong = 0x8000000000000000L - bLong;
|
||||
|
||||
final long longDiff = Math.abs(aLong - bLong);
|
||||
return longDiff <= maxUlps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitPostfixExpr(ExpressionParser.PostfixExprContext ctx) {
|
||||
double value = evaluateForValue(ctx.expr);
|
||||
if (ctx.op.getType() == EXCLAMATION_MARK) {
|
||||
return factorial(value);
|
||||
}
|
||||
throw evalException(ctx,
|
||||
"Invalid text for post-unary expr: " + ctx.op.getText());
|
||||
}
|
||||
|
||||
private static final double[] factorials = new double[171];
|
||||
|
||||
static {
|
||||
factorials[0] = 1;
|
||||
for (int i = 1; i < factorials.length; ++i) {
|
||||
factorials[i] = factorials[i - 1] * i;
|
||||
}
|
||||
}
|
||||
|
||||
private static double factorial(double x) throws EvaluationException {
|
||||
final int n = (int) x;
|
||||
|
||||
if (n < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n >= factorials.length) {
|
||||
return Double.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
return factorials[n];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitAssignment(ExpressionParser.AssignmentContext ctx) {
|
||||
int type = extractToken(ctx.assignmentOperator()).getType();
|
||||
String target = ctx.target.getText();
|
||||
double value;
|
||||
double arg = evaluateForValue(ctx.expression());
|
||||
LocalSlot.Variable variable;
|
||||
if (type == ASSIGN) {
|
||||
variable = initVariable(target, ctx);
|
||||
value = arg;
|
||||
} else {
|
||||
variable = getVariable(target, ctx);
|
||||
value = variable.getValue();
|
||||
switch (type) {
|
||||
case POWER_ASSIGN:
|
||||
value = Math.pow(value, arg);
|
||||
break;
|
||||
case TIMES_ASSIGN:
|
||||
value *= arg;
|
||||
break;
|
||||
case DIVIDE_ASSIGN:
|
||||
value /= arg;
|
||||
break;
|
||||
case MODULO_ASSIGN:
|
||||
value %= arg;
|
||||
break;
|
||||
case PLUS_ASSIGN:
|
||||
value += arg;
|
||||
break;
|
||||
case MINUS_ASSIGN:
|
||||
value -= arg;
|
||||
break;
|
||||
default:
|
||||
throw evalException(ctx, "Invalid text for assign expr: " +
|
||||
ctx.assignmentOperator().getText());
|
||||
}
|
||||
}
|
||||
variable.setValue(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitFunctionCall(ExpressionParser.FunctionCallContext ctx) {
|
||||
MethodHandle handle = resolveFunction(functions, ctx);
|
||||
String fnName = ctx.name.getText();
|
||||
Object[] arguments = new Object[ctx.args.size()];
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
ExpressionParser.ExpressionContext arg = ctx.args.get(i);
|
||||
Object transformed = getArgument(fnName, handle.type(), i, arg);
|
||||
arguments[i] = transformed;
|
||||
}
|
||||
try {
|
||||
// Some methods return other Numbers
|
||||
Number number = (Number) handle.invokeWithArguments(arguments);
|
||||
return number == null ? null : number.doubleValue();
|
||||
} catch (Throwable throwable) {
|
||||
Throwables.throwIfUnchecked(throwable);
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private Object getArgument(String fnName, MethodType type, int i, ParserRuleContext arg) {
|
||||
// Pass variable handle in for modification?
|
||||
String handleName = getArgumentHandleName(fnName, type, i, arg);
|
||||
if (handleName == null) {
|
||||
return evaluateForValue(arg);
|
||||
}
|
||||
if (handleName.equals(WRAPPED_CONSTANT)) {
|
||||
return new LocalSlot.Constant(evaluateForValue(arg));
|
||||
}
|
||||
return getVariable(handleName, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitConstantExpression(ExpressionParser.ConstantExpressionContext ctx) {
|
||||
try {
|
||||
return Double.parseDouble(ctx.getText());
|
||||
} catch (NumberFormatException e) {
|
||||
// Rare, but might happen, e.g. if too many digits
|
||||
throw evalException(ctx, "Invalid constant: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitIdExpr(ExpressionParser.IdExprContext ctx) {
|
||||
String source = ctx.source.getText();
|
||||
return getSlotValue(source, ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double visitChildren(RuleNode node) {
|
||||
Double result = defaultResult();
|
||||
int n = node.getChildCount();
|
||||
for (int i = 0; i < n; i++) {
|
||||
ParseTree c = node.getChild(i);
|
||||
if (c instanceof TerminalNode && ((TerminalNode) c).getSymbol().getType() == Token.EOF) {
|
||||
break;
|
||||
}
|
||||
|
||||
Double childResult = c.accept(this);
|
||||
if (c instanceof ExpressionParser.ReturnStatementContext) {
|
||||
return childResult;
|
||||
}
|
||||
result = aggregateResult(result, childResult);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Double aggregateResult(Double aggregate, Double nextResult) {
|
||||
return Optional.ofNullable(nextResult).orElse(aggregate);
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.runtime;
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
|
@ -19,26 +19,28 @@
|
||||
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.Lexer;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.Token;
|
||||
import com.sk89q.worldedit.internal.expression.parser.Parser;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Constant;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.ExpressionEnvironment;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.ExpressionTimeoutException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Functions;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.RValue;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.ReturnException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Variable;
|
||||
import com.sk89q.worldedit.antlr.ExpressionLexer;
|
||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.misc.ParseCancellationException;
|
||||
import com.sk89q.worldedit.session.request.Request;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.HashMap;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
@ -68,61 +70,64 @@ import java.util.concurrent.TimeoutException;
|
||||
* If you wish to run the equation multiple times, you can then optimize it,
|
||||
* by calling {@link #optimize()}. You can then run the equation as many times
|
||||
* as you want by calling {@link #evaluate(double...)}. You do not need to
|
||||
* pass values for all variables specified while compiling.
|
||||
* To query variables after evaluation, you can use
|
||||
* {@link #getVariable(String, boolean)}. To get a value out of these, use
|
||||
* {@link Variable#getValue()}.</p>
|
||||
*
|
||||
* <p>Variables are also supported and can be set either by passing values
|
||||
* to {@link #evaluate(double...)}.</p>
|
||||
* pass values for all slots specified while compiling.
|
||||
* To query slots after evaluation, you can use the {@linkplain #getSlots() slot table}.
|
||||
*/
|
||||
public class Expression {
|
||||
|
||||
private static final ThreadLocal<ArrayDeque<Expression>> instance = ThreadLocal.withInitial(ArrayDeque::new);
|
||||
private static final ExecutorService evalThread = Executors.newCachedThreadPool(
|
||||
new ThreadFactoryBuilder()
|
||||
.setDaemon(true)
|
||||
.setNameFormat("worldedit-expression-eval-%d")
|
||||
.build());
|
||||
private static final ExecutorService evalThread = Executors.newFixedThreadPool(
|
||||
Runtime.getRuntime().availableProcessors(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setDaemon(true)
|
||||
.setNameFormat("worldedit-expression-eval-%d")
|
||||
.build());
|
||||
|
||||
private final Map<String, RValue> variables = new HashMap<>();
|
||||
private final SlotTable slots = new SlotTable();
|
||||
private final List<String> providedSlots;
|
||||
private Variable[] variableArray;
|
||||
private RValue root;
|
||||
private final Functions functions = new Functions();
|
||||
private ExpressionParser.AllStatementsContext root;
|
||||
private final SetMultimap<String, MethodHandle> functions = Functions.getFunctionMap();
|
||||
private ExpressionEnvironment environment;
|
||||
|
||||
public static Expression compile(String expression, String... variableNames) throws ExpressionException {
|
||||
return new Expression(expression, variableNames);
|
||||
}
|
||||
|
||||
private Expression(String expression, String... variableNames) throws ExpressionException {
|
||||
slots.putSlot("e", new LocalSlot.Constant(Math.E));
|
||||
slots.putSlot("pi", new LocalSlot.Constant(Math.PI));
|
||||
slots.putSlot("true", new LocalSlot.Constant(1));
|
||||
slots.putSlot("false", new LocalSlot.Constant(0));
|
||||
|
||||
for (String variableName : variableNames) {
|
||||
slots.initVariable(variableName)
|
||||
.orElseThrow(() -> new ExpressionException(-1,
|
||||
"Tried to overwrite identifier '" + variableName + "'"));
|
||||
}
|
||||
this.providedSlots = ImmutableList.copyOf(variableNames);
|
||||
|
||||
CharStream cs = CharStreams.fromString(expression, "<input>");
|
||||
ExpressionLexer lexer = new ExpressionLexer(cs);
|
||||
lexer.removeErrorListeners();
|
||||
lexer.addErrorListener(new LexerErrorListener());
|
||||
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
ExpressionParser parser = new ExpressionParser(tokens);
|
||||
parser.removeErrorListeners();
|
||||
parser.addErrorListener(new ParserErrorListener());
|
||||
try {
|
||||
root = parser.allStatements();
|
||||
Objects.requireNonNull(root, "Unable to parse root, but no exceptions?");
|
||||
} catch (ParseCancellationException e) {
|
||||
throw new ParserException(parser.getState(), e);
|
||||
}
|
||||
ParseTreeWalker.DEFAULT.walk(new ExpressionValidator(slots.keySet(), functions), root);
|
||||
}
|
||||
|
||||
public Expression(double constant) {
|
||||
root = new Constant(0, constant);
|
||||
}
|
||||
|
||||
private Expression(String expression, String... variableNames) throws ExpressionException {
|
||||
this(Lexer.tokenize(expression), variableNames);
|
||||
}
|
||||
|
||||
private Expression(List<Token> tokens, String... variableNames) throws ExpressionException {
|
||||
|
||||
variables.put("e", new Constant(-1, Math.E));
|
||||
variables.put("pi", new Constant(-1, Math.PI));
|
||||
variables.put("true", new Constant(-1, 1));
|
||||
variables.put("false", new Constant(-1, 0));
|
||||
|
||||
variableArray = new Variable[variableNames.length];
|
||||
for (int i = 0; i < variableNames.length; i++) {
|
||||
if (variables.containsKey(variableNames[i])) {
|
||||
throw new ExpressionException(-1, "Tried to overwrite identifier '" + variableNames[i] + "'");
|
||||
}
|
||||
Variable var = new Variable(0);
|
||||
variables.put(variableNames[i], var);
|
||||
variableArray[i] = var;
|
||||
}
|
||||
|
||||
root = Parser.parse(tokens, this);
|
||||
}
|
||||
|
||||
public double evaluate(double x, double y, double z) throws EvaluationException {
|
||||
return evaluateTimeout(WorldEdit.getInstance().getConfiguration().calculationTimeout, x, y, z);
|
||||
}
|
||||
@ -157,10 +162,19 @@ public class Expression {
|
||||
return root.getValue();
|
||||
}
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
Variable var = variableArray[i];
|
||||
var.value = values[i];
|
||||
String slotName = providedSlots.get(i);
|
||||
LocalSlot.Variable slot = slots.getVariable(slotName)
|
||||
.orElseThrow(() -> new EvaluationException(-1,
|
||||
"Tried to assign to non-variable " + slotName + "."));
|
||||
|
||||
slot.setValue(values[i]);
|
||||
}
|
||||
return evaluateFinal(timeout);
|
||||
|
||||
// evaluation exceptions are thrown out of this method
|
||||
if (timeout < 0) {
|
||||
return evaluateRoot();
|
||||
}
|
||||
return evaluateRootTimed(timeout);
|
||||
}
|
||||
|
||||
private double evaluateFinal(int timeout) throws EvaluationException {
|
||||
@ -175,6 +189,7 @@ public class Expression {
|
||||
}
|
||||
|
||||
private double evaluateRootTimed(int timeout) throws EvaluationException {
|
||||
CountDownLatch startLatch = new CountDownLatch(1);
|
||||
Request request = Request.request();
|
||||
Future<Double> result = evalThread.submit(() -> {
|
||||
Request local = Request.request();
|
||||
@ -182,12 +197,14 @@ public class Expression {
|
||||
local.setWorld(request.getWorld());
|
||||
local.setEditSession(request.getEditSession());
|
||||
try {
|
||||
startLatch.countDown();
|
||||
return Expression.this.evaluateRoot();
|
||||
} finally {
|
||||
Request.reset();
|
||||
}
|
||||
});
|
||||
try {
|
||||
startLatch.await();
|
||||
return result.get(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
@ -197,12 +214,8 @@ public class Expression {
|
||||
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof EvaluationException) {
|
||||
throw (EvaluationException) cause;
|
||||
}
|
||||
if (cause instanceof RuntimeException) {
|
||||
throw (RuntimeException) cause;
|
||||
}
|
||||
Throwables.throwIfInstanceOf(cause, EvaluationException.class);
|
||||
Throwables.throwIfUnchecked(cause);
|
||||
throw new RuntimeException(cause);
|
||||
}
|
||||
}
|
||||
@ -210,14 +223,18 @@ public class Expression {
|
||||
private Double evaluateRoot() throws EvaluationException {
|
||||
pushInstance();
|
||||
try {
|
||||
return root.getValue();
|
||||
return root.accept(new EvaluatingVisitor(slots, functions));
|
||||
} finally {
|
||||
popInstance();
|
||||
}
|
||||
}
|
||||
|
||||
public void optimize() throws EvaluationException {
|
||||
root = root.optimize();
|
||||
public void optimize() {
|
||||
// TODO optimizing
|
||||
}
|
||||
|
||||
public SlotTable getSlots() {
|
||||
return slots;
|
||||
}
|
||||
|
||||
public RValue getRoot() {
|
||||
@ -229,15 +246,6 @@ public class Expression {
|
||||
return root.toString();
|
||||
}
|
||||
|
||||
public RValue getVariable(String name, boolean create) {
|
||||
RValue variable = variables.get(name);
|
||||
if (variable == null && create) {
|
||||
variables.put(name, variable = new Variable(0));
|
||||
}
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
public static Expression getInstance() {
|
||||
return instance.get().peek();
|
||||
}
|
||||
@ -253,10 +261,6 @@ public class Expression {
|
||||
foo.pop();
|
||||
}
|
||||
|
||||
public Functions getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
public ExpressionEnvironment getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.runtime;
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
/**
|
||||
* Represents a way to access blocks in a world. Has to accept non-rounded coordinates.
|
@ -23,7 +23,7 @@ package com.sk89q.worldedit.internal.expression;
|
||||
* Thrown when there's a problem during any stage of the expression
|
||||
* compilation or evaluation.
|
||||
*/
|
||||
public class ExpressionException extends Exception {
|
||||
public class ExpressionException extends RuntimeException {
|
||||
|
||||
private final int position;
|
||||
|
||||
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.sk89q.worldedit.antlr.ExpressionLexer.ID;
|
||||
|
||||
class ExpressionHelper {
|
||||
|
||||
static void check(boolean condition, ParserRuleContext ctx, String message) {
|
||||
if (!condition) {
|
||||
throw evalException(ctx, message);
|
||||
}
|
||||
}
|
||||
|
||||
static EvaluationException evalException(ParserRuleContext ctx, String message) {
|
||||
return new EvaluationException(
|
||||
ctx.getStart().getCharPositionInLine(),
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
static void checkIterations(int iterations, ParserRuleContext ctx) {
|
||||
check(iterations <= 256, ctx, "Loop exceeded 256 iterations");
|
||||
}
|
||||
|
||||
static void checkTimeout() {
|
||||
if (Thread.interrupted()) {
|
||||
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
|
||||
}
|
||||
}
|
||||
|
||||
static MethodHandle resolveFunction(SetMultimap<String, MethodHandle> functions,
|
||||
ExpressionParser.FunctionCallContext ctx) {
|
||||
String fnName = ctx.name.getText();
|
||||
Set<MethodHandle> matchingFns = functions.get(fnName);
|
||||
check(!matchingFns.isEmpty(), ctx, "Unknown function '" + fnName + "'");
|
||||
for (MethodHandle function : matchingFns) {
|
||||
MethodType type = function.type();
|
||||
// Validate argc if not varargs
|
||||
if (!function.isVarargsCollector() && type.parameterCount() != ctx.args.size()) {
|
||||
// skip non-matching function
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < ctx.args.size(); i++) {
|
||||
ExpressionParser.ExpressionContext arg = ctx.args.get(i);
|
||||
getArgumentHandleName(fnName, type, i, arg);
|
||||
}
|
||||
// good match!
|
||||
return function;
|
||||
}
|
||||
// We matched no function, fail with appropriate message.
|
||||
String possibleCounts = matchingFns.stream()
|
||||
.map(mh -> mh.isVarargsCollector()
|
||||
? (mh.type().parameterCount() - 1) + "+"
|
||||
: String.valueOf(mh.type().parameterCount()))
|
||||
.collect(Collectors.joining("/"));
|
||||
throw evalException(ctx, "Incorrect number of arguments for function '" + fnName + "', " +
|
||||
"expected " + possibleCounts + ", " +
|
||||
"got " + ctx.args.size());
|
||||
}
|
||||
|
||||
// Special argument handle names
|
||||
/**
|
||||
* The argument should be wrapped in a {@link LocalSlot.Constant} before being passed.
|
||||
*/
|
||||
static final String WRAPPED_CONSTANT = "<wrapped constant>";
|
||||
|
||||
/**
|
||||
* If this argument needs a handle, returns the name of the handle needed. Otherwise, returns
|
||||
* {@code null}. If {@code arg} isn't a valid handle reference, throws.
|
||||
*/
|
||||
static String getArgumentHandleName(String fnName, MethodType type, int i,
|
||||
ParserRuleContext arg) {
|
||||
// Pass variable handle in for modification?
|
||||
Class<?> pType = type.parameterType(i);
|
||||
Optional<String> id = tryResolveId(arg);
|
||||
if (pType == LocalSlot.Variable.class) {
|
||||
// MUST be an id
|
||||
check(id.isPresent(), arg,
|
||||
"Function '" + fnName + "' requires a variable in parameter " + i);
|
||||
return id.get();
|
||||
} else if (pType == LocalSlot.class) {
|
||||
return id.orElse(WRAPPED_CONSTANT);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Optional<String> tryResolveId(ParserRuleContext arg) {
|
||||
Optional<ExpressionParser.WrappedExprContext> wrappedExprContext =
|
||||
tryAs(arg, ExpressionParser.WrappedExprContext.class);
|
||||
if (wrappedExprContext.isPresent()) {
|
||||
return tryResolveId(wrappedExprContext.get().expression());
|
||||
}
|
||||
Token token = arg.start;
|
||||
int tokenType = token.getType();
|
||||
boolean isId = arg.start == arg.stop && tokenType == ID;
|
||||
return isId ? Optional.of(token.getText()) : Optional.empty();
|
||||
}
|
||||
|
||||
private static <T extends ParserRuleContext> Optional<T> tryAs(
|
||||
ParserRuleContext ctx,
|
||||
Class<T> rule
|
||||
) {
|
||||
if (rule.isInstance(ctx)) {
|
||||
return Optional.of(rule.cast(ctx));
|
||||
}
|
||||
if (ctx.children.size() != 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
List<ParserRuleContext> ctxs = ctx.getRuleContexts(ParserRuleContext.class);
|
||||
if (ctxs.size() != 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return tryAs(ctxs.get(0), rule);
|
||||
}
|
||||
|
||||
private ExpressionHelper() {
|
||||
}
|
||||
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.runtime;
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
/**
|
||||
* Thrown when an evaluation exceeds the timeout time.
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.sk89q.worldedit.antlr.ExpressionBaseListener;
|
||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.check;
|
||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.resolveFunction;
|
||||
|
||||
class ExpressionValidator extends ExpressionBaseListener {
|
||||
|
||||
private final Set<String> variableNames = new HashSet<>();
|
||||
private final SetMultimap<String, MethodHandle> functions;
|
||||
|
||||
ExpressionValidator(Collection<String> variableNames,
|
||||
SetMultimap<String, MethodHandle> functions) {
|
||||
this.variableNames.addAll(variableNames);
|
||||
this.functions = functions;
|
||||
}
|
||||
|
||||
private void bindVariable(String name) {
|
||||
variableNames.add(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterAssignment(ExpressionParser.AssignmentContext ctx) {
|
||||
bindVariable(ctx.target.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterSimpleForStatement(ExpressionParser.SimpleForStatementContext ctx) {
|
||||
bindVariable(ctx.counter.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterIdExpr(ExpressionParser.IdExprContext ctx) {
|
||||
String text = ctx.source.getText();
|
||||
check(variableNames.contains(text), ctx,
|
||||
"Variable '" + text + "' is not bound");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterFunctionCall(ExpressionParser.FunctionCallContext ctx) {
|
||||
resolveFunction(functions, ctx);
|
||||
}
|
||||
}
|
@ -0,0 +1,337 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.common.primitives.Doubles;
|
||||
import com.sk89q.worldedit.internal.expression.LocalSlot.Variable;
|
||||
import com.sk89q.worldedit.math.Vector3;
|
||||
import com.sk89q.worldedit.math.noise.PerlinNoise;
|
||||
import com.sk89q.worldedit.math.noise.RidgedMultiFractalNoise;
|
||||
import com.sk89q.worldedit.math.noise.VoronoiNoise;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static java.lang.invoke.MethodType.methodType;
|
||||
|
||||
/**
|
||||
* Contains all functions that can be used in expressions.
|
||||
*/
|
||||
final class Functions {
|
||||
|
||||
static SetMultimap<String, MethodHandle> getFunctionMap() {
|
||||
SetMultimap<String, MethodHandle> map = HashMultimap.create();
|
||||
Functions functions = new Functions();
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
|
||||
try {
|
||||
addMathHandles(map, lookup);
|
||||
addStaticFunctionHandles(map, lookup);
|
||||
functions.addInstanceFunctionHandles(map, lookup);
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
return ImmutableSetMultimap.copyOf(map);
|
||||
}
|
||||
|
||||
private static void addMathHandles(
|
||||
SetMultimap<String, MethodHandle> map,
|
||||
MethodHandles.Lookup lookup
|
||||
) throws NoSuchMethodException, IllegalAccessException {
|
||||
// double <name>(double) functions
|
||||
for (String name : ImmutableList.of(
|
||||
"sin", "cos", "tan", "asin", "acos", "atan",
|
||||
"sinh", "cosh", "tanh", "sqrt", "cbrt", "abs",
|
||||
"ceil", "floor", "rint", "exp", "log", "log10"
|
||||
)) {
|
||||
map.put(name, lookup.findStatic(Math.class, name,
|
||||
methodType(double.class, double.class)));
|
||||
}
|
||||
// Alias ln -> log
|
||||
map.put("ln", lookup.findStatic(Math.class, "log",
|
||||
methodType(double.class, double.class)));
|
||||
map.put("round", lookup.findStatic(Math.class, "round",
|
||||
methodType(long.class, double.class)));
|
||||
|
||||
map.put("atan2", lookup.findStatic(Math.class, "atan2",
|
||||
methodType(double.class, double.class, double.class)));
|
||||
|
||||
// Special cases: we accept varargs for these
|
||||
map.put("min", lookup.findStatic(Doubles.class, "min",
|
||||
methodType(double.class, double[].class))
|
||||
.asVarargsCollector(double[].class));
|
||||
map.put("max", lookup.findStatic(Doubles.class, "max",
|
||||
methodType(double.class, double[].class))
|
||||
.asVarargsCollector(double[].class));
|
||||
}
|
||||
|
||||
private static void addStaticFunctionHandles(
|
||||
SetMultimap<String, MethodHandle> map,
|
||||
MethodHandles.Lookup lookup
|
||||
) throws NoSuchMethodException, IllegalAccessException {
|
||||
map.put("rotate", lookup.findStatic(Functions.class, "rotate",
|
||||
methodType(double.class, Variable.class, Variable.class, double.class)));
|
||||
map.put("swap", lookup.findStatic(Functions.class, "swap",
|
||||
methodType(double.class, Variable.class, Variable.class)));
|
||||
map.put("gmegabuf", lookup.findStatic(Functions.class, "gmegabuf",
|
||||
methodType(double.class, double.class)));
|
||||
map.put("gmegabuf", lookup.findStatic(Functions.class, "gmegabuf",
|
||||
methodType(double.class, double.class, double.class)));
|
||||
map.put("gclosest", lookup.findStatic(Functions.class, "gclosest",
|
||||
methodType(double.class, double.class, double.class, double.class, double.class,
|
||||
double.class, double.class)));
|
||||
map.put("random", lookup.findStatic(Functions.class, "random",
|
||||
methodType(double.class)));
|
||||
map.put("randint", lookup.findStatic(Functions.class, "randint",
|
||||
methodType(double.class, double.class)));
|
||||
map.put("perlin", lookup.findStatic(Functions.class, "perlin",
|
||||
methodType(double.class, double.class, double.class, double.class, double.class,
|
||||
double.class, double.class, double.class)));
|
||||
map.put("voronoi", lookup.findStatic(Functions.class, "voronoi",
|
||||
methodType(double.class, double.class, double.class, double.class, double.class,
|
||||
double.class)));
|
||||
map.put("ridgedmulti", lookup.findStatic(Functions.class, "ridgedmulti",
|
||||
methodType(double.class, double.class, double.class, double.class, double.class,
|
||||
double.class, double.class)));
|
||||
map.put("query", lookup.findStatic(Functions.class, "query",
|
||||
methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
|
||||
LocalSlot.class)));
|
||||
map.put("queryAbs", lookup.findStatic(Functions.class, "queryAbs",
|
||||
methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
|
||||
LocalSlot.class)));
|
||||
map.put("queryRel", lookup.findStatic(Functions.class, "queryRel",
|
||||
methodType(double.class, double.class, double.class, double.class, LocalSlot.class,
|
||||
LocalSlot.class)));
|
||||
}
|
||||
|
||||
private void addInstanceFunctionHandles(
|
||||
SetMultimap<String, MethodHandle> map,
|
||||
MethodHandles.Lookup lookup
|
||||
) throws NoSuchMethodException, IllegalAccessException {
|
||||
map.put("megabuf", lookup.findSpecial(Functions.class, "megabuf",
|
||||
methodType(double.class, double.class), Functions.class)
|
||||
.bindTo(this));
|
||||
map.put("megabuf", lookup.findSpecial(Functions.class, "megabuf",
|
||||
methodType(double.class, double.class, double.class), Functions.class)
|
||||
.bindTo(this));
|
||||
map.put("closest", lookup.findSpecial(Functions.class, "closest",
|
||||
methodType(double.class, double.class, double.class, double.class, double.class,
|
||||
double.class, double.class), Functions.class)
|
||||
.bindTo(this));
|
||||
}
|
||||
|
||||
private static double rotate(Variable x, Variable y, double angle) {
|
||||
final double cosF = Math.cos(angle);
|
||||
final double sinF = Math.sin(angle);
|
||||
|
||||
final double xOld = x.getValue();
|
||||
final double yOld = y.getValue();
|
||||
|
||||
x.setValue(xOld * cosF - yOld * sinF);
|
||||
y.setValue(xOld * sinF + yOld * cosF);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private static double swap(Variable x, Variable y) {
|
||||
final double tmp = x.getValue();
|
||||
|
||||
x.setValue(y.getValue());
|
||||
y.setValue(tmp);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
|
||||
private static final Int2ObjectMap<double[]> globalMegaBuffer = new Int2ObjectOpenHashMap<>();
|
||||
private final Int2ObjectMap<double[]> megaBuffer = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private static double[] getSubBuffer(Int2ObjectMap<double[]> megabuf, int key) {
|
||||
return megabuf.computeIfAbsent(key, k -> new double[1024]);
|
||||
}
|
||||
|
||||
private static double getBufferItem(final Int2ObjectMap<double[]> megabuf, final int index) {
|
||||
return getSubBuffer(megabuf, index & ~1023)[index & 1023];
|
||||
}
|
||||
|
||||
private static double setBufferItem(final Int2ObjectMap<double[]> megabuf, final int index, double value) {
|
||||
return getSubBuffer(megabuf, index & ~1023)[index & 1023] = value;
|
||||
}
|
||||
|
||||
private static double gmegabuf(double index) {
|
||||
return getBufferItem(globalMegaBuffer, (int) index);
|
||||
}
|
||||
|
||||
private static double gmegabuf(double index, double value) {
|
||||
return setBufferItem(globalMegaBuffer, (int) index, value);
|
||||
}
|
||||
|
||||
private double megabuf(double index) {
|
||||
return getBufferItem(megaBuffer, (int) index);
|
||||
}
|
||||
|
||||
private double megabuf(double index, double value) {
|
||||
return setBufferItem(megaBuffer, (int) index, value);
|
||||
}
|
||||
|
||||
private double closest(double x, double y, double z, double index, double count, double stride) {
|
||||
return findClosest(
|
||||
megaBuffer, x, y, z, (int) index, (int) count, (int) stride
|
||||
);
|
||||
}
|
||||
|
||||
private static double gclosest(double x, double y, double z, double index, double count, double stride) {
|
||||
return findClosest(
|
||||
globalMegaBuffer, x, y, z, (int) index, (int) count, (int) stride
|
||||
);
|
||||
}
|
||||
|
||||
private static double findClosest(Int2ObjectMap<double[]> megabuf, double x, double y, double z, int index, int count, int stride) {
|
||||
int closestIndex = -1;
|
||||
double minDistanceSquared = Double.MAX_VALUE;
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
double currentX = getBufferItem(megabuf, index) - x;
|
||||
double currentY = getBufferItem(megabuf, index+1) - y;
|
||||
double currentZ = getBufferItem(megabuf, index+2) - z;
|
||||
|
||||
double currentDistanceSquared = currentX*currentX + currentY*currentY + currentZ*currentZ;
|
||||
|
||||
if (currentDistanceSquared < minDistanceSquared) {
|
||||
minDistanceSquared = currentDistanceSquared;
|
||||
closestIndex = index;
|
||||
}
|
||||
|
||||
index += stride;
|
||||
}
|
||||
|
||||
return closestIndex;
|
||||
}
|
||||
|
||||
private static double random() {
|
||||
return ThreadLocalRandom.current().nextDouble();
|
||||
}
|
||||
|
||||
private static double randint(double max) {
|
||||
return ThreadLocalRandom.current().nextInt((int) Math.floor(max));
|
||||
}
|
||||
|
||||
private static final ThreadLocal<PerlinNoise> localPerlin = ThreadLocal.withInitial(PerlinNoise::new);
|
||||
|
||||
private static double perlin(double seed, double x, double y, double z,
|
||||
double frequency, double octaves, double persistence) {
|
||||
PerlinNoise perlin = localPerlin.get();
|
||||
try {
|
||||
perlin.setSeed((int) seed);
|
||||
perlin.setFrequency(frequency);
|
||||
perlin.setOctaveCount((int) octaves);
|
||||
perlin.setPersistence(persistence);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new EvaluationException(0, "Perlin noise error: " + e.getMessage());
|
||||
}
|
||||
return perlin.noise(Vector3.at(x, y, z));
|
||||
}
|
||||
|
||||
private static final ThreadLocal<VoronoiNoise> localVoronoi = ThreadLocal.withInitial(VoronoiNoise::new);
|
||||
|
||||
private static double voronoi(double seed, double x, double y, double z, double frequency) {
|
||||
VoronoiNoise voronoi = localVoronoi.get();
|
||||
try {
|
||||
voronoi.setSeed((int) seed);
|
||||
voronoi.setFrequency(frequency);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new EvaluationException(0, "Voronoi error: " + e.getMessage());
|
||||
}
|
||||
return voronoi.noise(Vector3.at(x, y, z));
|
||||
}
|
||||
|
||||
private static final ThreadLocal<RidgedMultiFractalNoise> localRidgedMulti = ThreadLocal.withInitial(RidgedMultiFractalNoise::new);
|
||||
|
||||
private static double ridgedmulti(double seed, double x, double y, double z,
|
||||
double frequency, double octaves) {
|
||||
RidgedMultiFractalNoise ridgedMulti = localRidgedMulti.get();
|
||||
try {
|
||||
ridgedMulti.setSeed((int) seed);
|
||||
ridgedMulti.setFrequency(frequency);
|
||||
ridgedMulti.setOctaveCount((int) octaves);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new EvaluationException(0, "Ridged multi error: " + e.getMessage());
|
||||
}
|
||||
return ridgedMulti.noise(Vector3.at(x, y, z));
|
||||
}
|
||||
|
||||
private static double queryInternal(LocalSlot type, LocalSlot data, double typeId, double dataValue) {
|
||||
// Compare to input values and determine return value
|
||||
// -1 is a wildcard, always true
|
||||
double ret = ((type.getValue() == -1 || typeId == type.getValue())
|
||||
&& (data.getValue() == -1 || dataValue == data.getValue())) ? 1.0 : 0.0;
|
||||
|
||||
if (type instanceof Variable) {
|
||||
((Variable) type).setValue(typeId);
|
||||
}
|
||||
if (data instanceof Variable) {
|
||||
((Variable) data).setValue(dataValue);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static double query(double x, double y, double z, LocalSlot type, LocalSlot data) {
|
||||
final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
|
||||
|
||||
// Read values from world
|
||||
final double typeId = environment.getBlockType(x, y, z);
|
||||
final double dataValue = environment.getBlockData(x, y, z);
|
||||
|
||||
return queryInternal(type, data, typeId, dataValue);
|
||||
}
|
||||
|
||||
private static double queryAbs(double x, double y, double z, LocalSlot type, LocalSlot data) {
|
||||
final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
|
||||
|
||||
// Read values from world
|
||||
final double typeId = environment.getBlockTypeAbs(x, y, z);
|
||||
final double dataValue = environment.getBlockDataAbs(x, y, z);
|
||||
|
||||
return queryInternal(type, data, typeId, dataValue);
|
||||
}
|
||||
|
||||
private static double queryRel(double x, double y, double z, LocalSlot type, LocalSlot data) {
|
||||
final ExpressionEnvironment environment = Expression.getInstance().getEnvironment();
|
||||
|
||||
// Read values from world
|
||||
final double typeId = environment.getBlockTypeRel(x, y, z);
|
||||
final double dataValue = environment.getBlockDataRel(x, y, z);
|
||||
|
||||
return queryInternal(type, data, typeId, dataValue);
|
||||
}
|
||||
|
||||
private Functions() {
|
||||
}
|
||||
|
||||
}
|
@ -17,28 +17,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.lexer.tokens;
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
/**
|
||||
* A unary or binary operator.
|
||||
*/
|
||||
public class OperatorToken extends Token {
|
||||
|
||||
public final String operator;
|
||||
|
||||
public OperatorToken(int position, String operator) {
|
||||
super(position);
|
||||
this.operator = operator;
|
||||
}
|
||||
import org.antlr.v4.runtime.BaseErrorListener;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
|
||||
class LexerErrorListener extends BaseErrorListener {
|
||||
@Override
|
||||
public char id() {
|
||||
return 'o';
|
||||
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
|
||||
throw new LexerException(charPositionInLine, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OperatorToken(" + operator + ")";
|
||||
}
|
||||
|
||||
}
|
@ -17,9 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.lexer;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
/**
|
||||
* Thrown when the lexer encounters a problem.
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
/**
|
||||
* Represents the metadata for a named local slot.
|
||||
*/
|
||||
public interface LocalSlot {
|
||||
|
||||
final class Constant implements LocalSlot {
|
||||
private final double value;
|
||||
|
||||
public Constant(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
final class Variable implements LocalSlot {
|
||||
private double value;
|
||||
|
||||
public Variable(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public void setValue(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
double getValue();
|
||||
|
||||
}
|
@ -17,28 +17,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.lexer.tokens;
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
/**
|
||||
* An identifier.
|
||||
*/
|
||||
public class IdentifierToken extends Token {
|
||||
|
||||
public final String value;
|
||||
|
||||
public IdentifierToken(int position, String value) {
|
||||
super(position);
|
||||
this.value = value;
|
||||
}
|
||||
import org.antlr.v4.runtime.BaseErrorListener;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
|
||||
class ParserErrorListener extends BaseErrorListener {
|
||||
@Override
|
||||
public char id() {
|
||||
return 'i';
|
||||
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
|
||||
throw new ParserException(charPositionInLine, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IdentifierToken(" + value + ")";
|
||||
}
|
||||
|
||||
}
|
@ -17,9 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.parser;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
/**
|
||||
* Thrown when the parser encounters a problem.
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.Set;
|
||||
|
||||
public class SlotTable {
|
||||
|
||||
private final Map<String, LocalSlot> slots = new HashMap<>();
|
||||
|
||||
public Set<String> keySet() {
|
||||
return slots.keySet();
|
||||
}
|
||||
|
||||
public void putSlot(String name, LocalSlot slot) {
|
||||
slots.put(name, slot);
|
||||
}
|
||||
|
||||
public boolean containsSlot(String name) {
|
||||
return slots.containsKey(name);
|
||||
}
|
||||
|
||||
public Optional<LocalSlot.Variable> initVariable(String name) {
|
||||
slots.computeIfAbsent(name, n -> new LocalSlot.Variable(0));
|
||||
return getVariable(name);
|
||||
}
|
||||
|
||||
public Optional<LocalSlot> getSlot(String name) {
|
||||
return Optional.ofNullable(slots.get(name));
|
||||
}
|
||||
|
||||
public Optional<LocalSlot.Variable> getVariable(String name) {
|
||||
return getSlot(name)
|
||||
.filter(LocalSlot.Variable.class::isInstance)
|
||||
.map(LocalSlot.Variable.class::cast);
|
||||
}
|
||||
|
||||
public OptionalDouble getSlotValue(String name) {
|
||||
LocalSlot slot = slots.get(name);
|
||||
return slot == null ? OptionalDouble.empty() : OptionalDouble.of(slot.getValue());
|
||||
}
|
||||
|
||||
}
|
@ -1,240 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.lexer;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.CharacterToken;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.IdentifierToken;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.KeywordToken;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.NumberToken;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.OperatorToken;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.Token;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Processes a string into a list of tokens.
|
||||
*
|
||||
* <p>Tokens can be numbers, identifiers, operators and assorted other
|
||||
* characters.</p>
|
||||
*/
|
||||
public class Lexer {
|
||||
|
||||
private final String expression;
|
||||
private int position = 0;
|
||||
|
||||
private Lexer(String expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public static List<Token> tokenize(String expression) throws LexerException {
|
||||
return new Lexer(expression).tokenize();
|
||||
}
|
||||
|
||||
private final DecisionTree operatorTree = new DecisionTree(null,
|
||||
'+', new DecisionTree("+",
|
||||
'=', new DecisionTree("+="),
|
||||
'+', new DecisionTree("++")
|
||||
),
|
||||
'-', new DecisionTree("-",
|
||||
'=', new DecisionTree("-="),
|
||||
'-', new DecisionTree("--")
|
||||
),
|
||||
'*', new DecisionTree("*",
|
||||
'=', new DecisionTree("*="),
|
||||
'*', new DecisionTree("**")
|
||||
),
|
||||
'/', new DecisionTree("/",
|
||||
'=', new DecisionTree("/=")
|
||||
),
|
||||
'%', new DecisionTree("%",
|
||||
'=', new DecisionTree("%=")
|
||||
),
|
||||
'^', new DecisionTree("^",
|
||||
'=', new DecisionTree("^=")
|
||||
),
|
||||
'=', new DecisionTree("=",
|
||||
'=', new DecisionTree("==")
|
||||
),
|
||||
'!', new DecisionTree("!",
|
||||
'=', new DecisionTree("!=")
|
||||
),
|
||||
'<', new DecisionTree("<",
|
||||
'<', new DecisionTree("<<"),
|
||||
'=', new DecisionTree("<=")
|
||||
),
|
||||
'>', new DecisionTree(">",
|
||||
'>', new DecisionTree(">>"),
|
||||
'=', new DecisionTree(">=")
|
||||
),
|
||||
'&', new DecisionTree(null, // not implemented
|
||||
'&', new DecisionTree("&&")
|
||||
),
|
||||
'|', new DecisionTree(null, // not implemented
|
||||
'|', new DecisionTree("||")
|
||||
),
|
||||
'~', new DecisionTree("~",
|
||||
'=', new DecisionTree("~=")
|
||||
)
|
||||
);
|
||||
|
||||
private static final Set<Character> characterTokens = new HashSet<>();
|
||||
static {
|
||||
characterTokens.add(',');
|
||||
characterTokens.add('(');
|
||||
characterTokens.add(')');
|
||||
characterTokens.add('{');
|
||||
characterTokens.add('}');
|
||||
characterTokens.add(';');
|
||||
characterTokens.add('?');
|
||||
characterTokens.add(':');
|
||||
}
|
||||
|
||||
private static final Set<String> keywords =
|
||||
new HashSet<>(Arrays.asList("if", "else", "while", "do", "for", "break", "continue", "return", "switch", "case", "default"));
|
||||
|
||||
private static final Pattern numberPattern = Pattern.compile("^([0-9]*(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)");
|
||||
private static final Pattern identifierPattern = Pattern.compile("^([A-Za-z][0-9A-Za-z_]*)");
|
||||
|
||||
private List<Token> tokenize() throws LexerException {
|
||||
List<Token> tokens = new ArrayList<>();
|
||||
|
||||
do {
|
||||
skipWhitespace();
|
||||
if (position >= expression.length()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Token token = operatorTree.evaluate(position);
|
||||
if (token != null) {
|
||||
tokens.add(token);
|
||||
continue;
|
||||
}
|
||||
|
||||
final char ch = peek();
|
||||
|
||||
if (characterTokens.contains(ch)) {
|
||||
tokens.add(new CharacterToken(position++, ch));
|
||||
continue;
|
||||
}
|
||||
|
||||
final Matcher numberMatcher = numberPattern.matcher(expression.substring(position));
|
||||
if (numberMatcher.lookingAt()) {
|
||||
String numberPart = numberMatcher.group(1);
|
||||
if (!numberPart.isEmpty()) {
|
||||
try {
|
||||
tokens.add(new NumberToken(position, Double.parseDouble(numberPart)));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new LexerException(position, "Number parsing failed", e);
|
||||
}
|
||||
|
||||
position += numberPart.length();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
final Matcher identifierMatcher = identifierPattern.matcher(expression.substring(position));
|
||||
if (identifierMatcher.lookingAt()) {
|
||||
String identifierPart = identifierMatcher.group(1);
|
||||
if (!identifierPart.isEmpty()) {
|
||||
if (keywords.contains(identifierPart)) {
|
||||
tokens.add(new KeywordToken(position, identifierPart));
|
||||
} else {
|
||||
tokens.add(new IdentifierToken(position, identifierPart));
|
||||
}
|
||||
|
||||
position += identifierPart.length();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw new LexerException(position, "Unknown character '" + ch + "'");
|
||||
} while (position < expression.length());
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private char peek() {
|
||||
return expression.charAt(position);
|
||||
}
|
||||
|
||||
private void skipWhitespace() {
|
||||
while (position < expression.length() && Character.isWhitespace(peek())) {
|
||||
++position;
|
||||
}
|
||||
}
|
||||
|
||||
public class DecisionTree {
|
||||
private final String tokenName;
|
||||
private final Map<Character, DecisionTree> subTrees = new HashMap<>();
|
||||
|
||||
private DecisionTree(String tokenName, Object... args) {
|
||||
this.tokenName = tokenName;
|
||||
|
||||
if (args.length % 2 != 0) {
|
||||
throw new UnsupportedOperationException("You need to pass an even number of arguments.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < args.length; i += 2) {
|
||||
if (!(args[i] instanceof Character)) {
|
||||
throw new UnsupportedOperationException("Argument #" + i + " expected to be 'Character', not '" + args[i].getClass().getName() + "'.");
|
||||
}
|
||||
if (!(args[i + 1] instanceof DecisionTree)) {
|
||||
throw new UnsupportedOperationException("Argument #" + (i + 1) + " expected to be 'DecisionTree', not '" + args[i + 1].getClass().getName() + "'.");
|
||||
}
|
||||
|
||||
Character next = (Character) args[i];
|
||||
DecisionTree subTree = (DecisionTree) args[i + 1];
|
||||
|
||||
subTrees.put(next, subTree);
|
||||
}
|
||||
}
|
||||
|
||||
private Token evaluate(int startPosition) throws LexerException {
|
||||
if (position < expression.length()) {
|
||||
final char next = peek();
|
||||
|
||||
final DecisionTree subTree = subTrees.get(next);
|
||||
if (subTree != null) {
|
||||
++position;
|
||||
final Token subTreeResult = subTree.evaluate(startPosition);
|
||||
if (subTreeResult != null) {
|
||||
return subTreeResult;
|
||||
}
|
||||
--position;
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new OperatorToken(startPosition, tokenName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.lexer.tokens;
|
||||
|
||||
/**
|
||||
* A single character that doesn't fit any of the other token categories.
|
||||
*/
|
||||
public class CharacterToken extends Token {
|
||||
|
||||
public final char character;
|
||||
|
||||
public CharacterToken(int position, char character) {
|
||||
super(position);
|
||||
this.character = character;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char id() {
|
||||
return character;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CharacterToken(" + character + ")";
|
||||
}
|
||||
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.lexer.tokens;
|
||||
|
||||
/**
|
||||
* A number.
|
||||
*/
|
||||
public class NumberToken extends Token {
|
||||
|
||||
public final double value;
|
||||
|
||||
public NumberToken(int position, double value) {
|
||||
super(position);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char id() {
|
||||
return '0';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NumberToken(" + value + ")";
|
||||
}
|
||||
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.lexer.tokens;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.Identifiable;
|
||||
|
||||
/**
|
||||
* A token. The lexer generates these to make the parser's job easier.
|
||||
*/
|
||||
public abstract class Token implements Identifiable {
|
||||
|
||||
private final int position;
|
||||
|
||||
public Token(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
}
|
@ -1,464 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.parser;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.Expression;
|
||||
import com.sk89q.worldedit.internal.expression.Identifiable;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.IdentifierToken;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.KeywordToken;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.NumberToken;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.OperatorToken;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.Token;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Break;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Conditional;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Constant;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.For;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Function;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Functions;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.LValue;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.RValue;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Return;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Sequence;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.SimpleFor;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Switch;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.While;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Processes a list of tokens into an executable tree.
|
||||
*
|
||||
* <p>Tokens can be numbers, identifiers, operators and assorted other characters.</p>
|
||||
*/
|
||||
public class Parser {
|
||||
private final class NullToken extends Token {
|
||||
private NullToken(int position) {
|
||||
super(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char id() {
|
||||
return '\0';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NullToken";
|
||||
}
|
||||
}
|
||||
|
||||
private final List<Token> tokens;
|
||||
private int position = 0;
|
||||
private Expression expression;
|
||||
|
||||
private Parser(List<Token> tokens, Expression expression) {
|
||||
this.tokens = tokens;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public static RValue parse(List<Token> tokens, Expression expression) throws ParserException {
|
||||
return new Parser(tokens, expression).parse();
|
||||
}
|
||||
|
||||
private RValue parse() throws ParserException {
|
||||
final RValue ret = parseStatements(false);
|
||||
if (position < tokens.size()) {
|
||||
final Token token = peek();
|
||||
throw new ParserException(token.getPosition(), "Extra token at the end of the input: " + token);
|
||||
}
|
||||
|
||||
ret.bindVariables(expression, false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private RValue parseStatements(boolean singleStatement) throws ParserException {
|
||||
List<RValue> statements = new ArrayList<>();
|
||||
loop: while (position < tokens.size()) {
|
||||
boolean expectSemicolon = false;
|
||||
|
||||
final Token current = peek();
|
||||
switch (current.id()) {
|
||||
case '{':
|
||||
consumeCharacter('{');
|
||||
|
||||
statements.add(parseStatements(false));
|
||||
|
||||
consumeCharacter('}');
|
||||
|
||||
break;
|
||||
|
||||
case '}':
|
||||
break loop;
|
||||
|
||||
case 'k':
|
||||
final String keyword = ((KeywordToken) current).value;
|
||||
switch (keyword.charAt(0)) {
|
||||
case 'i': { // if
|
||||
++position;
|
||||
final RValue condition = parseBracket();
|
||||
final RValue truePart = parseStatements(true);
|
||||
final RValue falsePart;
|
||||
|
||||
if (hasKeyword("else")) {
|
||||
++position;
|
||||
falsePart = parseStatements(true);
|
||||
} else {
|
||||
falsePart = null;
|
||||
}
|
||||
|
||||
statements.add(new Conditional(current.getPosition(), condition, truePart, falsePart));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'w': { // while
|
||||
++position;
|
||||
final RValue condition = parseBracket();
|
||||
final RValue body = parseStatements(true);
|
||||
|
||||
statements.add(new While(current.getPosition(), condition, body, false));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'd': { // do/default
|
||||
if (hasKeyword("default")) {
|
||||
break loop;
|
||||
}
|
||||
|
||||
++position;
|
||||
final RValue body = parseStatements(true);
|
||||
|
||||
consumeKeyword("while");
|
||||
|
||||
final RValue condition = parseBracket();
|
||||
|
||||
statements.add(new While(current.getPosition(), condition, body, true));
|
||||
|
||||
expectSemicolon = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'f': { // for
|
||||
++position;
|
||||
consumeCharacter('(');
|
||||
int oldPosition = position;
|
||||
final RValue init = parseExpression(true);
|
||||
//if ((init instanceof LValue) && )
|
||||
if (peek().id() == ';') {
|
||||
++position;
|
||||
final RValue condition = parseExpression(true);
|
||||
consumeCharacter(';');
|
||||
final RValue increment = parseExpression(true);
|
||||
consumeCharacter(')');
|
||||
final RValue body = parseStatements(true);
|
||||
|
||||
statements.add(new For(current.getPosition(), init, condition, increment, body));
|
||||
} else {
|
||||
position = oldPosition;
|
||||
|
||||
final Token variableToken = peek();
|
||||
if (!(variableToken instanceof IdentifierToken)) {
|
||||
throw new ParserException(variableToken.getPosition(), "Expected identifier");
|
||||
}
|
||||
|
||||
RValue variable = expression.getVariable(((IdentifierToken) variableToken).value, true);
|
||||
if (!(variable instanceof LValue)) {
|
||||
throw new ParserException(variableToken.getPosition(), "Expected variable");
|
||||
}
|
||||
++position;
|
||||
|
||||
final Token equalsToken = peek();
|
||||
if (!(equalsToken instanceof OperatorToken) || !((OperatorToken) equalsToken).operator.equals("=")) {
|
||||
throw new ParserException(variableToken.getPosition(), "Expected '=' or a term and ';'");
|
||||
}
|
||||
++position;
|
||||
|
||||
final RValue first = parseExpression(true);
|
||||
consumeCharacter(',');
|
||||
final RValue last = parseExpression(true);
|
||||
consumeCharacter(')');
|
||||
final RValue body = parseStatements(true);
|
||||
|
||||
statements.add(new SimpleFor(current.getPosition(), (LValue) variable, first, last, body));
|
||||
} // switch (keyword.charAt(0))
|
||||
break;
|
||||
}
|
||||
|
||||
case 'b': // break
|
||||
++position;
|
||||
statements.add(new Break(current.getPosition(), false));
|
||||
break;
|
||||
|
||||
case 'c': // continue/case
|
||||
if (hasKeyword("case")) {
|
||||
break loop;
|
||||
}
|
||||
|
||||
++position;
|
||||
statements.add(new Break(current.getPosition(), true));
|
||||
break;
|
||||
|
||||
case 'r': // return
|
||||
++position;
|
||||
statements.add(new Return(current.getPosition(), parseExpression(true)));
|
||||
|
||||
expectSemicolon = true;
|
||||
break;
|
||||
|
||||
case 's': // switch
|
||||
++position;
|
||||
final RValue parameter = parseBracket();
|
||||
final List<Double> values = new ArrayList<>();
|
||||
final List<RValue> caseStatements = new ArrayList<>();
|
||||
RValue defaultCase = null;
|
||||
|
||||
consumeCharacter('{');
|
||||
while (peek().id() != '}') {
|
||||
if (position >= tokens.size()) {
|
||||
throw new ParserException(current.getPosition(), "Expected '}' instead of EOF");
|
||||
}
|
||||
if (defaultCase != null) {
|
||||
throw new ParserException(current.getPosition(), "Expected '}' instead of " + peek());
|
||||
}
|
||||
|
||||
if (hasKeyword("case")) {
|
||||
++position;
|
||||
|
||||
final Token valueToken = peek();
|
||||
if (!(valueToken instanceof NumberToken)) {
|
||||
throw new ParserException(current.getPosition(), "Expected number instead of " + peek());
|
||||
}
|
||||
|
||||
++position;
|
||||
|
||||
values.add(((NumberToken) valueToken).value);
|
||||
|
||||
consumeCharacter(':');
|
||||
caseStatements.add(parseStatements(false));
|
||||
} else if (hasKeyword("default")) {
|
||||
++position;
|
||||
|
||||
consumeCharacter(':');
|
||||
defaultCase = parseStatements(false);
|
||||
} else {
|
||||
throw new ParserException(current.getPosition(), "Expected 'case' or 'default' instead of " + peek());
|
||||
}
|
||||
}
|
||||
consumeCharacter('}');
|
||||
|
||||
statements.add(new Switch(current.getPosition(), parameter, values, caseStatements, defaultCase));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ParserException(current.getPosition(), "Unexpected keyword '" + keyword + "'");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
statements.add(parseExpression(true));
|
||||
|
||||
expectSemicolon = true;
|
||||
} // switch (current.id())
|
||||
|
||||
if (expectSemicolon) {
|
||||
if (peek().id() == ';') {
|
||||
++position;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (singleStatement) {
|
||||
break;
|
||||
}
|
||||
} // while (position < tokens.size())
|
||||
|
||||
switch (statements.size()) {
|
||||
case 0:
|
||||
if (singleStatement) {
|
||||
throw new ParserException(peek().getPosition(), "Statement expected.");
|
||||
}
|
||||
|
||||
return new Sequence(peek().getPosition());
|
||||
|
||||
case 1:
|
||||
return statements.get(0);
|
||||
|
||||
default:
|
||||
return new Sequence(peek().getPosition(), statements.toArray(new RValue[statements.size()]));
|
||||
}
|
||||
}
|
||||
|
||||
private RValue parseExpression(boolean canBeEmpty) throws ParserException {
|
||||
LinkedList<Identifiable> halfProcessed = new LinkedList<>();
|
||||
|
||||
// process brackets, numbers, functions, variables and detect prefix operators
|
||||
boolean expressionStart = true;
|
||||
loop: while (position < tokens.size()) {
|
||||
final Token current = peek();
|
||||
|
||||
switch (current.id()) {
|
||||
case '0':
|
||||
halfProcessed.add(new Constant(current.getPosition(), ((NumberToken) current).value));
|
||||
++position;
|
||||
expressionStart = false;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
final IdentifierToken identifierToken = (IdentifierToken) current;
|
||||
++position;
|
||||
|
||||
final Token next = peek();
|
||||
if (next.id() == '(') {
|
||||
halfProcessed.add(parseFunctionCall(identifierToken));
|
||||
} else {
|
||||
final RValue variable = expression.getVariable(identifierToken.value, false);
|
||||
if (variable == null) {
|
||||
halfProcessed.add(new UnboundVariable(identifierToken.getPosition(), identifierToken.value));
|
||||
} else {
|
||||
halfProcessed.add(variable);
|
||||
}
|
||||
}
|
||||
expressionStart = false;
|
||||
break;
|
||||
|
||||
case '(':
|
||||
halfProcessed.add(parseBracket());
|
||||
expressionStart = false;
|
||||
break;
|
||||
|
||||
case ',':
|
||||
case ')':
|
||||
case '}':
|
||||
case ';':
|
||||
break loop;
|
||||
|
||||
case 'o':
|
||||
if (expressionStart) {
|
||||
// Preprocess prefix operators into unary operators
|
||||
halfProcessed.add(new UnaryOperator((OperatorToken) current));
|
||||
} else {
|
||||
halfProcessed.add(current);
|
||||
}
|
||||
++position;
|
||||
expressionStart = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
halfProcessed.add(current);
|
||||
++position;
|
||||
expressionStart = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (halfProcessed.isEmpty() && canBeEmpty) {
|
||||
return new Sequence(peek().getPosition());
|
||||
}
|
||||
|
||||
return ParserProcessors.processExpression(halfProcessed);
|
||||
}
|
||||
|
||||
|
||||
private Token peek() {
|
||||
if (position >= tokens.size()) {
|
||||
return new NullToken(tokens.get(tokens.size() - 1).getPosition() + 1);
|
||||
}
|
||||
|
||||
return tokens.get(position);
|
||||
}
|
||||
|
||||
private Function parseFunctionCall(IdentifierToken identifierToken) throws ParserException {
|
||||
consumeCharacter('(');
|
||||
|
||||
try {
|
||||
if (peek().id() == ')') {
|
||||
++position;
|
||||
return Functions.getFunction(identifierToken.getPosition(), identifierToken.value);
|
||||
}
|
||||
|
||||
List<RValue> args = new ArrayList<>();
|
||||
|
||||
loop: while (true) {
|
||||
args.add(parseExpression(false));
|
||||
|
||||
final Token current = peek();
|
||||
++position;
|
||||
|
||||
switch (current.id()) {
|
||||
case ',':
|
||||
continue;
|
||||
|
||||
case ')':
|
||||
break loop;
|
||||
|
||||
default:
|
||||
throw new ParserException(current.getPosition(), "Unmatched opening bracket");
|
||||
}
|
||||
}
|
||||
|
||||
return Functions.getFunction(identifierToken.getPosition(), identifierToken.value, args.toArray(new RValue[args.size()]));
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new ParserException(identifierToken.getPosition(), "Function '" + identifierToken.value + "' not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
private RValue parseBracket() throws ParserException {
|
||||
consumeCharacter('(');
|
||||
|
||||
final RValue ret = parseExpression(false);
|
||||
|
||||
consumeCharacter(')');
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private boolean hasKeyword(String keyword) {
|
||||
final Token next = peek();
|
||||
|
||||
return next instanceof KeywordToken && ((KeywordToken) next).value.equals(keyword);
|
||||
}
|
||||
|
||||
private void assertCharacter(char character) throws ParserException {
|
||||
final Token next = peek();
|
||||
if (next.id() != character) {
|
||||
throw new ParserException(next.getPosition(), "Expected '" + character + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertKeyword(String keyword) throws ParserException {
|
||||
if (!hasKeyword(keyword)) {
|
||||
throw new ParserException(peek().getPosition(), "Expected '" + keyword + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private void consumeCharacter(char character) throws ParserException {
|
||||
assertCharacter(character);
|
||||
++position;
|
||||
}
|
||||
|
||||
private void consumeKeyword(String keyword) throws ParserException {
|
||||
assertKeyword(keyword);
|
||||
++position;
|
||||
}
|
||||
}
|
@ -1,352 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.parser;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.Identifiable;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.OperatorToken;
|
||||
import com.sk89q.worldedit.internal.expression.lexer.tokens.Token;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Conditional;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Operators;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.RValue;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper classfor Parser. Contains processors for statements and operators.
|
||||
*/
|
||||
public final class ParserProcessors {
|
||||
|
||||
private static final Map<String, String> unaryOpMap = new HashMap<>();
|
||||
|
||||
private static final Map<String, String>[] binaryOpMapsLA;
|
||||
private static final Map<String, String>[] binaryOpMapsRA;
|
||||
|
||||
static {
|
||||
unaryOpMap.put("-", "neg");
|
||||
unaryOpMap.put("!", "not");
|
||||
unaryOpMap.put("~", "inv");
|
||||
unaryOpMap.put("++", "inc");
|
||||
unaryOpMap.put("--", "dec");
|
||||
unaryOpMap.put("x++", "postinc");
|
||||
unaryOpMap.put("x--", "postdec");
|
||||
unaryOpMap.put("x!", "fac");
|
||||
|
||||
final Object[][][] binaryOpsLA = {
|
||||
{
|
||||
{ "^", "pow" },
|
||||
{ "**", "pow" },
|
||||
},
|
||||
{
|
||||
{ "*", "mul" },
|
||||
{ "/", "div" },
|
||||
{ "%", "mod" },
|
||||
},
|
||||
{
|
||||
{ "+", "add" },
|
||||
{ "-", "sub" },
|
||||
},
|
||||
{
|
||||
{ "<<", "shl" },
|
||||
{ ">>", "shr" },
|
||||
},
|
||||
{
|
||||
{ "<", "lth" },
|
||||
{ ">", "gth" },
|
||||
{ "<=", "leq" },
|
||||
{ ">=", "geq" },
|
||||
},
|
||||
{
|
||||
{ "==", "equ" },
|
||||
{ "!=", "neq" },
|
||||
{ "~=", "near" },
|
||||
},
|
||||
{
|
||||
{ "&&", "and" },
|
||||
},
|
||||
{
|
||||
{ "||", "or" },
|
||||
},
|
||||
};
|
||||
final Object[][][] binaryOpsRA = {
|
||||
{
|
||||
{ "=", "ass" },
|
||||
{ "+=", "aadd" },
|
||||
{ "-=", "asub" },
|
||||
{ "*=", "amul" },
|
||||
{ "/=", "adiv" },
|
||||
{ "%=", "amod" },
|
||||
{ "^=", "aexp" },
|
||||
},
|
||||
};
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final Map<String, String>[] lBinaryOpMapsLA = binaryOpMapsLA = new Map[binaryOpsLA.length];
|
||||
for (int i = 0; i < binaryOpsLA.length; ++i) {
|
||||
final Object[][] a = binaryOpsLA[i];
|
||||
switch (a.length) {
|
||||
case 0:
|
||||
lBinaryOpMapsLA[i] = Collections.emptyMap();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
final Object[] first = a[0];
|
||||
lBinaryOpMapsLA[i] = Collections.singletonMap((String) first[0], (String) first[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
Map<String, String> m = lBinaryOpMapsLA[i] = new HashMap<>();
|
||||
for (final Object[] element : a) {
|
||||
m.put((String) element[0], (String) element[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final Map<String, String>[] lBinaryOpMapsRA = binaryOpMapsRA = new Map[binaryOpsRA.length];
|
||||
for (int i = 0; i < binaryOpsRA.length; ++i) {
|
||||
final Object[][] a = binaryOpsRA[i];
|
||||
switch (a.length) {
|
||||
case 0:
|
||||
lBinaryOpMapsRA[i] = Collections.emptyMap();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
final Object[] first = a[0];
|
||||
lBinaryOpMapsRA[i] = Collections.singletonMap((String) first[0], (String) first[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
Map<String, String> m = lBinaryOpMapsRA[i] = new HashMap<>();
|
||||
for (final Object[] element : a) {
|
||||
m.put((String) element[0], (String) element[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ParserProcessors() {
|
||||
}
|
||||
|
||||
static RValue processExpression(LinkedList<Identifiable> input) throws ParserException {
|
||||
return processBinaryOpsRA(input, binaryOpMapsRA.length - 1);
|
||||
}
|
||||
|
||||
private static RValue processBinaryOpsLA(LinkedList<Identifiable> input, int level) throws ParserException {
|
||||
if (level < 0) {
|
||||
return processUnaryOps(input);
|
||||
}
|
||||
|
||||
LinkedList<Identifiable> lhs = new LinkedList<>();
|
||||
LinkedList<Identifiable> rhs = new LinkedList<>();
|
||||
String operator = null;
|
||||
|
||||
for (Iterator<Identifiable> it = input.descendingIterator(); it.hasNext();) {
|
||||
Identifiable identifiable = it.next();
|
||||
if (operator == null) {
|
||||
rhs.addFirst(identifiable);
|
||||
|
||||
if (!(identifiable instanceof OperatorToken)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
operator = binaryOpMapsLA[level].get(((OperatorToken) identifiable).operator);
|
||||
if (operator == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rhs.removeFirst();
|
||||
} else {
|
||||
lhs.addFirst(identifiable);
|
||||
}
|
||||
}
|
||||
|
||||
RValue rhsInvokable = processBinaryOpsLA(rhs, level - 1);
|
||||
if (operator == null) return rhsInvokable;
|
||||
|
||||
RValue lhsInvokable = processBinaryOpsLA(lhs, level);
|
||||
|
||||
try {
|
||||
return Operators.getOperator(input.get(0).getPosition(), operator, lhsInvokable, rhsInvokable);
|
||||
} catch (NoSuchMethodException e) {
|
||||
final Token operatorToken = (Token) input.get(lhs.size());
|
||||
throw new ParserException(operatorToken.getPosition(), "Couldn't find operator '" + operator + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private static RValue processBinaryOpsRA(LinkedList<Identifiable> input, int level) throws ParserException {
|
||||
if (level < 0) {
|
||||
return processTernaryOps(input);
|
||||
}
|
||||
|
||||
LinkedList<Identifiable> lhs = new LinkedList<>();
|
||||
LinkedList<Identifiable> rhs = new LinkedList<>();
|
||||
String operator = null;
|
||||
|
||||
for (Identifiable identifiable : input) {
|
||||
if (operator == null) {
|
||||
lhs.addLast(identifiable);
|
||||
|
||||
if (!(identifiable instanceof OperatorToken)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
operator = binaryOpMapsRA[level].get(((OperatorToken) identifiable).operator);
|
||||
if (operator == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lhs.removeLast();
|
||||
} else {
|
||||
rhs.addLast(identifiable);
|
||||
}
|
||||
}
|
||||
|
||||
RValue lhsInvokable = processBinaryOpsRA(lhs, level - 1);
|
||||
if (operator == null) return lhsInvokable;
|
||||
|
||||
RValue rhsInvokable = processBinaryOpsRA(rhs, level);
|
||||
|
||||
try {
|
||||
return Operators.getOperator(input.get(0).getPosition(), operator, lhsInvokable, rhsInvokable);
|
||||
} catch (NoSuchMethodException e) {
|
||||
final Token operatorToken = (Token) input.get(lhs.size());
|
||||
throw new ParserException(operatorToken.getPosition(), "Couldn't find operator '" + operator + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private static RValue processTernaryOps(LinkedList<Identifiable> input) throws ParserException {
|
||||
LinkedList<Identifiable> lhs = new LinkedList<>();
|
||||
LinkedList<Identifiable> mhs = new LinkedList<>();
|
||||
LinkedList<Identifiable> rhs = new LinkedList<>();
|
||||
|
||||
int partsFound = 0;
|
||||
int conditionalsFound = 0;
|
||||
|
||||
for (Identifiable identifiable : input) {
|
||||
final char character = identifiable.id();
|
||||
switch (character) {
|
||||
case '?':
|
||||
++conditionalsFound;
|
||||
break;
|
||||
case ':':
|
||||
--conditionalsFound;
|
||||
break;
|
||||
}
|
||||
|
||||
if (conditionalsFound < 0) {
|
||||
throw new ParserException(identifiable.getPosition(), "Unexpected ':'");
|
||||
}
|
||||
|
||||
switch (partsFound) {
|
||||
case 0:
|
||||
if (character == '?') {
|
||||
partsFound = 1;
|
||||
} else {
|
||||
lhs.addLast(identifiable);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (conditionalsFound == 0 && character == ':') {
|
||||
partsFound = 2;
|
||||
} else {
|
||||
mhs.addLast(identifiable);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
rhs.addLast(identifiable);
|
||||
}
|
||||
}
|
||||
|
||||
if (partsFound < 2) {
|
||||
return processBinaryOpsLA(input, binaryOpMapsLA.length - 1);
|
||||
}
|
||||
|
||||
RValue lhsInvokable = processBinaryOpsLA(lhs, binaryOpMapsLA.length - 1);
|
||||
RValue mhsInvokable = processTernaryOps(mhs);
|
||||
RValue rhsInvokable = processTernaryOps(rhs);
|
||||
|
||||
return new Conditional(input.get(lhs.size()).getPosition(), lhsInvokable, mhsInvokable, rhsInvokable);
|
||||
}
|
||||
|
||||
private static RValue processUnaryOps(LinkedList<Identifiable> input) throws ParserException {
|
||||
// Preprocess postfix operators into unary operators
|
||||
final Identifiable center;
|
||||
LinkedList<UnaryOperator> postfixes = new LinkedList<>();
|
||||
do {
|
||||
if (input.isEmpty()) {
|
||||
throw new ParserException(-1, "Expression missing.");
|
||||
}
|
||||
|
||||
final Identifiable last = input.removeLast();
|
||||
if (last instanceof OperatorToken) {
|
||||
postfixes.addLast(new UnaryOperator(last.getPosition(), "x" + ((OperatorToken) last).operator));
|
||||
} else if (last instanceof UnaryOperator) {
|
||||
postfixes.addLast(new UnaryOperator(last.getPosition(), "x" + ((UnaryOperator) last).operator));
|
||||
} else {
|
||||
center = last;
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
if (!(center instanceof RValue)) {
|
||||
throw new ParserException(center.getPosition(), "Expected expression, found " + center);
|
||||
}
|
||||
|
||||
input.addAll(postfixes);
|
||||
|
||||
RValue ret = (RValue) center;
|
||||
while (!input.isEmpty()) {
|
||||
final Identifiable last = input.removeLast();
|
||||
final int lastPosition = last.getPosition();
|
||||
if (last instanceof UnaryOperator) {
|
||||
final String operator = ((UnaryOperator) last).operator;
|
||||
if (operator.equals("+")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String opName = unaryOpMap.get(operator);
|
||||
if (opName != null) {
|
||||
try {
|
||||
ret = Operators.getOperator(lastPosition, opName, ret);
|
||||
continue;
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new ParserException(lastPosition, "No such prefix operator: " + operator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (last instanceof Token) {
|
||||
throw new ParserException(lastPosition, "Extra token found in expression: " + last);
|
||||
} else if (last instanceof RValue) {
|
||||
throw new ParserException(lastPosition, "Extra expression found: " + last);
|
||||
} else {
|
||||
throw new ParserException(lastPosition, "Extra element found: " + last);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* 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 Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.parser;
|
||||
|
||||
import com.sk89q.worldedit.internal.expression.Identifiable;
|
||||
|
||||
/**
|
||||
* A pseudo-token, inserted by the parser instead of the lexer.
|
||||
*/
|
||||
public abstract class PseudoToken implements Identifiable {
|
||||
|
||||
private final int position;
|
||||
|
||||
public PseudoToken(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract char id();
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user