mirror of
https://github.com/IntellectualSites/PlotSquared.git
synced 2024-12-27 17:28:28 +01:00
Feature/v6/arkitektonika (#2916)
* Start working on Arkitektonika support * Progress on Arkitektonika * Add license headers * Some QoL and javadocs * Fix maximum calculation * Fix minor formatting * Reimplement legacy webinterface support * Add documentation and fix deletion link * Resolve conflicts/gradle stuff * Fix links * Make message readable * Do not allow download of merged plots
This commit is contained in:
parent
c8ad936d26
commit
ff70d5db14
@ -63,6 +63,10 @@ dependencies {
|
||||
// Other libraries
|
||||
implementation("com.sk89q:squirrelid:1.0.0-SNAPSHOT") { isTransitive = false }
|
||||
|
||||
// Our libraries
|
||||
implementation("com.intellectualsites.arkitektonika:Arkitektonika-Client:2.0-SNAPSHOT")
|
||||
implementation("com.intellectualsites.http:HTTP4J:1.1-SNAPSHOT")
|
||||
|
||||
// Adventure
|
||||
implementation("net.kyori:adventure-platform-bukkit:4.0.0-SNAPSHOT")
|
||||
}
|
||||
@ -91,6 +95,8 @@ tasks.named<ShadowJar>("shadowJar") {
|
||||
relocate("javax.inject", "com.plotsquared.core.inject.javax")
|
||||
relocate("org.aopalliance", "com.plotsquared.core.aopalliance")
|
||||
relocate("com.intellectualsites.services", "com.plotsquared.core.services")
|
||||
relocate("com.intellectualsites.arkitektonika", "com.plotsquared.core.arkitektonika")
|
||||
relocate("com.intellectualsites.http", "com.plotsquared.core.http")
|
||||
|
||||
// Get rid of all the libs which are 100% unused.
|
||||
minimize()
|
||||
|
@ -45,6 +45,7 @@ dependencies {
|
||||
api("com.intellectualsites:Pipeline:1.4.0-SNAPSHOT") {
|
||||
exclude(group = "com.google.guava")
|
||||
}
|
||||
api("com.intellectualsites.arkitektonika:Arkitektonika-Client:2.0-SNAPSHOT")
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
|
@ -26,21 +26,21 @@
|
||||
package com.plotsquared.core.command;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.plotsquared.core.permissions.Permission;
|
||||
import com.plotsquared.core.configuration.Settings;
|
||||
import com.plotsquared.core.configuration.caption.StaticCaption;
|
||||
import com.plotsquared.core.configuration.caption.TranslatableCaption;
|
||||
import com.plotsquared.core.permissions.Permission;
|
||||
import com.plotsquared.core.player.PlotPlayer;
|
||||
import com.plotsquared.core.plot.Plot;
|
||||
import com.plotsquared.core.plot.flag.implementations.DoneFlag;
|
||||
import com.plotsquared.core.plot.world.PlotAreaManager;
|
||||
import com.plotsquared.core.util.Permissions;
|
||||
import com.plotsquared.core.util.PlotUploader;
|
||||
import com.plotsquared.core.util.SchematicHandler;
|
||||
import com.plotsquared.core.util.StringMan;
|
||||
import com.plotsquared.core.util.TabCompletions;
|
||||
import com.plotsquared.core.util.WorldUtil;
|
||||
import com.plotsquared.core.util.task.RunnableVal;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import net.kyori.adventure.text.minimessage.Template;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@ -60,13 +60,16 @@ import java.util.stream.Collectors;
|
||||
public class Download extends SubCommand {
|
||||
|
||||
private final PlotAreaManager plotAreaManager;
|
||||
private final SchematicHandler schematicHandler;
|
||||
private final PlotUploader plotUploader;
|
||||
@Nonnull private final SchematicHandler schematicHandler;
|
||||
private final WorldUtil worldUtil;
|
||||
|
||||
@Inject public Download(@Nonnull final PlotAreaManager plotAreaManager,
|
||||
@Nonnull final PlotUploader plotUploader,
|
||||
@Nonnull final SchematicHandler schematicHandler,
|
||||
@Nonnull final WorldUtil worldUtil) {
|
||||
this.plotAreaManager = plotAreaManager;
|
||||
this.plotUploader = plotUploader;
|
||||
this.schematicHandler = schematicHandler;
|
||||
this.worldUtil = worldUtil;
|
||||
}
|
||||
@ -96,6 +99,10 @@ public class Download extends SubCommand {
|
||||
player.sendMessage(TranslatableCaption.of("permission.no_plot_perms"));
|
||||
return false;
|
||||
}
|
||||
if (plot.isMerged()) {
|
||||
player.sendMessage(TranslatableCaption.of("web.plot_merged"));
|
||||
return false;
|
||||
}
|
||||
if (plot.getRunning() > 0) {
|
||||
player.sendMessage(TranslatableCaption.of("errors.wait_for_timer"));
|
||||
return false;
|
||||
@ -103,27 +110,11 @@ public class Download extends SubCommand {
|
||||
if (args.length == 0 || (args.length == 1 && StringMan
|
||||
.isEqualIgnoreCaseToAny(args[0], "sch", "schem", "schematic"))) {
|
||||
if (plot.getVolume() > Integer.MAX_VALUE) {
|
||||
player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large"));
|
||||
player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large"));
|
||||
return false;
|
||||
}
|
||||
plot.addRunning();
|
||||
this.schematicHandler.getCompoundTag(plot, new RunnableVal<CompoundTag>() {
|
||||
@Override public void run(CompoundTag value) {
|
||||
plot.removeRunning();
|
||||
schematicHandler.upload(value, null, null, new RunnableVal<URL>() {
|
||||
@Override public void run(URL url) {
|
||||
if (url == null) {
|
||||
player.sendMessage(TranslatableCaption.of("web.generating_link_failed"));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(
|
||||
TranslatableCaption.of("web.generation_link_success"),
|
||||
Template.of("url", url.toString())
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
upload(player, plot);
|
||||
} else if (args.length == 1 && StringMan
|
||||
.isEqualIgnoreCaseToAny(args[0], "mcr", "world", "mca")) {
|
||||
if (!Permissions.hasPermission(player, Permission.PERMISSION_DOWNLOAD_WORLD)) {
|
||||
@ -153,6 +144,7 @@ public class Download extends SubCommand {
|
||||
player.sendMessage(TranslatableCaption.of("web.generating_link"));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Command> tab(final PlotPlayer<?> player, final String[] args, final boolean space) {
|
||||
if (args.length == 1) {
|
||||
@ -173,4 +165,36 @@ public class Download extends SubCommand {
|
||||
}
|
||||
return TabCompletions.completePlayers(String.join(",", args).trim(), Collections.emptyList());
|
||||
}
|
||||
|
||||
private void upload(PlotPlayer<?> player, Plot plot) {
|
||||
if (Settings.Web.LEGACY_WEBINTERFACE) {
|
||||
schematicHandler
|
||||
.getCompoundTag(plot)
|
||||
.whenComplete((compoundTag, throwable) -> {
|
||||
schematicHandler.upload(compoundTag, null, null, new RunnableVal<URL>() {
|
||||
@Override
|
||||
public void run(URL value) {
|
||||
player.sendMessage(
|
||||
TranslatableCaption.of("web.generation_link_success"),
|
||||
Template.of("download", value.toString()),
|
||||
Template.of("delete", "Not available"));
|
||||
player.sendMessage(StaticCaption.of(value.toString()));
|
||||
}
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
// TODO legacy support
|
||||
this.plotUploader.upload(plot)
|
||||
.whenComplete((result, throwable) -> {
|
||||
if (throwable != null || !result.isSuccess()) {
|
||||
player.sendMessage(TranslatableCaption.of("web.generating_link_failed"));
|
||||
} else {
|
||||
player.sendMessage(
|
||||
TranslatableCaption.of("web.generation_link_success"),
|
||||
Template.of("download", result.getDownloadUrl()),
|
||||
Template.of("delete", result.getDeletionUrl()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,10 @@ public class MainCommand extends Command {
|
||||
final List<Class<? extends Command>> commands = new LinkedList<>();
|
||||
commands.add(Caps.class);
|
||||
commands.add(Buy.class);
|
||||
commands.add(Save.class);
|
||||
if (Settings.Web.LEGACY_WEBINTERFACE) {
|
||||
logger.warn("Legacy webinterface is used. Please note that it will be removed in future.");
|
||||
commands.add(Save.class);
|
||||
}
|
||||
commands.add(Load.class);
|
||||
commands.add(Confirm.class);
|
||||
commands.add(Template.class);
|
||||
|
@ -86,36 +86,36 @@ public class Save extends SubCommand {
|
||||
return false;
|
||||
}
|
||||
plot.addRunning();
|
||||
this.schematicHandler.getCompoundTag(plot, new RunnableVal<CompoundTag>() {
|
||||
@Override public void run(final CompoundTag value) {
|
||||
TaskManager.runTaskAsync(() -> {
|
||||
String time = (System.currentTimeMillis() / 1000) + "";
|
||||
Location[] corners = plot.getCorners();
|
||||
corners[0] = corners[0].withY(0);
|
||||
corners[1] = corners[1].withY(255);
|
||||
int size = (corners[1].getX() - corners[0].getX()) + 1;
|
||||
PlotId id = plot.getId();
|
||||
String world1 = plot.getArea().toString().replaceAll(";", "-")
|
||||
.replaceAll("[^A-Za-z0-9]", "");
|
||||
final String file = time + '_' + world1 + '_' + id.getX() + '_' + id.getY() + '_' + size;
|
||||
UUID uuid = player.getUUID();
|
||||
schematicHandler.upload(value, uuid, file, new RunnableVal<URL>() {
|
||||
@Override public void run(URL url) {
|
||||
plot.removeRunning();
|
||||
if (url == null) {
|
||||
player.sendMessage(TranslatableCaption.of("backups.backup_save_failed"));
|
||||
return;
|
||||
this.schematicHandler.getCompoundTag(plot)
|
||||
.whenComplete((compoundTag, throwable) -> {
|
||||
TaskManager.runTaskAsync(() -> {
|
||||
String time = (System.currentTimeMillis() / 1000) + "";
|
||||
Location[] corners = plot.getCorners();
|
||||
corners[0] = corners[0].withY(0);
|
||||
corners[1] = corners[1].withY(255);
|
||||
int size = (corners[1].getX() - corners[0].getX()) + 1;
|
||||
PlotId id = plot.getId();
|
||||
String world1 = plot.getArea().toString().replaceAll(";", "-")
|
||||
.replaceAll("[^A-Za-z0-9]", "");
|
||||
final String file = time + '_' + world1 + '_' + id.getX() + '_' + id.getY() + '_' + size;
|
||||
UUID uuid = player.getUUID();
|
||||
schematicHandler.upload(compoundTag, uuid, file, new RunnableVal<URL>() {
|
||||
@Override
|
||||
public void run(URL url) {
|
||||
plot.removeRunning();
|
||||
if (url == null) {
|
||||
player.sendMessage(TranslatableCaption.of("backups.backup_save_failed"));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(TranslatableCaption.of("web.save_success"));
|
||||
try (final MetaDataAccess<List<String>> schematicAccess =
|
||||
player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_SCHEMATICS)) {
|
||||
schematicAccess.get().ifPresent(schematics -> schematics.add(file + ".schem"));
|
||||
}
|
||||
}
|
||||
player.sendMessage(TranslatableCaption.of("web.save_success"));
|
||||
try (final MetaDataAccess<List<String>> schematicAccess =
|
||||
player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_SCHEMATICS)) {
|
||||
schematicAccess.get().ifPresent(schematics -> schematics.add(file + ".schem"));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -409,12 +409,31 @@ public class Settings extends Config {
|
||||
}
|
||||
|
||||
|
||||
@Deprecated
|
||||
@Comment("Schematic interface related settings")
|
||||
public static class Web {
|
||||
@Comment({"The web interface for schematics", " - All schematics are anonymous and private",
|
||||
" - Downloads can be deleted by the user",
|
||||
" - Supports plot uploads, downloads and saves",}) public static String URL =
|
||||
"https://schem.intellectualsites.com/plots/";
|
||||
@Comment({"Whether or not the legacy web interface will be used for /plot download and /plot save",
|
||||
"Note that this will be removed in future versions. Updating to Arkitektonika is highly suggested"})
|
||||
public static boolean LEGACY_WEBINTERFACE = false;
|
||||
}
|
||||
|
||||
@Comment("Schematic web interface related settings")
|
||||
public static class Arkitektonika {
|
||||
|
||||
@Comment("The url of the backend server (Arkitektonika)")
|
||||
public static String BACKEND_URL = "https://ark.jacobandersen.dev/";
|
||||
|
||||
@Comment({"The url used to generate a download link from.",
|
||||
"{key} will be replaced with the generated key"})
|
||||
public static String DOWNLOAD_URL = "https://sw.jacobandersen.dev/download/{key}";
|
||||
|
||||
@Comment({"The url used to generate a deletion link from.",
|
||||
"{key} will be replaced with the generated key"})
|
||||
public static String DELETE_URL = "https://sw.jacobandersen.dev/delete/{key}";
|
||||
}
|
||||
|
||||
|
||||
|
@ -504,27 +504,25 @@ public class HybridUtils {
|
||||
int tz = sz - 1;
|
||||
int ty = get_ey(plotManager, queue, sx, ex, bz, tz, sy);
|
||||
|
||||
Set<CuboidRegion> sideRoad = new HashSet<>(Collections.singletonList(RegionUtil.createRegion(sx, ex, sy, ey, sz, ez)));
|
||||
final Set<CuboidRegion> intersection = new HashSet<>(Collections.singletonList(RegionUtil.createRegion(sx, ex, sy, ty, bz, tz)));
|
||||
final Set<CuboidRegion> sideRoad = Collections.singleton(RegionUtil.createRegion(sx, ex, sy, ey, sz, ez));
|
||||
final Set<CuboidRegion> intersection = Collections.singleton(RegionUtil.createRegion(sx, ex, sy, ty, bz, tz));
|
||||
|
||||
final String dir = "schematics" + File.separator + "GEN_ROAD_SCHEMATIC" + File.separator + plot.getArea().toString() + File.separator;
|
||||
|
||||
this.schematicHandler.getCompoundTag(world, sideRoad, new RunnableVal<CompoundTag>() {
|
||||
@Override public void run(CompoundTag value) {
|
||||
schematicHandler.save(value, dir + "sideroad.schem");
|
||||
schematicHandler.getCompoundTag(world, intersection, new RunnableVal<CompoundTag>() {
|
||||
@Override public void run(CompoundTag value) {
|
||||
schematicHandler.save(value, dir + "intersection.schem");
|
||||
plotworld.ROAD_SCHEMATIC_ENABLED = true;
|
||||
try {
|
||||
plotworld.setupSchematics();
|
||||
} catch (SchematicHandler.UnsupportedFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
this.schematicHandler.getCompoundTag(world, sideRoad)
|
||||
.whenComplete((compoundTag, throwable) -> {
|
||||
schematicHandler.save(compoundTag, dir + "sideroad.schem");
|
||||
schematicHandler.getCompoundTag(world, intersection)
|
||||
.whenComplete((c, t) -> {
|
||||
schematicHandler.save(c, dir + "intersection.schem");
|
||||
plotworld.ROAD_SCHEMATIC_ENABLED = true;
|
||||
try {
|
||||
plotworld.setupSchematics();
|
||||
} catch (SchematicHandler.UnsupportedFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
196
Core/src/main/java/com/plotsquared/core/util/PlotUploader.java
Normal file
196
Core/src/main/java/com/plotsquared/core/util/PlotUploader.java
Normal file
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* _____ _ _ _____ _
|
||||
* | __ \| | | | / ____| | |
|
||||
* | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| |
|
||||
* | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
|
||||
* | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| |
|
||||
* |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_|
|
||||
* | |
|
||||
* |_|
|
||||
* PlotSquared plot management system for Minecraft
|
||||
* Copyright (C) 2020 IntellectualSites
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.plotsquared.core.util;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.intellectualsites.arkitektonika.Arkitektonika;
|
||||
import com.intellectualsites.arkitektonika.SchematicKeys;
|
||||
import com.plotsquared.core.PlotSquared;
|
||||
import com.plotsquared.core.configuration.Settings;
|
||||
import com.plotsquared.core.plot.Plot;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.NBTOutputStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* This class handles communication with the Arkitektonika REST service.
|
||||
*/
|
||||
public class PlotUploader {
|
||||
private static final Logger logger = LoggerFactory.getLogger("P2/" + PlotUploader.class.getSimpleName());
|
||||
private static final Path TEMP_DIR = Paths.get(PlotSquared.platform().getDirectory().getPath());
|
||||
private final SchematicHandler schematicHandler;
|
||||
private final Arkitektonika arkitektonika;
|
||||
|
||||
/**
|
||||
* Create a new PlotUploader instance that uses the given schematic handler to create
|
||||
* schematics of plots.
|
||||
*
|
||||
* @param schematicHandler the handler to create schematics of plots.
|
||||
*/
|
||||
@Inject
|
||||
public PlotUploader(@Nonnull final SchematicHandler schematicHandler) {
|
||||
this.schematicHandler = schematicHandler;
|
||||
this.arkitektonika = Arkitektonika.builder().withUrl(Settings.Arkitektonika.BACKEND_URL).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a plot and retrieve a result. The plot will be saved into a temporary
|
||||
* schematic file and uploaded to the REST service
|
||||
* specified by {@link Settings.Arkitektonika#BACKEND_URL}.
|
||||
*
|
||||
* @param plot The plot to upload
|
||||
* @return a {@link CompletableFuture} that provides a {@link PlotUploadResult} if finished.
|
||||
*/
|
||||
public CompletableFuture<PlotUploadResult> upload(@Nonnull final Plot plot) {
|
||||
return this.schematicHandler.getCompoundTag(plot)
|
||||
.handle((tag, t) -> {
|
||||
plot.removeRunning();
|
||||
return tag;
|
||||
})
|
||||
.thenApply(this::writeToTempFile)
|
||||
.thenApply(this::uploadAndDelete)
|
||||
.thenApply(this::wrapIntoResult);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private PlotUploadResult wrapIntoResult(@Nullable final SchematicKeys schematicKeys) {
|
||||
if (schematicKeys == null) {
|
||||
return PlotUploadResult.failed();
|
||||
}
|
||||
String download = Settings.Arkitektonika.DOWNLOAD_URL.replace("{key}", schematicKeys.getAccessKey());
|
||||
String delete = Settings.Arkitektonika.DELETE_URL.replace("{key}", schematicKeys.getDeletionKey());
|
||||
return PlotUploadResult.success(download, delete);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SchematicKeys uploadAndDelete(@Nonnull final Path file) {
|
||||
try {
|
||||
final CompletableFuture<SchematicKeys> upload = this.arkitektonika.upload(file.toFile());
|
||||
return upload.join();
|
||||
} catch (CompletionException e) {
|
||||
logger.error("Failed to upload schematic", e);
|
||||
return null;
|
||||
} finally {
|
||||
try {
|
||||
Files.delete(file);
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to delete temporary file {}", file, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private Path writeToTempFile(@Nonnull final CompoundTag schematic) {
|
||||
try {
|
||||
final Path tempFile = Files.createTempFile(TEMP_DIR, null, null);
|
||||
try (final OutputStream stream = Files.newOutputStream(tempFile)) {
|
||||
writeSchematic(schematic, stream);
|
||||
}
|
||||
return tempFile;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a schematic provided as CompoundTag to an OutputStream.
|
||||
*
|
||||
* @param schematic The schematic to write to the stream
|
||||
* @param stream The stream to write the schematic to
|
||||
* @throws IOException if an I/O error occurred
|
||||
*/
|
||||
private void writeSchematic(@Nonnull final CompoundTag schematic, @Nonnull final OutputStream stream)
|
||||
throws IOException {
|
||||
try (final NBTOutputStream nbtOutputStream = new NBTOutputStream(new GZIPOutputStream(stream))) {
|
||||
nbtOutputStream.writeNamedTag("Schematic", schematic);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A result of a plot upload process.
|
||||
*/
|
||||
public static class PlotUploadResult {
|
||||
private final boolean success;
|
||||
private final String downloadUrl;
|
||||
private final String deletionUrl;
|
||||
|
||||
private PlotUploadResult(boolean success, @Nullable final String downloadUrl,
|
||||
@Nullable final String deletionUrl) {
|
||||
this.success = success;
|
||||
this.downloadUrl = downloadUrl;
|
||||
this.deletionUrl = deletionUrl;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static PlotUploadResult success(@Nonnull final String downloadUrl, @Nullable final String deletionUrl) {
|
||||
return new PlotUploadResult(true, downloadUrl, deletionUrl);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static PlotUploadResult failed() {
|
||||
return new PlotUploadResult(false, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this result is a success.
|
||||
*
|
||||
* @return {@code true} if this is a sucessful result, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url that can be used to download the uploaded plot schematic.
|
||||
*
|
||||
* @return The url to download the schematic.
|
||||
*/
|
||||
public String getDownloadUrl() {
|
||||
return downloadUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url that can be used to delete the uploaded plot schematic.
|
||||
*
|
||||
* @return The url to delete the schematic.
|
||||
*/
|
||||
public String getDeletionUrl() {
|
||||
return deletionUrl;
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import com.sk89q.worldedit.regions.CuboidRegion;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class RegionUtil {
|
||||
|
||||
@ -45,31 +46,33 @@ public class RegionUtil {
|
||||
}
|
||||
|
||||
@Nonnull public static Location[] getCorners(String world, Collection<CuboidRegion> regions) {
|
||||
Location min = null;
|
||||
Location max = null;
|
||||
for (CuboidRegion region : regions) {
|
||||
Location[] corners = getCorners(world, region);
|
||||
if (min == null) {
|
||||
min = corners[0];
|
||||
max = corners[1];
|
||||
continue;
|
||||
}
|
||||
Location pos1 = corners[0];
|
||||
Location pos2 = corners[1];
|
||||
if (pos2.getX() > max.getX()) {
|
||||
max = max.withX(pos2.getX());
|
||||
}
|
||||
if (pos1.getX() < min.getX()) {
|
||||
min = min.withX(pos1.getX());
|
||||
}
|
||||
if (pos2.getZ() > max.getZ()) {
|
||||
max = max.withZ(pos2.getZ());
|
||||
}
|
||||
if (pos1.getZ() < min.getZ()) {
|
||||
min = min.withZ(pos1.getZ());
|
||||
}
|
||||
CuboidRegion aabb = getAxisAlignedBoundingBox(regions);
|
||||
return getCorners(world, aabb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a minimum {@link CuboidRegion} containing all given regions.
|
||||
*
|
||||
* @param regions The regions the bounding box should contain.
|
||||
* @return a CuboidRegion that contains all given regions.
|
||||
*/
|
||||
@Nonnull
|
||||
public static CuboidRegion getAxisAlignedBoundingBox(Iterable<CuboidRegion> regions) {
|
||||
Iterator<CuboidRegion> iterator = regions.iterator();
|
||||
if (!iterator.hasNext()) {
|
||||
throw new IllegalArgumentException("No regions given");
|
||||
}
|
||||
return new Location[] {min, max};
|
||||
CuboidRegion next = iterator.next();
|
||||
BlockVector3 min = next.getMinimumPoint();
|
||||
BlockVector3 max = next.getMaximumPoint();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
next = iterator.next();
|
||||
// as max >= min, this is enough to check
|
||||
min = min.getMinimum(next.getMinimumPoint());
|
||||
max = max.getMaximum(next.getMaximumPoint());
|
||||
}
|
||||
return new CuboidRegion(min, max);
|
||||
}
|
||||
|
||||
public static CuboidRegion createRegion(int pos1x, int pos2x, int pos1z, int pos2z) {
|
||||
|
@ -42,7 +42,7 @@ import com.plotsquared.core.queue.QueueCoordinator;
|
||||
import com.plotsquared.core.util.net.AbstractDelegateOutputStream;
|
||||
import com.plotsquared.core.util.task.RunnableVal;
|
||||
import com.plotsquared.core.util.task.TaskManager;
|
||||
import com.plotsquared.core.util.task.TaskTime;
|
||||
import com.plotsquared.core.util.task.YieldRunnable;
|
||||
import com.sk89q.jnbt.ByteArrayTag;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.IntArrayTag;
|
||||
@ -90,6 +90,8 @@ import java.net.URLConnection;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -99,11 +101,12 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
@ -111,6 +114,7 @@ public abstract class SchematicHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger("P2/" + SchematicHandler.class.getSimpleName());
|
||||
private static final Gson GSON = new Gson();
|
||||
private static final Path TEMP_DIR = Paths.get("TODO-PATH");
|
||||
public static SchematicHandler manager;
|
||||
private final WorldUtil worldUtil;
|
||||
private boolean exportAll = false;
|
||||
@ -121,6 +125,7 @@ public abstract class SchematicHandler {
|
||||
this.subscriberFactory = subscriberFactory;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void upload(@Nullable UUID uuid,
|
||||
@Nullable final String file,
|
||||
@Nonnull final String extension,
|
||||
@ -237,19 +242,18 @@ public abstract class SchematicHandler {
|
||||
}
|
||||
|
||||
final Runnable THIS = this;
|
||||
getCompoundTag(plot, new RunnableVal<CompoundTag>() {
|
||||
@Override public void run(final CompoundTag value) {
|
||||
if (value != null) {
|
||||
TaskManager.runTaskAsync(() -> {
|
||||
boolean result = save(value, directory + File.separator + name + ".schem");
|
||||
if (!result) {
|
||||
logger.error("Failed to save {}", plot.getId());
|
||||
}
|
||||
TaskManager.runTask(THIS);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
getCompoundTag(plot)
|
||||
.whenComplete((compoundTag, throwable) -> {
|
||||
if (compoundTag != null) {
|
||||
TaskManager.runTaskAsync(() -> {
|
||||
boolean result = save(compoundTag, directory + File.separator + name + ".schem");
|
||||
if (!result) {
|
||||
logger.error("Failed to save {}", plot.getId());
|
||||
}
|
||||
TaskManager.runTask(THIS);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return true;
|
||||
@ -487,6 +491,7 @@ public abstract class SchematicHandler {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void upload(final CompoundTag tag, UUID uuid, String file, RunnableVal<URL> whenDone) {
|
||||
if (tag == null) {
|
||||
TaskManager.runTask(whenDone);
|
||||
@ -529,37 +534,85 @@ public abstract class SchematicHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void getCompoundTag(final String world, final Set<CuboidRegion> regions, final RunnableVal<CompoundTag> whenDone) {
|
||||
// async
|
||||
private void writeSchematicData(@Nonnull final Map<String, Tag> schematic,
|
||||
@Nonnull final Map<String, Integer> palette,
|
||||
@Nonnull final Map<String, Integer> biomePalette,
|
||||
@Nonnull final List<CompoundTag> tileEntities,
|
||||
@Nonnull final ByteArrayOutputStream buffer,
|
||||
@Nonnull final ByteArrayOutputStream biomeBuffer) {
|
||||
schematic.put("PaletteMax", new IntTag(palette.size()));
|
||||
|
||||
Map<String, Tag> paletteTag = new HashMap<>();
|
||||
palette.forEach((key, value) -> paletteTag.put(key, new IntTag(value)));
|
||||
|
||||
schematic.put("Palette", new CompoundTag(paletteTag));
|
||||
schematic.put("BlockData", new ByteArrayTag(buffer.toByteArray()));
|
||||
schematic.put("TileEntities", new ListTag(CompoundTag.class, tileEntities));
|
||||
|
||||
schematic.put("BiomePaletteMax", new IntTag(biomePalette.size()));
|
||||
|
||||
Map<String, Tag> biomePaletteTag = new HashMap<>();
|
||||
biomePalette.forEach((key, value) -> biomePaletteTag.put(key, new IntTag(value)));
|
||||
|
||||
schematic.put("BiomePalette", new CompoundTag(biomePaletteTag));
|
||||
schematic.put("BiomeData", new ByteArrayTag(biomeBuffer.toByteArray()));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private Map<String, Tag> initSchematic(short width, short height, short length) {
|
||||
Map<String, Tag> schematic = new HashMap<>();
|
||||
schematic.put("Version", new IntTag(2));
|
||||
schematic.put("DataVersion",
|
||||
new IntTag(WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataVersion()));
|
||||
|
||||
Map<String, Tag> metadata = new HashMap<>();
|
||||
metadata.put("WEOffsetX", new IntTag(0));
|
||||
metadata.put("WEOffsetY", new IntTag(0));
|
||||
metadata.put("WEOffsetZ", new IntTag(0));
|
||||
|
||||
schematic.put("Metadata", new CompoundTag(metadata));
|
||||
|
||||
schematic.put("Width", new ShortTag(width));
|
||||
schematic.put("Height", new ShortTag(height));
|
||||
schematic.put("Length", new ShortTag(length));
|
||||
|
||||
// The Sponge format Offset refers to the 'min' points location in the world. That's our 'Origin'
|
||||
schematic.put("Offset", new IntArrayTag(new int[] {0, 0, 0,}));
|
||||
return schematic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the given plot as {@link CompoundTag} matching the Sponge schematic format.
|
||||
*
|
||||
* @param plot The plot to get the contents from.
|
||||
* @return a {@link CompletableFuture} that provides the created {@link CompoundTag}.
|
||||
*/
|
||||
public CompletableFuture<CompoundTag> getCompoundTag(@Nonnull final Plot plot) {
|
||||
return getCompoundTag(Objects.requireNonNull(plot.getWorldName()), plot.getRegions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of the given regions in the given world as {@link CompoundTag}
|
||||
* matching the Sponge schematic format.
|
||||
*
|
||||
* @param worldName The world to get the contents from.
|
||||
* @param regions The regions to get the contents from.
|
||||
* @return a {@link CompletableFuture} that provides the created {@link CompoundTag}.
|
||||
*/
|
||||
@Nonnull
|
||||
public CompletableFuture<CompoundTag> getCompoundTag(@Nonnull final String worldName,
|
||||
@Nonnull final Set<CuboidRegion> regions) {
|
||||
CompletableFuture<CompoundTag> completableFuture = new CompletableFuture<>();
|
||||
TaskManager.runTaskAsync(() -> {
|
||||
// Main positions
|
||||
Location[] corners = RegionUtil.getCorners(world, regions);
|
||||
final Location bot = corners[0];
|
||||
final Location top = corners[1];
|
||||
CuboidRegion aabb = RegionUtil.getAxisAlignedBoundingBox(regions);
|
||||
aabb.setWorld(this.worldUtil.getWeWorld(worldName));
|
||||
|
||||
CuboidRegion cuboidRegion = new CuboidRegion(this.worldUtil.getWeWorld(world), bot.getBlockVector3(), top.getBlockVector3());
|
||||
final int width = aabb.getWidth();
|
||||
int height = aabb.getHeight();
|
||||
final int length = aabb.getLength();
|
||||
|
||||
final int width = cuboidRegion.getWidth();
|
||||
int height = cuboidRegion.getHeight();
|
||||
final int length = cuboidRegion.getLength();
|
||||
Map<String, Tag> schematic = new HashMap<>();
|
||||
schematic.put("Version", new IntTag(2));
|
||||
schematic.put("DataVersion",
|
||||
new IntTag(WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataVersion()));
|
||||
|
||||
Map<String, Tag> metadata = new HashMap<>();
|
||||
metadata.put("WEOffsetX", new IntTag(0));
|
||||
metadata.put("WEOffsetY", new IntTag(0));
|
||||
metadata.put("WEOffsetZ", new IntTag(0));
|
||||
|
||||
schematic.put("Metadata", new CompoundTag(metadata));
|
||||
|
||||
schematic.put("Width", new ShortTag((short) width));
|
||||
schematic.put("Height", new ShortTag((short) height));
|
||||
schematic.put("Length", new ShortTag((short) length));
|
||||
|
||||
// The Sponge format Offset refers to the 'min' points location in the world. That's our 'Origin'
|
||||
schematic.put("Offset", new IntArrayTag(new int[] {0, 0, 0,}));
|
||||
Map<String, Tag> schematic = initSchematic((short) width, (short) height, (short) length);
|
||||
|
||||
Map<String, Integer> palette = new HashMap<>();
|
||||
Map<String, Integer> biomePalette = new HashMap<>();
|
||||
@ -573,147 +626,110 @@ public abstract class SchematicHandler {
|
||||
@Override public void run() {
|
||||
if (queue.isEmpty()) {
|
||||
TaskManager.runTaskAsync(() -> {
|
||||
schematic.put("PaletteMax", new IntTag(palette.size()));
|
||||
|
||||
Map<String, Tag> paletteTag = new HashMap<>();
|
||||
palette.forEach((key, value) -> paletteTag.put(key, new IntTag(value)));
|
||||
|
||||
schematic.put("Palette", new CompoundTag(paletteTag));
|
||||
schematic.put("BlockData", new ByteArrayTag(buffer.toByteArray()));
|
||||
schematic.put("TileEntities", new ListTag(CompoundTag.class, tileEntities));
|
||||
|
||||
schematic.put("BiomePaletteMax", new IntTag(biomePalette.size()));
|
||||
|
||||
Map<String, Tag> biomePaletteTag = new HashMap<>();
|
||||
biomePalette.forEach((key, value) -> biomePaletteTag.put(key, new IntTag(value)));
|
||||
|
||||
schematic.put("BiomePalette", new CompoundTag(biomePaletteTag));
|
||||
schematic.put("BiomeData", new ByteArrayTag(biomeBuffer.toByteArray()));
|
||||
whenDone.value = new CompoundTag(schematic);
|
||||
TaskManager.runTask(whenDone);
|
||||
writeSchematicData(schematic, palette, biomePalette, tileEntities, buffer, biomeBuffer);
|
||||
completableFuture.complete(new CompoundTag(schematic));
|
||||
});
|
||||
return;
|
||||
}
|
||||
final Runnable regionTask = this;
|
||||
CuboidRegion region = queue.poll();
|
||||
|
||||
final Location pos1 = Location.at(world, region.getMinimumPoint());
|
||||
final Location pos2 = Location.at(world, region.getMaximumPoint());
|
||||
final BlockVector3 minimum = region.getMinimumPoint();
|
||||
final BlockVector3 maximum = region.getMaximumPoint();
|
||||
|
||||
final int p1x = pos1.getX();
|
||||
final int sy = pos1.getY();
|
||||
final int p1z = pos1.getZ();
|
||||
final int p2x = pos2.getX();
|
||||
final int p2z = pos2.getZ();
|
||||
final int ey = pos2.getY();
|
||||
Iterator<Integer> yiter = IntStream.range(sy, ey + 1).iterator();
|
||||
final Runnable yTask = new Runnable() {
|
||||
final int minX = minimum.getX();
|
||||
final int minZ = minimum.getZ();
|
||||
final int minY = minimum.getY();
|
||||
|
||||
final int maxX = maximum.getX();
|
||||
final int maxZ = maximum.getZ();
|
||||
final int maxY = maximum.getY();
|
||||
|
||||
final Runnable yTask = new YieldRunnable() {
|
||||
int currentY = minY;
|
||||
int currentX = minX;
|
||||
int currentZ = minZ;
|
||||
@Override public void run() {
|
||||
long ystart = System.currentTimeMillis();
|
||||
while (yiter.hasNext() && System.currentTimeMillis() - ystart < 20) {
|
||||
final int y = yiter.next();
|
||||
Iterator<Integer> ziter = IntStream.range(p1z, p2z + 1).iterator();
|
||||
final Runnable zTask = new Runnable() {
|
||||
@Override public void run() {
|
||||
long zstart = System.currentTimeMillis();
|
||||
while (ziter.hasNext() && System.currentTimeMillis() - zstart < 20) {
|
||||
final int z = ziter.next();
|
||||
Iterator<Integer> xiter = IntStream.range(p1x, p2x + 1).iterator();
|
||||
final Runnable xTask = new Runnable() {
|
||||
@Override public void run() {
|
||||
long xstart = System.currentTimeMillis();
|
||||
final int ry = y - sy;
|
||||
final int rz = z - p1z;
|
||||
while (xiter.hasNext() && System.currentTimeMillis() - xstart < 20) {
|
||||
final int x = xiter.next();
|
||||
final int rx = x - p1x;
|
||||
BlockVector3 point = BlockVector3.at(x, y, z);
|
||||
BaseBlock block = cuboidRegion.getWorld().getFullBlock(point);
|
||||
if (block.getNbtData() != null) {
|
||||
Map<String, Tag> values = new HashMap<>();
|
||||
for (Map.Entry<String, Tag> entry : block.getNbtData().getValue().entrySet()) {
|
||||
values.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// Remove 'id' if it exists. We want 'Id'
|
||||
values.remove("id");
|
||||
|
||||
// Positions are kept in NBT, we don't want that.
|
||||
values.remove("x");
|
||||
values.remove("y");
|
||||
values.remove("z");
|
||||
|
||||
values.put("Id", new StringTag(block.getNbtId()));
|
||||
values.put("Pos", new IntArrayTag(new int[] {rx, ry, rz}));
|
||||
|
||||
tileEntities.add(new CompoundTag(values));
|
||||
}
|
||||
String blockKey = block.toImmutableState().getAsString();
|
||||
int blockId;
|
||||
if (palette.containsKey(blockKey)) {
|
||||
blockId = palette.get(blockKey);
|
||||
} else {
|
||||
blockId = palette.size();
|
||||
palette.put(blockKey, palette.size());
|
||||
}
|
||||
|
||||
while ((blockId & -128) != 0) {
|
||||
buffer.write(blockId & 127 | 128);
|
||||
blockId >>>= 7;
|
||||
}
|
||||
buffer.write(blockId);
|
||||
|
||||
if (ry > 0) {
|
||||
continue;
|
||||
}
|
||||
BlockVector2 pt = BlockVector2.at(x, z);
|
||||
BiomeType biome = cuboidRegion.getWorld().getBiome(pt);
|
||||
String biomeStr = biome.getId();
|
||||
int biomeId;
|
||||
if (biomePalette.containsKey(biomeStr)) {
|
||||
biomeId = biomePalette.get(biomeStr);
|
||||
} else {
|
||||
biomeId = biomePalette.size();
|
||||
biomePalette.put(biomeStr, biomeId);
|
||||
}
|
||||
while ((biomeId & -128) != 0) {
|
||||
biomeBuffer.write(biomeId & 127 | 128);
|
||||
biomeId >>>= 7;
|
||||
}
|
||||
biomeBuffer.write(biomeId);
|
||||
}
|
||||
if (xiter.hasNext()) {
|
||||
this.run();
|
||||
}
|
||||
}
|
||||
};
|
||||
xTask.run();
|
||||
long start = System.currentTimeMillis();
|
||||
for (; currentY <= maxY; currentY++) {
|
||||
int relativeY = currentY - minY;
|
||||
for (; currentZ <= maxZ; currentZ++) {
|
||||
int relativeZ = currentZ - minZ;
|
||||
for (; currentX <= maxX; currentX++) {
|
||||
// if too much time was spent here, we yield this task
|
||||
// note that current(X/Y/Z) aren't incremented, so the same position
|
||||
// as *right now* will be visited again
|
||||
if (System.currentTimeMillis() - start > 40) {
|
||||
this.yield();
|
||||
return;
|
||||
}
|
||||
if (ziter.hasNext()) {
|
||||
this.run();
|
||||
int relativeX = currentX - minX;
|
||||
BlockVector3 point = BlockVector3.at(currentX, currentY, currentZ);
|
||||
BaseBlock block = aabb.getWorld().getFullBlock(point);
|
||||
if (block.getNbtData() != null) {
|
||||
Map<String, Tag> values = new HashMap<>();
|
||||
for (Map.Entry<String, Tag> entry : block.getNbtData().getValue().entrySet()) {
|
||||
values.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// Remove 'id' if it exists. We want 'Id'
|
||||
values.remove("id");
|
||||
|
||||
// Positions are kept in NBT, we don't want that.
|
||||
values.remove("x");
|
||||
values.remove("y");
|
||||
values.remove("z");
|
||||
|
||||
values.put("Id", new StringTag(block.getNbtId()));
|
||||
values.put("Pos", new IntArrayTag(new int[] {relativeX, relativeY, relativeZ}));
|
||||
|
||||
tileEntities.add(new CompoundTag(values));
|
||||
}
|
||||
String blockKey = block.toImmutableState().getAsString();
|
||||
int blockId;
|
||||
if (palette.containsKey(blockKey)) {
|
||||
blockId = palette.get(blockKey);
|
||||
} else {
|
||||
blockId = palette.size();
|
||||
palette.put(blockKey, palette.size());
|
||||
}
|
||||
|
||||
while ((blockId & -128) != 0) {
|
||||
buffer.write(blockId & 127 | 128);
|
||||
blockId >>>= 7;
|
||||
}
|
||||
buffer.write(blockId);
|
||||
|
||||
if (relativeY > 0) {
|
||||
continue;
|
||||
}
|
||||
BlockVector2 pt = BlockVector2.at(currentX, currentZ);
|
||||
BiomeType biome = aabb.getWorld().getBiome(pt);
|
||||
String biomeStr = biome.getId();
|
||||
int biomeId;
|
||||
if (biomePalette.containsKey(biomeStr)) {
|
||||
biomeId = biomePalette.get(biomeStr);
|
||||
} else {
|
||||
biomeId = biomePalette.size();
|
||||
biomePalette.put(biomeStr, biomeId);
|
||||
}
|
||||
while ((biomeId & -128) != 0) {
|
||||
biomeBuffer.write(biomeId & 127 | 128);
|
||||
biomeId >>>= 7;
|
||||
}
|
||||
biomeBuffer.write(biomeId);
|
||||
}
|
||||
};
|
||||
zTask.run();
|
||||
}
|
||||
if (yiter.hasNext()) {
|
||||
TaskManager.runTaskLater(this, TaskTime.ticks(1L));
|
||||
} else {
|
||||
regionTask.run();
|
||||
currentX = minX; // reset manually as not using local variable
|
||||
}
|
||||
currentZ = minZ; // reset manually as not using local variable
|
||||
}
|
||||
regionTask.run();
|
||||
}
|
||||
};
|
||||
yTask.run();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void getCompoundTag(final Plot plot, final RunnableVal<CompoundTag> whenDone) {
|
||||
getCompoundTag(plot.getWorldName(), plot.getRegions(), new RunnableVal<CompoundTag>() {
|
||||
@Override public void run(CompoundTag value) {
|
||||
whenDone.run(value);
|
||||
}
|
||||
});
|
||||
return completableFuture;
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* _____ _ _ _____ _
|
||||
* | __ \| | | | / ____| | |
|
||||
* | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| |
|
||||
* | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
|
||||
* | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| |
|
||||
* |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_|
|
||||
* | |
|
||||
* |_|
|
||||
* PlotSquared plot management system for Minecraft
|
||||
* Copyright (C) 2020 IntellectualSites
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.plotsquared.core.util.task;
|
||||
|
||||
/**
|
||||
* A runnable that can be yielded.
|
||||
* If {@link #yield()} is invoked, {@link #run()} will be called
|
||||
* on the next tick again. Implementations need to save their state
|
||||
* correctly.
|
||||
*/
|
||||
public interface YieldRunnable extends Runnable {
|
||||
|
||||
/**
|
||||
* Runs the {@link #run()} method again on the next tick.
|
||||
*/
|
||||
default void yield() {
|
||||
TaskManager.runTaskLater(this, TaskTime.ticks(1L));
|
||||
}
|
||||
}
|
@ -24,8 +24,9 @@
|
||||
"area.set_pos2": "You will now set pos2: <command>. Note: The chosen plot size may result in the created area not exactly matching your second position.",
|
||||
|
||||
"web.generating_link": "<prefix><gold>Processing plot...</gold>",
|
||||
"web.plot_merged": "<prefix><red>This plot is merged and therefore cannot be downloaded</red>",
|
||||
"web.generating_link_failed": "<prefix><red>Failed to generate download link!</red>",
|
||||
"web.generation_link_success": "<click:open_url:<url>><url></click>",
|
||||
"web.generation_link_success": "<prefix><gold>Download: <gray><click:open_url:<download>><download></click></gray> \n Deletion: <gray><click:open_url:<delete>><delete></click></gray>\n<red>Attention: Opening the deletion link will delete the file immediately.</red></gold>",
|
||||
"web.save_failed": "<prefix><red>Failed to save.</red>",
|
||||
"web.load_null": "<prefix><gray>Please use </gray><dark_aqua><command> </dark_aqua><gray>to get a list of schematics.</gray>",
|
||||
"web.load_failed": "<prefix><red>Failed to load schematic.</red>",
|
||||
|
@ -124,6 +124,10 @@ allprojects {
|
||||
id.set("N0tMyFaultOG")
|
||||
name.set("NotMyFault")
|
||||
}
|
||||
developer {
|
||||
id.set("SirYwell")
|
||||
name.set("Hannes Greule")
|
||||
}
|
||||
}
|
||||
|
||||
scm {
|
||||
|
Loading…
Reference in New Issue
Block a user