geforkt von Mirrors/AxiomPaperPlugin
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 {
|
plugins {
|
||||||
`java-library`
|
`java-library`
|
||||||
id("io.papermc.paperweight.userdev") version "1.5.8"
|
id("io.papermc.paperweight.userdev") version "1.5.11"
|
||||||
id("xyz.jpenilla.run-paper") version "2.2.0" // Adds runServer and runMojangMappedServer tasks for testing
|
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/
|
// Shades and relocates dependencies into our plugin jar. See https://imperceptiblethoughts.com/shadow/introduction/
|
||||||
id("com.github.johnrengelman.shadow") version "8.1.1"
|
id("com.github.johnrengelman.shadow") version "8.1.1"
|
||||||
@ -20,8 +20,8 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||||
maven("https://jitpack.io")
|
maven("https://jitpack.io")
|
||||||
maven("https://maven.enginehub.org/repo/")
|
|
||||||
maven("https://repo.papermc.io/repository/maven-public/")
|
maven("https://repo.papermc.io/repository/maven-public/")
|
||||||
|
maven("https://maven.enginehub.org/repo/")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package com.moulberry.axiom;
|
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.buffer.CompressedBlockEntity;
|
||||||
import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent;
|
import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent;
|
||||||
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
|
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
|
||||||
@ -12,6 +15,11 @@ import io.papermc.paper.event.world.WorldGameRuleChangeEvent;
|
|||||||
import io.papermc.paper.network.ChannelInitializeListener;
|
import io.papermc.paper.network.ChannelInitializeListener;
|
||||||
import io.papermc.paper.network.ChannelInitializeListenerHolder;
|
import io.papermc.paper.network.ChannelInitializeListenerHolder;
|
||||||
import net.kyori.adventure.key.Key;
|
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.Connection;
|
||||||
import net.minecraft.network.ConnectionProtocol;
|
import net.minecraft.network.ConnectionProtocol;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
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.PacketFlow;
|
||||||
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
|
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
|
||||||
import net.minecraft.server.MinecraftServer;
|
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.*;
|
||||||
import org.bukkit.configuration.Configuration;
|
import org.bukkit.configuration.Configuration;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -32,14 +43,20 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class AxiomPaper extends JavaPlugin implements Listener {
|
public class AxiomPaper extends JavaPlugin implements Listener {
|
||||||
|
|
||||||
public static AxiomPaper PLUGIN; // tsk tsk tsk
|
public static AxiomPaper PLUGIN; // tsk tsk tsk
|
||||||
|
|
||||||
public final Set<UUID> activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
public final Set<UUID> activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||||
|
public final Map<UUID, RateLimiter> playerBlockBufferRateLimiters = new ConcurrentHashMap<>();
|
||||||
public Configuration configuration;
|
public Configuration configuration;
|
||||||
|
|
||||||
|
public IdMapper<BlockState> allowedBlockRegistry = null;
|
||||||
|
private boolean logLargeBlockBufferChanges = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
PLUGIN = this;
|
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.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(this, this);
|
||||||
// Bukkit.getPluginManager().registerEvents(new WorldPropertiesExample(), this);
|
// Bukkit.getPluginManager().registerEvents(new WorldPropertiesExample(), this);
|
||||||
CompressedBlockEntity.initialize(this);
|
CompressedBlockEntity.initialize(this);
|
||||||
@ -70,7 +92,7 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
|||||||
msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties");
|
msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties");
|
||||||
|
|
||||||
if (configuration.getBoolean("packet-handlers.hello")) {
|
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")) {
|
if (configuration.getBoolean("packet-handlers.set-gamemode")) {
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:set_gamemode", new SetGamemodePacketListener(this));
|
msg.registerIncomingPluginChannel(this, "axiom:set_gamemode", new SetGamemodePacketListener(this));
|
||||||
@ -129,7 +151,7 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
|
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
|
||||||
HashSet<UUID> newActiveAxiomPlayers = new HashSet<>();
|
HashSet<UUID> stillActiveAxiomPlayers = new HashSet<>();
|
||||||
|
|
||||||
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
|
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
|
||||||
if (activeAxiomPlayers.contains(player.getUniqueId())) {
|
if (activeAxiomPlayers.contains(player.getUniqueId())) {
|
||||||
@ -140,13 +162,13 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
|||||||
buf.getBytes(0, bytes);
|
buf.getBytes(0, bytes);
|
||||||
player.sendPluginMessage(this, "axiom:enable", bytes);
|
player.sendPluginMessage(this, "axiom:enable", bytes);
|
||||||
} else {
|
} else {
|
||||||
newActiveAxiomPlayers.add(player.getUniqueId());
|
stillActiveAxiomPlayers.add(player.getUniqueId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activeAxiomPlayers.clear();
|
activeAxiomPlayers.retainAll(stillActiveAxiomPlayers);
|
||||||
activeAxiomPlayers.addAll(newActiveAxiomPlayers);
|
playerBlockBufferRateLimiters.keySet().retainAll(stillActiveAxiomPlayers);
|
||||||
}, 20, 20);
|
}, 20, 20);
|
||||||
|
|
||||||
int maxChunkRelightsPerTick = configuration.getInt("max-chunk-relights-per-tick");
|
int maxChunkRelightsPerTick = configuration.getInt("max-chunk-relights-per-tick");
|
||||||
@ -157,10 +179,18 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
|||||||
}, 1, 1);
|
}, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean logLargeBlockBufferChanges() {
|
||||||
|
return this.logLargeBlockBufferChanges;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean canUseAxiom(Player player) {
|
public boolean canUseAxiom(Player player) {
|
||||||
return player.hasPermission("axiom.*") && activeAxiomPlayers.contains(player.getUniqueId());
|
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<>();
|
private final WeakHashMap<World, ServerWorldPropertiesRegistry> worldProperties = new WeakHashMap<>();
|
||||||
|
|
||||||
public @Nullable ServerWorldPropertiesRegistry getWorldPropertiesIfPresent(World world) {
|
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;
|
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.Object2ByteMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.world.level.biome.Biome;
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class BiomeBuffer {
|
public class BiomeBuffer {
|
||||||
|
|
||||||
@ -27,6 +31,10 @@ public class BiomeBuffer {
|
|||||||
this.paletteSize = this.paletteReverse.size();
|
this.paletteSize = this.paletteReverse.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return this.map.size();
|
||||||
|
}
|
||||||
|
|
||||||
public void save(FriendlyByteBuf friendlyByteBuf) {
|
public void save(FriendlyByteBuf friendlyByteBuf) {
|
||||||
friendlyByteBuf.writeByte(this.paletteSize);
|
friendlyByteBuf.writeByte(this.paletteSize);
|
||||||
for (int i = 0; i < this.paletteSize; i++) {
|
for (int i = 0; i < this.paletteSize; i++) {
|
||||||
@ -35,7 +43,7 @@ public class BiomeBuffer {
|
|||||||
this.map.save(friendlyByteBuf);
|
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();
|
int paletteSize = friendlyByteBuf.readByte();
|
||||||
ResourceKey<Biome>[] palette = new ResourceKey[255];
|
ResourceKey<Biome>[] palette = new ResourceKey[255];
|
||||||
Object2ByteMap<ResourceKey<Biome>> paletteReverse = new Object2ByteOpenHashMap<>();
|
Object2ByteMap<ResourceKey<Biome>> paletteReverse = new Object2ByteOpenHashMap<>();
|
||||||
@ -44,7 +52,7 @@ public class BiomeBuffer {
|
|||||||
palette[i] = key;
|
palette[i] = key;
|
||||||
paletteReverse.put(key, (byte)(i+1));
|
paletteReverse.put(key, (byte)(i+1));
|
||||||
}
|
}
|
||||||
Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf);
|
Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf, rateLimiter, reachedRateLimit);
|
||||||
return new BiomeBuffer(map, palette, paletteReverse);
|
return new BiomeBuffer(map, palette, paletteReverse);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.moulberry.axiom.buffer;
|
package com.moulberry.axiom.buffer;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
import com.moulberry.axiom.AxiomConstants;
|
import com.moulberry.axiom.AxiomConstants;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
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 net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class BlockBuffer {
|
public class BlockBuffer {
|
||||||
|
|
||||||
public static final BlockState EMPTY_STATE = Blocks.STRUCTURE_VOID.defaultBlockState();
|
public static final BlockState EMPTY_STATE = Blocks.STRUCTURE_VOID.defaultBlockState();
|
||||||
@ -24,6 +27,8 @@ public class BlockBuffer {
|
|||||||
private PalettedContainer<BlockState> last = null;
|
private PalettedContainer<BlockState> last = null;
|
||||||
private long lastId = AxiomConstants.MIN_POSITION_LONG;
|
private long lastId = AxiomConstants.MIN_POSITION_LONG;
|
||||||
private final Long2ObjectMap<Short2ObjectMap<CompressedBlockEntity>> blockEntities = new Long2ObjectOpenHashMap<>();
|
private final Long2ObjectMap<Short2ObjectMap<CompressedBlockEntity>> blockEntities = new Long2ObjectOpenHashMap<>();
|
||||||
|
private long totalBlockEntities = 0;
|
||||||
|
private long totalBlockEntityBytes = 0;
|
||||||
|
|
||||||
public BlockBuffer() {
|
public BlockBuffer() {
|
||||||
this.values = new Long2ObjectOpenHashMap<>();
|
this.values = new Long2ObjectOpenHashMap<>();
|
||||||
@ -53,54 +58,57 @@ public class BlockBuffer {
|
|||||||
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
|
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();
|
BlockBuffer buffer = new BlockBuffer();
|
||||||
|
|
||||||
|
long totalBlockEntities = 0;
|
||||||
|
long totalBlockEntityBytes = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
long index = friendlyByteBuf.readLong();
|
long index = friendlyByteBuf.readLong();
|
||||||
if (index == AxiomConstants.MIN_POSITION_LONG) break;
|
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<BlockState> palettedContainer = buffer.getOrCreateSection(index);
|
||||||
palettedContainer.read(friendlyByteBuf);
|
palettedContainer.read(friendlyByteBuf);
|
||||||
|
|
||||||
int blockEntitySize = Math.min(4096, friendlyByteBuf.readVarInt());
|
int blockEntitySize = Math.min(4096, friendlyByteBuf.readVarInt());
|
||||||
if (blockEntitySize > 0) {
|
if (blockEntitySize > 0) {
|
||||||
Short2ObjectMap<CompressedBlockEntity> map = new Short2ObjectOpenHashMap<>(blockEntitySize);
|
Short2ObjectMap<CompressedBlockEntity> map = new Short2ObjectOpenHashMap<>(blockEntitySize);
|
||||||
|
|
||||||
|
int startIndex = friendlyByteBuf.readerIndex();
|
||||||
|
|
||||||
for (int i = 0; i < blockEntitySize; i++) {
|
for (int i = 0; i < blockEntitySize; i++) {
|
||||||
short offset = friendlyByteBuf.readShort();
|
short offset = friendlyByteBuf.readShort();
|
||||||
CompressedBlockEntity blockEntity = CompressedBlockEntity.read(friendlyByteBuf);
|
CompressedBlockEntity blockEntity = CompressedBlockEntity.read(friendlyByteBuf);
|
||||||
map.put(offset, blockEntity);
|
map.put(offset, blockEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.blockEntities.put(index, map);
|
buffer.blockEntities.put(index, map);
|
||||||
|
totalBlockEntities += blockEntitySize;
|
||||||
|
totalBlockEntityBytes += friendlyByteBuf.readerIndex() - startIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffer.totalBlockEntities = totalBlockEntities;
|
||||||
|
buffer.totalBlockEntityBytes = totalBlockEntityBytes;
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public long getTotalBlockEntities() {
|
||||||
this.last = null;
|
return this.totalBlockEntities;
|
||||||
this.lastId = AxiomConstants.MIN_POSITION_LONG;
|
|
||||||
this.values.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putBlockEntity(int x, int y, int z, CompressedBlockEntity blockEntity) {
|
public long getTotalBlockEntityBytes() {
|
||||||
long cpos = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
|
return this.totalBlockEntityBytes;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -170,7 +178,7 @@ public class BlockBuffer {
|
|||||||
public PalettedContainer<BlockState> getOrCreateSection(long id) {
|
public PalettedContainer<BlockState> getOrCreateSection(long id) {
|
||||||
if (this.last == null || id != this.lastId) {
|
if (this.last == null || id != this.lastId) {
|
||||||
this.lastId = id;
|
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));
|
EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import com.github.luben.zstd.ZstdDictCompress;
|
|||||||
import com.github.luben.zstd.ZstdDictDecompress;
|
import com.github.luben.zstd.ZstdDictDecompress;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.NbtAccounter;
|
||||||
import net.minecraft.nbt.NbtIo;
|
import net.minecraft.nbt.NbtIo;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ public record CompressedBlockEntity(int originalSize, byte compressionDict, byte
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] nbt = Zstd.decompress(this.compressed, zstdDictDecompress, this.originalSize);
|
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) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
package com.moulberry.axiom.buffer;
|
package com.moulberry.axiom.buffer;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
import com.moulberry.axiom.AxiomConstants;
|
import com.moulberry.axiom.AxiomConstants;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.LongFunction;
|
import java.util.function.LongFunction;
|
||||||
|
|
||||||
public class Position2ByteMap {
|
public class Position2ByteMap {
|
||||||
@ -42,6 +45,10 @@ public class Position2ByteMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return this.map.size();
|
||||||
|
}
|
||||||
|
|
||||||
public void save(FriendlyByteBuf friendlyByteBuf) {
|
public void save(FriendlyByteBuf friendlyByteBuf) {
|
||||||
friendlyByteBuf.writeByte(this.defaultValue);
|
friendlyByteBuf.writeByte(this.defaultValue);
|
||||||
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
|
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
|
||||||
@ -51,13 +58,20 @@ public class Position2ByteMap {
|
|||||||
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
|
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());
|
Position2ByteMap map = new Position2ByteMap(friendlyByteBuf.readByte());
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
long pos = friendlyByteBuf.readLong();
|
long pos = friendlyByteBuf.readLong();
|
||||||
if (pos == AxiomConstants.MIN_POSITION_LONG) break;
|
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];
|
byte[] bytes = new byte[16*16*16];
|
||||||
friendlyByteBuf.readBytes(bytes);
|
friendlyByteBuf.readBytes(bytes);
|
||||||
map.map.put(pos, bytes);
|
map.map.put(pos, bytes);
|
||||||
|
@ -47,6 +47,7 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
|||||||
if (player != null && player.getBukkitEntity().hasPermission("axiom.*")) {
|
if (player != null && player.getBukkitEntity().hasPermission("axiom.*")) {
|
||||||
if (listener.onReceive(player, buf)) {
|
if (listener.onReceive(player, buf)) {
|
||||||
success = true;
|
success = true;
|
||||||
|
in.skipBytes(in.readableBytes());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
import com.moulberry.axiom.AxiomConstants;
|
import com.moulberry.axiom.AxiomConstants;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.View;
|
import com.moulberry.axiom.View;
|
||||||
@ -29,11 +30,9 @@ import java.util.UUID;
|
|||||||
public class HelloPacketListener implements PluginMessageListener {
|
public class HelloPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
private final AxiomPaper plugin;
|
private final AxiomPaper plugin;
|
||||||
private final Set<UUID> activeAxiomPlayers;
|
|
||||||
|
|
||||||
public HelloPacketListener(AxiomPaper plugin, Set<UUID> activeAxiomPlayers) {
|
public HelloPacketListener(AxiomPaper plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.activeAxiomPlayers = activeAxiomPlayers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -86,7 +85,11 @@ public class HelloPacketListener implements PluginMessageListener {
|
|||||||
return;
|
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
|
// Enable
|
||||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.WorldExtension;
|
import com.moulberry.axiom.WorldExtension;
|
||||||
import com.moulberry.axiom.buffer.BiomeBuffer;
|
import com.moulberry.axiom.buffer.BiomeBuffer;
|
||||||
@ -10,12 +11,14 @@ import com.moulberry.axiom.integration.SectionPermissionChecker;
|
|||||||
import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration;
|
import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
|
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.core.SectionPos;
|
import net.minecraft.core.SectionPos;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket;
|
import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.server.MinecraftServer;
|
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.chunk.PalettedContainer;
|
||||||
import net.minecraft.world.level.levelgen.Heightmap;
|
import net.minecraft.world.level.levelgen.Heightmap;
|
||||||
import net.minecraft.world.level.lighting.LightEngine;
|
import net.minecraft.world.level.lighting.LightEngine;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
|
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class SetBlockBufferPacketListener {
|
public class SetBlockBufferPacketListener {
|
||||||
|
|
||||||
private final AxiomPaper plugin;
|
private final AxiomPaper plugin;
|
||||||
private final Method updateBlockEntityTicker;
|
private final Method updateBlockEntityTicker;
|
||||||
|
private final WeakHashMap<ServerPlayer, RateLimiter> packetRateLimiter = new WeakHashMap<>();
|
||||||
|
|
||||||
public SetBlockBufferPacketListener(AxiomPaper plugin) {
|
public SetBlockBufferPacketListener(AxiomPaper plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
@ -76,12 +80,38 @@ public class SetBlockBufferPacketListener {
|
|||||||
friendlyByteBuf.readNbt(); // Discard sourceInfo
|
friendlyByteBuf.readNbt(); // Discard sourceInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RateLimiter rateLimiter = this.plugin.getBlockBufferRateLimiter(player.getUUID());
|
||||||
|
|
||||||
byte type = friendlyByteBuf.readByte();
|
byte type = friendlyByteBuf.readByte();
|
||||||
if (type == 0) {
|
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);
|
applyBlockBuffer(player, server, buffer, worldKey);
|
||||||
} else if (type == 1) {
|
} 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);
|
applyBiomeBuffer(player, server, buffer, worldKey);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Unknown buffer type: " + type);
|
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) {
|
private void applyBlockBuffer(ServerPlayer player, MinecraftServer server, BlockBuffer buffer, ResourceKey<Level> worldKey) {
|
||||||
server.execute(() -> {
|
server.execute(() -> {
|
||||||
ServerLevel world = player.serverLevel();
|
try {
|
||||||
if (!world.dimension().equals(worldKey)) return;
|
ServerLevel world = player.serverLevel();
|
||||||
|
if (!world.dimension().equals(worldKey)) return;
|
||||||
|
|
||||||
if (!this.plugin.canUseAxiom(player.getBukkitEntity())) {
|
if (!this.plugin.canUseAxiom(player.getBukkitEntity())) {
|
||||||
return;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionPermissionChecker checker = PlotSquaredIntegration.checkSection(player.getBukkitEntity(), world.getWorld(), cx, cy, cz);
|
if (!this.plugin.canModifyWorld(player.getBukkitEntity(), world.getWorld())) {
|
||||||
if (checker != null && checker.noneAllowed()) {
|
return;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
BlockState emptyState = BlockBuffer.EMPTY_STATE;
|
||||||
PalettedContainer<BlockState> sectionStates = section.getStates();
|
|
||||||
boolean hasOnlyAir = section.hasOnlyAir();
|
|
||||||
|
|
||||||
Heightmap worldSurface = null;
|
for (Long2ObjectMap.Entry<PalettedContainer<BlockState>> entry : buffer.entrySet()) {
|
||||||
Heightmap oceanFloor = null;
|
int cx = BlockPos.getX(entry.getLongKey());
|
||||||
Heightmap motionBlocking = null;
|
int cy = BlockPos.getY(entry.getLongKey());
|
||||||
Heightmap motionBlockingNoLeaves = null;
|
int cz = BlockPos.getZ(entry.getLongKey());
|
||||||
for (Map.Entry<Heightmap.Types, Heightmap> heightmap : chunk.getHeightmaps()) {
|
PalettedContainer<BlockState> container = entry.getValue();
|
||||||
switch (heightmap.getKey()) {
|
|
||||||
case WORLD_SURFACE -> worldSurface = heightmap.getValue();
|
if (cy < world.getMinSection() || cy >= world.getMaxSection()) {
|
||||||
case OCEAN_FLOOR -> oceanFloor = heightmap.getValue();
|
continue;
|
||||||
case MOTION_BLOCKING -> motionBlocking = heightmap.getValue();
|
|
||||||
case MOTION_BLOCKING_NO_LEAVES -> motionBlockingNoLeaves = heightmap.getValue();
|
|
||||||
default -> {}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
boolean sectionChanged = false;
|
SectionPermissionChecker checker = PlotSquaredIntegration.checkSection(player.getBukkitEntity(), world.getWorld(), cx, cy, cz);
|
||||||
boolean sectionLightChanged = false;
|
if (checker != null && checker.noneAllowed()) {
|
||||||
|
continue;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (int x = minX; x <= maxX; x++) {
|
LevelChunk chunk = world.getChunk(cx, cz);
|
||||||
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;
|
LevelChunkSection section = chunk.getSection(world.getSectionIndexFromSectionY(cy));
|
||||||
int by = cy*16 + y;
|
PalettedContainer<BlockState> sectionStates = section.getStates();
|
||||||
int bz = cz*16 + z;
|
boolean hasOnlyAir = section.hasOnlyAir();
|
||||||
|
|
||||||
if (hasOnlyAir && blockState.isAir()) {
|
Heightmap worldSurface = null;
|
||||||
continue;
|
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);
|
boolean containerMaybeHasPoi = container.maybeHas(PoiTypes::hasPoi);
|
||||||
if (blockState != old) {
|
boolean sectionMaybeHasPoi = section.maybeHas(PoiTypes::hasPoi);
|
||||||
sectionChanged = true;
|
|
||||||
blockPos.set(bx, by, bz);
|
|
||||||
|
|
||||||
Block block = blockState.getBlock();
|
Short2ObjectMap<CompressedBlockEntity> blockEntityChunkMap = buffer.getBlockEntityChunkMap(entry.getLongKey());
|
||||||
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
|
int minX = 0;
|
||||||
old.onRemove(world, blockPos, blockState, false);
|
int minY = 0;
|
||||||
|
int minZ = 0;
|
||||||
|
int maxX = 15;
|
||||||
|
int maxY = 15;
|
||||||
|
int maxZ = 15;
|
||||||
|
|
||||||
if (sectionStates.get(x, y, z).is(block)) {
|
if (checker != null) {
|
||||||
blockState.onPlace(world, blockPos, old, false);
|
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()) {
|
if (checker != null && !checker.allowed(x, y, z)) continue;
|
||||||
BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK);
|
|
||||||
|
|
||||||
if (blockEntity == null) {
|
BlockState old = section.setBlockState(x, y, z, blockState, true);
|
||||||
// There isn't a block entity here, create it!
|
if (blockState != old) {
|
||||||
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
|
sectionChanged = true;
|
||||||
if (blockEntity != null) {
|
blockPos.set(bx, by, bz);
|
||||||
chunk.addAndRegisterBlockEntity(blockEntity);
|
|
||||||
}
|
|
||||||
} else if (blockEntity.getType().isValid(blockState)) {
|
|
||||||
// Block entity is here and the type is correct
|
|
||||||
blockEntity.setBlockState(blockState);
|
|
||||||
|
|
||||||
try {
|
Block block = blockState.getBlock();
|
||||||
this.updateBlockEntityTicker.invoke(chunk, blockEntity);
|
motionBlocking.update(x, by, z, blockState);
|
||||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
motionBlockingNoLeaves.update(x, by, z, blockState);
|
||||||
throw new RuntimeException(e);
|
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);
|
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);
|
// Update Light
|
||||||
CompressedBlockEntity savedBlockEntity = blockEntityChunkMap.get((short) key);
|
sectionLightChanged |= LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState);
|
||||||
if (savedBlockEntity != null) {
|
|
||||||
blockEntity.load(savedBlockEntity.decompress());
|
// 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();
|
boolean nowHasOnlyAir = section.hasOnlyAir();
|
||||||
if (hasOnlyAir != nowHasOnlyAir) {
|
if (hasOnlyAir != nowHasOnlyAir) {
|
||||||
world.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir);
|
world.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sectionChanged) {
|
if (sectionChanged) {
|
||||||
extension.sendChunk(cx, cz);
|
extension.sendChunk(cx, cz);
|
||||||
chunk.setUnsaved(true);
|
chunk.setUnsaved(true);
|
||||||
}
|
}
|
||||||
if (sectionLightChanged) {
|
if (sectionLightChanged) {
|
||||||
extension.lightChunk(cx, cz);
|
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) {
|
private void applyBiomeBuffer(ServerPlayer player, MinecraftServer server, BiomeBuffer biomeBuffer, ResourceKey<Level> worldKey) {
|
||||||
server.execute(() -> {
|
server.execute(() -> {
|
||||||
ServerLevel world = player.serverLevel();
|
try {
|
||||||
if (!world.dimension().equals(worldKey)) return;
|
ServerLevel world = player.serverLevel();
|
||||||
|
if (!world.dimension().equals(worldKey)) return;
|
||||||
|
|
||||||
if (!this.plugin.canUseAxiom(player.getBukkitEntity())) {
|
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) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var chunk = (LevelChunk) world.getChunk(x >> 2, z >> 2, ChunkStatus.FULL, false);
|
if (!this.plugin.canModifyWorld(player.getBukkitEntity(), world.getWorld())) {
|
||||||
if (chunk == null) return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var section = chunk.getSection(cy - minSection);
|
Set<LevelChunk> changedChunks = new HashSet<>();
|
||||||
PalettedContainer<Holder<Biome>> container = (PalettedContainer<Holder<Biome>>) section.getBiomes();
|
|
||||||
|
|
||||||
var holder = registry.getHolder(biome);
|
int minSection = world.getMinSection();
|
||||||
if (holder.isPresent()) {
|
int maxSection = world.getMaxSection();
|
||||||
if (!PlotSquaredIntegration.canPlaceBlock(player.getBukkitEntity(),
|
|
||||||
|
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;
|
new Location(player.getBukkitEntity().getWorld(), x+1, y+1, z+1))) return;
|
||||||
|
|
||||||
container.set(x & 3, y & 3, z & 3, holder.get());
|
container.set(x & 3, y & 3, z & 3, holder.get());
|
||||||
changedChunks.add(chunk);
|
changedChunks.add(chunk);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var chunkMap = world.getChunkSource().chunkMap;
|
var chunkMap = world.getChunkSource().chunkMap;
|
||||||
HashMap<ServerPlayer, List<LevelChunk>> map = new HashMap<>();
|
HashMap<ServerPlayer, List<LevelChunk>> map = new HashMap<>();
|
||||||
for (LevelChunk chunk : changedChunks) {
|
for (LevelChunk chunk : changedChunks) {
|
||||||
chunk.setUnsaved(true);
|
chunk.setUnsaved(true);
|
||||||
ChunkPos chunkPos = chunk.getPos();
|
ChunkPos chunkPos = chunk.getPos();
|
||||||
for (ServerPlayer serverPlayer2 : chunkMap.getPlayers(chunkPos, false)) {
|
for (ServerPlayer serverPlayer2 : chunkMap.getPlayers(chunkPos, false)) {
|
||||||
map.computeIfAbsent(serverPlayer2, serverPlayer -> new ArrayList<>()).add(chunk);
|
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));
|
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||||
IntFunction<Map<BlockPos, BlockState>> mapFunction = FriendlyByteBuf.limitValue(Maps::newLinkedHashMapWithExpectedSize, 512);
|
IntFunction<Map<BlockPos, BlockState>> mapFunction = FriendlyByteBuf.limitValue(Maps::newLinkedHashMapWithExpectedSize, 512);
|
||||||
Map<BlockPos, BlockState> blocks = friendlyByteBuf.readMap(mapFunction,
|
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();
|
boolean updateNeighbors = friendlyByteBuf.readBoolean();
|
||||||
|
|
||||||
int reason = friendlyByteBuf.readVarInt();
|
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
|
# If the regex matches the world's name, the world can't be modified
|
||||||
blacklist-world-regex: null
|
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
|
# Toggles for individual packet handlers. May break certain features
|
||||||
packet-handlers:
|
packet-handlers:
|
||||||
hello: true
|
hello: true
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren