Rewrite mob stacking, performance improvements

This commit is contained in:
ceze88 2023-02-04 14:48:13 +01:00
parent 4e4fda611c
commit 71baf3a7e8
25 changed files with 258 additions and 877 deletions

View File

@ -26,8 +26,7 @@ import com.songoda.ultimatestacker.database.DataManager;
import com.songoda.ultimatestacker.database.migrations._1_InitialMigration;
import com.songoda.ultimatestacker.database.migrations._2_EntityStacks;
import com.songoda.ultimatestacker.database.migrations._3_BlockStacks;
import com.songoda.ultimatestacker.database.migrations._4_DataPurge;
import com.songoda.ultimatestacker.database.migrations._5_StackedEntitiesTableUpdate;
import com.songoda.ultimatestacker.database.migrations._6_RemoveStackedEntityTable;
import com.songoda.ultimatestacker.hook.StackerHook;
import com.songoda.ultimatestacker.hook.hooks.JobsHook;
import com.songoda.ultimatestacker.listeners.*;
@ -49,6 +48,7 @@ import com.songoda.ultimatestacker.stackable.spawner.SpawnerStackManager;
import com.songoda.ultimatestacker.tasks.StackingTask;
import com.songoda.ultimatestacker.utils.Async;
import com.songoda.ultimatestacker.utils.Methods;
import io.lumine.mythic.bukkit.listeners.ChunkListeners;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@ -106,7 +106,7 @@ public class UltimateStacker extends SongodaPlugin {
@Override
public void onPluginDisable() {
this.stackingTask.cancel();
this.stackingTask.stop();
this.stackingTask = null;
this.dataManager.bulkUpdateSpawners(this.spawnerStackManager.getStacks());
HologramManager.removeAllHolograms();
@ -235,8 +235,7 @@ public class UltimateStacker extends SongodaPlugin {
new _1_InitialMigration(),
new _2_EntityStacks(),
new _3_BlockStacks(),
new _4_DataPurge(),
new _5_StackedEntitiesTableUpdate());
new _6_RemoveStackedEntityTable());
this.dataMigrationManager.runMigrations();
}
@ -260,12 +259,8 @@ public class UltimateStacker extends SongodaPlugin {
}
}
});
this.dataManager.getEntities((entities) -> {
entityStackManager.addStacks(entities.values());
entityStackManager.tryAndLoadColdEntities();
this.stackingTask = new StackingTask(this);
getServer().getPluginManager().registerEvents(new ChunkListeners(entityStackManager), this);
});
this.stackingTask = new StackingTask(this);
final boolean useBlockHolo = Settings.BLOCK_HOLOGRAMS.getBoolean();
this.dataManager.getBlocks((blocks) -> {
this.blockStackManager.addBlocks(blocks);
@ -302,7 +297,7 @@ public class UltimateStacker extends SongodaPlugin {
this.setLocale(getConfig().getString("System.Language Mode"), true);
this.locale.reloadMessages();
this.stackingTask.cancel();
this.stackingTask.stop();
this.stackingTask = new StackingTask(this);
this.mobFile.load();

View File

@ -46,10 +46,9 @@ public class CommandRemoveAll extends AbstractCommand {
for (Entity entityO : world.getEntities()) {
if (entityO instanceof Player) continue;
if (entityO instanceof LivingEntity && (stackManager.isStackedAndLoaded((LivingEntity)entityO) || all)
if (entityO instanceof LivingEntity && (stackManager.isStackedEntity(entityO) || all)
&& type.equalsIgnoreCase("entities")) {
entityO.remove();
plugin.getEntityStackManager().removeStack(entityO);
plugin.getEntityStackManager().getStack((LivingEntity) entityO).destroy();
amountRemoved++;
} else if (entityO.getType() == EntityType.DROPPED_ITEM && type.equalsIgnoreCase("items")) {
if (!UltimateStacker.hasCustomAmount((Item)entityO) && !all)

View File

@ -56,9 +56,7 @@ 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().addStack(entity);
stack.createDuplicates(((Methods.isInt(args[1])) ? Integer.parseInt(args[1]) : 1) - 1);
stack.updateStack();
EntityStack stack = plugin.getEntityStackManager().createStack(entity, Integer.parseInt(args[1]));
plugin.getStackingTask().attemptSplit(stack, entity);
}

View File

@ -36,14 +36,6 @@ public class StackMobConvert implements Convert {
@Override
public void convertEntities() {
EntityStackManager entityStackManager = plugin.getEntityStackManager();
for (Map.Entry<UUID, Integer> entry : stackMob.getStorageManager().getAmountCache().entrySet()) {
if (!entityStackManager.isStackedAndLoaded(entry.getKey())) {
entityStackManager.addLegacyColdStack(entry.getKey(), entry.getValue());
continue;
}
}
}
@Override

View File

@ -43,23 +43,6 @@ public class WildStackerConvert implements Convert {
@Override
public void convertEntities() {
EntityStackManager entityStackManager = plugin.getEntityStackManager();
DatabaseConnector connector = new SQLiteConnector(this.wildStacker);
connector.connect(connection -> {
try (Statement statement = connection.createStatement()) {
ResultSet result = statement.executeQuery("SELECT uuid, stackAmount FROM entities");
while (result.next()) {
UUID uuid = UUID.fromString(result.getString("uuid"));
int amount = result.getInt("stackAmount");
if (!entityStackManager.isEntityInColdStorage(uuid))
entityStackManager.addLegacyColdStack(uuid, amount);
}
}
});
}
@Override

View File

@ -6,7 +6,6 @@ import com.songoda.core.database.DatabaseConnector;
import com.songoda.core.database.DatabaseType;
import com.songoda.ultimatestacker.settings.Settings;
import com.songoda.ultimatestacker.stackable.block.BlockStack;
import com.songoda.ultimatestacker.stackable.entity.ColdEntityStack;
import com.songoda.ultimatestacker.stackable.entity.EntityStack;
import com.songoda.ultimatestacker.stackable.entity.StackedEntity;
import com.songoda.ultimatestacker.stackable.spawner.SpawnerStack;
@ -122,130 +121,6 @@ public class DataManager extends DataManagerAbstract {
});
}
public void createHostEntity(ColdEntityStack stack) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String createSerializedEntity = "INSERT " + getSyntax("INTO ", DatabaseType.MYSQL) + getSyntax("OR REPLACE INTO ", DatabaseType.SQLITE) + this.getTablePrefix() + "host_entities (uuid, create_duplicates) VALUES (?, ?)";
PreparedStatement statement = connection.prepareStatement(createSerializedEntity);
if (stack == null || stack.getHostUniqueId() == null) return;
statement.setString(1, stack.getHostUniqueId().toString());
statement.setInt(2, stack.getCreateDuplicates());
statement.executeUpdate();
int stackId = this.lastInsertedId(connection, "host_entities");
this.sync(() -> stack.setId(stackId));
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void createStackedEntity(EntityStack hostStack, StackedEntity stackedEntity) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()){
String createSerializedEntity = "INSERT " + getSyntax("INTO ", DatabaseType.MYSQL) + getSyntax("OR REPLACE INTO ", DatabaseType.SQLITE) + this.getTablePrefix() + "stacked_entities (uuid, host, serialized_entity) VALUES (?, ?, ?) "
+ (Settings.MYSQL_ENABLED.getBoolean() ? "ON DUPLICATE KEY UPDATE host = ?, serialized_entity = ?" : "ON CONFLICT(uuid) DO UPDATE SET host = ?, serialized_entity = ?");
PreparedStatement statement = connection.prepareStatement(createSerializedEntity);
if (hostStack.getHostUniqueId() == null) return;
statement.setString(1, stackedEntity.getUniqueId().toString());
statement.setInt(2, hostStack.getId());
statement.setBytes(3, stackedEntity.getSerializedEntity());
statement.setInt(4, hostStack.getId());
statement.setBytes(5, stackedEntity.getSerializedEntity());
statement.executeUpdate();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void createStackedEntities(ColdEntityStack hostStack, List<StackedEntity> stackedEntities) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String createSerializedEntity = "REPLACE INTO " + this.getTablePrefix() + "stacked_entities (uuid, host, serialized_entity) VALUES (?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(createSerializedEntity);
if (hostStack.getHostUniqueId() == null) return;
for (StackedEntity entity : stackedEntities) {
statement.setString(1, entity.getUniqueId().toString());
statement.setInt(2, hostStack.getId());
statement.setBytes(3, entity.getSerializedEntity());
statement.addBatch();
}
statement.executeBatch();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void updateHost(ColdEntityStack hostStack) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String updateHost = "UPDATE " + this.getTablePrefix() + "host_entities SET uuid = ?, create_duplicates = ?, updated_at = current_timestamp WHERE id = ?";
PreparedStatement statement = connection.prepareStatement(updateHost);
if (hostStack.getHostUniqueId() == null) return;
statement.setString(1, hostStack.getHostUniqueId().toString());
statement.setInt(2, hostStack.getCreateDuplicates());
statement.setInt(3, hostStack.getId());
statement.executeUpdate();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void deleteHost(ColdEntityStack stack) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String deleteHost = "DELETE FROM " + this.getTablePrefix() + "host_entities WHERE id = ?";
try (PreparedStatement statement = connection.prepareStatement(deleteHost)) {
statement.setInt(1, stack.getId());
statement.executeUpdate();
}
String deleteStackedEntities = "DELETE FROM " + this.getTablePrefix() + "stacked_entities WHERE host = ?";
try (PreparedStatement statement = connection.prepareStatement(deleteStackedEntities)) {
statement.setInt(1, stack.getId());
statement.executeUpdate();
}
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void deleteStackedEntity(UUID uuid) {
if (uuid == null)
return;
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String deleteStackedEntity = "DELETE FROM " + this.getTablePrefix() + "stacked_entities WHERE uuid = ?";
PreparedStatement statement = connection.prepareStatement(deleteStackedEntity);
statement.setString(1, uuid.toString());
statement.executeUpdate();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void deleteStackedEntities(List<StackedEntity> entities) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
String deleteStackedEntities = "DELETE FROM " + this.getTablePrefix() + "stacked_entities WHERE uuid = ?";
PreparedStatement statement = connection.prepareStatement(deleteStackedEntities);
for (StackedEntity entity : entities) {
if (entity == null) continue;
statement.setString(1, entity.getUniqueId().toString());
statement.addBatch();
}
statement.executeBatch();
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void deleteSpawner(SpawnerStack spawnerStack) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
@ -272,76 +147,6 @@ public class DataManager extends DataManagerAbstract {
});
}
public void getEntities(Consumer<Map<Integer, ColdEntityStack>> callback) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()) {
Map<Integer, ColdEntityStack> entities = new HashMap<>();
boolean mysql = Settings.MYSQL_ENABLED.getBoolean();
int databasePurge = Settings.DATABASE_PURGE.getInt();
String whereStatement = mysql ? "WHERE updated_at < NOW() - INTERVAL " + databasePurge + " DAY" : "WHERE updated_at <= date('now','-" + databasePurge + " day')";
String selectOldEntities = "SELECT * FROM " + this.getTablePrefix() + "host_entities " + whereStatement;
try (Statement statement = connection.createStatement()) {
List<String> toDelete = new ArrayList<>();
ResultSet result = statement.executeQuery(selectOldEntities);
while (result.next()) {
int hostId = result.getInt("id");
toDelete.add(String.valueOf(hostId));
}
if (!toDelete.isEmpty()) {
statement.execute("DELETE FROM " + this.getTablePrefix() + "host_entities " + whereStatement);
statement.execute("DELETE FROM " + this.getTablePrefix() + "stacked_entities WHERE host IN (" + String.join(", ", toDelete) + ")");
}
} catch (Exception e) {
e.printStackTrace();
}
String selectEntities = "SELECT * FROM " + this.getTablePrefix() + "host_entities";
try (Statement statement = connection.createStatement()) {
ResultSet result = statement.executeQuery(selectEntities);
while (result.next()) {
int hostId = result.getInt("id");
UUID host = UUID.fromString(result.getString("uuid"));
int createDuplicates = result.getInt("create_duplicates");
ColdEntityStack stack = new ColdEntityStack(host, hostId);
stack.createDuplicates(createDuplicates);
entities.put(hostId, stack);
}
} catch (Exception e) {
e.printStackTrace();
}
String selectStackedEntities = "SELECT * FROM " + this.getTablePrefix() + "stacked_entities";
try (Statement statement = connection.createStatement()) {
ResultSet result = statement.executeQuery(selectStackedEntities);
while (result.next()) {
UUID uuid = UUID.fromString(result.getString("uuid"));
int hostId = result.getInt("host");
byte[] serializedEntity = result.getBytes("serialized_entity");
ColdEntityStack stack = entities.get(hostId);
if (stack == null) continue;
stack.addEntityToStackSilently(new StackedEntity(uuid, serializedEntity));
}
} catch (Exception e) {
e.printStackTrace();
}
this.sync(() -> callback.accept(entities));
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
public void getSpawners(Consumer<Map<Location, SpawnerStack>> callback) {
this.runAsync(() -> {
try (Connection connection = this.databaseConnector.getConnection()){

View File

@ -26,14 +26,5 @@ public class _2_EntityStacks extends DataMigration {
"create_duplicates INTEGER NOT NULL DEFAULT 0" +
")");
}
// Create stacked entities table
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE IF NOT EXISTS " + tablePrefix + "stacked_entities (" +
"uuid VARCHAR(36) PRIMARY KEY NOT NULL," +
"host INTEGER NOT NULL," +
"serialized_entity VARBINARY(9999) NOT NULL" +
")");
}
}
}

View File

@ -1,24 +0,0 @@
package com.songoda.ultimatestacker.database.migrations;
import com.songoda.core.database.DataMigration;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class _5_StackedEntitiesTableUpdate extends DataMigration {
public _5_StackedEntitiesTableUpdate() {
super(5);
}
@Override
public void migrate(Connection connection, String tablePrefix) throws SQLException {
try (Statement statement = connection.createStatement()) {
statement.execute("ALTER TABLE " + tablePrefix + "stacked_entities MODIFY serialized_entity VARBINARY(9999)");
} catch (SQLException e) {
// Ignore
//TODO fix it for sqlite
}
}
}

View File

@ -1,25 +1,23 @@
package com.songoda.ultimatestacker.database.migrations;
import com.songoda.core.database.DataMigration;
import com.songoda.core.database.MySQLConnector;
import com.songoda.ultimatestacker.UltimateStacker;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class _4_DataPurge extends DataMigration {
public class _6_RemoveStackedEntityTable extends DataMigration {
public _4_DataPurge() {
super(4);
public _6_RemoveStackedEntityTable() {
super(6);
}
@Override
public void migrate(Connection connection, String tablePrefix) throws SQLException {
public void migrate(Connection connection, String tablePrefix) {
try (Statement statement = connection.createStatement()) {
statement.execute("ALTER TABLE " + tablePrefix + "host_entities ADD COLUMN updated_at datetime DEFAULT NULL");
statement.execute("DROP TABLE " + tablePrefix + "stacked_entities");
} catch (SQLException e) {
// Ignore
e.printStackTrace();
}
}
}

View File

@ -55,7 +55,7 @@ public class GUIConvertWhat extends Gui {
private void run(Player player) {
if (entities) {
convertFrom.convertEntities();
UltimateStacker.getInstance().getEntityStackManager().tryAndLoadColdEntities();
//UltimateStacker.getInstance().getEntityStackManager().tryAndLoadColdEntities();
}
if (spawners) {
convertFrom.convertSpawners();

View File

@ -1,41 +0,0 @@
package com.songoda.ultimatestacker.listeners;
import com.songoda.ultimatestacker.stackable.entity.EntityStackManager;
import org.bukkit.Chunk;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
public class ChunkListeners implements Listener {
private final EntityStackManager entityStackManager;
public ChunkListeners(EntityStackManager entityStackManager) {
this.entityStackManager = entityStackManager;
}
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
Chunk chunk = event.getChunk();
for (Entity entity : chunk.getEntities()) {
if (!(entity instanceof LivingEntity)) continue;
if (entityStackManager.isEntityInColdStorage((LivingEntity) entity)) {
entityStackManager.loadStack((LivingEntity) entity);
}
}
}
@EventHandler
public void onChunkUnload(ChunkUnloadEvent event) {
Chunk chunk = event.getChunk();
for (Entity entity : chunk.getEntities()) {
if (!(entity instanceof LivingEntity)) continue;
if (entityStackManager.isStackedAndLoaded((LivingEntity) entity)) {
entityStackManager.unloadStack((LivingEntity) entity);
}
}
}
}

View File

@ -18,8 +18,8 @@ public class ClearLagListeners implements Listener {
@EventHandler
public void onClearLaggTask(EntityRemoveEvent event) {
for (Entity entity : event.getWorld().getEntities()) {
if (entity instanceof LivingEntity && plugin.getEntityStackManager().isStackedAndLoaded((LivingEntity)entity)) {
plugin.getEntityStackManager().removeStack(entity);
if (entity instanceof LivingEntity && plugin.getEntityStackManager().isStackedEntity(entity)) {
plugin.getEntityStackManager().getStack((LivingEntity) entity).destroy();
event.addEntity(entity);
}
}

View File

@ -82,7 +82,7 @@ public class DeathListeners implements Listener {
drops.clear();
if (plugin.getCustomEntityManager().getCustomEntity(entity) == null) {
if (plugin.getEntityStackManager().isStackedAndLoaded(event.getEntity())) {
if (plugin.getEntityStackManager().isStackedEntity(event.getEntity())) {
plugin.getEntityStackManager().getStack(event.getEntity()).onDeath(entity, drops, custom, event.getDroppedExp(), event);
} else {
DropUtils.processStackedDrop(event.getEntity(), drops, event);
@ -157,7 +157,7 @@ public class DeathListeners implements Listener {
if (!(event.getEntity() instanceof LivingEntity)) return;
LivingEntity entity = (LivingEntity) event.getEntity();
if (!plugin.getEntityStackManager().isStackedAndLoaded(entity)) return;
if (!plugin.getEntityStackManager().isStackedEntity(entity)) return;
EntityStack stack = plugin.getEntityStackManager().getStack(entity);
Player player = (Player) event.getDamager();

View File

@ -38,7 +38,7 @@ public class InteractListeners implements Listener {
ItemStack item = player.getInventory().getItemInHand();
if (!plugin.getEntityStackManager().isStackedAndLoaded(entity)) return;
if (!plugin.getEntityStackManager().isStackedEntity(entity)) return;
if (item.getType() != Material.NAME_TAG && !correctFood(item, entity)) return;

View File

@ -38,7 +38,7 @@ public class ShearListeners implements Listener {
&& entity.getType() != EntityType.MUSHROOM_COW
&& entity.getType() != EntityType.SNOWMAN) return;
EntityStackManager stackManager = plugin.getEntityStackManager();
if (!stackManager.isStackedAndLoaded(entity)) return;
if (!stackManager.isStackedEntity(entity)) return;
if (event.getEntity().getType() == EntityType.SHEEP
&& Settings.SPLIT_CHECKS.getStringList().stream().noneMatch(line -> Split.valueOf(line) == Split.SHEEP_SHEAR)

View File

@ -24,7 +24,7 @@ public class SheepDyeListeners implements Listener {
LivingEntity entity = event.getEntity();
EntityStackManager stackManager = plugin.getEntityStackManager();
if (!stackManager.isStackedAndLoaded(entity)) return;
if (!stackManager.isStackedEntity(entity)) return;
if (Settings.SPLIT_CHECKS.getStringList().stream().noneMatch(line -> Split.valueOf(line) == Split.SHEEP_DYE))
return;

View File

@ -67,12 +67,16 @@ public class SpawnerListeners implements Listener {
SpawnerStack spawnerStack = spawnerStackManager.getSpawner(location);
spawnerStack.spawn(spawnerStack.calculateSpawnCount(), "EXPLOSION_NORMAL", null, (e) -> {
int amountToSpawn = spawnerStack.calculateSpawnCount();
spawnerStack.spawn(amountToSpawn, "EXPLOSION_NORMAL", null, (e) -> {
if (Settings.NO_AI.getBoolean())
EntityUtils.setUnaware(e);
if (mcmmo)
entity.setMetadata("mcMMO: Spawned Entity", new FixedMetadataValue(plugin, true));
UltimateStacker.getInstance().getEntityStackManager().setStack(e, amountToSpawn);
return true;
}, event.getEntityType());
}

View File

@ -22,7 +22,7 @@ public class TameListeners implements Listener {
LivingEntity entity = event.getEntity();
EntityStackManager stackManager = plugin.getEntityStackManager();
if (!stackManager.isStackedAndLoaded(entity)) return;
if (!stackManager.isStackedEntity(entity)) return;
EntityStack stack = plugin.getEntityStackManager().getStack(entity);

View File

@ -20,13 +20,11 @@ public class EntityCurrentListener implements Listener {
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onSpawn(EntityTransformEvent event) {
EntityStackManager stackManager = plugin.getEntityStackManager();
if (stackManager.isStackedAndLoaded(event.getEntity().getUniqueId())
if (stackManager.isStackedEntity(event.getEntity())
&& event.getEntity() instanceof LivingEntity
&& event.getTransformedEntity() instanceof LivingEntity) {
EntityStack stack = stackManager.updateStack((LivingEntity) event.getEntity(),
(LivingEntity) event.getTransformedEntity());
EntityStack stack = stackManager.updateStack((LivingEntity) event.getEntity(), (LivingEntity) event.getTransformedEntity());
stack.releaseHost();
stack.updateStack();
}
}
}

View File

@ -59,7 +59,7 @@ public class EntityListeners implements Listener {
EntityStackManager stackManager = plugin.getEntityStackManager();
if (!stackManager.isStackedAndLoaded(entity)) return;
if (!stackManager.isStackedEntity(entity)) return;
EntityStack stack = stackManager.getStack(entity);
@ -78,7 +78,7 @@ public class EntityListeners implements Listener {
Entity entity = event.getEntity();
if (entity instanceof LivingEntity && plugin.getEntityStackManager().isStackedAndLoaded((LivingEntity) entity)
if (entity instanceof LivingEntity && plugin.getEntityStackManager().isStackedEntity(entity)
&& Settings.DISABLE_KNOCKBACK.getBoolean()
&& ((Player) event.getDamager()).getItemInHand().getEnchantmentLevel(Enchantment.KNOCKBACK) == 0) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {

View File

@ -1,169 +0,0 @@
package com.songoda.ultimatestacker.stackable.entity;
import com.songoda.core.nms.NmsManager;
import com.songoda.core.nms.nbt.NBTEntity;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.stackable.entity.custom.CustomEntity;
import com.songoda.ultimatestacker.utils.Stackable;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.Collectors;
public class ColdEntityStack implements Stackable {
protected static final UltimateStacker plugin = UltimateStacker.getInstance();
// The id to identify this stack in the database.
private int id;
// The unique id of the stacks host.
protected UUID hostUniqueId;
// These are the entities below the host.
protected final Deque<StackedEntity> stackedEntities = new ConcurrentLinkedDeque<>();
// The amount of duplicates of the host to create when loaded.
protected int createDuplicates = 0;
public ColdEntityStack(UUID hostUniqueId, int id) {
this.hostUniqueId = hostUniqueId;
this.id = id;
}
public ColdEntityStack(UUID hostUniqueId) {
this.hostUniqueId = hostUniqueId;
}
public StackedEntity addEntityToStackSilently(Entity entity) {
return addEntityToStackSilently(getStackedEntity(entity));
}
public List<StackedEntity> addRawEntitiesToStackSilently(List<LivingEntity> unstackedEntities) {
List<StackedEntity> stackedEntities = unstackedEntities.stream()
.map(this::getStackedEntity).collect(Collectors.toList());
addEntitiesToStackSilently(stackedEntities);
return stackedEntities;
}
public void addEntitiesToStackSilently(List<StackedEntity> stackedEntities) {
stackedEntities.removeIf(Objects::isNull);
this.stackedEntities.addAll(stackedEntities);
}
public synchronized StackedEntity addEntityToStackSilently(StackedEntity stackedEntity) {
if (stackedEntity == null) return null;
stackedEntities.push(stackedEntity);
return stackedEntity;
}
public void moveEntitiesFromStack(EntityStack stack, int amount) {
List<StackedEntity> stackedEntities = stack.takeEntities(amount);
this.stackedEntities.addAll(stackedEntities);
plugin.getDataManager().createStackedEntities(this, stackedEntities);
}
public List<StackedEntity> takeEntities(int amount) {
List<StackedEntity> entities = new LinkedList<>();
for (int i = 0; i < amount; i++) {
StackedEntity entity = stackedEntities.pollFirst();
if (entity != null)
entities.add(entity);
}
plugin.getDataManager().deleteStackedEntities(entities);
return entities;
}
public List<StackedEntity> takeAllEntities() {
List<StackedEntity> entities = new LinkedList<>(stackedEntities);
stackedEntities.clear();
return entities;
}
public LivingEntity takeOneAndSpawnEntity(Location location) {
if (stackedEntities.isEmpty()) return null;
NBTEntity nbtEntity = NmsManager.getNbt().newEntity();
nbtEntity.deSerialize(stackedEntities.getFirst().getSerializedEntity());
for (CustomEntity customEntity : plugin.getCustomEntityManager().getRegisteredCustomEntities()) {
String identifier = customEntity.getPluginName() + "_UltimateStacker";
if (!nbtEntity.has(identifier)) continue;
LivingEntity entity = customEntity.spawnFromIdentifier(nbtEntity.getString(identifier), location);
if (entity == null) continue;
stackedEntities.removeFirst();
plugin.getDataManager().deleteStackedEntity(entity.getUniqueId());
return entity;
}
LivingEntity newEntity = null;
for (int i = 0; i < 5; i++) {
newEntity = (LivingEntity) nbtEntity.spawn(location);
if (newEntity != null) {
stackedEntities.removeFirst();
plugin.getDataManager().deleteStackedEntity(newEntity.getUniqueId());
break;
}
}
if (newEntity == null)
plugin.getDataManager().deleteStackedEntity(hostUniqueId);
return newEntity;
}
@Override
public int getAmount() {
return stackedEntities.size() + 1;
}
@Override
public boolean isValid() {
return true;
}
public void createDuplicates(int duplicates) {
this.createDuplicates = duplicates;
}
public int getCreateDuplicates() {
return createDuplicates;
}
protected StackedEntity getStackedEntity(Entity entity) {
return getStackedEntity(entity, false);
}
protected synchronized StackedEntity getStackedEntity(Entity entity, boolean newUUID) {
if (entity == null) return null;
UUID uuid = entity.getUniqueId();
NBTEntity nbtEntity = NmsManager.getNbt().of(entity);
CustomEntity customEntity = plugin.getCustomEntityManager().getCustomEntity(entity);
if (customEntity != null)
nbtEntity.set(customEntity.getPluginName() + "_UltimateStacker", customEntity.getNBTIdentifier(entity));
return new StackedEntity(uuid, nbtEntity.serialize());
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public UUID getHostUniqueId() {
return hostUniqueId;
}
public void setHostUniqueId(UUID hostUniqueId) {
this.hostUniqueId = hostUniqueId;
}
}

View File

@ -3,9 +3,12 @@ package com.songoda.ultimatestacker.stackable.entity;
import com.songoda.core.compatibility.ServerVersion;
import com.songoda.core.lootables.loot.Drop;
import com.songoda.core.lootables.loot.DropUtils;
import com.songoda.core.nms.NmsManager;
import com.songoda.core.nms.nbt.NBTEntity;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.events.EntityStackKillEvent;
import com.songoda.ultimatestacker.settings.Settings;
import com.songoda.ultimatestacker.stackable.entity.custom.CustomEntity;
import com.songoda.ultimatestacker.utils.Async;
import com.songoda.ultimatestacker.utils.Methods;
import org.bukkit.Bukkit;
@ -21,96 +24,42 @@ import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
public class EntityStack extends ColdEntityStack {
public class EntityStack extends StackedEntity {
// This is the host entity which is not stored in serialized nbt.
private LivingEntity hostEntity;
private static final UltimateStacker plugin = UltimateStacker.getInstance();
public EntityStack(LivingEntity hostEntity) {
super(hostEntity.getUniqueId());
this.hostEntity = hostEntity;
super(hostEntity);
}
public EntityStack(LivingEntity hostEntity, ColdEntityStack coldEntityStack) {
this(hostEntity);
this.setId(coldEntityStack.getId());
this.stackedEntities.addAll(coldEntityStack.stackedEntities);
public EntityStack(LivingEntity hostEntity, int amount) {
super(hostEntity, amount);
}
public StackedEntity addEntityToStack(Entity entity) {
StackedEntity stackedEntity = addEntityToStackSilently(entity);
updateStack();
return stackedEntity;
public synchronized EntityStack addEntityToStack(int amount) {
setAmount(getAmount() + amount);
return this;
}
@Override
public List<StackedEntity> takeEntities(int amount) {
List<StackedEntity> entities = super.takeEntities(amount);
if (this.stackedEntities.isEmpty())
destroy(true);
return entities;
}
@Override
public List<StackedEntity> takeAllEntities() {
destroy(true);
return super.takeAllEntities();
}
public void updateStack() {
Async.run(() -> {
if (createDuplicates != 0) {
List<StackedEntity> stackedEntities = new ArrayList<>();
try {
for (int i = 0; i < createDuplicates; i++) {
StackedEntity entity = addEntityToStackSilently(getStackedEntity(hostEntity, true));
if (entity != null)
stackedEntities.add(entity);
}
plugin.getDataManager().createStackedEntities(this, stackedEntities);
createDuplicates = 0;
updateNametag();
} catch (Exception ignored) {
//Ignored for now
}
}
});
updateNametag();
}
public void updateNametag() {
if (hostEntity == null) {
//Delay with 1 tick to make sure the entity is loaded.
Bukkit.getScheduler().scheduleSyncDelayedTask(UltimateStacker.getInstance(), this::updateNametag, 1L);
return;
}
hostEntity.setCustomNameVisible(!Settings.HOLOGRAMS_ON_LOOK_ENTITY.getBoolean());
hostEntity.setCustomName(Methods.compileEntityName(hostEntity, getAmount()));
public synchronized EntityStack removeEntityFromStack(int amount) {
setAmount(getAmount() - amount);
return this;
}
public LivingEntity getHostEntity() {
if (hostEntity == null) {
plugin.getEntityStackManager().removeStack(this.hostUniqueId);
return null;
}
return hostEntity;
}
public StackedEntity getHostAsStackedEntity() {
return getStackedEntity(hostEntity);
}
protected void setHostEntity(LivingEntity hostEntity) {
this.hostEntity = hostEntity;
this.hostUniqueId = hostEntity.getUniqueId();
}
private void handleWholeStackDeath(LivingEntity killed, List<Drop> drops, boolean custom, int droppedExp, EntityDeathEvent event) {
plugin.getDataManager().deleteHost(this);
EntityStack stack = plugin.getEntityStackManager().getStack(killed);
// In versions 1.14 and below experience is not dropping. Because of this we are doing this ourselves.
if (ServerVersion.isServerVersionAtOrBelow(ServerVersion.V1_14)) {
Location killedLocation = killed.getLocation();
@ -133,7 +82,7 @@ public class EntityStack extends ColdEntityStack {
}
event.getDrops().clear();
plugin.getEntityStackManager().removeStack(event.getEntity());
stack.destroy();
if (killed.getKiller() == null) return;
plugin.addExp(killed.getKiller(), this);
}
@ -162,12 +111,6 @@ public class EntityStack extends ColdEntityStack {
newEntity.setVelocity(velocity);
stackManager.updateStack(killed, newEntity);
updateStack();
if (stackedEntities.isEmpty()) {
destroy();
}
}
public void onDeath(LivingEntity killed, List<Drop> drops, boolean custom, int droppedExp, EntityDeathEvent event) {
@ -195,42 +138,37 @@ public class EntityStack extends ColdEntityStack {
}
}
public LivingEntity takeOneAndSpawnEntity(Location location) {
if (amount <= 0) return null;
amount--;
LivingEntity entity = Objects.requireNonNull(location.getWorld()).spawn(location, hostEntity.getClass());
this.hostEntity = entity;
updateNameTag();
return entity;
}
public void releaseHost() {
LivingEntity oldHost = hostEntity;
LivingEntity entity = takeOneAndSpawnEntity(hostEntity.getLocation());
if (!stackedEntities.isEmpty()) {
destroy(false);
if (getAmount() >= 0) {
plugin.getEntityStackManager().updateStack(oldHost, entity);
entity.setVelocity(new Vector(ThreadLocalRandom.current().nextDouble(-1, 1.01),
0, ThreadLocalRandom.current().nextDouble(-1, 1.01)).normalize().multiply(0.5));
updateNameTag();
} else {
destroy();
}
updateStack();
}
public void destroy() {
destroy(true);
}
public void destroy(boolean full) {
if (full)
plugin.getEntityStackManager().removeStack(this.hostUniqueId);
if (hostEntity != null) {
try {
hostEntity.setCustomNameVisible(false);
hostEntity.setCustomName(null);
} catch (NullPointerException ignored) {}
}
if (hostEntity == null) return;
Bukkit.getScheduler().runTask(plugin, hostEntity::remove);
hostEntity = null;
hostUniqueId = null;
}
@Override
public String toString() {
return "EntityStack{" +
"hostEntity=" + hostEntity +
", amount=" + amount +
'}';
}
}

View File

@ -7,6 +7,7 @@ import org.bukkit.ChatColor;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import java.util.Collection;
@ -17,157 +18,92 @@ import java.util.UUID;
public class EntityStackManager {
// These are all stacked mobs loaded into memory.
private static final Map<UUID, EntityStack> stacks = new HashMap<>();
// This will only be used for stacks that have not yet been loaded into the game.
private static final Map<UUID, ColdEntityStack> coldStacks = new HashMap<>();
private final UltimateStacker plugin;
public EntityStackManager(UltimateStacker plugin) {
this.plugin = plugin;
}
public EntityStack addStack(EntityStack stack) {
stacks.put(stack.getHostEntity().getUniqueId(), stack);
return stack;
public EntityStack createStack(LivingEntity entity, int amount) {
return new EntityStack(entity, amount);
}
public boolean isStackedEntity(Entity entity) {
return entity.hasMetadata("US_AMOUNT");
}
public int getAmount(Entity entity) {
if (!isStackedEntity(entity)) return 1;
if (entity.getMetadata("US_AMOUNT").isEmpty()) return 1;
return entity.getMetadata("US_AMOUNT").get(0).asInt();
}
public EntityStack addStack(LivingEntity entity) {
if (entity == null) return null;
EntityStack stack = new EntityStack(entity);
plugin.getDataManager().createHostEntity(stack);
stacks.put(entity.getUniqueId(), stack);
return stack;
return addStack(entity, getAmount(entity) == 1 ? 1 : getAmount(entity));
}
public EntityStack addStack(LivingEntity entity, int amount) {
if (entity == null) return null;
EntityStack stack = new EntityStack(entity);
plugin.getDataManager().createHostEntity(stack);
stacks.put(entity.getUniqueId(), stack);
stack.createDuplicates(amount - 1);
plugin.getDataManager().updateHost(stack);
stack.updateStack();
return stack;
}
@Deprecated
public EntityStack addSerializedStack(LivingEntity entity, String customName) {
if (customName != null && customName.contains(String.valueOf(ChatColor.COLOR_CHAR))) {
String name = customName.replace(String.valueOf(ChatColor.COLOR_CHAR), "")
.replace(";", "");
if (!name.contains(":")) return null;
String split = name.split(":")[0];
int amount = Methods.isInt(split) ? Integer.parseInt(split) : 0;
return addStack(entity, amount);
if (isStackedEntity(entity)) {
EntityStack stack = getStack(entity);
stack.addEntityToStack(amount);
return stack;
}
return null;
}
@Deprecated
public EntityStack addSerializedStack(LivingEntity entity) {
return addSerializedStack(entity, entity.getCustomName());
public EntityStack getStack(UUID uuid) {
Entity entity = Bukkit.getEntity(uuid);
if (entity == null) return null;
if (isStackedEntity(entity)) {
if (entity instanceof LivingEntity) {
return new EntityStack((LivingEntity) entity);
}
}
return null;
}
public EntityStack getStack(LivingEntity entity) {
EntityStack stack = getStack(entity.getUniqueId());
if (stack == null) stack = addSerializedStack(entity);
if (!isStackedEntity(entity)) return null;
return new EntityStack(entity);
}
public EntityStack decreaseStack(Entity entity) {
EntityStack stack = getStack((LivingEntity) entity);
if (stack == null) return null;
stack.removeEntityFromStack(1);
return stack;
}
public EntityStack getStack(UUID uuid) {
return stacks.get(uuid);
}
public EntityStack removeStack(Entity entity) {
return removeStack(entity.getUniqueId());
}
public EntityStack removeStack(UUID uuid) {
EntityStack stack = stacks.remove(uuid);
if (stack != null) {
plugin.getDataManager().deleteHost(stack);
stack.destroy();
}
public EntityStack decreaseStack(Entity entity, int amount) {
EntityStack stack = getStack((LivingEntity) entity);
if (stack == null) return null;
stack.removeEntityFromStack(amount);
return stack;
}
public Map<UUID, EntityStack> getStacks() {
return Collections.unmodifiableMap(stacks);
public EntityStack updateStack(LivingEntity entity) {
EntityStack stack = getStack(entity);
if (stack == null) return null;
stack.updateNameTag();
return stack;
}
public EntityStack updateStack(LivingEntity oldEntity, LivingEntity newEntity) {
EntityStack stack = stacks.remove(oldEntity.getUniqueId());
EntityStack stack = getStack(oldEntity);
if (stack == null) return null;
stack.setHostEntity(newEntity);
stacks.put(newEntity.getUniqueId(), stack);
plugin.getDataManager().updateHost(stack);
return stack;
}
@Deprecated
public boolean isStacked(UUID entity) {
return isStackedAndLoaded(entity);
}
public boolean isStackedAndLoaded(LivingEntity entity) {
return stacks.containsKey(entity.getUniqueId());
}
public boolean isStackedAndLoaded(UUID entity) {
return stacks.containsKey(entity);
}
public boolean isEntityInColdStorage(UUID entity) {
return coldStacks.containsKey(entity);
}
public boolean isEntityInColdStorage(LivingEntity entity) {
return isEntityInColdStorage(entity.getUniqueId());
}
public void loadStack(LivingEntity entity) {
ColdEntityStack coldStack = coldStacks.get(entity.getUniqueId());
if (coldStack == null) return;
EntityStack stack = new EntityStack(entity, coldStack);
stack.updateStack();
stacks.put(entity.getUniqueId(), stack);
plugin.getDataManager().updateHost(coldStack);
}
public void unloadStack(LivingEntity entity) {
EntityStack stack = stacks.get(entity.getUniqueId());
if (stack == null) return;
ColdEntityStack coldStack = new EntityStack(entity, stack);
int amount = stack.getAmount();
stack.destroy();
coldStacks.put(entity.getUniqueId(), coldStack);
return createStack(newEntity, amount);
}
public void addStacks(Collection<ColdEntityStack> entities) {
for (ColdEntityStack stack : entities)
coldStacks.put(stack.hostUniqueId, stack);
}
public ColdEntityStack addLegacyColdStack(UUID entity, int amount) {
ColdEntityStack stack = new ColdEntityStack(entity);
plugin.getDataManager().createHostEntity(stack);
stack.createDuplicates(amount - 1);
plugin.getDataManager().updateHost(stack);
coldStacks.put(entity, stack);
return stack;
}
public void tryAndLoadColdEntities() {
for (World world : Bukkit.getWorlds()) {
for (Chunk chunk : world.getLoadedChunks()) {
for (Entity entity : chunk.getEntities()) {
if (entity instanceof LivingEntity)
loadStack((LivingEntity)entity);
}
}
public void setStack(LivingEntity newEntity, int amount) {
if (isStackedEntity(newEntity)) {
EntityStack stack = getStack(newEntity);
stack.setAmount(amount);
System.err.println("Stacked entity already exists, updating stack amount to " + amount);
} else {
createStack(newEntity, amount);
}
}
}

View File

@ -1,22 +1,78 @@
package com.songoda.ultimatestacker.stackable.entity;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.settings.Settings;
import com.songoda.ultimatestacker.utils.Methods;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.metadata.FixedMetadataValue;
import java.util.UUID;
public class StackedEntity {
private final UUID uniqueId;
private final byte[] serializedEntity;
protected int amount;
protected LivingEntity hostEntity;
public StackedEntity(UUID uniqueId, byte[] serializedEntity) {
this.uniqueId = uniqueId;
this.serializedEntity = serializedEntity;
/**
* Gets an existing stack from an entity or creates a new one if it doesn't exist.
* @param entity The entity to get the stack from.
*/
public StackedEntity(LivingEntity entity) {
if (entity == null) return;
if (!UltimateStacker.getInstance().getEntityStackManager().isStackedEntity(entity)) {
entity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), 1));
this.amount = 1;
updateNameTag();
} else {
//get the amount from the entity
this.amount = UltimateStacker.getInstance().getEntityStackManager().getAmount(entity);
}
this.hostEntity = entity;
}
public UUID getUniqueId() {
return uniqueId;
/**
* Creates a new stack or overrides an existing stack.
* @param entity The entity to create the stack for.
* @param amount The amount of entities in the stack.
*/
public StackedEntity(LivingEntity entity, int amount) {
if (entity == null) return;
this.hostEntity = entity;
this.amount = amount;
entity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), amount));
updateNameTag();
}
public byte[] getSerializedEntity() {
return serializedEntity;
public EntityType getType() {
return hostEntity.getType();
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
this.hostEntity.setMetadata("US_AMOUNT", new FixedMetadataValue(UltimateStacker.getInstance(), amount));
updateNameTag();
}
public UUID getUuid() {
return hostEntity.getUniqueId();
}
public LivingEntity getHostEntity() {
return hostEntity;
}
protected void updateNameTag() {
if (hostEntity == null) {
return;
}
hostEntity.setCustomNameVisible(!Settings.HOLOGRAMS_ON_LOOK_ENTITY.getBoolean());
hostEntity.setCustomName(Methods.compileEntityName(hostEntity, getAmount()));
}
}

View File

@ -58,11 +58,14 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
public class StackingTask extends BukkitRunnable {
public class StackingTask implements Runnable {
private final UltimateStacker plugin;
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(4);
private final EntityStackManager stackManager;
@ -96,7 +99,12 @@ public class StackingTask extends BukkitRunnable {
loadedWorlds.add(new SWorld(world));
// Start the stacking task.
runTaskTimerAsynchronously(plugin, 0, Settings.STACK_SEARCH_TICK_SPEED.getInt());
//runTaskTimerAsynchronously(plugin, 0, Settings.STACK_SEARCH_TICK_SPEED.getInt());
executorService.scheduleAtFixedRate(this, 0, Settings.STACK_SEARCH_TICK_SPEED.getInt()*20L, java.util.concurrent.TimeUnit.MILLISECONDS);
}
public void stop() {
executorService.shutdown();
}
@Override
@ -197,32 +205,46 @@ public class StackingTask extends BukkitRunnable {
}
private void processEntity(LivingEntity livingEntity, SWorld sWorld, Location location) {
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;
}
// Get the stack from the entity. It should be noted that this value will
// be null if our entity is not a stack.
EntityStack stack = plugin.getEntityStackManager().getStack(livingEntity);
// Is this entity stacked?
boolean isStack = stack != null;
// The amount that is stackable.
int amountToStack = isStack ? stack.getAmount() : 1;
// Attempt to split our stack. If the split is successful then skip this entity.
if (isStack && attemptSplit(stack, livingEntity)) return;
// If this entity is named, a custom entity or disabled then skip it.
if (!isStack && (livingEntity.getCustomName() != null
&& plugin.getCustomEntityManager().getCustomEntity(livingEntity) == null)
|| !configurationSection.getBoolean("Mobs." + livingEntity.getType().name() + ".Enabled"))
return;
EntityStack baseStack = plugin.getEntityStackManager().getStack(baseEntity);
// Get the maximum stack size for this entity.
int maxEntityStackSize = getEntityStackSize(livingEntity);
int maxEntityStackSize = getEntityStackSize(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.
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"))
return;
// Get similar entities around our entity and make sure those entities are both compatible and stackable.
List<LivingEntity> stackableFriends = new LinkedList<>();
for (LivingEntity entity : getSimilarEntitiesAroundEntity(livingEntity, sWorld, location)) {
for (LivingEntity entity : getSimilarEntitiesAroundEntity(baseEntity, sWorld, location)) {
// Check to see if entity is not stackable.
if (!isEntityStackable(entity))
continue;
@ -231,156 +253,56 @@ public class StackingTask extends BukkitRunnable {
}
// Loop through our similar stackable entities.
for (LivingEntity entity : stackableFriends) {
// Make sure the entity has not already been processed.
if (this.processed.contains(entity.getUniqueId())) continue;
// Check our WorldGuard flag.
Boolean flag = WorldGuardHook.isEnabled() ? WorldGuardHook.getBooleanFlag(livingEntity.getLocation(), "mob-stacking") : null;
if (flag != null && !flag)
continue;
for (LivingEntity friendlyEntity : stackableFriends) {
// Make sure the friendlyEntity has not already been processed.
if (this.processed.contains(friendlyEntity.getUniqueId())) continue;
// Get this entities friendStack.
EntityStack friendStack = stackManager.getStack(entity);
EntityStack friendStack = stackManager.getStack(friendlyEntity);
// Check to see if this entity is stacked and friendStack plus
if (friendStack == null) continue;
// Check to see if this friendlyEntity is stacked and friendStack plus
// our amount to stack is not above our max friendStack size
// for this entity.
if (friendStack != null && (friendStack.getAmount() + amountToStack) <= maxEntityStackSize) {
// for this friendlyEntity.
// If we are a stack lets merge our stack with the just found friend stack.
if (isStack) {
// Get the host entity.
StackedEntity host = stack.getHostAsStackedEntity();
// Get all the stacked entities in our stack and add them to a list.
List<StackedEntity> entities = stack.takeAllEntities();
// Add the host to this list.
entities.add(host);
// Add the collected entities to the new stack.
friendStack.addEntitiesToStackSilently(entities);
// Update friend stack to display changes.
friendStack.updateStack();
// Push changes to the database.
plugin.getDataManager().createStackedEntities(friendStack, entities);
} else {
// If we are not stacked add ourselves to the found friendStack.
plugin.getDataManager().createStackedEntity(friendStack, friendStack.addEntityToStack(livingEntity));
boolean overstack = (friendStack.getAmount() + amountToStack) > maxEntityStackSize;
if (!overstack) {
friendStack.setAmount(friendStack.getAmount() + amountToStack);
if (baseEntity.isLeashed())
Bukkit.getScheduler().runTask(plugin, () -> baseEntity.getWorld()
.dropItemNaturally(baseEntity.getLocation(), CompatibleMaterial.LEAD.getItem()));
if (baseStack != null) {
baseStack.destroy();
}
// Drop lead if applicable then remove our entity and mark it as processed.
if (livingEntity.isLeashed())
Bukkit.getScheduler().runTask(plugin, () -> livingEntity.getWorld()
.dropItemNaturally(livingEntity.getLocation(), CompatibleMaterial.LEAD.getItem()));
Bukkit.getScheduler().runTask(plugin, livingEntity::remove);
processed.add(livingEntity.getUniqueId());
return;
} else if (friendStack == null
&& isStack
&& (stack.getAmount() + 1) <= maxEntityStackSize
&& canFly(entity)
&& Settings.ONLY_STACK_FLYING_DOWN.getBoolean()
&& location.getY() > entity.getLocation().getY()) {
// Make the friend the new stack host.
EntityStack newStack = stackManager.updateStack(livingEntity, entity);
if (newStack == null) {
continue;
}
// Add our entity to that stack
plugin.getDataManager().createStackedEntity(newStack, newStack.addEntityToStack(livingEntity));
// Remove our entity and mark it as processed.
Bukkit.getScheduler().runTask(plugin, livingEntity::remove);
processed.add(livingEntity.getUniqueId());
processed.add(baseEntity.getUniqueId());
return;
}
}
// If our entity is stacked then skip this entity.
if (isStack) return;
// Check our WorldGuard flag.
Boolean flag = WorldGuardHook.isEnabled() ? WorldGuardHook.getBooleanFlag(livingEntity.getLocation(), "mob-stacking") : null;
if (flag != null && !flag)
return;
// Remove all stacked entities from our stackable friends.
stackableFriends.removeIf(stackManager::isStackedAndLoaded);
// If the stack cap is met then delete this entity.
if (maxPerTypeStacksPerChunk != -1
&& (getSimilarStacksInChunk(sWorld, livingEntity) + 1) > maxPerTypeStacksPerChunk) {
Bukkit.getScheduler().runTask(plugin, livingEntity::remove);
this.processed.add(livingEntity.getUniqueId());
return;
}
// If there are none or not enough stackable friends left to create a new entity,
// the stack sizes overlap then skip this entity.
if (stackableFriends.isEmpty()
|| stackableFriends.size() < minEntityStackSize - 1
|| minEntityStackSize > maxEntityStackSize) return;
// If a stack was never found create a new one.
EntityStack newStack = stackManager.addStack(livingEntity);
List<LivingEntity> livingEntities = new LinkedList<>();
// Loop through the unstacked and unprocessed stackable friends while not creating
// a stack larger than the maximum.
stackableFriends.stream().filter(entity -> !stackManager.isStackedAndLoaded(entity)
&& !this.processed.contains(entity.getUniqueId())).limit(maxEntityStackSize).forEach(entity -> {
// Make sure we're not naming some poor kids pet.
if (entity.getCustomName() != null
&& plugin.getCustomEntityManager().getCustomEntity(entity) == null) {
processed.add(livingEntity.getUniqueId());
newStack.destroy();
return;
}
// Drop lead if applicable then remove our entity and mark it as processed.
if (entity.isLeashed()) {
Bukkit.getScheduler().runTask(plugin, () -> entity.getWorld().dropItemNaturally(entity.getLocation(), CompatibleMaterial.LEAD.getItem()));
}
livingEntities.add(entity);
Bukkit.getScheduler().runTask(plugin, entity::remove);
processed.add(entity.getUniqueId());
});
// Add our new approved entities to the new stack and commit them to the database.
plugin.getDataManager().createStackedEntities(newStack,
newStack.addRawEntitiesToStackSilently(livingEntities));
// Update our stack.
newStack.updateStack();
}
public boolean attemptSplit(EntityStack stack, LivingEntity livingEntity) {
int stackSize = stack.getAmount();
public boolean attemptSplit(EntityStack baseStack, LivingEntity livingEntity) {
int stackSize = baseStack.getAmount();
int maxEntityStackAmount = getEntityStackSize(livingEntity);
if (stackSize <= maxEntityStackAmount) return false;
// Destroy the stack.
stack.destroy();
baseStack.setAmount(maxEntityStackAmount);
Bukkit.getScheduler().runTask(plugin, () -> {
for (int i = stackSize; i > 0; i -= maxEntityStackAmount) {
LivingEntity entity = stack.takeOneAndSpawnEntity(livingEntity.getLocation());
if (entity == null) continue;
EntityStack newStack = plugin.getEntityStackManager().addStack(entity);
newStack.moveEntitiesFromStack(stack, Math.min(i, maxEntityStackAmount) - 1);
newStack.updateStack();
}
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.createStack(newEntity, toAdd);
processed.add(newEntity.getUniqueId());
finalStackSize -= maxEntityStackAmount;
} while (finalStackSize >= 0);
});
// Remove our entity and mark it as processed.
Bukkit.getScheduler().runTask(plugin, livingEntity::remove);
processed.add(livingEntity.getUniqueId());
return true;
}
@ -443,7 +365,7 @@ public class StackingTask extends BukkitRunnable {
public int getSimilarStacksInChunk(SWorld sWorld, LivingEntity entity) {
int count = 0;
for (LivingEntity e : getNearbyEntities(sWorld, entity.getLocation(), -1, true)) {
if (entity.getType() == e.getType() && plugin.getEntityStackManager().isStackedAndLoaded(e))
if (entity.getType() == e.getType() && plugin.getEntityStackManager().isStackedEntity(e))
count++;
}
return count;