geforkt von Mirrors/AxiomPaperPlugin
WIP Multiversion (Small sections remaining)
Einige Prüfungen sind fehlgeschlagen
SteamWarCI Build failed
Einige Prüfungen sind fehlgeschlagen
SteamWarCI Build failed
Dieser Commit ist enthalten in:
Ursprung
6f44fad535
Commit
e9a2b15259
@ -4,9 +4,12 @@ import com.moulberry.axiom.integration.PaperFailMoveListener;
|
|||||||
import com.moulberry.axiom.packet.*;
|
import com.moulberry.axiom.packet.*;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.plugin.Plugin;
|
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 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) {
|
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
|
@Override
|
||||||
|
@ -1,34 +1,23 @@
|
|||||||
package com.moulberry.axiom.buffer;
|
package com.moulberry.axiom.buffer;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import net.minecraft.core.registries.Registries;
|
import org.bukkit.block.Biome;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
|
||||||
import net.minecraft.resources.ResourceKey;
|
|
||||||
import net.minecraft.world.level.biome.Biome;
|
|
||||||
|
|
||||||
public class BiomeBuffer {
|
public class BiomeBuffer {
|
||||||
|
|
||||||
private final Position2ByteMap map;
|
private final Position2ByteMap map;
|
||||||
private final ResourceKey<Biome>[] palette;
|
private final Biome[] palette;
|
||||||
|
|
||||||
private BiomeBuffer(Position2ByteMap map, ResourceKey<Biome>[] palette) {
|
public BiomeBuffer(ByteBuf buf) {
|
||||||
this.map = map;
|
palette = new Biome[buf.readByte()];
|
||||||
this.palette = palette;
|
for (int i = 0; i < palette.length; i++) {
|
||||||
}
|
palette[i] = Biome.valueOf(MojBuf.readKey(buf).getKey().toUpperCase());
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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) -> {
|
this.map.forEachEntry((x, y, z, v) -> {
|
||||||
if (v != 0) consumer.accept(x, y, z, this.palette[(v & 0xFF) - 1]);
|
if (v != 0) consumer.accept(x, y, z, this.palette[(v & 0xFF) - 1]);
|
||||||
});
|
});
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,10 +1,11 @@
|
|||||||
package com.moulberry.axiom.buffer;
|
package com.moulberry.axiom.buffer;
|
||||||
|
|
||||||
import com.moulberry.axiom.AxiomConstants;
|
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.Long2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
|
||||||
|
|
||||||
public class Position2ByteMap {
|
public class Position2ByteMap {
|
||||||
|
|
||||||
@ -20,26 +21,29 @@ public class Position2ByteMap {
|
|||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Position2ByteMap load(FriendlyByteBuf friendlyByteBuf) {
|
public static Position2ByteMap load(ByteBuf byteBuf) {
|
||||||
Position2ByteMap map = new Position2ByteMap(friendlyByteBuf.readByte());
|
Position2ByteMap map = new Position2ByteMap(byteBuf.readByte());
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
long pos = friendlyByteBuf.readLong();
|
long pos = byteBuf.readLong();
|
||||||
if (pos == AxiomConstants.MIN_POSITION_LONG) break;
|
if (pos == AxiomConstants.MIN_POSITION_LONG) break;
|
||||||
|
|
||||||
byte[] bytes = new byte[16*16*16];
|
byte[] bytes = new byte[16*16*16];
|
||||||
friendlyByteBuf.readBytes(bytes);
|
byteBuf.readBytes(bytes);
|
||||||
map.map.put(pos, bytes);
|
map.map.put(pos, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
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) {
|
public void forEachEntry(EntryConsumer consumer) {
|
||||||
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
|
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
|
||||||
int cx = BlockPos.getX(entry.getLongKey()) * 16;
|
int cx = getX.invoke(null, entry.getLongKey()) * 16;
|
||||||
int cy = BlockPos.getY(entry.getLongKey()) * 16;
|
int cy = getY.invoke(null, entry.getLongKey()) * 16;
|
||||||
int cz = BlockPos.getZ(entry.getLongKey()) * 16;
|
int cz = getZ.invoke(null, entry.getLongKey()) * 16;
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (int z=0; z<16; z++) {
|
for (int z=0; z<16; z++) {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
package com.moulberry.axiom.integration;
|
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.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
@ -7,16 +10,10 @@ import java.util.function.IntFunction;
|
|||||||
|
|
||||||
public interface VersionTranslator {
|
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 {
|
void readPalettedContainer(Player player, ByteBuf buf, PositionConsumer<BlockState> consumer);
|
||||||
|
|
||||||
@Override
|
|
||||||
public IntFunction<Integer> blockStateMapper(Player player) {
|
|
||||||
return id -> id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
package com.moulberry.axiom.integration;
|
package com.moulberry.axiom.integration;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.buffer.PositionConsumer;
|
||||||
import com.viaversion.viaversion.api.Via;
|
import com.viaversion.viaversion.api.Via;
|
||||||
import com.viaversion.viaversion.api.data.MappingData;
|
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.ProtocolManager;
|
||||||
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
|
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
|
||||||
import com.viaversion.viaversion.api.protocol.version.ServerProtocolVersion;
|
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 org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -22,10 +28,10 @@ public class ViaVersionTranslator implements VersionTranslator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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());
|
List<ProtocolPathEntry> path = protocolManager.getProtocolPath(Via.getAPI().getPlayerVersion(player.getUniqueId()), serverVersion.highestSupportedVersion());
|
||||||
if(path == null)
|
if(path == null)
|
||||||
return id -> id;
|
return NoVersionTranslator::idToState;
|
||||||
|
|
||||||
List<IntFunction<Integer>> mappers = new ArrayList<>(path.size());
|
List<IntFunction<Integer>> mappers = new ArrayList<>(path.size());
|
||||||
for(ProtocolPathEntry entry : path) {
|
for(ProtocolPathEntry entry : path) {
|
||||||
@ -37,8 +43,30 @@ public class ViaVersionTranslator implements VersionTranslator {
|
|||||||
return id -> {
|
return id -> {
|
||||||
for(IntFunction<Integer> transformer : mappers)
|
for(IntFunction<Integer> transformer : mappers)
|
||||||
id = transformer.apply(id);
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
135
src/main/java/com/moulberry/axiom/packet/ChunkSectionModifier.java
Normale Datei
135
src/main/java/com/moulberry/axiom/packet/ChunkSectionModifier.java
Normale Datei
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.OutChannel;
|
import com.moulberry.axiom.OutChannel;
|
||||||
import com.moulberry.axiom.Reflection;
|
import com.moulberry.axiom.Reflection;
|
||||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||||
@ -21,10 +22,6 @@ import java.io.ByteArrayOutputStream;
|
|||||||
|
|
||||||
public class RequestBlockEntityPacketListener implements AxiomPacketListener {
|
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<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);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerLevel level = getHandle.invoke(world);
|
ServerLevel level = AxiomPaper.convert(world);
|
||||||
Long2ObjectMap<CompressedBlockEntity> map = new Long2ObjectOpenHashMap<>();
|
Long2ObjectMap<CompressedBlockEntity> map = new Long2ObjectOpenHashMap<>();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
@ -47,7 +44,7 @@ public class RequestBlockEntityPacketListener implements AxiomPacketListener {
|
|||||||
int count = MojBuf.readVarInt(buf);
|
int count = MojBuf.readVarInt(buf);
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
long pos = buf.readLong();
|
long pos = buf.readLong();
|
||||||
BlockEntity blockEntity = getBlockEntity.invoke(level, of.invoke(null, pos));
|
BlockEntity blockEntity = getBlockEntity.invoke(level, AxiomPaper.convert(pos));
|
||||||
if (blockEntity != null) {
|
if (blockEntity != null) {
|
||||||
CompoundTag tag = saveWithoutMetadata.invoke(blockEntity);
|
CompoundTag tag = saveWithoutMetadata.invoke(blockEntity);
|
||||||
map.put(pos, CompressedBlockEntity.compress(tag, baos));
|
map.put(pos, CompressedBlockEntity.compress(tag, baos));
|
||||||
|
@ -1,259 +1,101 @@
|
|||||||
package com.moulberry.axiom.packet;
|
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.BiomeBuffer;
|
||||||
import com.moulberry.axiom.buffer.BlockBuffer;
|
|
||||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||||
import com.moulberry.axiom.buffer.MojBuf;
|
import com.moulberry.axiom.buffer.MojBuf;
|
||||||
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
|
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
|
||||||
import com.moulberry.axiom.integration.RegionProtection;
|
import com.moulberry.axiom.integration.RegionProtection;
|
||||||
|
import com.moulberry.axiom.integration.VersionTranslator;
|
||||||
import io.netty.buffer.ByteBuf;
|
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.BlockPos;
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.core.Registry;
|
|
||||||
import net.minecraft.core.SectionPos;
|
|
||||||
import net.minecraft.core.registries.Registries;
|
|
||||||
import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket;
|
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.server.level.ServerPlayer;
|
||||||
import net.minecraft.world.level.ChunkPos;
|
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.Block;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.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.PalettedContainer;
|
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.Bukkit;
|
||||||
import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.World;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class SetBlockBufferPacketListener implements AxiomPacketListener {
|
public class SetBlockBufferPacketListener implements AxiomPacketListener {
|
||||||
|
|
||||||
private static final Reflection.Method<LevelChunk, Object> updateBlockEntityTicker = Reflection.getMethod(LevelChunk.class, BlockEntity.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(Player player, ByteBuf buf) {
|
public void onMessage(Player player, ByteBuf buf) {
|
||||||
MinecraftServer server = ((CraftServer)player.getServer()).getServer();
|
World world = Bukkit.getWorld(MojBuf.readKey(buf));
|
||||||
if (server == null) return;
|
|
||||||
|
// 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
|
MojBuf.readUUID(buf); // Discard, we don't need to associate buffers
|
||||||
|
|
||||||
if (!buf.readBoolean()) {
|
if (!buf.readBoolean()) {
|
||||||
MojBuf.readNbt(buf); // Discard sourceInfo
|
MojBuf.readNbt(buf); // Discard sourceInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
byte type = buf.readByte();
|
byte type = buf.readByte();
|
||||||
if (type == 0) {
|
if (type == 0) {
|
||||||
BlockBuffer buffer = BlockBuffer.load(buf);
|
applyBlockBuffer(world, player, buf);
|
||||||
applyBlockBuffer(player, server, buffer, worldKey);
|
|
||||||
} else if (type == 1) {
|
} else if (type == 1) {
|
||||||
BiomeBuffer buffer = BiomeBuffer.load(buf);
|
applyBiomeBuffer(world, new BiomeBuffer(buf));
|
||||||
applyBiomeBuffer(server, buffer, worldKey);
|
|
||||||
} else {
|
} 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) {
|
private void applyBlockBuffer(World world, Player player, ByteBuf buf) {
|
||||||
ServerLevel world = server.getLevel(worldKey);
|
RegionProtection regionProtection = RegionProtection.getProtection.apply(player, world);
|
||||||
if (world == null) return;
|
|
||||||
|
|
||||||
// Call AxiomModifyWorldEvent event
|
long sectionPos;
|
||||||
AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player, world.getWorld());
|
while ((sectionPos = buf.readLong()) != AxiomConstants.MIN_POSITION_LONG) {
|
||||||
Bukkit.getPluginManager().callEvent(modifyWorldEvent);
|
int cx = BlockPos.getX(sectionPos);
|
||||||
if (modifyWorldEvent.isCancelled()) return;
|
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
|
VersionTranslator.impl.readPalettedContainer(player, buf, canBuildInSection ? section::setState : (x, y, z, blockState) -> {});
|
||||||
BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
|
|
||||||
|
|
||||||
var lightEngine = world.getChunkSource().getLightEngine();
|
int blockEntitySize = Math.min(4096, Math.max(0, MojBuf.readVarInt(buf)));
|
||||||
|
for (int i = 0; i < blockEntitySize; i++) {
|
||||||
BlockState emptyState = BlockBuffer.EMPTY_STATE;
|
short offset = buf.readShort();
|
||||||
|
CompressedBlockEntity entity = CompressedBlockEntity.read(buf);
|
||||||
for (Long2ObjectMap.Entry<PalettedContainer<BlockState>> entry : buffer.entrySet()) {
|
if(canBuildInSection)
|
||||||
int cx = BlockPos.getX(entry.getLongKey());
|
section.setBlockEntity(offset & 0xF, (offset >> 4) & 0xF, offset >> 8, entity.decompress());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!regionProtection.canBuildInSection(cx, cy, cz)) {
|
if(canBuildInSection)
|
||||||
continue;
|
section.finish();
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyBiomeBuffer(World world, BiomeBuffer biomeBuffer) {
|
||||||
private void applyBiomeBuffer(MinecraftServer server, BiomeBuffer biomeBuffer, ResourceKey<Level> worldKey) {
|
Set<Chunk> changedChunks = new HashSet<>();
|
||||||
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) -> {
|
biomeBuffer.forEachEntry((x, y, z, biome) -> {
|
||||||
int cy = y >> 2;
|
world.setBiome(x << 2, y << 2, z << 2, biome);
|
||||||
if (cy < minSection || cy >= maxSection) {
|
changedChunks.add(world.getChunkAt(x << 2, z << 2));
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//TODO multiversioning
|
||||||
var chunkMap = world.getChunkSource().chunkMap;
|
var chunkMap = world.getChunkSource().chunkMap;
|
||||||
HashMap<ServerPlayer, List<LevelChunk>> map = new HashMap<>();
|
HashMap<ServerPlayer, List<LevelChunk>> map = new HashMap<>();
|
||||||
for (LevelChunk chunk : changedChunks) {
|
for (LevelChunk chunk : changedChunks) {
|
||||||
chunk.setUnsaved(true);
|
|
||||||
ChunkPos chunkPos = chunk.getPos();
|
ChunkPos chunkPos = chunk.getPos();
|
||||||
for (ServerPlayer serverPlayer2 : chunkMap.getPlayers(chunkPos, false)) {
|
for (ServerPlayer serverPlayer2 : chunkMap.getPlayers(chunkPos, false)) {
|
||||||
map.computeIfAbsent(serverPlayer2, serverPlayer -> new ArrayList<>()).add(chunk);
|
map.computeIfAbsent(serverPlayer2, serverPlayer -> new ArrayList<>()).add(chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
map.forEach((serverPlayer, list) -> serverPlayer.connection.send(ClientboundChunksBiomesPacket.forChunks(list)));
|
map.forEach((serverPlayer, list) -> serverPlayer.connection.send(ClientboundChunksBiomesPacket.forChunks(list)));
|
||||||
|
//TODO end multiversioning
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,133 +1,48 @@
|
|||||||
package com.moulberry.axiom.packet;
|
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.event.AxiomModifyWorldEvent;
|
||||||
import com.moulberry.axiom.integration.VersionTranslator;
|
import com.moulberry.axiom.integration.VersionTranslator;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import net.minecraft.core.BlockPos;
|
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.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.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.Bukkit;
|
||||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class SetBlockPacketListener implements AxiomPacketListener {
|
public class SetBlockPacketListener implements AxiomPacketListener {
|
||||||
|
|
||||||
private static final Reflection.Method<LevelChunk, Object> updateBlockEntityTicker = Reflection.getMethod(LevelChunk.class, BlockEntity.class);
|
|
||||||
|
|
||||||
@Override
|
@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
|
// 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);
|
Bukkit.getPluginManager().callEvent(modifyWorldEvent);
|
||||||
if (modifyWorldEvent.isCancelled()) return;
|
if (modifyWorldEvent.isCancelled()) return;
|
||||||
|
|
||||||
// Read packet
|
// Read packet
|
||||||
BlockPos blockPos = buf.readBlockPos();
|
BlockPos pos = AxiomPaper.convert(buf.readLong());
|
||||||
BlockState blockState = Block.BLOCK_STATE_REGISTRY.byId(VersionTranslator.impl.blockStateMapper(bukkitPlayer).apply(buf.readVarInt()));
|
BlockState state = VersionTranslator.impl.blockStateMapper(player).apply(MojBuf.readVarInt(buf));
|
||||||
boolean updateNeighbors = buf.readBoolean();
|
boolean updateNeighbors = buf.readBoolean();
|
||||||
int sequenceId = buf.readInt();
|
int sequenceId = buf.readInt();
|
||||||
|
|
||||||
ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle();
|
ServerPlayer serverPlayer = AxiomPaper.convert(player);
|
||||||
|
|
||||||
// Update blocks
|
// Update blocks
|
||||||
|
//TODO multiversioning
|
||||||
if (updateNeighbors) {
|
if (updateNeighbors) {
|
||||||
player.level().setBlock(blockPos, blockState, 3);
|
AxiomPaper.convert(player.getWorld()).setBlock(pos, state, 3);
|
||||||
} else {
|
} else {
|
||||||
int bx = blockPos.getX();
|
ChunkSectionModifier section = new ChunkSectionModifier(player.getWorld(), pos.getX() >> 4, pos.getY() >> 4, pos.getZ() >> 4);
|
||||||
int by = blockPos.getY();
|
section.setState(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF, state);
|
||||||
int bz = blockPos.getZ();
|
section.finish();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sequenceId >= 0) {
|
if (sequenceId >= 0) {
|
||||||
player.connection.ackBlockChangesUpTo(sequenceId);
|
serverPlayer.connection.ackBlockChangesUpTo(sequenceId);
|
||||||
}
|
}
|
||||||
|
//TODO end multiversioning
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package com.moulberry.axiom.packet;
|
|||||||
|
|
||||||
import com.moulberry.axiom.buffer.MojBuf;
|
import com.moulberry.axiom.buffer.MojBuf;
|
||||||
import com.moulberry.axiom.event.AxiomTeleportEvent;
|
import com.moulberry.axiom.event.AxiomTeleportEvent;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
public class TeleportPacketListener implements AxiomPacketListener {
|
public class TeleportPacketListener implements AxiomPacketListener {
|
||||||
|
|
||||||
@Override
|
@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));
|
World world = Bukkit.getWorld(MojBuf.readKey(buf));
|
||||||
double x = buf.readDouble();
|
double x = buf.readDouble();
|
||||||
double y = buf.readDouble();
|
double y = buf.readDouble();
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren