add support for entity equipment

This commit is contained in:
Pyrbu 2023-05-24 23:27:11 +01:00
parent 1bbee6681a
commit 5ae5ca200d
11 changed files with 167 additions and 14 deletions

@ -1,5 +1,6 @@
package lol.pyr.znpcsplus.commands; package lol.pyr.znpcsplus.commands;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lol.pyr.director.adventure.command.CommandContext; import lol.pyr.director.adventure.command.CommandContext;
import lol.pyr.director.adventure.command.CommandHandler; import lol.pyr.director.adventure.command.CommandHandler;
import lol.pyr.director.common.command.CommandExecutionException; import lol.pyr.director.common.command.CommandExecutionException;
@ -10,6 +11,7 @@ import lol.pyr.znpcsplus.npc.NpcImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -23,15 +25,32 @@ public class PropertiesCommand implements CommandHandler {
@Override @Override
public void run(CommandContext context) throws CommandExecutionException { public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " properties <id> <property> <value>");
NpcEntryImpl entry = context.parse(NpcEntryImpl.class); NpcEntryImpl entry = context.parse(NpcEntryImpl.class);
NpcImpl npc = entry.getNpc(); NpcImpl npc = entry.getNpc();
EntityPropertyImpl<?> property = context.parse(EntityPropertyImpl.class); EntityPropertyImpl<?> property = context.parse(EntityPropertyImpl.class);
if (!npc.getType().getAllowedProperties().contains(property)) context.halt(Component.text("Property " + property.getName() + " not allowed for npc type " + npc.getType().getName())); if (!npc.getType().getAllowedProperties().contains(property)) context.halt(Component.text("Property " + property.getName() + " not allowed for npc type " + npc.getType().getName(), NamedTextColor.RED));
Class<?> type = property.getType();
Object value;
String valueName;
if (type == ItemStack.class) {
org.bukkit.inventory.ItemStack bukkitStack = context.ensureSenderIsPlayer().getInventory().getItemInMainHand();
if (bukkitStack.getType().isAir()) {
value = null;
valueName = "EMPTY";
} else {
value = SpigotConversionUtil.fromBukkitItemStack(bukkitStack);
valueName = bukkitStack.toString();
}
}
else {
value = context.parse(property.getType());
valueName = String.valueOf(value);
}
Object value = context.parse(property.getType());
npc.UNSAFE_setProperty(property, value); npc.UNSAFE_setProperty(property, value);
context.send(Component.text("Set property " + property.getName() + " for NPC " + entry.getId() + " to " + value.toString(), NamedTextColor.GREEN)); context.send(Component.text("Set property " + property.getName() + " for NPC " + entry.getId() + " to " + valueName, NamedTextColor.GREEN));
} }
@Override @Override

@ -1,11 +1,9 @@
package lol.pyr.znpcsplus.entity; package lol.pyr.znpcsplus.entity;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry; import lol.pyr.znpcsplus.api.entity.EntityPropertyRegistry;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.entity.serializers.BooleanPropertySerializer; import lol.pyr.znpcsplus.entity.serializers.*;
import lol.pyr.znpcsplus.entity.serializers.ComponentPropertySerializer;
import lol.pyr.znpcsplus.entity.serializers.NamedTextColorPropertySerializer;
import lol.pyr.znpcsplus.entity.serializers.SkinDescriptorSerializer;
import lol.pyr.znpcsplus.skin.cache.SkinCache; import lol.pyr.znpcsplus.skin.cache.SkinCache;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@ -23,6 +21,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
registerSerializer(new ComponentPropertySerializer()); registerSerializer(new ComponentPropertySerializer());
registerSerializer(new NamedTextColorPropertySerializer()); registerSerializer(new NamedTextColorPropertySerializer());
registerSerializer(new SkinDescriptorSerializer(skinCache)); registerSerializer(new SkinDescriptorSerializer(skinCache));
registerSerializer(new ItemStackPropertySerializer());
registerType("glow", NamedTextColor.class); registerType("glow", NamedTextColor.class);
registerType("skin_layers", true); registerType("skin_layers", true);
@ -32,6 +31,13 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry {
registerType("skin", SkinDescriptor.class); registerType("skin", SkinDescriptor.class);
registerType("name", Component.class); registerType("name", Component.class);
registerType("look", false); registerType("look", false);
registerType("helmet", ItemStack.class);
registerType("chestplate", ItemStack.class);
registerType("leggings", ItemStack.class);
registerType("boots", ItemStack.class);
registerType("hand", ItemStack.class);
registerType("offhand", ItemStack.class);
} }
private void registerSerializer(PropertySerializer<?> serializer) { private void registerSerializer(PropertySerializer<?> serializer) {

@ -0,0 +1,23 @@
package lol.pyr.znpcsplus.entity.serializers;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lol.pyr.znpcsplus.entity.PropertySerializer;
import lol.pyr.znpcsplus.util.ItemSerializationUtil;
public class ItemStackPropertySerializer implements PropertySerializer<ItemStack> {
@Override
public String serialize(ItemStack property) {
return ItemSerializationUtil.itemToB64(SpigotConversionUtil.toBukkitItemStack(property));
}
@Override
public ItemStack deserialize(String property) {
return SpigotConversionUtil.fromBukkitItemStack(ItemSerializationUtil.itemFromB64(property));
}
@Override
public Class<ItemStack> getTypeClass() {
return ItemStack.class;
}
}

@ -114,8 +114,7 @@ public class NpcImpl extends Viewable implements Npc {
} }
public <T> void setProperty(EntityPropertyImpl<T> key, T value) { public <T> void setProperty(EntityPropertyImpl<T> key, T value) {
if (value == null) return; if (value == null || value.equals(key.getDefaultValue())) removeProperty(key);
if (value.equals(key.getDefaultValue())) removeProperty(key);
else propertyMap.put(key, value); else propertyMap.put(key, value);
UNSAFE_refreshMeta(); UNSAFE_refreshMeta();
if (key.getName().equalsIgnoreCase("glow")) UNSAFE_remakeTeam(); if (key.getName().equalsIgnoreCase("glow")) UNSAFE_remakeTeam();

@ -59,6 +59,11 @@ public class NpcTypeImpl implements NpcType {
return this; return this;
} }
public Builder addProperties(String... names) {
for (String name : names) allowedProperties.add(propertyRegistry.getByName(name));
return this;
}
public Builder setEnableGlobalProperties(boolean enabled) { public Builder setEnableGlobalProperties(boolean enabled) {
globalProperties = enabled; globalProperties = enabled;
return this; return this;

@ -29,7 +29,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry {
ServerVersion version = packetEvents.getServerManager().getVersion(); ServerVersion version = packetEvents.getServerManager().getVersion();
register(new NpcTypeImpl.Builder(propertyRegistry, "player", EntityTypes.PLAYER).setHologramOffset(-0.15D) register(new NpcTypeImpl.Builder(propertyRegistry, "player", EntityTypes.PLAYER).setHologramOffset(-0.15D)
.addProperties(propertyRegistry.getByName("skin"), propertyRegistry.getByName("skin_layers"))); .addProperties("skin", "skin_layers", "helmet", "chestplate", "leggings", "boots", "hand", "offhand"));
register(new NpcTypeImpl.Builder(propertyRegistry, "armor_stand", EntityTypes.ARMOR_STAND)); register(new NpcTypeImpl.Builder(propertyRegistry, "armor_stand", EntityTypes.ARMOR_STAND));
register(new NpcTypeImpl.Builder(propertyRegistry, "bat", EntityTypes.BAT).setHologramOffset(-1.365)); register(new NpcTypeImpl.Builder(propertyRegistry, "bat", EntityTypes.BAT).setHologramOffset(-1.365));

@ -21,4 +21,5 @@ public interface PacketFactory {
Map<Integer, EntityData> generateMetadata(Player player, PacketEntity entity, PropertyHolder properties); Map<Integer, EntityData> generateMetadata(Player player, PacketEntity entity, PropertyHolder properties);
void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties); void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties);
void sendMetadata(Player player, PacketEntity entity, List<EntityData> data); void sendMetadata(Player player, PacketEntity entity, List<EntityData> data);
void sendEquipment(Player player, PacketEntity entity, PropertyHolder properties);
} }

@ -0,0 +1,26 @@
package lol.pyr.znpcsplus.packets;
import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.protocol.player.Equipment;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityEquipment;
import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.metadata.MetadataFactory;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.List;
public class V1_16PacketFactory extends V1_14PacketFactory {
public V1_16PacketFactory(TaskScheduler scheduler, MetadataFactory metadataFactory, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry) {
super(scheduler, metadataFactory, packetEvents, propertyRegistry);
}
@Override
public void sendEquipment(Player player, PacketEntity entity, PropertyHolder properties) {
List<Equipment> equipments = generateEquipments(properties);
if (equipments.size() > 0) sendPacket(player, new WrapperPlayServerEntityEquipment(entity.getEntityId(), equipments));
}
}

@ -18,7 +18,7 @@ import org.bukkit.plugin.Plugin;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class V1_19PacketFactory extends V1_14PacketFactory { public class V1_19PacketFactory extends V1_16PacketFactory {
public V1_19PacketFactory(TaskScheduler scheduler, MetadataFactory metadataFactory, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry) { public V1_19PacketFactory(TaskScheduler scheduler, MetadataFactory metadataFactory, PacketEventsAPI<Plugin> packetEvents, EntityPropertyRegistryImpl propertyRegistry) {
super(scheduler, metadataFactory, packetEvents, propertyRegistry); super(scheduler, metadataFactory, packetEvents, propertyRegistry);
} }

@ -4,14 +4,15 @@ import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.protocol.entity.data.EntityData; import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType; import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.player.ClientVersion; import com.github.retrooper.packetevents.protocol.item.ItemStack;
import com.github.retrooper.packetevents.protocol.player.GameMode; import com.github.retrooper.packetevents.protocol.item.type.ItemTypes;
import com.github.retrooper.packetevents.protocol.player.UserProfile; import com.github.retrooper.packetevents.protocol.player.*;
import com.github.retrooper.packetevents.util.Vector3d; import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.PacketWrapper; import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.play.server.*; import com.github.retrooper.packetevents.wrapper.play.server.*;
import lol.pyr.znpcsplus.api.entity.PropertyHolder; import lol.pyr.znpcsplus.api.entity.PropertyHolder;
import lol.pyr.znpcsplus.api.skin.SkinDescriptor; import lol.pyr.znpcsplus.api.skin.SkinDescriptor;
import lol.pyr.znpcsplus.entity.EntityPropertyImpl;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.entity.PacketEntity; import lol.pyr.znpcsplus.entity.PacketEntity;
import lol.pyr.znpcsplus.metadata.MetadataFactory; import lol.pyr.znpcsplus.metadata.MetadataFactory;
@ -135,6 +136,7 @@ public class V1_8PacketFactory implements PacketFactory {
@Override @Override
public void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties) { public void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties) {
sendMetadata(player, entity, new ArrayList<>(generateMetadata(player, entity, properties).values())); sendMetadata(player, entity, new ArrayList<>(generateMetadata(player, entity, properties).values()));
sendEquipment(player, entity, properties);
} }
@Override @Override
@ -142,6 +144,37 @@ public class V1_8PacketFactory implements PacketFactory {
packetEvents.getPlayerManager().sendPacket(player, new WrapperPlayServerEntityMetadata(entity.getEntityId(), data)); packetEvents.getPlayerManager().sendPacket(player, new WrapperPlayServerEntityMetadata(entity.getEntityId(), data));
} }
@Override
public void sendEquipment(Player player, PacketEntity entity, PropertyHolder properties) {
for (Equipment equipment : generateEquipments(properties))
sendPacket(player, new WrapperPlayServerEntityEquipment(entity.getEntityId(), Collections.singletonList(equipment)));
}
protected List<Equipment> generateEquipments(PropertyHolder properties) {
List<Equipment> equipements = new ArrayList<>();
ItemStack air = new ItemStack.Builder().type(ItemTypes.AIR).build();
EntityPropertyImpl<ItemStack> helmet = propertyRegistry.getByName("helmet", ItemStack.class);
equipements.add(new Equipment(EquipmentSlot.HELMET, properties.hasProperty(helmet) ? properties.getProperty(helmet) : air));
EntityPropertyImpl<ItemStack> chestplate = propertyRegistry.getByName("chestplate", ItemStack.class);
equipements.add(new Equipment(EquipmentSlot.CHEST_PLATE, properties.hasProperty(chestplate) ? properties.getProperty(chestplate) : air));
EntityPropertyImpl<ItemStack> leggings = propertyRegistry.getByName("leggings", ItemStack.class);
equipements.add(new Equipment(EquipmentSlot.LEGGINGS, properties.hasProperty(leggings) ? properties.getProperty(leggings) : air));
EntityPropertyImpl<ItemStack> boots = propertyRegistry.getByName("boots", ItemStack.class);
equipements.add(new Equipment(EquipmentSlot.BOOTS, properties.hasProperty(boots) ? properties.getProperty(boots) : air));
EntityPropertyImpl<ItemStack> hand = propertyRegistry.getByName("hand", ItemStack.class);
equipements.add(new Equipment(EquipmentSlot.MAIN_HAND, properties.hasProperty(hand) ? properties.getProperty(hand) : air));
EntityPropertyImpl<ItemStack> offhand = propertyRegistry.getByName("offhand", ItemStack.class);
equipements.add(new Equipment(EquipmentSlot.OFF_HAND, properties.hasProperty(offhand) ? properties.getProperty(offhand) : air));
return equipements;
}
protected void sendPacket(Player player, PacketWrapper<?> packet) { protected void sendPacket(Player player, PacketWrapper<?> packet) {
packetEvents.getPlayerManager().sendPacket(player, packet); packetEvents.getPlayerManager().sendPacket(player, packet);
} }

@ -0,0 +1,41 @@
package lol.pyr.znpcsplus.util;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
public class ItemSerializationUtil {
public static byte[] itemToBytes(ItemStack item) {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
new BukkitObjectOutputStream(bout).writeObject(item);
} catch (IOException e) {
throw new RuntimeException(e);
}
return bout.toByteArray();
}
public static ItemStack itemFromBytes(byte[] bytes) {
ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
try {
return (ItemStack) new BukkitObjectInputStream(bin).readObject();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public static String itemToB64(ItemStack item) {
if (item == null) return null;
return Base64.getEncoder().encodeToString(itemToBytes(item));
}
public static ItemStack itemFromB64(String str) {
if (str == null) return null;
return itemFromBytes(Base64.getDecoder().decode(str));
}
}