ARG_PARSERS = new ArrayList<>();
+
+ /**
+ * Register a new custom item.
+ *
+ * @param key The key of the item.
+ * @param item The item.
+ */
+ public static void registerCustomEntity(@NotNull final NamespacedKey key,
+ @NotNull final TestableEntity item) {
+ REGISTRY.put(key, item);
+ }
+
+ /**
+ * Register a new arg parser.
+ *
+ * @param parser The parser.
+ */
+ public static void registerArgParser(@NotNull final EntityArgParser parser) {
+ ARG_PARSERS.add(parser);
+ }
+
+ /**
+ * Remove an entity.
+ *
+ * @param key The key of the entity.
+ */
+ public static void removeCustomEntity(@NotNull final NamespacedKey key) {
+ REGISTRY.remove(key);
+ }
+
+ /**
+ * This is the backbone of the entire eco entity system.
+ *
+ * You can look up a TestableEntity for any type or custom entity,
+ * and it will return it with any modifiers passed as parameters.
+ *
+ * If you want to get an Entity instance from this, then just call
+ * {@link TestableEntity#spawn(Location)}.
+ *
+ * The advantages of the testable entity system are that there is the inbuilt
+ * {@link TestableEntity#matches(Entity)} - this allows to check if any entity
+ * is that testable entity; which may sound negligible, but actually it allows for
+ * much more power and flexibility. For example, you can have an entity with an
+ * extra metadata tag, extra lore lines, different display name - and it
+ * will still work as long as the test passes.
+ *
+ * Unlike {@link com.willfp.eco.core.items.Items#lookup(String)}, this can return
+ * null as there is no empty entity equivalent.
+ *
+ * @param key The lookup string.
+ * @return The testable entity, or null if not found.
+ */
+ @Nullable
+ public static TestableEntity lookup(@NotNull final String key) {
+ if (key.contains("?")) {
+ String[] options = key.split("\\?");
+ for (String option : options) {
+ TestableEntity lookup = lookup(option);
+ if (lookup != null) {
+ return lookup;
+ }
+ }
+
+ return null;
+ }
+
+ String[] args = StringUtils.parseTokens(key);
+
+ if (args.length == 0) {
+ return null;
+ }
+
+ TestableEntity entity;
+
+ String[] split = args[0].toLowerCase().split(":");
+
+ if (split.length == 1) {
+ EntityType type;
+ try {
+ type = EntityType.valueOf(args[0].toUpperCase());
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ entity = new SimpleTestableEntity(type);
+ } else {
+ String namespace = split[0];
+ String keyID = split[1];
+ NamespacedKey namespacedKey = NamespacedKeyUtils.create(namespace, keyID);
+
+ TestableEntity part = REGISTRY.get(namespacedKey);
+
+ if (part == null) {
+ return null;
+ }
+
+ entity = part;
+ }
+
+
+ String[] modifierArgs = Arrays.copyOfRange(args, 1, args.length);
+
+ List parseResults = new ArrayList<>();
+
+ for (EntityArgParser argParser : ARG_PARSERS) {
+ EntityArgParseResult result = argParser.parseArguments(modifierArgs);
+ if (result != null) {
+ parseResults.add(result);
+ }
+ }
+
+ Function spawner = entity::spawn;
+
+ if (!parseResults.isEmpty()) {
+ entity = new ModifiedTestableEntity(
+ entity,
+ test -> {
+ for (EntityArgParseResult parseResult : parseResults) {
+ if (!parseResult.test().test(test)) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+ location -> {
+ Entity spawned = spawner.apply(location);
+
+ for (EntityArgParseResult parseResult : parseResults) {
+ parseResult.modifier().accept(spawned);
+ }
+
+ return spawned;
+ }
+ );
+ }
+
+ return entity;
+ }
+
+
+ /**
+ * Get a Testable Entity from an ItemStack.
+ *
+ * Will search for registered entity first. If there are no matches in the registry,
+ * then it will return a {@link com.willfp.eco.core.entities.impl.SimpleTestableEntity} matching the entity type.
+ *
+ * If the entity is not custom and has unknown type, this will return null.
+ *
+ * @param entity The Entity.
+ * @return The found Testable Entity.
+ */
+ @Nullable
+ public static TestableEntity getEntity(@Nullable final Entity entity) {
+ if (entity == null) {
+ return null;
+ }
+
+ TestableEntity customEntity = getEntity(entity);
+
+ if (customEntity != null) {
+ return customEntity;
+ }
+
+ for (TestableEntity known : REGISTRY.values()) {
+ if (known.matches(entity)) {
+ return known;
+ }
+ }
+
+ if (entity.getType() == EntityType.UNKNOWN) {
+ return null;
+ }
+
+ return new SimpleTestableEntity(entity.getType());
+ }
+
+ /**
+ * Get if entity is a custom entity.
+ *
+ * @param entity The entity to check.
+ * @return If is custom.
+ */
+ public static boolean isCustomEntity(@NotNull final Entity entity) {
+ for (TestableEntity testable : REGISTRY.values()) {
+ if (testable.matches(entity)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get all registered custom items.
+ *
+ * @return A set of all items.
+ */
+ public static Set getCustomEntities() {
+ return new HashSet<>(REGISTRY.values());
+ }
+
+ private Entities() {
+ throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
+ }
+}
diff --git a/eco-api/src/main/java/com/willfp/eco/core/entities/TestableEntity.java b/eco-api/src/main/java/com/willfp/eco/core/entities/TestableEntity.java
new file mode 100644
index 00000000..f02bdd3f
--- /dev/null
+++ b/eco-api/src/main/java/com/willfp/eco/core/entities/TestableEntity.java
@@ -0,0 +1,27 @@
+package com.willfp.eco.core.entities;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * An item with a test to see if any item is that item.
+ */
+public interface TestableEntity {
+ /**
+ * If an Entity matches the test.
+ *
+ * @param entity The entity to test.
+ * @return If the entity matches.
+ */
+ boolean matches(@Nullable Entity entity);
+
+ /**
+ * Spawn the entity.
+ *
+ * @param location The location.
+ * @return The entity.
+ */
+ Entity spawn(@NotNull Location location);
+}
diff --git a/eco-api/src/main/java/com/willfp/eco/core/entities/args/EntityArgParseResult.java b/eco-api/src/main/java/com/willfp/eco/core/entities/args/EntityArgParseResult.java
new file mode 100644
index 00000000..eb0e4493
--- /dev/null
+++ b/eco-api/src/main/java/com/willfp/eco/core/entities/args/EntityArgParseResult.java
@@ -0,0 +1,16 @@
+package com.willfp.eco.core.entities.args;
+
+import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * @param test The test for the entity.
+ * @param modifier The modifier to apply to the entity.
+ * @see EntityArgParser
+ */
+public record EntityArgParseResult(@NotNull Predicate test,
+ @NotNull Consumer modifier) {
+}
diff --git a/eco-api/src/main/java/com/willfp/eco/core/entities/args/EntityArgParser.java b/eco-api/src/main/java/com/willfp/eco/core/entities/args/EntityArgParser.java
new file mode 100644
index 00000000..4fdd82c1
--- /dev/null
+++ b/eco-api/src/main/java/com/willfp/eco/core/entities/args/EntityArgParser.java
@@ -0,0 +1,20 @@
+package com.willfp.eco.core.entities.args;
+
+import com.willfp.eco.core.entities.TestableEntity;
+import org.bukkit.Location;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * An argument parser should generate the predicate as well
+ * as modify the Entity for {@link TestableEntity#spawn(Location)}.
+ */
+public interface EntityArgParser {
+ /**
+ * Parse the arguments.
+ *
+ * @param args The arguments.
+ * @return The predicate test to apply to the modified entity.
+ */
+ @Nullable EntityArgParseResult parseArguments(@NotNull String[] args);
+}
diff --git a/eco-api/src/main/java/com/willfp/eco/core/entities/impl/ModifiedTestableEntity.java b/eco-api/src/main/java/com/willfp/eco/core/entities/impl/ModifiedTestableEntity.java
new file mode 100644
index 00000000..d18179e9
--- /dev/null
+++ b/eco-api/src/main/java/com/willfp/eco/core/entities/impl/ModifiedTestableEntity.java
@@ -0,0 +1,75 @@
+package com.willfp.eco.core.entities.impl;
+
+import com.willfp.eco.core.entities.TestableEntity;
+import org.apache.commons.lang3.Validate;
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * Existing testable entity with an extra filter.
+ *
+ * @see com.willfp.eco.core.entities.CustomEntity
+ */
+public class ModifiedTestableEntity implements TestableEntity {
+ /**
+ * The item.
+ */
+ private final TestableEntity handle;
+
+ /**
+ * The amount.
+ */
+ private final Predicate test;
+
+ /**
+ * The provider to spawn the entity.
+ */
+ private final Function provider;
+
+ /**
+ * Create a new modified testable entity.
+ *
+ * @param entity The base entity.
+ * @param test The test.
+ * @param provider The provider to spawn the entity.
+ */
+ public ModifiedTestableEntity(@NotNull final TestableEntity entity,
+ @NotNull final Predicate<@NotNull Entity> test,
+ @NotNull final Function provider) {
+ this.handle = entity;
+ this.test = test;
+ this.provider = provider;
+ }
+
+ /**
+ * If the entity matches the test.
+ *
+ * @param entity The entity to test.
+ * @return If the entity matches the test.
+ */
+ @Override
+ public boolean matches(@Nullable final Entity entity) {
+ return entity != null && handle.matches(entity) && test.test(entity);
+ }
+
+ @Override
+ public Entity spawn(@NotNull final Location location) {
+ Validate.notNull(location.getWorld());
+
+ return provider.apply(location);
+ }
+
+ /**
+ * Get the handle.
+ *
+ * @return The handle.
+ */
+ public TestableEntity getHandle() {
+ return this.handle;
+ }
+}
diff --git a/eco-api/src/main/java/com/willfp/eco/core/entities/impl/SimpleTestableEntity.java b/eco-api/src/main/java/com/willfp/eco/core/entities/impl/SimpleTestableEntity.java
new file mode 100644
index 00000000..1cb5f8f6
--- /dev/null
+++ b/eco-api/src/main/java/com/willfp/eco/core/entities/impl/SimpleTestableEntity.java
@@ -0,0 +1,59 @@
+package com.willfp.eco.core.entities.impl;
+
+import com.willfp.eco.core.entities.TestableEntity;
+import org.apache.commons.lang3.Validate;
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Default vanilla entities.
+ */
+public class SimpleTestableEntity implements TestableEntity {
+ /**
+ * The entity type.
+ */
+ private final EntityType type;
+
+ /**
+ * Create a new simple testable entity.
+ *
+ * @param type The entity type.
+ */
+ public SimpleTestableEntity(@NotNull final EntityType type) {
+ this.type = type;
+
+ Validate.notNull(type.getEntityClass(), "Entity cannot be of unknown type!");
+ }
+
+ /**
+ * If the entity matches the type.
+ *
+ * @param entity The entity to test.
+ * @return If the entity is of the specified type.
+ */
+ @Override
+ public boolean matches(@Nullable final Entity entity) {
+ return entity != null && entity.getType() == type;
+ }
+
+ @Override
+ public Entity spawn(@NotNull final Location location) {
+ Validate.notNull(location.getWorld());
+
+ assert type.getEntityClass() != null;
+
+ return location.getWorld().spawn(location, type.getEntityClass());
+ }
+
+ /**
+ * Get the type.
+ *
+ * @return The type.
+ */
+ public EntityType getType() {
+ return this.type;
+ }
+}
diff --git a/eco-api/src/main/java/com/willfp/eco/core/items/Items.java b/eco-api/src/main/java/com/willfp/eco/core/items/Items.java
index 6a762323..6902d883 100644
--- a/eco-api/src/main/java/com/willfp/eco/core/items/Items.java
+++ b/eco-api/src/main/java/com/willfp/eco/core/items/Items.java
@@ -8,6 +8,7 @@ import com.willfp.eco.core.recipe.parts.ModifiedTestableItem;
import com.willfp.eco.core.recipe.parts.TestableStack;
import com.willfp.eco.util.NamespacedKeyUtils;
import com.willfp.eco.util.NumberUtils;
+import com.willfp.eco.util.StringUtils;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
@@ -118,7 +119,7 @@ public final class Items {
return new EmptyTestableItem();
}
- String[] args = parseLookupString(key);
+ String[] args = StringUtils.parseTokens(key);
if (args.length == 0) {
return new EmptyTestableItem();
@@ -237,54 +238,6 @@ public final class Items {
}
}
- /**
- * Parse lookup string into arguments.
- *
- * Handles quoted strings for names.
- *
- * @param lookup The lookup string.
- * @return An array of arguments to be processed.
- * @author Shawn (https://stackoverflow.com/questions/70606170/split-a-list-on-spaces-and-group-quoted-characters/70606653#70606653)
- */
- @NotNull
- public static String[] parseLookupString(@NotNull final String lookup) {
- char[] chars = lookup.toCharArray();
- List arguments = new ArrayList<>();
- StringBuilder argumentBuilder = new StringBuilder();
- for (int i = 0; i < chars.length; i++) {
- if (chars[i] == ' ') {
- /*
- Take the current value of the argument builder, append it to the
- list of found arguments, and then clear it for the next argument.
- */
- arguments.add(argumentBuilder.toString());
- argumentBuilder.setLength(0);
- } else if (chars[i] == '"') {
- /*
- Work until the next unescaped quote to handle quotes with
- spaces in them - assumes the input string is well-formatted
- */
- for (i++; chars[i] != '"'; i++) {
- /*
- If the found quote is escaped, ignore it in the parsing
- */
- if (chars[i] == '\\') {
- i++;
- }
- argumentBuilder.append(chars[i]);
- }
- } else {
- /*
- If it's a regular character, just append it to the current argument.
- */
- argumentBuilder.append(chars[i]);
- }
- }
- arguments.add(argumentBuilder.toString()); // Adds the last argument to the arguments.
- return arguments.toArray(new String[0]);
- }
-
-
/**
* Get a Testable Item from an ItemStack.
*
diff --git a/eco-api/src/main/java/com/willfp/eco/util/StringUtils.java b/eco-api/src/main/java/com/willfp/eco/util/StringUtils.java
index 5318ff76..0f86f8d2 100644
--- a/eco-api/src/main/java/com/willfp/eco/util/StringUtils.java
+++ b/eco-api/src/main/java/com/willfp/eco/util/StringUtils.java
@@ -477,6 +477,53 @@ public final class StringUtils {
return LEGACY_COMPONENT_SERIALIZER.serialize(component);
}
+ /**
+ * Parse string into tokens.
+ *
+ * Handles quoted strings for names.
+ *
+ * @param lookup The lookup string.
+ * @return An array of tokens to be processed.
+ * @author Shawn (https://stackoverflow.com/questions/70606170/split-a-list-on-spaces-and-group-quoted-characters/70606653#70606653)
+ */
+ @NotNull
+ public static String[] parseTokens(@NotNull final String lookup) {
+ char[] chars = lookup.toCharArray();
+ List tokens = new ArrayList<>();
+ StringBuilder tokenBuilder = new StringBuilder();
+ for (int i = 0; i < chars.length; i++) {
+ if (chars[i] == ' ') {
+ /*
+ Take the current value of the argument builder, append it to the
+ list of found tokens, and then clear it for the next argument.
+ */
+ tokens.add(tokenBuilder.toString());
+ tokenBuilder.setLength(0);
+ } else if (chars[i] == '"') {
+ /*
+ Work until the next unescaped quote to handle quotes with
+ spaces in them - assumes the input string is well-formatted
+ */
+ for (i++; chars[i] != '"'; i++) {
+ /*
+ If the found quote is escaped, ignore it in the parsing
+ */
+ if (chars[i] == '\\') {
+ i++;
+ }
+ tokenBuilder.append(chars[i]);
+ }
+ } else {
+ /*
+ If it's a regular character, just append it to the current argument.
+ */
+ tokenBuilder.append(chars[i]);
+ }
+ }
+ tokens.add(tokenBuilder.toString()); // Adds the last argument to the tokens.
+ return tokens.toArray(new String[0]);
+ }
+
/**
* Options for formatting.
*/
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserAttackDamage.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserAttackDamage.kt
new file mode 100644
index 00000000..2cb6faa7
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserAttackDamage.kt
@@ -0,0 +1,43 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserAttackDamage : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var attributeValue: Double? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("attack-damage", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ attributeValue = argSplit[1].toDoubleOrNull()
+ }
+
+ attributeValue ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ val inst = it.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE) ?: return@EntityArgParseResult false
+ inst.value >= attributeValue
+ },
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult
+ }
+
+ it.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE)?.baseValue = attributeValue
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserAttackSpeed.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserAttackSpeed.kt
new file mode 100644
index 00000000..12f3f989
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserAttackSpeed.kt
@@ -0,0 +1,43 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserAttackSpeed : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var attributeValue: Double? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("attack-speed", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ attributeValue = argSplit[1].toDoubleOrNull()
+ }
+
+ attributeValue ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ val inst = it.getAttribute(Attribute.GENERIC_ATTACK_SPEED) ?: return@EntityArgParseResult false
+ inst.value >= attributeValue
+ },
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult
+ }
+
+ it.getAttribute(Attribute.GENERIC_ATTACK_SPEED)?.baseValue = attributeValue
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserFlySpeed.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserFlySpeed.kt
new file mode 100644
index 00000000..8a2ca9b3
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserFlySpeed.kt
@@ -0,0 +1,43 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserFlySpeed : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var attributeValue: Double? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("fly-speed", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ attributeValue = argSplit[1].toDoubleOrNull()
+ }
+
+ attributeValue ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ val inst = it.getAttribute(Attribute.GENERIC_FLYING_SPEED) ?: return@EntityArgParseResult false
+ inst.value >= attributeValue
+ },
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult
+ }
+
+ it.getAttribute(Attribute.GENERIC_FLYING_SPEED)?.baseValue = attributeValue
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserFollowRange.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserFollowRange.kt
new file mode 100644
index 00000000..46eed58e
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserFollowRange.kt
@@ -0,0 +1,43 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserFollowRange : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var attributeValue: Double? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("follow-range", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ attributeValue = argSplit[1].toDoubleOrNull()
+ }
+
+ attributeValue ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ val inst = it.getAttribute(Attribute.GENERIC_FOLLOW_RANGE) ?: return@EntityArgParseResult false
+ inst.value >= attributeValue
+ },
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult
+ }
+
+ it.getAttribute(Attribute.GENERIC_FOLLOW_RANGE)?.baseValue = attributeValue
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserHealth.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserHealth.kt
new file mode 100644
index 00000000..95da7e06
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserHealth.kt
@@ -0,0 +1,43 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserHealth : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var health: Double? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("health", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ health = argSplit[1].toDoubleOrNull()
+ }
+
+ health ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ it.health >= health
+ },
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult
+ }
+
+ it.getAttribute(Attribute.GENERIC_MAX_HEALTH)?.baseValue = health
+ it.health = health
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserJumpStrength.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserJumpStrength.kt
new file mode 100644
index 00000000..cd5764c4
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserJumpStrength.kt
@@ -0,0 +1,43 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserJumpStrength : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var attributeValue: Double? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("jump-strength", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ attributeValue = argSplit[1].toDoubleOrNull()
+ }
+
+ attributeValue ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ val inst = it.getAttribute(Attribute.HORSE_JUMP_STRENGTH) ?: return@EntityArgParseResult false
+ inst.value >= attributeValue
+ },
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult
+ }
+
+ it.getAttribute(Attribute.HORSE_JUMP_STRENGTH)?.baseValue = attributeValue
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserKnockback.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserKnockback.kt
new file mode 100644
index 00000000..bb41a345
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserKnockback.kt
@@ -0,0 +1,43 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserKnockback : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var attributeValue: Double? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("knockback", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ attributeValue = argSplit[1].toDoubleOrNull()
+ }
+
+ attributeValue ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ val inst = it.getAttribute(Attribute.GENERIC_ATTACK_KNOCKBACK) ?: return@EntityArgParseResult false
+ inst.value >= attributeValue
+ },
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult
+ }
+
+ it.getAttribute(Attribute.GENERIC_ATTACK_KNOCKBACK)?.baseValue = attributeValue
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserKnockbackResistance.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserKnockbackResistance.kt
new file mode 100644
index 00000000..02f29cb8
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserKnockbackResistance.kt
@@ -0,0 +1,43 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserKnockbackResistance : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var attributeValue: Double? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("knockback-resistance", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ attributeValue = argSplit[1].toDoubleOrNull()
+ }
+
+ attributeValue ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ val inst = it.getAttribute(Attribute.GENERIC_KNOCKBACK_RESISTANCE) ?: return@EntityArgParseResult false
+ inst.value >= attributeValue
+ },
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult
+ }
+
+ it.getAttribute(Attribute.GENERIC_KNOCKBACK_RESISTANCE)?.baseValue = attributeValue
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserName.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserName.kt
new file mode 100644
index 00000000..882bb09f
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserName.kt
@@ -0,0 +1,34 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import com.willfp.eco.util.StringUtils
+
+class EntityArgParserName : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var name: String? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("name", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ name = argSplit[1]
+ }
+
+ name ?: return null
+
+ val formatted = StringUtils.format(name)
+
+ return EntityArgParseResult(
+ { it.customName == formatted },
+ {
+ it.isCustomNameVisible = true
+ it.customName = formatted
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserNoAI.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserNoAI.kt
new file mode 100644
index 00000000..b8999480
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserNoAI.kt
@@ -0,0 +1,34 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserNoAI : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var noAI = false
+
+ for (arg in args) {
+ if (arg.equals("unbreakable", true)) {
+ noAI = true
+ }
+ }
+
+ if (!noAI) {
+ return null
+ }
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ !it.hasAI()
+ },
+ {
+ (it as? LivingEntity)?.setAI(false)
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserSize.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserSize.kt
new file mode 100644
index 00000000..dbe10a04
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserSize.kt
@@ -0,0 +1,41 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.entity.Slime
+
+class EntityArgParserSize : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var size: Int? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("size", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ size = argSplit[1].toIntOrNull()
+ }
+
+ size ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is Slime) {
+ return@EntityArgParseResult false
+ }
+
+ it.size == size
+ },
+ {
+ if (it !is Slime) {
+ return@EntityArgParseResult
+ }
+
+ it.size = size
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserSpawnReinforcements.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserSpawnReinforcements.kt
new file mode 100644
index 00000000..3840b508
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserSpawnReinforcements.kt
@@ -0,0 +1,43 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserSpawnReinforcements : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var attributeValue: Double? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("spawn-reinforcements", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ attributeValue = argSplit[1].toDoubleOrNull()
+ }
+
+ attributeValue ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ val inst = it.getAttribute(Attribute.ZOMBIE_SPAWN_REINFORCEMENTS) ?: return@EntityArgParseResult false
+ inst.value >= attributeValue
+ },
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult
+ }
+
+ it.getAttribute(Attribute.ZOMBIE_SPAWN_REINFORCEMENTS)?.baseValue = attributeValue
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserSpeed.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserSpeed.kt
new file mode 100644
index 00000000..f23f16aa
--- /dev/null
+++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/entities/EntityArgParserSpeed.kt
@@ -0,0 +1,43 @@
+package com.willfp.eco.internal.entities
+
+import com.willfp.eco.core.entities.args.EntityArgParseResult
+import com.willfp.eco.core.entities.args.EntityArgParser
+import org.bukkit.attribute.Attribute
+import org.bukkit.entity.LivingEntity
+
+class EntityArgParserSpeed : EntityArgParser {
+ override fun parseArguments(args: Array): EntityArgParseResult? {
+ var attributeValue: Double? = null
+
+ for (arg in args) {
+ val argSplit = arg.split(":")
+ if (!argSplit[0].equals("speed", ignoreCase = true)) {
+ continue
+ }
+ if (argSplit.size < 2) {
+ continue
+ }
+ attributeValue = argSplit[1].toDoubleOrNull()
+ }
+
+ attributeValue ?: return null
+
+ return EntityArgParseResult(
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult false
+ }
+
+ val inst = it.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED) ?: return@EntityArgParseResult false
+ inst.value >= attributeValue
+ },
+ {
+ if (it !is LivingEntity) {
+ return@EntityArgParseResult
+ }
+
+ it.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED)?.baseValue = attributeValue
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt
index 774510e8..37f88d73 100644
--- a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt
+++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt
@@ -5,6 +5,7 @@ import com.willfp.eco.core.Eco
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.Prerequisite
import com.willfp.eco.core.display.Display
+import com.willfp.eco.core.entities.Entities
import com.willfp.eco.core.integrations.IntegrationLoader
import com.willfp.eco.core.integrations.afk.AFKManager
import com.willfp.eco.core.integrations.anticheat.AnticheatManager
@@ -17,6 +18,19 @@ import com.willfp.eco.core.integrations.shop.ShopManager
import com.willfp.eco.core.items.Items
import com.willfp.eco.internal.display.EcoDisplayHandler
import com.willfp.eco.internal.drops.DropManager
+import com.willfp.eco.internal.entities.EntityArgParserAttackDamage
+import com.willfp.eco.internal.entities.EntityArgParserAttackSpeed
+import com.willfp.eco.internal.entities.EntityArgParserFlySpeed
+import com.willfp.eco.internal.entities.EntityArgParserFollowRange
+import com.willfp.eco.internal.entities.EntityArgParserHealth
+import com.willfp.eco.internal.entities.EntityArgParserJumpStrength
+import com.willfp.eco.internal.entities.EntityArgParserKnockback
+import com.willfp.eco.internal.entities.EntityArgParserKnockbackResistance
+import com.willfp.eco.internal.entities.EntityArgParserName
+import com.willfp.eco.internal.entities.EntityArgParserNoAI
+import com.willfp.eco.internal.entities.EntityArgParserSize
+import com.willfp.eco.internal.entities.EntityArgParserSpawnReinforcements
+import com.willfp.eco.internal.entities.EntityArgParserSpeed
import com.willfp.eco.internal.items.ArgParserColor
import com.willfp.eco.internal.items.ArgParserCustomModelData
import com.willfp.eco.internal.items.ArgParserEnchantment
@@ -105,6 +119,20 @@ abstract class EcoSpigotPlugin : EcoPlugin(
Items.registerArgParser(ArgParserUnbreakable())
Items.registerArgParser(ArgParserName())
+ Entities.registerArgParser(EntityArgParserName())
+ Entities.registerArgParser(EntityArgParserNoAI())
+ Entities.registerArgParser(EntityArgParserAttackDamage())
+ Entities.registerArgParser(EntityArgParserAttackSpeed())
+ Entities.registerArgParser(EntityArgParserFlySpeed())
+ Entities.registerArgParser(EntityArgParserFollowRange())
+ Entities.registerArgParser(EntityArgParserHealth())
+ Entities.registerArgParser(EntityArgParserJumpStrength())
+ Entities.registerArgParser(EntityArgParserKnockback())
+ Entities.registerArgParser(EntityArgParserKnockbackResistance())
+ Entities.registerArgParser(EntityArgParserSize())
+ Entities.registerArgParser(EntityArgParserSpawnReinforcements())
+ Entities.registerArgParser(EntityArgParserSpeed())
+
val skullProxy = getProxy(SkullProxy::class.java)
SkullUtils.initialize(
{ meta, base64 -> skullProxy.setSkullTexture(meta, base64) },