mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-22 09:17:39 +00:00
Upstream Merge
This commit is contained in:
parent
b2be1ea9fb
commit
0d2fff2cd2
@ -84,10 +84,3 @@ if (!project.hasProperty("gitCommitHash")) {
|
||||
"no_git_id"
|
||||
}
|
||||
}
|
||||
|
||||
//buildScan {
|
||||
// setTermsOfServiceUrl("https://gradle.com/terms-of-service")
|
||||
// setTermsOfServiceAgree("yes")
|
||||
//
|
||||
// publishAlways()
|
||||
//}
|
||||
|
@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,7 @@ fun Project.applyCommonConfiguration() {
|
||||
}
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
cacheChangingModulesFor(10, "minutes")
|
||||
cacheChangingModulesFor(5, "minutes")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -121,3 +121,8 @@ fun Project.applyShadowConfiguration() {
|
||||
minimize()
|
||||
}
|
||||
}
|
||||
|
||||
val CLASSPATH = listOf("truezip", "truevfs", "js")
|
||||
.map { "$it.jar" }
|
||||
.flatMap { listOf(it, "WorldEdit/$it") }
|
||||
.joinToString(separator = " ")
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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")) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())));
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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++;
|
||||
|
@ -26,4 +26,4 @@ public interface CompiledExpression {
|
||||
|
||||
Double execute(ExecutionData executionData);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -48,4 +48,4 @@ public class ExecutionData {
|
||||
public SetMultimap<String, MethodHandle> getFunctions() {
|
||||
return requireNonNull(functions, "Cannot use functions in a constant");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,4 +31,4 @@ class ExecNode {
|
||||
this.ctx = ctx;
|
||||
this.handle = handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -49,4 +49,4 @@ public final class BitMath {
|
||||
private BitMath() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
@ -71,4 +71,5 @@ public class LazyReference<T> {
|
||||
refInfo.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -47,6 +47,7 @@ public class TextUtils {
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Java Locale object by the Minecraft locale tag.
|
||||
*
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -51,4 +51,4 @@ public class ResourceLoader {
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -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() {
|
||||
}
|
||||
}
|
@ -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() {
|
||||
}
|
||||
}
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
@ -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$", "")
|
||||
));
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -33,4 +33,4 @@ public interface ItemMaterial {
|
||||
* @return the maximum damage, or 0 if not applicable
|
||||
*/
|
||||
int getMaxDamage();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -42,4 +42,4 @@ public class PassthroughItemMaterial implements ItemMaterial {
|
||||
public int getMaxDamage() {
|
||||
return itemMaterial.getMaxDamage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
@ -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() {
|
||||
}
|
||||
}
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
@ -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;
|
||||
|
||||
|
@ -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 "
|
||||
|
@ -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;
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user