Change the way nms handle metadata packet

This commit is contained in:
Flowsqy 2022-12-31 17:48:32 +01:00
parent 9cde5e2e5d
commit c8035de739
19 changed files with 293 additions and 142 deletions

View File

@ -5,14 +5,4 @@ package de.epiceric.shopchest.nms;
*/
public interface FakeArmorStand extends FakeEntity {
/**
* Register a 'metadata' packet in the {@link PacketQueue}
* <br>
* It sets the invisibility, the custom name, make it visible and the marker flag
*
* @param packetQueue The {@link PacketQueue} to store the packet
* @param customName The name to set
*/
void metadata(PacketQueue packetQueue, NMSComponent customName);
}

View File

@ -1,5 +1,6 @@
package de.epiceric.shopchest.nms;
import de.epiceric.shopchest.nms.metadata.MetadataValue;
import org.bukkit.Location;
import org.bukkit.util.Vector;
@ -41,4 +42,19 @@ public interface FakeEntity {
*/
void teleport(PacketQueue packetQueue, Vector position);
/**
* Register a 'metadata' packet in the {@link PacketQueue}
*
* @param packetQueue The {@link PacketQueue} to store the packet
* @param metadataValues The {@link MetadataValue}s to set
*/
void metadata(PacketQueue packetQueue, MetadataValue... metadataValues);
/**
* Register a zero 'velocity' packet in the {@link PacketQueue} to stop the item from moving
*
* @param packetQueue The {@link PacketQueue} to store the packet
*/
void cancelVelocity(PacketQueue packetQueue);
}

View File

@ -1,27 +1,8 @@
package de.epiceric.shopchest.nms;
import org.bukkit.inventory.ItemStack;
/**
* Represent an Item entity that only exists clientside
*/
public interface FakeItem extends FakeEntity {
/**
* Register a 'metadata' packet in the {@link PacketQueue}
* <br>
* It sets the type of item
*
* @param packetQueue The {@link PacketQueue} to store the packet
* @param item The {@link ItemStack} type
*/
void metadata(PacketQueue packetQueue, ItemStack item);
/**
* Register a zero 'velocity' packet in the {@link PacketQueue} to stop the item from moving
*
* @param packetQueue The {@link PacketQueue} to store the packet
*/
void cancelVelocity(PacketQueue packetQueue);
}

View File

@ -1,5 +1,7 @@
package de.epiceric.shopchest.nms;
import de.epiceric.shopchest.nms.metadata.MetadataProperties;
/**
* The platform that create all nms objects
*/
@ -29,4 +31,11 @@ public interface Platform {
TextComponentHelper getTextComponentHelper();
/**
* Get a list of {@link de.epiceric.shopchest.nms.metadata.MetadataProperty}
*
* @return The {@link MetadataProperties} instance
*/
MetadataProperties getMetadataProperties();
}

View File

@ -0,0 +1,35 @@
package de.epiceric.shopchest.nms.metadata;
import de.epiceric.shopchest.nms.NMSComponent;
import org.bukkit.inventory.ItemStack;
public interface MetadataProperties {
Entity entity();
ArmorStand armorStand();
Item item();
interface Entity {
MetadataProperty<Boolean> noGravity();
MetadataProperty<Boolean> silent();
MetadataProperty<Boolean> invisible();
MetadataProperty<NMSComponent> customName();
MetadataProperty<Boolean> customNameVisible();
}
interface ArmorStand {
MetadataProperty<Boolean> marker();
}
interface Item {
MetadataProperty<ItemStack> item();
}
}

View File

@ -0,0 +1,14 @@
package de.epiceric.shopchest.nms.metadata;
public interface MetadataProperty<T> {
/**
* Create a new {@link MetadataValue} for this property
*
* @param value The value of the {@link MetadataValue}
* @return a new {@link MetadataValue}
*/
MetadataValue set(T value);
}

View File

@ -0,0 +1,5 @@
package de.epiceric.shopchest.nms.metadata;
public interface MetadataValue {
}

View File

@ -1,54 +1,14 @@
package de.epiceric.shopchest.nms.v1_19_R2;
import de.epiceric.shopchest.nms.FakeArmorStand;
import de.epiceric.shopchest.nms.NMSComponent;
import de.epiceric.shopchest.nms.PacketQueue;
import de.epiceric.shopchest.nms.ReflectionUtils;
import net.minecraft.network.chat.Component;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.ArmorStand;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class FakeArmorStandImpl extends FakeEntityImpl implements FakeArmorStand {
private final static byte INVISIBLE_FLAG = 0b100000;
private final static byte MARKER_FLAG = 0b10000;
private final static EntityDataAccessor<Byte> DATA_SHARED_FLAGS_ID;
private final static EntityDataAccessor<Optional<Component>> DATA_CUSTOM_NAME;
private final static EntityDataAccessor<Boolean> DATA_CUSTOM_NAME_VISIBLE;
static {
try {
DATA_SHARED_FLAGS_ID = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "Z"));
DATA_CUSTOM_NAME = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "aM"));
DATA_CUSTOM_NAME_VISIBLE = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "aN"));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public FakeArmorStandImpl() {
super();
}
@Override
public void metadata(PacketQueue packetQueue, NMSComponent customName) {
final List<SynchedEntityData.DataValue<?>> addProperties = Arrays.asList(
SynchedEntityData.DataValue.create(DATA_SHARED_FLAGS_ID, INVISIBLE_FLAG),
// TODO Handle customName properly
SynchedEntityData.DataValue.create(DATA_CUSTOM_NAME, Optional.ofNullable(null)),
SynchedEntityData.DataValue.create(DATA_CUSTOM_NAME_VISIBLE, true),
SynchedEntityData.DataValue.create(ArmorStand.DATA_CLIENT_FLAGS, MARKER_FLAG)
);
super.metadata(packetQueue, addProperties);
}
@Override
protected EntityType<?> getEntityType() {
return EntityType.ARMOR_STAND;

View File

@ -3,13 +3,11 @@ package de.epiceric.shopchest.nms.v1_19_R2;
import de.epiceric.shopchest.nms.FakeEntity;
import de.epiceric.shopchest.nms.PacketQueue;
import de.epiceric.shopchest.nms.ReflectionUtils;
import de.epiceric.shopchest.nms.metadata.MetadataValue;
import de.epiceric.shopchest.nms.v1_19_R2.metadata.ExplicitMetadataValue;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
@ -17,22 +15,19 @@ import net.minecraft.world.phys.Vec3;
import org.bukkit.Location;
import org.bukkit.util.Vector;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class FakeEntityImpl implements FakeEntity {
private final static AtomicInteger ENTITY_COUNTER;
private final static EntityDataAccessor<Boolean> DATA_NO_GRAVITY;
private final static EntityDataAccessor<Boolean> DATA_SILENT;
static {
try {
ENTITY_COUNTER = (AtomicInteger) ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "c");
DATA_NO_GRAVITY = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "aP"));
DATA_SILENT = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "aO"));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
@ -89,20 +84,19 @@ public abstract class FakeEntityImpl implements FakeEntity {
((PacketQueueImpl) packetQueue).register(positionPacket);
}
/**
* Register a 'metadata' packet in the {@link PacketQueue} with the silent and no gravity properties
*
* @param packetQueue The {@link PacketQueue} to store the packet
* @param addProperties A {@link List} of {@link net.minecraft.network.syncher.SynchedEntityData.DataValue} to add
*/
public void metadata(PacketQueue packetQueue, List<SynchedEntityData.DataValue<?>> addProperties) {
final List<SynchedEntityData.DataValue<?>> packedItems = new LinkedList<>();
packedItems.add(SynchedEntityData.DataValue.create(DATA_NO_GRAVITY, true));
packedItems.add(SynchedEntityData.DataValue.create(DATA_SILENT, true));
packedItems.addAll(addProperties);
@Override
public void metadata(PacketQueue packetQueue, MetadataValue[] values) {
final List<SynchedEntityData.DataValue<?>> packedItems = Stream.of(values)
.map(value -> ((ExplicitMetadataValue) value).toNMS())
.collect(Collectors.toList());
final ClientboundSetEntityDataPacket dataPacket = new ClientboundSetEntityDataPacket(entityId, packedItems);
((PacketQueueImpl) packetQueue).register(dataPacket);
}
@Override
public void cancelVelocity(PacketQueue packetQueue) {
final ClientboundSetEntityMotionPacket velocityPacket = new ClientboundSetEntityMotionPacket(getEntityId(), Vec3.ZERO);
((PacketQueueImpl) packetQueue).register(velocityPacket);
}
}

View File

@ -1,50 +1,14 @@
package de.epiceric.shopchest.nms.v1_19_R2;
import de.epiceric.shopchest.nms.FakeItem;
import de.epiceric.shopchest.nms.PacketQueue;
import de.epiceric.shopchest.nms.ReflectionUtils;
import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.phys.Vec3;
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
import java.util.Collections;
import java.util.List;
public class FakeItemImpl extends FakeEntityImpl implements FakeItem {
private final static EntityDataAccessor<net.minecraft.world.item.ItemStack> DATA_ITEM;
static {
try {
DATA_ITEM = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(ItemEntity.class, "c"));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public FakeItemImpl() {
super();
}
@Override
public void metadata(PacketQueue packetQueue, ItemStack item) {
final List<SynchedEntityData.DataValue<?>> addProperties = Collections.singletonList(
SynchedEntityData.DataValue.create(DATA_ITEM, CraftItemStack.asNMSCopy(item))
);
metadata(packetQueue, addProperties);
}
@Override
public void cancelVelocity(PacketQueue packetQueue) {
final ClientboundSetEntityMotionPacket velocityPacket = new ClientboundSetEntityMotionPacket(getEntityId(), Vec3.ZERO);
((PacketQueueImpl) packetQueue).register(velocityPacket);
}
@Override
protected EntityType<?> getEntityType() {
return EntityType.ITEM;

View File

@ -1,6 +1,8 @@
package de.epiceric.shopchest.nms.v1_19_R2;
import de.epiceric.shopchest.nms.*;
import de.epiceric.shopchest.nms.metadata.MetadataProperties;
import de.epiceric.shopchest.nms.v1_19_R2.metadata.MetadataPropertiesImpl;
public class PlatformImpl implements Platform {
@ -24,4 +26,9 @@ public class PlatformImpl implements Platform {
return new TextComponentHelperImpl();
}
@Override
public MetadataProperties getMetadataProperties() {
return new MetadataPropertiesImpl();
}
}

View File

@ -0,0 +1,17 @@
package de.epiceric.shopchest.nms.v1_19_R2.metadata;
import de.epiceric.shopchest.nms.metadata.MetadataProperties;
import de.epiceric.shopchest.nms.metadata.MetadataProperty;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.decoration.ArmorStand;
public class ArmorStandMetadataProperties implements MetadataProperties.ArmorStand {
private final static byte MARKER_FLAG = 0b10000;
@Override
public MetadataProperty<Boolean> marker() {
return value -> (ExplicitMetadataValue) () -> SynchedEntityData.DataValue.create(ArmorStand.DATA_CLIENT_FLAGS, value ? MARKER_FLAG : 0);
}
}

View File

@ -0,0 +1,60 @@
package de.epiceric.shopchest.nms.v1_19_R2.metadata;
import de.epiceric.shopchest.nms.NMSComponent;
import de.epiceric.shopchest.nms.ReflectionUtils;
import de.epiceric.shopchest.nms.metadata.MetadataProperties;
import de.epiceric.shopchest.nms.metadata.MetadataProperty;
import net.minecraft.network.chat.Component;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import java.util.Optional;
public class EntityMetadataProperties implements MetadataProperties.Entity {
private final static EntityDataAccessor<Boolean> DATA_NO_GRAVITY;
private final static EntityDataAccessor<Boolean> DATA_SILENT;
private final static EntityDataAccessor<Byte> DATA_SHARED_FLAGS_ID;
private final static EntityDataAccessor<Optional<Component>> DATA_CUSTOM_NAME;
private final static EntityDataAccessor<Boolean> DATA_CUSTOM_NAME_VISIBLE;
private final static byte INVISIBLE_FLAG = 0b100000;
static {
try {
DATA_NO_GRAVITY = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "aP"));
DATA_SILENT = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "aO"));
DATA_SHARED_FLAGS_ID = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "Z"));
DATA_CUSTOM_NAME = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "aM"));
DATA_CUSTOM_NAME_VISIBLE = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(Entity.class, "aN"));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public MetadataProperty<Boolean> noGravity() {
return value -> (ExplicitMetadataValue) () -> SynchedEntityData.DataValue.create(DATA_NO_GRAVITY, value);
}
@Override
public MetadataProperty<Boolean> silent() {
return value -> (ExplicitMetadataValue) () -> SynchedEntityData.DataValue.create(DATA_SILENT, value);
}
@Override
public MetadataProperty<Boolean> invisible() {
return value -> (ExplicitMetadataValue) () -> SynchedEntityData.DataValue.create(DATA_SHARED_FLAGS_ID, value ? INVISIBLE_FLAG : 0);
}
@Override
public MetadataProperty<NMSComponent> customName() {
// TODO Handle customName properly
return value -> (ExplicitMetadataValue) () -> SynchedEntityData.DataValue.create(DATA_CUSTOM_NAME, Optional.ofNullable(null));
}
@Override
public MetadataProperty<Boolean> customNameVisible() {
return value -> (ExplicitMetadataValue) () -> SynchedEntityData.DataValue.create(DATA_CUSTOM_NAME_VISIBLE, true);
}
}

View File

@ -0,0 +1,10 @@
package de.epiceric.shopchest.nms.v1_19_R2.metadata;
import de.epiceric.shopchest.nms.metadata.MetadataValue;
import net.minecraft.network.syncher.SynchedEntityData;
public interface ExplicitMetadataValue extends MetadataValue {
SynchedEntityData.DataValue<?> toNMS();
}

View File

@ -0,0 +1,28 @@
package de.epiceric.shopchest.nms.v1_19_R2.metadata;
import de.epiceric.shopchest.nms.ReflectionUtils;
import de.epiceric.shopchest.nms.metadata.MetadataProperties;
import de.epiceric.shopchest.nms.metadata.MetadataProperty;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.item.ItemEntity;
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
public class ItemMetadataProperties implements MetadataProperties.Item {
private final static EntityDataAccessor<net.minecraft.world.item.ItemStack> DATA_ITEM;
static {
try {
DATA_ITEM = ReflectionUtils.forceCast(ReflectionUtils.getPrivateStaticFieldValue(ItemEntity.class, "c"));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public MetadataProperty<ItemStack> item() {
return value -> (ExplicitMetadataValue) () -> SynchedEntityData.DataValue.create(DATA_ITEM, CraftItemStack.asNMSCopy(value));
}
}

View File

@ -0,0 +1,21 @@
package de.epiceric.shopchest.nms.v1_19_R2.metadata;
import de.epiceric.shopchest.nms.metadata.MetadataProperties;
public class MetadataPropertiesImpl implements MetadataProperties {
@Override
public Entity entity() {
return new EntityMetadataProperties();
}
@Override
public ArmorStand armorStand() {
return new ArmorStandMetadataProperties();
}
@Override
public Item item() {
return new ItemMetadataProperties();
}
}

View File

@ -1,17 +1,18 @@
package de.epiceric.shopchest.nms;
import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.nms.metadata.MetadataProperties;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class ArmorStandWrapper {
private final UUID uuid = UUID.randomUUID();
private final Platform platform;
private final FakeArmorStand fakeArmorStand;
private Location location;
@ -20,28 +21,47 @@ public class ArmorStandWrapper {
public ArmorStandWrapper(ShopChest plugin, Location location, String customName) {
this.location = location;
this.customName = customName;
this.fakeArmorStand = plugin.getPlatform().createFakeArmorStand();
this.platform = plugin.getPlatform();
this.fakeArmorStand = platform.createFakeArmorStand();
}
public void setVisible(Player player, boolean visible) {
final List<Player> receiver = Collections.singletonList(player);
if(visible){
fakeArmorStand.spawn(uuid, location, receiver);
fakeArmorStand.sendData(customName, receiver);
final PacketQueue packetQueue = platform.createPacketQueue();
fakeArmorStand.create(packetQueue, uuid, location);
final MetadataProperties mdp = platform.getMetadataProperties();
final MetadataProperties.Entity entityMdp = mdp.entity();
fakeArmorStand.metadata(packetQueue,
entityMdp.noGravity().set(true),
entityMdp.silent().set(true),
entityMdp.invisible().set(true),
// TODO Handle custom name properly
entityMdp.customName().set(new NMSComponent()),
entityMdp.customNameVisible().set(true),
mdp.armorStand().marker().set(true)
);
packetQueue.send(Collections.singletonList(player));
}
else if(fakeArmorStand.getEntityId() != -1){
fakeArmorStand.remove(receiver);
final PacketQueue packetQueue = platform.createPacketQueue();
fakeArmorStand.remove(packetQueue);
packetQueue.send(Collections.singletonList(player));
}
}
public void setLocation(Location location) {
this.location = location;
fakeArmorStand.setLocation(location, Objects.requireNonNull(location.getWorld()).getPlayers());
final PacketQueue packetQueue = platform.createPacketQueue();
fakeArmorStand.teleport(packetQueue, location.toVector());
packetQueue.send(Objects.requireNonNull(location.getWorld()).getPlayers());
}
public void setCustomName(String customName) {
this.customName = customName;
fakeArmorStand.sendData(customName, Objects.requireNonNull(location.getWorld()).getPlayers());
final PacketQueue packetQueue = platform.createPacketQueue();
// TODO Handle custom name properly
fakeArmorStand.metadata(packetQueue, platform.getMetadataProperties().entity().customName().set(new NMSComponent()));
packetQueue.send(Objects.requireNonNull(location.getWorld()).getPlayers());
}
public void remove() {

View File

@ -376,6 +376,9 @@ public class Shop {
holoLocation.add(0, GlobalConfig.hologramLift, 0);
final float MARKER_ARMOR_STAND_OFFSET = 1.975f;
holoLocation.add(0, MARKER_ARMOR_STAND_OFFSET, 0);
return holoLocation;
}

View File

@ -2,12 +2,18 @@ package de.epiceric.shopchest.shop;
import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.nms.FakeItem;
import de.epiceric.shopchest.nms.PacketQueue;
import de.epiceric.shopchest.nms.Platform;
import de.epiceric.shopchest.nms.metadata.MetadataProperties;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class ShopItem {
@ -17,12 +23,14 @@ public class ShopItem {
private final ItemStack itemStack;
private final Location location;
private final UUID uuid = UUID.randomUUID();
private final Platform platform;
private final FakeItem fakeItem;
public ShopItem(ShopChest plugin, ItemStack itemStack, Location location) {
this.itemStack = itemStack;
this.location = location;
this.fakeItem = plugin.getPlatform().createFakeItem();
this.platform = plugin.getPlatform();
this.fakeItem = platform.createFakeItem();
}
/**
@ -60,10 +68,17 @@ public class ShopItem {
*/
public void showPlayer(Player p, boolean force) {
if (viewers.add(p.getUniqueId()) || force) {
final List<Player> receiver = Collections.singletonList(p);
fakeItem.spawn(uuid, location, receiver);
fakeItem.sendData(itemStack, receiver);
fakeItem.resetVelocity(receiver);
final PacketQueue packetQueue = platform.createPacketQueue();
fakeItem.create(packetQueue, uuid, location);
final MetadataProperties mdp = platform.getMetadataProperties();
final MetadataProperties.Entity entityMdp = mdp.entity();
fakeItem.metadata(packetQueue,
entityMdp.noGravity().set(true),
entityMdp.silent().set(true),
mdp.item().item().set(itemStack)
);
fakeItem.cancelVelocity(packetQueue);
packetQueue.send(Collections.singletonList(p));
}
}
@ -81,7 +96,9 @@ public class ShopItem {
public void hidePlayer(Player p, boolean force) {
if (viewers.remove(p.getUniqueId()) || force) {
if (p.isOnline()) {
fakeItem.remove(Collections.singletonList(p));
final PacketQueue packetQueue = platform.createPacketQueue();
fakeItem.remove(packetQueue);
packetQueue.send(Collections.singletonList(p));
}
}
}