Skip to content

Commit

Permalink
1.21
Browse files Browse the repository at this point in the history
  • Loading branch information
Envel-Nikita-Gutsenkov committed Nov 2, 2024
1 parent 3dd9500 commit f9d8ed6
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 374 deletions.
1 change: 1 addition & 0 deletions bukkit/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repositories {

dependencies {
testImplementation 'com.comphenix.protocol:ProtocolLib:5.3.0'
implementation 'com.google.inject:guice:7.0.0'

implementation project(':shared')
implementation project(':api')
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public boolean showEntity(Player player) {
@Path("betterportals/test/smallTeleport")
@RequiresPlayer
public boolean testTeleport(Player player) {
player.teleport(player.getLocation().add(0.1, 0.0, 0.0));
player.teleportAsync(player.getLocation().add(0.1, 0.0, 0.0));
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,26 +294,26 @@ public void sendMetadata(EntityInfo tracker, Collection<Player> players) {

packet.getIntegers().write(0, tracker.getEntityId());

WrappedDataWatcher dataWatcher = EntityUtil.getActualDataWatcher(tracker.getEntity()); // Use the Entity's actual data watcher, not ProtocolLib's method which gives us a dummy
if (MinecraftVersion.getCurrentVersion().isAtLeast(new MinecraftVersion("1.19.3"))) {
List<WrappedDataValue> wrappedDataValueList = dataWatcher.getWatchableObjects().stream()
.filter(Objects::nonNull)
.map(entry -> {
WrappedDataWatcher.WrappedDataWatcherObject dataWatcherObject = entry.getWatcherObject();
return new WrappedDataValue(
dataWatcherObject.getIndex(),
dataWatcherObject.getSerializer(),
entry.getRawValue());
})
.toList();
packet.getDataValueCollectionModifier().write(0, wrappedDataValueList);
} else {
packet.getWatchableCollectionModifier().write(0, dataWatcher.getWatchableObjects());
}

sendPacket(packet, players);
WrappedDataWatcher dataWatcher = EntityUtil.getActualDataWatcher(tracker.getEntity()); // Get the entity's actual data watcher

// Convert the data watcher to a list of WrappedDataValues
List<WrappedDataValue> wrappedDataValueList = dataWatcher.getWatchableObjects().stream()
.filter(Objects::nonNull)
.map(entry -> {
WrappedDataWatcher.WrappedDataWatcherObject dataWatcherObject = entry.getWatcherObject();
return new WrappedDataValue(
dataWatcherObject.getIndex(),
dataWatcherObject.getSerializer(),
entry.getRawValue());
})
.toList();

packet.getDataValueCollectionModifier().write(0, wrappedDataValueList); // Write the data values to the packet

sendPacket(packet, players); // Send the packet to the specified players
}


@Override
public void sendEntityVelocity(EntityInfo tracker, Vector newVelocity, Collection<Player> players) {
// Rotate the velocity back to the origin of the portal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.lauriethefish.betterportals.api.IntVector;
import com.lauriethefish.betterportals.bukkit.util.VersionUtil;
import com.lauriethefish.betterportals.shared.util.ReflectionUtil;
import org.bukkit.Bukkit;
import org.bukkit.Material;
Expand All @@ -14,7 +12,6 @@
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Method;
import java.util.Objects;

public class BlockDataUtil {
private static final Method GET_HANDLE;
Expand All @@ -23,24 +20,22 @@ public class BlockDataUtil {
private static final Method FROM_HANDLE;
private static final Method GET_TILE_ENTITY;
private static final Method GET_UPDATE_PACKET;
private static final BlockData DEFAULT_BLOCK_DATA;

static {
DEFAULT_BLOCK_DATA = Bukkit.createBlockData(Material.AIR);
Class<?> nmsBlock = ReflectionUtil.findClass("net.minecraft.world.level.block.Block");
Class<?> craftBlockData = CraftBukkitClassUtil.findCraftBukkitClass("block.data.CraftBlockData");
Class<?> nmsBlockData = ReflectionUtil.findClass("net.minecraft.world.level.block.state.IBlockData");

// Reflection to get necessary methods
GET_HANDLE = ReflectionUtil.findMethod(craftBlockData, "getState");
GET_COMBINED_ID = ReflectionUtil.findMethod(nmsBlock, VersionUtil.isMcVersionAtLeast("1.18.0") ? "i" : "getCombinedId", nmsBlockData);

GET_FROM_COMBINED_ID = ReflectionUtil.findMethod(nmsBlock, VersionUtil.isMcVersionAtLeast("1.18.0") ? "a" : "getByCombinedId", int.class);
GET_COMBINED_ID = ReflectionUtil.findMethod(nmsBlock, "getCombinedId", nmsBlockData);
GET_FROM_COMBINED_ID = ReflectionUtil.findMethod(nmsBlock, "getByCombinedId", int.class);
FROM_HANDLE = ReflectionUtil.findMethod(craftBlockData, "fromData", nmsBlockData);

Class<?> blockEntityState = CraftBukkitClassUtil.findCraftBukkitClass("block.CraftBlockEntityState");
Class<?> nmsTileEntity = ReflectionUtil.findClass("net.minecraft.world.level.block.entity.TileEntity");
GET_TILE_ENTITY = ReflectionUtil.findMethod(blockEntityState, "getTileEntity");
GET_UPDATE_PACKET = ReflectionUtil.findMethod(nmsTileEntity, VersionUtil.isMcVersionAtLeast("1.18.0") ? "h" : "getUpdatePacket");
GET_UPDATE_PACKET = ReflectionUtil.findMethod(nmsTileEntity, "getUpdatePacket");
}

/**
Expand All @@ -54,49 +49,33 @@ public static int getCombinedId(@NotNull BlockData blockData) {
}

/**
* Converts <code>combinedId</code> as created in {@link BlockDataUtil#getCombinedId(BlockData)} back into a {@link BlockData}.
* Converts <code>combinedId</code> back into a {@link BlockData}.
* @param combinedId The ID to convert
* @return The bukkit block data
* @return The Bukkit block data
*/
public static BlockData getByCombinedId(int combinedId) {
Object nmsData = ReflectionUtil.invokeMethod(null, GET_FROM_COMBINED_ID, combinedId);
BlockData data = (BlockData) ReflectionUtil.invokeMethod(null, FROM_HANDLE, nmsData);

return data == null ? DEFAULT_BLOCK_DATA : data;
return (BlockData) ReflectionUtil.invokeMethod(null, FROM_HANDLE, nmsData);
}

/**
* Finds the ProtocolLib wrapper around the <code>PacketPlayOutTileEntityData</code> which updates the tile entity data for <code>tileState</code>.
* @param tileState The tile entity to get the packet of (Not a TileState since that doesn't exist on 1.12)
* @return The ProtocolLib wrapper
* @param tileState The tile entity to get the packet of
* @return The ProtocolLib wrapper, or null if not applicable
*/
public static @Nullable PacketContainer getUpdatePacket(@NotNull BlockState tileState) {
Object nmsTileEntity = ReflectionUtil.invokeMethod(tileState, GET_TILE_ENTITY);
Object unwrappedPacket = ReflectionUtil.invokeMethod(nmsTileEntity, GET_UPDATE_PACKET);

if(unwrappedPacket == null) {
return null;
}

return PacketContainer.fromPacket(unwrappedPacket);
return unwrappedPacket != null ? PacketContainer.fromPacket(unwrappedPacket) : null;
}

/**
* Sets the position of a <code>PacketPlayOutTileEntityData</code> in both the NBT and packet itself
* Sets the position of a <code>PacketPlayOutTileEntityData</code> in the packet itself.
* @param packet The packet to modify the position of
* @param position The new position
*/
public static void setTileEntityPosition(@NotNull PacketContainer packet, @NotNull IntVector position) {
BlockPosition blockPosition = new BlockPosition(position.getX(), position.getY(), position.getZ());
packet.getBlockPositionModifier().write(0, blockPosition);

// The NBT Data also stores the position
NbtCompound compound = (NbtCompound) packet.getNbtModifier().read(0);
if (Objects.nonNull(compound)) {
compound.put("x", blockPosition.getX());
compound.put("y", blockPosition.getY());
compound.put("z", blockPosition.getZ());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,15 @@
import com.lauriethefish.betterportals.shared.util.ReflectionUtil;
import org.bukkit.Bukkit;

/**
* Bukkit is strange and puts the version number in the NMS package names.
* This means that we have to use reflection to access them if we want to work across versions.
*/
public class CraftBukkitClassUtil {
private static final String packageVersion; // Name of the NMS/craftbukkit packages, e.g. 1_12_R1
private static final String craftBukkitClassPrefix; // E.g. org.bukkit.craftbukkit.1_12_R1

static {
packageVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
craftBukkitClassPrefix = String.format("org.bukkit.craftbukkit.%s.", packageVersion);
}
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();

/**
* Finds a class in the <code>org.bukkit.craftbukkit.version</code> package
* @param name Name of the class relative to this package, with no dot at the start.
* @return The located class
*/
public static Class<?> findCraftBukkitClass(String name) {
return ReflectionUtil.findClass(craftBukkitClassPrefix + name);
return ReflectionUtil.findClass(CRAFTBUKKIT_PACKAGE + "." + name);
}
}
Original file line number Diff line number Diff line change
@@ -1,81 +1,41 @@
package com.lauriethefish.betterportals.bukkit.nms;

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.lauriethefish.betterportals.bukkit.util.VersionUtil;
import com.lauriethefish.betterportals.shared.util.ReflectionUtil;
import org.bukkit.Location;
import org.bukkit.entity.EnderDragonPart;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Marker;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class EntityUtil {
private static final Method GET_HANDLE;
private static final Field DATA_WATCHER;
private static final boolean USE_DIRECT_ENTITY_PACKET;
private static final Method GET_ENTITY_SPAWN_PACKET;
private static final Constructor<?> ENTITY_TRACKER_ENTRY_NEW;

static {
Class<?> NMS_ENTITY = ReflectionUtil.findClass("net.minecraft.world.entity.Entity");
Class<?> NMS_DATA_WATCHER = ReflectionUtil.findClass("net.minecraft.network.syncher.DataWatcher");

GET_HANDLE = ReflectionUtil.findMethod(CraftBukkitClassUtil.findCraftBukkitClass("entity.CraftEntity"), "getHandle");

DATA_WATCHER = ReflectionUtil.findFieldByType(NMS_ENTITY, NMS_DATA_WATCHER);

// On newer versions of the game, the Entity NMS class have an abstract method for getting the correct spawn packet that is overridden by every entity.
USE_DIRECT_ENTITY_PACKET = true;
ENTITY_TRACKER_ENTRY_NEW = null;

Class<?> NMS_PACKET = ReflectionUtil.findClass("net.minecraft.network.protocol.Packet");

int mask = Modifier.PUBLIC;
GET_ENTITY_SPAWN_PACKET = ReflectionUtil.findMethodByTypes(NMS_ENTITY, NMS_PACKET, mask, mask);
}

/**
* ProtocolLib unfortunately doesn't provide any methods for getting the <i>actual</i> {@link WrappedDataWatcher} of an entity.
* {@link WrappedDataWatcher#WrappedDataWatcher(Entity)} doesn't do this - it returns a new empty {@link WrappedDataWatcher} for this entity.
* This function wraps the entities actual watcher in the ProtocolLib wrapper.
* Gets the actual data watcher for the given entity.
* ProtocolLib unfortunately doesn't provide any methods for getting the actual WrappedDataWatcher of an entity.
* @param entity The entity to wrap the data watcher of
* @return The wrapped data watcher
*/
@NotNull
public static WrappedDataWatcher getActualDataWatcher(@NotNull Entity entity) {
Object nmsEntity = ReflectionUtil.invokeMethod(entity, GET_HANDLE);
Object nmsDataWatcher = ReflectionUtil.getField(nmsEntity, DATA_WATCHER);
return new WrappedDataWatcher(nmsDataWatcher);
// Utilize ProtocolLib to get the data watcher directly from the entity
return WrappedDataWatcher.getEntityWatcher(entity);
}

/**
* Getting a valid spawn packet that works correctly for a specific {@link Entity} is surprisingly difficult.
* This method uses some NMS to get the correct spawn packet.
* Getting a valid spawn packet that works correctly for a specific Entity is surprisingly difficult.
* This method checks if the entity is a type that can be spawned and returns a packet if it can.
* @param entity The entity to get the spawn packet of
* @return A container with the valid packet, or <code>null</code> since some entities can't be spawned with a packet.
* @return A container with the valid packet, or null since some entities can't be spawned with a packet.
*/
public static @Nullable PacketContainer getRawEntitySpawnPacket(@NotNull Entity entity) {
if (entity instanceof EnderDragonPart) return null;
if (entity instanceof Marker) return null;
if (entity instanceof EnderDragonPart || entity instanceof Marker) return null;

Object nmsEntity = ReflectionUtil.invokeMethod(entity, GET_HANDLE);
if(USE_DIRECT_ENTITY_PACKET) {
return PacketContainer.fromPacket(ReflectionUtil.invokeMethod(nmsEntity, GET_ENTITY_SPAWN_PACKET));
} else {
// Create a dummy tracker entry
Object trackerEntry = ReflectionUtil.invokeConstructor(ENTITY_TRACKER_ENTRY_NEW, nmsEntity, 0, 0, 0, false);
Object nmsPacket = ReflectionUtil.invokeMethod(trackerEntry, GET_ENTITY_SPAWN_PACKET);
if(nmsPacket == null) {return null;}
// Use ProtocolLib to create a spawn packet for the entity
PacketContainer spawnPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY);
spawnPacket.getIntegers().write(0, entity.getEntityId());
spawnPacket.getEntityTypeModifier().write(0, entity.getType());

return PacketContainer.fromPacket(nmsPacket);
}
return spawnPacket;
}
}
Loading

0 comments on commit f9d8ed6

Please sign in to comment.