Merge branch 'development'

This commit is contained in:
Brianna O'Keefe 2024-03-12 14:26:39 -05:00
commit 92027b0bb6
20 changed files with 613 additions and 362 deletions

View File

@ -35,7 +35,7 @@ jobs:
with:
append_snapshot: ${{ github.ref_type == 'tag' && 'false' || 'true' }}
version: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
increment_version: ${{ github.ref_type == 'tag' && '' || 'patch' }}
increment_version: ${{ github.ref_type != 'tag' && 'patch' || '' }}
increment_version_only_if_not_snapshot_version: ${{ github.ref == 'refs/heads/development' && 'true' || 'false' }}
- name: Build with Maven

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.craftaro</groupId>
<artifactId>UltimateStacker-Parent</artifactId>
<version>3.0.1</version>
<version>3.1.2</version>
</parent>
<artifactId>UltimateStacker-API</artifactId>
<version>1.0.0-SNAPSHOT</version>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.craftaro</groupId>
<artifactId>UltimateStacker-Parent</artifactId>
<version>3.0.1</version>
<version>3.1.2</version>
</parent>
<artifactId>UltimateStacker-Plugin</artifactId>
@ -118,6 +118,13 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.craftaro</groupId>
<artifactId>CraftaroCore</artifactId>
@ -128,7 +135,7 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.18-R0.1-SNAPSHOT</version>
<version>1.20-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>

View File

@ -10,12 +10,10 @@ import com.craftaro.core.dependency.Dependency;
import com.craftaro.core.gui.GuiManager;
import com.craftaro.core.hooks.EntityStackerManager;
import com.craftaro.core.hooks.HologramManager;
import com.craftaro.core.hooks.HookManager;
import com.craftaro.core.hooks.ProtectionManager;
import com.craftaro.core.hooks.WorldGuardHook;
import com.craftaro.core.hooks.holograms.DecentHologramsHolograms;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.core.utils.TextUtils;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatestacker.api.UltimateStackerApi;
import com.craftaro.ultimatestacker.api.stack.block.BlockStack;
import com.craftaro.ultimatestacker.api.stack.block.BlockStackManager;
@ -25,13 +23,7 @@ import com.craftaro.ultimatestacker.api.stack.item.StackedItemManager;
import com.craftaro.ultimatestacker.api.stack.spawner.SpawnerStack;
import com.craftaro.ultimatestacker.api.stack.spawner.SpawnerStackManager;
import com.craftaro.ultimatestacker.api.utils.Hologramable;
import com.craftaro.ultimatestacker.commands.CommandConvert;
import com.craftaro.ultimatestacker.commands.CommandGiveSpawner;
import com.craftaro.ultimatestacker.commands.CommandLootables;
import com.craftaro.ultimatestacker.commands.CommandReload;
import com.craftaro.ultimatestacker.commands.CommandRemoveAll;
import com.craftaro.ultimatestacker.commands.CommandSettings;
import com.craftaro.ultimatestacker.commands.CommandSpawn;
import com.craftaro.ultimatestacker.commands.*;
import com.craftaro.ultimatestacker.database.migrations._1_InitialMigration;
import com.craftaro.ultimatestacker.database.migrations._2_EntityStacks;
import com.craftaro.ultimatestacker.database.migrations._3_BlockStacks;
@ -39,15 +31,7 @@ import com.craftaro.ultimatestacker.database.migrations._6_RemoveStackedEntityTa
import com.craftaro.ultimatestacker.hook.StackerHook;
import com.craftaro.ultimatestacker.hook.hooks.JobsHook;
import com.craftaro.ultimatestacker.hook.hooks.SuperiorSkyblock2Hook;
import com.craftaro.ultimatestacker.listeners.BlockListeners;
import com.craftaro.ultimatestacker.listeners.BreedListeners;
import com.craftaro.ultimatestacker.listeners.ClearLagListeners;
import com.craftaro.ultimatestacker.listeners.DeathListeners;
import com.craftaro.ultimatestacker.listeners.InteractListeners;
import com.craftaro.ultimatestacker.listeners.ShearListeners;
import com.craftaro.ultimatestacker.listeners.SheepDyeListeners;
import com.craftaro.ultimatestacker.listeners.SpawnerListeners;
import com.craftaro.ultimatestacker.listeners.TameListeners;
import com.craftaro.ultimatestacker.listeners.*;
import com.craftaro.ultimatestacker.listeners.entity.EntityCurrentListener;
import com.craftaro.ultimatestacker.listeners.entity.EntityListeners;
import com.craftaro.ultimatestacker.listeners.item.ItemCurrentListener;
@ -62,6 +46,7 @@ import com.craftaro.ultimatestacker.stackable.entity.custom.CustomEntityManager;
import com.craftaro.ultimatestacker.stackable.item.StackedItemManagerImpl;
import com.craftaro.ultimatestacker.stackable.spawner.SpawnerStackImpl;
import com.craftaro.ultimatestacker.stackable.spawner.SpawnerStackManagerImpl;
import com.craftaro.ultimatestacker.tasks.BreedingTask;
import com.craftaro.ultimatestacker.tasks.StackingTask;
import com.craftaro.ultimatestacker.utils.Async;
import org.apache.commons.lang.WordUtils;
@ -72,12 +57,7 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.PluginManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.*;
public class UltimateStacker extends SongodaPlugin {
@ -99,8 +79,10 @@ public class UltimateStacker extends SongodaPlugin {
private CommandManager commandManager;
private CustomEntityManager customEntityManager;
private StackingTask stackingTask;
private BreedingTask breedingTask;
private UltimateStackerApi API;
private SuperiorSkyblock2Hook superiorSkyblock2Hook;
private boolean instantStacking;
public static UltimateStacker getInstance() {
return INSTANCE;
@ -121,9 +103,8 @@ public class UltimateStacker extends SongodaPlugin {
@Override
public void onPluginDisable() {
if (this.stackingTask != null) {
this.stackingTask.stop();
}
if (this.stackingTask != null)
this.stackingTask.cancel();
this.dataManager.saveBatchSync(this.spawnerStackManager.getStacksData());
this.dataManager.saveBatchSync(this.blockStackManager.getStacksData());
this.dataManager.shutdownNow();
@ -153,7 +134,7 @@ public class UltimateStacker extends SongodaPlugin {
new CommandGiveSpawner(this),
new CommandSpawn(this),
new CommandLootables(this),
new CommandConvert( guiManager)
new CommandConvert(guiManager)
);
PluginManager pluginManager = Bukkit.getPluginManager();
@ -212,6 +193,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);
@ -246,7 +228,7 @@ public class UltimateStacker extends SongodaPlugin {
public void onDataLoad() {
if (HologramManager.isEnabled())
// Set the offset so that the holograms don't end up inside the blocks.
HologramManager.getHolograms().setPositionOffset(.5,.65,.5);
HologramManager.getHolograms().setPositionOffset(.5, .65, .5);
// Load current data.
final boolean useSpawnerHolo = Settings.SPAWNER_HOLOGRAMS.getBoolean();
@ -262,16 +244,21 @@ public class UltimateStacker extends SongodaPlugin {
}
});
this.stackingTask = new StackingTask(this);
//Start stacking task
if (Settings.STACK_ENTITIES.getBoolean()) {
this.breedingTask = new BreedingTask(this);
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;
this.blockStackManager.addBlock(blockStack);
if (useBlockHolo) {
if (blockStack == null) return;
if (blockStack.getLocation().getWorld() != null) {
if (blockStack.getLocation().getWorld() != null)
updateHologram(blockStack);
}
}
});
}
@ -301,8 +288,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.cancel();
if (Settings.STACK_ENTITIES.getBoolean()) {
this.stackingTask = new StackingTask(this);
}
this.mobFile.load();
this.itemFile.load();
@ -409,6 +400,10 @@ public class UltimateStacker extends SongodaPlugin {
return superiorSkyblock2Hook;
}
public boolean isInstantStacking() {
return instantStacking;
}
//////// Convenient API //////////
/**
@ -460,4 +455,8 @@ public class UltimateStacker extends SongodaPlugin {
return !whitelist.isEmpty() && !whitelist.contains(combined)
|| !blacklist.isEmpty() && blacklist.contains(combined);
}
public BreedingTask getBreedingTask() {
return breedingTask;
}
}

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,29 +1,39 @@
package com.craftaro.ultimatestacker.listeners;
import com.craftaro.ultimatestacker.UltimateStacker;
import org.bukkit.Bukkit;
import com.craftaro.ultimatestacker.api.stack.entity.EntityStack;
import com.craftaro.ultimatestacker.api.stack.entity.EntityStackManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityBreedEvent;
import org.bukkit.metadata.FixedMetadataValue;
public class BreedListeners implements Listener {
private final UltimateStacker plugin;
private final EntityStackManager entityStackManager;
public BreedListeners(UltimateStacker plugin) {
this.plugin = plugin;
this.entityStackManager = plugin.getEntityStackManager();
}
@EventHandler
public void onBread(EntityBreedEvent event) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
event.getFather().removeMetadata("breedCooldown", plugin);
event.getMother().removeMetadata("breedCooldown", plugin);
}, 5 * 20 * 60);
event.getFather().setMetadata("breedCooldown", new FixedMetadataValue(plugin, true));
event.getFather().removeMetadata("inLove", plugin);
event.getMother().setMetadata("breedCooldown", new FixedMetadataValue(plugin, true));
event.getMother().removeMetadata("inLove", plugin);
EntityStack stackedMother = entityStackManager.getStackedEntity(event.getMother());
EntityStack stackedFather = entityStackManager.getStackedEntity(event.getFather());
plugin.getBreedingTask().addBreedingTicket(event.getMother(), event.getFather());
if (stackedMother != null) {
EntityStack stack = entityStackManager.getStackedEntity(event.getMother());
if (stack.getAmount() <= 1) return;
stack.releaseHost();
}
if (stackedFather != null) {
EntityStack stack = entityStackManager.getStackedEntity(event.getFather());
if (stack.getAmount() <= 1) return;
stack.releaseHost();
}
}
}

View File

@ -77,12 +77,10 @@ public class DeathListeners implements Listener {
List<Drop> drops = custom ? plugin.getLootablesManager().getDrops(event.getEntity())
: event.getDrops().stream().map(Drop::new).collect(Collectors.toList());
if (custom) {
for (ItemStack item : new ArrayList<>(event.getDrops())) {
if (custom)
for (ItemStack item : new ArrayList<>(event.getDrops()))
if (shouldDrop(event.getEntity(), item.getType()))
drops.add(new Drop(item));
}
}
if (plugin.getCustomEntityManager().getCustomEntity(entity) == null) {
//Run commands here, or it will be buggy

View File

@ -1,20 +1,12 @@
package com.craftaro.ultimatestacker.listeners;
import com.craftaro.core.compatibility.ServerVersion;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatestacker.UltimateStacker;
import com.craftaro.ultimatestacker.api.stack.entity.EntityStack;
import com.craftaro.ultimatestacker.settings.Settings;
import com.craftaro.ultimatestacker.stackable.entity.Split;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.Cat;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Horse;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Wolf;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
@ -64,66 +56,75 @@ public class InteractListeners implements Listener {
&& !((Ageable) entity).isAdult()) {
return;
}
entity.setMetadata("inLove", new FixedMetadataValue(plugin, true));
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
if (entity.isDead()) return;
entity.removeMetadata("inLove", plugin);
}, 20 * 20);
plugin.getBreedingTask().addInLoveTicket(entity);
}
}
private boolean correctFood(ItemStack is, Entity entity) {
Material type = is.getType();
switch (entity.getType().name()) {
case "COW":
case "MUSHROOM_COW":
case "SHEEP":
return type == Material.WHEAT;
case "PIG":
return type == Material.CARROT || (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9) && type == Material.BEETROOT) || type == Material.POTATO;
return type == Material.CARROT || type == Material.BEETROOT || type == Material.POTATO;
case "CHICKEN":
return type == XMaterial.WHEAT_SEEDS.parseMaterial()
return type == Material.WHEAT_SEEDS
|| type == Material.MELON_SEEDS
|| type == Material.PUMPKIN_SEEDS
|| type == XMaterial.BEETROOT_SEEDS.parseMaterial();
|| type == Material.BEETROOT_SEEDS;
case "HORSE":
return (type == Material.GOLDEN_APPLE || type == Material.GOLDEN_CARROT) && ((Horse)entity).isTamed();
case "DONKEY":
case "MULE":
return (type == Material.GOLDEN_APPLE || type == Material.GOLDEN_CARROT) && ((AbstractHorse) entity).isTamed();
case "WOLF":
return type == XMaterial.BEEF.parseMaterial()
|| type == XMaterial.CHICKEN.parseMaterial()
|| type == XMaterial.COD.parseMaterial()
|| type == XMaterial.MUTTON.parseMaterial()
|| type == XMaterial.PORKCHOP.parseMaterial()
|| type == XMaterial.RABBIT.parseMaterial()
|| XMaterial.SALMON.equals(XMaterial.matchXMaterial(is))
|| type == XMaterial.COOKED_BEEF.parseMaterial()
|| type == XMaterial.COOKED_CHICKEN.parseMaterial()
|| type == XMaterial.COOKED_COD.parseMaterial()
|| type == XMaterial.COOKED_MUTTON.parseMaterial()
|| type == XMaterial.COOKED_PORKCHOP.parseMaterial()
|| type == XMaterial.COOKED_RABBIT.parseMaterial()
|| XMaterial.COOKED_SALMON.equals(XMaterial.matchXMaterial(is))
return type == Material.BEEF
|| type == Material.CHICKEN
|| type == Material.COD
|| type == Material.MUTTON
|| type == Material.PORKCHOP
|| type == Material.RABBIT
|| type == Material.SALMON
|| type == Material.COOKED_BEEF
|| type == Material.COOKED_CHICKEN
|| type == Material.COOKED_COD
|| type == Material.COOKED_MUTTON
|| type == Material.COOKED_PORKCHOP
|| type == Material.COOKED_RABBIT
|| type == Material.COOKED_SALMON
|| type == Material.ROTTEN_FLESH
&& ((Wolf) entity).isTamed();
case "OCELOT":
return (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)
? type == Material.SALMON
|| type == Material.COD
|| type == Material.PUFFERFISH
|| type == Material.TROPICAL_FISH
: type == XMaterial.COD.parseMaterial()); // Now broken in 1.13 ((Ocelot) entity).isTamed()
case "PANDA":
return (type == Material.BAMBOO);
case "FOX":
return type == Material.SWEET_BERRIES;
case "CAT":
return (type == Material.COD || type == Material.SALMON) && ((Cat) entity).isTamed();
return (type == Material.COD || type == Material.SALMON) && ((Tameable) entity).isTamed();
case "PANDA":
return type == Material.BAMBOO;
case "FOX":
return type == Material.SWEET_BERRIES || type == Material.GLOW_BERRIES;
case "RABBIT":
return type == Material.CARROT || type == Material.GOLDEN_CARROT || type == Material.DANDELION;
case "LLAMA":
case "TRADER_LLAMA":
return type == Material.HAY_BLOCK;
case "TURTLE":
return type == Material.SEAGRASS;
case "HOGLIN":
return type == Material.CRIMSON_FUNGUS;
case "STRIDER":
return type == Material.WARPED_FUNGUS;
case "BEE":
return type == Material.HONEYCOMB || type == Material.HONEY_BOTTLE;
case "AXOLOTL":
return type == Material.TROPICAL_FISH_BUCKET;
case "GOAT":
return type == Material.WHEAT;
case "GLOW_SQUID":
return type == Material.GLOW_INK_SAC;
case "CAMEL":
return type == Material.CACTUS;
default:
return false;
}

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?");
@ -103,7 +108,11 @@ public class Settings implements com.craftaro.ultimatestacker.api.Settings {
"\"HORSE_STYLE\", \"HORSE_CARRYING_CHEST\", \"HORSE_HAS_ARMOR\", \"HORSE_HAS_SADDLE\",",
"\"HORSE_JUMP\", \"RABBIT_TYPE\", \"VILLAGER_PROFESSION\", \"LLAMA_COLOR\",",
"\"LLAMA_STRENGTH\", \"PARROT_TYPE\", \"PUFFERFISH_STATE\", \"TROPICALFISH_PATTERN\",",
"\"TROPICALFISH_BODY_COLOR\", \"TROPICALFISH_PATTERN_COLOR\", \"PHANTOM_SIZE\", \"CAT_TYPE\".");
"\"TROPICALFISH_BODY_COLOR\", \"TROPICALFISH_PATTERN_COLOR\", \"PHANTOM_SIZE\", \"CAT_TYPE\"",
"\"AXOLOTL_VARIANT\", \"AXOLOTL_PLAYING_DEAD\", \"GLOW_SQUID_DARK_TICKS\", \"GOAT_HAS_HORNS\",",
"\"FROG_VARIANT\", \"TADPOLE_AGE\", \"WARDEN_ANGER_LEVEL\", \"SNIFFER_HAS_SEEDS\",",
"\"FOX_TYPE\", \"HOGLIN_IMMUNE\".");
public static final ConfigSetting SPLIT_CHECKS = new ConfigSetting(config, "Entities.Split Checks", Arrays.asList(Split.values()).stream()
.map(Split::name).collect(Collectors.toList()),

View File

@ -19,7 +19,7 @@ public enum Check {
SLIME_SIZE(true),
PIG_SADDLE(true),
SHEEP_SHEARED(true),
SHEEP_COLOR(false),
SHEEP_COLOR(true),
SNOWMAN_DERPED(true),
WOLF_COLLAR_COLOR(true),
OCELOT_TYPE(false),
@ -39,8 +39,17 @@ public enum Check {
TROPICALFISH_BODY_COLOR(true),
TROPICALFISH_PATTERN_COLOR(true),
PHANTOM_SIZE(true),
CAT_TYPE(false);
CAT_TYPE(false),
AXOLOTL_VARIANT(false),
AXOLOTL_PLAYING_DEAD(true),
GLOW_SQUID_DARK_TICKS(true),
GOAT_HAS_HORNS(false),
FROG_VARIANT(true),
TADPOLE_AGE(false),
WARDEN_ANGER_LEVEL(false),
SNIFFER_HAS_SEEDS(true),
FOX_TYPE(false),
HOGLIN_IMMUNE(true);
private final boolean isEnabledByDefault;
private final static Map<String, Check> checks = new HashMap();

View File

@ -1,12 +1,10 @@
package com.craftaro.ultimatestacker.stackable.entity;
import com.craftaro.core.SongodaCore;
import com.craftaro.core.compatibility.ServerVersion;
import com.craftaro.core.lootables.loot.Drop;
import com.craftaro.core.lootables.loot.DropUtils;
import com.craftaro.core.utils.EntityUtils;
import com.craftaro.ultimatestacker.UltimateStacker;
import com.craftaro.ultimatestacker.api.UltimateStackerApi;
import com.craftaro.ultimatestacker.api.events.entity.EntityStackKillEvent;
import com.craftaro.ultimatestacker.api.stack.entity.EntityStack;
import com.craftaro.ultimatestacker.settings.Settings;
@ -14,7 +12,7 @@ import com.craftaro.ultimatestacker.utils.Async;
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,10 +20,10 @@ 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;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@ -33,35 +31,73 @@ 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;
/**
* 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 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.
* @param amount The amount of entities in the stack.
*/
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 +114,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();
}
@ -196,10 +237,10 @@ public class EntityStackImpl implements EntityStack {
@Override
public synchronized LivingEntity takeOneAndSpawnEntity(Location location) {
if (amount <= 0) return null;
LivingEntity entity = (LivingEntity) Objects.requireNonNull(location.getWorld()).spawnEntity(location, hostEntity.getType());
if (Settings.NO_AI.getBoolean()) {
if (Settings.NO_AI.getBoolean())
EntityUtils.setUnaware(entity);
}
this.hostEntity = entity;
setAmount(amount--);
updateNameTag();
@ -208,13 +249,27 @@ public class EntityStackImpl implements EntityStack {
@Override
public synchronized void releaseHost() {
LivingEntity oldHost = hostEntity;
LivingEntity entity = takeOneAndSpawnEntity(hostEntity.getLocation());
if (getAmount() >= 0) {
plugin.getEntityStackManager().updateStack(oldHost, entity);
updateNameTag();
wipeData();
//Summon a new entity, update the stack and remove the metadata from the old entity
this.hostEntity = takeOneAndSpawnEntity(hostEntity.getLocation());
if (amount == 2) {
wipeData();
} else {
destroy();
setAmount(amount - 1);
updateNameTag();
}
}
private synchronized void wipeData() {
hostEntity.setCustomName(null);
hostEntity.setCustomNameVisible(false);
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14)) {
PersistentDataContainer container = hostEntity.getPersistentDataContainer();
container.remove(STACKED_ENTITY_KEY);
} else {
hostEntity.removeMetadata("US_AMOUNT", plugin);
}
}
@ -226,9 +281,9 @@ public class EntityStackImpl implements EntityStack {
}
public void updateNameTag() {
if (hostEntity == null) {
if (hostEntity == null)
return;
}
hostEntity.setCustomNameVisible(!Settings.HOLOGRAMS_ON_LOOK_ENTITY.getBoolean());
hostEntity.setCustomName(Methods.compileEntityName(hostEntity, getAmount()));
}

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

@ -22,8 +22,6 @@ import java.util.concurrent.atomic.AtomicReference;
public class StackedItemManagerImpl implements StackedItemManager {
private final static int MAX_INT = 1500000000;
@Override
public @NotNull StackedItem getStackedItem(Item item) {
return new StackedItemImpl(item);
@ -106,8 +104,8 @@ public class StackedItemManagerImpl implements StackedItemManager {
}
}
int maxItemStackSize = Settings.MAX_STACK_ITEMS.getInt();
if (maxItemStackSize > MAX_INT) maxItemStackSize = MAX_INT;
long maxItemStackSize = Settings.MAX_STACK_ITEMS.getLong();
if (maxItemStackSize > Integer.MAX_VALUE) maxItemStackSize = Integer.MAX_VALUE;
ItemStack fromItemStack = from.getItemStack();
ItemStack toItemStack = to.getItemStack();

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

@ -0,0 +1,48 @@
package com.craftaro.ultimatestacker.tasks;
import com.craftaro.core.task.TaskScheduler;
import com.craftaro.ultimatestacker.UltimateStacker;
import org.bukkit.entity.LivingEntity;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class BreedingTask extends TaskScheduler {
private final UltimateStacker plugin;
private final Set<UUID> breedingEntities = new HashSet<>();
private final Set<UUID> inLoveEntities = new HashSet<>();
public BreedingTask(UltimateStacker plugin) {
super(plugin);
this.plugin = plugin;
}
public void addBreedingTicket(LivingEntity mother, LivingEntity father) {
UUID motherUUID = mother.getUniqueId();
UUID fatherUUID = father.getUniqueId();
addTask(() -> {
breedingEntities.remove(motherUUID);
breedingEntities.remove(fatherUUID);
}, 20L * 1000L);
breedingEntities.add(motherUUID);
inLoveEntities.remove(motherUUID);
breedingEntities.add(fatherUUID);
inLoveEntities.remove(fatherUUID);
}
public void addInLoveTicket(LivingEntity entity) {
UUID entityUUID = entity.getUniqueId();
addTask(() -> inLoveEntities.remove(entityUUID), 5L * 60L * 1000L);
inLoveEntities.add(entityUUID);
}
public boolean isInQueue(UUID uniqueId) {
return breedingEntities.contains(uniqueId) || inLoveEntities.contains(uniqueId);
}
}

View File

@ -2,8 +2,8 @@ package com.craftaro.ultimatestacker.tasks;
import com.craftaro.core.compatibility.ServerVersion;
import com.craftaro.core.hooks.WorldGuardHook;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.core.world.SWorld;
import com.craftaro.third_party.com.cryptomorin.xseries.XMaterial;
import com.craftaro.ultimatestacker.UltimateStacker;
import com.craftaro.ultimatestacker.api.stack.entity.EntityStack;
import com.craftaro.ultimatestacker.api.stack.entity.EntityStackManager;
@ -11,64 +11,24 @@ import com.craftaro.ultimatestacker.settings.Settings;
import com.craftaro.ultimatestacker.stackable.entity.Check;
import com.craftaro.ultimatestacker.stackable.entity.custom.CustomEntity;
import com.craftaro.ultimatestacker.utils.CachedChunk;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.*;
import org.bukkit.configuration.ConfigurationSection;
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.entity.*;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;
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;
import java.util.concurrent.TimeUnit;
import java.util.*;
import java.util.stream.Collectors;
import static com.craftaro.ultimatestacker.stackable.entity.Check.getChecks;
public class StackingTask extends TimerTask {
public class StackingTask extends BukkitRunnable {
private final UltimateStacker plugin;
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
private final EntityStackManager stackManager;
private final BreedingTask breedingTask;
private final ConfigurationSection configurationSection = UltimateStacker.getInstance().getMobFile();
private final List<UUID> processed = new ArrayList<>();
@ -77,6 +37,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,278 +48,259 @@ 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();
stackManager = plugin.getEntityStackManager();
breedingTask = plugin.getBreedingTask();
// 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);
}
public void stop() {
executorService.shutdown();
int tickRate = Settings.STACK_SEARCH_TICK_SPEED.getInt();
runTaskTimer(plugin, tickRate, tickRate);
}
@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;
try {
entities = getLivingEntitiesSync(sWorld).get();
} catch (ExecutionException | InterruptedException ex) {
ex.printStackTrace();
continue;
// Get the loaded entities from the current world and reverse them.
entities = sWorld.getLivingEntities();
//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()));
}
Collections.reverse(entities);
Bukkit.getScheduler().runTask(plugin, () -> {
// Loop through the entities.
for (LivingEntity entity : entities) {
// Make sure our entity has not already been processed.
// Skip it if it has been.
if (this.processed.contains(entity.getUniqueId())) continue;
// Loop through the entities.
for (LivingEntity entity : entities) {
// Make sure our entity has not already been processed.
// Skip it if it has been.
if (processed.contains(entity.getUniqueId())) continue;
// 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();
// Get entity location to pass around as its faster this way.
Location location = entity.getLocation();
// Process the entity.
this.processEntity(entity, sWorld, location);
}
});
// Process the entity.
processEntity(entity, location, entities);
}
}
// Clear caches in preparation for the next run.
} catch (Exception e) {
e.printStackTrace();
} 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;
}
private Future<Entity[]> getEntitiesInChunkSync(CachedChunk cachedChunk) {
CompletableFuture<Entity[]> future = new CompletableFuture<>();
Bukkit.getScheduler().runTask(this.plugin, () -> future.complete(cachedChunk.getEntities()));
return future;
}
}
public boolean isWorldDisabled(World world) {
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
|| entity instanceof ArmorStand
// Make sure the entity is not in love.
|| entity.hasMetadata("inLove")
// Or in breeding cooldown.
|| entity.hasMetadata("breedCooldown"))
return false;
// Make sure the entity is not in love or in the breeding queue.
|| breedingTask.isInQueue(entity.getUniqueId()))
return true;
if (!configurationSection.getBoolean("Mobs." + entity.getType().name() + ".Enabled")) {
return false;
}
if (!configurationSection.getBoolean("Mobs." + entity.getType().name() + ".Enabled"))
return true;
// Allow spawn if stackreasons are set and match, or if from a spawner
// 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;
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, List<LivingEntity> entities) {
// Get the stack from the entity. It should be noted that this value will
// be null if our entity is not a stack.
EntityStack baseStack = plugin.getEntityStackManager().getStackedEntity(baseEntity);
// Get the maximum stack size for this entity.
int maxEntityStackSize = 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.
if (!entities.contains(friendlyEntity))
continue;
// 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 +312,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)
@ -616,6 +558,64 @@ public class StackingTask extends TimerTask {
entityList.removeIf(entity -> ((Phantom) entity).getSize() != phantom.getSize());
break;
}
case AXOLOTL_VARIANT: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_17)
|| !(initialEntity instanceof Axolotl)) break;
Axolotl axolotl = (Axolotl) initialEntity;
entityList.removeIf(entity -> ((Axolotl) entity).getVariant() != axolotl.getVariant());
break;
}
case GOAT_HAS_HORNS: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_17)
|| !(initialEntity instanceof Goat)) break;
Goat goat = (Goat) initialEntity;
boolean hasLeftHorn = goat.hasLeftHorn();
boolean hasRightHorn = goat.hasRightHorn();
entityList.removeIf(entity -> {
Goat otherGoat = (Goat) entity;
return otherGoat.hasLeftHorn() != hasLeftHorn || otherGoat.hasRightHorn() != hasRightHorn;
});
break;
}
case FROG_VARIANT: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_19)
|| !(initialEntity instanceof Frog)) break;
Frog frog = (Frog) initialEntity;
entityList.removeIf(entity -> ((Frog) entity).getVariant() != frog.getVariant());
break;
}
case TADPOLE_AGE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_19)
|| !(initialEntity instanceof Tadpole)) break;
Tadpole tadpole = (Tadpole) initialEntity;
entityList.removeIf(entity -> ((Tadpole) entity).getAge() != tadpole.getAge());
break;
}
case WARDEN_ANGER_LEVEL: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_19)
|| !(initialEntity instanceof Warden)) break;
Warden warden = (Warden) initialEntity;
entityList.removeIf(entity -> ((Warden) entity).getAnger() != warden.getAnger());
break;
}
case FOX_TYPE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14)
|| !(initialEntity instanceof Fox)) break;
Fox fox = (Fox) initialEntity;
entityList.removeIf(entity -> ((Fox) entity).getFoxType() != fox.getFoxType());
break;
}
case HOGLIN_IMMUNE: {
if (!ServerVersion.isServerVersionAtLeast(ServerVersion.V1_16)
|| !(initialEntity instanceof Hoglin)) break;
Hoglin hoglin = (Hoglin) initialEntity;
if (hoglin.isImmuneToZombification()) {
entityList.removeIf(entity -> !((Hoglin) entity).isImmuneToZombification());
} else {
entityList.removeIf(entity -> ((Hoglin) entity).isImmuneToZombification());
}
break;
}
}
}
@ -641,16 +641,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) {

View File

@ -7,7 +7,7 @@
<groupId>com.craftaro</groupId>
<artifactId>UltimateStacker-Parent</artifactId>
<packaging>pom</packaging>
<version>3.0.1</version>
<version>3.1.2</version>
<modules>
<module>UltimateStacker-API</module>
@ -19,7 +19,7 @@
<url>https://craftaro.com/marketplace/product/16</url>
<properties>
<craftaro.coreVersion>3.0.0-SNAPSHOT</craftaro.coreVersion>
<craftaro.coreVersion>3.0.1-SNAPSHOT</craftaro.coreVersion>
<maven.compiler.release>8</maven.compiler.release>
<maven.compiler.target>1.8</maven.compiler.target>