Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -291,4 +291,6 @@ default void setPaperFromMobSpawner(Entity entity) {

EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, @NotNull List<ItemStack> drops, int droppedExp);

List<ItemStack> getBoxContents(Item item);

}
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,11 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what,
return new AsyncEntityDeathEventImpl(what, drops, droppedExp);
}

@Override
public List<ItemStack> getBoxContents(Item item) {
return new ArrayList<>();
}

private SpawnReason toBukkitSpawnReason(EnumMobSpawn mobSpawnType) {
return switch (mobSpawnType) {
case SPAWN_EGG -> SpawnReason.SPAWNER_EGG;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@
import net.minecraft.world.entity.ai.goal.WrappedGoal;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.animal.Rabbit;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.monster.Spider;
import net.minecraft.world.entity.monster.Strider;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.entity.raid.Raider;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.BaseSpawner;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
Expand Down Expand Up @@ -418,6 +418,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what,
return new AsyncEntityDeathEventImpl(what, drops, droppedExp);
}

@Override
public List<ItemStack> getBoxContents(Item item) {
net.minecraft.world.item.ItemStack itemStack = ((ItemEntity) item).getItem();
CompoundTag contents = itemStack.getTag();

if (contents != null) {
return contents.getCompound("BlockEntityTag").getList("Items", 10).stream()
.map(CompoundTag.class::cast)
.map(net.minecraft.world.item.ItemStack::of)
.map(CraftItemStack::asBukkitCopy)
.toList();
}

return new ArrayList<>();
}

private SpawnReason toBukkitSpawnReason(MobSpawnType mobSpawnType) {
return switch (mobSpawnType) {
case SPAWN_EGG -> SpawnReason.SPAWNER_EGG;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import net.minecraft.world.entity.monster.Strider;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.entity.raid.Raider;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.BaseSpawner;
import net.minecraft.world.level.ClipContext;
Expand Down Expand Up @@ -422,6 +423,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what,
return new AsyncEntityDeathEventImpl(what, drops, droppedExp);
}

@Override
public List<ItemStack> getBoxContents(Item item) {
ItemStack itemStack = item.getItemStack();
CompoundTag contents = BlockItem.getBlockEntityData(CraftItemStack.asNMSCopy(itemStack));

if (contents != null && contents.contains("Items", 9)) {
return contents.getList("Items", 10).stream()
.map(CompoundTag.class::cast)
.map(net.minecraft.world.item.ItemStack::of)
.map(CraftItemStack::asBukkitCopy)
.toList();
}

return new ArrayList<>();
}

private SpawnReason toBukkitSpawnReason(MobSpawnType mobSpawnType) {
return switch (mobSpawnType) {
case SPAWN_EGG -> SpawnReason.SPAWNER_EGG;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import net.minecraft.world.entity.monster.Strider;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.entity.raid.Raider;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.BaseSpawner;
import net.minecraft.world.level.ClipContext;
Expand Down Expand Up @@ -499,6 +500,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what,
return new AsyncEntityDeathEventImpl(what, drops, droppedExp);
}

@Override
public List<ItemStack> getBoxContents(Item item) {
ItemStack itemStack = item.getItemStack();
CompoundTag contents = BlockItem.getBlockEntityData(CraftItemStack.asNMSCopy(itemStack));

if (contents != null && contents.contains("Items", 9)) {
return contents.getList("Items", 10).stream()
.map(CompoundTag.class::cast)
.map(net.minecraft.world.item.ItemStack::of)
.map(CraftItemStack::asBukkitCopy)
.toList();
}

return new ArrayList<>();
}

public void addEntityToWorld(ServerLevel world, Entity entity) throws ReflectiveOperationException {
if (field_ServerLevel_entityManager != null) {
PersistentEntitySectionManager<Entity> entityManager = (PersistentEntitySectionManager<Entity>) field_ServerLevel_entityManager.get(world);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import net.minecraft.world.entity.monster.Strider;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.entity.raid.Raider;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.BaseSpawner;
import net.minecraft.world.level.ClipContext;
Expand Down Expand Up @@ -499,6 +500,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what,
return new AsyncEntityDeathEventImpl(what, drops, droppedExp);
}

@Override
public List<ItemStack> getBoxContents(Item item) {
ItemStack itemStack = item.getItemStack();
CompoundTag contents = BlockItem.getBlockEntityData(CraftItemStack.asNMSCopy(itemStack));

if (contents != null && contents.contains("Items", 9)) {
return contents.getList("Items", 10).stream()
.map(CompoundTag.class::cast)
.map(net.minecraft.world.item.ItemStack::of)
.map(CraftItemStack::asBukkitCopy)
.toList();
}

return new ArrayList<>();
}

public void addEntityToWorld(ServerLevel world, Entity entity) throws ReflectiveOperationException {
if (field_ServerLevel_entityManager != null) {
PersistentEntitySectionManager<Entity> entityManager = (PersistentEntitySectionManager<Entity>) field_ServerLevel_entityManager.get(world);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import net.minecraft.world.entity.monster.Strider;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.entity.raid.Raider;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.BaseSpawner;
import net.minecraft.world.level.ClipContext;
Expand Down Expand Up @@ -496,6 +497,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what,
return new AsyncEntityDeathEventImpl(what, drops, droppedExp);
}

@Override
public List<ItemStack> getBoxContents(Item item) {
ItemStack itemStack = item.getItemStack();
CompoundTag contents = BlockItem.getBlockEntityData(CraftItemStack.asNMSCopy(itemStack));

if (contents != null && contents.contains("Items", 9)) {
return contents.getList("Items", 10).stream()
.map(CompoundTag.class::cast)
.map(net.minecraft.world.item.ItemStack::of)
.map(CraftItemStack::asBukkitCopy)
.toList();
}

return new ArrayList<>();
}

public void addEntityToWorld(ServerLevel world, Entity entity) throws ReflectiveOperationException {
if (field_ServerLevel_entityManager != null) {
PersistentEntitySectionManager<Entity> entityManager = (PersistentEntitySectionManager<Entity>) field_ServerLevel_entityManager.get(world);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import net.minecraft.world.entity.monster.Strider;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.entity.raid.Raider;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.BaseSpawner;
import net.minecraft.world.level.ClipContext;
Expand Down Expand Up @@ -496,6 +497,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what,
return new AsyncEntityDeathEventImpl(what, drops, droppedExp);
}

@Override
public List<ItemStack> getBoxContents(Item item) {
ItemStack itemStack = item.getItemStack();
CompoundTag contents = BlockItem.getBlockEntityData(CraftItemStack.asNMSCopy(itemStack));

if (contents != null && contents.contains("Items", 9)) {
return contents.getList("Items", 10).stream()
.map(CompoundTag.class::cast)
.map(net.minecraft.world.item.ItemStack::of)
.map(CraftItemStack::asBukkitCopy)
.toList();
}

return new ArrayList<>();
}

public void addEntityToWorld(ServerLevel world, Entity entity) throws ReflectiveOperationException {
if (field_ServerLevel_entityManager != null) {
PersistentEntitySectionManager<Entity> entityManager = (PersistentEntitySectionManager<Entity>) field_ServerLevel_entityManager.get(world);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import net.minecraft.world.entity.monster.Strider;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.entity.raid.Raider;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.BaseSpawner;
import net.minecraft.world.level.ClipContext;
Expand Down Expand Up @@ -498,6 +499,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what,
return new AsyncEntityDeathEventImpl(what, drops, droppedExp);
}

@Override
public List<ItemStack> getBoxContents(Item item) {
ItemStack itemStack = item.getItemStack();
CompoundTag contents = BlockItem.getBlockEntityData(CraftItemStack.asNMSCopy(itemStack));

if (contents != null && contents.contains("Items", 9)) {
return contents.getList("Items", 10).stream()
.map(CompoundTag.class::cast)
.map(net.minecraft.world.item.ItemStack::of)
.map(CraftItemStack::asBukkitCopy)
.toList();
}

return new ArrayList<>();
}

public void addEntityToWorld(ServerLevel world, Entity entity) throws ReflectiveOperationException {
if (field_ServerLevel_entityManager != null) {
PersistentEntitySectionManager<Entity> entityManager = (PersistentEntitySectionManager<Entity>) field_ServerLevel_entityManager.get(world);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@
import net.minecraft.world.entity.ai.goal.WrappedGoal;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.animal.Rabbit;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.monster.Spider;
import net.minecraft.world.entity.monster.Strider;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.entity.raid.Raider;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.component.ItemContainerContents;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.BaseSpawner;
import net.minecraft.world.level.ClipContext;
Expand Down Expand Up @@ -499,6 +501,21 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what,
return new AsyncEntityDeathEventImpl(what, drops, droppedExp);
}

@Override
public List<ItemStack> getBoxContents(Item item) {
net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(item.getItemStack());
ItemContainerContents contents = itemStack.set(DataComponents.CONTAINER, ItemContainerContents.EMPTY);

if (contents != null) {
return contents.stream()
.filter(x -> !x.isEmpty())
.map(CraftItemStack::asBukkitCopy)
.toList();
}

return new ArrayList<>();
}

public void addEntityToWorld(ServerLevel world, Entity entity) throws ReflectiveOperationException {
if (field_ServerLevel_entityManager != null) {
PersistentEntitySectionManager<Entity> entityManager = (PersistentEntitySectionManager<Entity>) field_ServerLevel_entityManager.get(world);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import dev.rosewood.guiframework.framework.util.GuiUtil;
import dev.rosewood.rosegarden.RosePlugin;
import dev.rosewood.rosegarden.utils.NMSUtil;
import dev.rosewood.rosestacker.RoseStacker;
import dev.rosewood.rosestacker.event.AsyncEntityDeathEvent;
import dev.rosewood.rosestacker.manager.ConfigurationManager.Setting;
import dev.rosewood.rosestacker.manager.EntityCacheManager;
import dev.rosewood.rosestacker.manager.StackManager;
import dev.rosewood.rosestacker.manager.StackSettingManager;
import dev.rosewood.rosestacker.nms.NMSAdapter;
import dev.rosewood.rosestacker.nms.NMSHandler;
import dev.rosewood.rosestacker.nms.storage.EntityDataEntry;
import dev.rosewood.rosestacker.nms.storage.StackedEntityDataStorageType;
import dev.rosewood.rosestacker.stack.StackedEntity;
Expand All @@ -31,6 +34,7 @@
import org.bukkit.Statistic;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.block.ShulkerBox;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Chicken;
import org.bukkit.entity.Creeper;
Expand Down Expand Up @@ -72,6 +76,7 @@
import org.bukkit.event.entity.SpawnerSpawnEvent;
import org.bukkit.event.player.PlayerShearEntityEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BlockStateMeta;
import org.bukkit.util.Vector;

public class EntityListener implements Listener {
Expand Down Expand Up @@ -240,6 +245,11 @@ public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {

@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onEntityDamage(EntityDamageEvent event) {
if (NMSUtil.getVersionNumber() >= 17 && event.getEntity() instanceof Item item && item.getItemStack().getType().toString().contains("SHULKER_BOX") && unpackShulkerBox(item, event.getFinalDamage())) {
event.setCancelled(true);
return;
}

if (!(event.getEntity() instanceof LivingEntity entity) || event.getEntity().getType() == EntityType.ARMOR_STAND || event.getEntity().getType() == EntityType.PLAYER)
return;

Expand Down Expand Up @@ -277,6 +287,65 @@ public void onEntityDamage(EntityDamageEvent event) {
}
}

private boolean unpackShulkerBox(Item item, double damage) {
StackedItem stackedItem = this.stackManager.getStackedItem(item);
if (stackedItem == null)
return false;

final int amount = stackedItem.getStackSize();

if (amount > 1 && damage >= item.getHealth()) {
final List<ItemStack> contents = getContents(item);
final List<ItemStack> totalContents = new ArrayList<>();
final Location location = item.getLocation();

item.remove();

for (int i = amount; i > 0; i--) {
totalContents.addAll(contents);
}

final int maxStackSize = Setting.ITEM_MAX_STACK_SIZE.getInt();

for (;;) {
if (totalContents.size() > maxStackSize) {
List<ItemStack> stack = new ArrayList<>(totalContents.subList(0, maxStackSize));
totalContents.subList(0, maxStackSize).clear();
this.stackManager.preStackItems(stack, location);
} else {
this.stackManager.preStackItems(totalContents, location);
break;
}
}

return true;
}

return false;
}

private List<ItemStack> getContents(Item item) {
List<ItemStack> contents = new ArrayList<>();

if (Setting.ITEM_UNPACK_BOX_LIKE_VANILLA.getBoolean()) {
NMSHandler nmsHandler = NMSAdapter.getHandler();
contents = nmsHandler.getBoxContents(item);
} else {
ItemStack itemStack = item.getItemStack();
if (!(itemStack.getItemMeta() instanceof BlockStateMeta meta)) return contents;

if (!(meta.getBlockState() instanceof ShulkerBox box)) return contents;

for (ItemStack content : box.getInventory().getContents()) {
if (content == null || content.getType().isAir()) continue;

contents.add(content);
}
}

return contents;
}

@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onEntityCombust(EntityCombustEvent event) {
Entity entity = event.getEntity();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public enum Setting implements RoseSetting {
ITEM_DISPLAY_DESPAWN_TIMER_PLACEHOLDER("global-item-settings.display-despawn-timer-placeholder", false, "Should the %timer% placeholder be available in item display tags?", "You will need to add the %timer% placeholder to the item display tag in your locale file manually", "Placeholder updates will occur at the same frequency as item-stack-frequency"),
ITEM_RESET_DESPAWN_TIMER_ON_MERGE("global-item-settings.reset-despawn-timer-on-merge", true, "Should the item despawn timer be reset when an item is merged into it?"),
ITEM_MERGE_INTO_NEWEST("global-item-settings.merge-into-newest", false, "Should items be merged into the newest stack?"),
ITEM_UNPACK_BOX_LIKE_VANILLA("global-item-settings.unpack-stacked-shulker-box-like-vanilla", false, "Use vanilla method to get items stored in box, which may allow unpacking an illegal amount of items from the box."),

GLOBAL_BLOCK_SETTINGS("global-block-settings", null, "Global block settings", "Changed values in block_settings.yml will override these values"),
BLOCK_STACKING_ENABLED("global-block-settings.stacking-enabled", true, "Should block stacking be enabled at all?"),
Expand Down