3
0
Mirror von https://github.com/Moulberry/AxiomPaperPlugin.git synchronisiert 2024-11-08 17:40:04 +01:00

Add disallowed-blocks, log-large-block-buffer-changes and block-buffer-rate-limit config options

Dieser Commit ist enthalten in:
Moulberry 2023-12-26 17:41:17 +08:00
Ursprung 313dd873d9
Commit 13df370a42
12 geänderte Dateien mit 437 neuen und 232 gelöschten Zeilen

Datei anzeigen

@ -1,7 +1,7 @@
plugins {
`java-library`
id("io.papermc.paperweight.userdev") version "1.5.8"
id("xyz.jpenilla.run-paper") version "2.2.0" // Adds runServer and runMojangMappedServer tasks for testing
id("io.papermc.paperweight.userdev") version "1.5.11"
id("xyz.jpenilla.run-paper") version "2.2.2" // Adds runServer and runMojangMappedServer tasks for testing
// Shades and relocates dependencies into our plugin jar. See https://imperceptiblethoughts.com/shadow/introduction/
id("com.github.johnrengelman.shadow") version "8.1.1"
@ -20,8 +20,8 @@ repositories {
mavenCentral()
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
maven("https://jitpack.io")
maven("https://maven.enginehub.org/repo/")
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://maven.enginehub.org/repo/")
}
dependencies {

Datei anzeigen

@ -1,5 +1,8 @@
package com.moulberry.axiom;
import com.google.common.util.concurrent.RateLimiter;
import com.mojang.brigadier.StringReader;
import com.moulberry.axiom.buffer.BlockBuffer;
import com.moulberry.axiom.buffer.CompressedBlockEntity;
import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent;
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
@ -12,6 +15,11 @@ import io.papermc.paper.event.world.WorldGameRuleChangeEvent;
import io.papermc.paper.network.ChannelInitializeListener;
import io.papermc.paper.network.ChannelInitializeListenerHolder;
import net.kyori.adventure.key.Key;
import net.minecraft.commands.arguments.blocks.BlockPredicateArgument;
import net.minecraft.commands.arguments.blocks.BlockStateArgument;
import net.minecraft.commands.arguments.blocks.BlockStateParser;
import net.minecraft.core.IdMapper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.Connection;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.FriendlyByteBuf;
@ -19,6 +27,9 @@ import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.*;
import org.bukkit.configuration.Configuration;
import org.bukkit.entity.Player;
@ -32,14 +43,20 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
public class AxiomPaper extends JavaPlugin implements Listener {
public static AxiomPaper PLUGIN; // tsk tsk tsk
public final Set<UUID> activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final Map<UUID, RateLimiter> playerBlockBufferRateLimiters = new ConcurrentHashMap<>();
public Configuration configuration;
public IdMapper<BlockState> allowedBlockRegistry = null;
private boolean logLargeBlockBufferChanges = false;
@Override
public void onEnable() {
PLUGIN = this;
@ -55,6 +72,11 @@ public class AxiomPaper extends JavaPlugin implements Listener {
this.getLogger().warning("Invalid value for unsupported-axiom-version, expected 'kick', 'warn' or 'ignore'");
}
this.logLargeBlockBufferChanges = this.configuration.getBoolean("log-large-block-buffer-changes");
List<String> disallowedBlocks = this.configuration.getStringList("disallowed-blocks");
this.allowedBlockRegistry = DisallowedBlocks.createAllowedBlockRegistry(disallowedBlocks);
Bukkit.getPluginManager().registerEvents(this, this);
// Bukkit.getPluginManager().registerEvents(new WorldPropertiesExample(), this);
CompressedBlockEntity.initialize(this);
@ -70,7 +92,7 @@ public class AxiomPaper extends JavaPlugin implements Listener {
msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties");
if (configuration.getBoolean("packet-handlers.hello")) {
msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this, activeAxiomPlayers));
msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this));
}
if (configuration.getBoolean("packet-handlers.set-gamemode")) {
msg.registerIncomingPluginChannel(this, "axiom:set_gamemode", new SetGamemodePacketListener(this));
@ -129,7 +151,7 @@ public class AxiomPaper extends JavaPlugin implements Listener {
}
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
HashSet<UUID> newActiveAxiomPlayers = new HashSet<>();
HashSet<UUID> stillActiveAxiomPlayers = new HashSet<>();
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
if (activeAxiomPlayers.contains(player.getUniqueId())) {
@ -140,13 +162,13 @@ public class AxiomPaper extends JavaPlugin implements Listener {
buf.getBytes(0, bytes);
player.sendPluginMessage(this, "axiom:enable", bytes);
} else {
newActiveAxiomPlayers.add(player.getUniqueId());
stillActiveAxiomPlayers.add(player.getUniqueId());
}
}
}
activeAxiomPlayers.clear();
activeAxiomPlayers.addAll(newActiveAxiomPlayers);
activeAxiomPlayers.retainAll(stillActiveAxiomPlayers);
playerBlockBufferRateLimiters.keySet().retainAll(stillActiveAxiomPlayers);
}, 20, 20);
int maxChunkRelightsPerTick = configuration.getInt("max-chunk-relights-per-tick");
@ -157,10 +179,18 @@ public class AxiomPaper extends JavaPlugin implements Listener {
}, 1, 1);
}
public boolean logLargeBlockBufferChanges() {
return this.logLargeBlockBufferChanges;
}
public boolean canUseAxiom(Player player) {
return player.hasPermission("axiom.*") && activeAxiomPlayers.contains(player.getUniqueId());
}
public @Nullable RateLimiter getBlockBufferRateLimiter(UUID uuid) {
return this.playerBlockBufferRateLimiters.get(uuid);
}
private final WeakHashMap<World, ServerWorldPropertiesRegistry> worldProperties = new WeakHashMap<>();
public @Nullable ServerWorldPropertiesRegistry getWorldPropertiesIfPresent(World world) {

Datei anzeigen

@ -0,0 +1,92 @@
package com.moulberry.axiom;
import com.mojang.brigadier.StringReader;
import com.mojang.datafixers.util.Either;
import com.moulberry.axiom.buffer.BlockBuffer;
import net.minecraft.commands.arguments.blocks.BlockPredicateArgument;
import net.minecraft.commands.arguments.blocks.BlockStateParser;
import net.minecraft.core.IdMapper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.NbtUtils;
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.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
public class DisallowedBlocks {
public static IdMapper<BlockState> createAllowedBlockRegistry(List<String> disallowedBlocks) {
List<Predicate<BlockState>> disallowedPredicates = new ArrayList<>();
for (String disallowedBlock : disallowedBlocks) {
try {
var parsed = BlockStateParser.parseForTesting(BuiltInRegistries.BLOCK.asLookup(), new StringReader(disallowedBlock), false);
parsed.left().ifPresent(result -> {
disallowedPredicates.add(blockState -> {
if (!blockState.is(result.blockState().getBlock())) {
return false;
} else {
for (Property<?> property : result.properties().keySet()) {
if (blockState.getValue(property) != result.blockState().getValue(property)) {
return false;
}
}
return true;
}
});
});
parsed.right().ifPresent(result -> {
disallowedPredicates.add(blockState -> {
if (!blockState.is(result.tag())) {
return false;
} else {
for(Map.Entry<String, String> entry : result.vagueProperties().entrySet()) {
Property<?> property = blockState.getBlock().getStateDefinition().getProperty(entry.getKey());
if (property == null) {
return false;
}
Comparable<?> comparable = property.getValue(entry.getValue()).orElse(null);
if (comparable == null) {
return false;
}
if (blockState.getValue(property) != comparable) {
return false;
}
}
return true;
}
});
});
} catch (Exception ignored) {}
}
IdMapper<BlockState> allowedBlockRegistry = new IdMapper<>();
// Create allowedBlockRegistry
blocks:
for (BlockState blockState : Block.BLOCK_STATE_REGISTRY) {
for (Predicate<BlockState> disallowedPredicate : disallowedPredicates) {
if (disallowedPredicate.test(blockState)) {
allowedBlockRegistry.add(BlockBuffer.EMPTY_STATE);
continue blocks;
}
}
allowedBlockRegistry.add(blockState);
}
allowedBlockRegistry.addMapping(BlockBuffer.EMPTY_STATE, Block.BLOCK_STATE_REGISTRY.getId(BlockBuffer.EMPTY_STATE));
return allowedBlockRegistry;
}
}

Datei anzeigen

@ -1,11 +1,15 @@
package com.moulberry.axiom.buffer;
import com.google.common.util.concurrent.RateLimiter;
import it.unimi.dsi.fastutil.objects.Object2ByteMap;
import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicBoolean;
public class BiomeBuffer {
@ -27,6 +31,10 @@ public class BiomeBuffer {
this.paletteSize = this.paletteReverse.size();
}
public int size() {
return this.map.size();
}
public void save(FriendlyByteBuf friendlyByteBuf) {
friendlyByteBuf.writeByte(this.paletteSize);
for (int i = 0; i < this.paletteSize; i++) {
@ -35,7 +43,7 @@ public class BiomeBuffer {
this.map.save(friendlyByteBuf);
}
public static BiomeBuffer load(FriendlyByteBuf friendlyByteBuf) {
public static BiomeBuffer load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit) {
int paletteSize = friendlyByteBuf.readByte();
ResourceKey<Biome>[] palette = new ResourceKey[255];
Object2ByteMap<ResourceKey<Biome>> paletteReverse = new Object2ByteOpenHashMap<>();
@ -44,7 +52,7 @@ public class BiomeBuffer {
palette[i] = key;
paletteReverse.put(key, (byte)(i+1));
}
Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf);
Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf, rateLimiter, reachedRateLimit);
return new BiomeBuffer(map, palette, paletteReverse);
}

Datei anzeigen

@ -1,5 +1,6 @@
package com.moulberry.axiom.buffer;
import com.google.common.util.concurrent.RateLimiter;
import com.moulberry.axiom.AxiomConstants;
import com.moulberry.axiom.AxiomPaper;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
@ -15,6 +16,8 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.PalettedContainer;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicBoolean;
public class BlockBuffer {
public static final BlockState EMPTY_STATE = Blocks.STRUCTURE_VOID.defaultBlockState();
@ -24,6 +27,8 @@ public class BlockBuffer {
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() {
this.values = new Long2ObjectOpenHashMap<>();
@ -53,54 +58,57 @@ public class BlockBuffer {
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
}
public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf) {
public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit) {
BlockBuffer buffer = new BlockBuffer();
long totalBlockEntities = 0;
long totalBlockEntityBytes = 0;
while (true) {
long index = friendlyByteBuf.readLong();
if (index == AxiomConstants.MIN_POSITION_LONG) break;
if (rateLimiter != null) {
if (!rateLimiter.tryAcquire()) {
reachedRateLimit.set(true);
buffer.totalBlockEntities = totalBlockEntities;
buffer.totalBlockEntityBytes = totalBlockEntityBytes;
return buffer;
}
}
PalettedContainer<BlockState> palettedContainer = buffer.getOrCreateSection(index);
palettedContainer.read(friendlyByteBuf);
int blockEntitySize = Math.min(4096, friendlyByteBuf.readVarInt());
if (blockEntitySize > 0) {
Short2ObjectMap<CompressedBlockEntity> map = new Short2ObjectOpenHashMap<>(blockEntitySize);
int startIndex = friendlyByteBuf.readerIndex();
for (int i = 0; i < blockEntitySize; i++) {
short offset = friendlyByteBuf.readShort();
CompressedBlockEntity blockEntity = CompressedBlockEntity.read(friendlyByteBuf);
map.put(offset, blockEntity);
}
buffer.blockEntities.put(index, map);
totalBlockEntities += blockEntitySize;
totalBlockEntityBytes += friendlyByteBuf.readerIndex() - startIndex;
}
}
buffer.totalBlockEntities = totalBlockEntities;
buffer.totalBlockEntityBytes = totalBlockEntityBytes;
return buffer;
}
public void clear() {
this.last = null;
this.lastId = AxiomConstants.MIN_POSITION_LONG;
this.values.clear();
public long getTotalBlockEntities() {
return this.totalBlockEntities;
}
public void putBlockEntity(int x, int y, int z, CompressedBlockEntity blockEntity) {
long cpos = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
Short2ObjectMap<CompressedBlockEntity> chunkMap = this.blockEntities.computeIfAbsent(cpos, k -> new Short2ObjectOpenHashMap<>());
int key = (x & 0xF) | ((y & 0xF) << 4) | ((z & 0xF) << 8);
chunkMap.put((short)key, blockEntity);
}
@Nullable
public CompressedBlockEntity getBlockEntity(int x, int y, int z) {
long cpos = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
Short2ObjectMap<CompressedBlockEntity> chunkMap = this.blockEntities.get(cpos);
if (chunkMap == null) return null;
int key = (x & 0xF) | ((y & 0xF) << 4) | ((z & 0xF) << 8);
return chunkMap.get((short)key);
public long getTotalBlockEntityBytes() {
return this.totalBlockEntityBytes;
}
@Nullable
@ -170,7 +178,7 @@ public class BlockBuffer {
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<>(Block.BLOCK_STATE_REGISTRY,
this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(AxiomPaper.PLUGIN.allowedBlockRegistry,
EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES));
}

Datei anzeigen

@ -5,6 +5,7 @@ import com.github.luben.zstd.ZstdDictCompress;
import com.github.luben.zstd.ZstdDictDecompress;
import com.moulberry.axiom.AxiomPaper;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.NbtIo;
import net.minecraft.network.FriendlyByteBuf;
@ -44,7 +45,7 @@ public record CompressedBlockEntity(int originalSize, byte compressionDict, byte
try {
byte[] nbt = Zstd.decompress(this.compressed, zstdDictDecompress, this.originalSize);
return NbtIo.read(new DataInputStream(new ByteArrayInputStream(nbt)));
return NbtIo.read(new DataInputStream(new ByteArrayInputStream(nbt)), NbtAccounter.create(131072));
} catch (IOException e) {
throw new RuntimeException(e);
}

Datei anzeigen

@ -1,13 +1,16 @@
package com.moulberry.axiom.buffer;
import com.google.common.util.concurrent.RateLimiter;
import com.moulberry.axiom.AxiomConstants;
import com.moulberry.axiom.AxiomPaper;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.LongFunction;
public class Position2ByteMap {
@ -42,6 +45,10 @@ public class Position2ByteMap {
}
}
public int size() {
return this.map.size();
}
public void save(FriendlyByteBuf friendlyByteBuf) {
friendlyByteBuf.writeByte(this.defaultValue);
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
@ -51,13 +58,20 @@ public class Position2ByteMap {
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
}
public static Position2ByteMap load(FriendlyByteBuf friendlyByteBuf) {
public static Position2ByteMap load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit) {
Position2ByteMap map = new Position2ByteMap(friendlyByteBuf.readByte());
while (true) {
long pos = friendlyByteBuf.readLong();
if (pos == AxiomConstants.MIN_POSITION_LONG) break;
if (rateLimiter != null) {
if (!rateLimiter.tryAcquire()) {
reachedRateLimit.set(true);
return map;
}
}
byte[] bytes = new byte[16*16*16];
friendlyByteBuf.readBytes(bytes);
map.map.put(pos, bytes);

Datei anzeigen

@ -47,6 +47,7 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
if (player != null && player.getBukkitEntity().hasPermission("axiom.*")) {
if (listener.onReceive(player, buf)) {
success = true;
in.skipBytes(in.readableBytes());
return;
}
}

Datei anzeigen

@ -1,5 +1,6 @@
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;
@ -29,11 +30,9 @@ import java.util.UUID;
public class HelloPacketListener implements PluginMessageListener {
private final AxiomPaper plugin;
private final Set<UUID> activeAxiomPlayers;
public HelloPacketListener(AxiomPaper plugin, Set<UUID> activeAxiomPlayers) {
public HelloPacketListener(AxiomPaper plugin) {
this.plugin = plugin;
this.activeAxiomPlayers = activeAxiomPlayers;
}
@Override
@ -86,7 +85,11 @@ public class HelloPacketListener implements PluginMessageListener {
return;
}
activeAxiomPlayers.add(player.getUniqueId());
this.plugin.activeAxiomPlayers.add(player.getUniqueId());
int rateLimit = this.plugin.configuration.getInt("block-buffer-rate-limit");
if (rateLimit > 0) {
this.plugin.playerBlockBufferRateLimiters.putIfAbsent(player.getUniqueId(), RateLimiter.create(rateLimit));
}
// Enable
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());

Datei anzeigen

@ -1,5 +1,6 @@
package com.moulberry.axiom.packet;
import com.google.common.util.concurrent.RateLimiter;
import com.moulberry.axiom.AxiomPaper;
import com.moulberry.axiom.WorldExtension;
import com.moulberry.axiom.buffer.BiomeBuffer;
@ -10,12 +11,14 @@ import com.moulberry.axiom.integration.SectionPermissionChecker;
import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
@ -36,18 +39,19 @@ import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.lighting.LightEngine;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class SetBlockBufferPacketListener {
private final AxiomPaper plugin;
private final Method updateBlockEntityTicker;
private final WeakHashMap<ServerPlayer, RateLimiter> packetRateLimiter = new WeakHashMap<>();
public SetBlockBufferPacketListener(AxiomPaper plugin) {
this.plugin = plugin;
@ -76,12 +80,38 @@ public class SetBlockBufferPacketListener {
friendlyByteBuf.readNbt(); // Discard sourceInfo
}
RateLimiter rateLimiter = this.plugin.getBlockBufferRateLimiter(player.getUUID());
byte type = friendlyByteBuf.readByte();
if (type == 0) {
BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf);
AtomicBoolean reachedRateLimit = new AtomicBoolean(false);
BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf, rateLimiter, reachedRateLimit);
if (reachedRateLimit.get()) {
player.sendSystemMessage(Component.literal("[Axiom] Exceeded server rate-limit of " + (int)rateLimiter.getRate() + " sections per second")
.withStyle(ChatFormatting.RED));
}
if (this.plugin.logLargeBlockBufferChanges()) {
this.plugin.getLogger().info("Player " + player.getUUID() + " modified " + buffer.entrySet().size() + " chunk sections (blocks)");
if (buffer.getTotalBlockEntities() > 0) {
this.plugin.getLogger().info("Player " + player.getUUID() + " modified " + buffer.getTotalBlockEntities() + " block entities, compressed bytes = " +
buffer.getTotalBlockEntityBytes());
}
}
applyBlockBuffer(player, server, buffer, worldKey);
} else if (type == 1) {
BiomeBuffer buffer = BiomeBuffer.load(friendlyByteBuf);
AtomicBoolean reachedRateLimit = new AtomicBoolean(false);
BiomeBuffer buffer = BiomeBuffer.load(friendlyByteBuf, rateLimiter, reachedRateLimit);
if (reachedRateLimit.get()) {
player.sendSystemMessage(Component.literal("[Axiom] Exceeded server rate-limit of " + (int)rateLimiter.getRate() + " sections per second")
.withStyle(ChatFormatting.RED));
}
if (this.plugin.logLargeBlockBufferChanges()) {
this.plugin.getLogger().info("Player " + player.getUUID() + " modified " + buffer.size() + " chunk sections (biomes)");
}
applyBiomeBuffer(player, server, buffer, worldKey);
} else {
throw new RuntimeException("Unknown buffer type: " + type);
@ -92,245 +122,252 @@ public class SetBlockBufferPacketListener {
private void applyBlockBuffer(ServerPlayer player, MinecraftServer server, BlockBuffer buffer, ResourceKey<Level> worldKey) {
server.execute(() -> {
ServerLevel world = player.serverLevel();
if (!world.dimension().equals(worldKey)) return;
try {
ServerLevel world = player.serverLevel();
if (!world.dimension().equals(worldKey)) return;
if (!this.plugin.canUseAxiom(player.getBukkitEntity())) {
return;
}
if (!this.plugin.canModifyWorld(player.getBukkitEntity(), world.getWorld())) {
return;
}
// Allowed, apply buffer
BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
WorldExtension extension = WorldExtension.get(world);
BlockState emptyState = BlockBuffer.EMPTY_STATE;
for (Long2ObjectMap.Entry<PalettedContainer<BlockState>> entry : buffer.entrySet()) {
int cx = BlockPos.getX(entry.getLongKey());
int cy = BlockPos.getY(entry.getLongKey());
int cz = BlockPos.getZ(entry.getLongKey());
PalettedContainer<BlockState> container = entry.getValue();
if (cy < world.getMinSection() || cy >= world.getMaxSection()) {
continue;
if (!this.plugin.canUseAxiom(player.getBukkitEntity())) {
return;
}
SectionPermissionChecker checker = PlotSquaredIntegration.checkSection(player.getBukkitEntity(), world.getWorld(), cx, cy, cz);
if (checker != null && checker.noneAllowed()) {
continue;
if (!this.plugin.canModifyWorld(player.getBukkitEntity(), world.getWorld())) {
return;
}
LevelChunk chunk = world.getChunk(cx, cz);
// Allowed, apply buffer
BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
WorldExtension extension = WorldExtension.get(world);
LevelChunkSection section = chunk.getSection(world.getSectionIndexFromSectionY(cy));
PalettedContainer<BlockState> sectionStates = section.getStates();
boolean hasOnlyAir = section.hasOnlyAir();
BlockState emptyState = BlockBuffer.EMPTY_STATE;
Heightmap worldSurface = null;
Heightmap oceanFloor = null;
Heightmap motionBlocking = null;
Heightmap motionBlockingNoLeaves = null;
for (Map.Entry<Heightmap.Types, Heightmap> heightmap : chunk.getHeightmaps()) {
switch (heightmap.getKey()) {
case WORLD_SURFACE -> worldSurface = heightmap.getValue();
case OCEAN_FLOOR -> oceanFloor = heightmap.getValue();
case MOTION_BLOCKING -> motionBlocking = heightmap.getValue();
case MOTION_BLOCKING_NO_LEAVES -> motionBlockingNoLeaves = heightmap.getValue();
default -> {}
for (Long2ObjectMap.Entry<PalettedContainer<BlockState>> entry : buffer.entrySet()) {
int cx = BlockPos.getX(entry.getLongKey());
int cy = BlockPos.getY(entry.getLongKey());
int cz = BlockPos.getZ(entry.getLongKey());
PalettedContainer<BlockState> container = entry.getValue();
if (cy < world.getMinSection() || cy >= world.getMaxSection()) {
continue;
}
}
boolean sectionChanged = false;
boolean sectionLightChanged = false;
boolean containerMaybeHasPoi = container.maybeHas(PoiTypes::hasPoi);
boolean sectionMaybeHasPoi = section.maybeHas(PoiTypes::hasPoi);
Short2ObjectMap<CompressedBlockEntity> blockEntityChunkMap = buffer.getBlockEntityChunkMap(entry.getLongKey());
int minX = 0;
int minY = 0;
int minZ = 0;
int maxX = 15;
int maxY = 15;
int maxZ = 15;
if (checker != null) {
minX = checker.bounds().minX();
minY = checker.bounds().minY();
minZ = checker.bounds().minZ();
maxX = checker.bounds().maxX();
maxY = checker.bounds().maxY();
maxZ = checker.bounds().maxZ();
if (checker.allAllowed()) {
checker = null;
SectionPermissionChecker checker = PlotSquaredIntegration.checkSection(player.getBukkitEntity(), world.getWorld(), cx, cy, cz);
if (checker != null && checker.noneAllowed()) {
continue;
}
}
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
for (int z = minZ; z <= maxZ; z++) {
BlockState blockState = container.get(x, y, z);
if (blockState == emptyState) continue;
LevelChunk chunk = world.getChunk(cx, cz);
int bx = cx*16 + x;
int by = cy*16 + y;
int bz = cz*16 + z;
LevelChunkSection section = chunk.getSection(world.getSectionIndexFromSectionY(cy));
PalettedContainer<BlockState> sectionStates = section.getStates();
boolean hasOnlyAir = section.hasOnlyAir();
if (hasOnlyAir && blockState.isAir()) {
continue;
}
Heightmap worldSurface = null;
Heightmap oceanFloor = null;
Heightmap motionBlocking = null;
Heightmap motionBlockingNoLeaves = null;
for (Map.Entry<Heightmap.Types, Heightmap> heightmap : chunk.getHeightmaps()) {
switch (heightmap.getKey()) {
case WORLD_SURFACE -> worldSurface = heightmap.getValue();
case OCEAN_FLOOR -> oceanFloor = heightmap.getValue();
case MOTION_BLOCKING -> motionBlocking = heightmap.getValue();
case MOTION_BLOCKING_NO_LEAVES -> motionBlockingNoLeaves = heightmap.getValue();
default -> {}
}
}
if (checker != null && !checker.allowed(x, y, z)) continue;
boolean sectionChanged = false;
boolean sectionLightChanged = false;
BlockState old = section.setBlockState(x, y, z, blockState, true);
if (blockState != old) {
sectionChanged = true;
blockPos.set(bx, by, bz);
boolean containerMaybeHasPoi = container.maybeHas(PoiTypes::hasPoi);
boolean sectionMaybeHasPoi = section.maybeHas(PoiTypes::hasPoi);
Block block = blockState.getBlock();
motionBlocking.update(x, by, z, blockState);
motionBlockingNoLeaves.update(x, by, z, blockState);
oceanFloor.update(x, by, z, blockState);
worldSurface.update(x, by, z, blockState);
Short2ObjectMap<CompressedBlockEntity> blockEntityChunkMap = buffer.getBlockEntityChunkMap(entry.getLongKey());
if (false) { // Full update
old.onRemove(world, blockPos, blockState, false);
int minX = 0;
int minY = 0;
int minZ = 0;
int maxX = 15;
int maxY = 15;
int maxZ = 15;
if (sectionStates.get(x, y, z).is(block)) {
blockState.onPlace(world, blockPos, old, false);
}
if (checker != null) {
minX = checker.bounds().minX();
minY = checker.bounds().minY();
minZ = checker.bounds().minZ();
maxX = checker.bounds().maxX();
maxY = checker.bounds().maxY();
maxZ = checker.bounds().maxZ();
if (checker.allAllowed()) {
checker = null;
}
}
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
for (int z = minZ; z <= maxZ; z++) {
BlockState blockState = container.get(x, y, z);
if (blockState == emptyState) continue;
int bx = cx*16 + x;
int by = cy*16 + y;
int bz = cz*16 + z;
if (hasOnlyAir && blockState.isAir()) {
continue;
}
if (blockState.hasBlockEntity()) {
BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK);
if (checker != null && !checker.allowed(x, y, z)) continue;
if (blockEntity == null) {
// There isn't a block entity here, create it!
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
if (blockEntity != null) {
chunk.addAndRegisterBlockEntity(blockEntity);
}
} else if (blockEntity.getType().isValid(blockState)) {
// Block entity is here and the type is correct
blockEntity.setBlockState(blockState);
BlockState old = section.setBlockState(x, y, z, blockState, true);
if (blockState != old) {
sectionChanged = true;
blockPos.set(bx, by, bz);
try {
this.updateBlockEntityTicker.invoke(chunk, blockEntity);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
Block block = blockState.getBlock();
motionBlocking.update(x, by, z, blockState);
motionBlockingNoLeaves.update(x, by, z, blockState);
oceanFloor.update(x, by, z, blockState);
worldSurface.update(x, by, z, blockState);
if (false) { // Full update
old.onRemove(world, blockPos, blockState, false);
if (sectionStates.get(x, y, z).is(block)) {
blockState.onPlace(world, blockPos, old, false);
}
} else {
// Block entity type isn't correct, we need to recreate it
}
if (blockState.hasBlockEntity()) {
BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK);
if (blockEntity == null) {
// There isn't a block entity here, create it!
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
if (blockEntity != null) {
chunk.addAndRegisterBlockEntity(blockEntity);
}
} else if (blockEntity.getType().isValid(blockState)) {
// Block entity is here and the type is correct
blockEntity.setBlockState(blockState);
try {
this.updateBlockEntityTicker.invoke(chunk, blockEntity);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
} else {
// Block entity type isn't correct, we need to recreate it
chunk.removeBlockEntity(blockPos);
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
if (blockEntity != null) {
chunk.addAndRegisterBlockEntity(blockEntity);
}
}
if (blockEntity != null && blockEntityChunkMap != null) {
int key = x | (y << 4) | (z << 8);
CompressedBlockEntity savedBlockEntity = blockEntityChunkMap.get((short) key);
if (savedBlockEntity != null) {
blockEntity.load(savedBlockEntity.decompress());
}
}
} else if (old.hasBlockEntity()) {
chunk.removeBlockEntity(blockPos);
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
if (blockEntity != null) {
chunk.addAndRegisterBlockEntity(blockEntity);
}
}
if (blockEntity != null && blockEntityChunkMap != null) {
int key = x | (y << 4) | (z << 8);
CompressedBlockEntity savedBlockEntity = blockEntityChunkMap.get((short) key);
if (savedBlockEntity != null) {
blockEntity.load(savedBlockEntity.decompress());
}
// Update Light
sectionLightChanged |= LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState);
// Update Poi
Optional<Holder<PoiType>> newPoi = containerMaybeHasPoi ? PoiTypes.forState(blockState) : Optional.empty();
Optional<Holder<PoiType>> oldPoi = sectionMaybeHasPoi ? PoiTypes.forState(old) : Optional.empty();
if (!Objects.equals(oldPoi, newPoi)) {
if (oldPoi.isPresent()) world.getPoiManager().remove(blockPos);
if (newPoi.isPresent()) world.getPoiManager().add(blockPos, newPoi.get());
}
} else if (old.hasBlockEntity()) {
chunk.removeBlockEntity(blockPos);
}
// Update Light
sectionLightChanged |= LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState);
// Update Poi
Optional<Holder<PoiType>> newPoi = containerMaybeHasPoi ? PoiTypes.forState(blockState) : Optional.empty();
Optional<Holder<PoiType>> oldPoi = sectionMaybeHasPoi ? PoiTypes.forState(old) : Optional.empty();
if (!Objects.equals(oldPoi, newPoi)) {
if (oldPoi.isPresent()) world.getPoiManager().remove(blockPos);
if (newPoi.isPresent()) world.getPoiManager().add(blockPos, newPoi.get());
}
}
}
}
}
boolean nowHasOnlyAir = section.hasOnlyAir();
if (hasOnlyAir != nowHasOnlyAir) {
world.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir);
}
boolean nowHasOnlyAir = section.hasOnlyAir();
if (hasOnlyAir != nowHasOnlyAir) {
world.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir);
}
if (sectionChanged) {
extension.sendChunk(cx, cz);
chunk.setUnsaved(true);
}
if (sectionLightChanged) {
extension.lightChunk(cx, cz);
if (sectionChanged) {
extension.sendChunk(cx, cz);
chunk.setUnsaved(true);
}
if (sectionLightChanged) {
extension.lightChunk(cx, cz);
}
}
} catch (Throwable t) {
player.getBukkitEntity().kick(net.kyori.adventure.text.Component.text("An error occured while processing block change: " + t.getMessage()));
}
});
}
private void applyBiomeBuffer(ServerPlayer player, MinecraftServer server, BiomeBuffer biomeBuffer, ResourceKey<Level> worldKey) {
server.execute(() -> {
ServerLevel world = player.serverLevel();
if (!world.dimension().equals(worldKey)) return;
try {
ServerLevel world = player.serverLevel();
if (!world.dimension().equals(worldKey)) return;
if (!this.plugin.canUseAxiom(player.getBukkitEntity())) {
return;
}
if (!this.plugin.canModifyWorld(player.getBukkitEntity(), world.getWorld())) {
return;
}
Set<LevelChunk> changedChunks = new HashSet<>();
int minSection = world.getMinSection();
int maxSection = world.getMaxSection();
Optional<Registry<Biome>> registryOptional = world.registryAccess().registry(Registries.BIOME);
if (registryOptional.isEmpty()) return;
Registry<Biome> registry = registryOptional.get();
biomeBuffer.forEachEntry((x, y, z, biome) -> {
int cy = y >> 2;
if (cy < minSection || cy >= maxSection) {
if (!this.plugin.canUseAxiom(player.getBukkitEntity())) {
return;
}
var chunk = (LevelChunk) world.getChunk(x >> 2, z >> 2, ChunkStatus.FULL, false);
if (chunk == null) return;
if (!this.plugin.canModifyWorld(player.getBukkitEntity(), world.getWorld())) {
return;
}
var section = chunk.getSection(cy - minSection);
PalettedContainer<Holder<Biome>> container = (PalettedContainer<Holder<Biome>>) section.getBiomes();
Set<LevelChunk> changedChunks = new HashSet<>();
var holder = registry.getHolder(biome);
if (holder.isPresent()) {
if (!PlotSquaredIntegration.canPlaceBlock(player.getBukkitEntity(),
int minSection = world.getMinSection();
int maxSection = world.getMaxSection();
Optional<Registry<Biome>> registryOptional = world.registryAccess().registry(Registries.BIOME);
if (registryOptional.isEmpty()) return;
Registry<Biome> registry = registryOptional.get();
biomeBuffer.forEachEntry((x, y, z, biome) -> {
int cy = y >> 2;
if (cy < minSection || cy >= maxSection) {
return;
}
var chunk = (LevelChunk) world.getChunk(x >> 2, z >> 2, ChunkStatus.FULL, false);
if (chunk == null) return;
var section = chunk.getSection(cy - minSection);
PalettedContainer<Holder<Biome>> container = (PalettedContainer<Holder<Biome>>) section.getBiomes();
var holder = registry.getHolder(biome);
if (holder.isPresent()) {
if (!PlotSquaredIntegration.canPlaceBlock(player.getBukkitEntity(),
new Location(player.getBukkitEntity().getWorld(), x+1, y+1, z+1))) return;
container.set(x & 3, y & 3, z & 3, holder.get());
changedChunks.add(chunk);
}
});
container.set(x & 3, y & 3, z & 3, holder.get());
changedChunks.add(chunk);
}
});
var chunkMap = world.getChunkSource().chunkMap;
HashMap<ServerPlayer, List<LevelChunk>> map = new HashMap<>();
for (LevelChunk chunk : changedChunks) {
chunk.setUnsaved(true);
ChunkPos chunkPos = chunk.getPos();
for (ServerPlayer serverPlayer2 : chunkMap.getPlayers(chunkPos, false)) {
map.computeIfAbsent(serverPlayer2, serverPlayer -> new ArrayList<>()).add(chunk);
var chunkMap = world.getChunkSource().chunkMap;
HashMap<ServerPlayer, List<LevelChunk>> map = new HashMap<>();
for (LevelChunk chunk : changedChunks) {
chunk.setUnsaved(true);
ChunkPos chunkPos = chunk.getPos();
for (ServerPlayer serverPlayer2 : chunkMap.getPlayers(chunkPos, false)) {
map.computeIfAbsent(serverPlayer2, serverPlayer -> new ArrayList<>()).add(chunk);
}
}
map.forEach((serverPlayer, list) -> serverPlayer.connection.send(ClientboundChunksBiomesPacket.forChunks(list)));
} catch (Throwable t) {
player.getBukkitEntity().kick(net.kyori.adventure.text.Component.text("An error occured while processing biome change: " + t.getMessage()));
}
map.forEach((serverPlayer, list) -> serverPlayer.connection.send(ClientboundChunksBiomesPacket.forChunks(list)));
});
}

Datei anzeigen

@ -80,7 +80,7 @@ public class SetBlockPacketListener implements PluginMessageListener {
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
IntFunction<Map<BlockPos, BlockState>> mapFunction = FriendlyByteBuf.limitValue(Maps::newLinkedHashMapWithExpectedSize, 512);
Map<BlockPos, BlockState> blocks = friendlyByteBuf.readMap(mapFunction,
FriendlyByteBuf::readBlockPos, buf -> buf.readById(Block.BLOCK_STATE_REGISTRY));
FriendlyByteBuf::readBlockPos, buf -> buf.readById(this.plugin.allowedBlockRegistry));
boolean updateNeighbors = friendlyByteBuf.readBoolean();
int reason = friendlyByteBuf.readVarInt();

Datei anzeigen

@ -28,6 +28,17 @@ whitelist-world-regex: null
# If the regex matches the world's name, the world can't be modified
blacklist-world-regex: null
# Block buffer rate-limit (in chunk sections per second), 0 = no limit
block-buffer-rate-limit: 0
# Log large block buffer changes
log-large-block-buffer-changes: false
# Disallowed blocks
disallowed-blocks:
# - "minecraft:wheat"
# - "minecraft:oak_stairs[waterlogged=true]"
# Toggles for individual packet handlers. May break certain features
packet-handlers:
hello: true