Merge remote-tracking branch 'upstream/master' into merge

This commit is contained in:
Jesse Boyd
2019-11-19 21:23:47 +00:00
272 changed files with 16041 additions and 6107 deletions

View File

@ -121,7 +121,6 @@ public class MobSpawnerBlock extends BaseBlock {
@Override
public CompoundTag getNbtData() {
Map<String, Tag> values = new HashMap<>();
values.put("EntityId", new StringTag(mobType));
values.put("Delay", new ShortTag(delay));
values.put("SpawnCount", new ShortTag(spawnCount));
values.put("SpawnRange", new ShortTag(spawnRange));
@ -180,7 +179,6 @@ public class MobSpawnerBlock extends BaseBlock {
this.delay = -1;
}
ShortTag spawnCountTag = null;
ShortTag spawnRangeTag = null;
ShortTag minSpawnDelayTag = null;

View File

@ -0,0 +1,236 @@
grammar Expression;
// Lexer tokens:
PLUS : '+' ;
MINUS : '-' ;
TIMES : '*' ;
DIVIDE : '/' ;
MODULO : '%' ;
POWER : '^' | '**' ;
LEFT_SHIFT : '<<' ;
RIGHT_SHIFT : '>>' ;
ASSIGN : '=' ;
COMPLEMENT : '~' ;
PLUS_ASSIGN : '+=' ;
MINUS_ASSIGN : '-=' ;
TIMES_ASSIGN : '*=' ;
DIVIDE_ASSIGN : '/=' ;
MODULO_ASSIGN : '%=' ;
POWER_ASSIGN : '^=' ;
EQUAL : '==' ;
NOT_EQUAL : '!=' ;
NEAR : '~=' ;
LESS_THAN : '<' ;
LESS_THAN_OR_EQUAL : '<=' ;
GREATER_THAN : '>' ;
GREATER_THAN_OR_EQUAL : '>=' ;
// SC = "Short Circuit"
// Non-SC variants not currently implemented.
AND_SC : '&&' ;
OR_SC : '||' ;
INCREMENT : '++' ;
DECREMENT : '--' ;
COMMA : ',' ;
OPEN_PAREN : '(' ;
CLOSE_PAREN : ')' ;
OPEN_BRACKET : '{' ;
CLOSE_BRACKET : '}' ;
SEMI_COLON : ';' ;
QUESTION_MARK : '?' ;
COLON : ':' ;
EXCLAMATION_MARK : '!' ;
IF : 'if' ;
ELSE : 'else' ;
WHILE : 'while' ;
DO : 'do' ;
FOR : 'for' ;
BREAK : 'break' ;
CONTINUE : 'continue' ;
RETURN : 'return' ;
SWITCH : 'switch' ;
CASE : 'case' ;
DEFAULT : 'default' ;
fragment DIGIT : [0-9] ;
fragment SIGN : [+-] ;
fragment EXP_CHAR : [eE] ;
fragment DECIMAL : '.' DIGIT+ ( EXP_CHAR SIGN? DIGIT+ )? ;
// All numbers are treated the same. No int/dec divide.
NUMBER : ( DIGIT+ DECIMAL? | DECIMAL ) ;
ID : [A-Za-z] [0-9A-Za-z_]* ;
WS : [ \t\r\n\u000C]+ -> skip ;
// Parser rules:
/**
* All statements parseable from the input. Forces consumption of EOF token.
*/
allStatements : statements EOF ;
statements : statement+ ;
statement
: ( block
| ifStatement
| whileStatement
| doStatement
| forStatement
| simpleForStatement
| breakStatement
| continueStatement
| returnStatement
| switchStatement
| expressionStatement
| emptyStatement
) SEMI_COLON?
;
block : '{' statements '}' ;
ifStatement : IF '(' condition=expression ')' trueBranch=statement ( ELSE falseBranch=statement )? ;
whileStatement : WHILE '(' condition=expression ')' body=statement ;
doStatement : DO body=statement WHILE '(' condition=expression ')' ;
// C-Style for loop
forStatement
: FOR '(' init=expression ';' condition=expression ';' update=expression ')' body=statement ;
// Range for loop
simpleForStatement
: FOR '(' counter=ID ASSIGN first=expression ',' last=expression ')' body=statement ;
breakStatement : BREAK ;
continueStatement : CONTINUE ;
returnStatement : RETURN value=expression? ;
switchStatement : SWITCH '(' target=expression ')' '{' (labels+=switchLabel ':' bodies+=statements )+ '}' ;
switchLabel
: CASE constant=constantExpression # Case
| DEFAULT # Default
;
expressionStatement : expression ;
emptyStatement: SEMI_COLON ;
expression : assignmentExpression ;
assignmentExpression
: conditionalExpression
| assignment
;
assignment
: target=ID assignmentOperator source=expression
;
assignmentOperator
: ASSIGN
| POWER_ASSIGN
| TIMES_ASSIGN
| DIVIDE_ASSIGN
| MODULO_ASSIGN
| PLUS_ASSIGN
| MINUS_ASSIGN
;
conditionalExpression
: conditionalOrExpression # CEFallthrough
| condition=conditionalOrExpression QUESTION_MARK
trueBranch=expression COLON falseBranch=conditionalExpression # TernaryExpr
;
conditionalOrExpression
: conditionalAndExpression # COFallthrough
| left=conditionalOrExpression OR_SC right=conditionalAndExpression # ConditionalOrExpr
;
conditionalAndExpression
: equalityExpression # CAFallthrough
| left=conditionalAndExpression AND_SC right=equalityExpression # ConditionalAndExpr
;
equalityExpression
: relationalExpression # EqFallthrough
| left=equalityExpression
op=
( EQUAL
| NOT_EQUAL
| NEAR
) right=relationalExpression # EqualityExpr
;
relationalExpression
: shiftExpression # ReFallthrough
| left=relationalExpression
op=
( LESS_THAN
| GREATER_THAN
| LESS_THAN_OR_EQUAL
| GREATER_THAN_OR_EQUAL
) right=shiftExpression # RelationalExpr
;
shiftExpression
: additiveExpression # ShFallthrough
| left=shiftExpression op=( LEFT_SHIFT | RIGHT_SHIFT ) right=additiveExpression # ShiftExpr
;
additiveExpression
: multiplicativeExpression # AdFallthrough
| left=additiveExpression op=( PLUS | MINUS ) right=multiplicativeExpression # AddExpr
;
multiplicativeExpression
: powerExpression # MuFallthrough
| left=multiplicativeExpression
op=
( TIMES
| DIVIDE
| MODULO
) right=powerExpression # MultiplicativeExpr
;
powerExpression
: unaryExpression # PwFallthrough
| left=powerExpression POWER right=unaryExpression # PowerExpr
;
unaryExpression
: op=( INCREMENT | DECREMENT ) target=ID # PreCrementExpr
| op=( PLUS | MINUS ) expr=unaryExpression # PlusMinusExpr
| postfixExpression # UaFallthrough
| COMPLEMENT expr=unaryExpression # ComplementExpr
| EXCLAMATION_MARK expr=unaryExpression # NotExpr
;
postfixExpression
: unprioritizedExpression # PoFallthrough
| target=ID op=( INCREMENT | DECREMENT) # PostCrementExpr
| expr=postfixExpression op=EXCLAMATION_MARK # PostfixExpr
;
unprioritizedExpression
: functionCall # FunctionCallExpr
| constantExpression # ConstantExpr
| source=ID # IdExpr
| '(' expression ')' # WrappedExpr
;
constantExpression : NUMBER ;
functionCall : name=ID '(' (args+=expression ( ',' args+=expression )*)? ')' ;

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>tar.gz</format>
<format>tar.bz2</format>
<format>zip</format>
</formats>
<files>
<file>
<source>${project.build.directory}/${artifactId}-${project.version}.jar</source>
<destName>WorldEdit.jar</destName>
<outputDirectory>/</outputDirectory>
<filtered>false</filtered>
</file>
<file>
<source>README.html</source>
<outputDirectory>/</outputDirectory>
<filtered>true</filtered>
</file>
</files>
<fileSets>
<fileSet>
<includes>
<include>LICENSE.txt</include>
<include>CHANGELOG.txt</include>
<include>contrib/craftscripts/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@ -404,4 +404,4 @@ public class SimplexNoise {
this.w = w;
}
}
}
}

View File

@ -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.
*/

View File

@ -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.
*/

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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.
*/

View File

@ -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.");
}
}

View File

@ -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}.
*/

View File

@ -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;

View File

@ -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;
}

View File

@ -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.");
}
}

View File

@ -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.");
}
}

View File

@ -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);
}

View File

@ -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();

View File

@ -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));
}
}

View File

@ -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" },

View File

@ -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.");
}
}

View File

@ -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());
}
}
}
}

View File

@ -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 + ".");
}
}

View File

@ -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.");
}
}

View File

@ -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();

View File

@ -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");
}
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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.
*/

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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();
}
}
}
}

View File

@ -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);

View File

@ -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 {

View File

@ -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;
/**

View File

@ -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 {

View File

@ -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.
*

View File

@ -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.
*/

View File

@ -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

View File

@ -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));
}
}

View File

@ -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())

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
/**

View File

@ -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)) {

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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
}
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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;
}
}
}
}

View File

@ -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);
}
/**

View File

@ -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;

View File

@ -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;

View File

@ -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());
}

View File

@ -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;
}

View File

@ -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 "";
}

View File

@ -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.
*/

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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.

View File

@ -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;

View File

@ -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() {
}
}

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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() {
}
}

View File

@ -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 + ")";
}
}

View File

@ -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.

View File

@ -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();
}

View File

@ -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 keyword.
*/
public class KeywordToken extends Token {
public final String value;
public KeywordToken(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 'k';
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 "KeywordToken(" + value + ")";
}
}

View File

@ -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.

View File

@ -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());
}
}

View File

@ -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);
}
}
}

View File

@ -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 + ")";
}
}

View File

@ -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;
/**
* An identifier.
*/
public class IdentifierToken extends Token {
public final String value;
public IdentifierToken(int position, String value) {
super(position);
this.value = value;
}
@Override
public char id() {
return 'i';
}
@Override
public String toString() {
return "IdentifierToken(" + value + ")";
}
}

View File

@ -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;
}
}

Some files were not shown because too many files have changed in this diff Show More