diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 9508c640252..a50371f5111 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -10,6 +10,7 @@ import ch.njol.skript.bukkitutil.SkriptTeleportFlag; import ch.njol.skript.classes.*; import ch.njol.skript.classes.registry.RegistryClassInfo; +import ch.njol.skript.entity.CowData.CowVariantDummy; import ch.njol.skript.entity.EntityData; import ch.njol.skript.entity.PigData.PigVariantDummy; import ch.njol.skript.entity.WolfData.WolfVariantDummy; @@ -1576,6 +1577,26 @@ public String toVariableNameString(WorldBorder border) { .requiredPlugins("Minecraft 1.21.5+") .documentationId("PigVariant")); + ClassInfo cowVariantClassInfo = getRegistryClassInfo( + "org.bukkit.entity.Cow$Variant", + "COW_VARIANT", + "cowvariant", + "cow variants" + ); + if (cowVariantClassInfo == null) { + // Registers a dummy/placeholder class to ensure working operation on MC versions that do not have 'Cow.Variant' (1.21.4-) + cowVariantClassInfo = new ClassInfo<>(CowVariantDummy.class, "cowvariant"); + } + Classes.registerClass(cowVariantClassInfo + .user("cow ?variants?") + .name("Cow Variant") + .description("Represents the variant of a cow entity.", + "NOTE: Minecraft namespaces are supported, ex: 'minecraft:warm'.") + .since("INSERT VERSION") + .requiredPlugins("Minecraft 1.21.5+") + .documentationId("CowVariant") + ); + } /** diff --git a/src/main/java/ch/njol/skript/entity/CowData.java b/src/main/java/ch/njol/skript/entity/CowData.java new file mode 100644 index 00000000000..e89f74710e0 --- /dev/null +++ b/src/main/java/ch/njol/skript/entity/CowData.java @@ -0,0 +1,184 @@ +package ch.njol.skript.entity; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.registrations.Classes; +import ch.njol.util.coll.CollectionUtils; +import com.google.common.collect.Iterators; +import org.bukkit.entity.Cow; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Objects; + +public class CowData extends EntityData { + + private static final boolean variantsEnabled; + private static final Object[] variants; + private static final Class cowClass; + private static final @Nullable Method getVariantMethod; + private static final @Nullable Method setVariantMethod; + + static { + Class cowClass1 = null; + + try { + //noinspection unchecked + cowClass1 = (Class) Class.forName("org.bukkit.entity.Cow"); + } catch (Exception ignored) {} + + cowClass = cowClass1; + register(CowData.class, "cow", cowClass, 0, "cow"); + if (Skript.classExists("org.bukkit.entity.Cow$Variant")) { + variantsEnabled = true; + variants = Iterators.toArray(Classes.getExactClassInfo(Cow.Variant.class).getSupplier().get(), Cow.Variant.class); + try { + getVariantMethod = cowClass.getDeclaredMethod("getVariant"); + setVariantMethod = cowClass.getDeclaredMethod("setVariant", Cow.Variant.class); + } catch (Exception e) { + throw new RuntimeException("Could not retrieve get/set variant methods for Cow.", e); + } + } else { + variantsEnabled = false; + variants = null; + getVariantMethod = null; + setVariantMethod = null; + } + } + + private @Nullable Object variant = null; + + public CowData() {} + + // TODO: When safe, 'variant' should have the type changed to 'Cow.Variant' + public CowData(@Nullable Object variant) { + this.variant = variant; + } + + @Override + protected boolean init(Literal[] exprs, int matchedPattern, ParseResult parseResult) { + if (variantsEnabled) { + Literal expr = null; + if (exprs[0] != null) { // cow + expr = exprs[0]; + } else if (exprs[1] != null) { // calf + expr = exprs[1]; + } + if (expr != null) + //noinspection unchecked + variant = ((Literal) expr).getSingle(); + } + return true; + } + + @Override + protected boolean init(@Nullable Class entityClass, @Nullable Cow entity) { + if (entity != null) { + if (variantsEnabled) { + variant = getVariant(entity); + } + } + return true; + } + + @Override + public void set(Cow entity) { + if (variantsEnabled) { + Object finalVariant = variant != null ? variant : CollectionUtils.getRandom(variants); + assert finalVariant != null; + setVariant(entity, finalVariant); + } + } + + @Override + protected boolean match(Cow entity) { + if (!variantsEnabled) + return true; + return variant == null || getVariant(entity) == variant; + } + + @Override + public Class getType() { + return cowClass; + } + + @Override + public EntityData getSuperType() { + return new CowData(); + } + + @Override + protected int hashCode_i() { + return Objects.hashCode(variant); + } + + @Override + protected boolean equals_i(EntityData obj) { + if (!(obj instanceof CowData other)) + return false; + if (variantsEnabled && variant != other.variant) + return false; + return true; + } + + @Override + public boolean isSupertypeOf(EntityData entityData) { + if (!(entityData instanceof CowData other)) + return false; + if (variantsEnabled) + return variant == other.variant; + return true; + } + + /** + * Due to the addition of 'AbstractCow' and 'api-version' being '1.19' + * This helper method is required in order to set the {@link #variant} of the {@link Cow} + * @param cow The {@link Cow} to set the variant + */ + public void setVariant(Cow cow) { + setVariant(cow, variant); + } + + /** + * Due to the addition of 'AbstractCow' and 'api-version' being '1.19' + * This helper method is required in order to set the {@code object} of the {@link Cow} + * @param cow The {@link Cow} to set the variant + * @param object The 'Cow.Variant' + */ + public void setVariant(Cow cow, Object object) { + if (!variantsEnabled || setVariantMethod == null) + return; + Entity entity = cowClass.cast(cow); + try { + setVariantMethod.invoke(entity, (Cow.Variant) object); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + /** + * Due to the addition of 'AbstractCow' and 'api-version' being '1.19' + * This helper method is required in order to get the 'Cow.Variant' of the {@link Cow} + * @param cow The {@link Cow} to get the variant + * @return The 'Cow.Variant' + */ + public @Nullable Object getVariant(Cow cow) { + if (!variantsEnabled || getVariantMethod == null) + return null; + Entity entity = cowClass.cast(cow); + try { + return getVariantMethod.invoke(entity); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + /** + * A dummy/placeholder class to ensure working operation on MC versions that do not have 'Cow.Variant' + */ + public static class CowVariantDummy {} + +} diff --git a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java index 0fbb0ee4605..0fa91eabc01 100644 --- a/src/main/java/ch/njol/skript/entity/SimpleEntityData.java +++ b/src/main/java/ch/njol/skript/entity/SimpleEntityData.java @@ -80,7 +80,6 @@ private static void addSuperEntity(String codeName, Class enti addSimpleEntity("blaze", Blaze.class); addSimpleEntity("chicken", Chicken.class); addSimpleEntity("mooshroom", MushroomCow.class); - addSimpleEntity("cow", Cow.class); addSimpleEntity("cave spider", CaveSpider.class); addSimpleEntity("dragon fireball", DragonFireball.class); addSimpleEntity("egg", Egg.class); diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 6d63c7206d4..3ceec0ac6f7 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -547,7 +547,7 @@ entities: pattern: chicken(|1¦s)|(4¦)chick(|1¦s) cow: name: cow¦s - pattern: cow(|1¦s)|(4¦)cal(f|1¦ves) + pattern: [%-cowvariant%] cow[1:s]|(4:)[%-cowvariant%] cal(f|1:ves) cave spider: name: cave spider¦s pattern: cave[ ]spider(|1¦s) @@ -2592,6 +2592,12 @@ pig variants: temperate: temperate warm: warm +# -- Cow Variants -- +cow variants: + cold: cold + temperate: temperate + warm: warm + # -- Boolean -- boolean: true: @@ -2696,6 +2702,7 @@ types: fishingstate: fishing state¦s @a equipmentslot: equipment slot¦s @an pigvariant: pig variant¦s @a + cowvariant: cow variant¦s @a # Skript weathertype: weather type¦s @a diff --git a/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk b/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk index 72ceab475a0..6e24bb14b36 100644 --- a/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk +++ b/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk @@ -180,3 +180,24 @@ test "spawn pig by variant" when running minecraft "1.21.5": assert size of all pigs is 5 with "Only temperate pigs should have been deleted" delete all warm pigs assert size of all warm pigs is 0 with "Warm pigs were not deleted" + +test "spawn cow by variant" when running minecraft "1.21.5": + delete all cows + set {_l} to test-location + spawn 5 warm cows at {_l} + assert size of all warm cows is 5 with "Size of all warm cows is not 5" + assert size of all cows is 5 with "Size of all cows is not 5" + spawn 3 temperate cows at {_l} + assert size of all temperate cows is 3 with "Size of all temperate cows is not 3" + assert size of all cows is 8 with "Size of all cows is not 8" + spawn 2 cold cows at {_l} + assert size of all cold cows is 2 with "Size of all cold cows is not 2" + assert size of all cows is 10 with "Size of all cows is not 10" + delete all cold cows + assert size of all cold cows is 0 with "Cold cows were not deleted" + assert size of all cows is 8 with "Only cold cows should have been deleted" + delete all temperate cows + assert size of all temperate cows is 0 with "Temperate cows were not deleted" + assert size of all cows is 5 with "Only temperate cows should have been deleted" + delete all warm cows + assert size of all warm cows is 0 with "Warm cows were not deleted"