diff --git a/pom.xml b/pom.xml
index aeba90f..dff2ba4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
com.songoda
UltimateStacker
4.0.0
- 2.3.0
+ 2.3.1
clean install
UltimateStacker-${project.version}
@@ -112,7 +112,7 @@
com.songoda
SongodaCore
- 2.6.16
+ 2.6.17-SNAPSHOT
compile
diff --git a/src/main/java/com/songoda/ultimatestacker/UltimateStacker.java b/src/main/java/com/songoda/ultimatestacker/UltimateStacker.java
index b8fdd84..e23b20d 100644
--- a/src/main/java/com/songoda/ultimatestacker/UltimateStacker.java
+++ b/src/main/java/com/songoda/ultimatestacker/UltimateStacker.java
@@ -49,6 +49,7 @@ import com.songoda.ultimatestacker.tasks.StackingTask;
import com.songoda.ultimatestacker.utils.Methods;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
+import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Item;
@@ -382,6 +383,19 @@ public class UltimateStacker extends SongodaPlugin {
//////// Convenient API //////////
+ /**
+ * Spawn a stacked item at a location
+ *
+ * @param item The item to spawn
+ * @param amount The amount of items to spawn
+ * @param location The location to spawn the item
+ */
+ public static void spawnStackedItem(ItemStack item, int amount, Location location) {
+ location.getWorld().dropItem(location, item, dropped -> {
+ updateItemAmount(dropped, amount);
+ });
+ }
+
/**
* Change the stacked amount for this item
*
diff --git a/src/main/java/com/songoda/ultimatestacker/listeners/DeathListeners.java b/src/main/java/com/songoda/ultimatestacker/listeners/DeathListeners.java
index 0232f20..620d073 100644
--- a/src/main/java/com/songoda/ultimatestacker/listeners/DeathListeners.java
+++ b/src/main/java/com/songoda/ultimatestacker/listeners/DeathListeners.java
@@ -7,6 +7,7 @@ import com.songoda.core.lootables.loot.DropUtils;
import com.songoda.ultimatestacker.UltimateStacker;
import com.songoda.ultimatestacker.settings.Settings;
import com.songoda.ultimatestacker.stackable.entity.EntityStack;
+import org.bukkit.GameMode;
import org.bukkit.GameRule;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
@@ -153,8 +154,9 @@ public class DeathListeners implements Listener {
if (!plugin.getEntityStackManager().isStackedAndLoaded(entity)) return;
EntityStack stack = plugin.getEntityStackManager().getStack(entity);
- if (Settings.KILL_WHOLE_STACK_ON_DEATH.getBoolean() && Settings.REALISTIC_DAMAGE.getBoolean()) {
- Player player = (Player) event.getDamager();
+ Player player = (Player) event.getDamager();
+
+ if (Settings.KILL_WHOLE_STACK_ON_DEATH.getBoolean() && Settings.REALISTIC_DAMAGE.getBoolean() && !player.getGameMode().equals(GameMode.CREATIVE)) {
ItemStack tool = player.getInventory().getItemInHand();
if (tool.getType().getMaxDurability() < 1 || (tool.getItemMeta() != null && (tool.getItemMeta().isUnbreakable()
|| (ServerProject.isServer(ServerProject.SPIGOT, ServerProject.PAPER) && tool.getItemMeta().isUnbreakable()))))
diff --git a/src/main/java/com/songoda/ultimatestacker/tasks/StackingTask.java b/src/main/java/com/songoda/ultimatestacker/tasks/StackingTask.java
index 97ae05d..a0950a1 100644
--- a/src/main/java/com/songoda/ultimatestacker/tasks/StackingTask.java
+++ b/src/main/java/com/songoda/ultimatestacker/tasks/StackingTask.java
@@ -17,7 +17,32 @@ import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.entity.*;
+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;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.scheduler.BukkitRunnable;
@@ -30,6 +55,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
public class StackingTask extends BukkitRunnable {
@@ -42,7 +70,7 @@ public class StackingTask extends BukkitRunnable {
private final Map cachedChunks = new HashMap<>();
- private final HashMap entityStackSizes = new HashMap();
+ private final Map entityStackSizes = new HashMap<>();
private final int maxEntityStackSize = Settings.MAX_STACK_ENTITIES.getInt(),
minEntityStackSize = Settings.MIN_STACK_ENTITIES.getInt(),
searchRadius = Settings.SEARCH_RADIUS.getInt(),
@@ -83,11 +111,10 @@ public class StackingTask extends BukkitRunnable {
// Get the loaded entities from the current world and reverse them.
List entities;
try {
- entities = sWorld.getLivingEntities();
- } catch (Exception ignored) {
+ entities = getLivingEntitiesSync(sWorld).get();
+ } catch (ExecutionException | InterruptedException ex) {
+ ex.printStackTrace();
continue;
- // Sometimes accessing this method asynchronously throws an error. This is super rare and
- // as such doesn't really affect the plugin so we're just going to ignore this failure.
}
Collections.reverse(entities);
@@ -113,6 +140,20 @@ public class StackingTask extends BukkitRunnable {
this.cachedChunks.clear();
}
+ private Future> getLivingEntitiesSync(SWorld sWorld) {
+ CompletableFuture> future = new CompletableFuture<>();
+ Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> future.complete(sWorld.getLivingEntities()));
+
+ return future;
+ }
+
+ private Future getEntitiesInChunkSync(CachedChunk cachedChunk) {
+ CompletableFuture future = new CompletableFuture<>();
+ Bukkit.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> future.complete(cachedChunk.getEntities()));
+
+ return future;
+ }
+
public boolean isWorldDisabled(World world) {
return disabledWorlds.stream().anyMatch(worldStr -> world.getName().equalsIgnoreCase(worldStr));
}
@@ -243,6 +284,10 @@ public class StackingTask extends BukkitRunnable {
// 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));
@@ -367,19 +412,21 @@ public class StackingTask extends BukkitRunnable {
List entities = new ArrayList<>();
for (CachedChunk chunk : getNearbyChunks(sWorld, location, radius, singleChunk)) {
if (chunk == null) continue;
- Entity[] entityArray = new Entity[0];
+ Entity[] entityArray;
if (cachedChunks.containsKey(chunk)) {
entityArray = cachedChunks.get(chunk);
} else {
try {
- entityArray = chunk.getEntities();
+ entityArray = getEntitiesInChunkSync(chunk).get();
cachedChunks.put(chunk, entityArray);
- } catch (Exception ignored) {
- // Sometimes accessing this method asynchronously throws an error. This is super rare and
- // as such doesn't really affect the plugin so we're just going to ignore this failure.
+ } catch (ExecutionException | InterruptedException ex) {
+ ex.printStackTrace();
+ continue;
}
}
+
if (entityArray == null) continue;
+
for (Entity e : entityArray) {
if (e == null) continue;
if (e.getWorld() != location.getWorld()
@@ -388,6 +435,7 @@ public class StackingTask extends BukkitRunnable {
entities.add((LivingEntity) e);
}
}
+
return entities;
}