diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/ArmorStandVehicleEntity.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/ArmorStandVehicleEntity.java new file mode 100644 index 0000000..0bc3886 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/ArmorStandVehicleEntity.java @@ -0,0 +1,72 @@ +package lol.pyr.znpcsplus.entity; + +import io.github.retrooper.packetevents.util.SpigotConversionUtil; +import lol.pyr.znpcsplus.api.entity.EntityProperty; +import lol.pyr.znpcsplus.api.entity.PropertyHolder; +import org.bukkit.inventory.ItemStack; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Represents an armor stand vehicle entity. + *

+ * This entity is used to make the NPC sit on an invisible armor stand. + *

+ */ +public class ArmorStandVehicleEntity implements PropertyHolder { + + private final Map, Object> propertyMap = new HashMap<>(); + + public ArmorStandVehicleEntity(EntityPropertyRegistryImpl propertyRegistry) { + setProperty(propertyRegistry.getByName("small", Boolean.class), true); + setProperty(propertyRegistry.getByName("invisible", Boolean.class), true); + setProperty(propertyRegistry.getByName("base_plate", Boolean.class), false); + } + + @SuppressWarnings("unchecked") + public T getProperty(EntityProperty key) { + return hasProperty(key) ? (T) propertyMap.get((EntityPropertyImpl) key) : key.getDefaultValue(); + } + + @Override + public boolean hasProperty(EntityProperty key) { + return propertyMap.containsKey((EntityPropertyImpl) key); + } + + @SuppressWarnings("unchecked") + @Override + public void setProperty(EntityProperty key, T value) { + Object val = value; + if (val instanceof ItemStack) val = SpigotConversionUtil.fromBukkitItemStack((ItemStack) val); + + setProperty((EntityPropertyImpl) key, (T) val); + } + + @Override + public void setItemProperty(EntityProperty key, ItemStack value) { + throw new UnsupportedOperationException("Cannot set item properties on armor stands"); + } + + @Override + public ItemStack getItemProperty(EntityProperty key) { + throw new UnsupportedOperationException("Cannot get item properties on armor stands"); + } + + public void setProperty(EntityPropertyImpl key, T value) { + if (key == null) return; + if (value == null || value.equals(key.getDefaultValue())) propertyMap.remove(key); + else propertyMap.put(key, value); + } + + public Set> getAllProperties() { + return Collections.unmodifiableSet(propertyMap.keySet()); + } + + @Override + public Set> getAppliedProperties() { + return Collections.unmodifiableSet(propertyMap.keySet()); + } +} 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 6bb9b3c..f097962 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java @@ -182,6 +182,8 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { register(new BooleanProperty("baby", babyIndex, false, legacyBooleans)); } + register(new EntitySittingProperty(packetFactory, this)); + // Player register(new DummyProperty<>("skin", SkinDescriptor.class, false)); final int skinLayersIndex; diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java index 4418152..5687adc 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/PacketEntity.java @@ -4,6 +4,7 @@ import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.protocol.entity.type.EntityType; import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; +import lol.pyr.znpcsplus.ZNpcsPlusBootstrap; import lol.pyr.znpcsplus.api.entity.EntityProperty; import lol.pyr.znpcsplus.api.entity.PropertyHolder; import lol.pyr.znpcsplus.packets.PacketFactory; @@ -13,6 +14,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import java.util.Collection; +import java.util.HashMap; import java.util.Set; import java.util.UUID; @@ -26,6 +28,8 @@ public class PacketEntity implements PropertyHolder { private final EntityType type; private NpcLocation location; + private final HashMap metadata = new HashMap<>(); + public PacketEntity(PacketFactory packetFactory, PropertyHolder properties, EntityType type, NpcLocation location) { this.packetFactory = packetFactory; this.properties = properties; @@ -67,6 +71,15 @@ public class PacketEntity implements PropertyHolder { public void despawn(Player player) { packetFactory.destroyEntity(player, this, properties); + if (hasMetadata("ridingVehicle")) { + try { + PacketEntity armorStand = (PacketEntity) getMetadata("ridingVehicle"); + armorStand.despawn(player); + } catch (Exception e) { + //noinspection CallToPrintStackTrace + e.printStackTrace(); + } + } } public void refreshMeta(Player player) { @@ -116,4 +129,32 @@ public class PacketEntity implements PropertyHolder { public Set> getAppliedProperties() { return properties.getAppliedProperties(); } + + public void setMetadata(String key, Object value) { + metadata.put(key, value); + } + + public Object getMetadata(String key) { + return metadata.get(key); + } + + public T getMetadata(String key, Class type) { + try { + return type.cast(metadata.get(key)); + } catch (ClassCastException e) { + return null; + } + } + + public void removeMetadata(String key) { + metadata.remove(key); + } + + public boolean hasMetadata(String key) { + return metadata.containsKey(key); + } + + public void clearMetadata() { + metadata.clear(); + } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/EntitySittingProperty.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/EntitySittingProperty.java new file mode 100644 index 0000000..fb8fc2c --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/EntitySittingProperty.java @@ -0,0 +1,53 @@ +package lol.pyr.znpcsplus.entity.properties; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSetPassengers; +import lol.pyr.znpcsplus.entity.ArmorStandVehicleEntity; +import lol.pyr.znpcsplus.entity.EntityPropertyImpl; +import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl; +import lol.pyr.znpcsplus.entity.PacketEntity; +import lol.pyr.znpcsplus.packets.PacketFactory; +import org.bukkit.entity.Player; + +import java.util.Map; + +public class EntitySittingProperty extends EntityPropertyImpl { + private final PacketFactory packetFactory; + private final EntityPropertyRegistryImpl propertyRegistry; + + public EntitySittingProperty(PacketFactory packetFactory, EntityPropertyRegistryImpl propertyRegistry) { + super("entity_sitting", false, Boolean.class); + this.packetFactory = packetFactory; + this.propertyRegistry = propertyRegistry; + } + + @Override + public void apply(Player player, PacketEntity entity, boolean isSpawned, Map properties) { + boolean sitting = entity.getProperty(this); + if (sitting) { + ArmorStandVehicleEntity vehicleEntity = new ArmorStandVehicleEntity(propertyRegistry); + PacketEntity vehiclePacketEntity = new PacketEntity(packetFactory, vehicleEntity, EntityTypes.ARMOR_STAND, entity.getLocation().withY(entity.getLocation().getY() - 0.9)); + vehiclePacketEntity.spawn(player); + entity.setMetadata("ridingVehicle", vehiclePacketEntity); + PacketEvents.getAPI().getPlayerManager().sendPacket(player, new WrapperPlayServerSetPassengers( + vehiclePacketEntity.getEntityId(), + new int[]{entity.getEntityId()} + )); + } else { + if (entity.hasMetadata("ridingVehicle")) { + PacketEntity vehicleEntity = (PacketEntity) entity.getMetadata("ridingVehicle"); + PacketEvents.getAPI().getPlayerManager().sendPacket(player, new WrapperPlayServerSetPassengers( + vehicleEntity.getEntityId(), + new int[]{} + )); + vehicleEntity.despawn(player); + entity.removeMetadata("ridingVehicle"); + // Send a packet to reset the npc's position + packetFactory.teleportEntity(player, entity); + } + } + } + +} 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 4fa6b67..0a5695a 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcImpl.java @@ -86,6 +86,10 @@ public class NpcImpl extends Viewable implements Npc { public void setLocation(NpcLocation location) { this.location = location; entity.setLocation(location, getViewers()); + if (entity.hasMetadata("ridingVehicle")) { + PacketEntity armorStand = (PacketEntity) entity.getMetadata("ridingVehicle"); + armorStand.setLocation(location.withY(location.getY() - 0.9), getViewers()); + } hologram.setLocation(location.withY(location.getY() + type.getHologramOffset())); } 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 d072740..1e6a26d 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java @@ -118,7 +118,7 @@ public class NpcTypeImpl implements NpcType { "potion_color", "potion_ambient", "display_name", "permission_required", "player_knockback", "player_knockback_exempt_permission", "player_knockback_distance", "player_knockback_vertical", "player_knockback_horizontal", "player_knockback_cooldown", "player_knockback_sound", "player_knockback_sound_name", - "player_knockback_sound_volume", "player_knockback_sound_pitch"); + "player_knockback_sound_volume", "player_knockback_sound_pitch", "entity_sitting"); if (!type.equals(EntityTypes.PLAYER)) addProperties("dinnerbone"); // TODO: make this look nicer after completing the rest of the properties if (version.isNewerThanOrEquals(ServerVersion.V_1_9)) addProperties("glow");