Skip to content

Cow Variants #7866

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/main/java/ch/njol/skript/classes/data/BukkitClasses.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import ch.njol.skript.classes.*;
import ch.njol.skript.classes.registry.RegistryClassInfo;
import ch.njol.skript.entity.ChickenData.ChickenVariantDummy;
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;
Expand Down Expand Up @@ -1597,7 +1598,27 @@ public String toVariableNameString(WorldBorder border) {
.requiredPlugins("Minecraft 1.21.5+")
.documentationId("ChickenVariant")
);


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

Classes.registerClass(new EnumClassInfo<>(VillagerCareerChangeEvent.ChangeReason.class, "villagercareerchangereason", "villager career change reasons")
.user("(villager )?career ?change ?reasons?")
.name("Villager Career Change Reason")
Expand Down
176 changes: 176 additions & 0 deletions src/main/java/ch/njol/skript/entity/CowData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
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<Cow> {

private static final boolean VARIANTS_ENABLED;
private static final Object[] variants;
private static final Class<Cow> COW_CLASS;
private static final @Nullable Method getVariantMethod;
private static final @Nullable Method setVariantMethod;

static {
Class<Cow> cowClass = null;

try {
//noinspection unchecked
cowClass = (Class<Cow>) Class.forName("org.bukkit.entity.Cow");
} catch (Exception ignored) {}

COW_CLASS = cowClass;
register(CowData.class, "cow", COW_CLASS, 0, "cow");
if (Skript.classExists("org.bukkit.entity.Cow$Variant")) {
VARIANTS_ENABLED = true;
variants = Iterators.toArray(Classes.getExactClassInfo(Cow.Variant.class).getSupplier().get(), Cow.Variant.class);
try {
getVariantMethod = COW_CLASS.getDeclaredMethod("getVariant");
setVariantMethod = COW_CLASS.getDeclaredMethod("setVariant", Cow.Variant.class);
} catch (Exception e) {
throw new RuntimeException("Could not retrieve get/set variant methods for Cow.", e);
}
} else {
VARIANTS_ENABLED = false;
variants = null;
getVariantMethod = null;
setVariantMethod = null;
}
}

private @Nullable Object variant = null;

public CowData() {}

// TODO: When the api-version is 1.21.5, 'variant' should have the type changed to 'Cow.Variant' and reflection can be removed
public CowData(@Nullable Object variant) {
this.variant = variant;
}

@Override
protected boolean init(Literal<?>[] exprs, int matchedPattern, ParseResult parseResult) {
if (VARIANTS_ENABLED) {
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<Cow.Variant>) expr).getSingle();
}
return true;
}

@Override
protected boolean init(@Nullable Class<? extends Cow> entityClass, @Nullable Cow cow) {
if (cow != null && VARIANTS_ENABLED) {
variant = getVariant(cow);
}
return true;
}

@Override
public void set(Cow entity) {
if (!VARIANTS_ENABLED)
return;
Object finalVariant = variant != null ? variant : CollectionUtils.getRandom(variants);
assert finalVariant != null;
setVariant(entity, finalVariant);
}

@Override
protected boolean match(Cow entity) {
return variant == null || getVariant(entity) == variant;
}

@Override
public Class<Cow> getType() {
return COW_CLASS;
}

@Override
public EntityData<Cow> 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;
return variant == other.variant;
}

@Override
public boolean isSupertypeOf(EntityData<?> entityData) {
if (!(entityData instanceof CowData other))
return false;
return variant == null || variant == other.variant;
}

/**
* 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 (!VARIANTS_ENABLED || setVariantMethod == null)
return;
Entity entity = COW_CLASS.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 (!VARIANTS_ENABLED || getVariantMethod == null)
return null;
Entity entity = COW_CLASS.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 {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ private static void addSuperEntity(String codeName, Class<? extends Entity> enti
addSimpleEntity("tipped arrow", TippedArrow.class);
addSimpleEntity("blaze", Blaze.class);
addSimpleEntity("mooshroom", MushroomCow.class);
addSimpleEntity("cow", Cow.class);
addSimpleEntity("cave spider", CaveSpider.class);
addSimpleEntity("dragon fireball", DragonFireball.class);
addSimpleEntity("egg", Egg.class);
Expand Down
9 changes: 8 additions & 1 deletion src/main/resources/lang/default.lang
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ entities:
pattern: <age> [%-chickenvariant%] chicken[1:s]|(4:)[%-chickenvariant%] chick[1:s]
cow:
name: cow¦s
pattern: <age> cow(|1¦s)|(4¦)cal(f|1¦ves)
pattern: <age> [%-cowvariant%] cow[1:s]|(4:)[%-cowvariant%] cal(f|1:ves)
cave spider:
name: cave spider¦s
pattern: cave[ ]spider(|1¦s)
Expand Down Expand Up @@ -2604,6 +2604,12 @@ chicken variants:
temperate: temperate
warm: warm

# -- Cow Variants --
cow variants:
cold: cold
temperate: temperate
warm: warm

# -- Villager Career Change Reasons
villager career change reasons:
employed: employment
Expand Down Expand Up @@ -2714,6 +2720,7 @@ types:
equipmentslot: equipment slot¦s @an
pigvariant: pig variant¦s @a
chickenvariant: chicken variant¦s @a
cowvariant: cow variant¦s @a
villagercareerchangereason: villager career change reason¦s @a

# Skript
Expand Down
42 changes: 42 additions & 0 deletions src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,45 @@ test "spawn baby chicken by variant" when running minecraft "1.21.5":
assert size of all chicks is 5 with "Only temperate chicks should have been deleted"
delete all warm chicks
assert size of all warm chicks is 0 with "Warm chicks 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"

test "spawn baby cow by variant" when running minecraft "1.21.5":
delete all calves
set {_l} to test-location
spawn 5 warm calves at {_l}
assert size of all warm calves is 5 with "Size of all warm calves is not 5"
assert size of all calves is 5 with "Size of all calves is not 5"
spawn 3 temperate calves at {_l}
assert size of all temperate calves is 3 with "Size of all temperate calves is not 3"
assert size of all calves is 8 with "Size of all calves is not 8"
spawn 2 cold calves at {_l}
assert size of all cold calves is 2 with "Size of all cold calves is not 2"
assert size of all calves is 10 with "Size of all calves is not 10"
delete all cold calves
assert size of all cold calves is 0 with "Cold calves were not deleted"
assert size of all calves is 8 with "Only cold calves should have been deleted"
delete all temperate calves
assert size of all temperate calves is 0 with "Temperate calves were not deleted"
assert size of all calves is 5 with "Only temperate calves should have been deleted"
delete all warm calves
assert size of all warm calves is 0 with "Warm calves were not deleted"