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:
Ursprung
313dd873d9
Commit
13df370a42
@ -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 {
|
||||
|
@ -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) {
|
||||
|
92
src/main/java/com/moulberry/axiom/DisallowedBlocks.java
Normale Datei
92
src/main/java/com/moulberry/axiom/DisallowedBlocks.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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)));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren