Upstream Merge

This commit is contained in:
MattBDev 2020-01-10 22:32:12 -05:00
parent b2be1ea9fb
commit 0d2fff2cd2
81 changed files with 2528 additions and 23695 deletions

View File

@ -84,10 +84,3 @@ if (!project.hasProperty("gitCommitHash")) {
"no_git_id"
}
}
//buildScan {
// setTermsOfServiceUrl("https://gradle.com/terms-of-service")
// setTermsOfServiceAgree("yes")
//
// publishAlways()
//}

View File

@ -26,9 +26,9 @@ configurations.all {
resolutionStrategy {
// Fabric needs this.
force(
"commons-io:commons-io:2.5",
"org.ow2.asm:asm:7.1",
"org.ow2.asm:asm-commons:7.1"
"commons-io:commons-io:2.5",
"org.ow2.asm:asm:7.1",
"org.ow2.asm:asm-commons:7.1"
)
}
}

View File

@ -21,8 +21,7 @@ fun Project.applyCommonConfiguration() {
}
configurations.all {
resolutionStrategy {
cacheChangingModulesFor(10, "minutes")
cacheChangingModulesFor(5, "minutes")
}
}
}

View File

@ -121,3 +121,8 @@ fun Project.applyShadowConfiguration() {
minimize()
}
}
val CLASSPATH = listOf("truezip", "truevfs", "js")
.map { "$it.jar" }
.flatMap { listOf(it, "WorldEdit/$it") }
.joinToString(separator = " ")

View File

@ -7,6 +7,7 @@ object Versions {
const val AUTO_VALUE = "1.6.5"
const val JUNIT = "5.5.0"
const val MOCKITO = "3.0.0"
const val LOGBACK = "1.2.3"
}
// Properties that need a project reference to resolve:

View File

@ -76,7 +76,7 @@ tasks.named<Copy>("processResources") {
tasks.named<Jar>("jar") {
manifest {
attributes("Class-Path" to "truezip.jar WorldEdit/truezip.jar js.jar WorldEdit/js.jar",
attributes("Class-Path" to CLASSPATH,
"WorldEdit-Version" to project.version)
}
}

View File

@ -27,6 +27,7 @@ configurations.all {
dependencies {
"compile"(project(":worldedit-libs:core"))
"compile"("de.schlichtherle:truezip:6.8.3")
"compile"("net.java.truevfs:truevfs-profile-default_2.13:0.12.1")
"compile"("org.mozilla:rhino:1.7.11")
"compile"("org.yaml:snakeyaml:1.23")
"compile"("com.google.guava:guava:21.0")
@ -46,6 +47,8 @@ dependencies {
"annotationProcessor"("com.google.guava:guava:21.0")
"compileOnly"("com.google.auto.value:auto-value-annotations:${Versions.AUTO_VALUE}")
"annotationProcessor"("com.google.auto.value:auto-value:${Versions.AUTO_VALUE}")
"testImplementation"("ch.qos.logback:logback-core:${Versions.LOGBACK}")
"testImplementation"("ch.qos.logback:logback-classic:${Versions.LOGBACK}")
"compile"("co.aikar:fastutil-lite:1.0")
"compile"("com.github.luben:zstd-jni:1.4.3-1")
"compileOnly"("net.fabiozumbi12:redprotect:1.9.6")

View File

@ -31,6 +31,7 @@ import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.storage.InvalidFormatException;
import java.util.HashMap;
import java.util.Map;

View File

@ -25,14 +25,21 @@ import com.sk89q.worldedit.extent.NullExtent;
import com.sk89q.worldedit.function.mask.BlockMask;
import com.sk89q.worldedit.function.mask.BlockMaskBuilder;
import com.sk89q.worldedit.util.formatting.component.TextUtils;
import com.sk89q.worldedit.util.io.file.ArchiveNioSupports;
import com.sk89q.worldedit.util.logging.LogFormat;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.registry.LegacyMapper;
import com.sk89q.worldedit.world.snapshot.SnapshotRepository;
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotDatabase;
import com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@ -44,6 +51,8 @@ import java.util.Set;
*/
public abstract class LocalConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(LocalConfiguration.class);
public boolean profile = false;
public boolean traceUnflushedSessions = false;
public Set<String> disallowedBlocks = new HashSet<>();
@ -55,7 +64,9 @@ public abstract class LocalConfiguration {
public int defaultMaxPolyhedronPoints = -1;
public int maxPolyhedronPoints = 20;
public String shellSaveType = "";
public boolean snapshotsConfigured = false;
public SnapshotRepository snapshotRepo = null;
public SnapshotDatabase snapshotDatabase = null;
public int maxRadius = -1;
public int maxSuperPickaxeSize = 5;
public int maxBrushRadius = 6;
@ -193,6 +204,29 @@ public abstract class LocalConfiguration {
return new File(".");
}
public void initializeSnapshotConfiguration(String directory, boolean experimental) {
// Reset for reload
snapshotRepo = null;
snapshotDatabase = null;
snapshotsConfigured = false;
if (!directory.isEmpty()) {
if (experimental) {
try {
snapshotDatabase = FileSystemSnapshotDatabase.maybeCreate(
Paths.get(directory),
ArchiveNioSupports.combined()
);
snapshotsConfigured = true;
} catch (IOException e) {
LOGGER.warn("Failed to open snapshotDatabase", e);
}
} else {
snapshotRepo = new SnapshotRepository(directory);
snapshotsConfigured = true;
}
}
}
public String convertLegacyItem(String legacy) {
String item = legacy;
try {
@ -211,6 +245,7 @@ public abstract class LocalConfiguration {
return item;
}
public void setDefaultLocaleName(String localeName) {
this.defaultLocaleName = localeName;
if (localeName.equals("default")) {

View File

@ -38,7 +38,6 @@ import com.boydti.fawe.util.StringMan;
import com.boydti.fawe.util.TextureHolder;
import com.boydti.fawe.util.TextureUtil;
import com.boydti.fawe.wrappers.WorldWrapper;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.sk89q.jchronic.Chronic;
import com.sk89q.jchronic.Options;
@ -154,7 +153,7 @@ public class LocalSession implements TextureHolder {
private transient VirtualWorld virtual;
private transient BlockVector3 cuiTemporaryBlock;
private transient List<Countable> lastDistribution;
private transient List<Countable<BlockState>> lastDistribution;
private transient World worldOverride;
private transient boolean tickingWatchdog = false;
private transient boolean hasBeenToldVersion;
@ -896,7 +895,7 @@ public class LocalSession implements TextureHolder {
/**
* Get the position use for commands that take a center point
* (i.e., //forestgen, etc.).
* (i.e. //forestgen, etc.).
*
* @param actor the actor
* @return the position to use
@ -1485,6 +1484,17 @@ public class LocalSession implements TextureHolder {
return editSession;
}
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);
}
/**
* Checks if the session has fast mode enabled.
*
@ -1566,7 +1576,6 @@ public class LocalSession implements TextureHolder {
/**
* Get the TextureUtil currently being used
* @return
*/
@Override
public TextureUtil getTextureUtil() {
@ -1601,14 +1610,14 @@ public class LocalSession implements TextureHolder {
*
* @return block distribution or {@code null}
*/
public List<Countable> getLastDistribution() {
public List<Countable<BlockState>> getLastDistribution() {
return lastDistribution == null ? null : Collections.unmodifiableList(lastDistribution);
}
/**
* Store a block distribution in this session.
*/
public void setLastDistribution(List<Countable> dist) {
public void setLastDistribution(List<Countable<BlockState>> dist) {
lastDistribution = dist;
}
@ -1630,14 +1639,4 @@ 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

@ -0,0 +1,203 @@
/*
* 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/>.
*/
// $Id$
package com.sk89q.worldedit.command;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.util.formatting.component.PaginationBox;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.util.formatting.text.event.ClickEvent;
import com.sk89q.worldedit.util.formatting.text.event.HoverEvent;
import com.sk89q.worldedit.util.formatting.text.format.TextColor;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.snapshot.InvalidSnapshotException;
import com.sk89q.worldedit.world.snapshot.Snapshot;
import com.sk89q.worldedit.world.storage.MissingWorldException;
import java.io.File;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* Legacy snapshot command implementations. Commands are still registered via
* {@link SnapshotCommands}, but it delegates to this class when legacy snapshots are in use.
*/
class LegacySnapshotCommands {
private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
private final WorldEdit we;
LegacySnapshotCommands(WorldEdit we) {
this.we = we;
}
void list(Actor actor, World world, int page) throws WorldEditException {
LocalConfiguration config = we.getConfiguration();
try {
List<Snapshot> snapshots = config.snapshotRepo.getSnapshots(true, world.getName());
if (!snapshots.isEmpty()) {
actor.print(new SnapshotListBox(world.getName(), snapshots).create(page));
} else {
actor.printError(TranslatableComponent.of("worldedit.restore.none-found-console"));
// Okay, let's toss some debugging information!
File dir = config.snapshotRepo.getDirectory();
try {
WorldEdit.logger.info("WorldEdit found no snapshots: looked in: "
+ dir.getCanonicalPath());
} catch (IOException e) {
WorldEdit.logger.info("WorldEdit found no snapshots: looked in "
+ "(NON-RESOLVABLE PATH - does it exist?): "
+ dir.getPath());
}
}
} catch (MissingWorldException ex) {
actor.printError(TranslatableComponent.of("worldedit.restore.none-for-world"));
}
}
void use(Actor actor, World world, LocalSession session, String name) {
LocalConfiguration config = we.getConfiguration();
// Want the latest snapshot?
if (name.equalsIgnoreCase("latest")) {
try {
Snapshot snapshot = config.snapshotRepo.getDefaultSnapshot(world.getName());
if (snapshot != null) {
session.setSnapshot(null);
actor.printInfo(TranslatableComponent.of("worldedit.snapshot.use.newest"));
} else {
actor.printError(TranslatableComponent.of("worldedit.restore.none-found"));
}
} catch (MissingWorldException ex) {
actor.printError(TranslatableComponent.of("worldedit.restore.none-for-world"));
}
} else {
try {
session.setSnapshot(config.snapshotRepo.getSnapshot(name));
actor.printInfo(TranslatableComponent.of("worldedit.snapshot.use", TextComponent.of(name)));
} catch (InvalidSnapshotException e) {
actor.printError(TranslatableComponent.of("worldedit.restore.not-available"));
}
}
}
void sel(Actor actor, World world, LocalSession session, int index) {
LocalConfiguration config = we.getConfiguration();
if (index < 1) {
actor.printError(TranslatableComponent.of("worldedit.snapshot.index-above-0"));
return;
}
try {
List<Snapshot> snapshots = config.snapshotRepo.getSnapshots(true, world.getName());
if (snapshots.size() < index) {
actor.printError(TranslatableComponent.of("worldedit.snapshot.index-oob", TextComponent.of(snapshots.size())));
return;
}
Snapshot snapshot = snapshots.get(index - 1);
if (snapshot == null) {
actor.printError(TranslatableComponent.of("worldedit.restore.not-available"));
return;
}
session.setSnapshot(snapshot);
actor.printInfo(TranslatableComponent.of("worldedit.snapshot.use", TextComponent.of(snapshot.getName())));
} catch (MissingWorldException e) {
actor.printError(TranslatableComponent.of("worldedit.restore.none-for-world"));
}
}
void before(Actor actor, World world, LocalSession session, ZonedDateTime date) {
LocalConfiguration config = we.getConfiguration();
try {
Snapshot snapshot = config.snapshotRepo.getSnapshotBefore(date, world.getName());
if (snapshot == null) {
actor.printError(TranslatableComponent.of(
"worldedit.snapshot.none-before",
TextComponent.of(dateFormat.withZone(session.getTimeZone()).format(date)))
);
} else {
session.setSnapshot(snapshot);
actor.printInfo(TranslatableComponent.of("worldedit.snapshot.use", TextComponent.of(snapshot.getName())));
}
} catch (MissingWorldException ex) {
actor.printError(TranslatableComponent.of("worldedit.restore.none-for-world"));
}
}
void after(Actor actor, World world, LocalSession session, ZonedDateTime date) {
LocalConfiguration config = we.getConfiguration();
try {
Snapshot snapshot = config.snapshotRepo.getSnapshotAfter(date, world.getName());
if (snapshot == null) {
actor.printError(TranslatableComponent.of(
"worldedit.snapshot.none-after",
TextComponent.of(dateFormat.withZone(session.getTimeZone()).format(date)))
);
} else {
session.setSnapshot(snapshot);
actor.printInfo(TranslatableComponent.of("worldedit.snapshot.use", TextComponent.of(snapshot.getName())));
}
} catch (MissingWorldException ex) {
actor.printError(TranslatableComponent.of("worldedit.restore.none-for-world"));
}
}
private static class SnapshotListBox extends PaginationBox {
private final List<Snapshot> snapshots;
SnapshotListBox(String world, List<Snapshot> snapshots) {
super("Snapshots for: " + world, "/snap list -p %page%");
this.snapshots = snapshots;
}
@Override
public Component getComponent(int number) {
final Snapshot snapshot = snapshots.get(number);
return TextComponent.of(number + 1 + ". ", TextColor.GOLD)
.append(TextComponent.of(snapshot.getName(), TextColor.LIGHT_PURPLE)
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to use")))
.clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/snap use " + snapshot.getName())));
}
@Override
public int getComponentsSize() {
return snapshots.size();
}
}
}

View File

@ -0,0 +1,136 @@
/*
* 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;
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.extension.platform.Actor;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.snapshot.InvalidSnapshotException;
import com.sk89q.worldedit.world.snapshot.Snapshot;
import com.sk89q.worldedit.world.snapshot.SnapshotRestore;
import com.sk89q.worldedit.world.storage.ChunkStore;
import com.sk89q.worldedit.world.storage.MissingWorldException;
import java.io.File;
import java.io.IOException;
class LegacySnapshotUtilCommands {
private final WorldEdit we;
LegacySnapshotUtilCommands(WorldEdit we) {
this.we = we;
}
void restore(Actor actor, World world, LocalSession session, EditSession editSession,
String snapshotName) throws WorldEditException {
LocalConfiguration config = we.getConfiguration();
Region region = session.getSelection(world);
Snapshot snapshot;
if (snapshotName != null) {
try {
snapshot = config.snapshotRepo.getSnapshot(snapshotName);
} catch (InvalidSnapshotException e) {
actor.printError(TranslatableComponent.of("worldedit.restore.not-available"));
return;
}
} else {
snapshot = session.getSnapshot();
}
// No snapshot set?
if (snapshot == null) {
try {
snapshot = config.snapshotRepo.getDefaultSnapshot(world.getName());
if (snapshot == null) {
actor.printError(TranslatableComponent.of("worldedit.restore.none-found-console"));
// Okay, let's toss some debugging information!
File dir = config.snapshotRepo.getDirectory();
try {
WorldEdit.logger.info("WorldEdit found no snapshots: looked in: "
+ dir.getCanonicalPath());
} catch (IOException e) {
WorldEdit.logger.info("WorldEdit found no snapshots: looked in "
+ "(NON-RESOLVABLE PATH - does it exist?): "
+ dir.getPath());
}
return;
}
} catch (MissingWorldException ex) {
actor.printError(TranslatableComponent.of("worldedit.restore.none-for-world"));
return;
}
}
ChunkStore chunkStore;
// Load chunk store
try {
chunkStore = snapshot.getChunkStore();
actor.printInfo(TranslatableComponent.of("worldedit.restore.loaded", TextComponent.of(snapshot.getName())));
} catch (DataException | IOException e) {
actor.printError(TranslatableComponent.of("worldedit.restore.failed", TextComponent.of(e.getMessage())));
return;
}
try {
// Restore snapshot
SnapshotRestore restore = new SnapshotRestore(chunkStore, editSession, region);
//player.print(restore.getChunksAffected() + " chunk(s) will be loaded.");
restore.restore();
if (restore.hadTotalFailure()) {
String error = restore.getLastErrorMessage();
if (!restore.getMissingChunks().isEmpty()) {
actor.printError(TranslatableComponent.of("worldedit.restore.chunk-not-present"));
} else if (error != null) {
actor.printError(TranslatableComponent.of("worldedit.restore.block-place-failed"));
actor.printError(TranslatableComponent.of("worldedit.restore.block-place-error", TextComponent.of(error)));
} else {
actor.printError(TranslatableComponent.of("worldedit.restore.chunk-load-failed"));
}
} else {
actor.printInfo(TranslatableComponent.of("worldedit.restore.restored",
TextComponent.of(restore.getMissingChunks().size()),
TextComponent.of(restore.getErrorChunks().size())));
}
} finally {
try {
chunkStore.close();
} catch (IOException ignored) {
}
}
}
}

View File

@ -19,6 +19,9 @@
package com.sk89q.worldedit.command;
import static java.util.Objects.requireNonNull;
import static org.enginehub.piston.part.CommandParts.arg;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.sk89q.worldedit.LocalSession;
@ -37,13 +40,13 @@ import com.sk89q.worldedit.function.factory.Paint;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.internal.annotation.Direction;
import com.sk89q.worldedit.internal.command.CommandRegistrationHandler;
import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.regions.factory.RegionFactory;
import com.sk89q.worldedit.util.TreeGenerator;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.util.formatting.text.format.TextColor;
import com.sk89q.worldedit.util.formatting.text.format.TextDecoration;
import java.util.stream.Collectors;
import org.enginehub.piston.CommandManager;
import org.enginehub.piston.CommandManagerService;
import org.enginehub.piston.CommandParameters;
@ -54,11 +57,6 @@ import org.enginehub.piston.inject.Key;
import org.enginehub.piston.part.CommandArgument;
import org.enginehub.piston.part.SubCommandPart;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
import static org.enginehub.piston.part.CommandParts.arg;
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
public class PaintBrushCommands {
@ -104,7 +102,7 @@ public class PaintBrushCommands {
double radius = requireNonNull(RADIUS.value(parameters).asSingle(double.class));
double density = requireNonNull(DENSITY.value(parameters).asSingle(double.class)) / 100;
RegionFactory regionFactory = REGION_FACTORY.value(parameters).asSingle(RegionFactory.class);
BrushCommands.setOperationBasedBrush(player, localSession, Expression.compile(Double.toString(radius)),
BrushCommands.setOperationBasedBrush(player, localSession, radius,
new Paint(generatorFactory, density), regionFactory, "worldedit.brush.paint");
}

View File

@ -107,7 +107,7 @@ public class RegionCommands {
public int set(Actor actor, EditSession editSession,
@Selection Region region,
@Arg(desc = "The pattern of blocks to set")
Pattern pattern) {
Pattern pattern) {
int affected = editSession.setBlocks(region, pattern);
if (affected != 0) {
actor.printInfo(TranslatableComponent.of("worldedit.set.done"));
@ -260,7 +260,7 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.curve")
@Logging(REGION)
@Confirm(Confirm.Processor.REGION)
public void curve(Actor actor, EditSession editSession,
public int curve(Actor actor, EditSession editSession,
@Selection Region region,
@Arg(desc = "The pattern of blocks to place")
Pattern pattern,
@ -269,8 +269,8 @@ public class RegionCommands {
@Switch(name = 'h', desc = "Generate only a shell")
boolean shell) throws WorldEditException {
if (!(region instanceof ConvexPolyhedralRegion)) {
actor.printError(TranslatableComponent.of("worldedit.curve.convex-only"));
return;
actor.printError(TranslatableComponent.of("worldedit.curve.invalid-type"));
return 0;
}
checkCommandArgument(thickness >= 0, "Thickness must be >= 0");
@ -280,6 +280,7 @@ public class RegionCommands {
int blocksChanged = editSession.drawSpline(pattern, vectors, 0, 0, 0, 10, thickness, !shell);
actor.printInfo(TranslatableComponent.of("worldedit.curve.changed", TextComponent.of(blocksChanged)));
return blocksChanged;
}
@Command(
@ -290,7 +291,7 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.replace")
@Logging(REGION)
@Confirm(Confirm.Processor.REGION)
public void replace(Actor actor, EditSession editSession, @Selection Region region,
public int replace(Actor actor, EditSession editSession, @Selection Region region,
@Arg(desc = "The mask representing blocks to replace", def = "")
Mask from,
@Arg(desc = "The pattern of blocks to replace with")
@ -300,6 +301,7 @@ public class RegionCommands {
}
int affected = editSession.replaceBlocks(region, from, to);
actor.printInfo(TranslatableComponent.of("worldedit.replace.replaced", TextComponent.of(affected)));
return affected;
}
@Command(
@ -309,11 +311,12 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.overlay")
@Logging(REGION)
@Confirm(Confirm.Processor.REGION)
public void overlay(Actor actor, EditSession editSession, @Selection Region region,
public int overlay(Actor actor, EditSession editSession, @Selection Region region,
@Arg(desc = "The pattern of blocks to overlay")
Pattern pattern) throws WorldEditException {
int affected = editSession.overlayCuboidBlocks(region, pattern);
actor.printInfo(TranslatableComponent.of("worldedit.overlay.overlaid", TextComponent.of(affected)));
return affected;
}
@Command(
@ -363,9 +366,10 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.naturalize")
@Logging(REGION)
@Confirm(Confirm.Processor.REGION)
public void naturalize(Actor actor, EditSession editSession, @Selection Region region) throws WorldEditException {
public int naturalize(Actor actor, EditSession editSession, @Selection Region region) throws WorldEditException {
int affected = editSession.naturalizeCuboidBlocks(region);
actor.printInfo(TranslatableComponent.of("worldedit.naturalize.naturalized", TextComponent.of(affected)));
return affected;
}
@Command(
@ -375,11 +379,12 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.walls")
@Logging(REGION)
@Confirm(Confirm.Processor.REGION)
public void walls(Actor actor, EditSession editSession, @Selection Region region,
public int walls(Actor actor, EditSession editSession, @Selection Region region,
@Arg(desc = "The pattern of blocks to set")
Pattern pattern) throws WorldEditException {
int affected = editSession.makeWalls(region, pattern);
actor.printInfo(TranslatableComponent.of("worldedit.walls.changed", TextComponent.of(affected)));
return affected;
}
@Command(
@ -390,11 +395,12 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.faces")
@Logging(REGION)
@Confirm(Confirm.Processor.REGION)
public void faces(Actor actor, EditSession editSession, @Selection Region region,
public int faces(Actor actor, EditSession editSession, @Selection Region region,
@Arg(desc = "The pattern of blocks to set")
Pattern pattern) throws WorldEditException {
int affected = editSession.makeCuboidFaces(region, pattern);
actor.printInfo(TranslatableComponent.of("worldedit.faces.changed", TextComponent.of(affected)));
return affected;
}
@Command(
@ -405,7 +411,7 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.smooth")
@Logging(REGION)
@Confirm(Confirm.Processor.REGION)
public void smooth(Actor actor, EditSession editSession, @Selection Region region,
public int smooth(Actor actor, EditSession editSession, @Selection Region region,
@Arg(desc = "# of iterations to perform", def = "1")
int iterations,
@Arg(desc = "The mask of blocks to use as the height map", def = "")
@ -418,14 +424,16 @@ public class RegionCommands {
if (volume >= limit.MAX_CHECKS) {
throw FaweCache.MAX_CHECKS;
}
int affected = 0;
try {
HeightMap heightMap = new HeightMap(editSession, region, mask, snow);
HeightMapFilter filter = new HeightMapFilter(new GaussianKernel(5, 1.0));
int affected = heightMap.applyFilter(filter, iterations);
actor.printInfo(TranslatableComponent.of("worldedit.smooth.changed", TextComponent.of(affected)));
HeightMapFilter filter = new HeightMapFilter(new GaussianKernel(5, 1.0));
affected = heightMap.applyFilter(filter, iterations);
actor.printInfo(TranslatableComponent.of("worldedit.smooth.changed", TextComponent.of(affected)));
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
return affected;
}
@Command(
@ -605,13 +613,20 @@ public class RegionCommands {
@Arg(def = "", desc = "Regenerate with biome") BiomeType biome,
@Arg(def = "", desc = "Regenerate with seed") Long seed) throws WorldEditException {
Mask mask = session.getMask();
session.setMask((Mask) null);
session.setSourceMask((Mask) null);
world.regenerate(region, editSession);
// editSession.regenerate(region, biome, seed);
session.setMask(mask);
session.setSourceMask(mask);
boolean success;
try {
session.setMask((Mask) null);
session.setSourceMask((Mask) null);
success = world.regenerate(region, editSession);
} finally {
session.setMask(mask);
session.setSourceMask(mask);
}
if (success) {
actor.printInfo(TranslatableComponent.of("worldedit.regen.regenerated"));
} else {
actor.printError(TranslatableComponent.of("worldedit.regen.failed"));
}
}
@Command(
@ -624,7 +639,7 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.deform")
@Logging(ALL)
@Confirm(Confirm.Processor.REGION)
public void deform(Actor actor, LocalSession session, EditSession editSession,
public int deform(Actor actor, LocalSession session, EditSession editSession,
@Selection Region region,
@Arg(desc = "The expression to use", variable = true)
List<String> expression,
@ -660,8 +675,10 @@ public class RegionCommands {
((Player) actor).findFreePosition();
}
actor.printInfo(TranslatableComponent.of("worldedit.deform.deformed", TextComponent.of(affected)));
return affected;
} catch (ExpressionException e) {
actor.printError(TextComponent.of(e.getMessage()));
return 0;
}
}
@ -676,17 +693,18 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.hollow")
@Logging(REGION)
@Confirm(Confirm.Processor.REGION)
public void hollow(Actor actor, EditSession editSession,
public int hollow(Actor actor, EditSession editSession,
@Selection Region region,
@Range(from=0, to=Integer.MAX_VALUE) @Arg(desc = "Thickness of the shell to leave", def = "0")
@Range(from = 0, to = Integer.MAX_VALUE) @Arg(desc = "Thickness of the shell to leave", def = "0")
int thickness,
@Arg(desc = "The pattern of blocks to replace the hollowed area with", def = "air")
Pattern pattern,
@ArgFlag(name = 'm', desc = "Mask to hollow with") Mask mask) throws WorldEditException {
@ArgFlag(name = 'm', desc = "Mask to hollow with") Mask mask) throws WorldEditException {
checkCommandArgument(thickness >= 0, "Thickness must be >= 0");
Mask finalMask = mask == null ? new SolidBlockMask(editSession) : mask;
int affected = editSession.hollowOutRegion(region, thickness, pattern, finalMask);
actor.printInfo(TranslatableComponent.of("worldedit.hollow.changed", TextComponent.of(affected)));
return affected;
}
@Command(
@ -714,18 +732,20 @@ public class RegionCommands {
@CommandPermissions("worldedit.region.flora")
@Logging(REGION)
@Confirm(Confirm.Processor.REGION)
public void flora(Actor actor, EditSession editSession, @Selection Region region,
public int flora(Actor actor, EditSession editSession, @Selection Region region,
@Arg(desc = "The density of the forest", def = "5")
double density) throws WorldEditException {
checkCommandArgument(0 <= density && density <= 100, "Density must be in [0, 100]");
density = density / 100;
FloraGenerator generator = new FloraGenerator(editSession);
GroundFunction ground = new GroundFunction(new ExistingBlockMask(editSession), generator);
LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground);
visitor.setMask(new NoiseFilter2D(new RandomNoise(), density / 100));
visitor.setMask(new NoiseFilter2D(new RandomNoise(), density));
Operations.completeLegacy(visitor);
int affected = ground.getAffected();
actor.printInfo(TranslatableComponent.of("worldedit.flora.created", TextComponent.of(affected)));
return affected;
}
}

View File

@ -213,11 +213,11 @@ public class SchematicCommands {
public void load(Actor actor, LocalSession session,
@Arg(desc = "File name.")
String filename,
@Arg(desc = "Format name.", def = "")
@Arg(desc = "Format name.", def = "sponge")
String formatName) throws FilenameException {
LocalConfiguration config = worldEdit.getConfiguration();
ClipboardFormat format = formatName != null ? ClipboardFormats.findByAlias(formatName) : null;
ClipboardFormat format = ClipboardFormats.findByAlias(formatName);
InputStream in = null;
try {
URI uri;
@ -537,7 +537,7 @@ public class SchematicCommands {
)
@CommandPermissions("worldedit.schematic.list")
public void list(Actor actor, LocalSession session,
@ArgFlag(name = 'p', desc = "Page to view.", def = "-1")
@ArgFlag(name = 'p', desc = "Page to view.", def = "1")
int page,
@Switch(name = 'd', desc = "Sort by date, oldest first")
boolean oldFirst,
@ -571,15 +571,6 @@ public class SchematicCommands {
final boolean hasShow = false;
//If player forgot -p argument
if (page == -1) {
page = 1;
if (args.size() != 0) {
String lastArg = args.get(args.size() - 1);
if (MathMan.isInteger(lastArg)) {
page = Integer.parseInt(lastArg);
}
}
}
boolean playerFolder = Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS;
UUID uuid = playerFolder ? actor.getUniqueId() : null;
List<File> files = UtilityCommands.getFiles(dir, actor, args, formatName, playerFolder, oldFirst, newFirst);

View File

@ -19,22 +19,15 @@
package com.sk89q.worldedit.command;
import com.boydti.fawe.config.Caption;
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;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.block.BlockDistributionCounter;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.visitor.RegionVisitor;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.boydti.fawe.config.Caption;
import com.boydti.fawe.object.clipboard.URIClipboardHolder;
import com.boydti.fawe.object.mask.IdMask;
import com.boydti.fawe.object.regions.selector.FuzzyRegionSelector;
import com.boydti.fawe.object.regions.selector.PolyhedralRegionSelector;
import com.boydti.fawe.util.ExtentTraverser;
import com.google.common.base.Strings;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
@ -50,9 +43,12 @@ import com.sk89q.worldedit.entity.Player;
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.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.block.BlockDistributionCounter;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.visitor.RegionVisitor;
import com.sk89q.worldedit.internal.annotation.Direction;
import com.sk89q.worldedit.internal.annotation.MultiDirection;
import com.sk89q.worldedit.math.BlockVector2;
@ -72,31 +68,32 @@ import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.Countable;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.formatting.component.CommandListBox;
import com.sk89q.worldedit.util.formatting.component.InvalidComponentException;
import com.sk89q.worldedit.util.formatting.component.PaginationBox;
import com.sk89q.worldedit.util.formatting.component.SubtleFormat;
import com.sk89q.worldedit.util.formatting.component.TextComponentProducer;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.util.formatting.text.event.ClickEvent;
import com.sk89q.worldedit.util.formatting.text.event.HoverEvent;
import com.sk89q.worldedit.util.formatting.text.format.TextColor;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.item.ItemType;
import com.sk89q.worldedit.world.item.ItemTypes;
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.annotation.param.Switch;
import org.enginehub.piston.exception.StopExecutionException;
/**
@ -306,9 +303,6 @@ public class SelectionCommands {
session.setTool(itemType, SelectionWand.INSTANCE);
player.printInfo(TranslatableComponent.of("worldedit.wand.selwand.info"));
}
if (!player.hasPermission("fawe.tips"))
System.out.println("TODO FIXME tips");
// TranslatableComponent.of("fawe.tips.tip.sel.list").or(TranslatableComponent.of("fawe.tips.tip.select.connected"), TranslatableComponent.of("fawe.tips.tip.set.pos1"), TranslatableComponent.of("fawe.tips.tip.farwand"), TranslatableComponent.of("fawe.tips.tip.discord")).send(player);
}
@Command(
@ -480,8 +474,7 @@ public class SelectionCommands {
region = clipboard.getRegion();
BlockVector3 size = region.getMaximumPoint()
.subtract(region.getMinimumPoint()).
add(1, 1, 1);
.subtract(region.getMinimumPoint()).add(1, 1, 1);
BlockVector3 origin = clipboard.getOrigin();
String sizeStr = size.getBlockX() + "*" + size.getBlockY() + "*" + size.getBlockZ();
@ -493,14 +486,13 @@ public class SelectionCommands {
}
return;
} else {
region = session.getSelection(world);
region = session.getSelection(world);
actor.printInfo(TranslatableComponent.of("worldedit.size.type", TextComponent.of(session.getRegionSelector(world).getTypeName())));
for (Component line : session.getRegionSelector(world).getSelectionInfoLines()) {
actor.print(line);
}
}
BlockVector3 size = region.getMaximumPoint()
.subtract(region.getMinimumPoint())
@ -529,34 +521,34 @@ public class SelectionCommands {
desc = "Get the distribution of blocks in the selection"
)
@CommandPermissions("worldedit.analysis.distr")
public void distr(Actor actor, World world, LocalSession session, EditSession editSession,
public void distr(Actor actor, World world, LocalSession session,
@Switch(name = 'c', desc = "Get the distribution of the clipboard instead")
boolean clipboardDistr,
@Switch(name = 'd', desc = "Separate blocks by state")
boolean separateStates,
@ArgFlag(name = 'p', desc = "Gets page from a previous distribution.", def = "")
Integer page) throws WorldEditException {
List<Countable> distribution;
List<Countable<BlockState>> distribution;
Region region;
if (page == null) {
Extent extent;
if (clipboardDistr) {
Clipboard clipboard = session.getClipboard().getClipboard(); // throws if missing
extent = clipboard;
region = clipboard.getRegion();
BlockDistributionCounter count = new BlockDistributionCounter(clipboard, separateStates);
RegionVisitor visitor = new RegionVisitor(clipboard.getRegion(), count);
Operations.completeBlindly(visitor);
distribution = count.getDistribution();
} else {
extent = editSession;
region = session.getSelection(world);
try (EditSession editSession = session.createEditSession(actor)) {
distribution = editSession
.getBlockDistribution(session.getSelection(world), separateStates);
}
}
if (separateStates)
distribution = (List) extent.getBlockDistributionWithData(region);
else
distribution = (List) extent.getBlockDistribution(region);
session.setLastDistribution(distribution);
page = 1;
} else {
distribution = (List) session.getLastDistribution();
distribution = session.getLastDistribution();
if (distribution == null) {
actor.printError(TranslatableComponent.of("worldedit.distr.no-previous"));
return;
@ -572,69 +564,6 @@ public class SelectionCommands {
actor.print(res.create(page));
}
public static class BlockDistributionResult extends PaginationBox {
private final List<Countable> distribution;
private final int totalBlocks;
private final boolean separateStates;
public BlockDistributionResult(List<Countable> distribution, boolean separateStates) {
this(distribution, separateStates, "//distr -p %page%" + (separateStates ? " -d" : ""));
}
public BlockDistributionResult(List<Countable> distribution, boolean separateStates, String pageCommand) {
super("Block Distribution", pageCommand);
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.GRAY);
TextComponent toolTip;
if (separateStates && state != blockType.getDefaultState()) {
toolTip = TextComponent.of(state.getAsString(), TextColor.GRAY);
blockName = blockName.append(TextComponent.of("*", TextColor.GRAY));
} 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(TranslatableComponent.of("worldedit.distr.total", TextColor.GRAY, TextComponent.of(totalBlocks)))
.append(TextComponent.newline());
return super.create(page);
}
}
@Command(
name = "/sel",
aliases = { ";", "/desel", "/deselect" },
@ -724,7 +653,7 @@ public class SelectionCommands {
box.appendCommand("sphere", TranslatableComponent.of("worldedit.select.sphere.description"), "//sel sphere");
box.appendCommand("cyl", TranslatableComponent.of("worldedit.select.cyl.description"), "//sel cyl");
box.appendCommand("convex", TranslatableComponent.of("worldedit.select.convex.description"), "//sel convex");
box.appendCommand("polyhedral", "Select a hollow polyhedral", "//sel polyhedral");
box.appendCommand("polyhedral", "Select a hollow polyhedral", "//sel polyhedral");
box.appendCommand("fuzzy[=<mask>]", "Select all connected blocks (magic wand)", "//sel fuzzy[=<mask>]");
actor.print(box.create(1));
@ -751,4 +680,67 @@ public class SelectionCommands {
session.setRegionSelector(world, newSelector);
session.dispatchCUISelection(actor);
}
public static class BlockDistributionResult extends PaginationBox {
private final List<Countable<BlockState>> distribution;
private final int totalBlocks;
private final boolean separateStates;
public BlockDistributionResult(List<Countable<BlockState>> distribution, boolean separateStates) {
this(distribution, separateStates, "//distr -p %page%" + (separateStates ? " -d" : ""));
}
public BlockDistributionResult(List<Countable<BlockState>> distribution, boolean separateStates, String pageCommand) {
super("Block Distribution", pageCommand);
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(TranslatableComponent.of("worldedit.distr.total", TextColor.GRAY, TextComponent.of(totalBlocks)))
.append(TextComponent.newline());
return super.create(page);
}
}
}

View File

@ -259,7 +259,7 @@ public class SnapshotCommands {
public Component getComponent(int number) {
final Snapshot snapshot = snapshots.get(number);
return TextComponent.of(number + 1 + ". ", TextColor.GOLD)
.append(TextComponent.of(snapshot.getName(), TextColor.GRAY)
.append(TextComponent.of(snapshot.getName(), TextColor.LIGHT_PURPLE)
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to use")))
.clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/snap use " + snapshot.getName())));
}

View File

@ -61,8 +61,8 @@ public class ExpressionMaskParser extends InputParser<Mask> {
exp.setEnvironment(env);
if (context.getActor() != null) {
SessionOwner owner = context.getActor();
Integer timeout = WorldEdit.getInstance().getSessionManager().get(owner).getTimeout();
return new ExpressionMask(exp, timeout::intValue);
IntSupplier timeout = () -> WorldEdit.getInstance().getSessionManager().get(owner).getTimeout();
return new ExpressionMask(exp, timeout);
}
return new ExpressionMask(exp);
} catch (ExpressionException e) {

View File

@ -32,8 +32,6 @@ import com.sk89q.worldedit.math.BlockVector3;
import java.util.stream.Stream;
import java.util.stream.Stream;
public class OffsetMaskParser extends InputParser<Mask> {
public OffsetMaskParser(WorldEdit worldEdit) {

View File

@ -37,9 +37,9 @@ public class BlockMaskBuilder {
}
private boolean filterRegex(BlockType blockType, PropertyKey key, String regex) {
Property property = blockType.getProperty(key);
Property<Object> property = blockType.getProperty(key);
if (property == null) return false;
List values = property.getValues();
List<Object> values = property.getValues();
boolean result = false;
for (int i = 0; i < values.size(); i++) {
Object value = values.get(i);
@ -52,10 +52,10 @@ public class BlockMaskBuilder {
}
private boolean filterOperator(BlockType blockType, PropertyKey key, Operator operator, CharSequence value) {
Property property = blockType.getProperty(key);
Property<Object> property = blockType.getProperty(key);
if (property == null) return false;
int index = property.getIndexFor(value);
List values = property.getValues();
List<Object> values = property.getValues();
boolean result = false;
for (int i = 0; i < values.size(); i++) {
if (!operator.test(index, i) && has(blockType, property, i)) {
@ -151,7 +151,7 @@ public class BlockMaskBuilder {
throw new SuggestInputParseException("No value for " + input, input, () -> {
HashSet<String> values = new HashSet<>();
types.stream().filter(t -> t.hasProperty(fKey)).forEach(t -> {
Property p = t.getProperty(fKey);
Property<Object> p = t.getProperty(fKey);
for (int j = 0; j < p.getValues().size(); j++) {
if (has(t, p, j)) {
String o = p.getValues().get(j).toString();
@ -220,8 +220,8 @@ public class BlockMaskBuilder {
return this;
}
private boolean has(BlockType type, Property property, int index) {
AbstractProperty prop = (AbstractProperty) property;
private <T> boolean has(BlockType type, Property<T> property, int index) {
AbstractProperty<T> prop = (AbstractProperty<T>) property;
long[] states = bitSets[type.getInternalId()];
if (states == null) return false;
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
@ -247,10 +247,7 @@ public class BlockMaskBuilder {
private boolean optimizedStates = true;
public boolean isEmpty() {
for (long[] bitSet : bitSets) {
if (bitSet != null) return false;
}
return true;
return Arrays.stream(bitSets).noneMatch(Objects::nonNull);
}
public BlockMaskBuilder() {
@ -283,7 +280,7 @@ public class BlockMaskBuilder {
return this;
}
public BlockMaskBuilder remove(BlockStateHolder state) {
public <T extends BlockStateHolder<T>> BlockMaskBuilder remove(BlockStateHolder<T> state) {
BlockType type = state.getBlockType();
int i = type.getInternalId();
long[] states = bitSets[i];
@ -308,7 +305,7 @@ public class BlockMaskBuilder {
return this;
}
public BlockMaskBuilder filter(BlockStateHolder state) {
public <T extends BlockStateHolder<T>> BlockMaskBuilder filter(BlockStateHolder<T> state) {
filter(state.getBlockType());
BlockType type = state.getBlockType();
int i = type.getInternalId();
@ -351,8 +348,8 @@ public class BlockMaskBuilder {
continue;
}
List<AbstractProperty<?>> properties = (List<AbstractProperty<?>>) type.getProperties();
for (AbstractProperty prop : properties) {
List values = prop.getValues();
for (AbstractProperty<?> prop : properties) {
List<?> values = prop.getValues();
for (int j = 0; j < values.size(); j++) {
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
if (states == ALL || FastBitSet.get(states, localI)) {
@ -376,7 +373,7 @@ public class BlockMaskBuilder {
return this;
}
public BlockMaskBuilder add(BlockStateHolder state) {
public <T extends BlockStateHolder<T>> BlockMaskBuilder add(BlockStateHolder<T> state) {
BlockType type = state.getBlockType();
int i = type.getInternalId();
long[] states = bitSets[i];
@ -391,8 +388,8 @@ public class BlockMaskBuilder {
return this;
}
public <T extends BlockStateHolder> BlockMaskBuilder addBlocks(Collection<T> blocks) {
for (BlockStateHolder block : blocks) add(block);
public <T extends BlockStateHolder<T>> BlockMaskBuilder addBlocks(Collection<T> blocks) {
for (BlockStateHolder<T> block : blocks) add(block);
return this;
}
@ -401,8 +398,8 @@ public class BlockMaskBuilder {
return this;
}
public <T extends BlockStateHolder> BlockMaskBuilder addBlocks(T... blocks) {
for (BlockStateHolder block : blocks) add(block);
public <T extends BlockStateHolder<T>> BlockMaskBuilder addBlocks(T... blocks) {
for (BlockStateHolder<T> block : blocks) add(block);
return this;
}
@ -429,8 +426,8 @@ public class BlockMaskBuilder {
if (!typePredicate.test(type)) {
continue;
}
for (AbstractProperty prop : (List<AbstractProperty<?>>) type.getProperties()) {
List values = prop.getValues();
for (AbstractProperty<?> prop : (List<AbstractProperty<?>>) type.getProperties()) {
List<?> values = prop.getValues();
for (int j = 0; j < values.size(); j++) {
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
if (states == null || !FastBitSet.get(states, localI)) {
@ -448,12 +445,12 @@ public class BlockMaskBuilder {
return this;
}
public BlockMaskBuilder add(BlockType type, Property property, int index) {
AbstractProperty prop = (AbstractProperty) property;
public <T> BlockMaskBuilder add(BlockType type, Property<T> property, int index) {
AbstractProperty<T> prop = (AbstractProperty<T>) property;
long[] states = bitSets[type.getInternalId()];
if (states == ALL) return this;
List values = property.getValues();
List<T> values = property.getValues();
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
if (states == null || !FastBitSet.get(states, localI)) {
if (states == null) {
@ -465,11 +462,11 @@ public class BlockMaskBuilder {
return this;
}
public BlockMaskBuilder filter(BlockType type, Property property, int index) {
AbstractProperty prop = (AbstractProperty) property;
public <T> BlockMaskBuilder filter(BlockType type, Property<T> property, int index) {
AbstractProperty<T> prop = (AbstractProperty<T>) property;
long[] states = bitSets[type.getInternalId()];
if (states == null) return this;
List values = property.getValues();
List<T> values = property.getValues();
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
if (states == ALL || FastBitSet.get(states, localI)) {
if (states == ALL) {
@ -537,8 +534,8 @@ public class BlockMaskBuilder {
}
int set = 0;
int clear = 0;
for (AbstractProperty prop : (List<AbstractProperty<?>>) type.getProperties()) {
List values = prop.getValues();
for (AbstractProperty<?> prop : (List<AbstractProperty<?>>) type.getProperties()) {
List<?> values = prop.getValues();
for (int j = 0; j < values.size(); j++) {
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
if (FastBitSet.get(bitSet, localI)) set++;

View File

@ -26,4 +26,4 @@ public interface CompiledExpression {
Double execute(ExecutionData executionData);
}
}

View File

@ -48,4 +48,4 @@ public class ExecutionData {
public SetMultimap<String, MethodHandle> getFunctions() {
return requireNonNull(functions, "Cannot use functions in a constant");
}
}
}

View File

@ -35,14 +35,6 @@ import static com.sk89q.worldedit.antlr.ExpressionLexer.ID;
public class ExpressionHelper {
/**
* The argument should be wrapped in a {@link LocalSlot.Constant} before being passed.
*/
public static final String WRAPPED_CONSTANT = "<wrapped constant>";
private ExpressionHelper() {
}
public static void check(boolean condition, ParserRuleContext ctx, String message) {
if (!condition) {
throw evalException(ctx, message);
@ -59,8 +51,8 @@ public class ExpressionHelper {
public static EvaluationException evalException(Token token, String message) {
return new EvaluationException(
getErrorPosition(token),
message
getErrorPosition(token),
message
);
}
@ -68,8 +60,6 @@ public class ExpressionHelper {
check(iterations <= 256, ctx, "Loop exceeded 256 iterations");
}
// Special argument handle names
public static void checkTimeout() {
if (Thread.interrupted()) {
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
@ -97,15 +87,21 @@ public class ExpressionHelper {
}
// 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("/"));
.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());
"expected " + possibleCounts + ", " +
"got " + ctx.args.size());
}
// Special argument handle names
/**
* The argument should be wrapped in a {@link LocalSlot.Constant} before being passed.
*/
public 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.
@ -118,7 +114,7 @@ public class ExpressionHelper {
if (pType == LocalSlot.Variable.class) {
// MUST be an id
check(id.isPresent(), arg,
"Function '" + fnName + "' requires a variable in parameter " + i);
"Function '" + fnName + "' requires a variable in parameter " + i);
return id.get();
} else if (pType == LocalSlot.class) {
return id.orElse(WRAPPED_CONSTANT);
@ -128,7 +124,7 @@ public class ExpressionHelper {
private static Optional<String> tryResolveId(ParserRuleContext arg) {
Optional<ExpressionParser.WrappedExprContext> wrappedExprContext =
tryAs(arg, ExpressionParser.WrappedExprContext.class);
tryAs(arg, ExpressionParser.WrappedExprContext.class);
if (wrappedExprContext.isPresent()) {
return tryResolveId(wrappedExprContext.get().expression());
}
@ -139,8 +135,8 @@ public class ExpressionHelper {
}
private static <T extends ParserRuleContext> Optional<T> tryAs(
ParserRuleContext ctx,
Class<T> rule
ParserRuleContext ctx,
Class<T> rule
) {
if (rule.isInstance(ctx)) {
return Optional.of(rule.cast(ctx));
@ -155,4 +151,7 @@ public class ExpressionHelper {
return tryAs(ctxs.get(0), rule);
}
}
private ExpressionHelper() {
}
}

View File

@ -22,8 +22,8 @@ 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.collect.Multimaps;
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;
@ -61,7 +61,7 @@ final class Functions {
// clean up all the functions
return ImmutableSetMultimap.copyOf(
Multimaps.transformValues(map, Functions::clean)
Multimaps.transformValues(map, Functions::clean)
);
}
@ -71,7 +71,7 @@ final class Functions {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
DOUBLE_VALUE = lookup.findVirtual(Number.class, "doubleValue",
methodType(double.class));
methodType(double.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
@ -83,7 +83,7 @@ final class Functions {
if (handle.type().returnType() != Double.class) {
// Ensure that the handle returns a Double, even if originally a Number
checkState(Number.class.isAssignableFrom(handle.type().returnType()),
"Function does not return a number");
"Function does not return a number");
handle = handle.asType(handle.type().changeReturnType(Number.class));
handle = filterReturnValue(handle, DOUBLE_VALUE);
}

View File

@ -22,7 +22,11 @@ package com.sk89q.worldedit.internal.expression.invoke;
import com.google.common.collect.SetMultimap;
import com.sk89q.worldedit.antlr.ExpressionBaseVisitor;
import com.sk89q.worldedit.antlr.ExpressionParser;
import com.sk89q.worldedit.internal.expression.*;
import com.sk89q.worldedit.internal.expression.BreakException;
import com.sk89q.worldedit.internal.expression.EvaluationException;
import com.sk89q.worldedit.internal.expression.ExecutionData;
import com.sk89q.worldedit.internal.expression.ExpressionHelper;
import com.sk89q.worldedit.internal.expression.LocalSlot;
import it.unimi.dsi.fastutil.doubles.Double2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
import org.antlr.v4.runtime.CommonToken;
@ -40,9 +44,35 @@ import java.util.function.DoubleBinaryOperator;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static com.sk89q.worldedit.antlr.ExpressionLexer.*;
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.invoke.ExpressionHandles.*;
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.CALL_BINARY_OP;
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.DOUBLE_TO_BOOL;
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.IS_NULL;
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.NEW_LS_CONSTANT;
import static com.sk89q.worldedit.internal.expression.invoke.ExpressionHandles.NULL_DOUBLE;
import static java.lang.invoke.MethodType.methodType;
/**
@ -50,26 +80,6 @@ import static java.lang.invoke.MethodType.methodType;
*/
class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
private static final MethodHandle BREAK_STATEMENT =
ExpressionHandles.dropData(MethodHandles.throwException(Double.class, BreakException.class)
.bindTo(BreakException.BREAK));
private static final MethodHandle CONTINUE_STATEMENT =
ExpressionHandles.dropData(MethodHandles.throwException(Double.class, BreakException.class)
.bindTo(BreakException.CONTINUE));
private static final double[] factorials = new double[171];
/**
* Method handle (ExecutionData)Double, returns null.
*/
private static final MethodHandle DEFAULT_RESULT =
ExpressionHandles.dropData(MethodHandles.constant(Double.class, null));
static {
factorials[0] = 1;
for (int i = 1; i < factorials.length; ++i) {
factorials[i] = factorials[i - 1] * i;
}
}
/*
* General idea is that we don't need to pass around variables, they're all in ExecutionData.
* We do need to pass that around, so most MethodHandles will be of the type
@ -82,43 +92,11 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
this.functions = functions;
}
// 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;
}
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];
}
private Token extractToken(ParserRuleContext ctx) {
List<TerminalNode> children = ctx.children.stream()
.filter(TerminalNode.class::isInstance)
.map(TerminalNode.class::cast)
.collect(Collectors.toList());
.filter(TerminalNode.class::isInstance)
.map(TerminalNode.class::cast)
.collect(Collectors.toList());
ExpressionHelper.check(children.size() == 1, ctx, "Expected exactly one token, got " + children.size());
return children.get(0).getSymbol();
}
@ -133,19 +111,19 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
private void checkHandle(MethodHandle mh, ParserRuleContext ctx) {
ExpressionHelper.check(mh.type().equals(ExpressionHandles.COMPILED_EXPRESSION_SIG), ctx,
"Incorrect type returned from handler for " + ctx.getClass());
"Incorrect type returned from handler for " + ctx.getClass());
}
private MethodHandle evaluateForNamedValue(ParserRuleContext ctx, String name) {
MethodHandle guard = MethodHandles.guardWithTest(
// if result is null
IS_NULL.asType(methodType(boolean.class, Double.class)),
// throw appropriate exception, dropping `result` argument
MethodHandles.dropArguments(
ExpressionHandles.throwEvalException(ctx, "Invalid expression for " + name), 0, Double.class
),
// else return the argument we were passed
MethodHandles.identity(Double.class)
// if result is null
IS_NULL.asType(methodType(boolean.class, Double.class)),
// throw appropriate exception, dropping `result` argument
MethodHandles.dropArguments(
ExpressionHandles.throwEvalException(ctx, "Invalid expression for " + name), 0, Double.class
),
// else return the argument we were passed
MethodHandles.identity(Double.class)
);
// now pass `result` into `guard`
MethodHandle result = evaluate(ctx).handle;
@ -161,7 +139,7 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
value = value.asType(value.type().unwrap());
// Pass `value` into converter, returns (ExecutionData)boolean;
return MethodHandles.collectArguments(
DOUBLE_TO_BOOL, 0, value
DOUBLE_TO_BOOL, 0, value
);
}
@ -170,9 +148,9 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
ParserRuleContext falseBranch) {
// easiest one of the bunch
return MethodHandles.guardWithTest(
evaluateBoolean(condition),
trueBranch == null ? NULL_DOUBLE : evaluate(trueBranch).handle,
falseBranch == null ? NULL_DOUBLE : evaluate(falseBranch).handle
evaluateBoolean(condition),
trueBranch == null ? NULL_DOUBLE : evaluate(trueBranch).handle,
falseBranch == null ? NULL_DOUBLE : evaluate(falseBranch).handle
);
}
@ -189,39 +167,46 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
@Override
public MethodHandle visitWhileStatement(ExpressionParser.WhileStatementContext ctx) {
return ExpressionHandles.whileLoop(
evaluateBoolean(ctx.condition),
evaluate(ctx.body)
evaluateBoolean(ctx.condition),
evaluate(ctx.body)
);
}
@Override
public MethodHandle visitDoStatement(ExpressionParser.DoStatementContext ctx) {
return ExpressionHandles.doWhileLoop(
evaluateBoolean(ctx.condition),
evaluate(ctx.body)
evaluateBoolean(ctx.condition),
evaluate(ctx.body)
);
}
@Override
public MethodHandle visitForStatement(ExpressionParser.ForStatementContext ctx) {
return ExpressionHandles.forLoop(
evaluate(ctx.init).handle,
evaluateBoolean(ctx.condition),
evaluate(ctx.body),
evaluate(ctx.update).handle
evaluate(ctx.init).handle,
evaluateBoolean(ctx.condition),
evaluate(ctx.body),
evaluate(ctx.update).handle
);
}
@Override
public MethodHandle visitSimpleForStatement(ExpressionParser.SimpleForStatementContext ctx) {
return ExpressionHandles.simpleForLoop(
evaluateForValue(ctx.first),
evaluateForValue(ctx.last),
ctx.counter,
evaluate(ctx.body)
evaluateForValue(ctx.first),
evaluateForValue(ctx.last),
ctx.counter,
evaluate(ctx.body)
);
}
private static final MethodHandle BREAK_STATEMENT =
ExpressionHandles.dropData(MethodHandles.throwException(Double.class, BreakException.class)
.bindTo(BreakException.BREAK));
private static final MethodHandle CONTINUE_STATEMENT =
ExpressionHandles.dropData(MethodHandles.throwException(Double.class, BreakException.class)
.bindTo(BreakException.CONTINUE));
@Override
public MethodHandle visitBreakStatement(ExpressionParser.BreakStatementContext ctx) {
return BREAK_STATEMENT;
@ -309,7 +294,7 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
return value;
case MINUS:
return ExpressionHandles.call(data ->
-(double) ExpressionHandles.standardInvoke(value, data)
-(double) ExpressionHandles.standardInvoke(value, data)
);
}
throw ExpressionHelper.evalException(ctx, "Invalid text for plus/minus expr: " + ctx.op.getText());
@ -319,7 +304,7 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
public MethodHandle visitNotExpr(ExpressionParser.NotExprContext ctx) {
MethodHandle expr = evaluateBoolean(ctx.expr);
return ExpressionHandles.call(data ->
ExpressionHandles.boolToDouble(!(boolean) ExpressionHandles.standardInvoke(expr, data))
ExpressionHandles.boolToDouble(!(boolean) ExpressionHandles.standardInvoke(expr, data))
);
}
@ -331,7 +316,7 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
// - Convert to long from double value
// - Convert from Object to Double to double.
return ExpressionHandles.call(data ->
(double) ~(long) (double) ExpressionHandles.standardInvoke(expr, data)
(double) ~(long) (double) ExpressionHandles.standardInvoke(expr, data)
);
}
@ -340,11 +325,11 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
MethodHandle left = evaluateBoolean(ctx.left);
MethodHandle right = evaluateForValue(ctx.right);
return MethodHandles.guardWithTest(
left,
right,
ExpressionHandles.dropData(
MethodHandles.constant(Double.class, ExpressionHandles.boolToDouble(false))
)
left,
right,
ExpressionHandles.dropData(
MethodHandles.constant(Double.class, ExpressionHandles.boolToDouble(false))
)
);
}
@ -355,20 +340,20 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
// Inject left as primary condition, on failure take right with data parameter
// logic = (Double,ExecutionData)Double
MethodHandle logic = MethodHandles.guardWithTest(
// data arg dropped implicitly
DOUBLE_TO_BOOL,
// drop data arg
MethodHandles.dropArguments(
MethodHandles.identity(Double.class), 1, ExecutionData.class
),
// drop left arg, call right
MethodHandles.dropArguments(
right, 0, Double.class
)
// data arg dropped implicitly
DOUBLE_TO_BOOL,
// drop data arg
MethodHandles.dropArguments(
MethodHandles.identity(Double.class), 1, ExecutionData.class
),
// drop left arg, call right
MethodHandles.dropArguments(
right, 0, Double.class
)
);
// mixed = (ExecutionData,ExecutionData)Double
MethodHandle mixed = MethodHandles.collectArguments(
logic, 0, left
logic, 0, left
);
// Deduplicate ExecutionData
return ExpressionHandles.dedupData(mixed);
@ -381,8 +366,8 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
MethodHandle mhRight = evaluateForValue(right);
// Map two data args to two double args, then evaluate op
MethodHandle doubleData = MethodHandles.filterArguments(
CALL_BINARY_OP.bindTo(op), 0,
mhLeft.asType(mhLeft.type().unwrap()), mhRight.asType(mhRight.type().unwrap())
CALL_BINARY_OP.bindTo(op), 0,
mhLeft.asType(mhLeft.type().unwrap()), mhRight.asType(mhRight.type().unwrap())
);
doubleData = doubleData.asType(doubleData.type().wrap());
return ExpressionHandles.dedupData(doubleData);
@ -474,16 +459,57 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
});
}
// 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 MethodHandle visitPostfixExpr(ExpressionParser.PostfixExprContext ctx) {
MethodHandle value = evaluateForValue(ctx.expr);
if (ctx.op.getType() == EXCLAMATION_MARK) {
return ExpressionHandles.call(data ->
factorial((double) ExpressionHandles.standardInvoke(value, data))
factorial((double) ExpressionHandles.standardInvoke(value, data))
);
}
throw ExpressionHelper.evalException(ctx,
"Invalid text for post-unary expr: " + ctx.op.getText());
"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
@ -522,7 +548,7 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
break;
default:
throw ExpressionHelper.evalException(ctx, "Invalid text for assign expr: " +
ctx.assignmentOperator().getText());
ctx.assignmentOperator().getText());
}
}
variable.setValue(value);
@ -551,7 +577,7 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
// Collapse every data into one argument
int[] permutation = new int[arguments.length];
return MethodHandles.permuteArguments(
manyData, ExpressionHandles.COMPILED_EXPRESSION_SIG, permutation
manyData, ExpressionHandles.COMPILED_EXPRESSION_SIG, permutation
);
}
@ -567,7 +593,7 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
MethodHandle filter = evaluateForValue(arg);
filter = filter.asType(filter.type().unwrap());
return MethodHandles.collectArguments(
NEW_LS_CONSTANT, 0, filter
NEW_LS_CONSTANT, 0, filter
);
}
// small hack
@ -580,7 +606,7 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
public MethodHandle visitConstantExpression(ExpressionParser.ConstantExpressionContext ctx) {
try {
return ExpressionHandles.dropData(
MethodHandles.constant(Double.class, Double.parseDouble(ctx.getText()))
MethodHandles.constant(Double.class, Double.parseDouble(ctx.getText()))
);
} catch (NumberFormatException e) {
// Rare, but might happen, e.g. if too many digits
@ -594,6 +620,12 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
return ExpressionHandles.call(data -> ExpressionHandles.getSlotValue(data, source));
}
/**
* Method handle (ExecutionData)Double, returns null.
*/
private static final MethodHandle DEFAULT_RESULT =
ExpressionHandles.dropData(MethodHandles.constant(Double.class, null));
@Override
protected MethodHandle defaultResult() {
return DEFAULT_RESULT;
@ -641,13 +673,13 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
}
// Add a dummy Double parameter to the end
MethodHandle dummyDouble = MethodHandles.dropArguments(
result, 1, Double.class
result, 1, Double.class
);
// Have oldResult turn it from data->Double
MethodHandle doubledData = MethodHandles.collectArguments(
dummyDouble, 1, oldResult
dummyDouble, 1, oldResult
);
// Deduplicate the `data` parameter
return ExpressionHandles.dedupData(doubledData);
}
}
}

View File

@ -31,4 +31,4 @@ class ExecNode {
this.ctx = ctx;
this.handle = handle;
}
}
}

View File

@ -38,7 +38,7 @@ public class ExpressionCompiler {
private static final String CE_EXECUTE = "execute";
private static final MethodType HANDLE_TO_CE =
methodType(CompiledExpression.class, MethodHandle.class);
methodType(CompiledExpression.class, MethodHandle.class);
private static final MethodHandle HANDLE_TO_CE_CONVERTER;
@ -46,17 +46,17 @@ public class ExpressionCompiler {
MethodHandle handleInvoker = MethodHandles.invoker(ExpressionHandles.COMPILED_EXPRESSION_SIG);
try {
HANDLE_TO_CE_CONVERTER = LambdaMetafactory.metafactory(
MethodHandles.lookup(),
// Implementing CompiledExpression.execute
CE_EXECUTE,
// Take a handle, to be converted to CompiledExpression
HANDLE_TO_CE,
// Raw signature for SAM type
ExpressionHandles.COMPILED_EXPRESSION_SIG,
// Handle to call the captured handle.
handleInvoker,
// Actual signature at invoke time
ExpressionHandles.COMPILED_EXPRESSION_SIG
MethodHandles.lookup(),
// Implementing CompiledExpression.execute
CE_EXECUTE,
// Take a handle, to be converted to CompiledExpression
HANDLE_TO_CE,
// Raw signature for SAM type
ExpressionHandles.COMPILED_EXPRESSION_SIG,
// Handle to call the captured handle.
handleInvoker,
// Actual signature at invoke time
ExpressionHandles.COMPILED_EXPRESSION_SIG
).dynamicInvoker().asType(HANDLE_TO_CE);
} catch (LambdaConversionException e) {
throw new IllegalStateException("Failed to load ExpressionCompiler MetaFactory", e);
@ -67,7 +67,7 @@ public class ExpressionCompiler {
SetMultimap<String, MethodHandle> functions) {
MethodHandle invokable = root.accept(new CompilingVisitor(functions));
return (CompiledExpression) ExpressionHandles.safeInvoke(
HANDLE_TO_CE_CONVERTER, h -> h.invoke(invokable)
HANDLE_TO_CE_CONVERTER, h -> h.invoke(invokable)
);
}
}
}

View File

@ -20,7 +20,12 @@
package com.sk89q.worldedit.internal.expression.invoke;
import com.google.common.base.Throwables;
import com.sk89q.worldedit.internal.expression.*;
import com.sk89q.worldedit.internal.expression.BreakException;
import com.sk89q.worldedit.internal.expression.CompiledExpression;
import com.sk89q.worldedit.internal.expression.EvaluationException;
import com.sk89q.worldedit.internal.expression.ExecutionData;
import com.sk89q.worldedit.internal.expression.ExpressionHelper;
import com.sk89q.worldedit.internal.expression.LocalSlot;
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
import it.unimi.dsi.fastutil.doubles.Double2ObjectMaps;
import org.antlr.v4.runtime.ParserRuleContext;
@ -34,18 +39,22 @@ import java.util.Objects;
import java.util.function.DoubleBinaryOperator;
import java.util.function.Supplier;
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.*;
import static java.lang.invoke.MethodHandles.*;
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.getErrorPosition;
import static java.lang.invoke.MethodHandles.collectArguments;
import static java.lang.invoke.MethodHandles.constant;
import static java.lang.invoke.MethodHandles.dropArguments;
import static java.lang.invoke.MethodHandles.insertArguments;
import static java.lang.invoke.MethodHandles.permuteArguments;
import static java.lang.invoke.MethodHandles.throwException;
import static java.lang.invoke.MethodType.methodType;
class ExpressionHandles {
static final MethodType COMPILED_EXPRESSION_SIG = methodType(Double.class, ExecutionData.class);
static final MethodHandle IS_NULL;
static final MethodHandle DOUBLE_TO_BOOL;
static final MethodHandle CALL_BINARY_OP;
static final MethodHandle NEW_LS_CONSTANT;
static final MethodHandle NULL_DOUBLE = dropData(constant(Double.class, null));
private static final MethodHandle EVAL_EXCEPTION_CONSTR;
private static final MethodHandle CALL_EXPRESSION;
private static final MethodHandle GET_VARIABLE;
@ -54,43 +63,52 @@ class ExpressionHandles {
private static final MethodHandle SIMPLE_FOR_LOOP_IMPL;
private static final MethodHandle SWITCH_IMPL;
static final MethodHandle IS_NULL;
static final MethodHandle DOUBLE_TO_BOOL;
static final MethodHandle CALL_BINARY_OP;
static final MethodHandle NEW_LS_CONSTANT;
static final MethodHandle NULL_DOUBLE = dropData(constant(Double.class, null));
static {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
EVAL_EXCEPTION_CONSTR = lookup.findConstructor(
EvaluationException.class, methodType(void.class, int.class, String.class));
EvaluationException.class, methodType(void.class, int.class, String.class));
CALL_EXPRESSION = lookup.findVirtual(
CompiledExpression.class, "execute",
methodType(Double.class, ExecutionData.class));
CompiledExpression.class, "execute",
methodType(Double.class, ExecutionData.class));
GET_VARIABLE = lookup.findStatic(ExpressionHandles.class, "getVariable",
methodType(LocalSlot.Variable.class, ExecutionData.class, Token.class));
methodType(LocalSlot.Variable.class, ExecutionData.class, Token.class));
WHILE_FOR_LOOP_IMPL = lookup.findStatic(ExpressionHandles.class,
"whileForLoopImpl",
methodType(Double.class, ExecutionData.class, MethodHandle.class,
MethodHandle.class, ExecNode.class, MethodHandle.class));
"whileForLoopImpl",
methodType(Double.class, ExecutionData.class, MethodHandle.class,
MethodHandle.class, ExecNode.class, MethodHandle.class));
DO_WHILE_LOOP_IMPL = lookup.findStatic(ExpressionHandles.class, "doWhileLoopImpl",
methodType(Double.class, ExecutionData.class, MethodHandle.class, ExecNode.class));
methodType(Double.class, ExecutionData.class, MethodHandle.class, ExecNode.class));
SIMPLE_FOR_LOOP_IMPL = lookup.findStatic(ExpressionHandles.class, "simpleForLoopImpl",
methodType(Double.class, ExecutionData.class, MethodHandle.class,
MethodHandle.class, Token.class, ExecNode.class));
methodType(Double.class, ExecutionData.class, MethodHandle.class,
MethodHandle.class, Token.class, ExecNode.class));
SWITCH_IMPL = lookup.findStatic(ExpressionHandles.class, "switchImpl",
methodType(Double.class, ExecutionData.class, Double2ObjectMap.class,
MethodHandle.class, ExecNode.class));
methodType(Double.class, ExecutionData.class, Double2ObjectMap.class,
MethodHandle.class, ExecNode.class));
IS_NULL = lookup.findStatic(Objects.class, "isNull",
methodType(boolean.class, Object.class));
methodType(boolean.class, Object.class));
DOUBLE_TO_BOOL = lookup.findStatic(ExpressionHandles.class, "doubleToBool",
methodType(boolean.class, double.class));
methodType(boolean.class, double.class));
CALL_BINARY_OP = lookup.findVirtual(DoubleBinaryOperator.class, "applyAsDouble",
methodType(double.class, double.class, double.class));
methodType(double.class, double.class, double.class));
NEW_LS_CONSTANT = lookup.findConstructor(LocalSlot.Constant.class,
methodType(void.class, double.class));
methodType(void.class, double.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
private ExpressionHandles() {
@FunctionalInterface
interface Invokable {
Object invoke(MethodHandle handle) throws Throwable;
}
static Object safeInvoke(MethodHandle handle, Invokable invokable) {
@ -116,22 +134,22 @@ class ExpressionHandles {
static MethodHandle dedupData(MethodHandle doubleData) {
return permuteArguments(
doubleData, COMPILED_EXPRESSION_SIG,
0, 0
doubleData, COMPILED_EXPRESSION_SIG,
0, 0
);
}
static LocalSlot.Variable initVariable(ExecutionData data, Token nameToken) {
String name = nameToken.getText();
return data.getSlots().initVariable(name)
.orElseThrow(() -> ExpressionHelper.evalException(
nameToken, "Cannot overwrite non-variable '" + name + "'"
));
.orElseThrow(() -> ExpressionHelper.evalException(
nameToken, "Cannot overwrite non-variable '" + name + "'"
));
}
private static Supplier<EvaluationException> varNotInitException(Token nameToken) {
return () -> ExpressionHelper.evalException(
nameToken, "'" + nameToken.getText() + "' is not initialized yet"
nameToken, "'" + nameToken.getText() + "' is not initialized yet"
);
}
@ -142,10 +160,10 @@ class ExpressionHandles {
static LocalSlot.Variable getVariable(ExecutionData data, Token nameToken) {
String name = nameToken.getText();
LocalSlot slot = data.getSlots().getSlot(name)
.orElseThrow(varNotInitException(nameToken));
.orElseThrow(varNotInitException(nameToken));
if (!(slot instanceof LocalSlot.Variable)) {
throw ExpressionHelper.evalException(
nameToken, "'" + name + "' is not a variable"
nameToken, "'" + name + "' is not a variable"
);
}
return (LocalSlot.Variable) slot;
@ -154,7 +172,7 @@ class ExpressionHandles {
static double getSlotValue(ExecutionData data, Token nameToken) {
String name = nameToken.getText();
return data.getSlots().getSlotValue(name)
.orElseThrow(varNotInitException(nameToken));
.orElseThrow(varNotInitException(nameToken));
}
/**
@ -163,7 +181,7 @@ class ExpressionHandles {
*/
private static MethodHandle evalException(ParserRuleContext ctx, String message) {
return insertArguments(EVAL_EXCEPTION_CONSTR, 0,
getErrorPosition(ctx.start), message);
getErrorPosition(ctx.start), message);
}
/**
@ -173,9 +191,9 @@ class ExpressionHandles {
static MethodHandle throwEvalException(ParserRuleContext ctx, String message) {
// replace arg0 of `throw` with `evalException`
return collectArguments(
throwException(Double.class, EvaluationException.class),
0,
evalException(ctx, message)
throwException(Double.class, EvaluationException.class),
0,
evalException(ctx, message)
);
}
@ -196,7 +214,7 @@ class ExpressionHandles {
static MethodHandle whileLoop(MethodHandle condition, ExecNode body) {
return insertArguments(WHILE_FOR_LOOP_IMPL, 1,
null, condition, body, null);
null, condition, body, null);
}
static MethodHandle forLoop(MethodHandle init,
@ -204,7 +222,7 @@ class ExpressionHandles {
ExecNode body,
MethodHandle update) {
return insertArguments(WHILE_FOR_LOOP_IMPL, 1,
init, condition, body, update);
init, condition, body, update);
}
private static Double whileForLoopImpl(ExecutionData data,
@ -264,7 +282,7 @@ class ExpressionHandles {
Token counter,
ExecNode body) {
return insertArguments(SIMPLE_FOR_LOOP_IMPL, 1,
first, last, counter, body);
first, last, counter, body);
}
private static Double simpleForLoopImpl(ExecutionData data,
@ -331,9 +349,7 @@ class ExpressionHandles {
return evaluated;
}
@FunctionalInterface
interface Invokable {
Object invoke(MethodHandle handle) throws Throwable;
private ExpressionHandles() {
}
}
}

View File

@ -19,10 +19,10 @@
package com.sk89q.worldedit.internal.util;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkArgument;
/**
* An explicit substring. Provides the range from which it was taken.
*/

View File

@ -49,4 +49,4 @@ public final class BitMath {
private BitMath() {
}
}
}

View File

@ -35,7 +35,7 @@ public final class Polygons {
}
/**
* Calculates the polygon shape of a cylinder, which can then be used for e.g., intersection detection.
* Calculates the polygon shape of a cylinder which can then be used for e.g. intersection detection.
*
* @param center the center point of the cylinder
* @param radius the radius of the cylinder

View File

@ -341,11 +341,6 @@ public class AffineTransform implements Transform, Serializable {
}
}
@Override
public String toString() {
return String.format("Affine[%g %g %g %g, %g %g %g %g, %g %g %g %g]}", m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23);
}
/**
* Returns if this affine transform is representing a horizontal flip.
*/
@ -354,5 +349,10 @@ public class AffineTransform implements Transform, Serializable {
return m00 * m22 - m02 * m20 < 0;
}
@Override
public String toString() {
return String.format("Affine[%g %g %g %g, %g %g %g %g, %g %g %g %g]}", m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23);
}
}

View File

@ -19,7 +19,6 @@
package com.sk89q.worldedit.regions;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.object.collection.BlockVectorSet;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;

View File

@ -21,7 +21,6 @@ package com.sk89q.worldedit.regions.selector;
import static com.google.common.base.Preconditions.checkNotNull;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.extension.platform.Actor;
@ -37,6 +36,7 @@ import com.sk89q.worldedit.regions.polyhedron.Triangle;
import com.sk89q.worldedit.regions.selector.limit.SelectorLimits;
import com.sk89q.worldedit.util.formatting.text.Component;
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.ArrayList;

View File

@ -37,11 +37,12 @@ import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.world.World;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
/**
* Creates a {@code CuboidRegion} from a user's selections.
*/
@ -156,7 +157,6 @@ public class CuboidRegionSelector implements RegionSelector, CUIRegion {
checkNotNull(session);
checkNotNull(pos);
//TODO Re-add better translation
if (position1 != null && position2 != null) {
player.printInfo(TranslatableComponent.of(
"worldedit.selection.cuboid.explain.primary-area",
@ -176,7 +176,6 @@ public class CuboidRegionSelector implements RegionSelector, CUIRegion {
checkNotNull(session);
checkNotNull(pos);
//TODO Re-add better translation
if (position1 != null && position2 != null) {
player.printInfo(TranslatableComponent.of(
"worldedit.selection.cuboid.explain.secondary-area",

View File

@ -19,8 +19,6 @@
package com.sk89q.worldedit.regions.selector;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import static com.google.common.base.Preconditions.checkNotNull;
import com.sk89q.worldedit.IncompleteRegionException;
@ -37,12 +35,14 @@ import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.limit.SelectorLimits;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.world.World;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
/**
* Creates a {@code EllipsoidRegionSelector} from a user's selections.
*/

View File

@ -19,13 +19,13 @@
package com.sk89q.worldedit.regions.selector;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.limit.SelectorLimits;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.world.World;
import javax.annotation.Nullable;

View File

@ -21,7 +21,6 @@ package com.sk89q.worldedit.regions.selector;
import static com.google.common.base.Preconditions.checkNotNull;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.extension.platform.Actor;
@ -37,6 +36,7 @@ import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.limit.SelectorLimits;
import com.sk89q.worldedit.util.formatting.text.Component;
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.Collections;

View File

@ -21,8 +21,8 @@ package com.sk89q.worldedit.regions.shape;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.internal.expression.ExpressionEnvironment;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.MutableVector3;
import com.sk89q.worldedit.math.Vector3;

View File

@ -111,7 +111,7 @@ public class CraftScriptContext extends CraftScriptEnvironment {
/**
* Print a regular message to the user.
*
*
* @param message a message
*/
public void print(String message) {
@ -120,7 +120,7 @@ public class CraftScriptContext extends CraftScriptEnvironment {
/**
* Print an error message to the user.
*
*
* @param message a message
*/
public void error(String message) {
@ -129,7 +129,7 @@ public class CraftScriptContext extends CraftScriptEnvironment {
/**
* Print a raw message to the user.
*
*
* @param message a message
*/
public void printRaw(String message) {

View File

@ -34,16 +34,20 @@ import com.sk89q.worldedit.command.tool.SelectionWand;
import com.sk89q.worldedit.command.tool.Tool;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent;
import com.sk89q.worldedit.extension.platform.Locatable;
import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.session.storage.JsonFileSessionStore;
import com.sk89q.worldedit.session.storage.SessionStore;
import com.sk89q.worldedit.session.storage.VoidStore;
import com.sk89q.worldedit.util.concurrency.EvenMoreExecutors;
import com.sk89q.worldedit.extension.platform.Locatable;
import com.sk89q.worldedit.util.eventbus.Subscribe;
import com.sk89q.worldedit.world.gamemode.GameModes;
import com.sk89q.worldedit.world.item.ItemType;
import com.sk89q.worldedit.world.item.ItemTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
@ -53,9 +57,6 @@ import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Session manager for WorldEdit.

View File

@ -34,8 +34,9 @@ import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import java.util.List;
import javax.annotation.Nullable;
import java.util.List;
public class RequestExtent implements Extent {

View File

@ -127,9 +127,8 @@ public class PropertiesConfiguration extends LocalConfiguration {
LocalSession.MAX_HISTORY_SIZE = Math.max(15, getInt("history-size", 15));
String snapshotsDir = getString("snapshots-dir", "");
if (!snapshotsDir.isEmpty()) {
snapshotRepo = new SnapshotRepository(snapshotsDir);
}
boolean experimentalSnapshots = getBool("snapshots-experimental", false);
initializeSnapshotConfiguration(snapshotsDir, experimentalSnapshots);
path.getParentFile().mkdirs();
try (OutputStream output = new FileOutputStream(path)) {

View File

@ -19,8 +19,6 @@
package com.sk89q.worldedit.util.collection;
import com.google.common.collect.Iterators;
import com.sk89q.worldedit.WorldEdit;
import com.google.common.collect.Iterators;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.math.BlockVector3;

View File

@ -0,0 +1,83 @@
/*
* 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.util.collection;
import com.google.common.collect.AbstractIterator;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Objects.requireNonNull;
/**
* Additionally stream facilities.
*/
public class MoreStreams {
/**
* Emit elements from {@code stream} until {@code predicate} returns {@code false}.
*/
public static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<T> predicate) {
return takeUntil(stream, predicate.negate());
}
/**
* Emit elements from {@code stream} until {@code predicate} returns {@code true}.
*/
public static <T> Stream<T> takeUntil(Stream<T> stream, Predicate<T> predicate) {
Spliterator<T> spliterator = stream.spliterator();
Iterator<T> iter = new AbstractIterator<T>() {
private Iterator<T> source = Spliterators.iterator(spliterator);
@Override
protected T computeNext() {
Iterator<T> src = requireNonNull(source);
if (!src.hasNext()) {
return done();
}
T next = src.next();
if (predicate.test(next)) {
return done();
}
return next;
}
private T done() {
// allow GC of source
source = null;
return endOfData();
}
};
int chars = spliterator.characteristics();
// Not SIZED, Not SUBSIZED
chars &= ~(Spliterator.SIZED | Spliterator.SUBSIZED);
return StreamSupport.stream(Spliterators.spliterator(
iter, spliterator.estimateSize(), chars
), stream.isParallel()).onClose(stream::close);
}
private MoreStreams() {
}
}

View File

@ -71,4 +71,5 @@ public class LazyReference<T> {
refInfo.lock.unlock();
}
}
}

View File

@ -20,8 +20,6 @@
package com.sk89q.worldedit.util.eventbus;
import com.google.common.collect.HashMultimap;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import org.slf4j.Logger;
@ -37,6 +35,8 @@ import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Dispatches events to listeners, and provides ways for listeners to register
* themselves.

View File

@ -47,6 +47,7 @@ public class TextUtils {
}
return builder.build();
}
/**
* Gets a Java Locale object by the Minecraft locale tag.
*

View File

@ -0,0 +1,32 @@
/*
* 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.util.function;
import java.io.IOException;
/**
* I/O runnable type.
*/
@FunctionalInterface
public interface IORunnable {
void run() throws IOException;
}

View File

@ -51,4 +51,4 @@ public class ResourceLoader {
}
return url;
}
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.util.io.file;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.Optional;
/**
* Something that can provide access to an archive file as a file system.
*/
public interface ArchiveNioSupport {
/**
* Try to open the given archive as a file system.
*
* @param archive the archive to open
* @return the path for the root of the archive, if available
*/
Optional<Path> tryOpenAsDir(Path archive) throws IOException;
}

View File

@ -0,0 +1,99 @@
/*
* 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.util.io.file;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.Stream;
public class ArchiveNioSupports {
private static final List<ArchiveNioSupport> SUPPORTS;
static {
ImmutableList.Builder<ArchiveNioSupport> builder = ImmutableList.builder();
try {
builder.add(TrueVfsArchiveNioSupport.getInstance());
} catch (NoClassDefFoundError ignore) {
// No TrueVFS available. That's OK.
}
SUPPORTS = builder.add(ZipArchiveNioSupport.getInstance())
.addAll(ServiceLoader.load(ArchiveNioSupport.class))
.build();
}
public static Optional<Path> tryOpenAsDir(Path archive) throws IOException {
for (ArchiveNioSupport support : SUPPORTS) {
Optional<Path> fs = support.tryOpenAsDir(archive);
if (fs.isPresent()) {
return fs;
}
}
return Optional.empty();
}
private static final ArchiveNioSupport COMBINED = ArchiveNioSupports::tryOpenAsDir;
/**
* Get an {@link ArchiveNioSupport} that combines all known instances.
* @return a combined {@link ArchiveNioSupport} instance
*/
public static ArchiveNioSupport combined() {
return COMBINED;
}
/**
* If root contains a folder with the same name as {@code name}, and no regular files,
* returns the path to that folder. Otherwise, return the root path.
*
* <p>
* This method is used to provide equal outputs for archives that do and do not contain
* their name as part of their root folder.
* </p>
*
* @param root the root path
* @param name the name that might exist inside root
* @return the corrected path
*/
public static Path skipRootSameName(Path root, String name) throws IOException {
Path innerDir = root.resolve(name);
if (Files.isDirectory(innerDir)) {
try (Stream<Path> files = Files.list(root)) {
// The reason we check this, is that macOS creates a __MACOSX directory inside
// its zip files. We want to allow this to pass if that exists, or a similar
// mechanism, but fail if there are regular files, since that indicates that
// it may not be the right thing to do.
if (files.allMatch(Files::isDirectory)) {
return innerDir;
}
}
}
return root;
}
private ArchiveNioSupports() {
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.util.io.file;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.Spliterator;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class MorePaths {
/**
* Starting with the first path element, add elements until reaching this path.
*/
public static Stream<Path> iterPaths(Path path) {
Deque<Path> parents = new ArrayDeque<>(path.getNameCount());
// Push parents to the front of the stack, so the "root" is at the front
Path next = path;
while (next != null) {
parents.addFirst(next);
next = next.getParent();
}
// now just iterate straight over them
return ImmutableList.copyOf(parents).stream();
}
/**
* Create an efficiently-splittable spliterator for the given path elements.
*
* <p>
* Since paths are so small, this is only useful for preventing heavy computations
* on later parts of the stream from occurring when using
* {@link Streams#findLast(IntStream)}, and not for parallelism.
* </p>
*
* @param path the path to create a spliterator for
* @return the spliterator
*/
public static Spliterator<Path> optimizedSpliterator(Path path) {
return Arrays.spliterator(Streams.stream(path).toArray(Path[]::new));
}
private MorePaths() {
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.util.io.file;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import net.java.truevfs.access.TArchiveDetector;
import net.java.truevfs.access.TPath;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;
public final class TrueVfsArchiveNioSupport implements ArchiveNioSupport {
private static final TrueVfsArchiveNioSupport INSTANCE = new TrueVfsArchiveNioSupport();
public static TrueVfsArchiveNioSupport getInstance() {
return INSTANCE;
}
private static final Set<String> ALLOWED_EXTENSIONS = ImmutableSet.copyOf(
Splitter.on('|').split(TArchiveDetector.ALL.getExtensions())
);
private TrueVfsArchiveNioSupport() {
}
@Override
public Optional<Path> tryOpenAsDir(Path archive) throws IOException {
String fileName = archive.getFileName().toString();
int dot = fileName.indexOf('.');
if (dot < 0 || dot >= fileName.length() || !ALLOWED_EXTENSIONS.contains(fileName.substring(dot + 1))) {
return Optional.empty();
}
TPath root = new TPath(archive).getFileSystem().getPath("/");
return Optional.of(ArchiveNioSupports.skipRootSameName(
root, fileName.substring(0, dot)
));
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.util.io.file;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Optional;
public final class ZipArchiveNioSupport implements ArchiveNioSupport {
private static final ZipArchiveNioSupport INSTANCE = new ZipArchiveNioSupport();
public static ZipArchiveNioSupport getInstance() {
return INSTANCE;
}
private ZipArchiveNioSupport() {
}
@Override
public Optional<Path> tryOpenAsDir(Path archive) throws IOException {
if (!archive.getFileName().toString().endsWith(".zip")) {
return Optional.empty();
}
FileSystem zipFs = FileSystems.newFileSystem(
archive, getClass().getClassLoader()
);
return Optional.of(ArchiveNioSupports.skipRootSameName(
zipFs.getPath("/"), archive.getFileName().toString()
.replaceFirst("\\.zip$", "")
));
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.util.time;
import com.google.common.collect.Streams;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.util.io.file.MorePaths;
import javax.annotation.Nullable;
import java.nio.file.Path;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/**
* Parses date-times by looking at the file name. File names without a time
* will use 00:00:00.
*
* <p>
* Elements may be separated by a space, dash, or colon.
* The date and time may additionally be separated by a 'T'.
* Only the year must have all digits, others may omit padding
* zeroes.
* </p>
*
* <p>
* Valid file name examples:
* <ul>
* <li>{@code 2019-06-15}</li>
* <li>{@code 2019-06-15 10:20:30}</li>
* <li>{@code 2019-06-15 10:20:30}</li>
* <li>{@code 2019-06-15T10:20:30}</li>
* <li>{@code 2019 06 15 10 20 30}</li>
* <li>{@code 2019-06-15-10-20-30}</li>
* <li>{@code 2019-6-1-1-2-3}</li>
* </ul>
* </p>
*/
public class FileNameDateTimeParser implements SnapshotDateTimeParser {
private static final FileNameDateTimeParser INSTANCE = new FileNameDateTimeParser();
public static FileNameDateTimeParser getInstance() {
return INSTANCE;
}
private static final String SEP = "[ \\-_:]";
private static final Pattern BASIC_FILTER = Pattern.compile(
"^(?<year>\\d{4})" + SEP + "(?<month>\\d{1,2})" + SEP + "(?<day>\\d{1,2})" +
// Optionally:
"(?:" + "[ \\-_:T]" +
"(?<hour>\\d{1,2})" + SEP + "(?<minute>\\d{1,2})" + SEP + "(?<second>\\d{1,2})" +
")?"
);
private FileNameDateTimeParser() {
}
@Nullable
@Override
public ZonedDateTime detectDateTime(Path path) {
// Make this perform a little better:
Matcher matcher = Streams.findLast(
StreamSupport.stream(MorePaths.optimizedSpliterator(path), false)
.map(p -> BASIC_FILTER.matcher(p.toString()))
.filter(Matcher::find)
).orElse(null);
if (matcher != null) {
int year = matchAndParseOrZero(matcher, "year");
int month = matchAndParseOrZero(matcher, "month");
int day = matchAndParseOrZero(matcher, "day");
int hour = matchAndParseOrZero(matcher, "hour");
int minute = matchAndParseOrZero(matcher, "minute");
int second = matchAndParseOrZero(matcher, "second");
return ZonedDateTime.of(year, month, day, hour, minute, second,
0, ZoneId.systemDefault());
}
return null;
}
private static int matchAndParseOrZero(Matcher matcher, String group) {
String match = matcher.group(group);
if (match == null) {
return 0;
}
return Integer.parseInt(match);
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.util.time;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class ModificationDateTimeParser implements SnapshotDateTimeParser {
private static final ModificationDateTimeParser INSTANCE = new ModificationDateTimeParser();
public static ModificationDateTimeParser getInstance() {
return INSTANCE;
}
private ModificationDateTimeParser() {
}
@Override
public ZonedDateTime detectDateTime(Path path) {
if (!Files.exists(path)) {
return null;
}
try {
return Files.getLastModifiedTime(path).toInstant().atZone(ZoneId.systemDefault());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.util.time;
import javax.annotation.Nullable;
import java.nio.file.Path;
import java.time.ZonedDateTime;
/**
* Instances of this interface try to determine an {@link ZonedDateTime} from a given
* {@link Path}.
*/
public interface SnapshotDateTimeParser {
/**
* Attempt to detect an ZonedDateTime from a path.
*
* <p>
* The path is not guaranteed to exist.
* </p>
*
* @param path the path
* @return date-time, if it can be parsed
*/
@Nullable
ZonedDateTime detectDateTime(Path path);
}

View File

@ -19,8 +19,6 @@
package com.sk89q.worldedit.util.translation;
import static java.util.stream.Collectors.toMap;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@ -29,6 +27,7 @@ import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.renderer.FriendlyComponentRenderer;
import com.sk89q.worldedit.util.io.ResourceLoader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@ -43,6 +42,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static java.util.stream.Collectors.toMap;
/**
* Handles translations for the plugin.
*

View File

@ -153,6 +153,7 @@ public abstract class AbstractWorld implements World {
@Override
public void setWeather(WeatherType weatherType, long duration) {
}
private class QueuedEffect implements Comparable<QueuedEffect> {
private final Vector3 position;
private final BlockType blockType;

View File

@ -69,6 +69,7 @@ public class NullWorld extends AbstractWorld {
public String getId() {
return "null";
}
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block, boolean notifyAndLight) throws WorldEditException {
return false;

View File

@ -23,11 +23,12 @@ import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockType;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Map;
import java.util.OptionalInt;
import javax.annotation.Nullable;
/**
* A block registry that uses {@link BundledBlockData} to serve information
* about blocks.

View File

@ -33,4 +33,4 @@ public interface ItemMaterial {
* @return the maximum damage, or 0 if not applicable
*/
int getMaxDamage();
}
}

View File

@ -56,15 +56,7 @@ import java.util.Map;
public final class LegacyMapper {
private static final Logger log = LoggerFactory.getLogger(LegacyMapper.class);
private static LegacyMapper INSTANCE = new LegacyMapper();
static {
try {
INSTANCE.loadFromResource();
} catch (Throwable e) {
log.warn("Failed to load the built-in legacy id registry", e);
}
}
private static LegacyMapper INSTANCE;
private final Int2ObjectArrayMap<Integer> blockStateToLegacyId4Data = new Int2ObjectArrayMap<>();
private final Int2ObjectArrayMap<Integer> extraId4DataToStateId = new Int2ObjectArrayMap<>();
@ -80,6 +72,12 @@ public final class LegacyMapper {
* Create a new instance.
*/
private LegacyMapper() {
try {
loadFromResource();
} catch (Throwable e) {
log.warn("Failed to load the built-in legacy id registry", e);
}
}
/**
@ -95,9 +93,8 @@ public final class LegacyMapper {
if (url == null) {
throw new IOException("Could not find legacy.json");
}
String source = Resources.toString(url, Charset.defaultCharset());
LegacyDataFile dataFile = gson.fromJson(source, new TypeToken<LegacyDataFile>() {
}.getType());
String data = Resources.toString(url, Charset.defaultCharset());
LegacyDataFile dataFile = gson.fromJson(data, new TypeToken<LegacyDataFile>() {}.getType());
DataFixer fixer = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataFixer();
ParserContext parserContext = new ParserContext();
@ -110,49 +107,51 @@ public final class LegacyMapper {
Integer combinedId = getCombinedId(blockEntry.getKey());
final String value = blockEntry.getValue();
blockEntries.put(id, value);
BlockState blockState = null;
BlockState state = null;
try {
blockState = BlockState.get(null, blockEntry.getValue());
BlockType type = blockState.getBlockType();
state = BlockState.get(null, blockEntry.getValue());
BlockType type = state.getBlockType();
if (type.hasProperty(PropertyKey.WATERLOGGED)) {
blockState = blockState.with(PropertyKey.WATERLOGGED, false);
state = state.with(PropertyKey.WATERLOGGED, false);
}
} catch (InputParseException e) {
} catch (InputParseException f) {
BlockFactory blockFactory = WorldEdit.getInstance().getBlockFactory();
// if fixer is available, try using that first, as some old blocks that were renamed share names with new blocks
if (fixer != null) {
try {
String newEntry = fixer.fixUp(DataFixer.FixTypes.BLOCK_STATE, value, 1631);
blockState = blockFactory.parseFromInput(newEntry, parserContext).toImmutableState();
} catch (InputParseException f) {
state = blockFactory.parseFromInput(newEntry, parserContext).toImmutableState();
} catch (InputParseException e) {
}
}
// if it's still null, the fixer was unavailable or failed
if (blockState == null) {
if (state == null) {
try {
blockState = blockFactory.parseFromInput(value, parserContext).toImmutableState();
} catch (InputParseException f) {
state = blockFactory.parseFromInput(value, parserContext).toImmutableState();
} catch (InputParseException e) {
}
}
// if it's still null, both fixer and default failed
if (blockState == null) {
if (state == null) {
log.debug("Unknown block: " + value);
} else {
// it's not null so one of them succeeded, now use it
blockToStringMap.put(blockState, id);
stringToBlockMap.put(id, blockState);
blockToStringMap.put(state, id);
stringToBlockMap.put(id, state);
}
}
if (blockState != null) {
blockArr[combinedId] = blockState.getInternalId();
blockStateToLegacyId4Data.put(blockState.getInternalId(), (Integer) combinedId);
blockStateToLegacyId4Data.putIfAbsent(blockState.getInternalBlockTypeId(), combinedId);
if (state != null) {
blockArr[combinedId] = state.getInternalId();
blockStateToLegacyId4Data.put(state.getInternalId(), (Integer) combinedId);
blockStateToLegacyId4Data.putIfAbsent(state.getInternalBlockTypeId(), combinedId);
}
}
for (int id = 0; id < 256; id++) {
int combinedId = id << 4;
int base = blockArr[combinedId];
if (base != 0) {
for (int data = 0; data < 16; data++, combinedId++) {
for (int data_ = 0; data_ < 16; data_++, combinedId++) {
if (blockArr[combinedId] == 0) blockArr[combinedId] = base;
}
}
@ -166,14 +165,14 @@ public final class LegacyMapper {
value = fixer.fixUp(DataFixer.FixTypes.ITEM_TYPE, value, 1631);
type = ItemTypes.get(value);
}
if (type != null) {
if (type == null) {
log.debug("Unknown item: " + value);
} else {
try {
itemMap.put(getCombinedId(id), type);
continue;
} catch (Exception e) {
} catch (Exception ignored) {
}
}
log.debug("Unknown item: " + value);
}
}
@ -289,7 +288,10 @@ public final class LegacyMapper {
return combinedId == null ? null : new int[] { combinedId >> 4, combinedId & 0xF };
}
public final static LegacyMapper getInstance() {
public static LegacyMapper getInstance() {
if (INSTANCE == null) {
INSTANCE = new LegacyMapper();
}
return INSTANCE;
}

View File

@ -42,4 +42,4 @@ public class PassthroughItemMaterial implements ItemMaterial {
public int getMaxDamage() {
return itemMaterial.getMaxDamage();
}
}
}

View File

@ -19,7 +19,7 @@
package com.sk89q.worldedit.world.registry;
class SimpleItemMaterial implements ItemMaterial {
public class SimpleItemMaterial implements ItemMaterial {
private int maxStackSize;
private int maxDamage;
@ -38,4 +38,4 @@ class SimpleItemMaterial implements ItemMaterial {
public int getMaxDamage() {
return maxDamage;
}
}
}

View File

@ -22,6 +22,7 @@
package com.sk89q.worldedit.world.snapshot;
import com.sk89q.worldedit.world.storage.MissingWorldException;
import javax.annotation.Nullable;
import java.io.File;
import java.io.FilenameFilter;

View File

@ -0,0 +1,62 @@
/*
* 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.world.snapshot.experimental;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.chunk.Chunk;
import com.sk89q.worldedit.world.storage.ChunkStoreHelper;
import java.io.Closeable;
import java.io.IOException;
/**
* Represents a world snapshot.
*/
public interface Snapshot extends Closeable {
SnapshotInfo getInfo();
/**
* Get the chunk information for the given position. Implementations may ignore the Y-chunk
* if its chunks are only stored in 2D.
*
* @param position the position of the chunk
* @return the tag containing chunk data
*/
CompoundTag getChunkTag(BlockVector3 position) throws DataException, IOException;
/**
* Get the chunk information for the given position.
*
* @see #getChunkTag(BlockVector3)
* @see ChunkStoreHelper#getChunk(CompoundTag)
*/
default Chunk getChunk(BlockVector3 position) throws DataException, IOException {
return ChunkStoreHelper.getChunk(getChunkTag(position));
}
/**
* Close this snapshot. This releases the IO handles used to load chunk information.
*/
@Override
void close() throws IOException;
}

View File

@ -0,0 +1,35 @@
/*
* 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.world.snapshot.experimental;
import java.util.Comparator;
public class SnapshotComparator {
private static final Comparator<Snapshot> COMPARATOR =
Comparator.comparing(Snapshot::getInfo);
public static Comparator<Snapshot> getInstance() {
return COMPARATOR;
}
private SnapshotComparator() {
}
}

View File

@ -0,0 +1,81 @@
/*
* 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.world.snapshot.experimental;
import java.io.IOException;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.stream.Stream;
import static com.sk89q.worldedit.util.collection.MoreStreams.takeWhile;
/**
* Handler for querying snapshot storage.
*/
public interface SnapshotDatabase {
/**
* Get the URI scheme handled by this database.
*/
String getScheme();
/**
* Get a snapshot by name.
*
* @param name the name of the snapshot
* @return the snapshot if available
*/
Optional<Snapshot> getSnapshot(URI name) throws IOException;
/**
* Get all snapshots by world, unsorted. The stream should be
* {@linkplain Stream#close() closed}, as it may allocate filesystem or network resources.
*
* @param worldName the name of the world
* @return a stream of all snapshots for the given world in this database
*/
Stream<Snapshot> getSnapshots(String worldName) throws IOException;
default Stream<Snapshot> getSnapshotsNewestFirst(String worldName) throws IOException {
return getSnapshots(worldName).sorted(SnapshotComparator.getInstance().reversed());
}
default Stream<Snapshot> getSnapshotsOldestFirst(String worldName) throws IOException {
return getSnapshots(worldName).sorted(SnapshotComparator.getInstance());
}
default Stream<Snapshot> getSnapshotsBefore(String worldName, ZonedDateTime date) throws IOException {
return takeWhile(
// sorted from oldest -> newest, so all `before` are at the front
getSnapshotsOldestFirst(worldName),
snap -> snap.getInfo().getDateTime().isBefore(date)
);
}
default Stream<Snapshot> getSnapshotsAfter(String worldName, ZonedDateTime date) throws IOException {
return takeWhile(
// sorted from newest -> oldest, so all `after` are at the front
getSnapshotsNewestFirst(worldName),
snap -> snap.getInfo().getDateTime().isAfter(date)
);
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.world.snapshot.experimental;
import com.google.common.collect.ComparisonChain;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Objects;
/**
* Information about a snapshot, such as name and date.
*/
public final class SnapshotInfo implements Comparable<SnapshotInfo> {
public static SnapshotInfo create(URI name, ZonedDateTime dateTime) {
return new SnapshotInfo(name, dateTime);
}
private final URI name;
private final ZonedDateTime dateTime;
private SnapshotInfo(URI name, ZonedDateTime dateTime) {
this.name = name;
this.dateTime = dateTime;
}
public URI getName() {
return name;
}
public String getDisplayName() {
if (name.getScheme().equals("snapfs")) {
// Stored raw as the scheme specific part
return name.getSchemeSpecificPart();
}
return name.toString();
}
public ZonedDateTime getDateTime() {
return dateTime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SnapshotInfo that = (SnapshotInfo) o;
return Objects.equals(name, that.name) &&
Objects.equals(dateTime, that.dateTime);
}
@Override
public int hashCode() {
return Objects.hash(name, dateTime);
}
@Override
public String toString() {
return "SnapshotInfo{" +
"name='" + name + '\'' +
",date=" + dateTime +
'}';
}
@Override
public int compareTo(SnapshotInfo o) {
return ComparisonChain.start()
.compare(dateTime, o.dateTime)
.compare(name, o.name)
.result();
}
}

View File

@ -0,0 +1,202 @@
/*
* 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.world.snapshot.experimental;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.chunk.Chunk;
import com.sk89q.worldedit.world.storage.ChunkStore;
import com.sk89q.worldedit.world.storage.MissingChunkException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A snapshot restore operation.
*/
public class SnapshotRestore {
private final Map<BlockVector2, ArrayList<BlockVector3>> neededChunks = new LinkedHashMap<>();
private final Snapshot snapshot;
private final EditSession editSession;
private ArrayList<BlockVector2> missingChunks;
private ArrayList<BlockVector2> errorChunks;
private String lastErrorMessage;
/**
* Construct the snapshot restore operation.
*
* @param snapshot The {@link Snapshot} to restore from
* @param editSession The {@link EditSession} to restore to
* @param region The {@link Region} to restore to
*/
public SnapshotRestore(Snapshot snapshot, EditSession editSession, Region region) {
this.snapshot = snapshot;
this.editSession = editSession;
if (region instanceof CuboidRegion) {
findNeededCuboidChunks(region);
} else {
findNeededChunks(region);
}
}
/**
* Find needed chunks in the axis-aligned bounding box of the region.
*
* @param region The {@link Region} to iterate
*/
private void findNeededCuboidChunks(Region region) {
BlockVector3 min = region.getMinimumPoint();
BlockVector3 max = region.getMaximumPoint();
// First, we need to group points by chunk so that we only need
// to keep one chunk in memory at any given moment
for (int x = min.getBlockX(); x <= max.getBlockX(); ++x) {
for (int y = min.getBlockY(); y <= max.getBlockY(); ++y) {
for (int z = min.getBlockZ(); z <= max.getBlockZ(); ++z) {
BlockVector3 pos = BlockVector3.at(x, y, z);
checkAndAddBlock(pos);
}
}
}
}
/**
* Find needed chunks in the region.
*
* @param region The {@link Region} to iterate
*/
private void findNeededChunks(Region region) {
// First, we need to group points by chunk so that we only need
// to keep one chunk in memory at any given moment
for (BlockVector3 pos : region) {
checkAndAddBlock(pos);
}
}
private void checkAndAddBlock(BlockVector3 pos) {
if (editSession.getMask() != null && !editSession.getMask().test(pos))
return;
BlockVector2 chunkPos = ChunkStore.toChunk(pos);
// Unidentified chunk
if (!neededChunks.containsKey(chunkPos)) {
neededChunks.put(chunkPos, new ArrayList<>());
}
neededChunks.get(chunkPos).add(pos);
}
/**
* Get the number of chunks that are needed.
*
* @return a number of chunks
*/
public int getChunksAffected() {
return neededChunks.size();
}
/**
* Restores to world.
*
* @throws MaxChangedBlocksException
*/
public void restore() throws MaxChangedBlocksException {
missingChunks = new ArrayList<>();
errorChunks = new ArrayList<>();
// Now let's start restoring!
for (Map.Entry<BlockVector2, ArrayList<BlockVector3>> entry : neededChunks.entrySet()) {
BlockVector2 chunkPos = entry.getKey();
Chunk chunk;
try {
// This will need to be changed if we start officially supporting 3d snapshots.
chunk = snapshot.getChunk(chunkPos.toBlockVector3());
// Good, the chunk could be at least loaded
// Now just copy blocks!
for (BlockVector3 pos : entry.getValue()) {
try {
editSession.setBlock(pos, chunk.getBlock(pos));
} catch (DataException e) {
// this is a workaround: just ignore for now
}
}
} catch (MissingChunkException me) {
missingChunks.add(chunkPos);
} catch (IOException | DataException me) {
errorChunks.add(chunkPos);
lastErrorMessage = me.getMessage();
}
}
}
/**
* Get a list of the missing chunks. restore() must have been called
* already.
*
* @return a list of coordinates
*/
public List<BlockVector2> getMissingChunks() {
return missingChunks;
}
/**
* Get a list of the chunks that could not have been loaded for other
* reasons. restore() must have been called already.
*
* @return a list of coordinates
*/
public List<BlockVector2> getErrorChunks() {
return errorChunks;
}
/**
* Checks to see where the backup succeeded in any capacity. False will
* be returned if no chunk could be successfully loaded.
*
* @return true if there was total failure
*/
public boolean hadTotalFailure() {
return missingChunks.size() + errorChunks.size() == getChunksAffected();
}
/**
* Get the last error message.
*
* @return a message
*/
public String getLastErrorMessage() {
return lastErrorMessage;
}
}

View File

@ -0,0 +1,300 @@
/*
* 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.world.snapshot.experimental.fs;
import com.google.common.collect.ImmutableList;
import com.google.common.net.UrlEscapers;
import com.sk89q.worldedit.util.function.IORunnable;
import com.sk89q.worldedit.util.io.Closer;
import com.sk89q.worldedit.util.io.file.ArchiveNioSupport;
import com.sk89q.worldedit.util.io.file.MorePaths;
import com.sk89q.worldedit.util.time.FileNameDateTimeParser;
import com.sk89q.worldedit.util.time.ModificationDateTimeParser;
import com.sk89q.worldedit.util.time.SnapshotDateTimeParser;
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotDatabase;
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Function;
import java.util.stream.Stream;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Implements a snapshot database based on a filesystem.
*/
public class FileSystemSnapshotDatabase implements SnapshotDatabase {
private static final Logger logger = LoggerFactory.getLogger(FileSystemSnapshotDatabase.class);
private static final String SCHEME = "snapfs";
private static final List<SnapshotDateTimeParser> DATE_TIME_PARSERS =
new ImmutableList.Builder<SnapshotDateTimeParser>()
.add(FileNameDateTimeParser.getInstance())
.addAll(ServiceLoader.load(SnapshotDateTimeParser.class))
.add(ModificationDateTimeParser.getInstance())
.build();
public static ZonedDateTime tryParseDate(Path path) {
return tryParseDateInternal(path)
.orElseThrow(() -> new IllegalStateException("Could not detect date of " + path));
}
private static Optional<ZonedDateTime> tryParseDateInternal(Path path) {
return DATE_TIME_PARSERS.stream()
.map(parser -> parser.detectDateTime(path))
.filter(Objects::nonNull)
.findFirst();
}
public static URI createUri(String name) {
return URI.create(SCHEME + ":" + UrlEscapers.urlFragmentEscaper().escape(name));
}
public static FileSystemSnapshotDatabase maybeCreate(
Path root,
ArchiveNioSupport archiveNioSupport
) throws IOException {
Files.createDirectories(root);
return new FileSystemSnapshotDatabase(root, archiveNioSupport);
}
private final Path root;
private final ArchiveNioSupport archiveNioSupport;
public FileSystemSnapshotDatabase(Path root, ArchiveNioSupport archiveNioSupport) {
checkArgument(Files.isDirectory(root), "Database root is not a directory");
this.root = root.toAbsolutePath();
this.archiveNioSupport = archiveNioSupport;
}
private SnapshotInfo createSnapshotInfo(Path fullPath, Path realPath) {
// Try full for parsing out of file name, real for parsing mod time.
ZonedDateTime date = tryParseDateInternal(fullPath).orElseGet(() -> tryParseDate(realPath));
return SnapshotInfo.create(createUri(fullPath.toString()), date);
}
private Snapshot createSnapshot(Path fullPath, Path realPath, @Nullable IORunnable closeCallback) {
return new FolderSnapshot(
createSnapshotInfo(fullPath, realPath), realPath, closeCallback
);
}
public Path getRoot() {
return root;
}
@Override
public String getScheme() {
return SCHEME;
}
@Override
public Optional<Snapshot> getSnapshot(URI name) throws IOException {
if (!name.getScheme().equals(SCHEME)) {
return Optional.empty();
}
// drop the / in the path to make it absolute
Path rawResolved = root.resolve(name.getSchemeSpecificPart());
// Catch trickery with paths:
Path realPath = rawResolved.normalize();
if (!realPath.startsWith(root)) {
return Optional.empty();
}
Optional<Snapshot> result = tryRegularFileSnapshot(root.relativize(realPath), realPath);
if (result.isPresent()) {
return result;
}
if (!Files.isDirectory(realPath)) {
return Optional.empty();
}
return Optional.of(createSnapshot(root.relativize(realPath), realPath, null));
}
private Optional<Snapshot> tryRegularFileSnapshot(Path fullPath, Path realPath) throws IOException {
Closer closer = Closer.create();
Path root = this.root;
Path relative = root.relativize(realPath);
Iterator<Path> iterator = null;
try {
while (true) {
if (iterator == null) {
iterator = MorePaths.iterPaths(relative).iterator();
}
if (!iterator.hasNext()) {
return Optional.empty();
}
Path relativeNext = iterator.next();
Path next = root.resolve(relativeNext);
if (!Files.isRegularFile(next)) {
// This will never be it.
continue;
}
Optional<Path> newRootOpt = archiveNioSupport.tryOpenAsDir(next);
if (newRootOpt.isPresent()) {
root = newRootOpt.get();
if (root.getFileSystem() != FileSystems.getDefault()) {
closer.register(root.getFileSystem());
}
// Switch path to path inside the archive
relative = root.resolve(relativeNext.relativize(relative).toString());
iterator = null;
// Check if it exists, if so open snapshot
if (Files.exists(relative)) {
return Optional.of(createSnapshot(fullPath, relative, closer::close));
}
// Otherwise, we may have more archives to open.
// Keep searching!
}
}
} catch (Throwable t) {
throw closer.rethrowAndClose(t);
}
}
@Override
public Stream<Snapshot> getSnapshots(String worldName) throws IOException {
/*
There are a few possible snapshot formats we accept:
- a world directory, identified by <worldName>/level.dat
- a world archive, identified by <worldName>.ext
* does not need to have level.dat inside
- a timestamped directory, identified by <stamp>, that can have
- the two world formats described above, inside the directory
- a timestamped archive, identified by <stamp>.ext, that can have
- the same as timestamped directory, but inside the archive.
- a directory with the world name, but no level.dat
- inside must be timestamped directory/archive, with the world inside that
All archives may have a root directory with the same name as the archive,
minus the extensions. Due to extension detection methods, this won't work properly
with some files, e.g. world.qux.zip/world.qux is invalid, but world.qux.zip/world isn't.
*/
return Stream.of(
listWorldEntries(Paths.get(""), root, worldName),
listTimestampedEntries(Paths.get(""), root, worldName)
).flatMap(Function.identity());
}
private Stream<Snapshot> listWorldEntries(Path fullPath, Path root, String worldName) throws IOException {
logger.debug("World check in: {}", root);
return Files.list(root)
.flatMap(candidate -> {
logger.debug("World trying: {}", candidate);
// Try world directory
String fileName = candidate.getFileName().toString();
if (isSameDirectoryName(fileName, worldName)) {
// Direct
if (Files.exists(candidate.resolve("level.dat"))) {
logger.debug("Direct!");
return Stream.of(createSnapshot(
fullPath.resolve(fileName), candidate, null
));
}
// Container for time-stamped entries
try {
return listTimestampedEntries(
fullPath.resolve(fileName), candidate, worldName
);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
// Try world archive
if (Files.isRegularFile(candidate)
&& fileName.startsWith(worldName + ".")) {
logger.debug("Archive!");
try {
return tryRegularFileSnapshot(
fullPath.resolve(fileName), candidate
).map(Stream::of).orElse(null);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
logger.debug("Nothing!");
return null;
});
}
private boolean isSameDirectoryName(String fileName, String worldName) {
if (fileName.lastIndexOf('/') == fileName.length() - 1) {
fileName = fileName.substring(0, fileName.length() - 1);
}
return fileName.equalsIgnoreCase(worldName);
}
private Stream<Snapshot> listTimestampedEntries(Path fullPath, Path root, String worldName) throws IOException {
logger.debug("Timestamp check in: {}", root);
return Files.list(root)
.filter(candidate -> {
ZonedDateTime date = FileNameDateTimeParser.getInstance().detectDateTime(candidate);
return date != null;
})
.flatMap(candidate -> {
logger.debug("Timestamp trying: {}", candidate);
// Try timestamped directory
if (Files.isDirectory(candidate)) {
logger.debug("Timestamped directory");
try {
return listWorldEntries(
fullPath.resolve(candidate.getFileName().toString()), candidate, worldName
);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
// Otherwise archive, get it as a directory & unpack it
try {
Optional<Path> newRoot = archiveNioSupport.tryOpenAsDir(candidate);
if (!newRoot.isPresent()) {
logger.debug("Nothing!");
return null;
}
logger.debug("Timestamped archive!");
return listWorldEntries(
fullPath.resolve(candidate.getFileName().toString()),
newRoot.get(),
worldName
);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
}

View File

@ -0,0 +1,166 @@
/*
* 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.world.snapshot.experimental.fs;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.function.IORunnable;
import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotInfo;
import com.sk89q.worldedit.world.storage.ChunkStoreHelper;
import com.sk89q.worldedit.world.storage.LegacyChunkStore;
import com.sk89q.worldedit.world.storage.McRegionChunkStore;
import com.sk89q.worldedit.world.storage.McRegionReader;
import com.sk89q.worldedit.world.storage.MissingChunkException;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import static com.google.common.base.Preconditions.checkState;
/**
* Snapshot based on a world folder. Extracts chunks from the region folder.
*
* <p>
* Note that the Path can belong to another filesystem. This allows easy integration with
* zips due to Java's built-in zipfs support.
* </p>
*/
public class FolderSnapshot implements Snapshot {
/**
* Object used by {@code getRegionFolder(Path)} to indicate that the path does not exist.
*/
private static final Object NOT_FOUND_TOKEN = new Object();
private static Object getRegionFolder(Path folder) throws IOException {
Path regionDir = folder.resolve("region");
if (Files.exists(regionDir)) {
checkState(Files.isDirectory(regionDir), "Region folder is actually a file");
return regionDir;
}
// Might be in a DIM* folder
try (Stream<Path> paths = Files.list(folder)) {
Optional<Path> path = paths
.filter(Files::isDirectory)
.filter(p -> p.getFileName().toString().startsWith("DIM"))
.map(p -> p.resolve("region"))
.filter(Files::isDirectory)
.findFirst();
if (path.isPresent()) {
return path.get();
}
}
// Might be its own region folder, check if the appropriate files exist
try (Stream<Path> paths = Files.list(folder)) {
if (paths
.filter(Files::isRegularFile)
.anyMatch(p -> {
String fileName = p.getFileName().toString();
return fileName.startsWith("r") &&
(fileName.endsWith(".mca") || fileName.endsWith(".mcr"));
})) {
return folder;
}
}
return NOT_FOUND_TOKEN;
}
private final SnapshotInfo info;
private final Path folder;
private final AtomicReference<Object> regionFolder = new AtomicReference<>();
private final @Nullable IORunnable closeCallback;
public FolderSnapshot(SnapshotInfo info, Path folder, @Nullable IORunnable closeCallback) {
this.info = info;
// This is required to force TrueVfs to properly resolve parents.
// Kinda odd, but whatever works.
this.folder = folder.toAbsolutePath();
this.closeCallback = closeCallback;
}
public Path getFolder() {
return folder;
}
@Override
public SnapshotInfo getInfo() {
return info;
}
private Optional<Path> getRegionFolder() throws IOException {
Object regFolder = regionFolder.get();
if (regFolder == null) {
Object update = getRegionFolder(folder);
if (!regionFolder.compareAndSet(null, update)) {
// failed race, get existing value
regFolder = regionFolder.get();
} else {
regFolder = update;
}
}
return regFolder == NOT_FOUND_TOKEN ? Optional.empty() : Optional.of((Path) regFolder);
}
@Override
public CompoundTag getChunkTag(BlockVector3 position) throws DataException, IOException {
BlockVector2 pos = position.toBlockVector2();
Optional<Path> regFolder = getRegionFolder();
if (!regFolder.isPresent()) {
Path chunkFile = getFolder().resolve(LegacyChunkStore.getFilename(pos, "/"));
if (!Files.exists(chunkFile)) {
throw new MissingChunkException();
}
return ChunkStoreHelper.readCompoundTag(() ->
new GZIPInputStream(Files.newInputStream(chunkFile))
);
}
Path regionFile = regFolder.get().resolve(McRegionChunkStore.getFilename(pos));
if (!Files.exists(regionFile)) {
// Try mcr as well
regionFile = regionFile.resolveSibling(
regionFile.getFileName().toString().replace(".mca", ".mcr")
);
if (!Files.exists(regionFile)) {
throw new MissingChunkException();
}
}
try (InputStream stream = Files.newInputStream(regionFile)) {
McRegionReader regionReader = new McRegionReader(stream);
return ChunkStoreHelper.readCompoundTag(() -> regionReader.getChunkInputStream(pos));
}
}
@Override
public void close() throws IOException {
if (closeCallback != null) {
closeCallback.run();
}
}
}

View File

@ -0,0 +1,30 @@
/*
* 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/>.
*/
/**
* Experimental, in-testing, snapshot API. Do NOT rely on this API in plugin releases, as it will
* move to the existing snapshot package when testing is complete.
*
* <p>
* The existing snapshot API will be removed when this API is made official. It aims to have 100%
* compatibility with old snapshot storage, bar some odd date formats.
* </p>
*/
package com.sk89q.worldedit.world.snapshot.experimental;
// TODO Un-experimentalize when ready.

View File

@ -25,6 +25,7 @@ import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.chunk.Chunk;
import java.io.Closeable;
import java.io.IOException;

View File

@ -47,7 +47,7 @@ public class ChunkStoreHelper {
public static CompoundTag readCompoundTag(ChunkDataInputSupplier input) throws DataException, IOException {
try (InputStream stream = input.openInputStream();
NBTInputStream nbt = new NBTInputStream(stream)) {
NBTInputStream nbt = new NBTInputStream(stream)) {
Tag tag = nbt.readNamedTag().getTag();
if (!(tag instanceof CompoundTag)) {
throw new ChunkStoreException("CompoundTag expected for chunk; got "

View File

@ -23,6 +23,7 @@ import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.World;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

View File

@ -20,8 +20,6 @@
package com.sk89q.worldedit.world.storage;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.World;
@ -67,19 +65,11 @@ public abstract class McRegionChunkStore extends ChunkStore {
@Override
public CompoundTag getChunkTag(BlockVector2 position, World world) throws DataException, IOException {
McRegionReader reader = getReader(position, world.getName());
return ChunkStoreHelper.readCompoundTag(() -> {
McRegionReader reader = getReader(position, world.getName());
InputStream stream = reader.getChunkInputStream(position);
Tag tag;
try (NBTInputStream nbt = new NBTInputStream(stream)) {
tag = nbt.readNamedTag().getTag();
if (!(tag instanceof CompoundTag)) {
throw new ChunkStoreException("CompoundTag expected for chunk; got " + tag.getClass().getName());
}
return (CompoundTag) tag;
}
return reader.getChunkInputStream(position);
});
}
/**

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long