package com.craftaro.ultimatestacker.tasks; import com.craftaro.core.compatibility.ServerVersion; import com.craftaro.core.hooks.WorldGuardHook; import com.craftaro.core.world.SWorld; import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial; import com.craftaro.ultimatestacker.UltimateStacker; import com.craftaro.ultimatestacker.api.stack.entity.EntityStack; import com.craftaro.ultimatestacker.api.stack.entity.EntityStackManager; import com.craftaro.ultimatestacker.settings.Settings; import com.craftaro.ultimatestacker.stackable.entity.Check; import com.craftaro.ultimatestacker.stackable.entity.custom.CustomEntity; import com.craftaro.ultimatestacker.utils.CachedChunk; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.*; import org.bukkit.inventory.EntityEquipment; import org.bukkit.scheduler.BukkitRunnable; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.stream.Collectors; import static com.craftaro.ultimatestacker.stackable.entity.Check.getChecks; public class StackingTask extends BukkitRunnable { private final UltimateStacker plugin; private final EntityStackManager stackManager; private final BreedingTask breedingTask; private final ConfigurationSection configurationSection = UltimateStacker.getInstance().getMobFile(); private final List processed = new ArrayList<>(); private final Map entityStackSizes = new HashMap<>(); private final int maxEntityStackSize = Settings.MAX_STACK_ENTITIES.getInt(), minEntityStackSize = Settings.MIN_STACK_ENTITIES.getInt(), searchRadius = Settings.SEARCH_RADIUS.getInt(), chunkRadius = Settings.STACK_WHOLE_CHUNK_RADIUS.getInt(), maxPerTypeStacksPerChunk = Settings.MAX_PER_TYPE_STACKS_PER_CHUNK.getInt(); private final List disabledWorlds = Settings.DISABLED_WORLDS.getStringList(), stackReasons = Settings.STACK_REASONS.getStringList(); private final List checks = getChecks(Settings.STACK_CHECKS.getStringList()); private final boolean stackFlyingDown = Settings.ONLY_STACK_FLYING_DOWN.getBoolean(), stackWholeChunk = Settings.STACK_WHOLE_CHUNK.getBoolean(), weaponsArentEquipment = Settings.WEAPONS_ARENT_EQUIPMENT.getBoolean(), onlyStackFromSpawners = Settings.ONLY_STACK_FROM_SPAWNERS.getBoolean(), onlyStackOnSurface = Settings.ONLY_STACK_ON_SURFACE.getBoolean(); private final Set loadedWorlds; public StackingTask(UltimateStacker plugin) { this.plugin = plugin; stackManager = plugin.getEntityStackManager(); breedingTask = plugin.getBreedingTask(); // Add loaded worlds. loadedWorlds = new HashSet<>(); for (World world : Bukkit.getWorlds()) { //Filter disabled worlds to avoid continuous checks in the stacking loop if (isWorldDisabled(world)) continue; loadedWorlds.add(new SWorld(world)); } int tickRate = Settings.STACK_SEARCH_TICK_SPEED.getInt(); runTaskTimerAsynchronously(plugin, tickRate, tickRate); } @Override public void run() { //Make sure to continue the task if any exception occurs try { // Loop through each world. for (SWorld sWorld : loadedWorlds) { List entities; // Get the loaded entities from the current world and reverse them. try { entities = getLivingEntitiesSync(sWorld).get(); } catch (ExecutionException | InterruptedException ex) { ex.printStackTrace(); continue; } //Filter non-stackable entities to improve performance on main thread entities.removeIf(this::isEntityNotStackable); List remove = new ArrayList<>(); for (LivingEntity entity : entities) { //Q: What can cause current modification exception here? // Check our WorldGuard flag. Boolean flag = WorldGuardHook.isEnabled() ? WorldGuardHook.getBooleanFlag(entity.getLocation(), "mob-stacking") : null; //Does this work async? if (flag != null && !flag) { remove.add(entity); } } entities.removeAll(remove); Bukkit.getScheduler().runTask(plugin, () -> { // Loop through the entities. for (LivingEntity entity : entities) { // Make sure our entity has not already been processed. // Skip it if it has been. if (processed.contains(entity.getUniqueId())) continue; // Get entity location to pass around as its faster this way. Location location = entity.getLocation(); // Process the entity. processEntity(entity, location, entities); } }); } } catch (Exception e) { e.printStackTrace(); } finally { // Make sure we clear the processed list. this.processed.clear(); } } private Future> getLivingEntitiesSync(SWorld sWorld) { CompletableFuture> future = new CompletableFuture<>(); Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> future.complete(sWorld.getLivingEntities())); return future; } public boolean isWorldDisabled(World world) { return disabledWorlds.stream().anyMatch(worldStr -> world.getName().equalsIgnoreCase(worldStr)); } //Returns true if the entity is not stackable, and it will be removed from the list private boolean isEntityNotStackable(LivingEntity entity) { if (isMaxStack(entity)) return true; // Make sure we have the correct entity type and that it is valid. if (!entity.isValid() || entity instanceof HumanEntity || entity instanceof ArmorStand // Make sure the entity is not in love or in the breeding queue. || breedingTask.isInQueue(entity.getUniqueId())) return true; if (!configurationSection.getBoolean("Mobs." + entity.getType().name() + ".Enabled")) return true; // Allow spawn if stack reasons are set and match, or if from a spawner final String spawnReason = entity.hasMetadata("US_REASON") && !entity.getMetadata("US_REASON").isEmpty() ? entity.getMetadata("US_REASON").get(0).asString() : null; List stackReasons; if (onlyStackFromSpawners) { // If only stack from spawners is enabled, make sure the entity spawned from a spawner. if (!"SPAWNER".equals(spawnReason)) return true; } else if (!(stackReasons = this.stackReasons).isEmpty() && !stackReasons.contains(spawnReason)) { // Only stack if on the list of events to stack return true; } // If only stack on surface is enabled make sure the entity is on a surface then entity is stackable. //return !onlyStackOnSurface || canFly(entity) || entity.getType().name().equals("SHULKER") || ((entity).isOnGround() || (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13) && (entity).isSwimming())); return onlyStackOnSurface && canFly(entity) && !entity.getType().name().equals("SHULKER") && !entity.isOnGround() && !(ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13) && (entity).isSwimming()); } private void processEntity(LivingEntity baseEntity, Location location, List entities) { // Get the stack from the entity. It should be noted that this value will // be null if our entity is not a stack. EntityStack baseStack = plugin.getEntityStackManager().getStackedEntity(baseEntity); // Get the maximum stack size for this entity. int maxEntityStackSize = getEntityMaxStackSize(baseEntity); // Is this entity stacked? boolean isStack = baseStack != null; // The amount that is stackable. int baseSize = isStack ? baseStack.getAmount() : 1; // Attempt to split overstacked entities. // If this is successful, we can return because the entity was processed if (isStack && attemptSplit(baseStack, maxEntityStackSize)) { return; } // If this entity is named or a custom entity skip it. if (!isStack && (baseEntity.getCustomName() != null && plugin.getCustomEntityManager().getCustomEntity(baseEntity) != null)) { processed.add(baseEntity.getUniqueId()); return; } // Get similar entities around our entity and make sure those entities are both compatible and stackable. List stackableFriends = getSimilarEntitiesAroundEntity(baseEntity, location); //Total entities that can be stacked into the base entity int maxStackable = maxEntityStackSize - baseSize; int toStack = 0; List remove = new ArrayList<>(); // Loop through our similar stackable entities. for (LivingEntity friendlyEntity : stackableFriends) { if (!entities.contains(friendlyEntity)) continue; // Process similar entities. EntityStack friendStack = stackManager.getStackedEntity(friendlyEntity); int amount = friendStack != null ? friendStack.getAmount() : 1; if (toStack + amount <= maxStackable) { toStack += amount; remove.add(friendlyEntity); continue; } break; //We max, exit loop } //Nothing to stack if (toStack == 0) { return; } //Add to base stack and remove stacked friends stackManager.createStackedEntity(baseEntity, baseSize + toStack); processed.add(baseEntity.getUniqueId()); //Remove merged entities //We in sync, so we can remove entities for (LivingEntity entity : remove) { processed.add(entity.getUniqueId()); entity.remove(); } } /** * This method splitting overstacked entities into new stacks. * Must be called synchronously. * * @param baseStack The base stack to check for splitting. * @param maxEntityStackSize The maximum stack size for the entity. -1 if we need to calculate it. * @return True if the split was successful, false otherwise. */ public boolean attemptSplit(EntityStack baseStack, int maxEntityStackSize) { LivingEntity hostEntity = baseStack.getHostEntity(); int stackSize = baseStack.getAmount(); int maxEntityStackAmount = maxEntityStackSize == -1 ? getEntityMaxStackSize(hostEntity) : maxEntityStackSize; if (stackSize <= maxEntityStackAmount) return false; baseStack.setAmount(maxEntityStackAmount); int finalStackSize = stackSize - maxEntityStackAmount; do { // Create a new stack, summon entity and add to stack. LivingEntity newEntity = (LivingEntity) hostEntity.getWorld().spawnEntity(hostEntity.getLocation(), hostEntity.getType()); int toAdd = Math.min(finalStackSize, maxEntityStackAmount); EntityStack newStack = stackManager.createStackedEntity(newEntity, toAdd); processed.add(newEntity.getUniqueId()); finalStackSize -= maxEntityStackAmount; } while (finalStackSize >= 0); //Mark it as processed. processed.add(hostEntity.getUniqueId()); return true; } private Set getNearbyChunks(SWorld sWorld, Location location) { //Only stack entities in the same chunk if (stackWholeChunk && chunkRadius == 0) { return Collections.singleton(new CachedChunk(sWorld, location.getChunk().getX(), location.getChunk().getZ())); } World world = location.getWorld(); if (world == null) return new HashSet<>(); CachedChunk firstChunk = new CachedChunk(sWorld, location); Set chunks = new TreeSet<>(Comparator.comparingInt(CachedChunk::getX).thenComparingInt(CachedChunk::getZ)); chunks.add(firstChunk); //Calculate chunk coordinates we need to check int minX = (int) Math.floor((location.getX() - chunkRadius) / 16.0D); int maxX = (int) Math.floor((location.getX() + chunkRadius) / 16.0D); int minZ = (int) Math.floor((location.getZ() - chunkRadius) / 16.0D); int maxZ = (int) Math.floor((location.getZ() + chunkRadius) / 16.0D); for (int x = minX; x <= maxX; ++x) { for (int z = minZ; z <= maxZ; ++z) { if (x == minX || x == maxX || z == minZ || z == maxZ) { chunks.add(new CachedChunk(sWorld, x, z)); } } } //Set a bedrock in the top left corner of the chunks for (CachedChunk chunk : chunks) { int x = chunk.getX() * 16; int z = chunk.getZ() * 16; world.getBlockAt(x, 319, z).setType(XMaterial.BEDROCK.parseMaterial()); } return chunks; } /** * Get all entities around an entity within a radius which are similar to the entity. * * @param entity The entity to get similar entities around. * @return A list of similar entities around the entity. */ public List getFriendlyStacksNearby(LivingEntity entity) { if (!stackWholeChunk) { return entity.getNearbyEntities(searchRadius / 2.0, searchRadius / 2.0, searchRadius / 2.0) .stream().filter(e -> e.getType() == entity.getType() && !isMaxStack((LivingEntity) e)) .map(e -> (LivingEntity) e).collect(Collectors.toList()); } List entities = new ArrayList<>(); try { Set chunks = getNearbyChunks(new SWorld(entity.getWorld()), entity.getLocation()); for (CachedChunk chunk : chunks) { Entity[] entityList = chunk.getEntities(); for (Entity e : entityList) { if (e.getType() == entity.getType() && !isMaxStack((LivingEntity) e)) { entities.add((LivingEntity) e); } } } entities.removeIf(entity1 -> entity1.equals(entity) || !UltimateStacker.getInstance().getCustomEntityManager().isStackable(entity1)); } catch (Exception ex) { ex.printStackTrace(); } return entities; } public List getSimilarEntitiesAroundEntity(LivingEntity initialEntity, Location location) { try { // Create a list of all entities around the initial entity of the same type. List entityList = new ArrayList<>(getFriendlyStacksNearby(initialEntity)); CustomEntity customEntity = plugin.getCustomEntityManager().getCustomEntity(initialEntity); if (customEntity != null) entityList.removeIf(entity -> !customEntity.isSimilar(initialEntity, entity)); if (stackFlyingDown && canFly(initialEntity)) entityList.removeIf(entity -> entity.getLocation().getY() > initialEntity.getLocation().getY()); for (Check check : checks) { if (check == null) continue; switch (check) { case SPAWN_REASON: { if (initialEntity.hasMetadata("US_REASON")) entityList.removeIf(entity -> entity.hasMetadata("US_REASON") && !entity.getMetadata("US_REASON").get(0).asString().equals("US_REASON")); } case AGE: { if (!(initialEntity instanceof Ageable)) break; if (((Ageable) initialEntity).isAdult()) { entityList.removeIf(entity -> !((Ageable) entity).isAdult()); } else { entityList.removeIf(entity -> ((Ageable) entity).isAdult()); } break; } case NERFED: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9)) break; entityList.removeIf(entity -> entity.hasAI() != initialEntity.hasAI()); } case IS_TAMED: { if (!(initialEntity instanceof Tameable)) break; if (((Tameable) initialEntity).isTamed()) { entityList.removeIf(entity -> !((Tameable) entity).isTamed()); } else { entityList.removeIf(entity -> ((Tameable) entity).isTamed()); } } case ANIMAL_OWNER: { if (!(initialEntity instanceof Tameable)) break; Tameable tameable = ((Tameable) initialEntity); entityList.removeIf(entity -> ((Tameable) entity).getOwner() != tameable.getOwner()); } case PIG_SADDLE: { if (!(initialEntity instanceof Pig)) break; entityList.removeIf(entity -> ((Pig) entity).hasSaddle()); break; } case SKELETON_TYPE: { if (!(initialEntity instanceof Skeleton)) break; Skeleton skeleton = (Skeleton) initialEntity; entityList.removeIf(entity -> ((Skeleton) entity).getSkeletonType() != skeleton.getSkeletonType()); break; } case SHEEP_COLOR: { if (!(initialEntity instanceof Sheep)) break; Sheep sheep = ((Sheep) initialEntity); entityList.removeIf(entity -> ((Sheep) entity).getColor() != sheep.getColor()); break; } case SHEEP_SHEARED: { if (!(initialEntity instanceof Sheep)) break; Sheep sheep = ((Sheep) initialEntity); if (sheep.isSheared()) { entityList.removeIf(entity -> !((Sheep) entity).isSheared()); } else { entityList.removeIf(entity -> ((Sheep) entity).isSheared()); } break; } case SNOWMAN_DERPED: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9) || !(initialEntity instanceof Snowman)) break; Snowman snowman = ((Snowman) initialEntity); if (snowman.isDerp()) { entityList.removeIf(entity -> !((Snowman) entity).isDerp()); } else { entityList.removeIf(entity -> ((Snowman) entity).isDerp()); } break; } case LLAMA_COLOR: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11) || !(initialEntity instanceof Llama)) break; Llama llama = ((Llama) initialEntity); entityList.removeIf(entity -> ((Llama) entity).getColor() != llama.getColor()); break; } case LLAMA_STRENGTH: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11) || !(initialEntity instanceof Llama)) break; Llama llama = ((Llama) initialEntity); entityList.removeIf(entity -> ((Llama) entity).getStrength() != llama.getStrength()); break; } case VILLAGER_PROFESSION: { if (!(initialEntity instanceof Villager)) break; Villager villager = ((Villager) initialEntity); entityList.removeIf(entity -> ((Villager) entity).getProfession() != villager.getProfession()); break; } case SLIME_SIZE: { if (!(initialEntity instanceof Slime)) break; Slime slime = ((Slime) initialEntity); entityList.removeIf(entity -> ((Slime) entity).getSize() != slime.getSize()); break; } case HORSE_CARRYING_CHEST: { if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) { if (!(initialEntity instanceof ChestedHorse)) break; entityList.removeIf(entity -> ((ChestedHorse) entity).isCarryingChest()); } else { if (!(initialEntity instanceof Horse)) break; entityList.removeIf(entity -> ((Horse) entity).isCarryingChest()); } break; } case HORSE_HAS_ARMOR: { if (!(initialEntity instanceof Horse)) break; entityList.removeIf(entity -> ((Horse) entity).getInventory().getArmor() != null); break; } case HORSE_HAS_SADDLE: { if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13) && initialEntity instanceof AbstractHorse) { entityList.removeIf(entity -> ((AbstractHorse) entity).getInventory().getSaddle() != null); break; } if (!(initialEntity instanceof Horse)) break; entityList.removeIf(entity -> ((Horse) entity).getInventory().getSaddle() != null); break; } case HORSE_JUMP: { if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) { if (!(initialEntity instanceof AbstractHorse)) break; AbstractHorse horse = ((AbstractHorse) initialEntity); entityList.removeIf(entity -> ((AbstractHorse) entity).getJumpStrength() != horse.getJumpStrength()); } else { if (!(initialEntity instanceof Horse)) break; Horse horse = ((Horse) initialEntity); entityList.removeIf(entity -> ((Horse) entity).getJumpStrength() != horse.getJumpStrength()); } break; } case HORSE_COLOR: { if (!(initialEntity instanceof Horse)) break; Horse horse = ((Horse) initialEntity); entityList.removeIf(entity -> ((Horse) entity).getColor() != horse.getColor()); break; } case HORSE_STYLE: { if (!(initialEntity instanceof Horse)) break; Horse horse = ((Horse) initialEntity); entityList.removeIf(entity -> ((Horse) entity).getStyle() != horse.getStyle()); break; } case ZOMBIE_BABY: { if (!(initialEntity instanceof Zombie)) break; Zombie zombie = (Zombie) initialEntity; entityList.removeIf(entity -> ((Zombie) entity).isBaby() != zombie.isBaby()); break; } case WOLF_COLLAR_COLOR: { if (!(initialEntity instanceof Wolf)) break; Wolf wolf = (Wolf) initialEntity; entityList.removeIf(entity -> ((Wolf) entity).getCollarColor() != wolf.getCollarColor()); break; } case OCELOT_TYPE: { if (!(initialEntity instanceof Ocelot)) break; Ocelot ocelot = (Ocelot) initialEntity; entityList.removeIf(entity -> ((Ocelot) entity).getCatType() != ocelot.getCatType()); } case CAT_TYPE: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14) || !(initialEntity instanceof Cat)) break; Cat cat = (Cat) initialEntity; entityList.removeIf(entity -> ((Cat) entity).getCatType() != cat.getCatType()); break; } case HAS_EQUIPMENT: { if (initialEntity.getEquipment() == null) break; boolean imEquipped = isEquipped(initialEntity); if (imEquipped) entityList = new ArrayList<>(); else entityList.removeIf(this::isEquipped); break; } case RABBIT_TYPE: { if (!(initialEntity instanceof Rabbit)) break; Rabbit rabbit = (Rabbit) initialEntity; entityList.removeIf(entity -> ((Rabbit) entity).getRabbitType() != rabbit.getRabbitType()); break; } case PARROT_TYPE: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_12) || !(initialEntity instanceof Parrot)) break; Parrot parrot = (Parrot) initialEntity; entityList.removeIf(entity -> ((Parrot) entity).getVariant() != parrot.getVariant()); break; } case PUFFERFISH_STATE: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13) || !(initialEntity instanceof PufferFish)) break; PufferFish pufferFish = (PufferFish) initialEntity; entityList.removeIf(entity -> ((PufferFish) entity).getPuffState() != pufferFish.getPuffState()); break; } case TROPICALFISH_PATTERN: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13) || !(initialEntity instanceof TropicalFish)) break; TropicalFish tropicalFish = (TropicalFish) initialEntity; entityList.removeIf(entity -> ((TropicalFish) entity).getPattern() != tropicalFish.getPattern()); break; } case TROPICALFISH_PATTERN_COLOR: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13) || !(initialEntity instanceof TropicalFish)) break; TropicalFish tropicalFish = (TropicalFish) initialEntity; entityList.removeIf(entity -> ((TropicalFish) entity).getPatternColor() != tropicalFish.getPatternColor()); break; } case TROPICALFISH_BODY_COLOR: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13) || !(initialEntity instanceof TropicalFish)) break; TropicalFish tropicalFish = (TropicalFish) initialEntity; entityList.removeIf(entity -> ((TropicalFish) entity).getBodyColor() != tropicalFish.getBodyColor()); break; } case PHANTOM_SIZE: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13) || !(initialEntity instanceof Phantom)) break; Phantom phantom = (Phantom) initialEntity; entityList.removeIf(entity -> ((Phantom) entity).getSize() != phantom.getSize()); break; } case AXOLOTL_VARIANT: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_17) || !(initialEntity instanceof Axolotl)) break; Axolotl axolotl = (Axolotl) initialEntity; entityList.removeIf(entity -> ((Axolotl) entity).getVariant() != axolotl.getVariant()); break; } case GOAT_HAS_HORNS: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_17) || !(initialEntity instanceof Goat)) break; Goat goat = (Goat) initialEntity; boolean hasLeftHorn = goat.hasLeftHorn(); boolean hasRightHorn = goat.hasRightHorn(); entityList.removeIf(entity -> { Goat otherGoat = (Goat) entity; return otherGoat.hasLeftHorn() != hasLeftHorn || otherGoat.hasRightHorn() != hasRightHorn; }); break; } case FROG_VARIANT: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_19) || !(initialEntity instanceof Frog)) break; Frog frog = (Frog) initialEntity; entityList.removeIf(entity -> ((Frog) entity).getVariant() != frog.getVariant()); break; } case TADPOLE_AGE: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_19) || !(initialEntity instanceof Tadpole)) break; Tadpole tadpole = (Tadpole) initialEntity; entityList.removeIf(entity -> ((Tadpole) entity).getAge() != tadpole.getAge()); break; } case WARDEN_ANGER_LEVEL: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_19) || !(initialEntity instanceof Warden)) break; Warden warden = (Warden) initialEntity; entityList.removeIf(entity -> ((Warden) entity).getAnger() != warden.getAnger()); break; } case FOX_TYPE: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14) || !(initialEntity instanceof Fox)) break; Fox fox = (Fox) initialEntity; entityList.removeIf(entity -> ((Fox) entity).getFoxType() != fox.getFoxType()); break; } case HOGLIN_IMMUNE: { if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_16) || !(initialEntity instanceof Hoglin)) break; Hoglin hoglin = (Hoglin) initialEntity; if (hoglin.isImmuneToZombification()) { entityList.removeIf(entity -> !((Hoglin) entity).isImmuneToZombification()); } else { entityList.removeIf(entity -> ((Hoglin) entity).isImmuneToZombification()); } break; } } } if (initialEntity.hasMetadata("breedCooldown")) { entityList.removeIf(entity -> !entity.hasMetadata("breedCooldown")); } return entityList; } catch (Exception ex) { ex.printStackTrace(); } return new ArrayList<>(); } public boolean isEquipped(LivingEntity initialEntity) { if (initialEntity.getEquipment() == null) return false; EntityEquipment equipment = initialEntity.getEquipment(); return (equipment.getItemInHand().getType() != Material.AIR && !weaponsArentEquipment && !equipment.getItemInHand().getEnchantments().isEmpty() || (equipment.getHelmet() != null && equipment.getHelmet().getType() != Material.AIR) || (equipment.getChestplate() != null && equipment.getChestplate().getType() != Material.AIR) || (equipment.getLeggings() != null && equipment.getLeggings().getType() != Material.AIR) || (equipment.getBoots() != null && equipment.getBoots().getType() != Material.AIR)); } private int getEntityMaxStackSize(LivingEntity initialEntity) { return entityStackSizes.computeIfAbsent(initialEntity.getType(), type -> { int maxStackSize = configurationSection.getInt("Mobs." + initialEntity.getType().name() + ".Max Stack Size"); if (maxStackSize == -1) { maxStackSize = maxEntityStackSize; } return maxStackSize; }); } private boolean isMaxStack(LivingEntity livingEntity) { EntityStack stack = stackManager.getStackedEntity(livingEntity); if (stack == null) return false; return stack.getAmount() >= getEntityMaxStackSize(livingEntity); } public boolean canFly(LivingEntity entity) { switch (entity.getType()) { case GHAST: case BLAZE: case PHANTOM: case BAT: case BEE: return true; default: return false; } } }