3
0
Mirror von https://github.com/Moulberry/AxiomPaperPlugin.git synchronisiert 2024-09-29 07:50:05 +02:00

Support Axiom b0.7

Dieser Commit ist enthalten in:
Moulberry 2023-07-06 12:34:07 +08:00
Ursprung 10240f435c
Commit 76b00ce0ad
7 geänderte Dateien mit 387 neuen und 16 gelöschten Zeilen

Datei anzeigen

@ -15,6 +15,7 @@ import io.papermc.paper.network.ChannelInitializeListenerHolder;
import io.papermc.paper.network.ConnectionEvent; import io.papermc.paper.network.ConnectionEvent;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Key;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.network.Connection; import net.minecraft.network.Connection;
import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.ConnectionProtocol;
@ -46,6 +47,13 @@ import java.util.Map;
public class AxiomPaper extends JavaPlugin implements Listener { 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 @Override
public void onEnable() { public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, this); Bukkit.getPluginManager().registerEvents(this, this);

Datei anzeigen

@ -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));
}
}

Datei anzeigen

@ -1,13 +1,15 @@
package com.moulberry.axiom.buffer; package com.moulberry.axiom.buffer;
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 it.unimi.dsi.fastutil.objects.ObjectSet; import it.unimi.dsi.fastutil.objects.ObjectSet;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.*; import net.minecraft.world.level.chunk.PalettedContainer;
public class BlockBuffer { public class BlockBuffer {
@ -16,7 +18,7 @@ public class BlockBuffer {
private final Long2ObjectMap<PalettedContainer<BlockState>> values; private final Long2ObjectMap<PalettedContainer<BlockState>> values;
private PalettedContainer<BlockState> last = null; private PalettedContainer<BlockState> last = null;
private long lastId = Long.MAX_VALUE; private long lastId = AxiomPaper.MIN_POSITION_LONG;
private int count; private int count;
public BlockBuffer() { public BlockBuffer() {
@ -31,9 +33,32 @@ public class BlockBuffer {
return this.count; 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() { public void clear() {
this.last = null; this.last = null;
this.lastId = Long.MAX_VALUE; this.lastId = AxiomPaper.MIN_POSITION_LONG;
this.values.clear(); this.values.clear();
} }
@ -112,7 +137,7 @@ public class BlockBuffer {
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<>(Block.BLOCK_STATE_REGISTRY,
EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES)); EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES));
} }
return this.last; return this.last;

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -0,0 +1,8 @@
package com.moulberry.axiom.buffer;
@FunctionalInterface
public interface PositionConsumer<T> {
void accept(int x, int y, int z, T t);
}

Datei anzeigen

@ -13,7 +13,7 @@ import java.util.List;
public class AxiomBigPayloadHandler extends ByteToMessageDecoder { 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 int payloadId;
private final Connection connection; private final Connection connection;
private final SetBlockBufferPacketListener listener; private final SetBlockBufferPacketListener listener;
@ -35,7 +35,7 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
if (packetId == payloadId) { if (packetId == payloadId) {
ResourceLocation identifier = buf.readResourceLocation(); ResourceLocation identifier = buf.readResourceLocation();
if (identifier.equals(SET_BLOCK_BUFFER)) { if (identifier.equals(SET_BUFFER)) {
ServerPlayer player = connection.getPlayer(); ServerPlayer player = connection.getPlayer();
if (player != null) { if (player != null) {
listener.onReceive(player, buf); listener.onReceive(player, buf);

Datei anzeigen

@ -1,21 +1,29 @@
package com.moulberry.axiom.packet; package com.moulberry.axiom.packet;
import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.AxiomPaper;
import com.moulberry.axiom.buffer.BiomeBuffer;
import com.moulberry.axiom.buffer.BlockBuffer; import com.moulberry.axiom.buffer.BlockBuffer;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
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.protocol.game.ClientboundChunksBiomesPacket;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level; 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.Block;
import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; 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.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer; 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.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.*;
public class SetBlockBufferPacketListener { public class SetBlockBufferPacketListener {
@ -52,19 +60,26 @@ public class SetBlockBufferPacketListener {
} }
public void onReceive(ServerPlayer player, FriendlyByteBuf friendlyByteBuf) { public void onReceive(ServerPlayer player, FriendlyByteBuf friendlyByteBuf) {
MinecraftServer server = player.getServer();
if (server == null) return;
ResourceKey<Level> worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION); ResourceKey<Level> worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION);
BlockBuffer buffer = new BlockBuffer();
while (true) { byte type = friendlyByteBuf.readByte();
long index = friendlyByteBuf.readLong(); if (type == 0) {
if (index == Long.MAX_VALUE) break; BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf);
applyBlockBuffer(server, buffer, worldKey);
PalettedContainer<BlockState> palettedContainer = buffer.getOrCreateSection(index); } else if (type == 1) {
palettedContainer.read(friendlyByteBuf); BiomeBuffer buffer = BiomeBuffer.load(friendlyByteBuf);
applyBiomeBuffer(server, buffer, worldKey);
} else {
throw new RuntimeException("Unknown buffer type: " + type);
} }
}
player.getServer().execute(() -> { private void applyBlockBuffer(MinecraftServer server, BlockBuffer buffer, ResourceKey<Level> worldKey) {
ServerLevel world = player.getServer().getLevel(worldKey); server.execute(() -> {
ServerLevel world = server.getLevel(worldKey);
if (world == null) return; if (world == null) return;
BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); 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)));
});
}
} }