mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2025-07-12 08:28:35 +00:00
Upstream Merge
This commit is contained in:
@ -153,6 +153,7 @@ public abstract class AbstractWorld implements World {
|
||||
@Override
|
||||
public void setWeather(WeatherType weatherType, long duration) {
|
||||
}
|
||||
|
||||
private class QueuedEffect implements Comparable<QueuedEffect> {
|
||||
private final Vector3 position;
|
||||
private final BlockType blockType;
|
||||
|
@ -69,6 +69,7 @@ public class NullWorld extends AbstractWorld {
|
||||
public String getId() {
|
||||
return "null";
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block, boolean notifyAndLight) throws WorldEditException {
|
||||
return false;
|
||||
|
@ -23,11 +23,12 @@ import com.sk89q.worldedit.registry.state.Property;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A block registry that uses {@link BundledBlockData} to serve information
|
||||
* about blocks.
|
||||
|
@ -33,4 +33,4 @@ public interface ItemMaterial {
|
||||
* @return the maximum damage, or 0 if not applicable
|
||||
*/
|
||||
int getMaxDamage();
|
||||
}
|
||||
}
|
||||
|
@ -56,15 +56,7 @@ import java.util.Map;
|
||||
public final class LegacyMapper {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LegacyMapper.class);
|
||||
private static LegacyMapper INSTANCE = new LegacyMapper();
|
||||
|
||||
static {
|
||||
try {
|
||||
INSTANCE.loadFromResource();
|
||||
} catch (Throwable e) {
|
||||
log.warn("Failed to load the built-in legacy id registry", e);
|
||||
}
|
||||
}
|
||||
private static LegacyMapper INSTANCE;
|
||||
|
||||
private final Int2ObjectArrayMap<Integer> blockStateToLegacyId4Data = new Int2ObjectArrayMap<>();
|
||||
private final Int2ObjectArrayMap<Integer> extraId4DataToStateId = new Int2ObjectArrayMap<>();
|
||||
@ -80,6 +72,12 @@ public final class LegacyMapper {
|
||||
* Create a new instance.
|
||||
*/
|
||||
private LegacyMapper() {
|
||||
try {
|
||||
loadFromResource();
|
||||
} catch (Throwable e) {
|
||||
log.warn("Failed to load the built-in legacy id registry", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,9 +93,8 @@ public final class LegacyMapper {
|
||||
if (url == null) {
|
||||
throw new IOException("Could not find legacy.json");
|
||||
}
|
||||
String source = Resources.toString(url, Charset.defaultCharset());
|
||||
LegacyDataFile dataFile = gson.fromJson(source, new TypeToken<LegacyDataFile>() {
|
||||
}.getType());
|
||||
String data = Resources.toString(url, Charset.defaultCharset());
|
||||
LegacyDataFile dataFile = gson.fromJson(data, new TypeToken<LegacyDataFile>() {}.getType());
|
||||
|
||||
DataFixer fixer = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataFixer();
|
||||
ParserContext parserContext = new ParserContext();
|
||||
@ -110,49 +107,51 @@ public final class LegacyMapper {
|
||||
Integer combinedId = getCombinedId(blockEntry.getKey());
|
||||
final String value = blockEntry.getValue();
|
||||
blockEntries.put(id, value);
|
||||
BlockState blockState = null;
|
||||
|
||||
BlockState state = null;
|
||||
try {
|
||||
blockState = BlockState.get(null, blockEntry.getValue());
|
||||
BlockType type = blockState.getBlockType();
|
||||
state = BlockState.get(null, blockEntry.getValue());
|
||||
BlockType type = state.getBlockType();
|
||||
if (type.hasProperty(PropertyKey.WATERLOGGED)) {
|
||||
blockState = blockState.with(PropertyKey.WATERLOGGED, false);
|
||||
state = state.with(PropertyKey.WATERLOGGED, false);
|
||||
}
|
||||
} catch (InputParseException e) {
|
||||
} catch (InputParseException f) {
|
||||
BlockFactory blockFactory = WorldEdit.getInstance().getBlockFactory();
|
||||
// if fixer is available, try using that first, as some old blocks that were renamed share names with new blocks
|
||||
if (fixer != null) {
|
||||
try {
|
||||
String newEntry = fixer.fixUp(DataFixer.FixTypes.BLOCK_STATE, value, 1631);
|
||||
blockState = blockFactory.parseFromInput(newEntry, parserContext).toImmutableState();
|
||||
} catch (InputParseException f) {
|
||||
state = blockFactory.parseFromInput(newEntry, parserContext).toImmutableState();
|
||||
} catch (InputParseException e) {
|
||||
}
|
||||
}
|
||||
// if it's still null, the fixer was unavailable or failed
|
||||
if (blockState == null) {
|
||||
if (state == null) {
|
||||
try {
|
||||
blockState = blockFactory.parseFromInput(value, parserContext).toImmutableState();
|
||||
} catch (InputParseException f) {
|
||||
state = blockFactory.parseFromInput(value, parserContext).toImmutableState();
|
||||
} catch (InputParseException e) {
|
||||
}
|
||||
}
|
||||
// if it's still null, both fixer and default failed
|
||||
if (blockState == null) {
|
||||
if (state == null) {
|
||||
log.debug("Unknown block: " + value);
|
||||
} else {
|
||||
// it's not null so one of them succeeded, now use it
|
||||
blockToStringMap.put(blockState, id);
|
||||
stringToBlockMap.put(id, blockState);
|
||||
blockToStringMap.put(state, id);
|
||||
stringToBlockMap.put(id, state);
|
||||
}
|
||||
}
|
||||
if (blockState != null) {
|
||||
blockArr[combinedId] = blockState.getInternalId();
|
||||
blockStateToLegacyId4Data.put(blockState.getInternalId(), (Integer) combinedId);
|
||||
blockStateToLegacyId4Data.putIfAbsent(blockState.getInternalBlockTypeId(), combinedId);
|
||||
if (state != null) {
|
||||
blockArr[combinedId] = state.getInternalId();
|
||||
blockStateToLegacyId4Data.put(state.getInternalId(), (Integer) combinedId);
|
||||
blockStateToLegacyId4Data.putIfAbsent(state.getInternalBlockTypeId(), combinedId);
|
||||
}
|
||||
}
|
||||
for (int id = 0; id < 256; id++) {
|
||||
int combinedId = id << 4;
|
||||
int base = blockArr[combinedId];
|
||||
if (base != 0) {
|
||||
for (int data = 0; data < 16; data++, combinedId++) {
|
||||
for (int data_ = 0; data_ < 16; data_++, combinedId++) {
|
||||
if (blockArr[combinedId] == 0) blockArr[combinedId] = base;
|
||||
}
|
||||
}
|
||||
@ -166,14 +165,14 @@ public final class LegacyMapper {
|
||||
value = fixer.fixUp(DataFixer.FixTypes.ITEM_TYPE, value, 1631);
|
||||
type = ItemTypes.get(value);
|
||||
}
|
||||
if (type != null) {
|
||||
if (type == null) {
|
||||
log.debug("Unknown item: " + value);
|
||||
} else {
|
||||
try {
|
||||
itemMap.put(getCombinedId(id), type);
|
||||
continue;
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
log.debug("Unknown item: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,7 +288,10 @@ public final class LegacyMapper {
|
||||
return combinedId == null ? null : new int[] { combinedId >> 4, combinedId & 0xF };
|
||||
}
|
||||
|
||||
public final static LegacyMapper getInstance() {
|
||||
public static LegacyMapper getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new LegacyMapper();
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
|
@ -42,4 +42,4 @@ public class PassthroughItemMaterial implements ItemMaterial {
|
||||
public int getMaxDamage() {
|
||||
return itemMaterial.getMaxDamage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
package com.sk89q.worldedit.world.registry;
|
||||
|
||||
class SimpleItemMaterial implements ItemMaterial {
|
||||
public class SimpleItemMaterial implements ItemMaterial {
|
||||
|
||||
private int maxStackSize;
|
||||
private int maxDamage;
|
||||
@ -38,4 +38,4 @@ class SimpleItemMaterial implements ItemMaterial {
|
||||
public int getMaxDamage() {
|
||||
return maxDamage;
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
package com.sk89q.worldedit.world.snapshot;
|
||||
|
||||
import com.sk89q.worldedit.world.storage.MissingWorldException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.world.snapshot.experimental;
|
||||
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.chunk.Chunk;
|
||||
import com.sk89q.worldedit.world.storage.ChunkStoreHelper;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Represents a world snapshot.
|
||||
*/
|
||||
public interface Snapshot extends Closeable {
|
||||
|
||||
SnapshotInfo getInfo();
|
||||
|
||||
/**
|
||||
* Get the chunk information for the given position. Implementations may ignore the Y-chunk
|
||||
* if its chunks are only stored in 2D.
|
||||
*
|
||||
* @param position the position of the chunk
|
||||
* @return the tag containing chunk data
|
||||
*/
|
||||
CompoundTag getChunkTag(BlockVector3 position) throws DataException, IOException;
|
||||
|
||||
/**
|
||||
* Get the chunk information for the given position.
|
||||
*
|
||||
* @see #getChunkTag(BlockVector3)
|
||||
* @see ChunkStoreHelper#getChunk(CompoundTag)
|
||||
*/
|
||||
default Chunk getChunk(BlockVector3 position) throws DataException, IOException {
|
||||
return ChunkStoreHelper.getChunk(getChunkTag(position));
|
||||
}
|
||||
|
||||
/**
|
||||
* Close this snapshot. This releases the IO handles used to load chunk information.
|
||||
*/
|
||||
@Override
|
||||
void close() throws IOException;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.world.snapshot.experimental;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class SnapshotComparator {
|
||||
|
||||
private static final Comparator<Snapshot> COMPARATOR =
|
||||
Comparator.comparing(Snapshot::getInfo);
|
||||
|
||||
public static Comparator<Snapshot> getInstance() {
|
||||
return COMPARATOR;
|
||||
}
|
||||
|
||||
private SnapshotComparator() {
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.world.snapshot.experimental;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.sk89q.worldedit.util.collection.MoreStreams.takeWhile;
|
||||
|
||||
/**
|
||||
* Handler for querying snapshot storage.
|
||||
*/
|
||||
public interface SnapshotDatabase {
|
||||
|
||||
/**
|
||||
* Get the URI scheme handled by this database.
|
||||
*/
|
||||
String getScheme();
|
||||
|
||||
/**
|
||||
* Get a snapshot by name.
|
||||
*
|
||||
* @param name the name of the snapshot
|
||||
* @return the snapshot if available
|
||||
*/
|
||||
Optional<Snapshot> getSnapshot(URI name) throws IOException;
|
||||
|
||||
/**
|
||||
* Get all snapshots by world, unsorted. The stream should be
|
||||
* {@linkplain Stream#close() closed}, as it may allocate filesystem or network resources.
|
||||
*
|
||||
* @param worldName the name of the world
|
||||
* @return a stream of all snapshots for the given world in this database
|
||||
*/
|
||||
Stream<Snapshot> getSnapshots(String worldName) throws IOException;
|
||||
|
||||
default Stream<Snapshot> getSnapshotsNewestFirst(String worldName) throws IOException {
|
||||
return getSnapshots(worldName).sorted(SnapshotComparator.getInstance().reversed());
|
||||
}
|
||||
|
||||
default Stream<Snapshot> getSnapshotsOldestFirst(String worldName) throws IOException {
|
||||
return getSnapshots(worldName).sorted(SnapshotComparator.getInstance());
|
||||
}
|
||||
|
||||
default Stream<Snapshot> getSnapshotsBefore(String worldName, ZonedDateTime date) throws IOException {
|
||||
return takeWhile(
|
||||
// sorted from oldest -> newest, so all `before` are at the front
|
||||
getSnapshotsOldestFirst(worldName),
|
||||
snap -> snap.getInfo().getDateTime().isBefore(date)
|
||||
);
|
||||
}
|
||||
|
||||
default Stream<Snapshot> getSnapshotsAfter(String worldName, ZonedDateTime date) throws IOException {
|
||||
return takeWhile(
|
||||
// sorted from newest -> oldest, so all `after` are at the front
|
||||
getSnapshotsNewestFirst(worldName),
|
||||
snap -> snap.getInfo().getDateTime().isAfter(date)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.world.snapshot.experimental;
|
||||
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Information about a snapshot, such as name and date.
|
||||
*/
|
||||
public final class SnapshotInfo implements Comparable<SnapshotInfo> {
|
||||
|
||||
public static SnapshotInfo create(URI name, ZonedDateTime dateTime) {
|
||||
return new SnapshotInfo(name, dateTime);
|
||||
}
|
||||
|
||||
private final URI name;
|
||||
private final ZonedDateTime dateTime;
|
||||
|
||||
private SnapshotInfo(URI name, ZonedDateTime dateTime) {
|
||||
this.name = name;
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
public URI getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
if (name.getScheme().equals("snapfs")) {
|
||||
// Stored raw as the scheme specific part
|
||||
return name.getSchemeSpecificPart();
|
||||
}
|
||||
return name.toString();
|
||||
}
|
||||
|
||||
public ZonedDateTime getDateTime() {
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SnapshotInfo that = (SnapshotInfo) o;
|
||||
return Objects.equals(name, that.name) &&
|
||||
Objects.equals(dateTime, that.dateTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, dateTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SnapshotInfo{" +
|
||||
"name='" + name + '\'' +
|
||||
",date=" + dateTime +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SnapshotInfo o) {
|
||||
return ComparisonChain.start()
|
||||
.compare(dateTime, o.dateTime)
|
||||
.compare(name, o.name)
|
||||
.result();
|
||||
}
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.world.snapshot.experimental;
|
||||
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.chunk.Chunk;
|
||||
import com.sk89q.worldedit.world.storage.ChunkStore;
|
||||
import com.sk89q.worldedit.world.storage.MissingChunkException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A snapshot restore operation.
|
||||
*/
|
||||
public class SnapshotRestore {
|
||||
|
||||
private final Map<BlockVector2, ArrayList<BlockVector3>> neededChunks = new LinkedHashMap<>();
|
||||
private final Snapshot snapshot;
|
||||
private final EditSession editSession;
|
||||
private ArrayList<BlockVector2> missingChunks;
|
||||
private ArrayList<BlockVector2> errorChunks;
|
||||
private String lastErrorMessage;
|
||||
|
||||
/**
|
||||
* Construct the snapshot restore operation.
|
||||
*
|
||||
* @param snapshot The {@link Snapshot} to restore from
|
||||
* @param editSession The {@link EditSession} to restore to
|
||||
* @param region The {@link Region} to restore to
|
||||
*/
|
||||
public SnapshotRestore(Snapshot snapshot, EditSession editSession, Region region) {
|
||||
this.snapshot = snapshot;
|
||||
this.editSession = editSession;
|
||||
|
||||
if (region instanceof CuboidRegion) {
|
||||
findNeededCuboidChunks(region);
|
||||
} else {
|
||||
findNeededChunks(region);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find needed chunks in the axis-aligned bounding box of the region.
|
||||
*
|
||||
* @param region The {@link Region} to iterate
|
||||
*/
|
||||
private void findNeededCuboidChunks(Region region) {
|
||||
BlockVector3 min = region.getMinimumPoint();
|
||||
BlockVector3 max = region.getMaximumPoint();
|
||||
|
||||
// First, we need to group points by chunk so that we only need
|
||||
// to keep one chunk in memory at any given moment
|
||||
for (int x = min.getBlockX(); x <= max.getBlockX(); ++x) {
|
||||
for (int y = min.getBlockY(); y <= max.getBlockY(); ++y) {
|
||||
for (int z = min.getBlockZ(); z <= max.getBlockZ(); ++z) {
|
||||
BlockVector3 pos = BlockVector3.at(x, y, z);
|
||||
checkAndAddBlock(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find needed chunks in the region.
|
||||
*
|
||||
* @param region The {@link Region} to iterate
|
||||
*/
|
||||
private void findNeededChunks(Region region) {
|
||||
// First, we need to group points by chunk so that we only need
|
||||
// to keep one chunk in memory at any given moment
|
||||
for (BlockVector3 pos : region) {
|
||||
checkAndAddBlock(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAndAddBlock(BlockVector3 pos) {
|
||||
if (editSession.getMask() != null && !editSession.getMask().test(pos))
|
||||
return;
|
||||
|
||||
BlockVector2 chunkPos = ChunkStore.toChunk(pos);
|
||||
|
||||
// Unidentified chunk
|
||||
if (!neededChunks.containsKey(chunkPos)) {
|
||||
neededChunks.put(chunkPos, new ArrayList<>());
|
||||
}
|
||||
|
||||
neededChunks.get(chunkPos).add(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of chunks that are needed.
|
||||
*
|
||||
* @return a number of chunks
|
||||
*/
|
||||
public int getChunksAffected() {
|
||||
return neededChunks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores to world.
|
||||
*
|
||||
* @throws MaxChangedBlocksException
|
||||
*/
|
||||
public void restore() throws MaxChangedBlocksException {
|
||||
|
||||
missingChunks = new ArrayList<>();
|
||||
errorChunks = new ArrayList<>();
|
||||
|
||||
// Now let's start restoring!
|
||||
for (Map.Entry<BlockVector2, ArrayList<BlockVector3>> entry : neededChunks.entrySet()) {
|
||||
BlockVector2 chunkPos = entry.getKey();
|
||||
Chunk chunk;
|
||||
|
||||
try {
|
||||
// This will need to be changed if we start officially supporting 3d snapshots.
|
||||
chunk = snapshot.getChunk(chunkPos.toBlockVector3());
|
||||
// Good, the chunk could be at least loaded
|
||||
|
||||
// Now just copy blocks!
|
||||
for (BlockVector3 pos : entry.getValue()) {
|
||||
try {
|
||||
editSession.setBlock(pos, chunk.getBlock(pos));
|
||||
} catch (DataException e) {
|
||||
// this is a workaround: just ignore for now
|
||||
}
|
||||
}
|
||||
} catch (MissingChunkException me) {
|
||||
missingChunks.add(chunkPos);
|
||||
} catch (IOException | DataException me) {
|
||||
errorChunks.add(chunkPos);
|
||||
lastErrorMessage = me.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the missing chunks. restore() must have been called
|
||||
* already.
|
||||
*
|
||||
* @return a list of coordinates
|
||||
*/
|
||||
public List<BlockVector2> getMissingChunks() {
|
||||
return missingChunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the chunks that could not have been loaded for other
|
||||
* reasons. restore() must have been called already.
|
||||
*
|
||||
* @return a list of coordinates
|
||||
*/
|
||||
public List<BlockVector2> getErrorChunks() {
|
||||
return errorChunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see where the backup succeeded in any capacity. False will
|
||||
* be returned if no chunk could be successfully loaded.
|
||||
*
|
||||
* @return true if there was total failure
|
||||
*/
|
||||
public boolean hadTotalFailure() {
|
||||
return missingChunks.size() + errorChunks.size() == getChunksAffected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last error message.
|
||||
*
|
||||
* @return a message
|
||||
*/
|
||||
public String getLastErrorMessage() {
|
||||
return lastErrorMessage;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,300 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.world.snapshot.experimental.fs;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.net.UrlEscapers;
|
||||
import com.sk89q.worldedit.util.function.IORunnable;
|
||||
import com.sk89q.worldedit.util.io.Closer;
|
||||
import com.sk89q.worldedit.util.io.file.ArchiveNioSupport;
|
||||
import com.sk89q.worldedit.util.io.file.MorePaths;
|
||||
import com.sk89q.worldedit.util.time.FileNameDateTimeParser;
|
||||
import com.sk89q.worldedit.util.time.ModificationDateTimeParser;
|
||||
import com.sk89q.worldedit.util.time.SnapshotDateTimeParser;
|
||||
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
||||
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotDatabase;
|
||||
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
/**
|
||||
* Implements a snapshot database based on a filesystem.
|
||||
*/
|
||||
public class FileSystemSnapshotDatabase implements SnapshotDatabase {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileSystemSnapshotDatabase.class);
|
||||
|
||||
private static final String SCHEME = "snapfs";
|
||||
|
||||
private static final List<SnapshotDateTimeParser> DATE_TIME_PARSERS =
|
||||
new ImmutableList.Builder<SnapshotDateTimeParser>()
|
||||
.add(FileNameDateTimeParser.getInstance())
|
||||
.addAll(ServiceLoader.load(SnapshotDateTimeParser.class))
|
||||
.add(ModificationDateTimeParser.getInstance())
|
||||
.build();
|
||||
|
||||
public static ZonedDateTime tryParseDate(Path path) {
|
||||
return tryParseDateInternal(path)
|
||||
.orElseThrow(() -> new IllegalStateException("Could not detect date of " + path));
|
||||
}
|
||||
|
||||
private static Optional<ZonedDateTime> tryParseDateInternal(Path path) {
|
||||
return DATE_TIME_PARSERS.stream()
|
||||
.map(parser -> parser.detectDateTime(path))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public static URI createUri(String name) {
|
||||
return URI.create(SCHEME + ":" + UrlEscapers.urlFragmentEscaper().escape(name));
|
||||
}
|
||||
|
||||
public static FileSystemSnapshotDatabase maybeCreate(
|
||||
Path root,
|
||||
ArchiveNioSupport archiveNioSupport
|
||||
) throws IOException {
|
||||
Files.createDirectories(root);
|
||||
return new FileSystemSnapshotDatabase(root, archiveNioSupport);
|
||||
}
|
||||
|
||||
private final Path root;
|
||||
private final ArchiveNioSupport archiveNioSupport;
|
||||
|
||||
public FileSystemSnapshotDatabase(Path root, ArchiveNioSupport archiveNioSupport) {
|
||||
checkArgument(Files.isDirectory(root), "Database root is not a directory");
|
||||
this.root = root.toAbsolutePath();
|
||||
this.archiveNioSupport = archiveNioSupport;
|
||||
}
|
||||
|
||||
private SnapshotInfo createSnapshotInfo(Path fullPath, Path realPath) {
|
||||
// Try full for parsing out of file name, real for parsing mod time.
|
||||
ZonedDateTime date = tryParseDateInternal(fullPath).orElseGet(() -> tryParseDate(realPath));
|
||||
return SnapshotInfo.create(createUri(fullPath.toString()), date);
|
||||
}
|
||||
|
||||
private Snapshot createSnapshot(Path fullPath, Path realPath, @Nullable IORunnable closeCallback) {
|
||||
return new FolderSnapshot(
|
||||
createSnapshotInfo(fullPath, realPath), realPath, closeCallback
|
||||
);
|
||||
}
|
||||
|
||||
public Path getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getScheme() {
|
||||
return SCHEME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Snapshot> getSnapshot(URI name) throws IOException {
|
||||
if (!name.getScheme().equals(SCHEME)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// drop the / in the path to make it absolute
|
||||
Path rawResolved = root.resolve(name.getSchemeSpecificPart());
|
||||
// Catch trickery with paths:
|
||||
Path realPath = rawResolved.normalize();
|
||||
if (!realPath.startsWith(root)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Optional<Snapshot> result = tryRegularFileSnapshot(root.relativize(realPath), realPath);
|
||||
if (result.isPresent()) {
|
||||
return result;
|
||||
}
|
||||
if (!Files.isDirectory(realPath)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(createSnapshot(root.relativize(realPath), realPath, null));
|
||||
}
|
||||
|
||||
private Optional<Snapshot> tryRegularFileSnapshot(Path fullPath, Path realPath) throws IOException {
|
||||
Closer closer = Closer.create();
|
||||
Path root = this.root;
|
||||
Path relative = root.relativize(realPath);
|
||||
Iterator<Path> iterator = null;
|
||||
try {
|
||||
while (true) {
|
||||
if (iterator == null) {
|
||||
iterator = MorePaths.iterPaths(relative).iterator();
|
||||
}
|
||||
if (!iterator.hasNext()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Path relativeNext = iterator.next();
|
||||
Path next = root.resolve(relativeNext);
|
||||
if (!Files.isRegularFile(next)) {
|
||||
// This will never be it.
|
||||
continue;
|
||||
}
|
||||
Optional<Path> newRootOpt = archiveNioSupport.tryOpenAsDir(next);
|
||||
if (newRootOpt.isPresent()) {
|
||||
root = newRootOpt.get();
|
||||
if (root.getFileSystem() != FileSystems.getDefault()) {
|
||||
closer.register(root.getFileSystem());
|
||||
}
|
||||
// Switch path to path inside the archive
|
||||
relative = root.resolve(relativeNext.relativize(relative).toString());
|
||||
iterator = null;
|
||||
// Check if it exists, if so open snapshot
|
||||
if (Files.exists(relative)) {
|
||||
return Optional.of(createSnapshot(fullPath, relative, closer::close));
|
||||
}
|
||||
// Otherwise, we may have more archives to open.
|
||||
// Keep searching!
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
throw closer.rethrowAndClose(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Snapshot> getSnapshots(String worldName) throws IOException {
|
||||
/*
|
||||
There are a few possible snapshot formats we accept:
|
||||
- a world directory, identified by <worldName>/level.dat
|
||||
- a world archive, identified by <worldName>.ext
|
||||
* does not need to have level.dat inside
|
||||
- a timestamped directory, identified by <stamp>, that can have
|
||||
- the two world formats described above, inside the directory
|
||||
- a timestamped archive, identified by <stamp>.ext, that can have
|
||||
- the same as timestamped directory, but inside the archive.
|
||||
- a directory with the world name, but no level.dat
|
||||
- inside must be timestamped directory/archive, with the world inside that
|
||||
|
||||
All archives may have a root directory with the same name as the archive,
|
||||
minus the extensions. Due to extension detection methods, this won't work properly
|
||||
with some files, e.g. world.qux.zip/world.qux is invalid, but world.qux.zip/world isn't.
|
||||
*/
|
||||
return Stream.of(
|
||||
listWorldEntries(Paths.get(""), root, worldName),
|
||||
listTimestampedEntries(Paths.get(""), root, worldName)
|
||||
).flatMap(Function.identity());
|
||||
}
|
||||
|
||||
private Stream<Snapshot> listWorldEntries(Path fullPath, Path root, String worldName) throws IOException {
|
||||
logger.debug("World check in: {}", root);
|
||||
return Files.list(root)
|
||||
.flatMap(candidate -> {
|
||||
logger.debug("World trying: {}", candidate);
|
||||
// Try world directory
|
||||
String fileName = candidate.getFileName().toString();
|
||||
if (isSameDirectoryName(fileName, worldName)) {
|
||||
// Direct
|
||||
if (Files.exists(candidate.resolve("level.dat"))) {
|
||||
logger.debug("Direct!");
|
||||
return Stream.of(createSnapshot(
|
||||
fullPath.resolve(fileName), candidate, null
|
||||
));
|
||||
}
|
||||
// Container for time-stamped entries
|
||||
try {
|
||||
return listTimestampedEntries(
|
||||
fullPath.resolve(fileName), candidate, worldName
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
// Try world archive
|
||||
if (Files.isRegularFile(candidate)
|
||||
&& fileName.startsWith(worldName + ".")) {
|
||||
logger.debug("Archive!");
|
||||
try {
|
||||
return tryRegularFileSnapshot(
|
||||
fullPath.resolve(fileName), candidate
|
||||
).map(Stream::of).orElse(null);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
logger.debug("Nothing!");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isSameDirectoryName(String fileName, String worldName) {
|
||||
if (fileName.lastIndexOf('/') == fileName.length() - 1) {
|
||||
fileName = fileName.substring(0, fileName.length() - 1);
|
||||
}
|
||||
return fileName.equalsIgnoreCase(worldName);
|
||||
}
|
||||
|
||||
private Stream<Snapshot> listTimestampedEntries(Path fullPath, Path root, String worldName) throws IOException {
|
||||
logger.debug("Timestamp check in: {}", root);
|
||||
return Files.list(root)
|
||||
.filter(candidate -> {
|
||||
ZonedDateTime date = FileNameDateTimeParser.getInstance().detectDateTime(candidate);
|
||||
return date != null;
|
||||
})
|
||||
.flatMap(candidate -> {
|
||||
logger.debug("Timestamp trying: {}", candidate);
|
||||
// Try timestamped directory
|
||||
if (Files.isDirectory(candidate)) {
|
||||
logger.debug("Timestamped directory");
|
||||
try {
|
||||
return listWorldEntries(
|
||||
fullPath.resolve(candidate.getFileName().toString()), candidate, worldName
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
// Otherwise archive, get it as a directory & unpack it
|
||||
try {
|
||||
Optional<Path> newRoot = archiveNioSupport.tryOpenAsDir(candidate);
|
||||
if (!newRoot.isPresent()) {
|
||||
logger.debug("Nothing!");
|
||||
return null;
|
||||
}
|
||||
logger.debug("Timestamped archive!");
|
||||
return listWorldEntries(
|
||||
fullPath.resolve(candidate.getFileName().toString()),
|
||||
newRoot.get(),
|
||||
worldName
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.world.snapshot.experimental.fs;
|
||||
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.util.function.IORunnable;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
||||
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotInfo;
|
||||
import com.sk89q.worldedit.world.storage.ChunkStoreHelper;
|
||||
import com.sk89q.worldedit.world.storage.LegacyChunkStore;
|
||||
import com.sk89q.worldedit.world.storage.McRegionChunkStore;
|
||||
import com.sk89q.worldedit.world.storage.McRegionReader;
|
||||
import com.sk89q.worldedit.world.storage.MissingChunkException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
/**
|
||||
* Snapshot based on a world folder. Extracts chunks from the region folder.
|
||||
*
|
||||
* <p>
|
||||
* Note that the Path can belong to another filesystem. This allows easy integration with
|
||||
* zips due to Java's built-in zipfs support.
|
||||
* </p>
|
||||
*/
|
||||
public class FolderSnapshot implements Snapshot {
|
||||
|
||||
/**
|
||||
* Object used by {@code getRegionFolder(Path)} to indicate that the path does not exist.
|
||||
*/
|
||||
private static final Object NOT_FOUND_TOKEN = new Object();
|
||||
|
||||
private static Object getRegionFolder(Path folder) throws IOException {
|
||||
Path regionDir = folder.resolve("region");
|
||||
if (Files.exists(regionDir)) {
|
||||
checkState(Files.isDirectory(regionDir), "Region folder is actually a file");
|
||||
return regionDir;
|
||||
}
|
||||
// Might be in a DIM* folder
|
||||
try (Stream<Path> paths = Files.list(folder)) {
|
||||
Optional<Path> path = paths
|
||||
.filter(Files::isDirectory)
|
||||
.filter(p -> p.getFileName().toString().startsWith("DIM"))
|
||||
.map(p -> p.resolve("region"))
|
||||
.filter(Files::isDirectory)
|
||||
.findFirst();
|
||||
if (path.isPresent()) {
|
||||
return path.get();
|
||||
}
|
||||
}
|
||||
// Might be its own region folder, check if the appropriate files exist
|
||||
try (Stream<Path> paths = Files.list(folder)) {
|
||||
if (paths
|
||||
.filter(Files::isRegularFile)
|
||||
.anyMatch(p -> {
|
||||
String fileName = p.getFileName().toString();
|
||||
return fileName.startsWith("r") &&
|
||||
(fileName.endsWith(".mca") || fileName.endsWith(".mcr"));
|
||||
})) {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
return NOT_FOUND_TOKEN;
|
||||
}
|
||||
|
||||
private final SnapshotInfo info;
|
||||
private final Path folder;
|
||||
private final AtomicReference<Object> regionFolder = new AtomicReference<>();
|
||||
private final @Nullable IORunnable closeCallback;
|
||||
|
||||
public FolderSnapshot(SnapshotInfo info, Path folder, @Nullable IORunnable closeCallback) {
|
||||
this.info = info;
|
||||
// This is required to force TrueVfs to properly resolve parents.
|
||||
// Kinda odd, but whatever works.
|
||||
this.folder = folder.toAbsolutePath();
|
||||
this.closeCallback = closeCallback;
|
||||
}
|
||||
|
||||
public Path getFolder() {
|
||||
return folder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SnapshotInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
private Optional<Path> getRegionFolder() throws IOException {
|
||||
Object regFolder = regionFolder.get();
|
||||
if (regFolder == null) {
|
||||
Object update = getRegionFolder(folder);
|
||||
if (!regionFolder.compareAndSet(null, update)) {
|
||||
// failed race, get existing value
|
||||
regFolder = regionFolder.get();
|
||||
} else {
|
||||
regFolder = update;
|
||||
}
|
||||
}
|
||||
return regFolder == NOT_FOUND_TOKEN ? Optional.empty() : Optional.of((Path) regFolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getChunkTag(BlockVector3 position) throws DataException, IOException {
|
||||
BlockVector2 pos = position.toBlockVector2();
|
||||
Optional<Path> regFolder = getRegionFolder();
|
||||
if (!regFolder.isPresent()) {
|
||||
Path chunkFile = getFolder().resolve(LegacyChunkStore.getFilename(pos, "/"));
|
||||
if (!Files.exists(chunkFile)) {
|
||||
throw new MissingChunkException();
|
||||
}
|
||||
return ChunkStoreHelper.readCompoundTag(() ->
|
||||
new GZIPInputStream(Files.newInputStream(chunkFile))
|
||||
);
|
||||
}
|
||||
Path regionFile = regFolder.get().resolve(McRegionChunkStore.getFilename(pos));
|
||||
if (!Files.exists(regionFile)) {
|
||||
// Try mcr as well
|
||||
regionFile = regionFile.resolveSibling(
|
||||
regionFile.getFileName().toString().replace(".mca", ".mcr")
|
||||
);
|
||||
if (!Files.exists(regionFile)) {
|
||||
throw new MissingChunkException();
|
||||
}
|
||||
}
|
||||
try (InputStream stream = Files.newInputStream(regionFile)) {
|
||||
McRegionReader regionReader = new McRegionReader(stream);
|
||||
return ChunkStoreHelper.readCompoundTag(() -> regionReader.getChunkInputStream(pos));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (closeCallback != null) {
|
||||
closeCallback.run();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Experimental, in-testing, snapshot API. Do NOT rely on this API in plugin releases, as it will
|
||||
* move to the existing snapshot package when testing is complete.
|
||||
*
|
||||
* <p>
|
||||
* The existing snapshot API will be removed when this API is made official. It aims to have 100%
|
||||
* compatibility with old snapshot storage, bar some odd date formats.
|
||||
* </p>
|
||||
*/
|
||||
package com.sk89q.worldedit.world.snapshot.experimental;
|
||||
// TODO Un-experimentalize when ready.
|
@ -25,6 +25,7 @@ import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
import com.sk89q.worldedit.world.chunk.Chunk;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -47,7 +47,7 @@ public class ChunkStoreHelper {
|
||||
|
||||
public static CompoundTag readCompoundTag(ChunkDataInputSupplier input) throws DataException, IOException {
|
||||
try (InputStream stream = input.openInputStream();
|
||||
NBTInputStream nbt = new NBTInputStream(stream)) {
|
||||
NBTInputStream nbt = new NBTInputStream(stream)) {
|
||||
Tag tag = nbt.readNamedTag().getTag();
|
||||
if (!(tag instanceof CompoundTag)) {
|
||||
throw new ChunkStoreException("CompoundTag expected for chunk; got "
|
||||
|
@ -23,6 +23,7 @@ import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -20,8 +20,6 @@
|
||||
package com.sk89q.worldedit.world.storage;
|
||||
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.NBTInputStream;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.world.DataException;
|
||||
import com.sk89q.worldedit.world.World;
|
||||
@ -67,19 +65,11 @@ public abstract class McRegionChunkStore extends ChunkStore {
|
||||
|
||||
@Override
|
||||
public CompoundTag getChunkTag(BlockVector2 position, World world) throws DataException, IOException {
|
||||
McRegionReader reader = getReader(position, world.getName());
|
||||
return ChunkStoreHelper.readCompoundTag(() -> {
|
||||
McRegionReader reader = getReader(position, world.getName());
|
||||
|
||||
InputStream stream = reader.getChunkInputStream(position);
|
||||
Tag tag;
|
||||
|
||||
try (NBTInputStream nbt = new NBTInputStream(stream)) {
|
||||
tag = nbt.readNamedTag().getTag();
|
||||
if (!(tag instanceof CompoundTag)) {
|
||||
throw new ChunkStoreException("CompoundTag expected for chunk; got " + tag.getClass().getName());
|
||||
}
|
||||
|
||||
return (CompoundTag) tag;
|
||||
}
|
||||
return reader.getChunkInputStream(position);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user