From e9a2b152597c60fda5929b1389a58f20dbaf894b Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 9 Sep 2023 10:32:44 +0200 Subject: [PATCH] WIP Multiversion (Small sections remaining) --- .../java/com/moulberry/axiom/AxiomPaper.java | 18 +- .../moulberry/axiom/buffer/BiomeBuffer.java | 27 +- .../moulberry/axiom/buffer/BlockBuffer.java | 75 ------ .../axiom/buffer/Position2ByteMap.java | 20 +- .../integration/NoVersionTranslator.java | 63 +++++ .../axiom/integration/VersionTranslator.java | 15 +- .../integration/ViaVersionTranslator.java | 34 ++- .../axiom/packet/ChunkSectionModifier.java | 135 ++++++++++ .../RequestBlockEntityPacketListener.java | 9 +- .../packet/SetBlockBufferPacketListener.java | 236 +++--------------- .../axiom/packet/SetBlockPacketListener.java | 113 ++------- .../axiom/packet/TeleportPacketListener.java | 4 +- 12 files changed, 329 insertions(+), 420 deletions(-) delete mode 100644 src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java create mode 100644 src/main/java/com/moulberry/axiom/integration/NoVersionTranslator.java create mode 100644 src/main/java/com/moulberry/axiom/packet/ChunkSectionModifier.java diff --git a/src/main/java/com/moulberry/axiom/AxiomPaper.java b/src/main/java/com/moulberry/axiom/AxiomPaper.java index 9172f79..a3be60c 100644 --- a/src/main/java/com/moulberry/axiom/AxiomPaper.java +++ b/src/main/java/com/moulberry/axiom/AxiomPaper.java @@ -4,9 +4,12 @@ import com.moulberry.axiom.integration.PaperFailMoveListener; import com.moulberry.axiom.packet.*; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import org.bukkit.Bukkit; +import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.Listener; import org.bukkit.plugin.Plugin; @@ -29,9 +32,20 @@ public class AxiomPaper extends JavaPlugin implements Listener { } private static final Class CraftPlayer = Reflection.getClass("org.bukkit.craftbukkit.entity.CraftPlayer"); - private static final Reflection.Method getHandle = Reflection.getTypedMethod(CraftPlayer, "getHandle", ServerPlayer.class); + private static final Reflection.Method playerGetHandle = Reflection.getTypedMethod(CraftPlayer, "getHandle", ServerPlayer.class); public static ServerPlayer convert(Player player) { - return getHandle.invoke(player); + return playerGetHandle.invoke(player); + } + + private static final Class CraftWorld = Reflection.getClass("org.bukkit.craftbukkit.CraftWorld"); + private static final Reflection.Method worldGetHandle = Reflection.getTypedMethod(CraftWorld, "getHandle", ServerLevel.class); + public static ServerLevel convert(World world) { + return worldGetHandle.invoke(world); + } + + private static final Reflection.Method of = Reflection.getTypedMethod(BlockPos.class, BlockPos.class, long.class); + public static BlockPos convert(long packed) { + return of.invoke(null, packed); } @Override diff --git a/src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java b/src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java index 0415614..35770bc 100644 --- a/src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java +++ b/src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java @@ -1,34 +1,23 @@ package com.moulberry.axiom.buffer; import io.netty.buffer.ByteBuf; -import net.minecraft.core.registries.Registries; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceKey; -import net.minecraft.world.level.biome.Biome; +import org.bukkit.block.Biome; public class BiomeBuffer { private final Position2ByteMap map; - private final ResourceKey[] palette; + private final Biome[] palette; - private BiomeBuffer(Position2ByteMap map, ResourceKey[] palette) { - this.map = map; - this.palette = palette; - } - - public static BiomeBuffer load(ByteBuf buf) { - int paletteSize = buf.readByte(); - ResourceKey[] palette = new ResourceKey[255]; - for (int i = 0; i < paletteSize; i++) { - ResourceKey key = buf.readResourceKey(Registries.BIOME); - palette[i] = key; + public BiomeBuffer(ByteBuf buf) { + palette = new Biome[buf.readByte()]; + for (int i = 0; i < palette.length; i++) { + palette[i] = Biome.valueOf(MojBuf.readKey(buf).getKey().toUpperCase()); } - Position2ByteMap map = Position2ByteMap.load(buf); - return new BiomeBuffer(map, palette); + map = Position2ByteMap.load(buf); } - public void forEachEntry(PositionConsumer> consumer) { + public void forEachEntry(PositionConsumer consumer) { this.map.forEachEntry((x, y, z, v) -> { if (v != 0) consumer.accept(x, y, z, this.palette[(v & 0xFF) - 1]); }); diff --git a/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java b/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java deleted file mode 100644 index 96e9900..0000000 --- a/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.moulberry.axiom.buffer; - -import com.moulberry.axiom.AxiomConstants; -import io.netty.buffer.ByteBuf; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectSet; -import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; -import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; -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.PalettedContainer; -import org.jetbrains.annotations.Nullable; - -public class BlockBuffer { - - public static final BlockState EMPTY_STATE = Blocks.STRUCTURE_VOID.defaultBlockState(); - - private final Long2ObjectMap> values; - - private PalettedContainer last = null; - private long lastId = AxiomConstants.MIN_POSITION_LONG; - private final Long2ObjectMap> blockEntities = new Long2ObjectOpenHashMap<>(); - - public BlockBuffer() { - this.values = new Long2ObjectOpenHashMap<>(); - } - - public static BlockBuffer load(ByteBuf buf) { - BlockBuffer buffer = new BlockBuffer(); - - while (true) { - long index = buf.readLong(); - if (index == AxiomConstants.MIN_POSITION_LONG) break; - - PalettedContainer palettedContainer = buffer.getOrCreateSection(index); - palettedContainer.read((FriendlyByteBuf) buf); - - int blockEntitySize = Math.min(4096, MojBuf.readVarInt(buf)); - if (blockEntitySize > 0) { - Short2ObjectMap map = new Short2ObjectOpenHashMap<>(blockEntitySize); - for (int i = 0; i < blockEntitySize; i++) { - short offset = buf.readShort(); - CompressedBlockEntity blockEntity = CompressedBlockEntity.read(buf); - map.put(offset, blockEntity); - } - buffer.blockEntities.put(index, map); - } - } - - return buffer; - } - - @Nullable - public Short2ObjectMap getBlockEntityChunkMap(long cpos) { - return this.blockEntities.get(cpos); - } - - public ObjectSet>> entrySet() { - return this.values.long2ObjectEntrySet(); - } - - private PalettedContainer getOrCreateSection(long id) { - if (this.last == null || id != this.lastId) { - this.lastId = id; - this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, - EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES)); - } - - return this.last; - } - -} diff --git a/src/main/java/com/moulberry/axiom/buffer/Position2ByteMap.java b/src/main/java/com/moulberry/axiom/buffer/Position2ByteMap.java index dfaf5bc..61fe290 100644 --- a/src/main/java/com/moulberry/axiom/buffer/Position2ByteMap.java +++ b/src/main/java/com/moulberry/axiom/buffer/Position2ByteMap.java @@ -1,10 +1,11 @@ package com.moulberry.axiom.buffer; import com.moulberry.axiom.AxiomConstants; +import com.moulberry.axiom.Reflection; +import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minecraft.core.BlockPos; -import net.minecraft.network.FriendlyByteBuf; public class Position2ByteMap { @@ -20,26 +21,29 @@ public class Position2ByteMap { this.defaultValue = defaultValue; } - public static Position2ByteMap load(FriendlyByteBuf friendlyByteBuf) { - Position2ByteMap map = new Position2ByteMap(friendlyByteBuf.readByte()); + public static Position2ByteMap load(ByteBuf byteBuf) { + Position2ByteMap map = new Position2ByteMap(byteBuf.readByte()); while (true) { - long pos = friendlyByteBuf.readLong(); + long pos = byteBuf.readLong(); if (pos == AxiomConstants.MIN_POSITION_LONG) break; byte[] bytes = new byte[16*16*16]; - friendlyByteBuf.readBytes(bytes); + byteBuf.readBytes(bytes); map.map.put(pos, bytes); } return map; } + private static final Reflection.Method getX = Reflection.getTypedMethod(BlockPos.class, int.class, 0, long.class); + private static final Reflection.Method getY = Reflection.getTypedMethod(BlockPos.class, int.class, 1, long.class); + private static final Reflection.Method getZ = Reflection.getTypedMethod(BlockPos.class, int.class, 2, long.class); public void forEachEntry(EntryConsumer consumer) { for (Long2ObjectMap.Entry 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 cx = getX.invoke(null, entry.getLongKey()) * 16; + int cy = getY.invoke(null, entry.getLongKey()) * 16; + int cz = getZ.invoke(null, entry.getLongKey()) * 16; int index = 0; for (int z=0; z<16; z++) { diff --git a/src/main/java/com/moulberry/axiom/integration/NoVersionTranslator.java b/src/main/java/com/moulberry/axiom/integration/NoVersionTranslator.java new file mode 100644 index 0000000..d78eaa7 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/integration/NoVersionTranslator.java @@ -0,0 +1,63 @@ +package com.moulberry.axiom.integration; + +import com.moulberry.axiom.Reflection; +import com.moulberry.axiom.buffer.PositionConsumer; +import io.netty.buffer.ByteBuf; +import net.minecraft.core.DefaultedRegistry; +import net.minecraft.core.IdMapper; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.PalettedContainer; +import org.bukkit.entity.Player; + +import java.util.function.IntFunction; + +public class NoVersionTranslator implements VersionTranslator { + + private static IdMapper BLOCK_STATE_REGISTRY = Reflection.getField(Block.class, IdMapper.class, BlockState.class).get(null); + public static BlockState idToState(int state) { + return BLOCK_STATE_REGISTRY.byId(state); + } + + private static final BlockState EMPTY_STATE = + Reflection.getTypedMethod(Block.class, BlockState.class).invoke((Block) // Block.defaultBlockState() + Reflection.getField(BuiltInRegistries.class, DefaultedRegistry.class, Block.class).get(null) // BuiltInRegistries.BLOCK + .get(new ResourceLocation("structure_void")) + ); + public static void iterOverSection(PosBlockState container, PositionConsumer consumer) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + BlockState state = container.at(x, y, z); + if (state == EMPTY_STATE) continue; + + consumer.accept(x, y, z, state); + } + } + } + } + + @Override + public IntFunction blockStateMapper(Player player) { + return NoVersionTranslator::idToState; + } + + private static final PalettedContainer.Strategy SECTION_STATES = Reflection.getField(PalettedContainer.Strategy.class, PalettedContainer.Strategy.class).get(null); + private static final Reflection.Method read = Reflection.getMethod(PalettedContainer.class, FriendlyByteBuf.class); + @Override + public void readPalettedContainer(Player player, ByteBuf buf, PositionConsumer consumer) { + //EMPTY_STATE is completely ignored + PalettedContainer container = new PalettedContainer<>(BLOCK_STATE_REGISTRY, EMPTY_STATE, SECTION_STATES); + read.invoke(container, buf); + iterOverSection(container::get, consumer); + } + + @FunctionalInterface + public interface PosBlockState { + BlockState at(int sx, int sy, int sz); + } + +} diff --git a/src/main/java/com/moulberry/axiom/integration/VersionTranslator.java b/src/main/java/com/moulberry/axiom/integration/VersionTranslator.java index 0f2f6d9..be72b60 100644 --- a/src/main/java/com/moulberry/axiom/integration/VersionTranslator.java +++ b/src/main/java/com/moulberry/axiom/integration/VersionTranslator.java @@ -1,5 +1,8 @@ package com.moulberry.axiom.integration; +import com.moulberry.axiom.buffer.PositionConsumer; +import io.netty.buffer.ByteBuf; +import net.minecraft.world.level.block.state.BlockState; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -7,16 +10,10 @@ import java.util.function.IntFunction; public interface VersionTranslator { - VersionTranslator impl = Bukkit.getPluginManager().isPluginEnabled("ViaVersion") ? new ViaVersionTranslator() : new Dummy(); + VersionTranslator impl = Bukkit.getPluginManager().isPluginEnabled("ViaVersion") ? new ViaVersionTranslator() : new NoVersionTranslator(); - IntFunction blockStateMapper(Player player); + IntFunction blockStateMapper(Player player); - class Dummy implements VersionTranslator { - - @Override - public IntFunction blockStateMapper(Player player) { - return id -> id; - } - } + void readPalettedContainer(Player player, ByteBuf buf, PositionConsumer consumer); } diff --git a/src/main/java/com/moulberry/axiom/integration/ViaVersionTranslator.java b/src/main/java/com/moulberry/axiom/integration/ViaVersionTranslator.java index 164eb25..5c292bf 100644 --- a/src/main/java/com/moulberry/axiom/integration/ViaVersionTranslator.java +++ b/src/main/java/com/moulberry/axiom/integration/ViaVersionTranslator.java @@ -1,10 +1,16 @@ package com.moulberry.axiom.integration; +import com.moulberry.axiom.buffer.PositionConsumer; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.data.MappingData; +import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; +import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.protocol.ProtocolManager; import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; import com.viaversion.viaversion.api.protocol.version.ServerProtocolVersion; +import com.viaversion.viaversion.api.type.types.version.PaletteType1_18; +import io.netty.buffer.ByteBuf; +import net.minecraft.world.level.block.state.BlockState; import org.bukkit.entity.Player; import java.util.ArrayList; @@ -22,10 +28,10 @@ public class ViaVersionTranslator implements VersionTranslator { } @Override - public IntFunction blockStateMapper(Player player) { + public IntFunction blockStateMapper(Player player) { List path = protocolManager.getProtocolPath(Via.getAPI().getPlayerVersion(player.getUniqueId()), serverVersion.highestSupportedVersion()); if(path == null) - return id -> id; + return NoVersionTranslator::idToState; List> mappers = new ArrayList<>(path.size()); for(ProtocolPathEntry entry : path) { @@ -37,8 +43,30 @@ public class ViaVersionTranslator implements VersionTranslator { return id -> { for(IntFunction transformer : mappers) id = transformer.apply(id); - return id; + return NoVersionTranslator.idToState(id); }; } + @Override + public void readPalettedContainer(Player player, ByteBuf buf, PositionConsumer consumer) { + //TODO GlobalPaletteBits depend on player version + DataPalette container; + try { + container = new PaletteType1_18(PaletteType.BLOCKS, 15).read(buf); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + + BlockState[] palette = new BlockState[container.size()]; + IntFunction mapper = blockStateMapper(player); + for(int i = 0; i < container.size(); i++) { + palette[i] = mapper.apply(container.idByIndex(i)); + } + + NoVersionTranslator.iterOverSection( + (sx, sy, sz) -> palette[container.paletteIndexAt(container.index(sx, sy, sz))], + consumer + ); + } + } diff --git a/src/main/java/com/moulberry/axiom/packet/ChunkSectionModifier.java b/src/main/java/com/moulberry/axiom/packet/ChunkSectionModifier.java new file mode 100644 index 0000000..13daf29 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/packet/ChunkSectionModifier.java @@ -0,0 +1,135 @@ +package com.moulberry.axiom.packet; + +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.Reflection; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +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.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.lighting.LightEngine; +import net.minecraft.world.level.lighting.LightEventListener; +import org.bukkit.World; + +import java.util.List; + +public class ChunkSectionModifier { + + //TODO multiversioning + private static final List types = List.of(Heightmap.Types.WORLD_SURFACE, Heightmap.Types.OCEAN_FLOOR, Heightmap.Types.MOTION_BLOCKING, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); + //TODO end multiversioning + + private final int cx; + private final int cy; + private final int cz; + + private final ServerChunkCache chunkSource; + private final LightEventListener lightEngine; + private final LevelChunk chunk; + private final LevelChunkSection section; + private final Heightmap[] heightmaps; + private final boolean hadOnlyAir; + + public ChunkSectionModifier(World world, int cx, int cy, int cz) { + this.cx = cx; + this.cy = cy; + this.cz = cz; + + ServerLevel level = AxiomPaper.convert(world); + //TODO multiversioning + chunkSource = level.getChunkSource(); + lightEngine = chunkSource.getLightEngine(); + chunk = level.getChunk(cx, cz); + + section = chunk.getSection(cy - world.getMinHeight() >> 4); + hadOnlyAir = section.hasOnlyAir(); + + heightmaps = types.stream().map(chunk.heightmaps::get).toArray(Heightmap[]::new); + //TODO end multiversioning + } + + public void setState(int sx, int sy, int sz, BlockState state) { + //TODO multiversioning + if (hadOnlyAir && state.isAir()) + return; + + BlockState old = section.setBlockState(sx, sy, sz, state, false); + if (state == old) + return; + + int by = cy * 16 + sy; + BlockPos pos = new BlockPos(cx * 16 + sx, by, cz * 16 + sz); + + for (Heightmap heightmap : heightmaps) + heightmap.update(sx, by, sz, state); + + if (state.hasBlockEntity()) { + setBlockEntity(state, pos); + } else if (old.hasBlockEntity()) { + chunk.removeBlockEntity(pos); + } + + chunkSource.blockChanged(pos); + + if (LightEngine.hasDifferentLightProperties(chunk, pos, old, state)) { + lightEngine.checkBlock(pos); + } + //TODO end multiversioning + } + + public void setBlockEntity(int sx, int sy, int sz, CompoundTag tag) { + //TODO multiversioning + BlockEntity blockEntity = chunk.getBlockEntity(new BlockPos(cx * 16 + sx, cy * 16 + sy, cz * 16 + sz), LevelChunk.EntityCreationType.CHECK); + + if(blockEntity != null) + blockEntity.load(tag); + //TODO end multiversioning + } + + private static final Reflection.Method updateBlockEntityTicker = Reflection.getMethod(LevelChunk.class, BlockEntity.class); + private void setBlockEntity(BlockState state, BlockPos pos) { + //TODO multiversioning + BlockEntity blockEntity = chunk.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); + + if (blockEntity != null) { + if (blockEntity.getType().isValid(state)) { + // Block entity is here and the type is correct + blockEntity.setBlockState(state); + + updateBlockEntityTicker.invoke(chunk, blockEntity); + } else { + // Block entity type isn't correct, we need to recreate it + chunk.removeBlockEntity(pos); + blockEntity = null; + } + } + + if (blockEntity == null) { + // There isn't a block entity here, create it! + Block block = state.getBlock(); + blockEntity = ((EntityBlock) block).newBlockEntity(pos, state); + if (blockEntity != null) { + chunk.addAndRegisterBlockEntity(blockEntity); + } + } + //TODO end multiversioning + } + + public void finish() { + //TODO multiversioning + boolean hasOnlyAir = section.hasOnlyAir(); + if (hadOnlyAir != hasOnlyAir) { + lightEngine.updateSectionStatus(SectionPos.of(cx, cy, cz), hasOnlyAir); + } + + chunk.setUnsaved(true); + //TODO end multiversioning + } +} diff --git a/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java b/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java index 9712ef1..d019397 100644 --- a/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java @@ -1,5 +1,6 @@ package com.moulberry.axiom.packet; +import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.OutChannel; import com.moulberry.axiom.Reflection; import com.moulberry.axiom.buffer.CompressedBlockEntity; @@ -21,10 +22,6 @@ import java.io.ByteArrayOutputStream; public class RequestBlockEntityPacketListener implements AxiomPacketListener { - private static final Class CraftWorld = Reflection.getClass("org.bukkit.craftbukkit.CraftWorld"); - private static final Reflection.Method getHandle = Reflection.getTypedMethod(CraftWorld, ServerLevel.class); - - private static final Reflection.Method of = Reflection.getTypedMethod(BlockPos.class, BlockPos.class, long.class); private static final Reflection.Method getBlockEntity = Reflection.getTypedMethod(ServerLevel.class, BlockEntity.class, BlockPos.class); private static final Reflection.Method saveWithoutMetadata = Reflection.getTypedMethod(BlockEntity.class, CompoundTag.class, 2); @@ -39,7 +36,7 @@ public class RequestBlockEntityPacketListener implements AxiomPacketListener { return; } - ServerLevel level = getHandle.invoke(world); + ServerLevel level = AxiomPaper.convert(world); Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -47,7 +44,7 @@ public class RequestBlockEntityPacketListener implements AxiomPacketListener { int count = MojBuf.readVarInt(buf); for (int i = 0; i < count; i++) { long pos = buf.readLong(); - BlockEntity blockEntity = getBlockEntity.invoke(level, of.invoke(null, pos)); + BlockEntity blockEntity = getBlockEntity.invoke(level, AxiomPaper.convert(pos)); if (blockEntity != null) { CompoundTag tag = saveWithoutMetadata.invoke(blockEntity); map.put(pos, CompressedBlockEntity.compress(tag, baos)); diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java index 89ce25c..abd4336 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java @@ -1,259 +1,101 @@ package com.moulberry.axiom.packet; -import com.moulberry.axiom.Reflection; +import com.moulberry.axiom.AxiomConstants; import com.moulberry.axiom.buffer.BiomeBuffer; -import com.moulberry.axiom.buffer.BlockBuffer; import com.moulberry.axiom.buffer.CompressedBlockEntity; import com.moulberry.axiom.buffer.MojBuf; import com.moulberry.axiom.event.AxiomModifyWorldEvent; import com.moulberry.axiom.integration.RegionProtection; +import com.moulberry.axiom.integration.VersionTranslator; import io.netty.buffer.ByteBuf; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; 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.Blocks; 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; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.lighting.LightEngine; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_20_R1.CraftServer; +import org.bukkit.Chunk; +import org.bukkit.World; import org.bukkit.entity.Player; import java.util.*; public class SetBlockBufferPacketListener implements AxiomPacketListener { - private static final Reflection.Method updateBlockEntityTicker = Reflection.getMethod(LevelChunk.class, BlockEntity.class); - @Override public void onMessage(Player player, ByteBuf buf) { - MinecraftServer server = ((CraftServer)player.getServer()).getServer(); - if (server == null) return; + World world = Bukkit.getWorld(MojBuf.readKey(buf)); + + // Call AxiomModifyWorldEvent event + AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player, world); + Bukkit.getPluginManager().callEvent(modifyWorldEvent); + if (modifyWorldEvent.isCancelled()) return; - ResourceKey worldKey = buf.readResourceKey(Registries.DIMENSION); MojBuf.readUUID(buf); // Discard, we don't need to associate buffers - if (!buf.readBoolean()) { MojBuf.readNbt(buf); // Discard sourceInfo } byte type = buf.readByte(); if (type == 0) { - BlockBuffer buffer = BlockBuffer.load(buf); - applyBlockBuffer(player, server, buffer, worldKey); + applyBlockBuffer(world, player, buf); } else if (type == 1) { - BiomeBuffer buffer = BiomeBuffer.load(buf); - applyBiomeBuffer(server, buffer, worldKey); + applyBiomeBuffer(world, new BiomeBuffer(buf)); } else { - throw new RuntimeException("Unknown buffer type: " + type); + throw new IllegalArgumentException("Unknown buffer type: " + type); } } - private void applyBlockBuffer(Player player, MinecraftServer server, BlockBuffer buffer, ResourceKey worldKey) { - ServerLevel world = server.getLevel(worldKey); - if (world == null) return; + private void applyBlockBuffer(World world, Player player, ByteBuf buf) { + RegionProtection regionProtection = RegionProtection.getProtection.apply(player, world); - // Call AxiomModifyWorldEvent event - AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player, world.getWorld()); - Bukkit.getPluginManager().callEvent(modifyWorldEvent); - if (modifyWorldEvent.isCancelled()) return; + long sectionPos; + while ((sectionPos = buf.readLong()) != AxiomConstants.MIN_POSITION_LONG) { + int cx = BlockPos.getX(sectionPos); + int cy = BlockPos.getY(sectionPos); + int cz = BlockPos.getZ(sectionPos); - RegionProtection regionProtection = RegionProtection.getProtection.apply(player, world.getWorld()); + boolean canBuildInSection = regionProtection.canBuildInSection(cx, cy, cz); + ChunkSectionModifier section = new ChunkSectionModifier(world, cx, cy, cz); - // Allowed, apply buffer - BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); + VersionTranslator.impl.readPalettedContainer(player, buf, canBuildInSection ? section::setState : (x, y, z, blockState) -> {}); - var lightEngine = world.getChunkSource().getLightEngine(); - - BlockState emptyState = BlockBuffer.EMPTY_STATE; - - for (Long2ObjectMap.Entry> entry : buffer.entrySet()) { - int cx = BlockPos.getX(entry.getLongKey()); - int cy = BlockPos.getY(entry.getLongKey()); - int cz = BlockPos.getZ(entry.getLongKey()); - PalettedContainer container = entry.getValue(); - - if (cy < world.getMinSection() || cy >= world.getMaxSection()) { - continue; + int blockEntitySize = Math.min(4096, Math.max(0, MojBuf.readVarInt(buf))); + for (int i = 0; i < blockEntitySize; i++) { + short offset = buf.readShort(); + CompressedBlockEntity entity = CompressedBlockEntity.read(buf); + if(canBuildInSection) + section.setBlockEntity(offset & 0xF, (offset >> 4) & 0xF, offset >> 8, entity.decompress()); } - if (!regionProtection.canBuildInSection(cx, cy, cz)) { - continue; - } - - LevelChunk chunk = world.getChunk(cx, cz); - chunk.setUnsaved(true); - - LevelChunkSection section = chunk.getSection(world.getSectionIndexFromSectionY(cy)); - PalettedContainer sectionStates = section.getStates(); - boolean hasOnlyAir = section.hasOnlyAir(); - - Heightmap worldSurface = null; - Heightmap oceanFloor = null; - Heightmap motionBlocking = null; - Heightmap motionBlockingNoLeaves = null; - for (Map.Entry 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 -> {} - } - } - - Short2ObjectMap blockEntityChunkMap = buffer.getBlockEntityChunkMap(entry.getLongKey()); - - sectionStates.acquire(); - try { - for (int x = 0; x < 16; x++) { - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; 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; - - blockPos.set(bx, by, bz); - - if (hasOnlyAir && blockState.isAir()) { - continue; - } - - BlockState old = section.setBlockState(x, y, z, blockState, false); - if (blockState != old) { - Block block = blockState.getBlock(); - motionBlocking.update(x, by, z, blockState); - motionBlockingNoLeaves.update(x, by, z, blockState); - oceanFloor.update(x, by, z, blockState); - worldSurface.update(x, by, z, blockState); - - if (false) { // Full update - old.onRemove(world, blockPos, blockState, false); - - if (sectionStates.get(x, y, z).is(block)) { - blockState.onPlace(world, blockPos, old, false); - } - } - - 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); - - updateBlockEntityTicker.invoke(chunk, blockEntity); - } 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); - } - - world.getChunkSource().blockChanged(blockPos); // todo: maybe simply resend chunk instead of this? - - if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) { - lightEngine.checkBlock(blockPos); - } - } - } - } - } - } finally { - sectionStates.release(); - } - - boolean nowHasOnlyAir = section.hasOnlyAir(); - if (hasOnlyAir != nowHasOnlyAir) { - world.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir); - } + if(canBuildInSection) + section.finish(); } } - - private void applyBiomeBuffer(MinecraftServer server, BiomeBuffer biomeBuffer, ResourceKey worldKey) { - ServerLevel world = server.getLevel(worldKey); - if (world == null) return; - - Set changedChunks = new HashSet<>(); - - int minSection = world.getMinSection(); - int maxSection = world.getMaxSection(); - - Optional> registryOptional = world.registryAccess().registry(Registries.BIOME); - if (registryOptional.isEmpty()) return; - - Registry registry = registryOptional.get(); - + private void applyBiomeBuffer(World world, BiomeBuffer biomeBuffer) { + Set changedChunks = new HashSet<>(); 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> container = (PalettedContainer>) section.getBiomes(); - - var holder = registry.getHolder(biome); - if (holder.isPresent()) { - container.set(x & 3, y & 3, z & 3, holder.get()); - changedChunks.add(chunk); - } + world.setBiome(x << 2, y << 2, z << 2, biome); + changedChunks.add(world.getChunkAt(x << 2, z << 2)); }); + //TODO multiversioning var chunkMap = world.getChunkSource().chunkMap; HashMap> 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))); + //TODO end multiversioning } } diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java index 34eb4d8..b62ee46 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java @@ -1,133 +1,48 @@ package com.moulberry.axiom.packet; -import com.moulberry.axiom.Reflection; +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.buffer.MojBuf; import com.moulberry.axiom.event.AxiomModifyWorldEvent; import com.moulberry.axiom.integration.VersionTranslator; import io.netty.buffer.ByteBuf; import net.minecraft.core.BlockPos; -import net.minecraft.core.SectionPos; -import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -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.LevelChunk; -import net.minecraft.world.level.chunk.LevelChunkSection; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.lighting.LightEngine; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import java.util.Map; - public class SetBlockPacketListener implements AxiomPacketListener { - private static final Reflection.Method updateBlockEntityTicker = Reflection.getMethod(LevelChunk.class, BlockEntity.class); - @Override - public void onMessage(@NotNull Player bukkitPlayer, @NotNull ByteBuf buf) { + public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) { // Check if player is allowed to modify this world - AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(bukkitPlayer, bukkitPlayer.getWorld()); + AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player, player.getWorld()); Bukkit.getPluginManager().callEvent(modifyWorldEvent); if (modifyWorldEvent.isCancelled()) return; // Read packet - BlockPos blockPos = buf.readBlockPos(); - BlockState blockState = Block.BLOCK_STATE_REGISTRY.byId(VersionTranslator.impl.blockStateMapper(bukkitPlayer).apply(buf.readVarInt())); + BlockPos pos = AxiomPaper.convert(buf.readLong()); + BlockState state = VersionTranslator.impl.blockStateMapper(player).apply(MojBuf.readVarInt(buf)); boolean updateNeighbors = buf.readBoolean(); int sequenceId = buf.readInt(); - ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle(); + ServerPlayer serverPlayer = AxiomPaper.convert(player); // Update blocks + //TODO multiversioning if (updateNeighbors) { - player.level().setBlock(blockPos, blockState, 3); + AxiomPaper.convert(player.getWorld()).setBlock(pos, state, 3); } else { - int bx = blockPos.getX(); - int by = blockPos.getY(); - int bz = blockPos.getZ(); - int x = bx & 0xF; - int y = by & 0xF; - int z = bz & 0xF; - int cx = bx >> 4; - int cy = by >> 4; - int cz = bz >> 4; - - ServerLevel level = player.serverLevel(); - LevelChunk chunk = level.getChunk(cx, cz); - chunk.setUnsaved(true); - - LevelChunkSection section = chunk.getSection(level.getSectionIndexFromSectionY(cy)); - boolean hasOnlyAir = section.hasOnlyAir(); - - Heightmap worldSurface = null; - Heightmap oceanFloor = null; - Heightmap motionBlocking = null; - Heightmap motionBlockingNoLeaves = null; - for (Map.Entry 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 -> {} - } - } - - BlockState old = section.setBlockState(x, y, z, blockState, false); - if (blockState != old) { - Block block = blockState.getBlock(); - motionBlocking.update(x, by, z, blockState); - motionBlockingNoLeaves.update(x, by, z, blockState); - oceanFloor.update(x, by, z, blockState); - worldSurface.update(x, by, z, blockState); - - if (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 - // Just update the state and ticker and move on - blockEntity.setBlockState(blockState); - - updateBlockEntityTicker.invoke(chunk, blockEntity); - } 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); - } - } - } else if (old.hasBlockEntity()) { - chunk.removeBlockEntity(blockPos); - } - - level.getChunkSource().blockChanged(blockPos); - if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) { - level.getChunkSource().getLightEngine().checkBlock(blockPos); - } - } - - boolean nowHasOnlyAir = section.hasOnlyAir(); - if (hasOnlyAir != nowHasOnlyAir) { - level.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir); - } + ChunkSectionModifier section = new ChunkSectionModifier(player.getWorld(), pos.getX() >> 4, pos.getY() >> 4, pos.getZ() >> 4); + section.setState(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF, state); + section.finish(); } if (sequenceId >= 0) { - player.connection.ackBlockChangesUpTo(sequenceId); + serverPlayer.connection.ackBlockChangesUpTo(sequenceId); } + //TODO end multiversioning } } diff --git a/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java b/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java index ae76cdd..47a80dc 100644 --- a/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java @@ -2,7 +2,7 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.buffer.MojBuf; import com.moulberry.axiom.event.AxiomTeleportEvent; -import net.minecraft.network.FriendlyByteBuf; +import io.netty.buffer.ByteBuf; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull; public class TeleportPacketListener implements AxiomPacketListener { @Override - public void onMessage(@NotNull Player player, @NotNull FriendlyByteBuf buf) { + public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) { World world = Bukkit.getWorld(MojBuf.readKey(buf)); double x = buf.readDouble(); double y = buf.readDouble();