mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-23 19:16:14 +01:00
Support for 1.18+ ClientboundLevelChunkWithLightPacket (#1592)
This commit is contained in:
parent
a75d383001
commit
7fd4ec3172
@ -929,6 +929,24 @@ public abstract class AbstractStructure {
|
||||
BukkitConverters.getWrappedPublicKeyDataConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for LevelChunkPacketData in 1.18+
|
||||
*
|
||||
* @return The Structure Modifier
|
||||
*/
|
||||
public StructureModifier<WrappedLevelChunkData.ChunkData> getLevelChunkData() {
|
||||
return structureModifier.withType(MinecraftReflection.getLevelChunkPacketDataClass(), BukkitConverters.getWrappedChunkDataConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for LightUpdatePacketData in 1.18+
|
||||
*
|
||||
* @return The Structure Modifier
|
||||
*/
|
||||
public StructureModifier<WrappedLevelChunkData.LightData> getLightUpdateData() {
|
||||
return structureModifier.withType(MinecraftReflection.getLightUpdatePacketDataClass(), BukkitConverters.getWrappedLightDataConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return read/write structure for login encryption packets
|
||||
*/
|
||||
|
@ -50,7 +50,7 @@ public class PrioritizedListener<TListener> implements Comparable<PrioritizedLis
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// We only care about the listener - priority itself should not make a difference
|
||||
if(obj instanceof PrioritizedListener){
|
||||
if (obj instanceof PrioritizedListener) {
|
||||
final PrioritizedListener<TListener> other = (PrioritizedListener<TListener>) obj;
|
||||
return Objects.equal(listener, other.listener);
|
||||
} else {
|
||||
|
@ -65,9 +65,9 @@ public class StructureCache {
|
||||
return accessor.invoke(MinecraftReflection.getPacketDataSerializer(new ZeroBuffer()));
|
||||
} catch (Exception exception) {
|
||||
// try trick nms around as they want a non-null compound in the map_chunk packet constructor
|
||||
ConstructorAccessor trickyDataSerializerAccessor = getTrickDataSerializerOrNull();
|
||||
if (trickyDataSerializerAccessor != null) {
|
||||
return accessor.invoke(trickyDataSerializerAccessor.invoke(new ZeroBuffer()));
|
||||
Object trickyDataSerializer = getTrickDataSerializerOrNull();
|
||||
if (trickyDataSerializer != null) {
|
||||
return accessor.invoke(trickyDataSerializer);
|
||||
}
|
||||
// the tricks are over
|
||||
throw new IllegalArgumentException("Unable to create packet " + clazz, exception);
|
||||
@ -127,7 +127,7 @@ public class StructureCache {
|
||||
*
|
||||
* @return an accessor to a constructor which creates a data serializer.
|
||||
*/
|
||||
private static ConstructorAccessor getTrickDataSerializerOrNull() {
|
||||
public static Object getTrickDataSerializerOrNull() {
|
||||
if (TRICKED_DATA_SERIALIZER == null && !TRICK_TRIED) {
|
||||
// ensure that we only try once to create the class
|
||||
TRICK_TRIED = true;
|
||||
@ -152,6 +152,7 @@ public class StructureCache {
|
||||
// can happen if unsupported
|
||||
}
|
||||
}
|
||||
return TRICKED_DATA_SERIALIZER;
|
||||
|
||||
return TRICKED_DATA_SERIALIZER == null ? null : TRICKED_DATA_SERIALIZER.invoke(new ZeroBuffer());
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -1594,4 +1595,29 @@ public final class MinecraftReflection {
|
||||
setMinecraftClass("MinecraftServer", params[0]);
|
||||
setMinecraftClass("PlayerList", params[1]);
|
||||
}
|
||||
|
||||
public static Class<?> getLevelChunkPacketDataClass() {
|
||||
return getNullableNMS("network.protocol.game.ClientboundLevelChunkPacketData");
|
||||
}
|
||||
|
||||
public static Class<?> getLightUpdatePacketDataClass() {
|
||||
return getNullableNMS("network.protocol.game.ClientboundLightUpdatePacketData");
|
||||
}
|
||||
|
||||
public static Class<?> getBlockEntityTypeClass() {
|
||||
return getMinecraftClass("world.level.block.entity.BlockEntityType", "world.level.block.entity.TileEntityTypes", "TileEntityTypes");
|
||||
}
|
||||
|
||||
public static Class<?> getBlockEntityInfoClass() {
|
||||
try {
|
||||
return getMinecraftClass("BlockEntityInfo");
|
||||
} catch (RuntimeException expected) {
|
||||
Class<?> infoClass = (Class<?>) ((ParameterizedType) FuzzyReflection.fromClass(getLevelChunkPacketDataClass(),
|
||||
true).getFieldListByType(List.class).get(0).getGenericType()).getActualTypeArguments()[0];
|
||||
|
||||
setMinecraftClass("BlockEntityInfo", infoClass);
|
||||
|
||||
return infoClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -610,6 +610,14 @@ public class BukkitConverters {
|
||||
return ignoreNull(handle(WrappedSaltedSignature::getHandle, WrappedSaltedSignature::new, WrappedSaltedSignature.class));
|
||||
}
|
||||
|
||||
public static EquivalentConverter<WrappedLevelChunkData.ChunkData> getWrappedChunkDataConverter() {
|
||||
return ignoreNull(handle(WrappedLevelChunkData.ChunkData::getHandle, WrappedLevelChunkData.ChunkData::new, WrappedLevelChunkData.ChunkData.class));
|
||||
}
|
||||
|
||||
public static EquivalentConverter<WrappedLevelChunkData.LightData> getWrappedLightDataConverter() {
|
||||
return ignoreNull(handle(WrappedLevelChunkData.LightData::getHandle, WrappedLevelChunkData.LightData::new, WrappedLevelChunkData.LightData.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for watchable objects and the respective wrapper.
|
||||
* @return A watchable object converter.
|
||||
|
@ -51,7 +51,8 @@ public class ComponentParser {
|
||||
// Should only be needed on 1.8.
|
||||
private static Object deserializeLegacy(Object gson, Class<?> component, StringReader str) {
|
||||
try {
|
||||
if(readerConstructor == null){
|
||||
if (readerConstructor == null) {
|
||||
|
||||
Class<?> readerClass = Class.forName("org.bukkit.craftbukkit.libs.com.google.gson.stream.JsonReader");
|
||||
readerConstructor = readerClass.getDeclaredConstructor(Reader.class);
|
||||
readerConstructor.setAccessible(true);
|
||||
|
@ -400,7 +400,7 @@ public abstract class EnumWrappers {
|
||||
* @return Wrapped {@link EntityPose}
|
||||
*/
|
||||
public static EntityPose fromNms(Object nms) {
|
||||
if(POSE_CONVERTER == null) {
|
||||
if (POSE_CONVERTER == null) {
|
||||
throw new IllegalStateException("EntityPose is only available in Minecraft version 1.13 +");
|
||||
}
|
||||
return POSE_CONVERTER.getSpecific(nms);
|
||||
@ -408,7 +408,7 @@ public abstract class EnumWrappers {
|
||||
|
||||
/** @return net.minecraft.server.EntityPose enum equivalent to this wrapper enum */
|
||||
public Object toNms() {
|
||||
if(POSE_CONVERTER == null) {
|
||||
if (POSE_CONVERTER == null) {
|
||||
throw new IllegalStateException("EntityPose is only available in Minecraft version 1.13 +");
|
||||
}
|
||||
return POSE_CONVERTER.getGeneric(this);
|
||||
@ -784,7 +784,7 @@ public abstract class EnumWrappers {
|
||||
* @return {@link EnumConverter} or null (if bellow 1.13 / nms EnumPose class cannot be found)
|
||||
*/
|
||||
public static EquivalentConverter<EntityPose> getEntityPoseConverter() {
|
||||
if(getEntityPoseClass() == null) return null;
|
||||
if (getEntityPoseClass() == null) return null;
|
||||
return new EnumConverter<>(getEntityPoseClass(), EntityPose.class);
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
@ -112,6 +113,23 @@ public class MinecraftKey {
|
||||
return key.toUpperCase(Locale.ENGLISH).replace(".", "_");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MinecraftKey that = (MinecraftKey) o;
|
||||
return Objects.equals(prefix, that.prefix) && Objects.equals(key, that.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(prefix, key);
|
||||
}
|
||||
|
||||
private static Constructor<?> constructor = null;
|
||||
|
||||
public static EquivalentConverter<MinecraftKey> getConverter() {
|
||||
|
@ -0,0 +1,465 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.ZeroBuffer;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Wrapper classes for ClientboundLevelChunkWithLightPacket
|
||||
*
|
||||
* @author Etrayed
|
||||
*/
|
||||
public final class WrappedLevelChunkData {
|
||||
|
||||
private WrappedLevelChunkData() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for ClientboundLevelChunkPacketData
|
||||
*/
|
||||
public static final class ChunkData extends AbstractWrapper {
|
||||
|
||||
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getLevelChunkPacketDataClass();
|
||||
|
||||
private static final ConstructorAccessor LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR;
|
||||
|
||||
private static final FieldAccessor BLOCK_ENTITIES_DATA_ACCESSOR;
|
||||
private static final FieldAccessor HEIGHTMAPS_ACCESSOR;
|
||||
private static final FieldAccessor BUFFER_ACCESSOR;
|
||||
|
||||
static {
|
||||
FuzzyReflection reflection = FuzzyReflection.fromClass(HANDLE_TYPE, true);
|
||||
|
||||
LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE,
|
||||
MinecraftReflection.getPacketDataSerializerClass(), int.class, int.class);
|
||||
BLOCK_ENTITIES_DATA_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(List.class)
|
||||
.build()));
|
||||
HEIGHTMAPS_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(MinecraftReflection.getNBTCompoundClass())
|
||||
.build()));
|
||||
BUFFER_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder().typeExact(byte[].class).build()));
|
||||
}
|
||||
|
||||
public ChunkData(Object handle) {
|
||||
super(HANDLE_TYPE);
|
||||
|
||||
setHandle(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* The heightmap of this chunk.
|
||||
*
|
||||
* @return an NBT-Tag
|
||||
*/
|
||||
public NbtCompound getHeightmapsTag() {
|
||||
return NbtFactory.fromNMSCompound(HEIGHTMAPS_ACCESSOR.get(handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the heightmap tag of this chunk.
|
||||
*
|
||||
* @param heightmapsTag the new heightmaps tag.
|
||||
*/
|
||||
public void setHeightmapsTag(NbtCompound heightmapsTag) {
|
||||
HEIGHTMAPS_ACCESSOR.set(handle, NbtFactory.fromBase(heightmapsTag).getHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual structural data of this chunk as bytes.
|
||||
*
|
||||
* @return a byte array containing the chunks structural data.
|
||||
*/
|
||||
public byte[] getBuffer() {
|
||||
return (byte[]) BUFFER_ACCESSOR.get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the structural data of this chunk.
|
||||
*
|
||||
* @param buffer the new buffer.
|
||||
*/
|
||||
public void setBuffer(byte[] buffer) {
|
||||
BUFFER_ACCESSOR.set(handle, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* All block entities of this chunk. Supports removal and other edits.
|
||||
*
|
||||
* @return a mutable (remove only) list containing {@link BlockEntityInfo}
|
||||
*/
|
||||
public List<BlockEntityInfo> getBlockEntityInfo() {
|
||||
//noinspection StaticPseudoFunctionalStyleMethod
|
||||
return Lists.transform((List<?>) BLOCK_ENTITIES_DATA_ACCESSOR.get(handle), BlockEntityInfo::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the block entities of this chunk. Supports removal and other edits.
|
||||
*
|
||||
* @param blockEntityInfo the new list of block entities
|
||||
*/
|
||||
public void setBlockEntityInfo(List<BlockEntityInfo> blockEntityInfo) {
|
||||
List handleList = new ArrayList<>(blockEntityInfo.size());
|
||||
|
||||
for (BlockEntityInfo info : blockEntityInfo) {
|
||||
//noinspection unchecked
|
||||
handleList.add(info.getHandle());
|
||||
}
|
||||
|
||||
BLOCK_ENTITIES_DATA_ACCESSOR.set(handle, handleList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new wrapper using predefined values.
|
||||
*
|
||||
* @param heightmapsTag the heightmaps tag
|
||||
* @param buffer the buffer
|
||||
* @param blockEntityInfo a list of wrapped block entities
|
||||
* @return a newly created wrapper
|
||||
*/
|
||||
public static ChunkData fromValues(NbtCompound heightmapsTag, byte[] buffer, List<BlockEntityInfo> blockEntityInfo) {
|
||||
ChunkData data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.getTrickDataSerializerOrNull(), 0, 0));
|
||||
|
||||
data.setHeightmapsTag(heightmapsTag);
|
||||
data.setBuffer(buffer);
|
||||
data.setBlockEntityInfo(blockEntityInfo);
|
||||
|
||||
return new ChunkData(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for ClientboundLightUpdatePacketData
|
||||
*/
|
||||
public static class LightData extends AbstractWrapper {
|
||||
|
||||
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getLightUpdatePacketDataClass();
|
||||
|
||||
private static final ConstructorAccessor LIGHT_UPDATE_PACKET_DATA_CONSTRUCTOR;
|
||||
|
||||
private static final FieldAccessor[] BIT_SET_ACCESSORS;
|
||||
private static final FieldAccessor[] BYTE_ARRAY_LIST_ACCESSORS;
|
||||
|
||||
private static final FieldAccessor TRUST_EDGES_ACCESSOR;
|
||||
|
||||
static {
|
||||
FuzzyReflection reflection = FuzzyReflection.fromClass(HANDLE_TYPE, true);
|
||||
|
||||
LIGHT_UPDATE_PACKET_DATA_CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE,
|
||||
MinecraftReflection.getPacketDataSerializerClass(), int.class, int.class);
|
||||
BIT_SET_ACCESSORS = Accessors.getFieldAccessorArray(HANDLE_TYPE, BitSet.class, true);
|
||||
BYTE_ARRAY_LIST_ACCESSORS = Accessors.getFieldAccessorArray(HANDLE_TYPE, List.class, true);
|
||||
TRUST_EDGES_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(boolean.class)
|
||||
.build()));
|
||||
}
|
||||
|
||||
public LightData(Object handle) {
|
||||
super(HANDLE_TYPE);
|
||||
|
||||
setHandle(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* The sky light mask.
|
||||
*
|
||||
* @return a {@link BitSet}
|
||||
*/
|
||||
public BitSet getSkyYMask() {
|
||||
return (BitSet) BIT_SET_ACCESSORS[0].get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sky light mask
|
||||
*
|
||||
* @param skyYMask the new mask
|
||||
*/
|
||||
public void setSkyYMask(BitSet skyYMask) {
|
||||
BIT_SET_ACCESSORS[0].set(handle, skyYMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* The block light mask.
|
||||
*
|
||||
* @return a {@link BitSet}
|
||||
*/
|
||||
public BitSet getBlockYMask() {
|
||||
return (BitSet) BIT_SET_ACCESSORS[1].get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the block light mask
|
||||
*
|
||||
* @param blockYMask the new mask
|
||||
*/
|
||||
public void setBlockYMask(BitSet blockYMask) {
|
||||
BIT_SET_ACCESSORS[1].set(handle, blockYMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* The empty sky light mask.
|
||||
*
|
||||
* @return a {@link BitSet}
|
||||
*/
|
||||
public BitSet getEmptySkyYMask() {
|
||||
return (BitSet) BIT_SET_ACCESSORS[2].get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the empty sky light mask
|
||||
*
|
||||
* @param emptySkyYMask the new mask
|
||||
*/
|
||||
public void setEmptySkyYMask(BitSet emptySkyYMask) {
|
||||
BIT_SET_ACCESSORS[2].set(handle, emptySkyYMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* The empty block light mask.
|
||||
*
|
||||
* @return a {@link BitSet}
|
||||
*/
|
||||
public BitSet getEmptyBlockYMask() {
|
||||
return (BitSet) BIT_SET_ACCESSORS[3].get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the empty block light mask
|
||||
*
|
||||
* @param emptyBlockYMask the new mask
|
||||
*/
|
||||
public void setEmptyBlockYMask(BitSet emptyBlockYMask) {
|
||||
BIT_SET_ACCESSORS[3].set(handle, emptyBlockYMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* A mutable list of sky light arrays.
|
||||
*
|
||||
* @return a mutable list of byte arrays.
|
||||
*/
|
||||
public List<byte[]> getSkyUpdates() {
|
||||
//noinspection unchecked
|
||||
return (List<byte[]>) BYTE_ARRAY_LIST_ACCESSORS[0].get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* A mutable list of block light arrays.
|
||||
*
|
||||
* @return a mutable list of byte arrays.
|
||||
*/
|
||||
public List<byte[]> getBlockUpdates() {
|
||||
//noinspection unchecked
|
||||
return (List<byte[]>) BYTE_ARRAY_LIST_ACCESSORS[1].get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether edges can be trusted for light updates or not.
|
||||
*
|
||||
* @return {@code true} if edges can be trusted, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isTrustEdges() {
|
||||
return (boolean) TRUST_EDGES_ACCESSOR.get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether edges can be trusted for light updates or not.
|
||||
*
|
||||
* @param trustEdges the new value
|
||||
*/
|
||||
public void setTrustEdges(boolean trustEdges) {
|
||||
TRUST_EDGES_ACCESSOR.set(handle, trustEdges);
|
||||
}
|
||||
|
||||
public static LightData fromValues(BitSet skyYMask, BitSet blockYMask, BitSet emptySkyYMask, BitSet emptyBlockYMask,
|
||||
List<byte[]> skyUpdates, List<byte[]> blockUpdates, boolean trustEdges) {
|
||||
LightData data = new LightData(LIGHT_UPDATE_PACKET_DATA_CONSTRUCTOR.invoke(MinecraftReflection.getPacketDataSerializer(new ZeroBuffer()), 0, 0));
|
||||
|
||||
data.setTrustEdges(trustEdges);
|
||||
data.setSkyYMask(skyYMask);
|
||||
data.setBlockYMask(blockYMask);
|
||||
data.setEmptySkyYMask(emptySkyYMask);
|
||||
data.setEmptyBlockYMask(emptyBlockYMask);
|
||||
data.getSkyUpdates().addAll(skyUpdates);
|
||||
data.getBlockUpdates().addAll(blockUpdates);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an immutable BlockEntityInfo in the MAP_CHUNK packet.
|
||||
*
|
||||
* @author Etrayed
|
||||
*/
|
||||
public static class BlockEntityInfo extends AbstractWrapper {
|
||||
|
||||
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getBlockEntityInfoClass();
|
||||
private static final WrappedRegistry REGISTRY = WrappedRegistry.getRegistry(MinecraftReflection.getBlockEntityTypeClass());
|
||||
|
||||
private static final ConstructorAccessor BLOCK_ENTITY_INFO_CONSTRUCTOR;
|
||||
|
||||
private static final FieldAccessor PACKED_XZ_ACCESSOR;
|
||||
private static final FieldAccessor Y_ACCESSOR;
|
||||
private static final FieldAccessor TYPE_ACCESSOR;
|
||||
private static final FieldAccessor TAG_ACCESSOR;
|
||||
|
||||
static {
|
||||
FuzzyReflection reflection = FuzzyReflection.fromClass(HANDLE_TYPE, true);
|
||||
List<Field> posFields = reflection.getFieldList(FuzzyFieldContract.newBuilder().typeExact(int.class).build());
|
||||
|
||||
BLOCK_ENTITY_INFO_CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE, int.class, int.class,
|
||||
MinecraftReflection.getBlockEntityTypeClass(), MinecraftReflection.getNBTCompoundClass());
|
||||
PACKED_XZ_ACCESSOR = Accessors.getFieldAccessor(posFields.get(0));
|
||||
Y_ACCESSOR = Accessors.getFieldAccessor(posFields.get(1));
|
||||
TYPE_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(MinecraftReflection.getBlockEntityTypeClass())
|
||||
.build()));
|
||||
TAG_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(MinecraftReflection.getNBTCompoundClass())
|
||||
.build()));
|
||||
}
|
||||
|
||||
public BlockEntityInfo(Object handle) {
|
||||
super(HANDLE_TYPE);
|
||||
|
||||
setHandle(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* The section-relative X-coordinate of the block entity.
|
||||
*
|
||||
* @return the unpacked X-coordinate.
|
||||
*/
|
||||
public int getSectionX() {
|
||||
return (int) PACKED_XZ_ACCESSOR.get(handle) >> 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the section-relative X-coordinate of the block entity
|
||||
*
|
||||
* @param sectionX the section-relative x coordinate
|
||||
*/
|
||||
public void setSectionX(int sectionX) {
|
||||
PACKED_XZ_ACCESSOR.set(handle, sectionX << 4 | getSectionZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* The section-relative Z-coordinate of the block entity.
|
||||
*
|
||||
* @return the unpacked Z-coordinate.
|
||||
*/
|
||||
public int getSectionZ() {
|
||||
return (int) PACKED_XZ_ACCESSOR.get(handle) & 0xF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the section-relative Z-coordinate of the block entity
|
||||
*
|
||||
* @param sectionZ the section-relative z coordinate
|
||||
*/
|
||||
public void setSectionZ(int sectionZ) {
|
||||
PACKED_XZ_ACCESSOR.set(handle, getSectionX() << 4 | sectionZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Y-coordinate of the block entity.
|
||||
*
|
||||
* @return the Y-coordinate.
|
||||
*/
|
||||
public int getY() {
|
||||
return (int) Y_ACCESSOR.get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Y-coordinate of the block entity.
|
||||
*
|
||||
* @param y the new y coordinate
|
||||
*/
|
||||
public void setY(int y) {
|
||||
Y_ACCESSOR.set(handle, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* The registry key of the block entity type.
|
||||
*
|
||||
* @return the registry key.
|
||||
*/
|
||||
public MinecraftKey getTypeKey() {
|
||||
return REGISTRY.getKey(TYPE_ACCESSOR.get(handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the registry key of the block entity type
|
||||
*
|
||||
* @param typeKey the new block entity type key
|
||||
*/
|
||||
public void setTypeKey(MinecraftKey typeKey) {
|
||||
TYPE_ACCESSOR.set(handle, REGISTRY.get(typeKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* The NBT-Tag of this block entity containing additional information. (ex. text lines for a sign)
|
||||
*
|
||||
* @return the NBT-Tag or {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
public NbtCompound getAdditionalData() {
|
||||
Object tagHandle = TAG_ACCESSOR.get(handle);
|
||||
|
||||
return tagHandle == null ? null : NbtFactory.fromNMSCompound(tagHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the additional data specified for this block entity.
|
||||
*
|
||||
* @param additionalData the additional data for this block entity, can be {@code null}
|
||||
*/
|
||||
public void setAdditionalData(@Nullable NbtCompound additionalData) {
|
||||
TAG_ACCESSOR.set(handle, additionalData == null ? null : NbtFactory.fromBase(additionalData).getHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapper using raw values
|
||||
*
|
||||
* @param sectionX the section-relative X-coordinate of the block entity.
|
||||
* @param sectionZ the section-relative Z-coordinate of the block entity.
|
||||
* @param y the Y-coordinate of the block entity.
|
||||
* @param typeKey the minecraft key of the block entity type.
|
||||
*/
|
||||
public static BlockEntityInfo fromValues(int sectionX, int sectionZ, int y, MinecraftKey typeKey) {
|
||||
return fromValues(sectionX, sectionZ, y, typeKey, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapper using raw values
|
||||
*
|
||||
* @param sectionX the section-relative X-coordinate of the block entity.
|
||||
* @param sectionZ the section-relative Z-coordinate of the block entity.
|
||||
* @param y the Y-coordinate of the block entity.
|
||||
* @param typeKey the minecraft key of the block entity type.
|
||||
* @param additionalData An NBT-Tag containing additional information. Can be {@code null}.
|
||||
*/
|
||||
public static BlockEntityInfo fromValues(int sectionX, int sectionZ, int y, MinecraftKey typeKey, @Nullable NbtCompound additionalData) {
|
||||
return new BlockEntityInfo(BLOCK_ENTITY_INFO_CONSTRUCTOR.invoke(
|
||||
sectionX << 4 | sectionZ,
|
||||
y,
|
||||
REGISTRY.get(typeKey),
|
||||
additionalData == null ? null : NbtFactory.fromBase(additionalData).getHandle()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import net.minecraft.core.BlockPosition;
|
||||
import net.minecraft.core.IRegistry;
|
||||
import net.minecraft.core.IRegistryCustom;
|
||||
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
||||
import net.minecraft.resources.MinecraftKey;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.level.BlockAccessAir;
|
||||
import net.minecraft.world.level.ChunkCoordIntPair;
|
||||
import net.minecraft.world.level.block.entity.TileEntityBell;
|
||||
import net.minecraft.world.level.block.state.IBlockData;
|
||||
import net.minecraft.world.level.chunk.Chunk;
|
||||
import net.minecraft.world.level.chunk.ILightAccess;
|
||||
import net.minecraft.world.level.lighting.LightEngine;
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.internal.matchers.apachecommons.ReflectionEquals;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Etrayed
|
||||
*/
|
||||
public class WrappedLevelChunkDataTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void initializeBukkitAndNMS() {
|
||||
BukkitInitialization.initializeAll();
|
||||
|
||||
ILightAccess access = mock(ILightAccess.class);
|
||||
|
||||
when(access.c(0, 0)).thenReturn(BlockAccessAir.a);
|
||||
when(access.p()).thenReturn(BlockAccessAir.a);
|
||||
|
||||
LightEngine engine = new LightEngine(access, true, true);
|
||||
WorldServer nmsWorld = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle();
|
||||
|
||||
when(nmsWorld.s()).thenReturn(IRegistryCustom.d.get());
|
||||
// TODO: somehow find a way to always call the real code for all LevelHeightAccessor implementations
|
||||
when(nmsWorld.v_()).thenReturn(256);
|
||||
when(nmsWorld.ai()).thenReturn(16); // LevelHeightAccessor is mocked and therefore always returns 0, there are further methods like this which might cause errors in the future
|
||||
|
||||
when(nmsWorld.l_()).thenReturn(engine);
|
||||
}
|
||||
|
||||
private final WorldServer nmsWorld;
|
||||
|
||||
private final Chunk chunk;
|
||||
|
||||
public WrappedLevelChunkDataTest() {
|
||||
this.nmsWorld = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle();
|
||||
this.chunk = new Chunk(nmsWorld, new ChunkCoordIntPair(5, 5));
|
||||
|
||||
IBlockData bellData = IRegistry.V.a(new MinecraftKey("bell")).m();
|
||||
|
||||
chunk.b(0).a(0, 0, 0, bellData);
|
||||
chunk.a(new TileEntityBell(BlockPosition.b, bellData));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChunkData() {
|
||||
ClientboundLevelChunkWithLightPacket packet = new ClientboundLevelChunkWithLightPacket(chunk,
|
||||
nmsWorld.l_(), null, null, false);
|
||||
PacketContainer container = PacketContainer.fromPacket(packet);
|
||||
Object rawInstance = container.getSpecificModifier(MinecraftReflection.getLevelChunkPacketDataClass()).read(0);
|
||||
Object virtualInstance = BukkitConverters.getWrappedChunkDataConverter().getGeneric(container.getLevelChunkData().read(0));
|
||||
|
||||
assertTrue(new ReflectionEquals(rawInstance, FuzzyReflection.fromClass(rawInstance.getClass(), true)
|
||||
.getFieldListByType(List.class).get(0).getName())
|
||||
.matches(virtualInstance));
|
||||
assertTrue(blockEntitiesEqual(rawInstance, virtualInstance));
|
||||
}
|
||||
|
||||
private boolean blockEntitiesEqual(Object raw, Object virtual) {
|
||||
if (raw == null && virtual == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (raw == null || virtual == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FieldAccessor accessor = Accessors.getFieldAccessor(FuzzyReflection.fromClass(raw.getClass(), true)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeExact(List.class).build()));
|
||||
List rawList = (List) accessor.get(raw);
|
||||
List virtualList = (List) accessor.get(virtual);
|
||||
|
||||
if (rawList.size() != virtualList.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rawList.size(); i++) {
|
||||
if (!EqualsBuilder.reflectionEquals(rawList.get(0), virtualList.get(0))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLightData() {
|
||||
ClientboundLevelChunkWithLightPacket packet = new ClientboundLevelChunkWithLightPacket(chunk,
|
||||
nmsWorld.l_(), null, null, false);
|
||||
PacketContainer container = PacketContainer.fromPacket(packet);
|
||||
|
||||
randomizeBitSets(container.getSpecificModifier(MinecraftReflection.getLightUpdatePacketDataClass()).read(0));
|
||||
|
||||
assertTrue(new ReflectionEquals(container.getSpecificModifier(MinecraftReflection.getLightUpdatePacketDataClass()).read(0))
|
||||
.matches(BukkitConverters.getWrappedLightDataConverter().getGeneric(container.getLightUpdateData().read(0))));
|
||||
}
|
||||
|
||||
private void randomizeBitSets(Object lightData) {
|
||||
for (Field field : FuzzyReflection.fromClass(MinecraftReflection.getLightUpdatePacketDataClass(), true).getFieldListByType(BitSet.class)) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
|
||||
randomizeBitSet((BitSet) field.get(lightData));
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
private void randomizeBitSet(BitSet bitSet) {
|
||||
for (int i = 0; i < bitSet.size(); i++) {
|
||||
if (Math.random() >= 0.5D) {
|
||||
bitSet.set(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user