Mirror von
https://github.com/Moulberry/AxiomPaperPlugin.git
synchronisiert 2024-11-14 12:10:05 +01:00
Implement Server Blueprints
Dieser Commit ist enthalten in:
Ursprung
b4757985de
Commit
4338fbd446
@ -1,6 +1,7 @@
|
|||||||
package com.moulberry.axiom;
|
package com.moulberry.axiom;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.RateLimiter;
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
import com.moulberry.axiom.blueprint.ServerBlueprintManager;
|
||||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||||
import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent;
|
import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent;
|
||||||
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
|
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.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@ -50,6 +54,8 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
|||||||
public IdMapper<BlockState> allowedBlockRegistry = null;
|
public IdMapper<BlockState> allowedBlockRegistry = null;
|
||||||
private boolean logLargeBlockBufferChanges = false;
|
private boolean logLargeBlockBufferChanges = false;
|
||||||
|
|
||||||
|
public Path blueprintFolder = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
PLUGIN = this;
|
PLUGIN = this;
|
||||||
@ -132,9 +138,13 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
|||||||
if (configuration.getBoolean("packet-handlers.marker-nbt-request")) {
|
if (configuration.getBoolean("packet-handlers.marker-nbt-request")) {
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:marker_nbt_request", new MarkerNbtRequestPacketListener(this));
|
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")) {
|
if (configuration.getBoolean("packet-handlers.set-buffer")) {
|
||||||
SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this);
|
SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this);
|
||||||
|
UploadBlueprintPacketListener uploadBlueprintPacketListener = new UploadBlueprintPacketListener(this);
|
||||||
|
|
||||||
ChannelInitializeListenerHolder.addListener(Key.key("axiom:handle_big_payload"), new ChannelInitializeListener() {
|
ChannelInitializeListenerHolder.addListener(Key.key("axiom:handle_big_payload"), new ChannelInitializeListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -153,11 +163,20 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
|||||||
|
|
||||||
Connection connection = (Connection) channel.pipeline().get("packet_handler");
|
Connection connection = (Connection) channel.pipeline().get("packet_handler");
|
||||||
channel.pipeline().addBefore("decoder", "axiom-big-payload-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, () -> {
|
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
|
||||||
HashSet<UUID> stillActiveAxiomPlayers = new HashSet<>();
|
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) {
|
int lineArgb, float lineThickness, int faceArgb) {
|
||||||
public static MarkerData read(FriendlyByteBuf friendlyByteBuf) {
|
public static MarkerData read(FriendlyByteBuf friendlyByteBuf) {
|
||||||
UUID uuid = friendlyByteBuf.readUUID();
|
UUID uuid = friendlyByteBuf.readUUID();
|
||||||
Vec3 position = friendlyByteBuf.readVec3();
|
Vec3 position = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
|
||||||
String name = friendlyByteBuf.readNullable(FriendlyByteBuf::readUtf);
|
String name = friendlyByteBuf.readNullable(FriendlyByteBuf::readUtf);
|
||||||
|
|
||||||
Vec3 minRegion = null;
|
Vec3 minRegion = null;
|
||||||
@ -31,8 +31,8 @@ public record MarkerData(UUID uuid, Vec3 position, @Nullable String name, @Nulla
|
|||||||
byte flags = friendlyByteBuf.readByte();
|
byte flags = friendlyByteBuf.readByte();
|
||||||
|
|
||||||
if (flags != 0) {
|
if (flags != 0) {
|
||||||
minRegion = friendlyByteBuf.readVec3();
|
minRegion = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
|
||||||
maxRegion = friendlyByteBuf.readVec3();
|
maxRegion = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
|
||||||
|
|
||||||
if ((flags & 2) != 0) {
|
if ((flags & 2) != 0) {
|
||||||
lineArgb = friendlyByteBuf.readInt();
|
lineArgb = friendlyByteBuf.readInt();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
@ -14,14 +15,18 @@ import java.util.List;
|
|||||||
public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
||||||
|
|
||||||
private static final ResourceLocation SET_BUFFER = new ResourceLocation("axiom", "set_buffer");
|
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 int payloadId;
|
||||||
private final Connection connection;
|
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.payloadId = payloadId;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.listener = listener;
|
this.setBlockBuffer = setBlockBuffer;
|
||||||
|
this.uploadBlueprint = uploadBlueprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -44,12 +49,19 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
|||||||
ResourceLocation identifier = buf.readResourceLocation();
|
ResourceLocation identifier = buf.readResourceLocation();
|
||||||
if (identifier.equals(SET_BUFFER)) {
|
if (identifier.equals(SET_BUFFER)) {
|
||||||
ServerPlayer player = connection.getPlayer();
|
ServerPlayer player = connection.getPlayer();
|
||||||
if (player != null && player.getBukkitEntity().hasPermission("axiom.*")) {
|
if (AxiomPaper.PLUGIN.canUseAxiom(player.getBukkitEntity())) {
|
||||||
if (listener.onReceive(player, buf)) {
|
setBlockBuffer.onReceive(player, buf);
|
||||||
success = true;
|
success = true;
|
||||||
in.skipBytes(in.readableBytes());
|
in.skipBytes(in.readableBytes());
|
||||||
return;
|
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) {
|
if (evt == ConnectionEvent.COMPRESSION_THRESHOLD_SET || evt == ConnectionEvent.COMPRESSION_DISABLED) {
|
||||||
ctx.channel().pipeline().remove("axiom-big-payload-handler");
|
ctx.channel().pipeline().remove("axiom-big-payload-handler");
|
||||||
ctx.channel().pipeline().addBefore("decoder", "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);
|
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.AxiomPaper;
|
||||||
import com.moulberry.axiom.View;
|
import com.moulberry.axiom.View;
|
||||||
import com.moulberry.axiom.WorldExtension;
|
import com.moulberry.axiom.WorldExtension;
|
||||||
|
import com.moulberry.axiom.blueprint.ServerBlueprintManager;
|
||||||
import com.moulberry.axiom.event.AxiomHandshakeEvent;
|
import com.moulberry.axiom.event.AxiomHandshakeEvent;
|
||||||
import com.moulberry.axiom.persistence.ItemStackDataType;
|
import com.moulberry.axiom.persistence.ItemStackDataType;
|
||||||
import com.moulberry.axiom.persistence.UUIDDataType;
|
import com.moulberry.axiom.persistence.UUIDDataType;
|
||||||
@ -17,6 +18,7 @@ import net.minecraft.network.FriendlyByteBuf;
|
|||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.World;
|
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.craftbukkit.v1_20_R2.inventory.CraftItemStack;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
@ -25,6 +27,7 @@ import org.bukkit.persistence.PersistentDataType;
|
|||||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -170,6 +173,8 @@ public class HelloPacketListener implements PluginMessageListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WorldExtension.onPlayerJoin(world, player);
|
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();
|
MinecraftServer server = player.getServer();
|
||||||
if (server == null) return false;
|
if (server == null) return;
|
||||||
|
|
||||||
ResourceKey<Level> worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION);
|
ResourceKey<Level> worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION);
|
||||||
friendlyByteBuf.readUUID(); // Discard, we don't need to associate buffers
|
friendlyByteBuf.readUUID(); // Discard, we don't need to associate buffers
|
||||||
@ -116,8 +116,6 @@ public class SetBlockBufferPacketListener {
|
|||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Unknown buffer type: " + type);
|
throw new RuntimeException("Unknown buffer type: " + type);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyBlockBuffer(ServerPlayer player, MinecraftServer server, BlockBuffer buffer, ResourceKey<Level> worldKey) {
|
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
|
# Whether players are allowed to teleport between worlds using views
|
||||||
allow-teleport-between-worlds: true
|
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
|
# Action to take when a user with an incompatible Minecraft version or Axiom version joins
|
||||||
# Valid actions are 'kick', 'warn' and 'ignore'
|
# Valid actions are 'kick', 'warn' and 'ignore'
|
||||||
# 'warn' will give the player a warning and disable Axiom
|
# 'warn' will give the player a warning and disable Axiom
|
||||||
@ -76,3 +79,4 @@ packet-handlers:
|
|||||||
manipulate-entity: true
|
manipulate-entity: true
|
||||||
delete-entity: true
|
delete-entity: true
|
||||||
marker-nbt-request: true
|
marker-nbt-request: true
|
||||||
|
blueprint-request: true
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren