Mirror von
https://github.com/Moulberry/AxiomPaperPlugin.git
synchronisiert 2024-11-14 04:00:05 +01:00
Implement Server Blueprints
Dieser Commit ist enthalten in:
Ursprung
b4757985de
Commit
4338fbd446
@ -1,6 +1,7 @@
|
||||
package com.moulberry.axiom;
|
||||
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import com.moulberry.axiom.blueprint.ServerBlueprintManager;
|
||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||
import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent;
|
||||
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
|
||||
@ -35,6 +36,9 @@ import org.bukkit.plugin.messaging.Messenger;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@ -50,6 +54,8 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
||||
public IdMapper<BlockState> allowedBlockRegistry = null;
|
||||
private boolean logLargeBlockBufferChanges = false;
|
||||
|
||||
public Path blueprintFolder = null;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
PLUGIN = this;
|
||||
@ -132,9 +138,13 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
||||
if (configuration.getBoolean("packet-handlers.marker-nbt-request")) {
|
||||
msg.registerIncomingPluginChannel(this, "axiom:marker_nbt_request", new MarkerNbtRequestPacketListener(this));
|
||||
}
|
||||
if (configuration.getBoolean("packet-handlers.blueprint-request")) {
|
||||
msg.registerIncomingPluginChannel(this, "axiom:request_blueprint", new BlueprintRequestPacketListener(this));
|
||||
}
|
||||
|
||||
if (configuration.getBoolean("packet-handlers.set-buffer")) {
|
||||
SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this);
|
||||
UploadBlueprintPacketListener uploadBlueprintPacketListener = new UploadBlueprintPacketListener(this);
|
||||
|
||||
ChannelInitializeListenerHolder.addListener(Key.key("axiom:handle_big_payload"), new ChannelInitializeListener() {
|
||||
@Override
|
||||
@ -153,11 +163,20 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
||||
|
||||
Connection connection = (Connection) channel.pipeline().get("packet_handler");
|
||||
channel.pipeline().addBefore("decoder", "axiom-big-payload-handler",
|
||||
new AxiomBigPayloadHandler(payloadId, connection, setBlockBufferPacketListener));
|
||||
new AxiomBigPayloadHandler(payloadId, connection, setBlockBufferPacketListener,
|
||||
uploadBlueprintPacketListener));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.configuration.getBoolean("blueprint-sharing")) {
|
||||
this.blueprintFolder = this.getDataFolder().toPath().resolve("blueprints");
|
||||
try {
|
||||
Files.createDirectories(this.blueprintFolder);
|
||||
} catch (IOException ignored) {}
|
||||
ServerBlueprintManager.initialize(this.blueprintFolder);
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
|
||||
HashSet<UUID> stillActiveAxiomPlayers = new HashSet<>();
|
||||
|
||||
|
25
src/main/java/com/moulberry/axiom/blueprint/BlockEntityMap.java
Normale Datei
25
src/main/java/com/moulberry/axiom/blueprint/BlockEntityMap.java
Normale Datei
@ -0,0 +1,25 @@
|
||||
package com.moulberry.axiom.blueprint;
|
||||
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BlockEntityMap {
|
||||
|
||||
private static final Map<Block, BlockEntityType<?>> blockBlockEntityTypeMap = new HashMap<>();
|
||||
static {
|
||||
for (BlockEntityType<?> blockEntityType : BuiltInRegistries.BLOCK_ENTITY_TYPE) {
|
||||
for (Block validBlock : blockEntityType.validBlocks) {
|
||||
blockBlockEntityTypeMap.put(validBlock, blockEntityType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static BlockEntityType<?> get(Block block) {
|
||||
return blockBlockEntityTypeMap.get(block);
|
||||
}
|
||||
|
||||
}
|
65
src/main/java/com/moulberry/axiom/blueprint/BlueprintHeader.java
Normale Datei
65
src/main/java/com/moulberry/axiom/blueprint/BlueprintHeader.java
Normale Datei
@ -0,0 +1,65 @@
|
||||
package com.moulberry.axiom.blueprint;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.StringTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public record BlueprintHeader(String name, String author, List<String> tags, float thumbnailYaw, float thumbnailPitch, boolean lockedThumbnail, int blockCount) {
|
||||
|
||||
private static final int CURRENT_VERSION = 0;
|
||||
|
||||
public void write(FriendlyByteBuf friendlyByteBuf) {
|
||||
friendlyByteBuf.writeUtf(this.name);
|
||||
friendlyByteBuf.writeUtf(this.author);
|
||||
friendlyByteBuf.writeCollection(this.tags, FriendlyByteBuf::writeUtf);
|
||||
friendlyByteBuf.writeInt(this.blockCount);
|
||||
}
|
||||
|
||||
public static BlueprintHeader read(FriendlyByteBuf friendlyByteBuf) {
|
||||
String name = friendlyByteBuf.readUtf();
|
||||
String author = friendlyByteBuf.readUtf();
|
||||
List<String> tags = friendlyByteBuf.readList(FriendlyByteBuf::readUtf);
|
||||
int blockCount = friendlyByteBuf.readInt();
|
||||
return new BlueprintHeader(name, author, tags, 0, 0, true, blockCount);
|
||||
}
|
||||
|
||||
public static BlueprintHeader load(CompoundTag tag) {
|
||||
long version = tag.getLong("Version");
|
||||
String name = tag.getString("Name");
|
||||
String author = tag.getString("Author");
|
||||
float thumbnailYaw = tag.contains("ThumbnailYaw", Tag.TAG_FLOAT) ? tag.getFloat("ThumbnailYaw") : 135;
|
||||
float thumbnailPitch = tag.contains("ThumbnailPitch", Tag.TAG_FLOAT) ? tag.getFloat("ThumbnailPitch") : 30;
|
||||
boolean lockedThumbnail = tag.getBoolean("LockedThumbnail");
|
||||
int blockCount = tag.getInt("BlockCount");
|
||||
|
||||
List<String> tags = new ArrayList<>();
|
||||
for (Tag string : tag.getList("Tags", Tag.TAG_STRING)) {
|
||||
tags.add(string.getAsString());
|
||||
}
|
||||
|
||||
return new BlueprintHeader(name, author, tags, thumbnailYaw, thumbnailPitch, lockedThumbnail, blockCount);
|
||||
}
|
||||
|
||||
public CompoundTag save(CompoundTag tag) {
|
||||
ListTag listTag = new ListTag();
|
||||
for (String string : this.tags) {
|
||||
listTag.add(StringTag.valueOf(string));
|
||||
}
|
||||
|
||||
tag.putLong("Version", CURRENT_VERSION);
|
||||
tag.putString("Name", this.name);
|
||||
tag.putString("Author", this.author);
|
||||
tag.put("Tags", listTag);
|
||||
tag.putFloat("ThumbnailYaw", this.thumbnailYaw);
|
||||
tag.putFloat("ThumbnailPitch", this.thumbnailPitch);
|
||||
tag.putBoolean("LockedThumbnail", this.lockedThumbnail);
|
||||
tag.putInt("BlockCount", this.blockCount);
|
||||
return tag;
|
||||
}
|
||||
|
||||
}
|
249
src/main/java/com/moulberry/axiom/blueprint/BlueprintIo.java
Normale Datei
249
src/main/java/com/moulberry/axiom/blueprint/BlueprintIo.java
Normale Datei
@ -0,0 +1,249 @@
|
||||
package com.moulberry.axiom.blueprint;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.Dynamic;
|
||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.datafix.DataFixers;
|
||||
import net.minecraft.util.datafix.fixes.References;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class BlueprintIo {
|
||||
|
||||
private static final int MAGIC = 0xAE5BB36;
|
||||
|
||||
private static final IOException NOT_VALID_BLUEPRINT = new IOException("Not a valid Blueprint");
|
||||
public static BlueprintHeader readHeader(InputStream inputStream) throws IOException {
|
||||
if (inputStream.available() < 4) throw NOT_VALID_BLUEPRINT;
|
||||
DataInputStream dataInputStream = new DataInputStream(inputStream);
|
||||
|
||||
int magic = dataInputStream.readInt();
|
||||
if (magic != MAGIC) throw NOT_VALID_BLUEPRINT;
|
||||
|
||||
dataInputStream.readInt(); // Ignore header length
|
||||
CompoundTag headerTag = NbtIo.read(dataInputStream);
|
||||
return BlueprintHeader.load(headerTag);
|
||||
}
|
||||
|
||||
public static RawBlueprint readRawBlueprint(InputStream inputStream) throws IOException {
|
||||
if (inputStream.available() < 4) throw NOT_VALID_BLUEPRINT;
|
||||
DataInputStream dataInputStream = new DataInputStream(inputStream);
|
||||
|
||||
int magic = dataInputStream.readInt();
|
||||
if (magic != MAGIC) throw NOT_VALID_BLUEPRINT;
|
||||
|
||||
// Header
|
||||
dataInputStream.readInt(); // Ignore header length
|
||||
CompoundTag headerTag = NbtIo.read(dataInputStream);
|
||||
BlueprintHeader header = BlueprintHeader.load(headerTag);
|
||||
|
||||
// Thumbnail
|
||||
int thumbnailLength = dataInputStream.readInt();
|
||||
byte[] thumbnailBytes = dataInputStream.readNBytes(thumbnailLength);
|
||||
if (thumbnailBytes.length < thumbnailLength) throw NOT_VALID_BLUEPRINT;
|
||||
|
||||
int currentDataVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion();
|
||||
|
||||
// Block data
|
||||
dataInputStream.readInt(); // Ignore block data length
|
||||
CompoundTag blockDataTag = NbtIo.readCompressed(dataInputStream);
|
||||
int blueprintDataVersion = blockDataTag.getInt("DataVersion");
|
||||
if (blueprintDataVersion == 0) blueprintDataVersion = currentDataVersion;
|
||||
|
||||
ListTag listTag = blockDataTag.getList("BlockRegion", Tag.TAG_COMPOUND);
|
||||
Long2ObjectMap<PalettedContainer<BlockState>> blockMap = readBlocks(listTag, blueprintDataVersion);
|
||||
|
||||
// Block Entities
|
||||
ListTag blockEntitiesTag = blockDataTag.getList("BlockEntities", Tag.TAG_COMPOUND);
|
||||
Long2ObjectMap<CompressedBlockEntity> blockEntities = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
for (Tag tag : blockEntitiesTag) {
|
||||
CompoundTag blockEntityCompound = (CompoundTag) tag;
|
||||
|
||||
// Data Fix
|
||||
if (blueprintDataVersion != currentDataVersion) {
|
||||
Dynamic<Tag> dynamic = new Dynamic<>(NbtOps.INSTANCE, blockEntityCompound);
|
||||
Dynamic<Tag> output = DataFixers.getDataFixer().update(References.BLOCK_ENTITY, dynamic,
|
||||
blueprintDataVersion, currentDataVersion);
|
||||
blockEntityCompound = (CompoundTag) output.getValue();
|
||||
}
|
||||
|
||||
BlockPos blockPos = BlockEntity.getPosFromTag(blockEntityCompound);
|
||||
long pos = blockPos.asLong();
|
||||
|
||||
String id = blockEntityCompound.getString("id");
|
||||
BlockEntityType<?> type = BuiltInRegistries.BLOCK_ENTITY_TYPE.get(new ResourceLocation(id));
|
||||
|
||||
if (type != null) {
|
||||
PalettedContainer<BlockState> container = blockMap.get(BlockPos.asLong(
|
||||
blockPos.getX() >> 4,
|
||||
blockPos.getY() >> 4,
|
||||
blockPos.getZ() >> 4
|
||||
));
|
||||
|
||||
BlockState blockState = container.get(blockPos.getX() & 0xF, blockPos.getY() & 0xF, blockPos.getZ() & 0xF);
|
||||
if (type.isValid(blockState)) {
|
||||
CompoundTag newTag = blockEntityCompound.copy();
|
||||
newTag.remove("x");
|
||||
newTag.remove("y");
|
||||
newTag.remove("z");
|
||||
newTag.remove("id");
|
||||
CompressedBlockEntity compressedBlockEntity = CompressedBlockEntity.compress(newTag, baos);
|
||||
blockEntities.put(pos, compressedBlockEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RawBlueprint(header, thumbnailBytes, blockMap, blockEntities);
|
||||
}
|
||||
|
||||
public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC,
|
||||
PalettedContainer.Strategy.SECTION_STATES, Blocks.STRUCTURE_VOID.defaultBlockState());
|
||||
|
||||
public static Long2ObjectMap<PalettedContainer<BlockState>> readBlocks(ListTag list, int dataVersion) {
|
||||
Long2ObjectMap<PalettedContainer<BlockState>> map = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
for (Tag tag : list) {
|
||||
if (tag instanceof CompoundTag compoundTag) {
|
||||
int cx = compoundTag.getInt("X");
|
||||
int cy = compoundTag.getInt("Y");
|
||||
int cz = compoundTag.getInt("Z");
|
||||
|
||||
CompoundTag blockStates = compoundTag.getCompound("BlockStates");
|
||||
blockStates = DFUHelper.updatePalettedContainer(blockStates, dataVersion);
|
||||
PalettedContainer<BlockState> container = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, blockStates)
|
||||
.getOrThrow(false, err -> {});
|
||||
map.put(BlockPos.asLong(cx, cy, cz), container);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void writeHeader(Path inPath, Path outPath, BlueprintHeader newHeader) throws IOException {
|
||||
byte[] thumbnailAndBlockBytes;
|
||||
try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(inPath))) {
|
||||
if (inputStream.available() < 4) throw NOT_VALID_BLUEPRINT;
|
||||
DataInputStream dataInputStream = new DataInputStream(inputStream);
|
||||
|
||||
int magic = dataInputStream.readInt();
|
||||
if (magic != MAGIC) throw NOT_VALID_BLUEPRINT;
|
||||
|
||||
// Header
|
||||
int headerLength = dataInputStream.readInt(); // Ignore header length
|
||||
if (dataInputStream.skip(headerLength) < headerLength) throw NOT_VALID_BLUEPRINT;
|
||||
|
||||
thumbnailAndBlockBytes = dataInputStream.readAllBytes();
|
||||
}
|
||||
|
||||
try (OutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(outPath))) {
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
|
||||
|
||||
dataOutputStream.writeInt(MAGIC);
|
||||
|
||||
// Write header
|
||||
CompoundTag headerTag = newHeader.save(new CompoundTag());
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (DataOutputStream os = new DataOutputStream(baos)) {
|
||||
NbtIo.write(headerTag, os);
|
||||
}
|
||||
dataOutputStream.writeInt(baos.size());
|
||||
baos.writeTo(dataOutputStream);
|
||||
|
||||
// Copy remaining bytes
|
||||
dataOutputStream.write(thumbnailAndBlockBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeRaw(OutputStream outputStream, RawBlueprint rawBlueprint) throws IOException {
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
|
||||
|
||||
dataOutputStream.writeInt(MAGIC);
|
||||
|
||||
// Write header
|
||||
CompoundTag headerTag = rawBlueprint.header().save(new CompoundTag());
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (DataOutputStream os = new DataOutputStream(baos)) {
|
||||
NbtIo.write(headerTag, os);
|
||||
}
|
||||
dataOutputStream.writeInt(baos.size());
|
||||
baos.writeTo(dataOutputStream);
|
||||
|
||||
// Write thumbnail
|
||||
dataOutputStream.writeInt(rawBlueprint.thumbnail().length);
|
||||
dataOutputStream.write(rawBlueprint.thumbnail());
|
||||
|
||||
// Write block data
|
||||
CompoundTag compound = new CompoundTag();
|
||||
ListTag savedBlockRegions = new ListTag();
|
||||
|
||||
for (Long2ObjectMap.Entry<PalettedContainer<BlockState>> entry : rawBlueprint.blocks().long2ObjectEntrySet()) {
|
||||
long pos = entry.getLongKey();
|
||||
PalettedContainer<BlockState> container = entry.getValue();
|
||||
|
||||
int cx = BlockPos.getX(pos);
|
||||
int cy = BlockPos.getY(pos);
|
||||
int cz = BlockPos.getZ(pos);
|
||||
|
||||
CompoundTag tag = new CompoundTag();
|
||||
tag.putInt("X", cx);
|
||||
tag.putInt("Y", cy);
|
||||
tag.putInt("Z", cz);
|
||||
Tag encoded = BlueprintIo.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, container)
|
||||
.getOrThrow(false, err -> {});
|
||||
tag.put("BlockStates", encoded);
|
||||
savedBlockRegions.add(tag);
|
||||
}
|
||||
|
||||
compound.putInt("DataVersion", SharedConstants.getCurrentVersion().getDataVersion().getVersion());
|
||||
compound.put("BlockRegion", savedBlockRegions);
|
||||
|
||||
// Write Block Entities
|
||||
ListTag blockEntitiesTag = new ListTag();
|
||||
rawBlueprint.blockEntities().forEach((pos, compressedBlockEntity) -> {
|
||||
int x = BlockPos.getX(pos);
|
||||
int y = BlockPos.getY(pos);
|
||||
int z = BlockPos.getZ(pos);
|
||||
|
||||
PalettedContainer<BlockState> container = rawBlueprint.blocks().get(BlockPos.asLong(x >> 4,y >> 4, z >> 4));
|
||||
BlockState blockState = container.get(x & 0xF, y & 0xF, z & 0xF);
|
||||
|
||||
BlockEntityType<?> type = BlockEntityMap.get(blockState.getBlock());
|
||||
if (type == null) return;
|
||||
|
||||
ResourceLocation resourceLocation = BlockEntityType.getKey(type);
|
||||
|
||||
if (resourceLocation != null) {
|
||||
CompoundTag tag = compressedBlockEntity.decompress();
|
||||
tag.putInt("x", x);
|
||||
tag.putInt("y", y);
|
||||
tag.putInt("z", z);
|
||||
tag.putString("id", resourceLocation.toString());
|
||||
blockEntitiesTag.add(tag);
|
||||
}
|
||||
});
|
||||
compound.put("BlockEntities", blockEntitiesTag);
|
||||
|
||||
baos.reset();
|
||||
NbtIo.writeCompressed(compound, baos);
|
||||
dataOutputStream.writeInt(baos.size());
|
||||
baos.writeTo(dataOutputStream);
|
||||
}
|
||||
|
||||
}
|
45
src/main/java/com/moulberry/axiom/blueprint/DFUHelper.java
Normale Datei
45
src/main/java/com/moulberry/axiom/blueprint/DFUHelper.java
Normale Datei
@ -0,0 +1,45 @@
|
||||
package com.moulberry.axiom.blueprint;
|
||||
|
||||
import com.mojang.serialization.Dynamic;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.util.datafix.DataFixers;
|
||||
import net.minecraft.util.datafix.fixes.References;
|
||||
|
||||
public class DFUHelper {
|
||||
|
||||
private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion();
|
||||
|
||||
public static CompoundTag updatePalettedContainer(CompoundTag tag, int fromVersion) {
|
||||
if (!hasExpectedPaletteTag(tag)) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
if (fromVersion == DATA_VERSION) return tag;
|
||||
|
||||
tag = tag.copy();
|
||||
|
||||
ListTag newPalette = new ListTag();
|
||||
for (Tag entry : tag.getList("palette", Tag.TAG_COMPOUND)) {
|
||||
Dynamic<Tag> dynamic = new Dynamic<>(NbtOps.INSTANCE, entry);
|
||||
Dynamic<Tag> output = DataFixers.getDataFixer().update(References.BLOCK_STATE, dynamic, fromVersion, DATA_VERSION);
|
||||
newPalette.add(output.getValue());
|
||||
}
|
||||
|
||||
tag.put("palette", newPalette);
|
||||
return tag;
|
||||
}
|
||||
|
||||
private static boolean hasExpectedPaletteTag(CompoundTag tag) {
|
||||
if (!tag.contains("palette", Tag.TAG_LIST)) return false;
|
||||
|
||||
ListTag listTag = (ListTag) tag.get("palette");
|
||||
if (listTag == null) return false;
|
||||
|
||||
return listTag.isEmpty() || listTag.getElementType() == Tag.TAG_COMPOUND;
|
||||
}
|
||||
|
||||
}
|
84
src/main/java/com/moulberry/axiom/blueprint/RawBlueprint.java
Normale Datei
84
src/main/java/com/moulberry/axiom/blueprint/RawBlueprint.java
Normale Datei
@ -0,0 +1,84 @@
|
||||
package com.moulberry.axiom.blueprint;
|
||||
|
||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
|
||||
public record RawBlueprint(BlueprintHeader header, byte[] thumbnail, Long2ObjectMap<PalettedContainer<BlockState>> blocks,
|
||||
Long2ObjectMap<CompressedBlockEntity> blockEntities) {
|
||||
|
||||
public static void writeHeader(FriendlyByteBuf friendlyByteBuf, RawBlueprint rawBlueprint) {
|
||||
rawBlueprint.header.write(friendlyByteBuf);
|
||||
friendlyByteBuf.writeByteArray(rawBlueprint.thumbnail);
|
||||
}
|
||||
|
||||
public static RawBlueprint readHeader(FriendlyByteBuf friendlyByteBuf) {
|
||||
BlueprintHeader header = BlueprintHeader.read(friendlyByteBuf);
|
||||
byte[] thumbnail = friendlyByteBuf.readByteArray();
|
||||
|
||||
return new RawBlueprint(header, thumbnail, null, null);
|
||||
}
|
||||
|
||||
public static void write(FriendlyByteBuf friendlyByteBuf, RawBlueprint rawBlueprint) {
|
||||
rawBlueprint.header.write(friendlyByteBuf);
|
||||
friendlyByteBuf.writeByteArray(rawBlueprint.thumbnail);
|
||||
|
||||
LongSet chunkKeys = rawBlueprint.blocks.keySet();
|
||||
friendlyByteBuf.writeVarInt(chunkKeys.size());
|
||||
|
||||
LongIterator longIterator = chunkKeys.longIterator();
|
||||
while (longIterator.hasNext()) {
|
||||
long pos = longIterator.nextLong();
|
||||
friendlyByteBuf.writeLong(pos);
|
||||
rawBlueprint.blocks.get(pos).write(friendlyByteBuf);
|
||||
}
|
||||
|
||||
LongSet blockEntityKeys = rawBlueprint.blockEntities.keySet();
|
||||
friendlyByteBuf.writeVarInt(blockEntityKeys.size());
|
||||
|
||||
longIterator = blockEntityKeys.longIterator();
|
||||
while (longIterator.hasNext()) {
|
||||
long pos = longIterator.nextLong();
|
||||
friendlyByteBuf.writeLong(pos);
|
||||
rawBlueprint.blockEntities.get(pos).write(friendlyByteBuf);
|
||||
}
|
||||
}
|
||||
|
||||
public static RawBlueprint read(FriendlyByteBuf friendlyByteBuf) {
|
||||
BlueprintHeader header = BlueprintHeader.read(friendlyByteBuf);
|
||||
byte[] thumbnail = friendlyByteBuf.readByteArray();
|
||||
|
||||
Long2ObjectMap<PalettedContainer<BlockState>> blocks = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
int chunkCount = friendlyByteBuf.readVarInt();
|
||||
for (int i = 0; i < chunkCount; i++) {
|
||||
long pos = friendlyByteBuf.readLong();
|
||||
|
||||
PalettedContainer<BlockState> palettedContainer = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY,
|
||||
Blocks.STRUCTURE_VOID.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
|
||||
palettedContainer.read(friendlyByteBuf);
|
||||
|
||||
blocks.put(pos, palettedContainer);
|
||||
}
|
||||
|
||||
Long2ObjectMap<CompressedBlockEntity> blockEntities = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
int blockEntityCount = friendlyByteBuf.readVarInt();
|
||||
for (int i = 0; i < blockEntityCount; i++) {
|
||||
long pos = friendlyByteBuf.readLong();
|
||||
|
||||
CompressedBlockEntity compressedBlockEntity = CompressedBlockEntity.read(friendlyByteBuf);
|
||||
blockEntities.put(pos, compressedBlockEntity);
|
||||
}
|
||||
|
||||
return new RawBlueprint(header, thumbnail, blocks, blockEntities);
|
||||
}
|
||||
|
||||
}
|
107
src/main/java/com/moulberry/axiom/blueprint/ServerBlueprintManager.java
Normale Datei
107
src/main/java/com/moulberry/axiom/blueprint/ServerBlueprintManager.java
Normale Datei
@ -0,0 +1,107 @@
|
||||
package com.moulberry.axiom.blueprint;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.packet.CustomByteArrayPayload;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class ServerBlueprintManager {
|
||||
|
||||
private static ServerBlueprintRegistry registry = null;
|
||||
|
||||
public static void initialize(Path blueprintDirectory) {
|
||||
Map<String, RawBlueprint> map = new HashMap<>();
|
||||
loadRegistryFromFolder(map, blueprintDirectory, "/");
|
||||
registry = new ServerBlueprintRegistry(map);
|
||||
}
|
||||
|
||||
private static final int MAX_SIZE = 1000000;
|
||||
private static final ResourceLocation PACKET_BLUEPRINT_MANIFEST_IDENTIFIER = new ResourceLocation("axiom:blueprint_manifest");
|
||||
|
||||
public static void sendManifest(List<ServerPlayer> serverPlayers) {
|
||||
if (registry != null) {
|
||||
List<ServerPlayer> sendTo = new ArrayList<>();
|
||||
|
||||
for (ServerPlayer serverPlayer : serverPlayers) {
|
||||
CraftPlayer craftPlayer = serverPlayer.getBukkitEntity();
|
||||
if (AxiomPaper.PLUGIN.canUseAxiom(craftPlayer) &&
|
||||
craftPlayer.getListeningPluginChannels().contains("axiom:blueprint_manifest")) {
|
||||
sendTo.add(serverPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
if (sendTo.isEmpty()) return;
|
||||
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
buf.writeBoolean(true); // replace
|
||||
|
||||
for (Map.Entry<String, RawBlueprint> entry : registry.blueprints().entrySet()) {
|
||||
buf.writeUtf(entry.getKey());
|
||||
RawBlueprint.writeHeader(buf, entry.getValue());
|
||||
|
||||
if (buf.writerIndex() > MAX_SIZE) {
|
||||
// Finish and send current packet
|
||||
buf.writeUtf("");
|
||||
byte[] bytes = new byte[buf.writerIndex()];
|
||||
buf.getBytes(0, bytes);
|
||||
var payload = new CustomByteArrayPayload(PACKET_BLUEPRINT_MANIFEST_IDENTIFIER, bytes);
|
||||
for (ServerPlayer serverPlayer : sendTo) {
|
||||
serverPlayer.connection.send(new ClientboundCustomPayloadPacket(payload));
|
||||
}
|
||||
|
||||
// Continue
|
||||
buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
buf.writeBoolean(false); // don't replace
|
||||
}
|
||||
}
|
||||
|
||||
buf.writeUtf("");
|
||||
byte[] bytes = new byte[buf.writerIndex()];
|
||||
buf.getBytes(0, bytes);
|
||||
var payload = new CustomByteArrayPayload(PACKET_BLUEPRINT_MANIFEST_IDENTIFIER, bytes);
|
||||
for (ServerPlayer serverPlayer : sendTo) {
|
||||
serverPlayer.connection.send(new ClientboundCustomPayloadPacket(payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ServerBlueprintRegistry getRegistry() {
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static void loadRegistryFromFolder(Map<String, RawBlueprint> map, Path folder, String location) {
|
||||
if (!Files.isDirectory(folder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(folder)) {
|
||||
for (Path path : directoryStream) {
|
||||
String filename = path.getFileName().toString();
|
||||
if (filename.endsWith(".bp")) {
|
||||
try {
|
||||
RawBlueprint rawBlueprint = BlueprintIo.readRawBlueprint(new BufferedInputStream(Files.newInputStream(path)));
|
||||
String newLocation = location + filename.substring(0, filename.length()-3);
|
||||
map.put(newLocation, rawBlueprint);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if (Files.isDirectory(path)) {
|
||||
String newLocation = location + filename + "/";
|
||||
loadRegistryFromFolder(map, path, newLocation);
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.moulberry.axiom.blueprint;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public record ServerBlueprintRegistry(Map<String, RawBlueprint> blueprints) {
|
||||
|
||||
public void writeManifest(FriendlyByteBuf friendlyByteBuf) {
|
||||
for (Map.Entry<String, RawBlueprint> entry : this.blueprints.entrySet()) {
|
||||
friendlyByteBuf.writeUtf(entry.getKey());
|
||||
RawBlueprint.writeHeader(friendlyByteBuf, entry.getValue());
|
||||
}
|
||||
friendlyByteBuf.writeUtf("");
|
||||
}
|
||||
|
||||
public static ServerBlueprintRegistry readManifest(FriendlyByteBuf friendlyByteBuf) {
|
||||
Map<String, RawBlueprint> blueprints = new HashMap<>();
|
||||
|
||||
while (true) {
|
||||
String path = friendlyByteBuf.readUtf();
|
||||
if (path.isEmpty()) {
|
||||
return new ServerBlueprintRegistry(blueprints);
|
||||
}
|
||||
|
||||
blueprints.put(path, RawBlueprint.readHeader(friendlyByteBuf));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -19,7 +19,7 @@ public record MarkerData(UUID uuid, Vec3 position, @Nullable String name, @Nulla
|
||||
int lineArgb, float lineThickness, int faceArgb) {
|
||||
public static MarkerData read(FriendlyByteBuf friendlyByteBuf) {
|
||||
UUID uuid = friendlyByteBuf.readUUID();
|
||||
Vec3 position = friendlyByteBuf.readVec3();
|
||||
Vec3 position = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
|
||||
String name = friendlyByteBuf.readNullable(FriendlyByteBuf::readUtf);
|
||||
|
||||
Vec3 minRegion = null;
|
||||
@ -31,8 +31,8 @@ public record MarkerData(UUID uuid, Vec3 position, @Nullable String name, @Nulla
|
||||
byte flags = friendlyByteBuf.readByte();
|
||||
|
||||
if (flags != 0) {
|
||||
minRegion = friendlyByteBuf.readVec3();
|
||||
maxRegion = friendlyByteBuf.readVec3();
|
||||
minRegion = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
|
||||
maxRegion = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
|
||||
|
||||
if ((flags & 2) != 0) {
|
||||
lineArgb = friendlyByteBuf.readInt();
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
@ -14,14 +15,18 @@ import java.util.List;
|
||||
public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
||||
|
||||
private static final ResourceLocation SET_BUFFER = new ResourceLocation("axiom", "set_buffer");
|
||||
private static final ResourceLocation UPLOAD_BLUEPRINT = new ResourceLocation("axiom", "upload_blueprint");
|
||||
private final int payloadId;
|
||||
private final Connection connection;
|
||||
private final SetBlockBufferPacketListener listener;
|
||||
private final SetBlockBufferPacketListener setBlockBuffer;
|
||||
private final UploadBlueprintPacketListener uploadBlueprint;
|
||||
|
||||
public AxiomBigPayloadHandler(int payloadId, Connection connection, SetBlockBufferPacketListener listener) {
|
||||
public AxiomBigPayloadHandler(int payloadId, Connection connection, SetBlockBufferPacketListener setBlockBuffer,
|
||||
UploadBlueprintPacketListener uploadBlueprint) {
|
||||
this.payloadId = payloadId;
|
||||
this.connection = connection;
|
||||
this.listener = listener;
|
||||
this.setBlockBuffer = setBlockBuffer;
|
||||
this.uploadBlueprint = uploadBlueprint;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -44,12 +49,19 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
||||
ResourceLocation identifier = buf.readResourceLocation();
|
||||
if (identifier.equals(SET_BUFFER)) {
|
||||
ServerPlayer player = connection.getPlayer();
|
||||
if (player != null && player.getBukkitEntity().hasPermission("axiom.*")) {
|
||||
if (listener.onReceive(player, buf)) {
|
||||
if (AxiomPaper.PLUGIN.canUseAxiom(player.getBukkitEntity())) {
|
||||
setBlockBuffer.onReceive(player, buf);
|
||||
success = true;
|
||||
in.skipBytes(in.readableBytes());
|
||||
return;
|
||||
}
|
||||
} else if (identifier.equals(UPLOAD_BLUEPRINT)) {
|
||||
ServerPlayer player = connection.getPlayer();
|
||||
if (AxiomPaper.PLUGIN.canUseAxiom(player.getBukkitEntity())) {
|
||||
uploadBlueprint.onReceive(player, buf);
|
||||
success = true;
|
||||
in.skipBytes(in.readableBytes());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -74,7 +86,7 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
||||
if (evt == ConnectionEvent.COMPRESSION_THRESHOLD_SET || evt == ConnectionEvent.COMPRESSION_DISABLED) {
|
||||
ctx.channel().pipeline().remove("axiom-big-payload-handler");
|
||||
ctx.channel().pipeline().addBefore("decoder", "axiom-big-payload-handler",
|
||||
new AxiomBigPayloadHandler(payloadId, connection, listener));
|
||||
new AxiomBigPayloadHandler(payloadId, connection, setBlockBuffer, uploadBlueprint));
|
||||
}
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.blueprint.RawBlueprint;
|
||||
import com.moulberry.axiom.blueprint.ServerBlueprintManager;
|
||||
import com.moulberry.axiom.blueprint.ServerBlueprintRegistry;
|
||||
import com.moulberry.axiom.marker.MarkerData;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.Marker;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BlueprintRequestPacketListener implements PluginMessageListener {
|
||||
|
||||
private final AxiomPaper plugin;
|
||||
public BlueprintRequestPacketListener(AxiomPaper plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private static final ResourceLocation RESPONSE_PACKET_IDENTIFIER = new ResourceLocation("axiom:response_blueprint");
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||
if (!this.plugin.canUseAxiom(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||
String path = friendlyByteBuf.readUtf();
|
||||
|
||||
ServerBlueprintRegistry registry = ServerBlueprintManager.getRegistry();
|
||||
if (registry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
RawBlueprint rawBlueprint = registry.blueprints().get(path);
|
||||
if (rawBlueprint != null) {
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
|
||||
buf.writeUtf(path);
|
||||
RawBlueprint.write(buf, rawBlueprint);
|
||||
|
||||
byte[] bytes = new byte[buf.writerIndex()];
|
||||
buf.getBytes(0, bytes);
|
||||
var payload = new CustomByteArrayPayload(RESPONSE_PACKET_IDENTIFIER, bytes);
|
||||
((CraftPlayer)player).getHandle().connection.send(new ClientboundCustomPayloadPacket(payload));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -5,6 +5,7 @@ import com.moulberry.axiom.AxiomConstants;
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.View;
|
||||
import com.moulberry.axiom.WorldExtension;
|
||||
import com.moulberry.axiom.blueprint.ServerBlueprintManager;
|
||||
import com.moulberry.axiom.event.AxiomHandshakeEvent;
|
||||
import com.moulberry.axiom.persistence.ItemStackDataType;
|
||||
import com.moulberry.axiom.persistence.UUIDDataType;
|
||||
@ -17,6 +18,7 @@ import net.minecraft.network.FriendlyByteBuf;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@ -25,6 +27,7 @@ import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -170,6 +173,8 @@ public class HelloPacketListener implements PluginMessageListener {
|
||||
}
|
||||
|
||||
WorldExtension.onPlayerJoin(world, player);
|
||||
|
||||
ServerBlueprintManager.sendManifest(List.of(((CraftPlayer)player).getHandle()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -68,9 +68,9 @@ public class SetBlockBufferPacketListener {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onReceive(ServerPlayer player, FriendlyByteBuf friendlyByteBuf) {
|
||||
public void onReceive(ServerPlayer player, FriendlyByteBuf friendlyByteBuf) {
|
||||
MinecraftServer server = player.getServer();
|
||||
if (server == null) return false;
|
||||
if (server == null) return;
|
||||
|
||||
ResourceKey<Level> worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION);
|
||||
friendlyByteBuf.readUUID(); // Discard, we don't need to associate buffers
|
||||
@ -116,8 +116,6 @@ public class SetBlockBufferPacketListener {
|
||||
} else {
|
||||
throw new RuntimeException("Unknown buffer type: " + type);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applyBlockBuffer(ServerPlayer player, MinecraftServer server, BlockBuffer buffer, ResourceKey<Level> worldKey) {
|
||||
|
@ -0,0 +1,83 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.blueprint.BlueprintIo;
|
||||
import com.moulberry.axiom.blueprint.RawBlueprint;
|
||||
import com.moulberry.axiom.blueprint.ServerBlueprintManager;
|
||||
import com.moulberry.axiom.blueprint.ServerBlueprintRegistry;
|
||||
import com.moulberry.axiom.marker.MarkerData;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.Marker;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UploadBlueprintPacketListener {
|
||||
|
||||
private final AxiomPaper plugin;
|
||||
public UploadBlueprintPacketListener(AxiomPaper plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void onReceive(ServerPlayer serverPlayer, FriendlyByteBuf friendlyByteBuf) {
|
||||
if (!this.plugin.canUseAxiom(serverPlayer.getBukkitEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerBlueprintRegistry registry = ServerBlueprintManager.getRegistry();
|
||||
if (registry == null || this.plugin.blueprintFolder == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String pathStr = friendlyByteBuf.readUtf();
|
||||
RawBlueprint rawBlueprint = RawBlueprint.read(friendlyByteBuf);
|
||||
|
||||
pathStr = pathStr.replace("\\", "/");
|
||||
|
||||
if (!pathStr.endsWith(".bp") || pathStr.contains("..") || !pathStr.startsWith("/")) {
|
||||
return;
|
||||
}
|
||||
|
||||
pathStr = pathStr.substring(1);
|
||||
|
||||
Path relative = Path.of(pathStr).normalize();
|
||||
if (relative.isAbsolute()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path path = this.plugin.blueprintFolder.resolve(relative);
|
||||
|
||||
// Write file
|
||||
try {
|
||||
Files.createDirectories(path.getParent());
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
try (OutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(path))) {
|
||||
BlueprintIo.writeRaw(outputStream, rawBlueprint);
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update registry
|
||||
registry.blueprints().put("/" + pathStr.substring(0, pathStr.length()-3), rawBlueprint);
|
||||
|
||||
// Resend manifest
|
||||
ServerBlueprintManager.sendManifest(serverPlayer.getServer().getPlayerList().getPlayers());
|
||||
}
|
||||
|
||||
}
|
@ -11,6 +11,9 @@ max-chunk-load-distance: 128
|
||||
# Whether players are allowed to teleport between worlds using views
|
||||
allow-teleport-between-worlds: true
|
||||
|
||||
# Whether to allow clients to save/load/share blueprints through the server
|
||||
blueprint-sharing: false
|
||||
|
||||
# Action to take when a user with an incompatible Minecraft version or Axiom version joins
|
||||
# Valid actions are 'kick', 'warn' and 'ignore'
|
||||
# 'warn' will give the player a warning and disable Axiom
|
||||
@ -76,3 +79,4 @@ packet-handlers:
|
||||
manipulate-entity: true
|
||||
delete-entity: true
|
||||
marker-nbt-request: true
|
||||
blueprint-request: true
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren