Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-12-25 02:20:07 +01:00
Upstream Merge
Dieser Commit ist enthalten in:
Ursprung
b2be1ea9fb
Commit
0d2fff2cd2
@ -84,10 +84,3 @@ if (!project.hasProperty("gitCommitHash")) {
|
|||||||
"no_git_id"
|
"no_git_id"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//buildScan {
|
|
||||||
// setTermsOfServiceUrl("https://gradle.com/terms-of-service")
|
|
||||||
// setTermsOfServiceAgree("yes")
|
|
||||||
//
|
|
||||||
// publishAlways()
|
|
||||||
//}
|
|
||||||
|
@ -21,8 +21,7 @@ fun Project.applyCommonConfiguration() {
|
|||||||
}
|
}
|
||||||
configurations.all {
|
configurations.all {
|
||||||
resolutionStrategy {
|
resolutionStrategy {
|
||||||
cacheChangingModulesFor(10, "minutes")
|
cacheChangingModulesFor(5, "minutes")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -121,3 +121,8 @@ fun Project.applyShadowConfiguration() {
|
|||||||
minimize()
|
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 AUTO_VALUE = "1.6.5"
|
||||||
const val JUNIT = "5.5.0"
|
const val JUNIT = "5.5.0"
|
||||||
const val MOCKITO = "3.0.0"
|
const val MOCKITO = "3.0.0"
|
||||||
|
const val LOGBACK = "1.2.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properties that need a project reference to resolve:
|
// Properties that need a project reference to resolve:
|
||||||
|
@ -76,7 +76,7 @@ tasks.named<Copy>("processResources") {
|
|||||||
|
|
||||||
tasks.named<Jar>("jar") {
|
tasks.named<Jar>("jar") {
|
||||||
manifest {
|
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)
|
"WorldEdit-Version" to project.version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ configurations.all {
|
|||||||
dependencies {
|
dependencies {
|
||||||
"compile"(project(":worldedit-libs:core"))
|
"compile"(project(":worldedit-libs:core"))
|
||||||
"compile"("de.schlichtherle:truezip:6.8.3")
|
"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.mozilla:rhino:1.7.11")
|
||||||
"compile"("org.yaml:snakeyaml:1.23")
|
"compile"("org.yaml:snakeyaml:1.23")
|
||||||
"compile"("com.google.guava:guava:21.0")
|
"compile"("com.google.guava:guava:21.0")
|
||||||
@ -46,6 +47,8 @@ dependencies {
|
|||||||
"annotationProcessor"("com.google.guava:guava:21.0")
|
"annotationProcessor"("com.google.guava:guava:21.0")
|
||||||
"compileOnly"("com.google.auto.value:auto-value-annotations:${Versions.AUTO_VALUE}")
|
"compileOnly"("com.google.auto.value:auto-value-annotations:${Versions.AUTO_VALUE}")
|
||||||
"annotationProcessor"("com.google.auto.value:auto-value:${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"("co.aikar:fastutil-lite:1.0")
|
||||||
"compile"("com.github.luben:zstd-jni:1.4.3-1")
|
"compile"("com.github.luben:zstd-jni:1.4.3-1")
|
||||||
"compileOnly"("net.fabiozumbi12:redprotect:1.9.6")
|
"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.BaseBlock;
|
||||||
import com.sk89q.worldedit.world.block.BlockState;
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
import com.sk89q.worldedit.world.storage.InvalidFormatException;
|
import com.sk89q.worldedit.world.storage.InvalidFormatException;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
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.BlockMask;
|
||||||
import com.sk89q.worldedit.function.mask.BlockMaskBuilder;
|
import com.sk89q.worldedit.function.mask.BlockMaskBuilder;
|
||||||
import com.sk89q.worldedit.util.formatting.component.TextUtils;
|
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.util.logging.LogFormat;
|
||||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||||
import com.sk89q.worldedit.world.block.BlockType;
|
import com.sk89q.worldedit.world.block.BlockType;
|
||||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||||
import com.sk89q.worldedit.world.registry.LegacyMapper;
|
import com.sk89q.worldedit.world.registry.LegacyMapper;
|
||||||
import com.sk89q.worldedit.world.snapshot.SnapshotRepository;
|
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.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -44,6 +51,8 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
public abstract class LocalConfiguration {
|
public abstract class LocalConfiguration {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(LocalConfiguration.class);
|
||||||
|
|
||||||
public boolean profile = false;
|
public boolean profile = false;
|
||||||
public boolean traceUnflushedSessions = false;
|
public boolean traceUnflushedSessions = false;
|
||||||
public Set<String> disallowedBlocks = new HashSet<>();
|
public Set<String> disallowedBlocks = new HashSet<>();
|
||||||
@ -55,7 +64,9 @@ public abstract class LocalConfiguration {
|
|||||||
public int defaultMaxPolyhedronPoints = -1;
|
public int defaultMaxPolyhedronPoints = -1;
|
||||||
public int maxPolyhedronPoints = 20;
|
public int maxPolyhedronPoints = 20;
|
||||||
public String shellSaveType = "";
|
public String shellSaveType = "";
|
||||||
|
public boolean snapshotsConfigured = false;
|
||||||
public SnapshotRepository snapshotRepo = null;
|
public SnapshotRepository snapshotRepo = null;
|
||||||
|
public SnapshotDatabase snapshotDatabase = null;
|
||||||
public int maxRadius = -1;
|
public int maxRadius = -1;
|
||||||
public int maxSuperPickaxeSize = 5;
|
public int maxSuperPickaxeSize = 5;
|
||||||
public int maxBrushRadius = 6;
|
public int maxBrushRadius = 6;
|
||||||
@ -193,6 +204,29 @@ public abstract class LocalConfiguration {
|
|||||||
return new File(".");
|
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) {
|
public String convertLegacyItem(String legacy) {
|
||||||
String item = legacy;
|
String item = legacy;
|
||||||
try {
|
try {
|
||||||
@ -211,6 +245,7 @@ public abstract class LocalConfiguration {
|
|||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDefaultLocaleName(String localeName) {
|
public void setDefaultLocaleName(String localeName) {
|
||||||
this.defaultLocaleName = localeName;
|
this.defaultLocaleName = localeName;
|
||||||
if (localeName.equals("default")) {
|
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.TextureHolder;
|
||||||
import com.boydti.fawe.util.TextureUtil;
|
import com.boydti.fawe.util.TextureUtil;
|
||||||
import com.boydti.fawe.wrappers.WorldWrapper;
|
import com.boydti.fawe.wrappers.WorldWrapper;
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.sk89q.jchronic.Chronic;
|
import com.sk89q.jchronic.Chronic;
|
||||||
import com.sk89q.jchronic.Options;
|
import com.sk89q.jchronic.Options;
|
||||||
@ -154,7 +153,7 @@ public class LocalSession implements TextureHolder {
|
|||||||
|
|
||||||
private transient VirtualWorld virtual;
|
private transient VirtualWorld virtual;
|
||||||
private transient BlockVector3 cuiTemporaryBlock;
|
private transient BlockVector3 cuiTemporaryBlock;
|
||||||
private transient List<Countable> lastDistribution;
|
private transient List<Countable<BlockState>> lastDistribution;
|
||||||
private transient World worldOverride;
|
private transient World worldOverride;
|
||||||
private transient boolean tickingWatchdog = false;
|
private transient boolean tickingWatchdog = false;
|
||||||
private transient boolean hasBeenToldVersion;
|
private transient boolean hasBeenToldVersion;
|
||||||
@ -896,7 +895,7 @@ public class LocalSession implements TextureHolder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the position use for commands that take a center point
|
* Get the position use for commands that take a center point
|
||||||
* (i.e., //forestgen, etc.).
|
* (i.e. //forestgen, etc.).
|
||||||
*
|
*
|
||||||
* @param actor the actor
|
* @param actor the actor
|
||||||
* @return the position to use
|
* @return the position to use
|
||||||
@ -1485,6 +1484,17 @@ public class LocalSession implements TextureHolder {
|
|||||||
return editSession;
|
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.
|
* Checks if the session has fast mode enabled.
|
||||||
*
|
*
|
||||||
@ -1566,7 +1576,6 @@ public class LocalSession implements TextureHolder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the TextureUtil currently being used
|
* Get the TextureUtil currently being used
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public TextureUtil getTextureUtil() {
|
public TextureUtil getTextureUtil() {
|
||||||
@ -1601,14 +1610,14 @@ public class LocalSession implements TextureHolder {
|
|||||||
*
|
*
|
||||||
* @return block distribution or {@code null}
|
* @return block distribution or {@code null}
|
||||||
*/
|
*/
|
||||||
public List<Countable> getLastDistribution() {
|
public List<Countable<BlockState>> getLastDistribution() {
|
||||||
return lastDistribution == null ? null : Collections.unmodifiableList(lastDistribution);
|
return lastDistribution == null ? null : Collections.unmodifiableList(lastDistribution);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store a block distribution in this session.
|
* Store a block distribution in this session.
|
||||||
*/
|
*/
|
||||||
public void setLastDistribution(List<Countable> dist) {
|
public void setLastDistribution(List<Countable<BlockState>> dist) {
|
||||||
lastDistribution = 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;
|
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.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.sk89q.worldedit.LocalSession;
|
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.function.pattern.Pattern;
|
||||||
import com.sk89q.worldedit.internal.annotation.Direction;
|
import com.sk89q.worldedit.internal.annotation.Direction;
|
||||||
import com.sk89q.worldedit.internal.command.CommandRegistrationHandler;
|
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.regions.factory.RegionFactory;
|
||||||
import com.sk89q.worldedit.util.TreeGenerator;
|
import com.sk89q.worldedit.util.TreeGenerator;
|
||||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||||
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
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.TextColor;
|
||||||
import com.sk89q.worldedit.util.formatting.text.format.TextDecoration;
|
import com.sk89q.worldedit.util.formatting.text.format.TextDecoration;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.enginehub.piston.CommandManager;
|
import org.enginehub.piston.CommandManager;
|
||||||
import org.enginehub.piston.CommandManagerService;
|
import org.enginehub.piston.CommandManagerService;
|
||||||
import org.enginehub.piston.CommandParameters;
|
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.CommandArgument;
|
||||||
import org.enginehub.piston.part.SubCommandPart;
|
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)
|
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
|
||||||
public class PaintBrushCommands {
|
public class PaintBrushCommands {
|
||||||
|
|
||||||
@ -104,7 +102,7 @@ public class PaintBrushCommands {
|
|||||||
double radius = requireNonNull(RADIUS.value(parameters).asSingle(double.class));
|
double radius = requireNonNull(RADIUS.value(parameters).asSingle(double.class));
|
||||||
double density = requireNonNull(DENSITY.value(parameters).asSingle(double.class)) / 100;
|
double density = requireNonNull(DENSITY.value(parameters).asSingle(double.class)) / 100;
|
||||||
RegionFactory regionFactory = REGION_FACTORY.value(parameters).asSingle(RegionFactory.class);
|
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");
|
new Paint(generatorFactory, density), regionFactory, "worldedit.brush.paint");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +260,7 @@ public class RegionCommands {
|
|||||||
@CommandPermissions("worldedit.region.curve")
|
@CommandPermissions("worldedit.region.curve")
|
||||||
@Logging(REGION)
|
@Logging(REGION)
|
||||||
@Confirm(Confirm.Processor.REGION)
|
@Confirm(Confirm.Processor.REGION)
|
||||||
public void curve(Actor actor, EditSession editSession,
|
public int curve(Actor actor, EditSession editSession,
|
||||||
@Selection Region region,
|
@Selection Region region,
|
||||||
@Arg(desc = "The pattern of blocks to place")
|
@Arg(desc = "The pattern of blocks to place")
|
||||||
Pattern pattern,
|
Pattern pattern,
|
||||||
@ -269,8 +269,8 @@ public class RegionCommands {
|
|||||||
@Switch(name = 'h', desc = "Generate only a shell")
|
@Switch(name = 'h', desc = "Generate only a shell")
|
||||||
boolean shell) throws WorldEditException {
|
boolean shell) throws WorldEditException {
|
||||||
if (!(region instanceof ConvexPolyhedralRegion)) {
|
if (!(region instanceof ConvexPolyhedralRegion)) {
|
||||||
actor.printError(TranslatableComponent.of("worldedit.curve.convex-only"));
|
actor.printError(TranslatableComponent.of("worldedit.curve.invalid-type"));
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
checkCommandArgument(thickness >= 0, "Thickness must be >= 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);
|
int blocksChanged = editSession.drawSpline(pattern, vectors, 0, 0, 0, 10, thickness, !shell);
|
||||||
|
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.curve.changed", TextComponent.of(blocksChanged)));
|
actor.printInfo(TranslatableComponent.of("worldedit.curve.changed", TextComponent.of(blocksChanged)));
|
||||||
|
return blocksChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -290,7 +291,7 @@ public class RegionCommands {
|
|||||||
@CommandPermissions("worldedit.region.replace")
|
@CommandPermissions("worldedit.region.replace")
|
||||||
@Logging(REGION)
|
@Logging(REGION)
|
||||||
@Confirm(Confirm.Processor.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 = "")
|
@Arg(desc = "The mask representing blocks to replace", def = "")
|
||||||
Mask from,
|
Mask from,
|
||||||
@Arg(desc = "The pattern of blocks to replace with")
|
@Arg(desc = "The pattern of blocks to replace with")
|
||||||
@ -300,6 +301,7 @@ public class RegionCommands {
|
|||||||
}
|
}
|
||||||
int affected = editSession.replaceBlocks(region, from, to);
|
int affected = editSession.replaceBlocks(region, from, to);
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.replace.replaced", TextComponent.of(affected)));
|
actor.printInfo(TranslatableComponent.of("worldedit.replace.replaced", TextComponent.of(affected)));
|
||||||
|
return affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -309,11 +311,12 @@ public class RegionCommands {
|
|||||||
@CommandPermissions("worldedit.region.overlay")
|
@CommandPermissions("worldedit.region.overlay")
|
||||||
@Logging(REGION)
|
@Logging(REGION)
|
||||||
@Confirm(Confirm.Processor.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")
|
@Arg(desc = "The pattern of blocks to overlay")
|
||||||
Pattern pattern) throws WorldEditException {
|
Pattern pattern) throws WorldEditException {
|
||||||
int affected = editSession.overlayCuboidBlocks(region, pattern);
|
int affected = editSession.overlayCuboidBlocks(region, pattern);
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.overlay.overlaid", TextComponent.of(affected)));
|
actor.printInfo(TranslatableComponent.of("worldedit.overlay.overlaid", TextComponent.of(affected)));
|
||||||
|
return affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -363,9 +366,10 @@ public class RegionCommands {
|
|||||||
@CommandPermissions("worldedit.region.naturalize")
|
@CommandPermissions("worldedit.region.naturalize")
|
||||||
@Logging(REGION)
|
@Logging(REGION)
|
||||||
@Confirm(Confirm.Processor.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);
|
int affected = editSession.naturalizeCuboidBlocks(region);
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.naturalize.naturalized", TextComponent.of(affected)));
|
actor.printInfo(TranslatableComponent.of("worldedit.naturalize.naturalized", TextComponent.of(affected)));
|
||||||
|
return affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -375,11 +379,12 @@ public class RegionCommands {
|
|||||||
@CommandPermissions("worldedit.region.walls")
|
@CommandPermissions("worldedit.region.walls")
|
||||||
@Logging(REGION)
|
@Logging(REGION)
|
||||||
@Confirm(Confirm.Processor.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")
|
@Arg(desc = "The pattern of blocks to set")
|
||||||
Pattern pattern) throws WorldEditException {
|
Pattern pattern) throws WorldEditException {
|
||||||
int affected = editSession.makeWalls(region, pattern);
|
int affected = editSession.makeWalls(region, pattern);
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.walls.changed", TextComponent.of(affected)));
|
actor.printInfo(TranslatableComponent.of("worldedit.walls.changed", TextComponent.of(affected)));
|
||||||
|
return affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -390,11 +395,12 @@ public class RegionCommands {
|
|||||||
@CommandPermissions("worldedit.region.faces")
|
@CommandPermissions("worldedit.region.faces")
|
||||||
@Logging(REGION)
|
@Logging(REGION)
|
||||||
@Confirm(Confirm.Processor.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")
|
@Arg(desc = "The pattern of blocks to set")
|
||||||
Pattern pattern) throws WorldEditException {
|
Pattern pattern) throws WorldEditException {
|
||||||
int affected = editSession.makeCuboidFaces(region, pattern);
|
int affected = editSession.makeCuboidFaces(region, pattern);
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.faces.changed", TextComponent.of(affected)));
|
actor.printInfo(TranslatableComponent.of("worldedit.faces.changed", TextComponent.of(affected)));
|
||||||
|
return affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -405,7 +411,7 @@ public class RegionCommands {
|
|||||||
@CommandPermissions("worldedit.region.smooth")
|
@CommandPermissions("worldedit.region.smooth")
|
||||||
@Logging(REGION)
|
@Logging(REGION)
|
||||||
@Confirm(Confirm.Processor.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")
|
@Arg(desc = "# of iterations to perform", def = "1")
|
||||||
int iterations,
|
int iterations,
|
||||||
@Arg(desc = "The mask of blocks to use as the height map", def = "")
|
@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) {
|
if (volume >= limit.MAX_CHECKS) {
|
||||||
throw FaweCache.MAX_CHECKS;
|
throw FaweCache.MAX_CHECKS;
|
||||||
}
|
}
|
||||||
|
int affected = 0;
|
||||||
try {
|
try {
|
||||||
HeightMap heightMap = new HeightMap(editSession, region, mask, snow);
|
HeightMap heightMap = new HeightMap(editSession, region, mask, snow);
|
||||||
HeightMapFilter filter = new HeightMapFilter(new GaussianKernel(5, 1.0));
|
HeightMapFilter filter = new HeightMapFilter(new GaussianKernel(5, 1.0));
|
||||||
int affected = heightMap.applyFilter(filter, iterations);
|
affected = heightMap.applyFilter(filter, iterations);
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.smooth.changed", TextComponent.of(affected)));
|
actor.printInfo(TranslatableComponent.of("worldedit.smooth.changed", TextComponent.of(affected)));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
return affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -605,13 +613,20 @@ public class RegionCommands {
|
|||||||
@Arg(def = "", desc = "Regenerate with biome") BiomeType biome,
|
@Arg(def = "", desc = "Regenerate with biome") BiomeType biome,
|
||||||
@Arg(def = "", desc = "Regenerate with seed") Long seed) throws WorldEditException {
|
@Arg(def = "", desc = "Regenerate with seed") Long seed) throws WorldEditException {
|
||||||
Mask mask = session.getMask();
|
Mask mask = session.getMask();
|
||||||
|
boolean success;
|
||||||
|
try {
|
||||||
session.setMask((Mask) null);
|
session.setMask((Mask) null);
|
||||||
session.setSourceMask((Mask) null);
|
session.setSourceMask((Mask) null);
|
||||||
world.regenerate(region, editSession);
|
success = world.regenerate(region, editSession);
|
||||||
// editSession.regenerate(region, biome, seed);
|
} finally {
|
||||||
session.setMask(mask);
|
session.setMask(mask);
|
||||||
session.setSourceMask(mask);
|
session.setSourceMask(mask);
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.regen.regenerated"));
|
actor.printInfo(TranslatableComponent.of("worldedit.regen.regenerated"));
|
||||||
|
} else {
|
||||||
|
actor.printError(TranslatableComponent.of("worldedit.regen.failed"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -624,7 +639,7 @@ public class RegionCommands {
|
|||||||
@CommandPermissions("worldedit.region.deform")
|
@CommandPermissions("worldedit.region.deform")
|
||||||
@Logging(ALL)
|
@Logging(ALL)
|
||||||
@Confirm(Confirm.Processor.REGION)
|
@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,
|
@Selection Region region,
|
||||||
@Arg(desc = "The expression to use", variable = true)
|
@Arg(desc = "The expression to use", variable = true)
|
||||||
List<String> expression,
|
List<String> expression,
|
||||||
@ -660,8 +675,10 @@ public class RegionCommands {
|
|||||||
((Player) actor).findFreePosition();
|
((Player) actor).findFreePosition();
|
||||||
}
|
}
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.deform.deformed", TextComponent.of(affected)));
|
actor.printInfo(TranslatableComponent.of("worldedit.deform.deformed", TextComponent.of(affected)));
|
||||||
|
return affected;
|
||||||
} catch (ExpressionException e) {
|
} catch (ExpressionException e) {
|
||||||
actor.printError(TextComponent.of(e.getMessage()));
|
actor.printError(TextComponent.of(e.getMessage()));
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -676,9 +693,9 @@ public class RegionCommands {
|
|||||||
@CommandPermissions("worldedit.region.hollow")
|
@CommandPermissions("worldedit.region.hollow")
|
||||||
@Logging(REGION)
|
@Logging(REGION)
|
||||||
@Confirm(Confirm.Processor.REGION)
|
@Confirm(Confirm.Processor.REGION)
|
||||||
public void hollow(Actor actor, EditSession editSession,
|
public int hollow(Actor actor, EditSession editSession,
|
||||||
@Selection Region region,
|
@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,
|
int thickness,
|
||||||
@Arg(desc = "The pattern of blocks to replace the hollowed area with", def = "air")
|
@Arg(desc = "The pattern of blocks to replace the hollowed area with", def = "air")
|
||||||
Pattern pattern,
|
Pattern pattern,
|
||||||
@ -687,6 +704,7 @@ public class RegionCommands {
|
|||||||
Mask finalMask = mask == null ? new SolidBlockMask(editSession) : mask;
|
Mask finalMask = mask == null ? new SolidBlockMask(editSession) : mask;
|
||||||
int affected = editSession.hollowOutRegion(region, thickness, pattern, finalMask);
|
int affected = editSession.hollowOutRegion(region, thickness, pattern, finalMask);
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.hollow.changed", TextComponent.of(affected)));
|
actor.printInfo(TranslatableComponent.of("worldedit.hollow.changed", TextComponent.of(affected)));
|
||||||
|
return affected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -714,18 +732,20 @@ public class RegionCommands {
|
|||||||
@CommandPermissions("worldedit.region.flora")
|
@CommandPermissions("worldedit.region.flora")
|
||||||
@Logging(REGION)
|
@Logging(REGION)
|
||||||
@Confirm(Confirm.Processor.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")
|
@Arg(desc = "The density of the forest", def = "5")
|
||||||
double density) throws WorldEditException {
|
double density) throws WorldEditException {
|
||||||
checkCommandArgument(0 <= density && density <= 100, "Density must be in [0, 100]");
|
checkCommandArgument(0 <= density && density <= 100, "Density must be in [0, 100]");
|
||||||
|
density = density / 100;
|
||||||
FloraGenerator generator = new FloraGenerator(editSession);
|
FloraGenerator generator = new FloraGenerator(editSession);
|
||||||
GroundFunction ground = new GroundFunction(new ExistingBlockMask(editSession), generator);
|
GroundFunction ground = new GroundFunction(new ExistingBlockMask(editSession), generator);
|
||||||
LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground);
|
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);
|
Operations.completeLegacy(visitor);
|
||||||
|
|
||||||
int affected = ground.getAffected();
|
int affected = ground.getAffected();
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.flora.created", TextComponent.of(affected)));
|
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,
|
public void load(Actor actor, LocalSession session,
|
||||||
@Arg(desc = "File name.")
|
@Arg(desc = "File name.")
|
||||||
String filename,
|
String filename,
|
||||||
@Arg(desc = "Format name.", def = "")
|
@Arg(desc = "Format name.", def = "sponge")
|
||||||
String formatName) throws FilenameException {
|
String formatName) throws FilenameException {
|
||||||
LocalConfiguration config = worldEdit.getConfiguration();
|
LocalConfiguration config = worldEdit.getConfiguration();
|
||||||
|
|
||||||
ClipboardFormat format = formatName != null ? ClipboardFormats.findByAlias(formatName) : null;
|
ClipboardFormat format = ClipboardFormats.findByAlias(formatName);
|
||||||
InputStream in = null;
|
InputStream in = null;
|
||||||
try {
|
try {
|
||||||
URI uri;
|
URI uri;
|
||||||
@ -537,7 +537,7 @@ public class SchematicCommands {
|
|||||||
)
|
)
|
||||||
@CommandPermissions("worldedit.schematic.list")
|
@CommandPermissions("worldedit.schematic.list")
|
||||||
public void list(Actor actor, LocalSession session,
|
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,
|
int page,
|
||||||
@Switch(name = 'd', desc = "Sort by date, oldest first")
|
@Switch(name = 'd', desc = "Sort by date, oldest first")
|
||||||
boolean oldFirst,
|
boolean oldFirst,
|
||||||
@ -571,15 +571,6 @@ public class SchematicCommands {
|
|||||||
final boolean hasShow = false;
|
final boolean hasShow = false;
|
||||||
|
|
||||||
//If player forgot -p argument
|
//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;
|
boolean playerFolder = Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS;
|
||||||
UUID uuid = playerFolder ? actor.getUniqueId() : null;
|
UUID uuid = playerFolder ? actor.getUniqueId() : null;
|
||||||
List<File> files = UtilityCommands.getFiles(dir, actor, args, formatName, playerFolder, oldFirst, newFirst);
|
List<File> files = UtilityCommands.getFiles(dir, actor, args, formatName, playerFolder, oldFirst, newFirst);
|
||||||
|
@ -19,22 +19,15 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.command;
|
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.POSITION;
|
||||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
||||||
|
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.boydti.fawe.config.Caption;
|
||||||
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.object.clipboard.URIClipboardHolder;
|
import com.boydti.fawe.object.clipboard.URIClipboardHolder;
|
||||||
import com.boydti.fawe.object.mask.IdMask;
|
import com.boydti.fawe.object.mask.IdMask;
|
||||||
import com.boydti.fawe.object.regions.selector.FuzzyRegionSelector;
|
import com.boydti.fawe.object.regions.selector.FuzzyRegionSelector;
|
||||||
import com.boydti.fawe.object.regions.selector.PolyhedralRegionSelector;
|
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.EditSession;
|
||||||
import com.sk89q.worldedit.LocalSession;
|
import com.sk89q.worldedit.LocalSession;
|
||||||
import com.sk89q.worldedit.WorldEdit;
|
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.Actor;
|
||||||
import com.sk89q.worldedit.extension.platform.Locatable;
|
import com.sk89q.worldedit.extension.platform.Locatable;
|
||||||
import com.sk89q.worldedit.extension.platform.permission.ActorSelectorLimits;
|
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.extent.clipboard.Clipboard;
|
||||||
|
import com.sk89q.worldedit.function.block.BlockDistributionCounter;
|
||||||
import com.sk89q.worldedit.function.mask.Mask;
|
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.Direction;
|
||||||
import com.sk89q.worldedit.internal.annotation.MultiDirection;
|
import com.sk89q.worldedit.internal.annotation.MultiDirection;
|
||||||
import com.sk89q.worldedit.math.BlockVector2;
|
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.Countable;
|
||||||
import com.sk89q.worldedit.util.Location;
|
import com.sk89q.worldedit.util.Location;
|
||||||
import com.sk89q.worldedit.util.formatting.component.CommandListBox;
|
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.SubtleFormat;
|
||||||
import com.sk89q.worldedit.util.formatting.component.TextComponentProducer;
|
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.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.ClickEvent;
|
||||||
|
import com.sk89q.worldedit.util.formatting.text.event.HoverEvent;
|
||||||
import com.sk89q.worldedit.util.formatting.text.format.TextColor;
|
import com.sk89q.worldedit.util.formatting.text.format.TextColor;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
import com.sk89q.worldedit.world.block.BlockState;
|
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.ItemType;
|
||||||
import com.sk89q.worldedit.world.item.ItemTypes;
|
import com.sk89q.worldedit.world.item.ItemTypes;
|
||||||
import com.sk89q.worldedit.world.storage.ChunkStore;
|
import com.sk89q.worldedit.world.storage.ChunkStore;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.List;
|
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 java.util.Optional;
|
||||||
import com.sk89q.worldedit.util.formatting.text.event.HoverEvent;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.enginehub.piston.annotation.Command;
|
import org.enginehub.piston.annotation.Command;
|
||||||
import org.enginehub.piston.annotation.CommandContainer;
|
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.Arg;
|
||||||
import org.enginehub.piston.annotation.param.Switch;
|
|
||||||
import org.enginehub.piston.annotation.param.ArgFlag;
|
import org.enginehub.piston.annotation.param.ArgFlag;
|
||||||
|
import org.enginehub.piston.annotation.param.Switch;
|
||||||
import org.enginehub.piston.exception.StopExecutionException;
|
import org.enginehub.piston.exception.StopExecutionException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -306,9 +303,6 @@ public class SelectionCommands {
|
|||||||
session.setTool(itemType, SelectionWand.INSTANCE);
|
session.setTool(itemType, SelectionWand.INSTANCE);
|
||||||
player.printInfo(TranslatableComponent.of("worldedit.wand.selwand.info"));
|
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(
|
@Command(
|
||||||
@ -480,8 +474,7 @@ public class SelectionCommands {
|
|||||||
|
|
||||||
region = clipboard.getRegion();
|
region = clipboard.getRegion();
|
||||||
BlockVector3 size = region.getMaximumPoint()
|
BlockVector3 size = region.getMaximumPoint()
|
||||||
.subtract(region.getMinimumPoint()).
|
.subtract(region.getMinimumPoint()).add(1, 1, 1);
|
||||||
add(1, 1, 1);
|
|
||||||
BlockVector3 origin = clipboard.getOrigin();
|
BlockVector3 origin = clipboard.getOrigin();
|
||||||
|
|
||||||
String sizeStr = size.getBlockX() + "*" + size.getBlockY() + "*" + size.getBlockZ();
|
String sizeStr = size.getBlockX() + "*" + size.getBlockY() + "*" + size.getBlockZ();
|
||||||
@ -500,7 +493,6 @@ public class SelectionCommands {
|
|||||||
for (Component line : session.getRegionSelector(world).getSelectionInfoLines()) {
|
for (Component line : session.getRegionSelector(world).getSelectionInfoLines()) {
|
||||||
actor.print(line);
|
actor.print(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
BlockVector3 size = region.getMaximumPoint()
|
BlockVector3 size = region.getMaximumPoint()
|
||||||
.subtract(region.getMinimumPoint())
|
.subtract(region.getMinimumPoint())
|
||||||
@ -529,34 +521,34 @@ public class SelectionCommands {
|
|||||||
desc = "Get the distribution of blocks in the selection"
|
desc = "Get the distribution of blocks in the selection"
|
||||||
)
|
)
|
||||||
@CommandPermissions("worldedit.analysis.distr")
|
@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")
|
@Switch(name = 'c', desc = "Get the distribution of the clipboard instead")
|
||||||
boolean clipboardDistr,
|
boolean clipboardDistr,
|
||||||
@Switch(name = 'd', desc = "Separate blocks by state")
|
@Switch(name = 'd', desc = "Separate blocks by state")
|
||||||
boolean separateStates,
|
boolean separateStates,
|
||||||
@ArgFlag(name = 'p', desc = "Gets page from a previous distribution.", def = "")
|
@ArgFlag(name = 'p', desc = "Gets page from a previous distribution.", def = "")
|
||||||
Integer page) throws WorldEditException {
|
Integer page) throws WorldEditException {
|
||||||
List<Countable> distribution;
|
List<Countable<BlockState>> distribution;
|
||||||
|
|
||||||
Region region;
|
Region region;
|
||||||
if (page == null) {
|
if (page == null) {
|
||||||
Extent extent;
|
Extent extent;
|
||||||
if (clipboardDistr) {
|
if (clipboardDistr) {
|
||||||
Clipboard clipboard = session.getClipboard().getClipboard(); // throws if missing
|
Clipboard clipboard = session.getClipboard().getClipboard(); // throws if missing
|
||||||
extent = clipboard;
|
BlockDistributionCounter count = new BlockDistributionCounter(clipboard, separateStates);
|
||||||
region = clipboard.getRegion();
|
RegionVisitor visitor = new RegionVisitor(clipboard.getRegion(), count);
|
||||||
|
Operations.completeBlindly(visitor);
|
||||||
|
distribution = count.getDistribution();
|
||||||
} else {
|
} else {
|
||||||
extent = editSession;
|
try (EditSession editSession = session.createEditSession(actor)) {
|
||||||
region = session.getSelection(world);
|
distribution = editSession
|
||||||
|
.getBlockDistribution(session.getSelection(world), separateStates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (separateStates)
|
|
||||||
distribution = (List) extent.getBlockDistributionWithData(region);
|
|
||||||
else
|
|
||||||
distribution = (List) extent.getBlockDistribution(region);
|
|
||||||
session.setLastDistribution(distribution);
|
session.setLastDistribution(distribution);
|
||||||
page = 1;
|
page = 1;
|
||||||
} else {
|
} else {
|
||||||
distribution = (List) session.getLastDistribution();
|
distribution = session.getLastDistribution();
|
||||||
if (distribution == null) {
|
if (distribution == null) {
|
||||||
actor.printError(TranslatableComponent.of("worldedit.distr.no-previous"));
|
actor.printError(TranslatableComponent.of("worldedit.distr.no-previous"));
|
||||||
return;
|
return;
|
||||||
@ -572,69 +564,6 @@ public class SelectionCommands {
|
|||||||
actor.print(res.create(page));
|
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(
|
@Command(
|
||||||
name = "/sel",
|
name = "/sel",
|
||||||
aliases = { ";", "/desel", "/deselect" },
|
aliases = { ";", "/desel", "/deselect" },
|
||||||
@ -751,4 +680,67 @@ public class SelectionCommands {
|
|||||||
session.setRegionSelector(world, newSelector);
|
session.setRegionSelector(world, newSelector);
|
||||||
session.dispatchCUISelection(actor);
|
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) {
|
public Component getComponent(int number) {
|
||||||
final Snapshot snapshot = snapshots.get(number);
|
final Snapshot snapshot = snapshots.get(number);
|
||||||
return TextComponent.of(number + 1 + ". ", TextColor.GOLD)
|
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")))
|
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to use")))
|
||||||
.clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/snap use " + snapshot.getName())));
|
.clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/snap use " + snapshot.getName())));
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,8 @@ public class ExpressionMaskParser extends InputParser<Mask> {
|
|||||||
exp.setEnvironment(env);
|
exp.setEnvironment(env);
|
||||||
if (context.getActor() != null) {
|
if (context.getActor() != null) {
|
||||||
SessionOwner owner = context.getActor();
|
SessionOwner owner = context.getActor();
|
||||||
Integer timeout = WorldEdit.getInstance().getSessionManager().get(owner).getTimeout();
|
IntSupplier timeout = () -> WorldEdit.getInstance().getSessionManager().get(owner).getTimeout();
|
||||||
return new ExpressionMask(exp, timeout::intValue);
|
return new ExpressionMask(exp, timeout);
|
||||||
}
|
}
|
||||||
return new ExpressionMask(exp);
|
return new ExpressionMask(exp);
|
||||||
} catch (ExpressionException e) {
|
} catch (ExpressionException e) {
|
||||||
|
@ -32,8 +32,6 @@ import com.sk89q.worldedit.math.BlockVector3;
|
|||||||
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class OffsetMaskParser extends InputParser<Mask> {
|
public class OffsetMaskParser extends InputParser<Mask> {
|
||||||
|
|
||||||
public OffsetMaskParser(WorldEdit worldEdit) {
|
public OffsetMaskParser(WorldEdit worldEdit) {
|
||||||
|
@ -37,9 +37,9 @@ public class BlockMaskBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean filterRegex(BlockType blockType, PropertyKey key, String regex) {
|
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;
|
if (property == null) return false;
|
||||||
List values = property.getValues();
|
List<Object> values = property.getValues();
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
for (int i = 0; i < values.size(); i++) {
|
for (int i = 0; i < values.size(); i++) {
|
||||||
Object value = values.get(i);
|
Object value = values.get(i);
|
||||||
@ -52,10 +52,10 @@ public class BlockMaskBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean filterOperator(BlockType blockType, PropertyKey key, Operator operator, CharSequence value) {
|
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;
|
if (property == null) return false;
|
||||||
int index = property.getIndexFor(value);
|
int index = property.getIndexFor(value);
|
||||||
List values = property.getValues();
|
List<Object> values = property.getValues();
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
for (int i = 0; i < values.size(); i++) {
|
for (int i = 0; i < values.size(); i++) {
|
||||||
if (!operator.test(index, i) && has(blockType, property, 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, () -> {
|
throw new SuggestInputParseException("No value for " + input, input, () -> {
|
||||||
HashSet<String> values = new HashSet<>();
|
HashSet<String> values = new HashSet<>();
|
||||||
types.stream().filter(t -> t.hasProperty(fKey)).forEach(t -> {
|
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++) {
|
for (int j = 0; j < p.getValues().size(); j++) {
|
||||||
if (has(t, p, j)) {
|
if (has(t, p, j)) {
|
||||||
String o = p.getValues().get(j).toString();
|
String o = p.getValues().get(j).toString();
|
||||||
@ -220,8 +220,8 @@ public class BlockMaskBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean has(BlockType type, Property property, int index) {
|
private <T> boolean has(BlockType type, Property<T> property, int index) {
|
||||||
AbstractProperty prop = (AbstractProperty) property;
|
AbstractProperty<T> prop = (AbstractProperty<T>) property;
|
||||||
long[] states = bitSets[type.getInternalId()];
|
long[] states = bitSets[type.getInternalId()];
|
||||||
if (states == null) return false;
|
if (states == null) return false;
|
||||||
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
||||||
@ -247,10 +247,7 @@ public class BlockMaskBuilder {
|
|||||||
private boolean optimizedStates = true;
|
private boolean optimizedStates = true;
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
for (long[] bitSet : bitSets) {
|
return Arrays.stream(bitSets).noneMatch(Objects::nonNull);
|
||||||
if (bitSet != null) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockMaskBuilder() {
|
public BlockMaskBuilder() {
|
||||||
@ -283,7 +280,7 @@ public class BlockMaskBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockMaskBuilder remove(BlockStateHolder state) {
|
public <T extends BlockStateHolder<T>> BlockMaskBuilder remove(BlockStateHolder<T> state) {
|
||||||
BlockType type = state.getBlockType();
|
BlockType type = state.getBlockType();
|
||||||
int i = type.getInternalId();
|
int i = type.getInternalId();
|
||||||
long[] states = bitSets[i];
|
long[] states = bitSets[i];
|
||||||
@ -308,7 +305,7 @@ public class BlockMaskBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockMaskBuilder filter(BlockStateHolder state) {
|
public <T extends BlockStateHolder<T>> BlockMaskBuilder filter(BlockStateHolder<T> state) {
|
||||||
filter(state.getBlockType());
|
filter(state.getBlockType());
|
||||||
BlockType type = state.getBlockType();
|
BlockType type = state.getBlockType();
|
||||||
int i = type.getInternalId();
|
int i = type.getInternalId();
|
||||||
@ -351,8 +348,8 @@ public class BlockMaskBuilder {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
List<AbstractProperty<?>> properties = (List<AbstractProperty<?>>) type.getProperties();
|
List<AbstractProperty<?>> properties = (List<AbstractProperty<?>>) type.getProperties();
|
||||||
for (AbstractProperty prop : properties) {
|
for (AbstractProperty<?> prop : properties) {
|
||||||
List values = prop.getValues();
|
List<?> values = prop.getValues();
|
||||||
for (int j = 0; j < values.size(); j++) {
|
for (int j = 0; j < values.size(); j++) {
|
||||||
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
||||||
if (states == ALL || FastBitSet.get(states, localI)) {
|
if (states == ALL || FastBitSet.get(states, localI)) {
|
||||||
@ -376,7 +373,7 @@ public class BlockMaskBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockMaskBuilder add(BlockStateHolder state) {
|
public <T extends BlockStateHolder<T>> BlockMaskBuilder add(BlockStateHolder<T> state) {
|
||||||
BlockType type = state.getBlockType();
|
BlockType type = state.getBlockType();
|
||||||
int i = type.getInternalId();
|
int i = type.getInternalId();
|
||||||
long[] states = bitSets[i];
|
long[] states = bitSets[i];
|
||||||
@ -391,8 +388,8 @@ public class BlockMaskBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends BlockStateHolder> BlockMaskBuilder addBlocks(Collection<T> blocks) {
|
public <T extends BlockStateHolder<T>> BlockMaskBuilder addBlocks(Collection<T> blocks) {
|
||||||
for (BlockStateHolder block : blocks) add(block);
|
for (BlockStateHolder<T> block : blocks) add(block);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,8 +398,8 @@ public class BlockMaskBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends BlockStateHolder> BlockMaskBuilder addBlocks(T... blocks) {
|
public <T extends BlockStateHolder<T>> BlockMaskBuilder addBlocks(T... blocks) {
|
||||||
for (BlockStateHolder block : blocks) add(block);
|
for (BlockStateHolder<T> block : blocks) add(block);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,8 +426,8 @@ public class BlockMaskBuilder {
|
|||||||
if (!typePredicate.test(type)) {
|
if (!typePredicate.test(type)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (AbstractProperty prop : (List<AbstractProperty<?>>) type.getProperties()) {
|
for (AbstractProperty<?> prop : (List<AbstractProperty<?>>) type.getProperties()) {
|
||||||
List values = prop.getValues();
|
List<?> values = prop.getValues();
|
||||||
for (int j = 0; j < values.size(); j++) {
|
for (int j = 0; j < values.size(); j++) {
|
||||||
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
||||||
if (states == null || !FastBitSet.get(states, localI)) {
|
if (states == null || !FastBitSet.get(states, localI)) {
|
||||||
@ -448,12 +445,12 @@ public class BlockMaskBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockMaskBuilder add(BlockType type, Property property, int index) {
|
public <T> BlockMaskBuilder add(BlockType type, Property<T> property, int index) {
|
||||||
AbstractProperty prop = (AbstractProperty) property;
|
AbstractProperty<T> prop = (AbstractProperty<T>) property;
|
||||||
long[] states = bitSets[type.getInternalId()];
|
long[] states = bitSets[type.getInternalId()];
|
||||||
if (states == ALL) return this;
|
if (states == ALL) return this;
|
||||||
|
|
||||||
List values = property.getValues();
|
List<T> values = property.getValues();
|
||||||
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
||||||
if (states == null || !FastBitSet.get(states, localI)) {
|
if (states == null || !FastBitSet.get(states, localI)) {
|
||||||
if (states == null) {
|
if (states == null) {
|
||||||
@ -465,11 +462,11 @@ public class BlockMaskBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockMaskBuilder filter(BlockType type, Property property, int index) {
|
public <T> BlockMaskBuilder filter(BlockType type, Property<T> property, int index) {
|
||||||
AbstractProperty prop = (AbstractProperty) property;
|
AbstractProperty<T> prop = (AbstractProperty<T>) property;
|
||||||
long[] states = bitSets[type.getInternalId()];
|
long[] states = bitSets[type.getInternalId()];
|
||||||
if (states == null) return this;
|
if (states == null) return this;
|
||||||
List values = property.getValues();
|
List<T> values = property.getValues();
|
||||||
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
int localI = index << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
||||||
if (states == ALL || FastBitSet.get(states, localI)) {
|
if (states == ALL || FastBitSet.get(states, localI)) {
|
||||||
if (states == ALL) {
|
if (states == ALL) {
|
||||||
@ -537,8 +534,8 @@ public class BlockMaskBuilder {
|
|||||||
}
|
}
|
||||||
int set = 0;
|
int set = 0;
|
||||||
int clear = 0;
|
int clear = 0;
|
||||||
for (AbstractProperty prop : (List<AbstractProperty<?>>) type.getProperties()) {
|
for (AbstractProperty<?> prop : (List<AbstractProperty<?>>) type.getProperties()) {
|
||||||
List values = prop.getValues();
|
List<?> values = prop.getValues();
|
||||||
for (int j = 0; j < values.size(); j++) {
|
for (int j = 0; j < values.size(); j++) {
|
||||||
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
int localI = j << prop.getBitOffset() >> BlockTypesCache.BIT_OFFSET;
|
||||||
if (FastBitSet.get(bitSet, localI)) set++;
|
if (FastBitSet.get(bitSet, localI)) set++;
|
||||||
|
@ -35,14 +35,6 @@ import static com.sk89q.worldedit.antlr.ExpressionLexer.ID;
|
|||||||
|
|
||||||
public class ExpressionHelper {
|
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) {
|
public static void check(boolean condition, ParserRuleContext ctx, String message) {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
throw evalException(ctx, message);
|
throw evalException(ctx, message);
|
||||||
@ -68,8 +60,6 @@ public class ExpressionHelper {
|
|||||||
check(iterations <= 256, ctx, "Loop exceeded 256 iterations");
|
check(iterations <= 256, ctx, "Loop exceeded 256 iterations");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special argument handle names
|
|
||||||
|
|
||||||
public static void checkTimeout() {
|
public static void checkTimeout() {
|
||||||
if (Thread.interrupted()) {
|
if (Thread.interrupted()) {
|
||||||
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
|
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
|
||||||
@ -106,6 +96,12 @@ public class ExpressionHelper {
|
|||||||
"got " + ctx.args.size());
|
"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
|
* 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.
|
* {@code null}. If {@code arg} isn't a valid handle reference, throws.
|
||||||
@ -155,4 +151,7 @@ public class ExpressionHelper {
|
|||||||
return tryAs(ctxs.get(0), rule);
|
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.HashMultimap;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSetMultimap;
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
import com.google.common.collect.SetMultimap;
|
|
||||||
import com.google.common.collect.Multimaps;
|
import com.google.common.collect.Multimaps;
|
||||||
|
import com.google.common.collect.SetMultimap;
|
||||||
import com.google.common.primitives.Doubles;
|
import com.google.common.primitives.Doubles;
|
||||||
import com.sk89q.worldedit.internal.expression.LocalSlot.Variable;
|
import com.sk89q.worldedit.internal.expression.LocalSlot.Variable;
|
||||||
import com.sk89q.worldedit.math.Vector3;
|
import com.sk89q.worldedit.math.Vector3;
|
||||||
|
@ -22,7 +22,11 @@ package com.sk89q.worldedit.internal.expression.invoke;
|
|||||||
import com.google.common.collect.SetMultimap;
|
import com.google.common.collect.SetMultimap;
|
||||||
import com.sk89q.worldedit.antlr.ExpressionBaseVisitor;
|
import com.sk89q.worldedit.antlr.ExpressionBaseVisitor;
|
||||||
import com.sk89q.worldedit.antlr.ExpressionParser;
|
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.Double2ObjectLinkedOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
|
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
|
||||||
import org.antlr.v4.runtime.CommonToken;
|
import org.antlr.v4.runtime.CommonToken;
|
||||||
@ -40,9 +44,35 @@ import java.util.function.DoubleBinaryOperator;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
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.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;
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,26 +80,6 @@ import static java.lang.invoke.MethodType.methodType;
|
|||||||
*/
|
*/
|
||||||
class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
|
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.
|
* 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
|
* We do need to pass that around, so most MethodHandles will be of the type
|
||||||
@ -82,38 +92,6 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
|
|||||||
this.functions = functions;
|
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) {
|
private Token extractToken(ParserRuleContext ctx) {
|
||||||
List<TerminalNode> children = ctx.children.stream()
|
List<TerminalNode> children = ctx.children.stream()
|
||||||
.filter(TerminalNode.class::isInstance)
|
.filter(TerminalNode.class::isInstance)
|
||||||
@ -222,6 +200,13 @@ 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));
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MethodHandle visitBreakStatement(ExpressionParser.BreakStatementContext ctx) {
|
public MethodHandle visitBreakStatement(ExpressionParser.BreakStatementContext ctx) {
|
||||||
return BREAK_STATEMENT;
|
return BREAK_STATEMENT;
|
||||||
@ -474,6 +459,24 @@ 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
|
@Override
|
||||||
public MethodHandle visitPostfixExpr(ExpressionParser.PostfixExprContext ctx) {
|
public MethodHandle visitPostfixExpr(ExpressionParser.PostfixExprContext ctx) {
|
||||||
MethodHandle value = evaluateForValue(ctx.expr);
|
MethodHandle value = evaluateForValue(ctx.expr);
|
||||||
@ -486,6 +489,29 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
|
|||||||
"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
|
@Override
|
||||||
public MethodHandle visitAssignment(ExpressionParser.AssignmentContext ctx) {
|
public MethodHandle visitAssignment(ExpressionParser.AssignmentContext ctx) {
|
||||||
int type = extractToken(ctx.assignmentOperator()).getType();
|
int type = extractToken(ctx.assignmentOperator()).getType();
|
||||||
@ -594,6 +620,12 @@ class CompilingVisitor extends ExpressionBaseVisitor<MethodHandle> {
|
|||||||
return ExpressionHandles.call(data -> ExpressionHandles.getSlotValue(data, source));
|
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
|
@Override
|
||||||
protected MethodHandle defaultResult() {
|
protected MethodHandle defaultResult() {
|
||||||
return DEFAULT_RESULT;
|
return DEFAULT_RESULT;
|
||||||
|
@ -20,7 +20,12 @@
|
|||||||
package com.sk89q.worldedit.internal.expression.invoke;
|
package com.sk89q.worldedit.internal.expression.invoke;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
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.Double2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.doubles.Double2ObjectMaps;
|
import it.unimi.dsi.fastutil.doubles.Double2ObjectMaps;
|
||||||
import org.antlr.v4.runtime.ParserRuleContext;
|
import org.antlr.v4.runtime.ParserRuleContext;
|
||||||
@ -34,18 +39,22 @@ import java.util.Objects;
|
|||||||
import java.util.function.DoubleBinaryOperator;
|
import java.util.function.DoubleBinaryOperator;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.*;
|
import static com.sk89q.worldedit.internal.expression.ExpressionHelper.check;
|
||||||
import static java.lang.invoke.MethodHandles.*;
|
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;
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
|
|
||||||
class ExpressionHandles {
|
class ExpressionHandles {
|
||||||
|
|
||||||
static final MethodType COMPILED_EXPRESSION_SIG = methodType(Double.class, ExecutionData.class);
|
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 EVAL_EXCEPTION_CONSTR;
|
||||||
private static final MethodHandle CALL_EXPRESSION;
|
private static final MethodHandle CALL_EXPRESSION;
|
||||||
private static final MethodHandle GET_VARIABLE;
|
private static final MethodHandle GET_VARIABLE;
|
||||||
@ -54,6 +63,13 @@ class ExpressionHandles {
|
|||||||
private static final MethodHandle SIMPLE_FOR_LOOP_IMPL;
|
private static final MethodHandle SIMPLE_FOR_LOOP_IMPL;
|
||||||
private static final MethodHandle SWITCH_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 {
|
static {
|
||||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||||
try {
|
try {
|
||||||
@ -90,7 +106,9 @@ class ExpressionHandles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExpressionHandles() {
|
@FunctionalInterface
|
||||||
|
interface Invokable {
|
||||||
|
Object invoke(MethodHandle handle) throws Throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Object safeInvoke(MethodHandle handle, Invokable invokable) {
|
static Object safeInvoke(MethodHandle handle, Invokable invokable) {
|
||||||
@ -331,9 +349,7 @@ class ExpressionHandles {
|
|||||||
return evaluated;
|
return evaluated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
private ExpressionHandles() {
|
||||||
interface Invokable {
|
|
||||||
Object invoke(MethodHandle handle) throws Throwable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -19,10 +19,10 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.internal.util;
|
package com.sk89q.worldedit.internal.util;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An explicit substring. Provides the range from which it was taken.
|
* An explicit substring. Provides the range from which it was taken.
|
||||||
*/
|
*/
|
||||||
|
@ -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 center the center point of the cylinder
|
||||||
* @param radius the radius 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.
|
* 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;
|
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;
|
package com.sk89q.worldedit.regions;
|
||||||
|
|
||||||
import com.boydti.fawe.FaweCache;
|
|
||||||
import com.boydti.fawe.object.collection.BlockVectorSet;
|
import com.boydti.fawe.object.collection.BlockVectorSet;
|
||||||
import com.sk89q.worldedit.math.BlockVector2;
|
import com.sk89q.worldedit.math.BlockVector2;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
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 static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
|
||||||
import com.sk89q.worldedit.IncompleteRegionException;
|
import com.sk89q.worldedit.IncompleteRegionException;
|
||||||
import com.sk89q.worldedit.LocalSession;
|
import com.sk89q.worldedit.LocalSession;
|
||||||
import com.sk89q.worldedit.extension.platform.Actor;
|
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.regions.selector.limit.SelectorLimits;
|
||||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||||
|
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
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.util.formatting.text.TranslatableComponent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code CuboidRegion} from a user's selections.
|
* Creates a {@code CuboidRegion} from a user's selections.
|
||||||
*/
|
*/
|
||||||
@ -156,7 +157,6 @@ public class CuboidRegionSelector implements RegionSelector, CUIRegion {
|
|||||||
checkNotNull(session);
|
checkNotNull(session);
|
||||||
checkNotNull(pos);
|
checkNotNull(pos);
|
||||||
|
|
||||||
//TODO Re-add better translation
|
|
||||||
if (position1 != null && position2 != null) {
|
if (position1 != null && position2 != null) {
|
||||||
player.printInfo(TranslatableComponent.of(
|
player.printInfo(TranslatableComponent.of(
|
||||||
"worldedit.selection.cuboid.explain.primary-area",
|
"worldedit.selection.cuboid.explain.primary-area",
|
||||||
@ -176,7 +176,6 @@ public class CuboidRegionSelector implements RegionSelector, CUIRegion {
|
|||||||
checkNotNull(session);
|
checkNotNull(session);
|
||||||
checkNotNull(pos);
|
checkNotNull(pos);
|
||||||
|
|
||||||
//TODO Re-add better translation
|
|
||||||
if (position1 != null && position2 != null) {
|
if (position1 != null && position2 != null) {
|
||||||
player.printInfo(TranslatableComponent.of(
|
player.printInfo(TranslatableComponent.of(
|
||||||
"worldedit.selection.cuboid.explain.secondary-area",
|
"worldedit.selection.cuboid.explain.secondary-area",
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.regions.selector;
|
package com.sk89q.worldedit.regions.selector;
|
||||||
|
|
||||||
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import com.sk89q.worldedit.IncompleteRegionException;
|
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.regions.selector.limit.SelectorLimits;
|
||||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||||
|
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code EllipsoidRegionSelector} from a user's selections.
|
* Creates a {@code EllipsoidRegionSelector} from a user's selections.
|
||||||
*/
|
*/
|
||||||
|
@ -19,13 +19,13 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.regions.selector;
|
package com.sk89q.worldedit.regions.selector;
|
||||||
|
|
||||||
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
|
||||||
import com.sk89q.worldedit.LocalSession;
|
import com.sk89q.worldedit.LocalSession;
|
||||||
import com.sk89q.worldedit.extension.platform.Actor;
|
import com.sk89q.worldedit.extension.platform.Actor;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.regions.RegionSelector;
|
import com.sk89q.worldedit.regions.RegionSelector;
|
||||||
import com.sk89q.worldedit.regions.selector.limit.SelectorLimits;
|
import com.sk89q.worldedit.regions.selector.limit.SelectorLimits;
|
||||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||||
|
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -21,7 +21,6 @@ package com.sk89q.worldedit.regions.selector;
|
|||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
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.IncompleteRegionException;
|
||||||
import com.sk89q.worldedit.LocalSession;
|
import com.sk89q.worldedit.LocalSession;
|
||||||
import com.sk89q.worldedit.extension.platform.Actor;
|
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.regions.selector.limit.SelectorLimits;
|
||||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||||
|
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -21,8 +21,8 @@ package com.sk89q.worldedit.regions.shape;
|
|||||||
|
|
||||||
import com.sk89q.worldedit.EditSession;
|
import com.sk89q.worldedit.EditSession;
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
|
||||||
import com.sk89q.worldedit.internal.expression.ExpressionEnvironment;
|
import com.sk89q.worldedit.internal.expression.ExpressionEnvironment;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.math.MutableVector3;
|
import com.sk89q.worldedit.math.MutableVector3;
|
||||||
import com.sk89q.worldedit.math.Vector3;
|
import com.sk89q.worldedit.math.Vector3;
|
||||||
|
|
||||||
|
@ -34,16 +34,20 @@ import com.sk89q.worldedit.command.tool.SelectionWand;
|
|||||||
import com.sk89q.worldedit.command.tool.Tool;
|
import com.sk89q.worldedit.command.tool.Tool;
|
||||||
import com.sk89q.worldedit.entity.Player;
|
import com.sk89q.worldedit.entity.Player;
|
||||||
import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent;
|
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.request.Request;
|
||||||
import com.sk89q.worldedit.session.storage.JsonFileSessionStore;
|
import com.sk89q.worldedit.session.storage.JsonFileSessionStore;
|
||||||
import com.sk89q.worldedit.session.storage.SessionStore;
|
import com.sk89q.worldedit.session.storage.SessionStore;
|
||||||
import com.sk89q.worldedit.session.storage.VoidStore;
|
import com.sk89q.worldedit.session.storage.VoidStore;
|
||||||
import com.sk89q.worldedit.util.concurrency.EvenMoreExecutors;
|
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.util.eventbus.Subscribe;
|
||||||
import com.sk89q.worldedit.world.gamemode.GameModes;
|
import com.sk89q.worldedit.world.gamemode.GameModes;
|
||||||
import com.sk89q.worldedit.world.item.ItemType;
|
import com.sk89q.worldedit.world.item.ItemType;
|
||||||
import com.sk89q.worldedit.world.item.ItemTypes;
|
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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -53,9 +57,6 @@ import java.util.Timer;
|
|||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Session manager for WorldEdit.
|
* 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.BaseBlock;
|
||||||
import com.sk89q.worldedit.world.block.BlockState;
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||||
import java.util.List;
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class RequestExtent implements Extent {
|
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));
|
LocalSession.MAX_HISTORY_SIZE = Math.max(15, getInt("history-size", 15));
|
||||||
|
|
||||||
String snapshotsDir = getString("snapshots-dir", "");
|
String snapshotsDir = getString("snapshots-dir", "");
|
||||||
if (!snapshotsDir.isEmpty()) {
|
boolean experimentalSnapshots = getBool("snapshots-experimental", false);
|
||||||
snapshotRepo = new SnapshotRepository(snapshotsDir);
|
initializeSnapshotConfiguration(snapshotsDir, experimentalSnapshots);
|
||||||
}
|
|
||||||
|
|
||||||
path.getParentFile().mkdirs();
|
path.getParentFile().mkdirs();
|
||||||
try (OutputStream output = new FileOutputStream(path)) {
|
try (OutputStream output = new FileOutputStream(path)) {
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.util.collection;
|
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.google.common.collect.Iterators;
|
||||||
import com.sk89q.worldedit.WorldEdit;
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
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();
|
refInfo.lock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,6 @@
|
|||||||
package com.sk89q.worldedit.util.eventbus;
|
package com.sk89q.worldedit.util.eventbus;
|
||||||
|
|
||||||
import com.google.common.collect.HashMultimap;
|
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.Multimap;
|
||||||
import com.google.common.collect.SetMultimap;
|
import com.google.common.collect.SetMultimap;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -37,6 +35,8 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
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
|
* Dispatches events to listeners, and provides ways for listeners to register
|
||||||
* themselves.
|
* themselves.
|
||||||
|
@ -47,6 +47,7 @@ public class TextUtils {
|
|||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a Java Locale object by the Minecraft locale tag.
|
* 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;
|
||||||
|
|
||||||
|
}
|
@ -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;
|
package com.sk89q.worldedit.util.translation;
|
||||||
|
|
||||||
import static java.util.stream.Collectors.toMap;
|
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
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.Component;
|
||||||
import com.sk89q.worldedit.util.formatting.text.renderer.FriendlyComponentRenderer;
|
import com.sk89q.worldedit.util.formatting.text.renderer.FriendlyComponentRenderer;
|
||||||
import com.sk89q.worldedit.util.io.ResourceLoader;
|
import com.sk89q.worldedit.util.io.ResourceLoader;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -43,6 +42,8 @@ import java.util.Optional;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.toMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles translations for the plugin.
|
* Handles translations for the plugin.
|
||||||
*
|
*
|
||||||
|
@ -153,6 +153,7 @@ public abstract class AbstractWorld implements World {
|
|||||||
@Override
|
@Override
|
||||||
public void setWeather(WeatherType weatherType, long duration) {
|
public void setWeather(WeatherType weatherType, long duration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class QueuedEffect implements Comparable<QueuedEffect> {
|
private class QueuedEffect implements Comparable<QueuedEffect> {
|
||||||
private final Vector3 position;
|
private final Vector3 position;
|
||||||
private final BlockType blockType;
|
private final BlockType blockType;
|
||||||
|
@ -69,6 +69,7 @@ public class NullWorld extends AbstractWorld {
|
|||||||
public String getId() {
|
public String getId() {
|
||||||
return "null";
|
return "null";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block, boolean notifyAndLight) throws WorldEditException {
|
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block, boolean notifyAndLight) throws WorldEditException {
|
||||||
return false;
|
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.BlockState;
|
||||||
import com.sk89q.worldedit.world.block.BlockType;
|
import com.sk89q.worldedit.world.block.BlockType;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A block registry that uses {@link BundledBlockData} to serve information
|
* A block registry that uses {@link BundledBlockData} to serve information
|
||||||
* about blocks.
|
* about blocks.
|
||||||
|
@ -56,15 +56,7 @@ import java.util.Map;
|
|||||||
public final class LegacyMapper {
|
public final class LegacyMapper {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(LegacyMapper.class);
|
private static final Logger log = LoggerFactory.getLogger(LegacyMapper.class);
|
||||||
private static LegacyMapper INSTANCE = new LegacyMapper();
|
private static LegacyMapper INSTANCE;
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
INSTANCE.loadFromResource();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
log.warn("Failed to load the built-in legacy id registry", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Int2ObjectArrayMap<Integer> blockStateToLegacyId4Data = new Int2ObjectArrayMap<>();
|
private final Int2ObjectArrayMap<Integer> blockStateToLegacyId4Data = new Int2ObjectArrayMap<>();
|
||||||
private final Int2ObjectArrayMap<Integer> extraId4DataToStateId = new Int2ObjectArrayMap<>();
|
private final Int2ObjectArrayMap<Integer> extraId4DataToStateId = new Int2ObjectArrayMap<>();
|
||||||
@ -80,6 +72,12 @@ public final class LegacyMapper {
|
|||||||
* Create a new instance.
|
* Create a new instance.
|
||||||
*/
|
*/
|
||||||
private LegacyMapper() {
|
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) {
|
if (url == null) {
|
||||||
throw new IOException("Could not find legacy.json");
|
throw new IOException("Could not find legacy.json");
|
||||||
}
|
}
|
||||||
String source = Resources.toString(url, Charset.defaultCharset());
|
String data = Resources.toString(url, Charset.defaultCharset());
|
||||||
LegacyDataFile dataFile = gson.fromJson(source, new TypeToken<LegacyDataFile>() {
|
LegacyDataFile dataFile = gson.fromJson(data, new TypeToken<LegacyDataFile>() {}.getType());
|
||||||
}.getType());
|
|
||||||
|
|
||||||
DataFixer fixer = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataFixer();
|
DataFixer fixer = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataFixer();
|
||||||
ParserContext parserContext = new ParserContext();
|
ParserContext parserContext = new ParserContext();
|
||||||
@ -110,49 +107,51 @@ public final class LegacyMapper {
|
|||||||
Integer combinedId = getCombinedId(blockEntry.getKey());
|
Integer combinedId = getCombinedId(blockEntry.getKey());
|
||||||
final String value = blockEntry.getValue();
|
final String value = blockEntry.getValue();
|
||||||
blockEntries.put(id, value);
|
blockEntries.put(id, value);
|
||||||
BlockState blockState = null;
|
|
||||||
|
BlockState state = null;
|
||||||
try {
|
try {
|
||||||
blockState = BlockState.get(null, blockEntry.getValue());
|
state = BlockState.get(null, blockEntry.getValue());
|
||||||
BlockType type = blockState.getBlockType();
|
BlockType type = state.getBlockType();
|
||||||
if (type.hasProperty(PropertyKey.WATERLOGGED)) {
|
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();
|
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) {
|
if (fixer != null) {
|
||||||
try {
|
try {
|
||||||
String newEntry = fixer.fixUp(DataFixer.FixTypes.BLOCK_STATE, value, 1631);
|
String newEntry = fixer.fixUp(DataFixer.FixTypes.BLOCK_STATE, value, 1631);
|
||||||
blockState = blockFactory.parseFromInput(newEntry, parserContext).toImmutableState();
|
state = blockFactory.parseFromInput(newEntry, parserContext).toImmutableState();
|
||||||
} catch (InputParseException f) {
|
} catch (InputParseException e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if it's still null, the fixer was unavailable or failed
|
// if it's still null, the fixer was unavailable or failed
|
||||||
if (blockState == null) {
|
if (state == null) {
|
||||||
try {
|
try {
|
||||||
blockState = blockFactory.parseFromInput(value, parserContext).toImmutableState();
|
state = blockFactory.parseFromInput(value, parserContext).toImmutableState();
|
||||||
} catch (InputParseException f) {
|
} catch (InputParseException e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if it's still null, both fixer and default failed
|
// if it's still null, both fixer and default failed
|
||||||
if (blockState == null) {
|
if (state == null) {
|
||||||
log.debug("Unknown block: " + value);
|
log.debug("Unknown block: " + value);
|
||||||
} else {
|
} else {
|
||||||
// it's not null so one of them succeeded, now use it
|
// it's not null so one of them succeeded, now use it
|
||||||
blockToStringMap.put(blockState, id);
|
blockToStringMap.put(state, id);
|
||||||
stringToBlockMap.put(id, blockState);
|
stringToBlockMap.put(id, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (blockState != null) {
|
if (state != null) {
|
||||||
blockArr[combinedId] = blockState.getInternalId();
|
blockArr[combinedId] = state.getInternalId();
|
||||||
blockStateToLegacyId4Data.put(blockState.getInternalId(), (Integer) combinedId);
|
blockStateToLegacyId4Data.put(state.getInternalId(), (Integer) combinedId);
|
||||||
blockStateToLegacyId4Data.putIfAbsent(blockState.getInternalBlockTypeId(), combinedId);
|
blockStateToLegacyId4Data.putIfAbsent(state.getInternalBlockTypeId(), combinedId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int id = 0; id < 256; id++) {
|
for (int id = 0; id < 256; id++) {
|
||||||
int combinedId = id << 4;
|
int combinedId = id << 4;
|
||||||
int base = blockArr[combinedId];
|
int base = blockArr[combinedId];
|
||||||
if (base != 0) {
|
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;
|
if (blockArr[combinedId] == 0) blockArr[combinedId] = base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,14 +165,14 @@ public final class LegacyMapper {
|
|||||||
value = fixer.fixUp(DataFixer.FixTypes.ITEM_TYPE, value, 1631);
|
value = fixer.fixUp(DataFixer.FixTypes.ITEM_TYPE, value, 1631);
|
||||||
type = ItemTypes.get(value);
|
type = ItemTypes.get(value);
|
||||||
}
|
}
|
||||||
if (type != null) {
|
if (type == null) {
|
||||||
|
log.debug("Unknown item: " + value);
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
itemMap.put(getCombinedId(id), type);
|
itemMap.put(getCombinedId(id), type);
|
||||||
continue;
|
} catch (Exception ignored) {
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.debug("Unknown item: " + value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,7 +288,10 @@ public final class LegacyMapper {
|
|||||||
return combinedId == null ? null : new int[] { combinedId >> 4, combinedId & 0xF };
|
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;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.world.registry;
|
package com.sk89q.worldedit.world.registry;
|
||||||
|
|
||||||
class SimpleItemMaterial implements ItemMaterial {
|
public class SimpleItemMaterial implements ItemMaterial {
|
||||||
|
|
||||||
private int maxStackSize;
|
private int maxStackSize;
|
||||||
private int maxDamage;
|
private int maxDamage;
|
@ -22,6 +22,7 @@
|
|||||||
package com.sk89q.worldedit.world.snapshot;
|
package com.sk89q.worldedit.world.snapshot;
|
||||||
|
|
||||||
import com.sk89q.worldedit.world.storage.MissingWorldException;
|
import com.sk89q.worldedit.world.storage.MissingWorldException;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
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.DataException;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
import com.sk89q.worldedit.world.chunk.Chunk;
|
import com.sk89q.worldedit.world.chunk.Chunk;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import com.sk89q.jnbt.CompoundTag;
|
|||||||
import com.sk89q.worldedit.math.BlockVector2;
|
import com.sk89q.worldedit.math.BlockVector2;
|
||||||
import com.sk89q.worldedit.world.DataException;
|
import com.sk89q.worldedit.world.DataException;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -20,8 +20,6 @@
|
|||||||
package com.sk89q.worldedit.world.storage;
|
package com.sk89q.worldedit.world.storage;
|
||||||
|
|
||||||
import com.sk89q.jnbt.CompoundTag;
|
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.math.BlockVector2;
|
||||||
import com.sk89q.worldedit.world.DataException;
|
import com.sk89q.worldedit.world.DataException;
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
@ -67,19 +65,11 @@ public abstract class McRegionChunkStore extends ChunkStore {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompoundTag getChunkTag(BlockVector2 position, World world) throws DataException, IOException {
|
public CompoundTag getChunkTag(BlockVector2 position, World world) throws DataException, IOException {
|
||||||
|
return ChunkStoreHelper.readCompoundTag(() -> {
|
||||||
McRegionReader reader = getReader(position, world.getName());
|
McRegionReader reader = getReader(position, world.getName());
|
||||||
|
|
||||||
InputStream stream = reader.getChunkInputStream(position);
|
return 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist
Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren