WIP Multiversion (Small sections remaining)
Einige Prüfungen sind fehlgeschlagen
SteamWarCI Build failed

Dieser Commit ist enthalten in:
Lixfel 2023-09-09 10:32:44 +02:00
Ursprung 6f44fad535
Commit e9a2b15259
12 geänderte Dateien mit 329 neuen und 420 gelöschten Zeilen

Datei anzeigen

@ -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<Player> CraftPlayer = Reflection.getClass("org.bukkit.craftbukkit.entity.CraftPlayer");
private static final Reflection.Method<Player, ServerPlayer> getHandle = Reflection.getTypedMethod(CraftPlayer, "getHandle", ServerPlayer.class);
private static final Reflection.Method<Player, ServerPlayer> 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<World> CraftWorld = Reflection.getClass("org.bukkit.craftbukkit.CraftWorld");
private static final Reflection.Method<World, ServerLevel> worldGetHandle = Reflection.getTypedMethod(CraftWorld, "getHandle", ServerLevel.class);
public static ServerLevel convert(World world) {
return worldGetHandle.invoke(world);
}
private static final Reflection.Method<BlockPos, BlockPos> of = Reflection.getTypedMethod(BlockPos.class, BlockPos.class, long.class);
public static BlockPos convert(long packed) {
return of.invoke(null, packed);
}
@Override

Datei anzeigen

@ -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<Biome>[] palette;
private final Biome[] palette;
private BiomeBuffer(Position2ByteMap map, ResourceKey<Biome>[] palette) {
this.map = map;
this.palette = palette;
}
public static BiomeBuffer load(ByteBuf buf) {
int paletteSize = buf.readByte();
ResourceKey<Biome>[] palette = new ResourceKey[255];
for (int i = 0; i < paletteSize; i++) {
ResourceKey<Biome> 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<ResourceKey<Biome>> consumer) {
public void forEachEntry(PositionConsumer<Biome> consumer) {
this.map.forEachEntry((x, y, z, v) -> {
if (v != 0) consumer.accept(x, y, z, this.palette[(v & 0xFF) - 1]);
});

Datei anzeigen

@ -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<PalettedContainer<BlockState>> values;
private PalettedContainer<BlockState> last = null;
private long lastId = AxiomConstants.MIN_POSITION_LONG;
private final Long2ObjectMap<Short2ObjectMap<CompressedBlockEntity>> 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<BlockState> palettedContainer = buffer.getOrCreateSection(index);
palettedContainer.read((FriendlyByteBuf) buf);
int blockEntitySize = Math.min(4096, MojBuf.readVarInt(buf));
if (blockEntitySize > 0) {
Short2ObjectMap<CompressedBlockEntity> 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<CompressedBlockEntity> getBlockEntityChunkMap(long cpos) {
return this.blockEntities.get(cpos);
}
public ObjectSet<Long2ObjectMap.Entry<PalettedContainer<BlockState>>> entrySet() {
return this.values.long2ObjectEntrySet();
}
private PalettedContainer<BlockState> getOrCreateSection(long id) {
if (this.last == null || id != this.lastId) {
this.lastId = id;
this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY,
EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES));
}
return this.last;
}
}

Datei anzeigen

@ -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<BlockPos, Integer> getX = Reflection.getTypedMethod(BlockPos.class, int.class, 0, long.class);
private static final Reflection.Method<BlockPos, Integer> getY = Reflection.getTypedMethod(BlockPos.class, int.class, 1, long.class);
private static final Reflection.Method<BlockPos, Integer> getZ = Reflection.getTypedMethod(BlockPos.class, int.class, 2, long.class);
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 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++) {

Datei anzeigen

@ -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<BlockState> 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<BlockState> 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<BlockState> 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<PalettedContainer, Object> read = Reflection.getMethod(PalettedContainer.class, FriendlyByteBuf.class);
@Override
public void readPalettedContainer(Player player, ByteBuf buf, PositionConsumer<BlockState> consumer) {
//EMPTY_STATE is completely ignored
PalettedContainer<BlockState> 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);
}
}

Datei anzeigen

@ -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<Integer> blockStateMapper(Player player);
IntFunction<BlockState> blockStateMapper(Player player);
class Dummy implements VersionTranslator {
@Override
public IntFunction<Integer> blockStateMapper(Player player) {
return id -> id;
}
}
void readPalettedContainer(Player player, ByteBuf buf, PositionConsumer<BlockState> consumer);
}

Datei anzeigen

@ -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<Integer> blockStateMapper(Player player) {
public IntFunction<BlockState> blockStateMapper(Player player) {
List<ProtocolPathEntry> path = protocolManager.getProtocolPath(Via.getAPI().getPlayerVersion(player.getUniqueId()), serverVersion.highestSupportedVersion());
if(path == null)
return id -> id;
return NoVersionTranslator::idToState;
List<IntFunction<Integer>> mappers = new ArrayList<>(path.size());
for(ProtocolPathEntry entry : path) {
@ -37,8 +43,30 @@ public class ViaVersionTranslator implements VersionTranslator {
return id -> {
for(IntFunction<Integer> transformer : mappers)
id = transformer.apply(id);
return id;
return NoVersionTranslator.idToState(id);
};
}
@Override
public void readPalettedContainer(Player player, ByteBuf buf, PositionConsumer<BlockState> 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<BlockState> 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
);
}
}

Datei anzeigen

@ -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<Heightmap.Types> 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<LevelChunk, Object> 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
}
}

Datei anzeigen

@ -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<World> CraftWorld = Reflection.getClass("org.bukkit.craftbukkit.CraftWorld");
private static final Reflection.Method<World, ServerLevel> getHandle = Reflection.getTypedMethod(CraftWorld, ServerLevel.class);
private static final Reflection.Method<BlockPos, BlockPos> of = Reflection.getTypedMethod(BlockPos.class, BlockPos.class, long.class);
private static final Reflection.Method<ServerLevel, BlockEntity> getBlockEntity = Reflection.getTypedMethod(ServerLevel.class, BlockEntity.class, BlockPos.class);
private static final Reflection.Method<BlockEntity, CompoundTag> 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<CompressedBlockEntity> 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));

Datei anzeigen

@ -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<LevelChunk, Object> 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<Level> 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<Level> 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<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;
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<BlockState> sectionStates = section.getStates();
boolean hasOnlyAir = section.hasOnlyAir();
Heightmap worldSurface = null;
Heightmap oceanFloor = null;
Heightmap motionBlocking = null;
Heightmap motionBlockingNoLeaves = null;
for (Map.Entry<Heightmap.Types, Heightmap> heightmap : chunk.getHeightmaps()) {
switch (heightmap.getKey()) {
case WORLD_SURFACE -> worldSurface = heightmap.getValue();
case OCEAN_FLOOR -> oceanFloor = heightmap.getValue();
case MOTION_BLOCKING -> motionBlocking = heightmap.getValue();
case MOTION_BLOCKING_NO_LEAVES -> motionBlockingNoLeaves = heightmap.getValue();
default -> {}
}
}
Short2ObjectMap<CompressedBlockEntity> 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<Level> worldKey) {
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();
private void applyBiomeBuffer(World world, BiomeBuffer biomeBuffer) {
Set<Chunk> 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<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);
}
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<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)));
//TODO end multiversioning
}
}

Datei anzeigen

@ -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<LevelChunk, Object> 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.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 -> {}
}
}
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
}
}

Datei anzeigen

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