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");