From 141ffaf1f2c523020dcc506442306a79df2bf2de Mon Sep 17 00:00:00 2001 From: Moulberry Date: Fri, 3 May 2024 19:23:31 +0800 Subject: [PATCH] Partial ViaVersion support --- build.gradle.kts | 4 ++ .../java/com/moulberry/axiom/AxiomPaper.java | 11 ++++ .../com/moulberry/axiom/DisallowedBlocks.java | 1 + .../moulberry/axiom/buffer/BlockBuffer.java | 16 ++--- .../axiom/packet/AxiomBigPayloadHandler.java | 14 +++-- .../axiom/packet/HelloPacketListener.java | 62 +++++++++++++++---- .../RequestChunkDataPacketListener.java | 3 +- .../packet/SetBlockBufferPacketListener.java | 2 +- .../axiom/packet/SetBlockPacketListener.java | 12 +++- .../packet/UploadBlueprintPacketListener.java | 41 +++++++----- .../axiom/viaversion/ViaVersionHelper.java | 42 ++++++++++++- src/main/resources/plugin.yml | 4 +- 12 files changed, 165 insertions(+), 47 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c7375d5..7b6f9d4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,7 @@ java { repositories { mavenCentral() + maven("https://repo.viaversion.com") maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") maven("https://jitpack.io") maven("https://repo.papermc.io/repository/maven-public/") @@ -31,6 +32,9 @@ dependencies { // Zstd Compression Library implementation("com.github.luben:zstd-jni:1.5.5-4") + // ViaVersion support + compileOnly("com.viaversion:viaversion-api:4.9.4-SNAPSHOT") + // WorldGuard support compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.1.0-SNAPSHOT") diff --git a/src/main/java/com/moulberry/axiom/AxiomPaper.java b/src/main/java/com/moulberry/axiom/AxiomPaper.java index 0a7f84b..c34c66c 100644 --- a/src/main/java/com/moulberry/axiom/AxiomPaper.java +++ b/src/main/java/com/moulberry/axiom/AxiomPaper.java @@ -36,6 +36,7 @@ import org.bukkit.plugin.messaging.Messenger; import org.checkerframework.checker.nullness.qual.NonNull; import org.jetbrains.annotations.Nullable; +import java.io.FileReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -49,6 +50,7 @@ public class AxiomPaper extends JavaPlugin implements Listener { public final Set activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>()); public final Map playerBlockBufferRateLimiters = new ConcurrentHashMap<>(); public final Map playerRestrictions = new ConcurrentHashMap<>(); + public final Map> playerBlockRegistry = new ConcurrentHashMap<>(); public Configuration configuration; public IdMapper allowedBlockRegistry = null; @@ -265,6 +267,7 @@ public class AxiomPaper extends JavaPlugin implements Listener { activeAxiomPlayers.retainAll(stillActiveAxiomPlayers); playerBlockBufferRateLimiters.keySet().retainAll(stillActiveAxiomPlayers); playerRestrictions.keySet().retainAll(stillActiveAxiomPlayers); + playerBlockRegistry.keySet().retainAll(stillActiveAxiomPlayers); }, 20, 20); boolean sendMarkers = configuration.getBoolean("send-markers"); @@ -292,6 +295,14 @@ public class AxiomPaper extends JavaPlugin implements Listener { return this.playerBlockBufferRateLimiters.get(uuid); } + public boolean hasCustomBlockRegistry(UUID uuid) { + return this.playerBlockRegistry.containsKey(uuid); + } + + public IdMapper getBlockRegistry(UUID uuid) { + return this.playerBlockRegistry.getOrDefault(uuid, this.allowedBlockRegistry); + } + private final WeakHashMap worldProperties = new WeakHashMap<>(); public @Nullable ServerWorldPropertiesRegistry getWorldPropertiesIfPresent(World world) { diff --git a/src/main/java/com/moulberry/axiom/DisallowedBlocks.java b/src/main/java/com/moulberry/axiom/DisallowedBlocks.java index f80d2b8..ef4ab3a 100644 --- a/src/main/java/com/moulberry/axiom/DisallowedBlocks.java +++ b/src/main/java/com/moulberry/axiom/DisallowedBlocks.java @@ -3,6 +3,7 @@ package com.moulberry.axiom; import com.mojang.brigadier.StringReader; import com.mojang.datafixers.util.Either; import com.moulberry.axiom.buffer.BlockBuffer; +import com.viaversion.viaversion.api.data.Mappings; import net.minecraft.commands.arguments.blocks.BlockPredicateArgument; import net.minecraft.commands.arguments.blocks.BlockStateParser; import net.minecraft.core.IdMapper; diff --git a/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java b/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java index aa24244..b7fdcd7 100644 --- a/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java +++ b/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java @@ -9,6 +9,7 @@ import it.unimi.dsi.fastutil.objects.ObjectSet; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; import net.minecraft.core.BlockPos; +import net.minecraft.core.IdMapper; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; @@ -23,18 +24,16 @@ public class BlockBuffer { private final Long2ObjectMap> values; + private IdMapper registry; private PalettedContainer last = null; private long lastId = AxiomConstants.MIN_POSITION_LONG; private final Long2ObjectMap> blockEntities = new Long2ObjectOpenHashMap<>(); private long totalBlockEntities = 0; private long totalBlockEntityBytes = 0; - public BlockBuffer() { + public BlockBuffer(IdMapper registry) { this.values = new Long2ObjectOpenHashMap<>(); - } - - public BlockBuffer(Long2ObjectMap> values) { - this.values = values; + this.registry = registry; } public void save(FriendlyByteBuf friendlyByteBuf) { @@ -57,8 +56,9 @@ public class BlockBuffer { friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG); } - public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit) { - BlockBuffer buffer = new BlockBuffer(); + public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit, + IdMapper registry) { + BlockBuffer buffer = new BlockBuffer(registry); long totalBlockEntities = 0; long totalBlockEntityBytes = 0; @@ -177,7 +177,7 @@ public class BlockBuffer { public PalettedContainer getOrCreateSection(long id) { if (this.last == null || id != this.lastId) { this.lastId = id; - this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(AxiomPaper.PLUGIN.allowedBlockRegistry, + this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(this.registry, EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES)); } diff --git a/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java b/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java index 83eb52d..520bda6 100644 --- a/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java +++ b/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java @@ -62,8 +62,7 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder { } else if (identifier.equals(UPLOAD_BLUEPRINT)) { ServerPlayer player = connection.getPlayer(); if (AxiomPaper.PLUGIN.canUseAxiom(player.getBukkitEntity())) { - player.getServer().execute(() -> uploadBlueprint.onReceive(player, buf)); - + uploadBlueprint.onReceive(player, buf); success = true; in.skipBytes(in.readableBytes()); return; @@ -74,8 +73,15 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder { byte[] bytes = new byte[buf.writerIndex() - buf.readerIndex()]; buf.getBytes(buf.readerIndex(), bytes); - player.getServer().execute(() -> requestChunkDataPacketListener.onPluginMessageReceived( - identifier.toString(), player.getBukkitEntity(), bytes)); + player.getServer().execute(() -> { + try { + requestChunkDataPacketListener.onPluginMessageReceived( + identifier.toString(), player.getBukkitEntity(), bytes); + } catch (Throwable t) { + player.getBukkitEntity().kick(net.kyori.adventure.text.Component.text( + "An error occured while requesting chunk data: " + t.getMessage())); + } + }); success = true; in.skipBytes(in.readableBytes()); diff --git a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java index 7c61eba..7adc864 100644 --- a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java @@ -1,15 +1,17 @@ package com.moulberry.axiom.packet; import com.google.common.util.concurrent.RateLimiter; -import com.moulberry.axiom.AxiomConstants; -import com.moulberry.axiom.AxiomPaper; -import com.moulberry.axiom.View; -import com.moulberry.axiom.WorldExtension; +import com.moulberry.axiom.*; import com.moulberry.axiom.blueprint.ServerBlueprintManager; import com.moulberry.axiom.event.AxiomHandshakeEvent; import com.moulberry.axiom.persistence.ItemStackDataType; import com.moulberry.axiom.persistence.UUIDDataType; +import com.moulberry.axiom.viaversion.ViaVersionHelper; import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.*; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import com.viaversion.viaversion.libs.opennbt.tag.builtin.CompoundTag; import io.netty.buffer.Unpooled; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -52,18 +54,52 @@ public class HelloPacketListener implements PluginMessageListener { int serverDataVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); if (dataVersion != serverDataVersion) { - Component text = Component.text("Axiom: Incompatible data version detected (client " + dataVersion + - ", server " + serverDataVersion + "), are you using ViaVersion?"); + if (!Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) { + Component text = Component.text("Axiom: Incompatible data version detected (client " + dataVersion + + ", server " + serverDataVersion + ")"); - String incompatibleDataVersion = plugin.configuration.getString("incompatible-data-version"); - if (incompatibleDataVersion == null) incompatibleDataVersion = "kick"; - if (incompatibleDataVersion.equals("warn")) { + String incompatibleDataVersion = plugin.configuration.getString("incompatible-data-version"); + if (incompatibleDataVersion == null) incompatibleDataVersion = "kick"; + if (incompatibleDataVersion.equals("warn")) { + player.sendMessage(text.color(NamedTextColor.RED)); + return; + } else if (!incompatibleDataVersion.equals("ignore")) { + player.kick(text); + return; + } + } else { +// int playerVersion = Via.getAPI().getPlayerVersion(player.getUniqueId()); +// if (ProtocolVersion.isRegistered(playerVersion)) { +// ProtocolVersion version = ProtocolVersion.getProtocol(playerVersion); +// String name = version.getName().split("/")[0]; +// +// +// } + + CompoundTag tag = MappingDataLoader.loadNBT("mappings-1.20.2to1.20.3.nbt"); + + if (tag == null) { + player.kick(Component.text("Axiom+ViaVersion: Failed to load mappings (1.20.2 <-> 1.20.3)")); + return; + } + + Mappings mappings = MappingDataLoader.loadMappings(tag, "blockstates"); + + if (mappings == null) { + player.kick(Component.text("Axiom+ViaVersion: Failed to load mapped blockstates (1.20.2 <-> 1.20.3")); + return; + } + + this.plugin.playerBlockRegistry.put(player.getUniqueId(), ViaVersionHelper.applyMappings(this.plugin.allowedBlockRegistry, + BiMappings.of(mappings).inverse())); + + Component text = Component.text("Axiom: Warning, client and server versions don't match. " + + "Axiom will try to use ViaVersion conversions, but this process may cause problems"); player.sendMessage(text.color(NamedTextColor.RED)); - return; - } else if (!incompatibleDataVersion.equals("ignore")) { - player.kick(text); - return; } +// inverse.getNewIdOrDefault() + + } if (apiVersion != AxiomConstants.API_VERSION) { diff --git a/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java b/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java index f396e63..1cd1812 100644 --- a/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java @@ -4,6 +4,7 @@ import com.moulberry.axiom.AxiomConstants; import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.buffer.CompressedBlockEntity; import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; +import com.viaversion.viaversion.api.Via; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.longs.*; import net.minecraft.core.BlockPos; @@ -45,7 +46,7 @@ public class RequestChunkDataPacketListener implements PluginMessageListener { FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); long id = friendlyByteBuf.readLong(); - if (!this.plugin.canUseAxiom(bukkitPlayer)) { + if (!this.plugin.canUseAxiom(bukkitPlayer) || this.plugin.hasCustomBlockRegistry(bukkitPlayer.getUniqueId())) { // We always send an 'empty' response in order to make the client happy sendEmptyResponse(player, id); return; diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java index 48615af..1722bf3 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java @@ -85,7 +85,7 @@ public class SetBlockBufferPacketListener { byte type = friendlyByteBuf.readByte(); if (type == 0) { AtomicBoolean reachedRateLimit = new AtomicBoolean(false); - BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf, rateLimiter, reachedRateLimit); + BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf, rateLimiter, reachedRateLimit, this.plugin.getBlockRegistry(player.getUUID())); if (reachedRateLimit.get()) { player.sendSystemMessage(Component.literal("[Axiom] Exceeded server rate-limit of " + (int)rateLimiter.getRate() + " sections per second") .withStyle(ChatFormatting.RED)); diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java index 74c7d5c..fa54a97 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java @@ -7,6 +7,7 @@ import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration; import io.netty.buffer.Unpooled; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.IdMapper; import net.minecraft.core.SectionPos; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; @@ -79,8 +80,9 @@ public class SetBlockPacketListener implements PluginMessageListener { // Read packet FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); IntFunction> mapFunction = FriendlyByteBuf.limitValue(Maps::newLinkedHashMapWithExpectedSize, 512); + IdMapper registry = this.plugin.getBlockRegistry(bukkitPlayer.getUniqueId()); Map blocks = friendlyByteBuf.readMap(mapFunction, - FriendlyByteBuf::readBlockPos, buf -> buf.readById(this.plugin.allowedBlockRegistry)); + FriendlyByteBuf::readBlockPos, buf -> buf.readById(registry)); boolean updateNeighbors = friendlyByteBuf.readBoolean(); int reason = friendlyByteBuf.readVarInt(); @@ -129,6 +131,10 @@ public class SetBlockPacketListener implements PluginMessageListener { BlockPos blockPos = entry.getKey(); BlockState blockState = entry.getValue(); + if (blockState == null) { + continue; + } + // Disallow in unloaded chunks if (!player.level().isLoaded(blockPos)) { continue; @@ -154,6 +160,10 @@ public class SetBlockPacketListener implements PluginMessageListener { BlockPos blockPos = entry.getKey(); BlockState blockState = entry.getValue(); + if (blockState == null) { + continue; + } + // Disallow in unloaded chunks if (!player.level().isLoaded(blockPos)) { continue; diff --git a/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java b/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java index e85270b..a2cb4bb 100644 --- a/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/UploadBlueprintPacketListener.java @@ -47,25 +47,34 @@ public class UploadBlueprintPacketListener { return; } - Path path = this.plugin.blueprintFolder.resolve(relative); + String pathName = pathStr.substring(0, pathStr.length()-3); - // 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; - } + serverPlayer.getServer().execute(() -> { + try { + Path path = this.plugin.blueprintFolder.resolve(relative); - // Update registry - registry.blueprints().put("/" + pathStr.substring(0, pathStr.length()-3), rawBlueprint); + // 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; + } - // Resend manifest - ServerBlueprintManager.sendManifest(serverPlayer.getServer().getPlayerList().getPlayers()); + // Update registry + registry.blueprints().put("/" + pathName, rawBlueprint); + + // Resend manifest + ServerBlueprintManager.sendManifest(serverPlayer.getServer().getPlayerList().getPlayers()); + } catch (Throwable t) { + serverPlayer.getBukkitEntity().kick(net.kyori.adventure.text.Component.text( + "An error occured while uploading blueprint: " + t.getMessage())); + } + }); } } diff --git a/src/main/java/com/moulberry/axiom/viaversion/ViaVersionHelper.java b/src/main/java/com/moulberry/axiom/viaversion/ViaVersionHelper.java index 3dcb8c3..ecba92a 100644 --- a/src/main/java/com/moulberry/axiom/viaversion/ViaVersionHelper.java +++ b/src/main/java/com/moulberry/axiom/viaversion/ViaVersionHelper.java @@ -1,2 +1,42 @@ -package com.moulberry.axiom.viaversion;public class ViaVersionHelper { +package com.moulberry.axiom.viaversion; + +import com.moulberry.axiom.buffer.BlockBuffer; +import com.viaversion.viaversion.api.data.BiMappings; +import com.viaversion.viaversion.api.data.Mappings; +import net.minecraft.core.IdMapper; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +public class ViaVersionHelper { + + public static IdMapper applyMappings(IdMapper registry, BiMappings mappings) { + IdMapper newBlockRegistry = new IdMapper<>(); + + // Add empty mappings for non-existent blocks + int size = mappings.mappedSize(); + for (int i = 0; i < size; i++) { + newBlockRegistry.addMapping(BlockBuffer.EMPTY_STATE, i); + } + + // Map blocks + for (int i = 0; i < registry.size(); i++) { + BlockState blockState = registry.byId(i); + + if (blockState != null) { + int newId = mappings.getNewId(i); + if (newId >= 0) { + newBlockRegistry.addMapping(blockState, newId); + } + } + } + + // Ensure block -> id is correct for the empty state + int newEmptyStateId = mappings.getNewId(registry.getId(BlockBuffer.EMPTY_STATE)); + if (newEmptyStateId >= 0) { + newBlockRegistry.addMapping(BlockBuffer.EMPTY_STATE, newEmptyStateId); + } + + return newBlockRegistry; + } + } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 53267da..2bbc099 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -8,7 +8,7 @@ api-version: "$apiVersion" permissions: axiom.*: description: Allows use of all default Axiom features - default: op + default: true # op axiom.entity.*: description: Allows use of all entity-related features (spawning, manipulating, deleting) @@ -22,7 +22,7 @@ permissions: axiom.allow_copying_other_plots: description: This permission allows users to copy other user's plots - default: true + default: false # true axiom.can_import_blocks: description: Allows players to import schematics/blueprints into Axiom default: true