diff --git a/eco-api/src/main/java/com/willfp/eco/core/entities/CustomEntity.java b/eco-api/src/main/java/com/willfp/eco/core/entities/CustomEntity.java new file mode 100644 index 00000000..62502d6c --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/entities/CustomEntity.java @@ -0,0 +1,84 @@ +package com.willfp.eco.core.entities; + +import org.apache.commons.lang3.Validate; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +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; + +/** + * A custom entity has 3 components. + * + * + */ +public class CustomEntity implements TestableEntity { + /** + * The key. + */ + private final NamespacedKey key; + + /** + * The test for Entities to pass. + */ + private final Predicate<@NotNull Entity> test; + + /** + * The provider to spawn the entity. + */ + private final Function provider; + + /** + * Create a new custom entity. + * + * @param key The entity key. + * @param test The test. + * @param provider The provider to spawn the entity. + */ + public CustomEntity(@NotNull final NamespacedKey key, + @NotNull final Predicate<@NotNull Entity> test, + @NotNull final Function provider) { + this.key = key; + this.test = test; + this.provider = provider; + } + + @Override + public boolean matches(@Nullable final Entity entity) { + if (entity == null) { + return false; + } + + return test.test(entity); + } + + @Override + public Entity spawn(@NotNull final Location location) { + Validate.notNull(location.getWorld()); + + return provider.apply(location); + } + + /** + * Register the entity. + */ + public void register() { + Entities.registerCustomEntity(this.getKey(), this); + } + + /** + * Get the key. + * + * @return The key. + */ + public NamespacedKey getKey() { + return this.key; + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/entities/Entities.java b/eco-api/src/main/java/com/willfp/eco/core/entities/Entities.java new file mode 100644 index 00000000..dd628095 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/entities/Entities.java @@ -0,0 +1,241 @@ +package com.willfp.eco.core.entities; + +import com.willfp.eco.core.entities.args.EntityArgParseResult; +import com.willfp.eco.core.entities.args.EntityArgParser; +import com.willfp.eco.core.entities.impl.ModifiedTestableEntity; +import com.willfp.eco.core.entities.impl.SimpleTestableEntity; +import com.willfp.eco.util.NamespacedKeyUtils; +import com.willfp.eco.util.StringUtils; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * Class to manage all custom and vanilla entities. + */ +public final class Entities { + /** + * All entities. + */ + private static final Map REGISTRY = new ConcurrentHashMap<>(); + + /** + * All entity parsers. + */ + private static final List 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) },