Skip to content

Commit

Permalink
Partial ViaVersion support
Browse files Browse the repository at this point in the history
  • Loading branch information
Moulberry committed May 3, 2024
1 parent 914084f commit 5ea1f32
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 50 deletions.
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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/")
Expand All @@ -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")

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/moulberry/axiom/AxiomPaper.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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;
Expand All @@ -49,6 +50,7 @@ public class AxiomPaper extends JavaPlugin implements Listener {
public final Set<UUID> activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final Map<UUID, RateLimiter> playerBlockBufferRateLimiters = new ConcurrentHashMap<>();
public final Map<UUID, Restrictions> playerRestrictions = new ConcurrentHashMap<>();
public final Map<UUID, IdMapper<BlockState>> playerBlockRegistry = new ConcurrentHashMap<>();
public Configuration configuration;

public IdMapper<BlockState> allowedBlockRegistry = null;
Expand Down Expand Up @@ -265,6 +267,7 @@ public void afterInitChannel(@NonNull Channel channel) {
activeAxiomPlayers.retainAll(stillActiveAxiomPlayers);
playerBlockBufferRateLimiters.keySet().retainAll(stillActiveAxiomPlayers);
playerRestrictions.keySet().retainAll(stillActiveAxiomPlayers);
playerBlockRegistry.keySet().retainAll(stillActiveAxiomPlayers);
}, 20, 20);

boolean sendMarkers = configuration.getBoolean("send-markers");
Expand Down Expand Up @@ -292,6 +295,14 @@ public boolean canUseAxiom(Player player) {
return this.playerBlockBufferRateLimiters.get(uuid);
}

public boolean hasCustomBlockRegistry(UUID uuid) {
return this.playerBlockRegistry.containsKey(uuid);
}

public IdMapper<BlockState> getBlockRegistry(UUID uuid) {
return this.playerBlockRegistry.getOrDefault(uuid, this.allowedBlockRegistry);
}

private final WeakHashMap<World, ServerWorldPropertiesRegistry> worldProperties = new WeakHashMap<>();

public @Nullable ServerWorldPropertiesRegistry getWorldPropertiesIfPresent(World world) {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/moulberry/axiom/DisallowedBlocks.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
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;
Expand Down
16 changes: 8 additions & 8 deletions src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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;
Expand All @@ -23,18 +24,16 @@ public class BlockBuffer {

private final Long2ObjectMap<PalettedContainer<BlockState>> values;

private IdMapper<BlockState> registry;
private PalettedContainer<BlockState> last = null;
private long lastId = AxiomConstants.MIN_POSITION_LONG;
private final Long2ObjectMap<Short2ObjectMap<CompressedBlockEntity>> blockEntities = new Long2ObjectOpenHashMap<>();
private long totalBlockEntities = 0;
private long totalBlockEntityBytes = 0;

public BlockBuffer() {
public BlockBuffer(IdMapper<BlockState> registry) {
this.values = new Long2ObjectOpenHashMap<>();
}

public BlockBuffer(Long2ObjectMap<PalettedContainer<BlockState>> values) {
this.values = values;
this.registry = registry;
}

public void save(FriendlyByteBuf friendlyByteBuf) {
Expand All @@ -57,8 +56,9 @@ public void save(FriendlyByteBuf friendlyByteBuf) {
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<BlockState> registry) {
BlockBuffer buffer = new BlockBuffer(registry);

long totalBlockEntities = 0;
long totalBlockEntityBytes = 0;
Expand Down Expand Up @@ -177,7 +177,7 @@ public PalettedContainer<BlockState> getOrCreateSectionForCoord(int x, int y, in
public PalettedContainer<BlockState> 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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) t
} 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;
Expand All @@ -74,8 +73,15 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) t
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());
Expand Down
62 changes: 49 additions & 13 deletions src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -52,18 +54,52 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla

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")) {
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()));

String incompatibleDataVersion = plugin.configuration.getString("incompatible-data-version");
if (incompatibleDataVersion == null) incompatibleDataVersion = "kick";
if (incompatibleDataVersion.equals("warn")) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.moulberry.axiom.VersionHelper;
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;
Expand Down Expand Up @@ -46,7 +47,7 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player buk
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public void onReceive(ServerPlayer player, FriendlyByteBuf friendlyByteBuf) {
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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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;
Expand Down Expand Up @@ -79,8 +80,9 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player buk
// Read packet
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
IntFunction<Map<BlockPos, BlockState>> mapFunction = FriendlyByteBuf.limitValue(Maps::newLinkedHashMapWithExpectedSize, 512);
IdMapper<BlockState> registry = this.plugin.getBlockRegistry(bukkitPlayer.getUniqueId());
Map<BlockPos, BlockState> 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();
Expand Down Expand Up @@ -129,6 +131,10 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player buk
BlockPos blockPos = entry.getKey();
BlockState blockState = entry.getValue();

if (blockState == null) {
continue;
}

// Disallow in unloaded chunks
if (!player.level().isLoaded(blockPos)) {
continue;
Expand All @@ -154,6 +160,10 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player buk
BlockPos blockPos = entry.getKey();
BlockState blockState = entry.getValue();

if (blockState == null) {
continue;
}

// Disallow in unloaded chunks
if (!player.level().isLoaded(blockPos)) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,34 @@ public void onReceive(ServerPlayer serverPlayer, FriendlyByteBuf friendlyByteBuf
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());
String pathName = pathStr.substring(0, pathStr.length()-3);

serverPlayer.getServer().execute(() -> {
try {
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("/" + 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()));
}
});
}

}
Original file line number Diff line number Diff line change
@@ -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<BlockState> applyMappings(IdMapper<BlockState> registry, BiMappings mappings) {
IdMapper<BlockState> 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;
}

}
Loading

0 comments on commit 5ea1f32

Please sign in to comment.