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 81b3beab652..6acf2d9be75 100644
--- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java
+++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java
@@ -1542,6 +1542,19 @@ public String toVariableNameString(EnchantmentOffer eo) {
.description("Represents a change reason of an experience cooldown change event.")
.since("INSERT VERSION"));
+ Classes.registerClass(new RegistryClassInfo<>(Villager.Type.class, Registry.VILLAGER_TYPE, "villagertype", "villager types")
+ .user("villager ?types?")
+ .name("Villager Type")
+ .description("Represents the different types of villagers. These are usually the biomes a villager can be from.")
+ .after("biome")
+ .since("INSERT VERSION"));
+
+ Classes.registerClass(new RegistryClassInfo<>(Villager.Profession.class, Registry.VILLAGER_PROFESSION, "villagerprofession", "villager professions")
+ .user("villager ?professions?")
+ .name("Villager Profession")
+ .description("Represents the different professions of villagers.")
+ .since("INSERT VERSION"));
+
if (Skript.classExists("org.bukkit.entity.EntitySnapshot")) {
Classes.registerClass(new ClassInfo<>(EntitySnapshot.class, "entitysnapshot")
.user("entity ?snapshots?")
@@ -1554,6 +1567,7 @@ public String toVariableNameString(EnchantmentOffer eo) {
.since("INSERT VERSION")
);
}
+
}
}
diff --git a/src/main/java/ch/njol/skript/expressions/ExprVillagerLevel.java b/src/main/java/ch/njol/skript/expressions/ExprVillagerLevel.java
new file mode 100644
index 00000000000..d57be3c4fc5
--- /dev/null
+++ b/src/main/java/ch/njol/skript/expressions/ExprVillagerLevel.java
@@ -0,0 +1,92 @@
+package ch.njol.skript.expressions;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.classes.Changer.ChangeMode;
+import ch.njol.skript.doc.Description;
+import ch.njol.skript.doc.Examples;
+import ch.njol.skript.doc.Name;
+import ch.njol.skript.doc.Since;
+import ch.njol.skript.expressions.base.SimplePropertyExpression;
+import ch.njol.util.Math2;
+import ch.njol.util.coll.CollectionUtils;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Villager;
+import org.bukkit.event.Event;
+import org.jetbrains.annotations.Nullable;
+
+@Name("Villager Level")
+@Description({
+ "Represents the level of a villager.",
+ "Level must be between 1 and 5, with 1 being the default level.",
+ "Do note when a villager's level is 1, they may lose their profession."})
+@Examples({
+ "set {_level} to villager level of {_villager}",
+ "set villager level of last spawned villager to 2",
+ "add 1 to villager level of target entity",
+ "remove 1 from villager level of event-entity",
+ "reset villager level of event-entity"
+})
+@Since("INSERT VERSION")
+public class ExprVillagerLevel extends SimplePropertyExpression {
+
+ private static final boolean HAS_INCREASE_METHOD = Skript.methodExists(Villager.class, "increaseLevel", int.class);
+
+ static {
+ register(ExprVillagerLevel.class, Number.class, "villager level", "livingentities");
+ }
+
+ @Override
+ public @Nullable Number convert(LivingEntity from) {
+ if (from instanceof Villager villager)
+ return villager.getVillagerLevel();
+ return null;
+ }
+
+ @Override
+ public Class> @Nullable [] acceptChange(ChangeMode mode) {
+ return switch (mode) {
+ case SET, ADD, REMOVE, RESET ->
+ CollectionUtils.array(Number.class);
+ default -> null;
+ };
+ }
+
+ @Override
+ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) {
+ Number number = delta != null && delta[0] instanceof Number num ? num : 1;
+ int changeValue = number.intValue();
+
+ for (LivingEntity livingEntity : getExpr().getArray(event)) {
+ if (!(livingEntity instanceof Villager villager)) continue;
+
+ int previousLevel = villager.getVillagerLevel();
+ int newLevel = switch (mode) {
+ case SET -> changeValue;
+ case ADD -> previousLevel + changeValue;
+ case REMOVE -> previousLevel - changeValue;
+ default -> 1;
+ };
+ newLevel = Math2.fit(1, newLevel, 5);
+ if (newLevel > previousLevel && HAS_INCREASE_METHOD) {
+ int increase = Math2.fit(1, newLevel - previousLevel, 5);
+ // According to the docs for this method:
+ // Increases the level of this villager.
+ // The villager will also unlock new recipes unlike the raw 'setVillagerLevel' method
+ villager.increaseLevel(increase);
+ } else {
+ villager.setVillagerLevel(newLevel);
+ }
+ }
+ }
+
+ @Override
+ protected String getPropertyName() {
+ return "villager level";
+ }
+
+ @Override
+ public Class extends Number> getReturnType() {
+ return Number.class;
+ }
+
+}
diff --git a/src/main/java/ch/njol/skript/expressions/ExprVillagerProfession.java b/src/main/java/ch/njol/skript/expressions/ExprVillagerProfession.java
new file mode 100644
index 00000000000..65eb4bc7827
--- /dev/null
+++ b/src/main/java/ch/njol/skript/expressions/ExprVillagerProfession.java
@@ -0,0 +1,66 @@
+package ch.njol.skript.expressions;
+
+import ch.njol.skript.classes.Changer.ChangeMode;
+import ch.njol.skript.doc.Description;
+import ch.njol.skript.doc.Examples;
+import ch.njol.skript.doc.Name;
+import ch.njol.skript.doc.Since;
+import ch.njol.skript.expressions.base.SimplePropertyExpression;
+import ch.njol.util.coll.CollectionUtils;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Villager;
+import org.bukkit.entity.Villager.Profession;
+import org.bukkit.event.Event;
+import org.jetbrains.annotations.Nullable;
+
+@Name("Villager Profession")
+@Description("Represents the profession of a villager.")
+@Examples({
+ "set {_p} to villager profession of event-entity",
+ "villager profession of event-entity = nitwit profession",
+ "set villager profession of {_villager} to librarian profession",
+ "delete villager profession of event-entity"
+})
+@Since("INSERT VERSION")
+public class ExprVillagerProfession extends SimplePropertyExpression {
+
+ static {
+ register(ExprVillagerProfession.class, Profession.class, "villager profession", "livingentities");
+ }
+
+ @Override
+ public @Nullable Profession convert(LivingEntity from) {
+ if (from instanceof Villager villager)
+ return villager.getProfession();
+ return null;
+ }
+
+ @Override
+ public Class> @Nullable [] acceptChange(ChangeMode mode) {
+ return switch (mode) {
+ case SET, DELETE -> CollectionUtils.array(Profession.class);
+ default -> null;
+ };
+ }
+
+ @Override
+ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) {
+ Profession profession = delta != null && delta[0] instanceof Profession pro ? pro : Profession.NONE;
+
+ for (LivingEntity livingEntity : getExpr().getArray(event)) {
+ if (livingEntity instanceof Villager villager)
+ villager.setProfession(profession);
+ }
+ }
+
+ @Override
+ protected String getPropertyName() {
+ return "villager profession";
+ }
+
+ @Override
+ public Class extends Profession> getReturnType() {
+ return Profession.class;
+ }
+
+}
diff --git a/src/main/java/ch/njol/skript/expressions/ExprVillagerType.java b/src/main/java/ch/njol/skript/expressions/ExprVillagerType.java
new file mode 100644
index 00000000000..f29da4972ba
--- /dev/null
+++ b/src/main/java/ch/njol/skript/expressions/ExprVillagerType.java
@@ -0,0 +1,66 @@
+package ch.njol.skript.expressions;
+
+import ch.njol.skript.classes.Changer.ChangeMode;
+import ch.njol.skript.doc.Description;
+import ch.njol.skript.doc.Examples;
+import ch.njol.skript.doc.Name;
+import ch.njol.skript.doc.Since;
+import ch.njol.skript.expressions.base.SimplePropertyExpression;
+import ch.njol.util.coll.CollectionUtils;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Villager;
+import org.bukkit.entity.Villager.Type;
+import org.bukkit.event.Event;
+import org.jetbrains.annotations.Nullable;
+
+@Name("Villager Type")
+@Description("Represents the type of a villager. This usually represents the biome the villager is from.")
+@Examples({
+ "set {_type} to villager type of {_villager}",
+ "villager type of {_villager} = plains",
+ "set villager type of event-entity to plains"
+})
+@Since("INSERT VERSION")
+public class ExprVillagerType extends SimplePropertyExpression {
+
+ static {
+ register(ExprVillagerType.class, Type.class, "villager type", "livingentities");
+ }
+
+ @Override
+ public @Nullable Type convert(LivingEntity from) {
+ if (from instanceof Villager villager)
+ return villager.getVillagerType();
+ return null;
+ }
+
+ @Override
+ public Class> @Nullable [] acceptChange(ChangeMode mode) {
+ if (mode == ChangeMode.SET)
+ return CollectionUtils.array(Type.class);
+ return null;
+ }
+
+ @Override
+ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) {
+ Type type = delta != null && delta[0] instanceof Type t ? t : null;
+ if (type == null)
+ return;
+
+ for (LivingEntity livingEntity : getExpr().getArray(event)) {
+ if (livingEntity instanceof Villager villager)
+ villager.setVillagerType(type);
+ }
+ }
+
+ @Override
+ protected String getPropertyName() {
+ return "villager type";
+ }
+
+ @Override
+ public Class extends Type> getReturnType() {
+ return Type.class;
+ }
+
+}
diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang
index 7ce573c244a..c35f69dfefc 100644
--- a/src/main/resources/lang/default.lang
+++ b/src/main/resources/lang/default.lang
@@ -2439,6 +2439,34 @@ item display transforms:
thirdperson_lefthand: third person left handed, third person left hand, left handed in third person
thirdperson_righthand: third person right handed, third person right hand, right handed in third person
+# -- Villager Types/Professions --
+villager types:
+ snow: snow
+ plains: plains
+ jungle: jungle
+ taiga: taiga
+ desert: desert
+ savanna: savanna
+ swamp: swamp
+
+villager professions:
+ # Suffix "profession" options added to help with EntityData conflicts
+ leatherworker: leatherworker, leatherworker profession
+ mason: mason, mason profession
+ fletcher: fletcher, fletcher profession
+ weaponsmith: weaponsmith, weaponsmith profession
+ toolsmith: toolsmith, toolsmith profession
+ librarian: librarian, librarian profession
+ shepherd: shepherd, shepherd profession
+ farmer: farmer, farmer profession
+ cleric: cleric, cleric profession
+ nitwit: nitwit, nitwit profession
+ cartographer: cartographer, cartographer profession
+ armorer: armorer, armorer profession
+ butcher: butcher, butcher profession
+ none: no profession, none profession, unemployed
+ fisherman: fisherman, fisherman profession
+
# -- Change Reasons --
experience cooldown change reasons:
plugin: plugin
@@ -2542,6 +2570,8 @@ types:
itemdisplaytransform: item display transform¦s @an
experiencecooldownchangereason: experience cooldown change reason¦s @a
inputkey: input key¦s @an
+ villagertype: villager type¦s @a
+ villagerprofession: villager profession¦s @a
entitysnapshot: entity snapshot¦s @an
loottable: loot table¦s @a
lootcontext: loot context¦s @a
diff --git a/src/test/skript/tests/syntaxes/expressions/ExprVillagerLevel.sk b/src/test/skript/tests/syntaxes/expressions/ExprVillagerLevel.sk
new file mode 100644
index 00000000000..2884356cfe0
--- /dev/null
+++ b/src/test/skript/tests/syntaxes/expressions/ExprVillagerLevel.sk
@@ -0,0 +1,18 @@
+test "villager level expression":
+ spawn a villager at event-location:
+ set {_e} to entity
+
+ assert villager level of {_e} = 1 with "Villager should start out with a level of 1"
+ set villager level of {_e} to 2
+ assert villager level of {_e} = 2 with "Villager level should now be 2"
+ add 2 to villager level of {_e}
+ assert villager level of {_e} = 4 with "Villager level should now be 4"
+ add 5 to villager level of {_e}
+ assert villager level of {_e} = 5 with "Villager level is capped at 5, and should now be 5"
+ remove 2 from villager level of {_e}
+ assert villager level of {_e} = 3 with "Villager level should be 3 now"
+ reset villager level of {_e}
+ assert villager level of {_e} = 1 with "Villager level should reset back to 1"
+
+ # Thank you for your service
+ delete entity within {_e}
diff --git a/src/test/skript/tests/syntaxes/expressions/ExprVillagerProfession.sk b/src/test/skript/tests/syntaxes/expressions/ExprVillagerProfession.sk
new file mode 100644
index 00000000000..71e2250d6fe
--- /dev/null
+++ b/src/test/skript/tests/syntaxes/expressions/ExprVillagerProfession.sk
@@ -0,0 +1,14 @@
+test "villager profession expression":
+ spawn a librarian at event-location:
+ # Make sure he keeps his job
+ set villager level of entity to 2
+ set {_e} to entity
+
+ assert villager profession of {_e} = librarian profession with "The villager should have spawned as a librarian"
+ set villager profession of {_e} to nitwit profession
+ assert villager profession of {_e} = nitwit profession with "The villager should now be a nitwit"
+ delete villager profession of {_e}
+ assert villager profession of {_e} = no profession with "The villager should now have no profession"
+
+ # Thank you for your service
+ delete entity within {_e}
diff --git a/src/test/skript/tests/syntaxes/expressions/ExprVillagerType.sk b/src/test/skript/tests/syntaxes/expressions/ExprVillagerType.sk
new file mode 100644
index 00000000000..ebae093a5db
--- /dev/null
+++ b/src/test/skript/tests/syntaxes/expressions/ExprVillagerType.sk
@@ -0,0 +1,11 @@
+test "villager type expression":
+ spawn a villager at event-location:
+ set {_e} to event-entity
+
+ set villager type of {_e} to plains
+ assert villager type of {_e} = plains with "The villager should now have the type plains"
+ set villager type of {_e} to desert
+ assert villager type of {_e} = desert with "The villager should now have the type desert"
+
+ # Thank you for your service
+ delete entity within {_e}