diff --git a/api/src/main/java/lol/pyr/znpcsplus/util/HorseArmor.java b/api/src/main/java/lol/pyr/znpcsplus/util/HorseArmor.java new file mode 100644 index 0000000..80fa2f2 --- /dev/null +++ b/api/src/main/java/lol/pyr/znpcsplus/util/HorseArmor.java @@ -0,0 +1,8 @@ +package lol.pyr.znpcsplus.util; + +public enum HorseArmor { + NONE, + IRON, + GOLD, + DIAMOND +} diff --git a/api/src/main/java/lol/pyr/znpcsplus/util/HorseColor.java b/api/src/main/java/lol/pyr/znpcsplus/util/HorseColor.java new file mode 100644 index 0000000..ae6327a --- /dev/null +++ b/api/src/main/java/lol/pyr/znpcsplus/util/HorseColor.java @@ -0,0 +1,11 @@ +package lol.pyr.znpcsplus.util; + +public enum HorseColor { + WHITE, + CREAMY, + CHESTNUT, + BROWN, + BLACK, + GRAY, + DARK_BROWN +} diff --git a/api/src/main/java/lol/pyr/znpcsplus/util/HorseStyle.java b/api/src/main/java/lol/pyr/znpcsplus/util/HorseStyle.java new file mode 100644 index 0000000..b5544c4 --- /dev/null +++ b/api/src/main/java/lol/pyr/znpcsplus/util/HorseStyle.java @@ -0,0 +1,9 @@ +package lol.pyr.znpcsplus.util; + +public enum HorseStyle { + NONE, + WHITE, + WHITEFIELD, + WHITE_DOTS, + BLACK_DOTS +} diff --git a/api/src/main/java/lol/pyr/znpcsplus/util/HorseType.java b/api/src/main/java/lol/pyr/znpcsplus/util/HorseType.java new file mode 100644 index 0000000..9526d67 --- /dev/null +++ b/api/src/main/java/lol/pyr/znpcsplus/util/HorseType.java @@ -0,0 +1,9 @@ +package lol.pyr.znpcsplus.util; + +public enum HorseType { + HORSE, + DONKEY, + MULE, + ZOMBIE, + SKELETON +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java index 77cf676..d414ead 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/ZNpcsPlus.java @@ -253,6 +253,7 @@ public class ZNpcsPlus extends JavaPlugin { manager.registerParser(Color.class, new ColorParser(incorrectUsageMessage)); manager.registerParser(Vector3f.class, new Vector3fParser(incorrectUsageMessage)); + // TODO: Need to find a better way to do this registerEnumParser(manager, NpcPose.class, incorrectUsageMessage); registerEnumParser(manager, DyeColor.class, incorrectUsageMessage); registerEnumParser(manager, CatVariant.class, incorrectUsageMessage); @@ -265,6 +266,10 @@ public class ZNpcsPlus extends JavaPlugin { registerEnumParser(manager, VillagerProfession.class, incorrectUsageMessage); registerEnumParser(manager, VillagerLevel.class, incorrectUsageMessage); registerEnumParser(manager, AxolotlVariant.class, incorrectUsageMessage); + registerEnumParser(manager, HorseType.class, incorrectUsageMessage); + registerEnumParser(manager, HorseStyle.class, incorrectUsageMessage); + registerEnumParser(manager, HorseColor.class, incorrectUsageMessage); + registerEnumParser(manager, HorseArmor.class, incorrectUsageMessage); manager.registerCommand("npc", new MultiCommand(loadHelpMessage("root")) .addSubcommand("create", new CreateCommand(npcRegistry, typeRegistry)) 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 ed29403..56d8788 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/EntityPropertyRegistryImpl.java @@ -59,6 +59,11 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { registerEnumSerializer(VillagerProfession.class); registerEnumSerializer(VillagerLevel.class); registerEnumSerializer(AxolotlVariant.class); + registerEnumSerializer(HorseType.class); + registerEnumSerializer(HorseColor.class); + registerEnumSerializer(HorseStyle.class); + registerEnumSerializer(HorseArmor.class); + /* registerType("using_item", false); // TODO: fix it for 1.8 and add new property to use offhand item and riptide animation @@ -97,11 +102,6 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { // Sniffer registerType("sniffer_state", null); // TODO: Nothing on wiki.vg, look in mc source - // Horse - registerType("horse_style", 0); // TODO: Figure this out - registerType("horse_chest", false); // TODO - registerType("horse_saddle", false); // TODO - // LLama registerType("carpet_color", DyeColor.class); // TODO registerType("llama_variant", 0); // TODO @@ -294,6 +294,56 @@ public class EntityPropertyRegistryImpl implements EntityPropertyRegistry { register(new EncodedIntegerProperty<>("creeper_state", CreeperState.IDLE, creeperIndex++, CreeperState::getState)); register(new BooleanProperty("creeper_charged", creeperIndex, false, legacyBooleans)); + // Abstract Horse + int horseIndex; + if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) horseIndex = 17; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) horseIndex = 16; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) horseIndex = 15; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) horseIndex = 13; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) horseIndex = 12; + else horseIndex = 16; + int horseEating = ver.isNewerThanOrEquals(ServerVersion.V_1_12) ? 0x10 : 0x20; + boolean v1_8 = ver.isOlderThan(ServerVersion.V_1_9); + register(new BitsetProperty("is_tame", horseIndex, 0x02, false, v1_8)); + register(new BitsetProperty("is_saddled", horseIndex, 0x04, false, v1_8)); + register(new BitsetProperty("is_eating", horseIndex, horseEating, false, v1_8)); + register(new BitsetProperty("is_rearing", horseIndex, horseEating << 1, false, v1_8)); + register(new BitsetProperty("has_mouth_open", horseIndex, horseEating << 2, false, v1_8)); + + // Horse + if (ver.isNewerThanOrEquals(ServerVersion.V_1_8) && ver.isOlderThan(ServerVersion.V_1_9)) { + register(new EncodedByteProperty<>("horse_type", HorseType.HORSE, 19, obj -> (byte) obj.ordinal())); + } else if (ver.isOlderThan(ServerVersion.V_1_11)) { + int horseTypeIndex = 14; + if (ver.isOlderThan(ServerVersion.V_1_10)) horseTypeIndex = 13; + register(new EncodedIntegerProperty<>("horse_type", HorseType.HORSE, horseTypeIndex, Enum::ordinal)); + } + int horseVariantIndex; + if (ver.isNewerThanOrEquals(ServerVersion.V_1_18)) horseVariantIndex = 18; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_17)) horseVariantIndex = 19; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_15)) horseVariantIndex = 18; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_14)) horseVariantIndex = 17; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_10)) horseVariantIndex = 15; + else if (ver.isNewerThanOrEquals(ServerVersion.V_1_9)) horseVariantIndex = 14; + else horseVariantIndex = 20; + register(new HorseStyleProperty(horseVariantIndex)); + register(new HorseColorProperty(horseVariantIndex)); + linkProperties("horse_style", "horse_color"); + + // Use chesteplate property for 1.14 and above + if (ver.isOlderThan(ServerVersion.V_1_14)) { + register(new EncodedIntegerProperty<>("horse_armor", HorseArmor.NONE, horseVariantIndex + 2, Enum::ordinal)); + } + + // Chested Horse + if (ver.isOlderThan(ServerVersion.V_1_11)) { + register(new BitsetProperty("has_chest", horseIndex, 0x08, false, v1_8)); + linkProperties("is_saddled", "has_chest", "is_eating", "is_rearing", "has_mouth_open"); + } else { + register(new BooleanProperty("has_chest", horseVariantIndex, false, legacyBooleans)); + linkProperties("is_saddled", "is_eating", "is_rearing", "has_mouth_open"); + } + if (!ver.isNewerThanOrEquals(ServerVersion.V_1_14)) return; // Pose register(new NpcPoseProperty()); diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/BitsetProperty.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/BitsetProperty.java index 579074e..1a1e5f2 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/BitsetProperty.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/BitsetProperty.java @@ -12,6 +12,12 @@ public class BitsetProperty extends EntityPropertyImpl { private final int index; private final int bitmask; private final boolean inverted; + private boolean integer = false; + + public BitsetProperty(String name, int index, int bitmask, boolean inverted, boolean integer) { + this(name, index, bitmask, inverted); + this.integer = integer; + } public BitsetProperty(String name, int index, int bitmask, boolean inverted) { super(name, inverted, Boolean.class); @@ -27,9 +33,11 @@ public class BitsetProperty extends EntityPropertyImpl { @Override public void apply(Player player, PacketEntity entity, boolean isSpawned, Map properties) { EntityData oldData = properties.get(index); - byte oldValue = oldData == null ? 0 : (byte) oldData.getValue(); boolean enabled = entity.getProperty(this); if (inverted) enabled = !enabled; - properties.put(index, newEntityData(index, EntityDataTypes.BYTE, (byte) (oldValue | (enabled ? bitmask : 0)))); + properties.put(index, + integer ? newEntityData(index, EntityDataTypes.INT, (oldData == null ? 0 : (int) oldData.getValue()) | (enabled ? bitmask : 0)) : + newEntityData(index, EntityDataTypes.BYTE, (byte) ((oldData == null ? 0 : (byte) oldData.getValue()) | (enabled ? bitmask : 0)))); + } } diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/EncodedByteProperty.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/EncodedByteProperty.java new file mode 100644 index 0000000..af64b11 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/EncodedByteProperty.java @@ -0,0 +1,48 @@ +package lol.pyr.znpcsplus.entity.properties; + +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.protocol.entity.data.EntityDataType; +import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import lol.pyr.znpcsplus.entity.EntityPropertyImpl; +import lol.pyr.znpcsplus.entity.PacketEntity; +import org.bukkit.entity.Player; + +import java.util.Map; + +public class EncodedByteProperty extends EntityPropertyImpl { + private final EntityDataType type; + private final ByteDecoder decoder; + private final int index; + + protected EncodedByteProperty(String name, T defaultValue, Class clazz, int index, ByteDecoder decoder, EntityDataType type) { + super(name, defaultValue, clazz); + this.decoder = decoder; + this.index = index; + this.type = type; + } + + @SuppressWarnings("unchecked") + public EncodedByteProperty(String name, T defaultValue, int index, ByteDecoder decoder) { + this(name, defaultValue, (Class) defaultValue.getClass(), index, decoder, EntityDataTypes.BYTE); + } + + @SuppressWarnings("unchecked") + public EncodedByteProperty(String name, T defaultValue, int index, ByteDecoder decoder, EntityDataType type) { + this(name, defaultValue, (Class) defaultValue.getClass(), index, decoder, type); + } + + public EncodedByteProperty(String name, Class clazz, int index, ByteDecoder decoder) { + this(name, null, clazz, index, decoder, EntityDataTypes.BYTE); + } + + @Override + public void apply(Player player, PacketEntity entity, boolean isSpawned, Map properties) { + T value = entity.getProperty(this); + if (value == null) return; + properties.put(index, newEntityData(index, type, decoder.decode(value))); + } + + public interface ByteDecoder { + byte decode(T obj); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HorseColorProperty.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HorseColorProperty.java new file mode 100644 index 0000000..b7b12ed --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HorseColorProperty.java @@ -0,0 +1,26 @@ +package lol.pyr.znpcsplus.entity.properties; + +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import lol.pyr.znpcsplus.entity.EntityPropertyImpl; +import lol.pyr.znpcsplus.entity.PacketEntity; +import lol.pyr.znpcsplus.util.HorseColor; +import org.bukkit.entity.Player; + +import java.util.Map; + +public class HorseColorProperty extends EntityPropertyImpl { + private final int index; + + public HorseColorProperty(int index) { + super("horse_color", HorseColor.WHITE, HorseColor.class); + this.index = index; + } + + @Override + public void apply(Player player, PacketEntity entity, boolean isSpawned, Map properties) { + EntityData oldData = properties.get(index); + HorseColor value = entity.getProperty(this); + properties.put(index, newEntityData(index, EntityDataTypes.INT, value.ordinal() | (oldData == null ? 0 : ((int) oldData.getValue() & 0xFF00)))); + } +} diff --git a/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HorseStyleProperty.java b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HorseStyleProperty.java new file mode 100644 index 0000000..ebb1b53 --- /dev/null +++ b/plugin/src/main/java/lol/pyr/znpcsplus/entity/properties/HorseStyleProperty.java @@ -0,0 +1,26 @@ +package lol.pyr.znpcsplus.entity.properties; + +import com.github.retrooper.packetevents.protocol.entity.data.EntityData; +import com.github.retrooper.packetevents.protocol.entity.data.EntityDataTypes; +import lol.pyr.znpcsplus.entity.EntityPropertyImpl; +import lol.pyr.znpcsplus.entity.PacketEntity; +import lol.pyr.znpcsplus.util.HorseStyle; +import org.bukkit.entity.Player; + +import java.util.Map; + +public class HorseStyleProperty extends EntityPropertyImpl { + private final int index; + + public HorseStyleProperty(int index) { + super("horse_style", HorseStyle.NONE, HorseStyle.class); + this.index = index; + } + + @Override + public void apply(Player player, PacketEntity entity, boolean isSpawned, Map properties) { + EntityData oldData = properties.get(index); + HorseStyle value = entity.getProperty(this); + properties.put(index, newEntityData(index, EntityDataTypes.INT, (oldData == null ? 0 : ((int) oldData.getValue() & 0x00FF)) | (value.ordinal() << 8))); + } +} 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 2c8f05c..5a9131e 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeImpl.java @@ -91,11 +91,24 @@ public class NpcTypeImpl implements NpcType { addProperties("fire", "invisible", "silent", "look", "using_item", "potion_color", "potion_ambient", "dinnerbone"); if (version.isNewerThanOrEquals(ServerVersion.V_1_9)) addProperties("glow"); - if (version.isNewerThanOrEquals(ServerVersion.V_1_14)) addProperties("pose"); + if (version.isNewerThanOrEquals(ServerVersion.V_1_14)) { + addProperties("pose"); + if (EntityTypes.isTypeInstanceOf(type, EntityTypes.HORSE)) { + addProperties("chestplate"); + } + } if (version.isNewerThanOrEquals(ServerVersion.V_1_17)) addProperties("shaking"); if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_AGEABLE)) { addProperties("baby"); } + if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_HORSE)) { + addProperties("is_saddled", "is_eating", "is_rearing", "has_mouth_open"); + } + if (EntityTypes.isTypeInstanceOf(type, EntityTypes.CHESTED_HORSE)) { + addProperties("has_chest"); + } else if (version.isOlderThan(ServerVersion.V_1_11) && type.equals(EntityTypes.HORSE)) { + addProperties("has_chest"); + } return new NpcTypeImpl(name, type, hologramOffset, new HashSet<>(allowedProperties)); } } 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 ba761c9..6835ead 100644 --- a/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeRegistryImpl.java +++ b/plugin/src/main/java/lol/pyr/znpcsplus/npc/NpcTypeRegistryImpl.java @@ -88,7 +88,8 @@ public class NpcTypeRegistryImpl implements NpcTypeRegistry { .setHologramOffset(-1.125)); register(builder(p, "horse", EntityTypes.HORSE) - .setHologramOffset(-0.375)); + .setHologramOffset(-0.375) + .addProperties("horse_type", "horse_style", "horse_color", "horse_armor")); register(builder(p, "iron_golem", EntityTypes.IRON_GOLEM) .setHologramOffset(0.725));