diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java b/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java
index 804d0376e2..fc16c32e36 100644
--- a/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java
+++ b/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java
@@ -145,6 +145,7 @@
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -780,6 +781,24 @@ private void registerCommands() {
return Bukkit.getWorldContainer();
}
+ @Override
+ public @NonNull Path getWorldContainer(final String namespace) {
+ Path root = Bukkit.getWorldContainer().toPath();
+ if (MinecraftVersion.current().isOlderOrEqualThan(MinecraftVersion.TINY_TAKEOVER)) {
+ return root.resolve("world").resolve("dimensions").resolve(namespace);
+ }
+ return root;
+ }
+
+ @Override
+ public @NonNull Path getWorldPath(final String namespace, final String world) {
+ Path root = Bukkit.getWorldContainer().toPath();
+ if (MinecraftVersion.current().isOlderOrEqualThan(MinecraftVersion.TINY_TAKEOVER)) {
+ return root.resolve("world").resolve("dimensions").resolve(namespace).resolve(world);
+ }
+ return root.resolve(world);
+ }
+
@SuppressWarnings("deprecation")
private void runEntityTask() {
TaskManager.runTaskRepeat(() -> this.plotAreaManager.forEachPlotArea(plotArea -> {
diff --git a/Core/src/main/java/com/plotsquared/core/PlotPlatform.java b/Core/src/main/java/com/plotsquared/core/PlotPlatform.java
index ab1d825cbc..14481bb80e 100644
--- a/Core/src/main/java/com/plotsquared/core/PlotPlatform.java
+++ b/Core/src/main/java/com/plotsquared/core/PlotPlatform.java
@@ -50,6 +50,7 @@
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.File;
+import java.nio.file.Path;
/**
* PlotSquared main utility class
@@ -67,10 +68,44 @@ public interface PlotPlatform
extends LocaleHolder {
/**
* Gets the folder where all world data is stored.
+ * For legacy versions this is the server root, for 26.1+ this is {@code ./world}
*
* @return the world folder
*/
- @NonNull File worldContainer();
+ @NonNull
+ File worldContainer();
+
+ /**
+ * Gets the path to the dimension directory for the given namespace, e.g. {@code ./world/dimension/}.
+ *
+ * Legacy versions ignore the namespace and return the default path (by default the server root).
+ *
+ * @param namespace the dimension namespace
+ * @return a path to the dimension directory. may not exist.
+ */
+ @NonNull
+ Path getWorldContainer(String namespace);
+
+ /**
+ * See {@link #getWorldPath(String, String)}. Uses the default {@code minecraft} namespace.
+ *
+ */
+ @NonNull
+ default Path getWorldPath(String world) {
+ return getWorldPath("minecraft", world);
+ }
+
+ /**
+ * Gets the path to the world directory for the given namespace, e.g. {@code ./world/dimension//}.
+ *
+ * Legacy versions ignore the namespace (by default {@code ./})
+ *
+ * @param namespace the dimension namespace
+ * @param world the name of the world in the given dimension
+ * @return a path to the world directory. may not exist.
+ */
+ @NonNull
+ Path getWorldPath(String namespace, String world);
/**
* Completely shuts down the plugin.
diff --git a/Core/src/main/java/com/plotsquared/core/command/DatabaseCommand.java b/Core/src/main/java/com/plotsquared/core/command/DatabaseCommand.java
index 5d24194c0d..3e7c06c3c0 100644
--- a/Core/src/main/java/com/plotsquared/core/command/DatabaseCommand.java
+++ b/Core/src/main/java/com/plotsquared/core/command/DatabaseCommand.java
@@ -43,9 +43,14 @@
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -61,6 +66,8 @@
usage = "/plot database [area] ")
public class DatabaseCommand extends SubCommand {
+ private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + DatabaseCommand.class.getSimpleName());
+
private final PlotAreaManager plotAreaManager;
private final EventDispatcher eventDispatcher;
private final PlotListener plotListener;
@@ -93,7 +100,7 @@ public static void insertPlots(
});
} catch (Exception e) {
player.sendMessage(TranslatableCaption.of("database.conversion_failed"));
- e.printStackTrace();
+ LOGGER.error("Database conversion failed", e);
}
});
}
@@ -171,18 +178,14 @@ public boolean onCommand(final PlotPlayer> player, String[] args) {
if (newPlot != null) {
PlotId newId = newPlot.getId();
PlotId id = plot.getId();
- File worldFile =
- new File(
- PlotSquared.platform().worldContainer(),
- id.toCommaSeparatedString()
- );
- if (worldFile.exists()) {
- File newFile =
- new File(
- PlotSquared.platform().worldContainer(),
- newId.toCommaSeparatedString()
- );
- worldFile.renameTo(newFile);
+ Path worldPath = PlotSquared.platform().getWorldPath(id.toCommaSeparatedString());
+ if (Files.exists(worldPath)) {
+ Path newPath = PlotSquared.platform().getWorldPath(newId.toCommaSeparatedString());
+ try {
+ Files.move(worldPath, newPath);
+ } catch (IOException e) {
+ LOGGER.error("Failed to rename world entry", e);
+ }
}
plot.setId(newId);
plot.setArea(pa);
@@ -257,7 +260,7 @@ public boolean onCommand(final PlotPlayer> player, String[] args) {
} catch (ClassNotFoundException | SQLException e) {
player.sendMessage(TranslatableCaption.of("database.failed_to_save_plots"));
player.sendMessage(TranslatableCaption.of("errors.stacktrace_begin"));
- e.printStackTrace();
+ LOGGER.error("Inserting plots failed", e);
player.sendMessage(TranslatableCaption.of("errors.stacktrace_end"));
player.sendMessage(TranslatableCaption.of("database.invalid_args"));
return false;
@@ -265,7 +268,7 @@ public boolean onCommand(final PlotPlayer> player, String[] args) {
} catch (ClassNotFoundException | SQLException e) {
player.sendMessage(TranslatableCaption.of("database.failed_to_open"));
player.sendMessage(TranslatableCaption.of("errors.stacktrace_begin"));
- e.printStackTrace();
+ LOGGER.error("Opening database connection failed", e);
player.sendMessage(TranslatableCaption.of("errors.stacktrace_end"));
player.sendMessage(TranslatableCaption.of("database.invalid_args"));
return false;
diff --git a/Core/src/main/java/com/plotsquared/core/command/DebugImportWorlds.java b/Core/src/main/java/com/plotsquared/core/command/DebugImportWorlds.java
index 7bc10e8db4..429916efb5 100644
--- a/Core/src/main/java/com/plotsquared/core/command/DebugImportWorlds.java
+++ b/Core/src/main/java/com/plotsquared/core/command/DebugImportWorlds.java
@@ -21,8 +21,10 @@
import com.google.common.base.Charsets;
import com.google.inject.Inject;
import com.plotsquared.core.PlotSquared;
+import com.plotsquared.core.configuration.caption.StaticCaption;
import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.player.PlotPlayer;
+import com.plotsquared.core.plot.PlotArea;
import com.plotsquared.core.plot.PlotId;
import com.plotsquared.core.plot.world.PlotAreaManager;
import com.plotsquared.core.plot.world.SinglePlotArea;
@@ -30,11 +32,18 @@
import com.plotsquared.core.util.WorldUtil;
import com.plotsquared.core.util.task.RunnableVal2;
import com.plotsquared.core.util.task.RunnableVal3;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
-import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
@CommandDeclaration(command = "debugimportworlds",
permission = "plots.admin",
@@ -42,6 +51,8 @@
category = CommandCategory.TELEPORT)
public class DebugImportWorlds extends Command {
+ private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + DebugImportWorlds.class.getSimpleName());
+
private final PlotAreaManager plotAreaManager;
private final WorldUtil worldUtil;
@@ -67,37 +78,65 @@ public CompletableFuture execute(
return CompletableFuture.completedFuture(false);
}
SinglePlotArea area = ((SinglePlotAreaManager) this.plotAreaManager).getArea();
- PlotId id = PlotId.of(0, 0);
- File container = PlotSquared.platform().worldContainer();
- if (container.equals(new File("."))) {
+ Path container = PlotSquared.platform().getWorldContainer("minecraft");
+ if (container.equals(Path.of("."))) {
player.sendMessage(TranslatableCaption.of("debugimportworlds.world_container"));
return CompletableFuture.completedFuture(false);
}
- for (File folder : container.listFiles()) {
- String name = folder.getName();
- if (!this.worldUtil.isWorld(name) && PlotId.fromStringOrNull(name) == null) {
- UUID uuid;
- if (name.length() > 16) {
- uuid = UUID.fromString(name);
- } else {
- player.sendMessage(TranslatableCaption.of("players.fetching_player"));
- uuid = PlotSquared.get().getImpromptuUUIDPipeline().getSingle(name, 60000L);
- }
- if (uuid == null) {
- uuid =
- UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8));
- }
- while (new File(container, id.toCommaSeparatedString()).exists()) {
- id = id.getNextId();
- }
- File newDir = new File(container, id.toCommaSeparatedString());
- if (folder.renameTo(newDir)) {
- area.getPlot(id).setOwner(uuid);
- }
- }
+ try (Stream stream = Files.walk(container, 1)) {
+ stream.map(path -> new PathWithName(path, path.getFileName().toString()))
+ .filter(p -> !this.worldUtil.isWorld(p.name()))
+ .filter(p -> PlotId.fromStringOrNull(p.name()) == null)
+ .forEach(new ImportAction(player, area, container));
+ } catch (IOException e) {
+ LOGGER.error("Failed to import world", e);
+ throw new CommandException(StaticCaption.of("World import failed. Check console."));
}
player.sendMessage(TranslatableCaption.of("players.done"));
return CompletableFuture.completedFuture(true);
}
+ private record PathWithName(Path path, String name) {
+
+ }
+
+ private static class ImportAction implements Consumer {
+
+ private final PlotPlayer> player;
+ private final PlotArea area;
+ private final Path container;
+
+ private PlotId id = PlotId.of(0, 0);
+
+ private ImportAction(final PlotPlayer> player, final PlotArea area, final Path container) {
+ this.player = player;
+ this.area = area;
+ this.container = container;
+ }
+
+ @Override
+ public void accept(final PathWithName p) {
+ UUID uuid;
+ if (p.name().length() > 16) {
+ uuid = UUID.fromString(p.name());
+ } else {
+ this.player.sendMessage(TranslatableCaption.of("players.fetching_player"));
+ uuid = PlotSquared.get().getImpromptuUUIDPipeline().getSingle(p.name(), 60000L);
+ }
+ if (uuid == null) {
+ uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + p.name()).getBytes(Charsets.UTF_8));
+ }
+ Path target;
+ if (Files.exists(target = this.container.resolve(this.id.toCommaSeparatedString()))) {
+ this.id = this.id.getNextId();
+ }
+ try {
+ Files.move(p.path(), target);
+ Objects.requireNonNull(this.area.getPlot(this.id)).setOwner(uuid);
+ } catch (IOException ignored) {
+ }
+ }
+
+ }
+
}
diff --git a/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotArea.java b/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotArea.java
index d24d43d53a..227a6a5946 100644
--- a/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotArea.java
+++ b/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotArea.java
@@ -44,9 +44,12 @@
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
-import java.io.File;
import java.io.IOException;
+import java.nio.file.FileVisitResult;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
public class SinglePlotArea extends GridPlotWorld {
@@ -135,45 +138,40 @@ public void loadWorld(final PlotId id) {
.settingsNodesWrapper(new SettingsNodesWrapper(new ConfigurationNode[0], null))
.worldName(worldName);
- File container = PlotSquared.platform().worldContainer();
- File destination = new File(container, worldName);
+ Path destination = PlotSquared.platform().getWorldPath(worldName);
{// convert old
- File oldFile = new File(container, id.toCommaSeparatedString());
- if (oldFile.exists()) {
- oldFile.renameTo(destination);
- } else {
- oldFile = new File(container, id.toSeparatedString("."));
- if (oldFile.exists()) {
- oldFile.renameTo(destination);
+ Path old = PlotSquared.platform().getWorldPath(id.toCommaSeparatedString());
+ if (!Files.exists(old)) {
+ old = PlotSquared.platform().getWorldPath(id.toSeparatedString("."));
+ }
+ if (Files.exists(old)) {
+ try {
+ Files.move(old, destination);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
}
}
// Duplicate 0;0
if (builder.plotAreaType() != PlotAreaType.NORMAL) {
- if (!destination.exists()) {
- File src = new File(container, "0_0");
- if (src.exists()) {
- if (!destination.exists()) {
- destination.mkdirs();
+ if (!Files.exists(destination)) {
+ Path src = PlotSquared.platform().getWorldPath("0_0");
+ if (Files.exists(src)) {
+ try {
+ Files.createDirectories(destination);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
- File levelDat = new File(src, "level.dat");
- if (levelDat.exists()) {
+ Path levelDat = src.resolve("level.dat");
+ if (Files.exists(levelDat)) {
try {
- Files.copy(
- levelDat.toPath(),
- new File(destination, levelDat.getName()).toPath()
- );
- File data = new File(src, "data");
- if (data.exists()) {
- File dataDest = new File(destination, "data");
- dataDest.mkdirs();
- for (File file : data.listFiles()) {
- Files.copy(
- file.toPath(),
- new File(dataDest, file.getName()).toPath()
- );
- }
+ Files.copy(levelDat, destination.resolve(levelDat.getFileName()));
+ Path data = src.resolve("data");
+ if (Files.exists(data)) {
+ Path dataDest = destination.resolve("data");
+ Files.createDirectories(dataDest);
+ Files.walkFileTree(data, new RecursiveDirectoryCopyVisitor(dataDest));
}
} catch (IOException exception) {
exception.printStackTrace();
@@ -207,6 +205,37 @@ public void loadWorld(final PlotId id) {
// return AsyncWorld.create(wc);
}
+ private static final class RecursiveDirectoryCopyVisitor extends SimpleFileVisitor {
+
+ private final Path target;
+ private Path base;
+
+ public RecursiveDirectoryCopyVisitor(Path target) {
+ this.target = target;
+ }
+
+ @Override
+ public @NonNull FileVisitResult preVisitDirectory(
+ @NonNull final Path dir,
+ @NonNull final BasicFileAttributes attrs
+ ) throws IOException {
+ if (this.base == null) {
+ // first iteration, root
+ this.base = dir;
+ } else {
+ Files.createDirectories(this.target.resolve(this.base.relativize(dir)));
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public @NonNull FileVisitResult visitFile(@NonNull final Path file, @NonNull final BasicFileAttributes attrs) throws
+ IOException {
+ Files.copy(file, this.target.resolve(this.base.relativize(file)));
+ return FileVisitResult.CONTINUE;
+ }
+
+ }
@Override
public ConfigurationNode[] getSettingNodes() {
diff --git a/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotManager.java b/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotManager.java
index 7c81fc2fff..6105fec10a 100644
--- a/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotManager.java
+++ b/Core/src/main/java/com/plotsquared/core/plot/world/SinglePlotManager.java
@@ -26,13 +26,14 @@
import com.plotsquared.core.plot.PlotId;
import com.plotsquared.core.plot.PlotManager;
import com.plotsquared.core.queue.QueueCoordinator;
-import com.plotsquared.core.util.FileUtils;
import com.plotsquared.core.util.task.TaskManager;
import com.sk89q.worldedit.function.pattern.Pattern;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
-import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.List;
public class SinglePlotManager extends PlotManager {
@@ -71,9 +72,13 @@ public boolean clearPlot(
@Nullable QueueCoordinator queue
) {
PlotSquared.platform().setupUtils().unload(plot.getWorldName(), false);
- final File worldFolder = new File(PlotSquared.platform().worldContainer(), plot.getWorldName());
+ Path path = PlotSquared.platform().getWorldPath(plot.getWorldName());
TaskManager.getPlatformImplementation().taskAsync(() -> {
- FileUtils.deleteDirectory(worldFolder);
+ try {
+ Files.deleteIfExists(path);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to delete directory", e);
+ }
if (whenDone != null) {
whenDone.run();
}
diff --git a/Core/src/main/java/com/plotsquared/core/util/CloseShieldOutputStream.java b/Core/src/main/java/com/plotsquared/core/util/CloseShieldOutputStream.java
new file mode 100644
index 0000000000..6a6b2dfa5a
--- /dev/null
+++ b/Core/src/main/java/com/plotsquared/core/util/CloseShieldOutputStream.java
@@ -0,0 +1,36 @@
+/*
+ * PlotSquared, a land and world management plugin for Minecraft.
+ * Copyright (C) IntellectualSites
+ * Copyright (C) IntellectualSites team and contributors
+ *
+ * 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 .
+ */
+package com.plotsquared.core.util;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public final class CloseShieldOutputStream extends FilterOutputStream {
+
+ public CloseShieldOutputStream(final OutputStream out) {
+ super(out);
+ }
+
+ @Override
+ public void close() throws IOException {
+ // NOOP
+ }
+
+}
diff --git a/Core/src/main/java/com/plotsquared/core/util/RegionManager.java b/Core/src/main/java/com/plotsquared/core/util/RegionManager.java
index 765bdc7453..6c51ee089a 100644
--- a/Core/src/main/java/com/plotsquared/core/util/RegionManager.java
+++ b/Core/src/main/java/com/plotsquared/core/util/RegionManager.java
@@ -45,7 +45,9 @@
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
-import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Collection;
import java.util.Set;
@@ -89,13 +91,15 @@ public static BlockVector2 getRegion(Location location) {
public abstract int[] countEntities(Plot plot);
public void deleteRegionFiles(final String world, final Collection chunks, final Runnable whenDone) {
+ Path regionRoot = PlotSquared.platform().getWorldPath(world).resolve("region");
TaskManager.runTaskAsync(() -> {
for (BlockVector2 loc : chunks) {
- String directory = world + File.separator + "region" + File.separator + "r." + loc.getX() + "." + loc.getZ() + ".mca";
- File file = new File(PlotSquared.platform().worldContainer(), directory);
- LOGGER.info("- Deleting file: {} (max 1024 chunks)", file.getName());
- if (file.exists()) {
- file.delete();
+ Path path = regionRoot.resolve(String.format("r.%s.%s.mca", loc.getX(), loc.getZ()));
+ LOGGER.info("- Deleting file: {} (max 1024 chunks)", path.getFileName());
+ try {
+ Files.deleteIfExists(path);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to delete region file", e);
}
}
TaskManager.runTask(whenDone);
diff --git a/Core/src/main/java/com/plotsquared/core/util/WorldUtil.java b/Core/src/main/java/com/plotsquared/core/util/WorldUtil.java
index 98d0765a4e..d92432eb4c 100644
--- a/Core/src/main/java/com/plotsquared/core/util/WorldUtil.java
+++ b/Core/src/main/java/com/plotsquared/core/util/WorldUtil.java
@@ -25,7 +25,7 @@
import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.util.task.RunnableVal;
import com.sk89q.jnbt.CompoundTag;
-import com.sk89q.jnbt.IntTag;
+import com.sk89q.jnbt.CompoundTagBuilder;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.NBTOutputStream;
import com.sk89q.jnbt.Tag;
@@ -37,24 +37,29 @@
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.entity.EntityType;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
@@ -62,6 +67,8 @@
public abstract class WorldUtil {
+ private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + WorldUtil.class.getSimpleName());
+
/**
* {@return whether the given location is valid in the world}
* @param location the location to check
@@ -257,40 +264,27 @@ public void upload(
final @Nullable String file,
final @NonNull RunnableVal whenDone
) {
+ boolean modern = MinecraftVersion.current().isNewerOrEqualThan(MinecraftVersion.TINY_TAKEOVER);
+ String relativeMcaRoot = modern ? "dimensions/minecraft/overworld/region" : "region";
plot.getHome(home -> SchematicHandler.upload(uuid, file, "zip", new RunnableVal<>() {
@Override
public void run(OutputStream output) {
try (final ZipOutputStream zos = new ZipOutputStream(output)) {
- File dat = getDat(plot.getWorldName());
+ Path dat = getDat(plot.getWorldName());
Location spawn = getSpawn(plot.getWorldName());
if (dat != null) {
- ZipEntry ze = new ZipEntry("world" + File.separator + dat.getName());
+ ZipEntry ze = new ZipEntry("level.dat");
zos.putNextEntry(ze);
- try (NBTInputStream nis = new NBTInputStream(new GZIPInputStream(new FileInputStream(dat)))) {
- Map tag = ((CompoundTag) nis.readNamedTag().getTag()).getValue();
- Map newMap = new HashMap<>();
- for (Map.Entry entry : tag.entrySet()) {
- if (!entry.getKey().equals("Data")) {
- newMap.put(entry.getKey(), entry.getValue());
- continue;
- }
- Map data = new HashMap<>(((CompoundTag) entry.getValue()).getValue());
- data.put("SpawnX", new IntTag(home.getX()));
- data.put("SpawnY", new IntTag(home.getY()));
- data.put("SpawnZ", new IntTag(home.getZ()));
- newMap.put("Data", new CompoundTag(data));
- }
- try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
- try (NBTOutputStream out = new NBTOutputStream(new GZIPOutputStream(baos, true))) {
- //TODO Find what this should be called
- out.writeNamedTag("Schematic????", new CompoundTag(newMap));
- }
- zos.write(baos.toByteArray());
+ try (NBTInputStream nis = new NBTInputStream(new GZIPInputStream(Files.newInputStream(dat)))) {
+ CompoundTag levelData = modifyLevelData((CompoundTag) nis.readNamedTag().getTag(), home);
+ try (NBTOutputStream out =
+ new NBTOutputStream(new GZIPOutputStream(new CloseShieldOutputStream(zos), true))) {
+ out.writeNamedTag("", levelData);
}
}
+ zos.closeEntry();
}
setSpawn(spawn);
- byte[] buffer = new byte[1024];
Set added = new HashSet<>();
for (Plot current : plot.getConnectedPlots()) {
Location bot = current.getBottomAbs();
@@ -303,18 +297,13 @@ public void run(OutputStream output) {
for (BlockVector2 mca : files) {
if (mca.getX() >= brx && mca.getX() <= trx && mca.getZ() >= brz && mca.getZ() <= trz && !added.contains(
mca)) {
- final File file = getMcr(plot.getWorldName(), mca.getX(), mca.getZ());
- if (file != null) {
- //final String name = "r." + (x - cx) + "." + (z - cz) + ".mca";
- String name = file.getName();
- final ZipEntry ze = new ZipEntry("world" + File.separator + "region" + File.separator + name);
+ final Path path = getMca(plot.getWorldName(), mca.getX(), mca.getZ());
+ if (path != null) {
+ final ZipEntry ze = new ZipEntry(relativeMcaRoot + "/" + path.getFileName().toString());
zos.putNextEntry(ze);
added.add(mca);
- try (FileInputStream in = new FileInputStream(file)) {
- int len;
- while ((len = in.read(buffer)) > 0) {
- zos.write(buffer, 0, len);
- }
+ try (InputStream in = Files.newInputStream(path)) {
+ in.transferTo(zos);
}
zos.closeEntry();
}
@@ -331,49 +320,95 @@ public void run(OutputStream output) {
}, whenDone));
}
- final @Nullable File getDat(final @NonNull String world) {
- File file = new File(PlotSquared.platform().worldContainer() + File.separator + world + File.separator + "level.dat");
- if (file.exists()) {
- return file;
+ private @Nullable Path getDat(final @NonNull String world) {
+ Path path;
+ if (MinecraftVersion.current().isOlderOrEqualThan(MinecraftVersion.TINY_TAKEOVER)) {
+ // 26.1+ only has a global level.dat
+ path = PlotSquared.platform().worldContainer().toPath().resolve("world").resolve("level.dat");
+ } else {
+ path = PlotSquared.platform().worldContainer().toPath().resolve(world).resolve("level.dat");
}
- return null;
+ return Files.exists(path) ? path : null;
}
@Nullable
- private File getMcr(final @NonNull String world, final int x, final int z) {
- final File file =
- new File(
- PlotSquared.platform().worldContainer(),
- world + File.separator + "region" + File.separator + "r." + x + '.' + z + ".mca"
+ private Path getMca(final @NonNull String world, final int x, final int z) {
+ Path path = PlotSquared.platform().getWorldPath(world).resolve("region").
+ resolve(String.format("r.%s.%s.mca", x, z));
+ return Files.exists(path) ? path : null;
+ }
+
+ private CompoundTag modifyLevelData(CompoundTag input, Location home) {
+ Map root = input.getValue();
+ if (!(root.get("Data") instanceof CompoundTag data)) {
+ return input;
+ }
+ CompoundTagBuilder dataBuilder = data.createBuilder();
+ if (MinecraftVersion.current().isNewerOrEqualThan(MinecraftVersion.TINY_TAKEOVER)) {
+ if (data.getValue().get("spawn") instanceof CompoundTag spawn) {
+ dataBuilder.put(
+ "spawn", spawn.createBuilder()
+ .putString("dimension", "minecraft:overworld")
+ .putIntArray("pos", new int[]{home.getX(), home.getY(), home.getZ()})
+ .build()
);
- if (file.exists()) {
- return file;
+ }
+ } else {
+ // legacy
+ dataBuilder
+ .putInt("SpawnX", home.getX())
+ .putInt("SpawnY", home.getY())
+ .putInt("SpawnZ", home.getZ());
}
- return null;
+ return input.createBuilder().put("Data", dataBuilder.build()).build();
}
public Set getChunkChunks(String world) {
- File folder = new File(PlotSquared.platform().worldContainer(), world + File.separator + "region");
- File[] regionFiles = folder.listFiles();
- if (regionFiles == null) {
- throw new RuntimeException("Could not find worlds folder: " + folder + " ? (no read access?)");
+ Path regionRoot = PlotSquared.platform().getWorldPath(world).resolve("region");
+ if (!Files.exists(regionRoot)) {
+ throw new RuntimeException("Could not find regions folder: " + regionRoot + " ? (no read access?)");
}
- HashSet chunks = new HashSet<>();
- for (File file : regionFiles) {
- String name = file.getName();
- if (name.endsWith("mca")) {
- String[] split = name.split("\\.");
- try {
- int x = Integer.parseInt(split[1]);
- int z = Integer.parseInt(split[2]);
- BlockVector2 loc = BlockVector2.at(x, z);
- chunks.add(loc);
- } catch (NumberFormatException ignored) {
- }
- }
+ try (Stream stream = Files.find(regionRoot, 1, WorldUtil::isMcaRegionFile)) {
+ return stream.map(Path::getFileName)
+ .map(this::fromMcaFileName)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ } catch (IOException e) {
+ LOGGER.error("Failed to traverse region directory", e);
+ return Set.of();
+ }
+ }
+
+ /**
+ * checks if the given file, by its path and BasicFileAttributes, is a mca region file
+ * @param path full path to file
+ * @param bfa attributes of the given file
+ * @return {@code true} if the given file is a seemingly valid mca region file. {@code false} otherwise
+ */
+ private static boolean isMcaRegionFile(Path path, BasicFileAttributes bfa) {
+ if (bfa.isDirectory()) {
+ return false;
+ }
+ String name = path.getFileName().toString();
+ return name.startsWith("r.") && name.endsWith(".mca");
+ }
+
+ /**
+ * Retrieves the coordinates from a region mca file
+ * @param filename the filename part of the full path ({@link Path#getFileName()})
+ * @return A BV2 containg the coordinates, or {@code null} if the filename does not match the expected format
+ */
+ private BlockVector2 fromMcaFileName(Path filename) {
+ String[] parts = filename.toString().split("\\.");
+ if (parts.length < 3) {
+ return null;
+ }
+ try {
+ return BlockVector2.at(Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
+ } catch (NumberFormatException e) {
+ return null;
}
- return chunks;
}
/**