2023-05-25 19:20:03 +02:00
|
|
|
package com.craftaro.ultimatestacker.tasks;
|
|
|
|
|
2023-06-30 22:28:37 +02:00
|
|
|
import com.craftaro.core.compatibility.ServerVersion;
|
|
|
|
import com.craftaro.core.hooks.WorldGuardHook;
|
2023-06-29 11:18:18 +02:00
|
|
|
import com.craftaro.core.third_party.com.cryptomorin.xseries.XMaterial;
|
2023-06-30 22:28:37 +02:00
|
|
|
import com.craftaro.core.world.SWorld;
|
2023-05-25 19:20:03 +02:00
|
|
|
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;
|
2018-11-06 04:33:10 +01:00
|
|
|
import org.bukkit.Bukkit;
|
2019-07-31 21:02:05 +02:00
|
|
|
import org.bukkit.Location;
|
2020-08-25 01:01:11 +02:00
|
|
|
import org.bukkit.Material;
|
2018-11-06 04:33:10 +01:00
|
|
|
import org.bukkit.World;
|
|
|
|
import org.bukkit.configuration.ConfigurationSection;
|
2022-10-26 22:04:01 +02:00
|
|
|
import org.bukkit.entity.AbstractHorse;
|
|
|
|
import org.bukkit.entity.Ageable;
|
|
|
|
import org.bukkit.entity.ArmorStand;
|
|
|
|
import org.bukkit.entity.Cat;
|
|
|
|
import org.bukkit.entity.ChestedHorse;
|
|
|
|
import org.bukkit.entity.Entity;
|
|
|
|
import org.bukkit.entity.EntityType;
|
|
|
|
import org.bukkit.entity.Horse;
|
|
|
|
import org.bukkit.entity.HumanEntity;
|
|
|
|
import org.bukkit.entity.LivingEntity;
|
|
|
|
import org.bukkit.entity.Llama;
|
|
|
|
import org.bukkit.entity.Ocelot;
|
|
|
|
import org.bukkit.entity.Parrot;
|
|
|
|
import org.bukkit.entity.Phantom;
|
|
|
|
import org.bukkit.entity.Pig;
|
|
|
|
import org.bukkit.entity.PufferFish;
|
|
|
|
import org.bukkit.entity.Rabbit;
|
|
|
|
import org.bukkit.entity.Sheep;
|
|
|
|
import org.bukkit.entity.Skeleton;
|
|
|
|
import org.bukkit.entity.Slime;
|
|
|
|
import org.bukkit.entity.Snowman;
|
|
|
|
import org.bukkit.entity.Tameable;
|
|
|
|
import org.bukkit.entity.TropicalFish;
|
|
|
|
import org.bukkit.entity.Villager;
|
|
|
|
import org.bukkit.entity.Wolf;
|
|
|
|
import org.bukkit.entity.Zombie;
|
2020-08-25 01:01:11 +02:00
|
|
|
import org.bukkit.inventory.EntityEquipment;
|
2018-11-06 04:33:10 +01:00
|
|
|
|
2020-09-01 20:44:39 +02:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Set;
|
2023-02-22 09:59:28 +01:00
|
|
|
import java.util.TimerTask;
|
2020-09-01 20:44:39 +02:00
|
|
|
import java.util.UUID;
|
2022-10-26 22:04:01 +02:00
|
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
|
import java.util.concurrent.ExecutionException;
|
2023-02-04 14:48:13 +01:00
|
|
|
import java.util.concurrent.Executors;
|
2022-10-26 22:04:01 +02:00
|
|
|
import java.util.concurrent.Future;
|
2023-02-04 14:48:13 +01:00
|
|
|
import java.util.concurrent.ScheduledExecutorService;
|
2023-02-22 09:59:28 +01:00
|
|
|
import java.util.concurrent.TimeUnit;
|
2018-11-06 04:33:10 +01:00
|
|
|
|
2023-05-30 11:21:46 +02:00
|
|
|
import static com.craftaro.ultimatestacker.stackable.entity.Check.getChecks;
|
2023-05-25 19:20:03 +02:00
|
|
|
|
2023-02-22 09:59:28 +01:00
|
|
|
public class StackingTask extends TimerTask {
|
2018-11-06 04:33:10 +01:00
|
|
|
|
2019-07-31 06:29:10 +02:00
|
|
|
private final UltimateStacker plugin;
|
2023-02-05 13:14:44 +01:00
|
|
|
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
|
2018-11-06 04:33:10 +01:00
|
|
|
|
2019-09-10 22:45:26 +02:00
|
|
|
private final EntityStackManager stackManager;
|
2019-06-27 20:34:33 +02:00
|
|
|
|
2020-08-25 01:01:11 +02:00
|
|
|
private final ConfigurationSection configurationSection = UltimateStacker.getInstance().getMobFile();
|
2019-09-10 22:45:26 +02:00
|
|
|
private final List<UUID> processed = new ArrayList<>();
|
2019-06-27 20:34:33 +02:00
|
|
|
|
2022-10-26 22:04:01 +02:00
|
|
|
private final Map<EntityType, Integer> entityStackSizes = new HashMap<>();
|
2020-08-25 01:01:11 +02:00
|
|
|
private final int maxEntityStackSize = Settings.MAX_STACK_ENTITIES.getInt(),
|
|
|
|
minEntityStackSize = Settings.MIN_STACK_ENTITIES.getInt(),
|
|
|
|
searchRadius = Settings.SEARCH_RADIUS.getInt(),
|
|
|
|
maxPerTypeStacksPerChunk = Settings.MAX_PER_TYPE_STACKS_PER_CHUNK.getInt();
|
|
|
|
private final List<String> disabledWorlds = Settings.DISABLED_WORLDS.getStringList(),
|
|
|
|
stackReasons = Settings.STACK_REASONS.getStringList();
|
2023-05-25 19:20:03 +02:00
|
|
|
private final List<Check> checks = getChecks(Settings.STACK_CHECKS.getStringList());
|
2020-08-25 01:01:11 +02:00
|
|
|
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();
|
2019-08-07 20:24:49 +02:00
|
|
|
|
2021-06-13 17:40:46 +02:00
|
|
|
Set<SWorld> loadedWorlds = new HashSet<>();
|
|
|
|
|
2019-07-31 06:29:10 +02:00
|
|
|
public StackingTask(UltimateStacker plugin) {
|
|
|
|
this.plugin = plugin;
|
|
|
|
this.stackManager = plugin.getEntityStackManager();
|
2021-06-13 17:40:46 +02:00
|
|
|
// Add loaded worlds.
|
|
|
|
for (World world : Bukkit.getWorlds())
|
|
|
|
loadedWorlds.add(new SWorld(world));
|
|
|
|
|
2020-08-25 01:01:11 +02:00
|
|
|
// Start the stacking task.
|
2023-02-04 14:48:13 +01:00
|
|
|
//runTaskTimerAsynchronously(plugin, 0, Settings.STACK_SEARCH_TICK_SPEED.getInt());
|
2023-02-22 09:59:28 +01:00
|
|
|
executorService.scheduleAtFixedRate(this, 0, (Settings.STACK_SEARCH_TICK_SPEED.getInt()*50L), TimeUnit.MILLISECONDS);
|
2023-02-04 14:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public void stop() {
|
|
|
|
executorService.shutdown();
|
2018-11-06 04:33:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
2023-02-22 09:59:28 +01:00
|
|
|
//make sure if the task running if any error occurs
|
|
|
|
try {
|
|
|
|
// Should entities be stacked?
|
|
|
|
if (!Settings.STACK_ENTITIES.getBoolean()) return;
|
|
|
|
|
|
|
|
// Loop through each world.
|
|
|
|
for (SWorld sWorld : loadedWorlds) {
|
|
|
|
// If world is disabled then continue to the next world.
|
|
|
|
if (isWorldDisabled(sWorld.getWorld())) continue;
|
|
|
|
|
|
|
|
// Get the loaded entities from the current world and reverse them.
|
|
|
|
List<LivingEntity> entities;
|
|
|
|
try {
|
|
|
|
entities = getLivingEntitiesSync(sWorld).get();
|
|
|
|
} catch (ExecutionException | InterruptedException ex) {
|
|
|
|
ex.printStackTrace();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Collections.reverse(entities);
|
2019-01-15 05:45:25 +01:00
|
|
|
|
2023-04-07 21:44:08 +02:00
|
|
|
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 (this.processed.contains(entity.getUniqueId())) continue;
|
|
|
|
|
|
|
|
// Check to see if entity is not stackable.
|
|
|
|
if (!isEntityStackable(entity)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get entity location to pass around as its faster this way.
|
|
|
|
Location location = entity.getLocation();
|
|
|
|
|
|
|
|
// Process the entity.
|
|
|
|
this.processEntity(entity, sWorld, location);
|
2023-02-22 09:59:28 +01:00
|
|
|
}
|
2023-04-07 21:44:08 +02:00
|
|
|
});
|
2019-06-27 20:34:33 +02:00
|
|
|
}
|
2023-02-22 09:59:28 +01:00
|
|
|
// Clear caches in preparation for the next run.
|
|
|
|
this.processed.clear();
|
|
|
|
} catch (Exception ignored) {}
|
2019-06-27 20:34:33 +02:00
|
|
|
}
|
2018-11-06 04:33:10 +01:00
|
|
|
|
2022-10-26 22:04:01 +02:00
|
|
|
private Future<List<LivingEntity>> getLivingEntitiesSync(SWorld sWorld) {
|
|
|
|
CompletableFuture<List<LivingEntity>> future = new CompletableFuture<>();
|
|
|
|
Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> future.complete(sWorld.getLivingEntities()));
|
|
|
|
|
|
|
|
return future;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Future<Entity[]> getEntitiesInChunkSync(CachedChunk cachedChunk) {
|
|
|
|
CompletableFuture<Entity[]> future = new CompletableFuture<>();
|
2023-04-07 21:44:08 +02:00
|
|
|
Bukkit.getScheduler().runTask(this.plugin, () -> future.complete(cachedChunk.getEntities()));
|
2022-10-26 22:04:01 +02:00
|
|
|
return future;
|
|
|
|
}
|
|
|
|
|
2019-07-31 21:47:01 +02:00
|
|
|
public boolean isWorldDisabled(World world) {
|
|
|
|
return disabledWorlds.stream().anyMatch(worldStr -> world.getName().equalsIgnoreCase(worldStr));
|
|
|
|
}
|
|
|
|
|
2020-08-25 01:01:11 +02:00
|
|
|
private boolean isEntityStackable(Entity entity) {
|
2019-07-19 02:49:41 +02:00
|
|
|
// Make sure we have the correct entity type and that it is valid.
|
|
|
|
if (!entity.isValid()
|
|
|
|
|| entity instanceof HumanEntity
|
|
|
|
|| entity instanceof ArmorStand
|
2019-06-28 05:14:40 +02:00
|
|
|
|
2019-07-19 02:49:41 +02:00
|
|
|
// Make sure the entity is not in love.
|
|
|
|
|| entity.hasMetadata("inLove")
|
|
|
|
// Or in breeding cooldown.
|
2019-08-19 03:48:35 +02:00
|
|
|
|| entity.hasMetadata("breedCooldown"))
|
|
|
|
return false;
|
2019-07-19 02:49:41 +02:00
|
|
|
|
2023-05-05 10:57:07 +02:00
|
|
|
if (!configurationSection.getBoolean("Mobs." + entity.getType().name() + ".Enabled")) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-08-19 03:48:35 +02:00
|
|
|
// Allow spawn if stackreasons are set and match, or if from a spawner
|
2019-10-24 00:18:42 +02:00
|
|
|
final String spawnReason = entity.hasMetadata("US_REASON") && !entity.getMetadata("US_REASON").isEmpty()
|
|
|
|
? entity.getMetadata("US_REASON").get(0).asString() : null;
|
2019-08-19 03:48:35 +02:00
|
|
|
List<String> stackReasons;
|
2019-12-11 23:31:32 +01:00
|
|
|
if (onlyStackFromSpawners) {
|
2019-08-19 03:48:35 +02:00
|
|
|
// If only stack from spawners is enabled, make sure the entity spawned from a spawner.
|
|
|
|
if (!"SPAWNER".equals(spawnReason))
|
|
|
|
return false;
|
2023-02-22 09:59:28 +01:00
|
|
|
} else if (!(stackReasons = this.stackReasons).isEmpty() && !stackReasons.contains(spawnReason)) {
|
2019-08-19 03:48:35 +02:00
|
|
|
// Only stack if on the list of events to stack
|
2019-06-28 05:14:40 +02:00
|
|
|
return false;
|
2023-02-22 09:59:28 +01:00
|
|
|
}
|
2019-06-28 05:14:40 +02:00
|
|
|
|
2019-07-19 02:49:41 +02:00
|
|
|
// Cast our entity to living entity.
|
|
|
|
LivingEntity livingEntity = (LivingEntity) entity;
|
2019-06-15 11:05:53 +02:00
|
|
|
|
2019-07-19 02:49:41 +02:00
|
|
|
// If only stack on surface is enabled make sure the entity is on a surface then entity is stackable.
|
2019-12-11 23:31:32 +01:00
|
|
|
return !onlyStackOnSurface
|
2020-08-25 01:01:11 +02:00
|
|
|
|| canFly(livingEntity)
|
2019-08-09 02:52:41 +02:00
|
|
|
|| entity.getType().name().equals("SHULKER")
|
2020-05-03 22:14:26 +02:00
|
|
|
|
|
|
|
|| (livingEntity.isOnGround()
|
|
|
|
|| (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
|
|
|
|
&& livingEntity.isSwimming()));
|
2019-06-27 20:34:33 +02:00
|
|
|
|
2019-07-19 02:49:41 +02:00
|
|
|
}
|
2019-06-27 20:34:33 +02:00
|
|
|
|
2023-02-04 14:48:13 +01:00
|
|
|
private void processEntity(LivingEntity baseEntity, SWorld sWorld, Location location) {
|
|
|
|
|
|
|
|
// Check our WorldGuard flag.
|
|
|
|
Boolean flag = WorldGuardHook.isEnabled() ? WorldGuardHook.getBooleanFlag(baseEntity.getLocation(), "mob-stacking") : null;
|
|
|
|
if (flag != null && !flag) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-07-19 02:49:41 +02:00
|
|
|
// Get the stack from the entity. It should be noted that this value will
|
|
|
|
// be null if our entity is not a stack.
|
2023-05-25 19:20:03 +02:00
|
|
|
EntityStack baseStack = plugin.getEntityStackManager().getStackedEntity(baseEntity);
|
2023-02-04 14:48:13 +01:00
|
|
|
|
|
|
|
// Get the maximum stack size for this entity.
|
|
|
|
int maxEntityStackSize = getEntityStackSize(baseEntity);
|
2019-06-27 20:34:33 +02:00
|
|
|
|
2019-07-19 02:49:41 +02:00
|
|
|
// Is this entity stacked?
|
2023-02-04 14:48:13 +01:00
|
|
|
boolean isStack = baseStack != null;
|
|
|
|
|
|
|
|
if (isStack && baseStack.getAmount() == maxEntityStackSize) {
|
|
|
|
// If the stack is already at the max size then we can skip it.
|
2023-02-22 09:59:28 +01:00
|
|
|
processed.add(baseEntity.getUniqueId());
|
2023-02-04 14:48:13 +01:00
|
|
|
return;
|
|
|
|
}
|
2018-11-06 04:33:10 +01:00
|
|
|
|
2019-07-19 02:49:41 +02:00
|
|
|
// The amount that is stackable.
|
2023-02-04 14:48:13 +01:00
|
|
|
int amountToStack = isStack ? baseStack.getAmount() : 1;
|
2019-06-27 20:34:33 +02:00
|
|
|
|
2019-07-19 02:49:41 +02:00
|
|
|
// Attempt to split our stack. If the split is successful then skip this entity.
|
2023-02-04 14:48:13 +01:00
|
|
|
if (isStack && attemptSplit(baseStack, baseEntity)) return;
|
2019-07-19 02:49:41 +02:00
|
|
|
|
2020-10-26 18:41:54 +01:00
|
|
|
// If this entity is named, a custom entity or disabled then skip it.
|
2023-02-04 14:48:13 +01:00
|
|
|
if (!isStack && (baseEntity.getCustomName() != null
|
|
|
|
&& plugin.getCustomEntityManager().getCustomEntity(baseEntity) == null)
|
2023-02-22 09:59:28 +01:00
|
|
|
|| !configurationSection.getBoolean("Mobs." + baseEntity.getType().name() + ".Enabled")) {
|
|
|
|
processed.add(baseEntity.getUniqueId());
|
2019-07-19 02:49:41 +02:00
|
|
|
return;
|
2023-02-22 09:59:28 +01:00
|
|
|
}
|
2019-07-19 02:49:41 +02:00
|
|
|
|
|
|
|
// Get similar entities around our entity and make sure those entities are both compatible and stackable.
|
2020-04-05 23:15:08 +02:00
|
|
|
List<LivingEntity> stackableFriends = new LinkedList<>();
|
2023-04-07 21:44:08 +02:00
|
|
|
List<LivingEntity> list = getSimilarEntitiesAroundEntity(baseEntity, sWorld, location);
|
|
|
|
for (LivingEntity entity : list) {
|
2020-08-25 01:01:11 +02:00
|
|
|
// Check to see if entity is not stackable.
|
|
|
|
if (!isEntityStackable(entity))
|
|
|
|
continue;
|
|
|
|
// Add this entity to our stackable friends.
|
2020-04-05 23:15:08 +02:00
|
|
|
stackableFriends.add(entity);
|
|
|
|
}
|
2019-07-19 02:49:41 +02:00
|
|
|
|
|
|
|
// Loop through our similar stackable entities.
|
2023-02-04 14:48:13 +01:00
|
|
|
for (LivingEntity friendlyEntity : stackableFriends) {
|
|
|
|
// Make sure the friendlyEntity has not already been processed.
|
|
|
|
if (this.processed.contains(friendlyEntity.getUniqueId())) continue;
|
2019-09-18 18:02:35 +02:00
|
|
|
|
2019-07-19 02:49:41 +02:00
|
|
|
// Get this entities friendStack.
|
2023-05-25 19:20:03 +02:00
|
|
|
EntityStack friendStack = stackManager.getStackedEntity(friendlyEntity);
|
2023-02-22 09:59:28 +01:00
|
|
|
int amount = friendStack != null ? friendStack.getAmount() : 1;
|
2019-06-28 22:00:30 +02:00
|
|
|
|
2023-02-04 14:48:13 +01:00
|
|
|
// Check to see if this friendlyEntity is stacked and friendStack plus
|
|
|
|
// our amount to stack is not above our max friendStack size
|
|
|
|
// for this friendlyEntity.
|
2019-06-30 01:32:16 +02:00
|
|
|
|
2023-02-22 09:59:28 +01:00
|
|
|
boolean overstack = (amount + amountToStack) > maxEntityStackSize;
|
2019-06-28 22:00:30 +02:00
|
|
|
|
2023-02-04 14:48:13 +01:00
|
|
|
if (!overstack) {
|
2023-05-25 19:20:03 +02:00
|
|
|
stackManager.createStackedEntity(friendlyEntity, amount + amountToStack);
|
2023-02-04 14:48:13 +01:00
|
|
|
processed.add(baseEntity.getUniqueId());
|
2023-02-22 09:59:28 +01:00
|
|
|
|
|
|
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
|
|
|
if (baseEntity.isLeashed()) {
|
2023-06-29 11:18:18 +02:00
|
|
|
baseEntity.getWorld().dropItemNaturally(baseEntity.getLocation(), XMaterial.LEAD.parseItem());
|
2023-02-22 09:59:28 +01:00
|
|
|
}
|
|
|
|
baseEntity.remove();
|
|
|
|
});
|
2019-07-19 02:49:41 +02:00
|
|
|
return;
|
2018-11-06 04:33:10 +01:00
|
|
|
}
|
|
|
|
}
|
2019-07-19 02:49:41 +02:00
|
|
|
}
|
|
|
|
|
2023-02-04 14:48:13 +01:00
|
|
|
public boolean attemptSplit(EntityStack baseStack, LivingEntity livingEntity) {
|
|
|
|
int stackSize = baseStack.getAmount();
|
2019-07-19 02:50:07 +02:00
|
|
|
int maxEntityStackAmount = getEntityStackSize(livingEntity);
|
2019-07-17 14:56:50 +02:00
|
|
|
|
2019-07-19 02:50:07 +02:00
|
|
|
if (stackSize <= maxEntityStackAmount) return false;
|
2019-07-19 02:50:29 +02:00
|
|
|
|
2023-02-04 14:48:13 +01:00
|
|
|
baseStack.setAmount(maxEntityStackAmount);
|
2019-07-17 14:56:50 +02:00
|
|
|
|
2020-08-25 01:01:11 +02:00
|
|
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
2023-02-04 14:48:13 +01:00
|
|
|
int finalStackSize = stackSize - maxEntityStackAmount;
|
|
|
|
do {
|
|
|
|
// Create a new stack, summon entity and add to stack.
|
|
|
|
LivingEntity newEntity = (LivingEntity) livingEntity.getWorld().spawnEntity(livingEntity.getLocation(), livingEntity.getType());
|
|
|
|
int toAdd = Math.min(finalStackSize, maxEntityStackAmount);
|
2023-05-25 19:20:03 +02:00
|
|
|
EntityStack newStack = stackManager.createStackedEntity(newEntity, toAdd);
|
2023-02-04 14:48:13 +01:00
|
|
|
processed.add(newEntity.getUniqueId());
|
|
|
|
finalStackSize -= maxEntityStackAmount;
|
|
|
|
} while (finalStackSize >= 0);
|
2020-08-25 01:01:11 +02:00
|
|
|
});
|
2019-07-19 02:50:07 +02:00
|
|
|
|
2023-02-22 09:59:28 +01:00
|
|
|
//Mark it as processed.
|
2019-07-19 02:50:07 +02:00
|
|
|
processed.add(livingEntity.getUniqueId());
|
|
|
|
return true;
|
2019-07-17 14:56:50 +02:00
|
|
|
}
|
|
|
|
|
2021-06-13 17:40:46 +02:00
|
|
|
private Set<CachedChunk> getNearbyChunks(SWorld sWorld, Location location, double radius, boolean singleChunk) {
|
2023-02-22 09:59:28 +01:00
|
|
|
//get current chunk
|
|
|
|
if (radius == -1) {
|
|
|
|
return new HashSet<>(Collections.singletonList(new CachedChunk(sWorld, location.getChunk().getX(), location.getChunk().getZ())));
|
|
|
|
}
|
2020-08-25 01:01:11 +02:00
|
|
|
World world = location.getWorld();
|
|
|
|
Set<CachedChunk> chunks = new HashSet<>();
|
|
|
|
if (world == null) return chunks;
|
|
|
|
|
2021-06-13 17:40:46 +02:00
|
|
|
CachedChunk firstChunk = new CachedChunk(sWorld, location);
|
2020-08-25 01:01:11 +02:00
|
|
|
chunks.add(firstChunk);
|
|
|
|
|
|
|
|
if (singleChunk) return chunks;
|
|
|
|
|
|
|
|
int minX = (int) Math.floor(((location.getX() - radius) - 2.0D) / 16.0D);
|
|
|
|
int maxX = (int) Math.floor(((location.getX() + radius) + 2.0D) / 16.0D);
|
|
|
|
int minZ = (int) Math.floor(((location.getZ() - radius) - 2.0D) / 16.0D);
|
|
|
|
int maxZ = (int) Math.floor(((location.getZ() + radius) + 2.0D) / 16.0D);
|
|
|
|
|
|
|
|
for (int x = minX; x <= maxX; ++x) {
|
|
|
|
for (int z = minZ; z <= maxZ; ++z) {
|
|
|
|
if (firstChunk.getX() == x && firstChunk.getZ() == z) continue;
|
2021-06-13 17:40:46 +02:00
|
|
|
chunks.add(new CachedChunk(sWorld, x, z));
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return chunks;
|
|
|
|
}
|
|
|
|
|
2023-02-22 09:59:28 +01:00
|
|
|
/**
|
|
|
|
* Get all entities around an entity within a radius which are similar to the entity.
|
|
|
|
* @param entity The entity to get similar entities around.
|
|
|
|
* @param radius The radius to get entities around.
|
|
|
|
* @param singleChunk Whether to only get entities in the same chunk as the entity.
|
|
|
|
* @return A list of similar entities around the entity.
|
|
|
|
*/
|
|
|
|
private List<LivingEntity> getFriendlyStacksNearby(LivingEntity entity, double radius, boolean singleChunk) {
|
2020-08-25 01:01:11 +02:00
|
|
|
List<LivingEntity> entities = new ArrayList<>();
|
2023-03-29 21:21:00 +02:00
|
|
|
try {
|
|
|
|
Set<CachedChunk> chunks = getNearbyChunks(new SWorld(entity.getWorld()), entity.getLocation(), radius, singleChunk);
|
|
|
|
for (CachedChunk chunk : chunks) {
|
2023-04-07 21:44:08 +02:00
|
|
|
Entity[] entityList = chunk.getEntities();
|
2023-03-29 21:21:00 +02:00
|
|
|
for (Entity e : entityList) {
|
|
|
|
if (!processed.contains(e.getUniqueId()) && e.getType() == entity.getType() && e instanceof LivingEntity && e.isValid() && e.getLocation().distance(entity.getLocation()) <= radius) {
|
|
|
|
entities.add((LivingEntity) e);
|
|
|
|
}
|
2020-09-07 16:49:08 +02:00
|
|
|
}
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
2023-03-29 21:21:00 +02:00
|
|
|
entities.removeIf(entity1 -> entity1.equals(entity) || !UltimateStacker.getInstance().getCustomEntityManager().isStackable(entity1));
|
|
|
|
} catch (Exception ex) {
|
|
|
|
ex.printStackTrace();
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
|
|
|
return entities;
|
|
|
|
}
|
|
|
|
|
2021-06-13 17:40:46 +02:00
|
|
|
public List<LivingEntity> getSimilarEntitiesAroundEntity(LivingEntity initialEntity, SWorld sWorld, Location location) {
|
2023-04-07 21:44:08 +02:00
|
|
|
try {
|
|
|
|
// Create a list of all entities around the initial entity of the same type.
|
|
|
|
List<LivingEntity> entityList = new LinkedList<>(getFriendlyStacksNearby(initialEntity, searchRadius, stackWholeChunk));
|
|
|
|
|
|
|
|
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"));
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
2023-04-07 21:44:08 +02:00
|
|
|
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;
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
2023-04-07 21:44:08 +02:00
|
|
|
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;
|
2020-08-25 01:01:11 +02:00
|
|
|
|
2023-04-07 21:44:08 +02:00
|
|
|
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;
|
2020-08-25 01:01:11 +02:00
|
|
|
|
2023-04-07 21:44:08 +02:00
|
|
|
Skeleton skeleton = (Skeleton) initialEntity;
|
|
|
|
entityList.removeIf(entity -> ((Skeleton) entity).getSkeletonType() != skeleton.getSkeletonType());
|
|
|
|
break;
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
2023-04-07 21:44:08 +02:00
|
|
|
case SHEEP_COLOR: {
|
|
|
|
if (!(initialEntity instanceof Sheep)) break;
|
2020-08-25 01:01:11 +02:00
|
|
|
|
2023-04-07 21:44:08 +02:00
|
|
|
Sheep sheep = ((Sheep) initialEntity);
|
|
|
|
entityList.removeIf(entity -> ((Sheep) entity).getColor() != sheep.getColor());
|
|
|
|
break;
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
2023-04-07 21:44:08 +02:00
|
|
|
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: {
|
2020-08-25 01:01:11 +02:00
|
|
|
if (!(initialEntity instanceof Horse)) break;
|
2023-04-07 21:44:08 +02:00
|
|
|
entityList.removeIf(entity -> ((Horse) entity).getInventory().getArmor() != null);
|
|
|
|
break;
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
2023-04-07 21:44:08 +02:00
|
|
|
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);
|
2020-08-25 01:01:11 +02:00
|
|
|
break;
|
|
|
|
}
|
2023-04-07 21:44:08 +02:00
|
|
|
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: {
|
2020-08-25 01:01:11 +02:00
|
|
|
if (!(initialEntity instanceof Horse)) break;
|
|
|
|
Horse horse = ((Horse) initialEntity);
|
2023-04-07 21:44:08 +02:00
|
|
|
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;
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-07 21:44:08 +02:00
|
|
|
if (initialEntity.hasMetadata("breedCooldown")) {
|
|
|
|
entityList.removeIf(entity -> !entity.hasMetadata("breedCooldown"));
|
|
|
|
}
|
|
|
|
return entityList;
|
|
|
|
} catch (Exception ex) {
|
|
|
|
ex.printStackTrace();
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
2023-04-07 21:44:08 +02:00
|
|
|
return new ArrayList<>();
|
2020-08-25 01:01:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isEquipped(LivingEntity initialEntity) {
|
|
|
|
if (initialEntity.getEquipment() == null) return false;
|
|
|
|
EntityEquipment equipment = initialEntity.getEquipment();
|
2019-07-19 02:49:41 +02:00
|
|
|
|
2020-08-25 01:01:11 +02:00
|
|
|
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));
|
2019-06-28 22:00:30 +02:00
|
|
|
}
|
|
|
|
|
2019-07-19 02:49:41 +02:00
|
|
|
private int getEntityStackSize(LivingEntity initialEntity) {
|
2019-09-10 22:45:26 +02:00
|
|
|
Integer max = entityStackSizes.get(initialEntity.getType());
|
2020-05-03 22:14:26 +02:00
|
|
|
if (max == null) {
|
2019-09-10 22:45:26 +02:00
|
|
|
max = configurationSection.getInt("Mobs." + initialEntity.getType().name() + ".Max Stack Size");
|
2020-05-03 22:14:26 +02:00
|
|
|
if (max == -1) {
|
2019-09-10 22:45:26 +02:00
|
|
|
max = maxEntityStackSize;
|
|
|
|
}
|
|
|
|
entityStackSizes.put(initialEntity.getType(), max);
|
|
|
|
}
|
|
|
|
return max;
|
2019-06-30 01:32:16 +02:00
|
|
|
}
|
2020-08-25 01:01:11 +02:00
|
|
|
|
|
|
|
public boolean canFly(LivingEntity entity) {
|
|
|
|
switch (entity.getType()) {
|
|
|
|
case GHAST:
|
|
|
|
case BLAZE:
|
|
|
|
case PHANTOM:
|
|
|
|
case BAT:
|
|
|
|
case BEE:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2018-11-06 04:33:10 +01:00
|
|
|
}
|