Improve stacking task speed. Add some new settings and instant stacking

This commit is contained in:
ceze88 2024-02-02 15:58:40 +01:00
parent 711d153fcf
commit 7120f9895a
11 changed files with 367 additions and 160 deletions

View File

@ -101,6 +101,7 @@ public class UltimateStacker extends SongodaPlugin {
private StackingTask stackingTask;
private UltimateStackerApi API;
private SuperiorSkyblock2Hook superiorSkyblock2Hook;
private boolean instantStacking;
public static UltimateStacker getInstance() {
return INSTANCE;
@ -212,6 +213,7 @@ public class UltimateStacker extends SongodaPlugin {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13))
pluginManager.registerEvents(new EntityCurrentListener(this), this);
this.instantStacking = Settings.STACK_ENTITIES.getBoolean() && Settings.INSTANT_STACKING.getBoolean();
pluginManager.registerEvents(new EntityListeners(this), this);
pluginManager.registerEvents(new ItemListeners(this), this);
@ -262,7 +264,11 @@ public class UltimateStacker extends SongodaPlugin {
}
});
this.stackingTask = new StackingTask(this);
if (Settings.STACK_ENTITIES.getBoolean()) {
//Start stacking task
this.stackingTask = new StackingTask(this);
}
this.instantStacking = Settings.STACK_ENTITIES.getBoolean() && Settings.INSTANT_STACKING.getBoolean();
final boolean useBlockHolo = Settings.BLOCK_HOLOGRAMS.getBoolean();
this.dataManager.loadBatch(BlockStackImpl.class, "blocks").forEach((data) -> {
BlockStack blockStack = (BlockStack) data;
@ -301,8 +307,12 @@ public class UltimateStacker extends SongodaPlugin {
this.setLocale(getConfig().getString("System.Language Mode"), true);
this.locale.reloadMessages();
this.stackingTask.stop();
this.stackingTask = new StackingTask(this);
if (stackingTask != null) {
this.stackingTask.stop();
}
if (Settings.STACK_ENTITIES.getBoolean()) {
this.stackingTask = new StackingTask(this);
}
this.mobFile.load();
this.itemFile.load();
@ -409,6 +419,10 @@ public class UltimateStacker extends SongodaPlugin {
return superiorSkyblock2Hook;
}
public boolean isInstantStacking() {
return instantStacking;
}
//////// Convenient API //////////
/**

View File

@ -0,0 +1,69 @@
package com.craftaro.ultimatestacker.commands;
import com.craftaro.core.commands.AbstractCommand;
import com.craftaro.core.world.SSpawner;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatestacker.UltimateStacker;
import com.craftaro.ultimatestacker.api.stack.spawner.SpawnerStack;
import org.bukkit.block.Block;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
public class CommandForceSpawn extends AbstractCommand {
private final UltimateStacker plugin;
public CommandForceSpawn(UltimateStacker plugin) {
super(CommandType.PLAYER_ONLY, "forcespawn");
this.plugin = plugin;
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
Player player = (Player) sender;
Block block = player.getTargetBlock(null, 200);
if (XMaterial.matchXMaterial(block.getType().name()).get() != XMaterial.SPAWNER) {
this.plugin.getLocale().newMessage("&cThat is not a spawner...").sendPrefixedMessage(player);
return ReturnType.FAILURE;
}
//PlacedSpawner spawner = this.plugin.getSpawnerManager().getSpawnerFromWorld(block.getLocation());
SpawnerStack spawner = this.plugin.getSpawnerStackManager().getSpawner(block.getLocation());
if (spawner == null) {
//it is a vanilla spawner
CreatureSpawner vanillaSpawner = (CreatureSpawner) block.getState();
SSpawner creatureSpawner = new SSpawner(block.getLocation());
creatureSpawner.spawn(vanillaSpawner.getSpawnCount(), vanillaSpawner.getSpawnedType());
} else {
//it is an epic spawner
spawner.spawn();
}
this.plugin.getLocale().newMessage("&aSpawning successful.").sendPrefixedMessage(player);
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(CommandSender sender, String... args) {
return Collections.emptyList();
}
@Override
public String getPermissionNode() {
return "ultimatestacker.admin.forcespawn";
}
@Override
public String getSyntax() {
return "forcespawn";
}
@Override
public String getDescription() {
return "Force the spawner you are looking at to spawn so long as the spawn conditions are met.";
}
}

View File

@ -4,6 +4,7 @@ import com.craftaro.core.commands.AbstractCommand;
import com.craftaro.core.utils.TextUtils;
import com.craftaro.ultimatestacker.UltimateStacker;
import com.craftaro.ultimatestacker.api.stack.entity.EntityStack;
import com.craftaro.ultimatestacker.tasks.StackingTask;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
@ -51,9 +52,12 @@ public class CommandSpawn extends AbstractCommand {
}
sender.sendMessage(TextUtils.formatText("&6" + list));
} else {
LivingEntity entity = (LivingEntity)player.getWorld().spawnEntity(player.getLocation(), type);
EntityStack stack = plugin.getEntityStackManager().createStackedEntity(entity, Integer.parseInt(args[1]));
plugin.getStackingTask().attemptSplit(stack, entity);
StackingTask stackingTask = plugin.getStackingTask();
if (stackingTask != null) {
LivingEntity entity = (LivingEntity)player.getWorld().spawnEntity(player.getLocation(), type);
EntityStack stack = plugin.getEntityStackManager().createStackedEntity(entity, Integer.parseInt(args[1]));
stackingTask.attemptSplit(stack, -1);
}
}
return ReturnType.SUCCESS;

View File

@ -1,6 +1,7 @@
package com.craftaro.ultimatestacker.listeners;
import com.craftaro.ultimatestacker.UltimateStacker;
import com.craftaro.ultimatestacker.api.stack.entity.EntityStack;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@ -17,6 +18,24 @@ public class BreedListeners implements Listener {
@EventHandler
public void onBread(EntityBreedEvent event) {
//TODO: Fix breed. It removes a entity from the stack but not spawn a new one. (Splitting mechanic)
boolean isMotherStacked = plugin.getEntityStackManager().isStackedEntity(event.getMother());
boolean isFatherStacked = plugin.getEntityStackManager().isStackedEntity(event.getFather());
if (!isMotherStacked && !isFatherStacked) return;
if (isMotherStacked) {
EntityStack stack = plugin.getEntityStackManager().getStackedEntity(event.getMother());
if (stack.getAmount() <= 1) return;
stack.releaseHost();
}
if (isFatherStacked) {
EntityStack stack = plugin.getEntityStackManager().getStackedEntity(event.getFather());
if (stack.getAmount() <= 1) return;
stack.releaseHost();
}
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
event.getFather().removeMetadata("breedCooldown", plugin);
event.getMother().removeMetadata("breedCooldown", plugin);

View File

@ -1,5 +1,6 @@
package com.craftaro.ultimatestacker.listeners.entity;
import com.craftaro.core.configuration.Config;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatestacker.UltimateStacker;
import com.craftaro.ultimatestacker.api.UltimateStackerApi;
@ -8,6 +9,7 @@ import com.craftaro.ultimatestacker.api.stack.entity.EntityStackManager;
import com.craftaro.ultimatestacker.api.stack.item.StackedItem;
import com.craftaro.ultimatestacker.api.stack.spawner.SpawnerStack;
import com.craftaro.ultimatestacker.settings.Settings;
import com.craftaro.ultimatestacker.tasks.StackingTask;
import com.craftaro.ultimatestacker.utils.Methods;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@ -38,9 +40,12 @@ import java.util.List;
public class EntityListeners implements Listener {
private final UltimateStacker plugin;
private int searchRadius = Settings.SEARCH_RADIUS.getInt() * 16; //SEARCH_RADIUS is in chunks, so multiply by 16 to get blocks
private Config mobsConfig;
public EntityListeners(UltimateStacker plugin) {
this.plugin = plugin;
mobsConfig = plugin.getMobFile();
}
@EventHandler
@ -73,9 +78,7 @@ public class EntityListeners implements Listener {
item.setAmount(Math.min(amount, item.getMaxStackSize()));
if (amount > item.getMaxStackSize()) {
StackedItem stackedItem = UltimateStackerApi.getStackedItemManager().getStackedItem(event.getEntity());
if (stackedItem != null) {
stackedItem.setAmount(amount - item.getMaxStackSize());
}
stackedItem.setAmount(amount - item.getMaxStackSize());
}
event.getEntity().setItemStack(item);
}
@ -102,7 +105,31 @@ public class EntityListeners implements Listener {
@EventHandler
public void onSpawn(CreatureSpawnEvent event) {
event.getEntity().setMetadata("US_REASON", new FixedMetadataValue(plugin, event.getSpawnReason().name()));
String spawnReason = event.getSpawnReason().name();
if (plugin.isInstantStacking()) {
LivingEntity spawningEntity = event.getEntity();
EntityStackManager stackManager = plugin.getEntityStackManager();
if (stackManager.isStackedEntity(spawningEntity)) return; //We don't want to stack split entities or respawned stacks
List<LivingEntity> stackableFriends = plugin.getStackingTask().getSimilarEntitiesAroundEntity(spawningEntity, spawningEntity.getLocation());
if (stackableFriends.isEmpty()) {
event.getEntity().setMetadata("US_REASON", new FixedMetadataValue(plugin, spawnReason));
return;
}
LivingEntity friendStack = stackableFriends.get(0);
if (stackManager.isStackedEntity(friendStack)) {
EntityStack stack = stackManager.getStackedEntity(friendStack);
//getSimilarEntitiesAroundEntity check for max stack size, we don't need to check again
stack.add(1);
event.setCancelled(true);
} else {
stackManager.createStackedEntity(friendStack, 2);
}
return;
}
event.getEntity().setMetadata("US_REASON", new FixedMetadataValue(plugin, spawnReason));
}
@EventHandler

View File

@ -67,6 +67,7 @@ public class LootablesManager {
//Apply SuperiorSkyblock2 mob-drops multiplier if present
if (superiorSkyblock2Hook.isEnabled()) {
for (Drop drop : toDrop) {
if (drop.getItemStack() == null) continue; //Maybe it is just exp
drop.getItemStack().setAmount(superiorSkyblock2Hook.getDropMultiplier(entity.getLocation()) * drop.getItemStack().getAmount());
}
}

View File

@ -19,6 +19,9 @@ public class Settings implements com.craftaro.ultimatestacker.api.Settings {
"The speed in which a new stacks will be created.",
"It is advised to keep this number low.");
public static final ConfigSetting INSTANT_STACKING = new ConfigSetting(config, "Main.Instant Stacking", false,
"Should entities stacked into existing stacks before they spawned?");
public static final ConfigSetting DISABLED_WORLDS = new ConfigSetting(config, "Main.Disabled Worlds", Arrays.asList("World1", "World2", "World3"),
"Worlds that stacking doesn't happen in.");
@ -45,8 +48,10 @@ public class Settings implements com.craftaro.ultimatestacker.api.Settings {
"The maximum amount of each entity type stack allowed in a chunk.");
public static final ConfigSetting STACK_WHOLE_CHUNK = new ConfigSetting(config, "Entities.Stack Whole Chunk", false,
"Should all qualifying entities in each chunk be stacked?",
"This will override the stacking radius.");
"Should all qualifying entities in each chunk be stacked?");
public static final ConfigSetting STACK_WHOLE_CHUNK_RADIUS = new ConfigSetting(config, "Entities.Stack Whole Chunk Radius", 1,
"Radius in chunks every direction around the entity that will be stacked.", "0 means only the chunk the entity is in.");
public static final ConfigSetting ENTITY_NAMETAGS = new ConfigSetting(config, "Entities.Holograms Enabled", true,
"Should holograms be displayed above stacked entities?");

View File

@ -15,6 +15,7 @@ import com.craftaro.ultimatestacker.utils.Methods;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.ExperienceOrb;
import org.bukkit.entity.LivingEntity;
@ -22,6 +23,8 @@ import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.Vector;
import java.math.BigDecimal;
@ -33,6 +36,7 @@ import java.util.UUID;
public class EntityStackImpl implements EntityStack {
private final UltimateStacker plugin = UltimateStacker.getInstance();
private final NamespacedKey STACKED_ENTITY_KEY = new NamespacedKey(plugin, "US_AMOUNT");
private int amount;
private LivingEntity hostEntity;
@ -43,15 +47,42 @@ public class EntityStackImpl implements EntityStack {
public EntityStackImpl(LivingEntity entity) {
if (entity == null) return;
if (!UltimateStacker.getInstance().getEntityStackManager().isStackedEntity(entity)) {
entity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), 1));
this.amount = 1;
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14)) {
PersistentDataContainer container = entity.getPersistentDataContainer();
if (container.has(STACKED_ENTITY_KEY, PersistentDataType.INTEGER)) {
this.amount = container.get(STACKED_ENTITY_KEY, PersistentDataType.INTEGER);
entity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), amount));
} else {
entity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), 1));
this.amount = 1;
}
} else {
entity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), 1));
this.amount = 1;
}
} else {
//get the amount from the entity
this.amount = entity.getMetadata("US_AMOUNT").get(0).asInt();
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14)) {
PersistentDataContainer container = entity.getPersistentDataContainer();
if (container.has(STACKED_ENTITY_KEY, PersistentDataType.INTEGER)) {
this.amount = container.get(STACKED_ENTITY_KEY, PersistentDataType.INTEGER);
} else {
this.amount = getMetaCount(entity);
}
} else {
this.amount = getMetaCount(entity);
}
}
this.hostEntity = entity;
}
private int getMetaCount(LivingEntity entity) {
if (entity.hasMetadata("US_AMOUNT")) {
return entity.getMetadata("US_AMOUNT").get(0).asInt();
} else {
return 1;
}
}
/**
* Creates a new stack or overrides an existing stack.
* @param entity The entity to create the stack for.
@ -60,8 +91,16 @@ public class EntityStackImpl implements EntityStack {
public EntityStackImpl(LivingEntity entity, int amount) {
if (entity == null) return;
this.hostEntity = entity;
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be greater than 0");
}
this.amount = amount;
entity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), amount));
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14)) {
PersistentDataContainer container = entity.getPersistentDataContainer();
container.set(STACKED_ENTITY_KEY, PersistentDataType.INTEGER, amount);
} else {
entity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), amount));
}
updateNameTag();
}
@ -78,7 +117,12 @@ public class EntityStackImpl implements EntityStack {
@Override
public void setAmount(int amount) {
this.amount = amount;
this.hostEntity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), amount));
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14)) {
PersistentDataContainer container = hostEntity.getPersistentDataContainer();
container.set(STACKED_ENTITY_KEY, PersistentDataType.INTEGER, amount);
} else {
hostEntity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), amount));
}
updateNameTag();
}

View File

@ -1,13 +1,17 @@
package com.craftaro.ultimatestacker.stackable.entity;
import com.craftaro.core.compatibility.ServerVersion;
import com.craftaro.ultimatestacker.UltimateStacker;
import com.craftaro.ultimatestacker.api.stack.entity.EntityStack;
import com.craftaro.ultimatestacker.api.stack.entity.EntityStackManager;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import uk.antiperson.stackmob.api.StackedEntity;
import java.util.UUID;
@ -15,9 +19,11 @@ import java.util.UUID;
public class EntityStackManagerImpl implements EntityStackManager {
private final UltimateStacker plugin;
private final NamespacedKey STACKED_ENTITY_KEY;
public EntityStackManagerImpl(UltimateStacker plugin) {
this.plugin = plugin;
this.STACKED_ENTITY_KEY = new NamespacedKey(plugin, "US_AMOUNT");
}
@Override
@ -29,7 +35,13 @@ public class EntityStackManagerImpl implements EntityStackManager {
@Override
public boolean isStackedEntity(Entity entity) {
return entity.hasMetadata("US_AMOUNT");
if (entity.hasMetadata("US_AMOUNT")) {
return true;
}
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14)) {
return entity.getPersistentDataContainer().has(STACKED_ENTITY_KEY, PersistentDataType.INTEGER);
}
return false;
}
@Override

View File

@ -84,7 +84,7 @@ public class SpawnerStackImpl implements SpawnerStack {
@Override
public int spawn() {
return spawn(-1, false);
return spawn(-1, Settings.NO_AI.getBoolean());
}
@Override

View File

@ -46,6 +46,7 @@ import org.bukkit.inventory.EntityEquipment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@ -53,6 +54,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@ -60,13 +62,14 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.craftaro.ultimatestacker.stackable.entity.Check.getChecks;
public class StackingTask extends TimerTask {
private final UltimateStacker plugin;
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "UltimateStacker-Stacking-Thread"));
private final EntityStackManager stackManager;
@ -77,6 +80,7 @@ public class StackingTask extends TimerTask {
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<String> disabledWorlds = Settings.DISABLED_WORLDS.getStringList(),
stackReasons = Settings.STACK_REASONS.getStringList();
@ -87,17 +91,19 @@ public class StackingTask extends TimerTask {
onlyStackFromSpawners = Settings.ONLY_STACK_FROM_SPAWNERS.getBoolean(),
onlyStackOnSurface = Settings.ONLY_STACK_ON_SURFACE.getBoolean();
Set<SWorld> loadedWorlds = new HashSet<>();
private final Set<SWorld> loadedWorlds;
public StackingTask(UltimateStacker plugin) {
this.plugin = plugin;
this.stackManager = plugin.getEntityStackManager();
// Add loaded worlds.
for (World world : Bukkit.getWorlds())
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));
}
// Start the stacking task.
//runTaskTimerAsynchronously(plugin, 0, Settings.STACK_SEARCH_TICK_SPEED.getInt());
executorService.scheduleAtFixedRate(this, 0, (Settings.STACK_SEARCH_TICK_SPEED.getInt()*50L), TimeUnit.MILLISECONDS);
}
@ -107,25 +113,30 @@ public class StackingTask extends TimerTask {
@Override
public void run() {
//make sure if the task running if any error occurs
//Make sure to continue the task if any exception 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;
// Get the loaded entities from the current world and reverse them.
try {
entities = getLivingEntitiesSync(sWorld).get();
} catch (ExecutionException | InterruptedException ex) {
ex.printStackTrace();
continue;
}
Collections.reverse(entities);
//Filter non-stackable entities to improve performance on main thread
entities.removeIf(this::isEntityNotStackable);
for (LivingEntity entity : entities) {
// Check our WorldGuard flag.
Boolean flag = WorldGuardHook.isEnabled() ? WorldGuardHook.getBooleanFlag(entity.getLocation(), "mob-stacking") : null; //Does this work async?
if (flag != null && !flag) {
entities.removeIf(entity1 -> entity1.getUniqueId().equals(entity.getUniqueId()));
}
}
Bukkit.getScheduler().runTask(plugin, () -> {
// Loop through the entities.
@ -134,28 +145,26 @@ public class StackingTask extends TimerTask {
// 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);
this.processEntity(entity, location);
}
});
}
// Clear caches in preparation for the next run.
} catch (Exception ignored) {
} finally {
// Make sure we clear the processed list.
this.processed.clear();
} catch (Exception ignored) {}
}
}
private Future<List<LivingEntity>> getLivingEntitiesSync(SWorld sWorld) {
CompletableFuture<List<LivingEntity>> future = new CompletableFuture<>();
Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> future.complete(sWorld.getLivingEntities()));
return future;
}
@ -169,7 +178,11 @@ public class StackingTask extends TimerTask {
return disabledWorlds.stream().anyMatch(worldStr -> world.getName().equalsIgnoreCase(worldStr));
}
private boolean isEntityStackable(Entity entity) {
//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
@ -178,187 +191,182 @@ public class StackingTask extends TimerTask {
// Make sure the entity is not in love.
|| entity.hasMetadata("inLove")
// Or in breeding cooldown.
|| entity.hasMetadata("breedCooldown"))
return false;
if (!configurationSection.getBoolean("Mobs." + entity.getType().name() + ".Enabled")) {
return false;
|| entity.hasMetadata("breedCooldown")) {
return true;
}
// Allow spawn if stackreasons are set and match, or if from a spawner
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<String> stackReasons;
if (onlyStackFromSpawners) {
// If only stack from spawners is enabled, make sure the entity spawned from a spawner.
if (!"SPAWNER".equals(spawnReason))
return false;
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 false;
return true;
}
// Cast our entity to living entity.
LivingEntity livingEntity = (LivingEntity) entity;
// If only stack on surface is enabled make sure the entity is on a surface then entity is stackable.
return !onlyStackOnSurface
|| canFly(livingEntity)
|| entity.getType().name().equals("SHULKER")
|| (livingEntity.isOnGround()
|| (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
&& livingEntity.isSwimming()));
//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, SWorld sWorld, Location location) {
// Check our WorldGuard flag.
Boolean flag = WorldGuardHook.isEnabled() ? WorldGuardHook.getBooleanFlag(baseEntity.getLocation(), "mob-stacking") : null;
if (flag != null && !flag) {
return;
}
private void processEntity(LivingEntity baseEntity, Location location) {
// 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 = getEntityStackSize(baseEntity);
int maxEntityStackSize = getEntityMaxStackSize(baseEntity);
// Is this entity stacked?
boolean isStack = baseStack != null;
if (isStack && baseStack.getAmount() == maxEntityStackSize) {
// If the stack is already at the max size then we can skip it.
processed.add(baseEntity.getUniqueId());
// 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;
}
// The amount that is stackable.
int amountToStack = isStack ? baseStack.getAmount() : 1;
// Attempt to split our stack. If the split is successful then skip this entity.
if (isStack && attemptSplit(baseStack, baseEntity)) return;
// If this entity is named, a custom entity or disabled then skip it.
if (!isStack && (baseEntity.getCustomName() != null
&& plugin.getCustomEntityManager().getCustomEntity(baseEntity) == null)
|| !configurationSection.getBoolean("Mobs." + baseEntity.getType().name() + ".Enabled")) {
// 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<LivingEntity> stackableFriends = new LinkedList<>();
List<LivingEntity> list = getSimilarEntitiesAroundEntity(baseEntity, sWorld, location);
for (LivingEntity entity : list) {
// Check to see if entity is not stackable.
if (!isEntityStackable(entity))
continue;
// Add this entity to our stackable friends.
stackableFriends.add(entity);
}
List<LivingEntity> stackableFriends = getSimilarEntitiesAroundEntity(baseEntity, location);
//Total entities that can be stacked into the base entity
int maxStackable = maxEntityStackSize - baseSize;
int toStack = 0;
List<LivingEntity> remove = new ArrayList<>();
// Loop through our similar stackable entities.
for (LivingEntity friendlyEntity : stackableFriends) {
// Make sure the friendlyEntity has not already been processed.
if (this.processed.contains(friendlyEntity.getUniqueId())) continue;
// Get this entities friendStack.
// Process similar entities.
EntityStack friendStack = stackManager.getStackedEntity(friendlyEntity);
int amount = friendStack != null ? friendStack.getAmount() : 1;
// 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.
boolean overstack = (amount + amountToStack) > maxEntityStackSize;
if (!overstack) {
stackManager.createStackedEntity(friendlyEntity, amount + amountToStack);
processed.add(baseEntity.getUniqueId());
Bukkit.getScheduler().runTask(plugin, () -> {
if (baseEntity.isLeashed()) {
baseEntity.getWorld().dropItemNaturally(baseEntity.getLocation(), XMaterial.LEAD.parseItem());
}
baseEntity.remove();
});
return;
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();
}
}
public boolean attemptSplit(EntityStack baseStack, LivingEntity livingEntity) {
/**
* 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 = getEntityStackSize(livingEntity);
int maxEntityStackAmount = maxEntityStackSize == -1 ? getEntityMaxStackSize(hostEntity) : maxEntityStackSize;
if (stackSize <= maxEntityStackAmount) return false;
baseStack.setAmount(maxEntityStackAmount);
Bukkit.getScheduler().runTask(plugin, () -> {
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);
EntityStack newStack = stackManager.createStackedEntity(newEntity, toAdd);
processed.add(newEntity.getUniqueId());
finalStackSize -= maxEntityStackAmount;
} while (finalStackSize >= 0);
});
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(livingEntity.getUniqueId());
processed.add(hostEntity.getUniqueId());
return true;
}
private Set<CachedChunk> getNearbyChunks(SWorld sWorld, Location location, double radius, boolean singleChunk) {
//get current chunk
if (radius == -1) {
return new HashSet<>(Collections.singletonList(new CachedChunk(sWorld, location.getChunk().getX(), location.getChunk().getZ())));
private Set<CachedChunk> 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();
Set<CachedChunk> chunks = new HashSet<>();
if (world == null) return chunks;
if (world == null) return new HashSet<>();
CachedChunk firstChunk = new CachedChunk(sWorld, location);
Set<CachedChunk> chunks = new TreeSet<>(Comparator.comparingInt(CachedChunk::getX).thenComparingInt(CachedChunk::getZ));
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);
//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 (firstChunk.getX() == x && firstChunk.getZ() == z) continue;
chunks.add(new CachedChunk(sWorld, x, 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.
* @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) {
public List<LivingEntity> 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<LivingEntity> entities = new ArrayList<>();
try {
Set<CachedChunk> chunks = getNearbyChunks(new SWorld(entity.getWorld()), entity.getLocation(), radius, singleChunk);
Set<CachedChunk> chunks = getNearbyChunks(new SWorld(entity.getWorld()), entity.getLocation());
for (CachedChunk chunk : chunks) {
Entity[] entityList = chunk.getEntities();
for (Entity e : entityList) {
if (!processed.contains(e.getUniqueId()) && e.getType() == entity.getType() && e instanceof LivingEntity && e.isValid() && e.getLocation().distance(entity.getLocation()) <= radius) {
if (e.getType() == entity.getType() && !isMaxStack((LivingEntity) e)) {
entities.add((LivingEntity) e);
}
}
@ -370,10 +378,10 @@ public class StackingTask extends TimerTask {
return entities;
}
public List<LivingEntity> getSimilarEntitiesAroundEntity(LivingEntity initialEntity, SWorld sWorld, Location location) {
public List<LivingEntity> getSimilarEntitiesAroundEntity(LivingEntity initialEntity, Location location) {
try {
// Create a list of all entities around the initial entity of the same type.
List<LivingEntity> entityList = new LinkedList<>(getFriendlyStacksNearby(initialEntity, searchRadius, stackWholeChunk));
List<LivingEntity> entityList = new ArrayList<>(getFriendlyStacksNearby(initialEntity));
CustomEntity customEntity = plugin.getCustomEntityManager().getCustomEntity(initialEntity);
if (customEntity != null)
@ -641,16 +649,20 @@ public class StackingTask extends TimerTask {
|| (equipment.getBoots() != null && equipment.getBoots().getType() != Material.AIR));
}
private int getEntityStackSize(LivingEntity initialEntity) {
Integer max = entityStackSizes.get(initialEntity.getType());
if (max == null) {
max = configurationSection.getInt("Mobs." + initialEntity.getType().name() + ".Max Stack Size");
if (max == -1) {
max = maxEntityStackSize;
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;
}
entityStackSizes.put(initialEntity.getType(), max);
}
return max;
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) {