diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/commands/PropertiesCommand.java b/plugin/src/main/java/lol/pyr/znpcsplus/commands/PropertiesCommand.java index 1faa16c..b8925d5 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/commands/PropertiesCommand.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/commands/PropertiesCommand.java @@ -1,5 +1,6 @@ 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.CommandHandler; import lol.pyr.director.common.command.CommandExecutionException; @@ -10,6 +11,7 @@ import lol.pyr.znpcsplus.npc.NpcImpl; import lol.pyr.znpcsplus.npc.NpcRegistryImpl; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import com.github.retrooper.packetevents.protocol.item.ItemStack; import java.util.Collections; import java.util.List; @@ -23,15 +25,32 @@ public class PropertiesCommand implements CommandHandler { @Override public void run(CommandContext context) throws CommandExecutionException { + context.setUsage(context.getLabel() + " properties "); NpcEntryImpl entry = context.parse(NpcEntryImpl.class); NpcImpl npc = entry.getNpc(); 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); - 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 diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java index 3cf1dae..55bcf21 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java @@ -1,11 +1,9 @@ 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.skin.SkinDescriptor; -import lol.pyr.znpcsplus.entity.serializers.BooleanPropertySerializer; -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.entity.serializers.*; import lol.pyr.znpcsplus.skin.cache.SkinCache; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -23,6 +21,7 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { registerSerializer(new ComponentPropertySerializer()); registerSerializer(new NamedTextColorPropertySerializer()); registerSerializer(new SkinDescriptorSerializer(skinCache)); + registerSerializer(new ItemStackPropertySerializer()); registerType("glow", NamedTextColor.class); registerType("skin_layers", true); @@ -32,6 +31,13 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { registerType("skin", SkinDescriptor.class); registerType("name", Component.class); 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) { diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/serializers/ItemStackPropertySerializer.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/serializers/ItemStackPropertySerializer.java new file mode 100644 index 0000000..c6f1cb5 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/serializers/ItemStackPropertySerializer.java @@ -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 { + @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 getTypeClass() { + return ItemStack.class; + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java index f0c3a80..fa5faad 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java @@ -114,8 +114,7 @@ public class NpcImpl extends Viewable implements Npc { } public void setProperty(EntityPropertyImpl key, T value) { - if (value == null) return; - if (value.equals(key.getDefaultValue())) removeProperty(key); + if (value == null || value.equals(key.getDefaultValue())) removeProperty(key); else propertyMap.put(key, value); UNSAFE_refreshMeta(); if (key.getName().equalsIgnoreCase("glow")) UNSAFE_remakeTeam(); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java index 7a1d8e9..31669d8 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java @@ -59,6 +59,11 @@ public class NpcTypeImpl implements NpcType { return this; } + public Builder addProperties(String... names) { + for (String name : names) allowedProperties.add(propertyRegistry.getByName(name)); + return this; + } + public Builder setEnableGlobalProperties(boolean enabled) { globalProperties = enabled; return this; diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeRegistryImpl.java b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeRegistryImpl.java index 7754f23..e765e86 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeRegistryImpl.java @@ -29,7 +29,7 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry { ServerVersion version = packetEvents.getServerManager().getVersion(); 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, "bat", EntityTypes.BAT).setHologramOffset(-1.365)); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java index 81244cb..a7ae861 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/PacketFactory.java @@ -21,4 +21,5 @@ public interface PacketFactory { Map generateMetadata(Player player, PacketEntity entity, PropertyHolder properties); void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties); void sendMetadata(Player player, PacketEntity entity, List data); + void sendEquipment(Player player, PacketEntity entity, PropertyHolder properties); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_16PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_16PacketFactory.java new file mode 100644 index 0000000..354d0a9 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_16PacketFactory.java @@ -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 packetEvents, EntityPropertyRegistryImpl propertyRegistry) { + super(scheduler, metadataFactory, packetEvents, propertyRegistry); + } + + @Override + public void sendEquipment(Player player, PacketEntity entity, PropertyHolder properties) { + List equipments = generateEquipments(properties); + if (equipments.size() > 0) sendPacket(player, new WrapperPlayServerEntityEquipment(entity.getEntityId(), equipments)); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_19PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_19PacketFactory.java index e54875e..cc29fcf 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_19PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_19PacketFactory.java @@ -18,7 +18,7 @@ import org.bukkit.plugin.Plugin; import java.util.EnumSet; 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 packetEvents, EntityPropertyRegistryImpl propertyRegistry) { super(scheduler, metadataFactory, packetEvents, propertyRegistry); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java index 8b22ac0..513d1da 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/packets/V1_8PacketFactory.java @@ -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.type.EntityType; import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; -import com.github.retrooper.packetevents.protocol.player.ClientVersion; -import com.github.retrooper.packetevents.protocol.player.GameMode; -import com.github.retrooper.packetevents.protocol.player.UserProfile; +import com.github.retrooper.packetevents.protocol.item.ItemStack; +import com.github.retrooper.packetevents.protocol.item.type.ItemTypes; +import com.github.retrooper.packetevents.protocol.player.*; import com.github.retrooper.packetevents.util.Vector3d; import com.github.retrooper.packetevents.wrapper.PacketWrapper; import com.github.retrooper.packetevents.wrapper.play.server.*; import lol.pyr.znpcsplus.api.entity.PropertyHolder; import lol.pyr.znpcsplus.api.skin.SkinDescriptor; +import lol.pyr.znpcsplus.entity.EntityPropertyImpl; import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; import lol.pyr.znpcsplus.entity.PacketEntity; import lol.pyr.znpcsplus.metadata.MetadataFactory; @@ -135,6 +136,7 @@ public class V1_8PacketFactory implements PacketFactory { @Override public void sendAllMetadata(Player player, PacketEntity entity, PropertyHolder properties) { sendMetadata(player, entity, new ArrayList<>(generateMetadata(player, entity, properties).values())); + sendEquipment(player, entity, properties); } @Override @@ -142,6 +144,37 @@ public class V1_8PacketFactory implements PacketFactory { 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 generateEquipments(PropertyHolder properties) { + List equipements = new ArrayList<>(); + ItemStack air = new ItemStack.Builder().type(ItemTypes.AIR).build(); + + EntityPropertyImpl helmet = propertyRegistry.getByName("helmet", ItemStack.class); + equipements.add(new Equipment(EquipmentSlot.HELMET, properties.hasProperty(helmet) ? properties.getProperty(helmet) : air)); + + EntityPropertyImpl chestplate = propertyRegistry.getByName("chestplate", ItemStack.class); + equipements.add(new Equipment(EquipmentSlot.CHEST_PLATE, properties.hasProperty(chestplate) ? properties.getProperty(chestplate) : air)); + + EntityPropertyImpl leggings = propertyRegistry.getByName("leggings", ItemStack.class); + equipements.add(new Equipment(EquipmentSlot.LEGGINGS, properties.hasProperty(leggings) ? properties.getProperty(leggings) : air)); + + EntityPropertyImpl boots = propertyRegistry.getByName("boots", ItemStack.class); + equipements.add(new Equipment(EquipmentSlot.BOOTS, properties.hasProperty(boots) ? properties.getProperty(boots) : air)); + + EntityPropertyImpl hand = propertyRegistry.getByName("hand", ItemStack.class); + equipements.add(new Equipment(EquipmentSlot.MAIN_HAND, properties.hasProperty(hand) ? properties.getProperty(hand) : air)); + + EntityPropertyImpl 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) { packetEvents.getPlayerManager().sendPacket(player, packet); } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/util/ItemSerializationUtil.java b/plugin/src/main/java/lol/pyr/znpcsplus/util/ItemSerializationUtil.java new file mode 100644 index 0000000..1959845 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/util/ItemSerializationUtil.java @@ -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)); + } +}