Mirror von
https://github.com/Moulberry/AxiomPaperPlugin.git
synchronisiert 2024-11-08 17:40:04 +01:00
Support Axiom b0.7
Dieser Commit ist enthalten in:
Ursprung
10240f435c
Commit
76b00ce0ad
@ -15,6 +15,7 @@ import io.papermc.paper.network.ChannelInitializeListenerHolder;
|
||||
import io.papermc.paper.network.ConnectionEvent;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.ConnectionProtocol;
|
||||
@ -46,6 +47,13 @@ import java.util.Map;
|
||||
|
||||
public class AxiomPaper extends JavaPlugin implements Listener {
|
||||
|
||||
public static final long MIN_POSITION_LONG = BlockPos.asLong(-33554432, -2048, -33554432);
|
||||
static {
|
||||
if (MIN_POSITION_LONG != 0b1000000000000000000000000010000000000000000000000000100000000000L) {
|
||||
throw new Error("BlockPos representation changed!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
Bukkit.getPluginManager().registerEvents(this, this);
|
||||
|
88
src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java
Normale Datei
88
src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java
Normale Datei
@ -0,0 +1,88 @@
|
||||
package com.moulberry.axiom.buffer;
|
||||
|
||||
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;
|
||||
|
||||
public class BiomeBuffer {
|
||||
|
||||
private final Position2ByteMap map;
|
||||
private final ResourceKey<Biome>[] palette;
|
||||
private final Object2ByteMap<ResourceKey<Biome>> paletteReverse;
|
||||
private int paletteSize = 0;
|
||||
|
||||
public BiomeBuffer() {
|
||||
this.map = new Position2ByteMap();
|
||||
this.palette = new ResourceKey[255];
|
||||
this.paletteReverse = new Object2ByteOpenHashMap<>();
|
||||
}
|
||||
|
||||
private BiomeBuffer(Position2ByteMap map, ResourceKey<Biome>[] palette, Object2ByteMap<ResourceKey<Biome>> paletteReverse) {
|
||||
this.map = map;
|
||||
this.palette = palette;
|
||||
this.paletteReverse = paletteReverse;
|
||||
this.paletteSize = this.paletteReverse.size();
|
||||
}
|
||||
|
||||
public void save(FriendlyByteBuf friendlyByteBuf) {
|
||||
friendlyByteBuf.writeByte(this.paletteSize);
|
||||
for (int i = 0; i < this.paletteSize; i++) {
|
||||
friendlyByteBuf.writeResourceKey(this.palette[i]);
|
||||
}
|
||||
this.map.save(friendlyByteBuf);
|
||||
}
|
||||
|
||||
public static BiomeBuffer load(FriendlyByteBuf friendlyByteBuf) {
|
||||
int paletteSize = friendlyByteBuf.readByte();
|
||||
ResourceKey<Biome>[] palette = new ResourceKey[255];
|
||||
Object2ByteMap<ResourceKey<Biome>> paletteReverse = new Object2ByteOpenHashMap<>();
|
||||
for (int i = 0; i < paletteSize; i++) {
|
||||
ResourceKey<Biome> key = friendlyByteBuf.readResourceKey(Registries.BIOME);
|
||||
palette[i] = key;
|
||||
paletteReverse.put(key, (byte)(i+1));
|
||||
}
|
||||
Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf);
|
||||
return new BiomeBuffer(map, palette, paletteReverse);
|
||||
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.map.clear();
|
||||
}
|
||||
|
||||
public void forEachEntry(PositionConsumer<ResourceKey<Biome>> consumer) {
|
||||
this.map.forEachEntry((x, y, z, v) -> {
|
||||
if (v != 0) consumer.accept(x, y, z, this.palette[(v & 0xFF) - 1]);
|
||||
});
|
||||
}
|
||||
|
||||
public ResourceKey<Biome> get(int quartX, int quartY, int quartZ) {
|
||||
int index = this.map.get(quartX, quartY, quartZ) & 0xFF;
|
||||
if (index == 0) return null;
|
||||
return this.palette[index - 1];
|
||||
}
|
||||
|
||||
private int getPaletteIndex(ResourceKey<Biome> biome) {
|
||||
int index = this.paletteReverse.getOrDefault(biome, (byte) 0) & 0xFF;
|
||||
if (index != 0) return index;
|
||||
|
||||
index = this.paletteSize;
|
||||
if (index >= this.palette.length) {
|
||||
throw new UnsupportedOperationException("Too many biomes! :(");
|
||||
}
|
||||
|
||||
this.palette[index] = biome;
|
||||
this.paletteReverse.put(biome, (byte)(index + 1));
|
||||
|
||||
this.paletteSize += 1;
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
public void set(int quartX, int quartY, int quartZ, ResourceKey<Biome> biome) {
|
||||
this.map.put(quartX, quartY, quartZ, (byte) this.getPaletteIndex(biome));
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
package com.moulberry.axiom.buffer;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.*;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
|
||||
public class BlockBuffer {
|
||||
|
||||
@ -16,7 +18,7 @@ public class BlockBuffer {
|
||||
private final Long2ObjectMap<PalettedContainer<BlockState>> values;
|
||||
|
||||
private PalettedContainer<BlockState> last = null;
|
||||
private long lastId = Long.MAX_VALUE;
|
||||
private long lastId = AxiomPaper.MIN_POSITION_LONG;
|
||||
private int count;
|
||||
|
||||
public BlockBuffer() {
|
||||
@ -31,9 +33,32 @@ public class BlockBuffer {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public void save(FriendlyByteBuf friendlyByteBuf) {
|
||||
for (Long2ObjectMap.Entry<PalettedContainer<BlockState>> entry : this.entrySet()) {
|
||||
friendlyByteBuf.writeLong(entry.getLongKey());
|
||||
entry.getValue().write(friendlyByteBuf);
|
||||
}
|
||||
|
||||
friendlyByteBuf.writeLong(AxiomPaper.MIN_POSITION_LONG);
|
||||
}
|
||||
|
||||
public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf) {
|
||||
BlockBuffer buffer = new BlockBuffer();
|
||||
|
||||
while (true) {
|
||||
long index = friendlyByteBuf.readLong();
|
||||
if (index == AxiomPaper.MIN_POSITION_LONG) break;
|
||||
|
||||
PalettedContainer<BlockState> palettedContainer = buffer.getOrCreateSection(index);
|
||||
palettedContainer.read(friendlyByteBuf);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.last = null;
|
||||
this.lastId = Long.MAX_VALUE;
|
||||
this.lastId = AxiomPaper.MIN_POSITION_LONG;
|
||||
this.values.clear();
|
||||
}
|
||||
|
||||
@ -112,7 +137,7 @@ public class BlockBuffer {
|
||||
if (this.last == null || id != this.lastId) {
|
||||
this.lastId = id;
|
||||
this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY,
|
||||
EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES));
|
||||
EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES));
|
||||
}
|
||||
|
||||
return this.last;
|
||||
|
179
src/main/java/com/moulberry/axiom/buffer/Position2ByteMap.java
Normale Datei
179
src/main/java/com/moulberry/axiom/buffer/Position2ByteMap.java
Normale Datei
@ -0,0 +1,179 @@
|
||||
package com.moulberry.axiom.buffer;
|
||||
|
||||
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 java.util.Arrays;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
public class Position2ByteMap {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface EntryConsumer {
|
||||
void consume(int x, int y, int z, byte v);
|
||||
}
|
||||
|
||||
private final byte defaultValue;
|
||||
private final LongFunction<byte[]> defaultFunction;
|
||||
private final Long2ObjectMap<byte[]> map = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
private long lastChunkPos = AxiomPaper.MIN_POSITION_LONG;
|
||||
private byte[] lastChunk = null;
|
||||
|
||||
public Position2ByteMap() {
|
||||
this((byte) 0);
|
||||
}
|
||||
|
||||
public Position2ByteMap(byte defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
|
||||
if (defaultValue == 0) {
|
||||
this.defaultFunction = k -> new byte[16*16*16];
|
||||
} else {
|
||||
this.defaultFunction = k -> {
|
||||
byte[] array = new byte[16*16*16];
|
||||
Arrays.fill(array, defaultValue);
|
||||
return array;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void save(FriendlyByteBuf friendlyByteBuf) {
|
||||
friendlyByteBuf.writeByte(this.defaultValue);
|
||||
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
|
||||
friendlyByteBuf.writeLong(entry.getLongKey());
|
||||
friendlyByteBuf.writeBytes(entry.getValue());
|
||||
}
|
||||
friendlyByteBuf.writeLong(AxiomPaper.MIN_POSITION_LONG);
|
||||
}
|
||||
|
||||
public static Position2ByteMap load(FriendlyByteBuf friendlyByteBuf) {
|
||||
Position2ByteMap map = new Position2ByteMap(friendlyByteBuf.readByte());
|
||||
|
||||
while (true) {
|
||||
long pos = friendlyByteBuf.readLong();
|
||||
if (pos == AxiomPaper.MIN_POSITION_LONG) break;
|
||||
|
||||
byte[] bytes = new byte[16*16*16];
|
||||
friendlyByteBuf.readBytes(bytes);
|
||||
map.map.put(pos, bytes);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.map.clear();
|
||||
this.lastChunkPos = AxiomPaper.MIN_POSITION_LONG;
|
||||
this.lastChunk = null;
|
||||
}
|
||||
|
||||
public byte get(int x, int y, int z) {
|
||||
int xC = x >> 4;
|
||||
int yC = y >> 4;
|
||||
int zC = z >> 4;
|
||||
|
||||
byte[] array = this.getChunk(xC, yC, zC);
|
||||
if (array == null) return this.defaultValue;
|
||||
|
||||
return array[(x&15) + (y&15)*16 + (z&15)*16*16];
|
||||
}
|
||||
|
||||
public void put(int x, int y, int z, byte v) {
|
||||
int xC = x >> 4;
|
||||
int yC = y >> 4;
|
||||
int zC = z >> 4;
|
||||
|
||||
byte[] array = this.getOrCreateChunk(xC, yC, zC);
|
||||
array[(x&15) + (y&15)*16 + (z&15)*16*16] = v;
|
||||
}
|
||||
|
||||
public byte add(int x, int y, int z, byte v) {
|
||||
if (v == 0) return this.get(x, y, z);
|
||||
|
||||
int xC = x >> 4;
|
||||
int yC = y >> 4;
|
||||
int zC = z >> 4;
|
||||
|
||||
byte[] array = this.getOrCreateChunk(xC, yC, zC);
|
||||
return array[(x&15) + (y&15)*16 + (z&15)*16*16] += v;
|
||||
}
|
||||
|
||||
|
||||
public byte binaryAnd(int x, int y, int z, byte v) {
|
||||
int xC = x >> 4;
|
||||
int yC = y >> 4;
|
||||
int zC = z >> 4;
|
||||
|
||||
byte[] array = this.getOrCreateChunk(xC, yC, zC);
|
||||
return array[(x&15) + (y&15)*16 + (z&15)*16*16] &= v;
|
||||
}
|
||||
|
||||
public boolean min(int x, int y, int z, byte v) {
|
||||
int xC = x >> 4;
|
||||
int yC = y >> 4;
|
||||
int zC = z >> 4;
|
||||
|
||||
byte[] array = this.getOrCreateChunk(xC, yC, zC);
|
||||
int index = (x&15) + (y&15)*16 + (z&15)*16*16;
|
||||
|
||||
if (v < array[index]) {
|
||||
array[index] = v;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void forEachEntry(EntryConsumer consumer) {
|
||||
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
|
||||
int cx = BlockPos.getX(entry.getLongKey()) * 16;
|
||||
int cy = BlockPos.getY(entry.getLongKey()) * 16;
|
||||
int cz = BlockPos.getZ(entry.getLongKey()) * 16;
|
||||
|
||||
int index = 0;
|
||||
for (int z=0; z<16; z++) {
|
||||
for (int y=0; y<16; y++) {
|
||||
for (int x=0; x<16; x++) {
|
||||
byte v = entry.getValue()[index++];
|
||||
if (v != this.defaultValue) {
|
||||
consumer.consume(cx + x, cy + y, cz + z, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getChunk(int xC, int yC, int zC) {
|
||||
return this.getChunk(BlockPos.asLong(xC, yC, zC));
|
||||
}
|
||||
|
||||
public byte[] getChunk(long pos) {
|
||||
if (this.lastChunkPos != pos) {
|
||||
byte[] chunk = this.map.get(pos);
|
||||
this.lastChunkPos = pos;
|
||||
this.lastChunk = chunk;
|
||||
}
|
||||
|
||||
return this.lastChunk;
|
||||
}
|
||||
|
||||
public byte[] getOrCreateChunk(int xC, int yC, int zC) {
|
||||
return this.getOrCreateChunk(BlockPos.asLong(xC, yC, zC));
|
||||
}
|
||||
|
||||
public byte[] getOrCreateChunk(long pos) {
|
||||
if (this.lastChunk == null || this.lastChunkPos != pos) {
|
||||
byte[] chunk = this.map.computeIfAbsent(pos, this.defaultFunction);
|
||||
this.lastChunkPos = pos;
|
||||
this.lastChunk = chunk;
|
||||
}
|
||||
|
||||
return this.lastChunk;
|
||||
}
|
||||
|
||||
}
|
8
src/main/java/com/moulberry/axiom/buffer/PositionConsumer.java
Normale Datei
8
src/main/java/com/moulberry/axiom/buffer/PositionConsumer.java
Normale Datei
@ -0,0 +1,8 @@
|
||||
package com.moulberry.axiom.buffer;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PositionConsumer<T> {
|
||||
|
||||
void accept(int x, int y, int z, T t);
|
||||
|
||||
}
|
@ -13,7 +13,7 @@ import java.util.List;
|
||||
|
||||
public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
||||
|
||||
private static final ResourceLocation SET_BLOCK_BUFFER = new ResourceLocation("axiom", "set_block_buffer");
|
||||
private static final ResourceLocation SET_BUFFER = new ResourceLocation("axiom", "set_buffer");
|
||||
private final int payloadId;
|
||||
private final Connection connection;
|
||||
private final SetBlockBufferPacketListener listener;
|
||||
@ -35,7 +35,7 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
||||
|
||||
if (packetId == payloadId) {
|
||||
ResourceLocation identifier = buf.readResourceLocation();
|
||||
if (identifier.equals(SET_BLOCK_BUFFER)) {
|
||||
if (identifier.equals(SET_BUFFER)) {
|
||||
ServerPlayer player = connection.getPlayer();
|
||||
if (player != null) {
|
||||
listener.onReceive(player, buf);
|
||||
|
@ -1,21 +1,29 @@
|
||||
package com.moulberry.axiom.packet;
|
||||
|
||||
import com.moulberry.axiom.AxiomPaper;
|
||||
import com.moulberry.axiom.buffer.BiomeBuffer;
|
||||
import com.moulberry.axiom.buffer.BlockBuffer;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
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.protocol.game.ClientboundChunksBiomesPacket;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
@ -29,7 +37,7 @@ import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
public class SetBlockBufferPacketListener {
|
||||
|
||||
@ -52,19 +60,26 @@ public class SetBlockBufferPacketListener {
|
||||
}
|
||||
|
||||
public void onReceive(ServerPlayer player, FriendlyByteBuf friendlyByteBuf) {
|
||||
MinecraftServer server = player.getServer();
|
||||
if (server == null) return;
|
||||
|
||||
ResourceKey<Level> worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION);
|
||||
BlockBuffer buffer = new BlockBuffer();
|
||||
|
||||
while (true) {
|
||||
long index = friendlyByteBuf.readLong();
|
||||
if (index == Long.MAX_VALUE) break;
|
||||
|
||||
PalettedContainer<BlockState> palettedContainer = buffer.getOrCreateSection(index);
|
||||
palettedContainer.read(friendlyByteBuf);
|
||||
byte type = friendlyByteBuf.readByte();
|
||||
if (type == 0) {
|
||||
BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf);
|
||||
applyBlockBuffer(server, buffer, worldKey);
|
||||
} else if (type == 1) {
|
||||
BiomeBuffer buffer = BiomeBuffer.load(friendlyByteBuf);
|
||||
applyBiomeBuffer(server, buffer, worldKey);
|
||||
} else {
|
||||
throw new RuntimeException("Unknown buffer type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
player.getServer().execute(() -> {
|
||||
ServerLevel world = player.getServer().getLevel(worldKey);
|
||||
private void applyBlockBuffer(MinecraftServer server, BlockBuffer buffer, ResourceKey<Level> worldKey) {
|
||||
server.execute(() -> {
|
||||
ServerLevel world = server.getLevel(worldKey);
|
||||
if (world == null) return;
|
||||
|
||||
BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
|
||||
@ -182,4 +197,52 @@ public class SetBlockBufferPacketListener {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void applyBiomeBuffer(MinecraftServer server, BiomeBuffer biomeBuffer, ResourceKey<Level> worldKey) {
|
||||
server.execute(() -> {
|
||||
ServerLevel world = server.getLevel(worldKey);
|
||||
if (world == null) 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;
|
||||
}
|
||||
|
||||
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()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
map.forEach((serverPlayer, list) -> serverPlayer.connection.send(ClientboundChunksBiomesPacket.forChunks(list)));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren