diff --git a/NMS/Wrapper/src/main/java/dev/rosewood/rosestacker/nms/NMSHandler.java b/NMS/Wrapper/src/main/java/dev/rosewood/rosestacker/nms/NMSHandler.java index a27cb034..99a78c2e 100644 --- a/NMS/Wrapper/src/main/java/dev/rosewood/rosestacker/nms/NMSHandler.java +++ b/NMS/Wrapper/src/main/java/dev/rosewood/rosestacker/nms/NMSHandler.java @@ -291,4 +291,6 @@ default void setPaperFromMobSpawner(Entity entity) { EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, @NotNull List drops, int droppedExp); + List getBoxContents(Item item); + } diff --git a/NMS/v1_16_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_16_R3/NMSHandlerImpl.java b/NMS/v1_16_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_16_R3/NMSHandlerImpl.java index 75fa2daa..6b27b6a1 100644 --- a/NMS/v1_16_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_16_R3/NMSHandlerImpl.java +++ b/NMS/v1_16_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_16_R3/NMSHandlerImpl.java @@ -437,6 +437,11 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, return new AsyncEntityDeathEventImpl(what, drops, droppedExp); } + @Override + public List getBoxContents(Item item) { + return new ArrayList<>(); + } + private SpawnReason toBukkitSpawnReason(EnumMobSpawn mobSpawnType) { return switch (mobSpawnType) { case SPAWN_EGG -> SpawnReason.SPAWNER_EGG; diff --git a/NMS/v1_17_R1/src/main/java/dev/rosewood/rosestacker/nms/v1_17_R1/NMSHandlerImpl.java b/NMS/v1_17_R1/src/main/java/dev/rosewood/rosestacker/nms/v1_17_R1/NMSHandlerImpl.java index 6ab6ce69..ed9b8aae 100644 --- a/NMS/v1_17_R1/src/main/java/dev/rosewood/rosestacker/nms/v1_17_R1/NMSHandlerImpl.java +++ b/NMS/v1_17_R1/src/main/java/dev/rosewood/rosestacker/nms/v1_17_R1/NMSHandlerImpl.java @@ -50,6 +50,7 @@ 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; @@ -57,7 +58,6 @@ 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; @@ -418,6 +418,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, return new AsyncEntityDeathEventImpl(what, drops, droppedExp); } + @Override + public List 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; diff --git a/NMS/v1_18_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_18_R2/NMSHandlerImpl.java b/NMS/v1_18_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_18_R2/NMSHandlerImpl.java index 1811d69b..47286887 100644 --- a/NMS/v1_18_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_18_R2/NMSHandlerImpl.java +++ b/NMS/v1_18_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_18_R2/NMSHandlerImpl.java @@ -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; @@ -422,6 +423,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, return new AsyncEntityDeathEventImpl(what, drops, droppedExp); } + @Override + public List 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; diff --git a/NMS/v1_19_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_19_R2/NMSHandlerImpl.java b/NMS/v1_19_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_19_R2/NMSHandlerImpl.java index 7f35471a..46e04c58 100644 --- a/NMS/v1_19_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_19_R2/NMSHandlerImpl.java +++ b/NMS/v1_19_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_19_R2/NMSHandlerImpl.java @@ -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; @@ -499,6 +500,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, return new AsyncEntityDeathEventImpl(what, drops, droppedExp); } + @Override + public List 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 entityManager = (PersistentEntitySectionManager) field_ServerLevel_entityManager.get(world); diff --git a/NMS/v1_19_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_19_R3/NMSHandlerImpl.java b/NMS/v1_19_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_19_R3/NMSHandlerImpl.java index 139803cb..79641c6d 100644 --- a/NMS/v1_19_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_19_R3/NMSHandlerImpl.java +++ b/NMS/v1_19_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_19_R3/NMSHandlerImpl.java @@ -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; @@ -499,6 +500,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, return new AsyncEntityDeathEventImpl(what, drops, droppedExp); } + @Override + public List 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 entityManager = (PersistentEntitySectionManager) field_ServerLevel_entityManager.get(world); diff --git a/NMS/v1_20_R1/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R1/NMSHandlerImpl.java b/NMS/v1_20_R1/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R1/NMSHandlerImpl.java index 4cd103de..a448fb65 100644 --- a/NMS/v1_20_R1/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R1/NMSHandlerImpl.java +++ b/NMS/v1_20_R1/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R1/NMSHandlerImpl.java @@ -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; @@ -496,6 +497,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, return new AsyncEntityDeathEventImpl(what, drops, droppedExp); } + @Override + public List 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 entityManager = (PersistentEntitySectionManager) field_ServerLevel_entityManager.get(world); diff --git a/NMS/v1_20_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R2/NMSHandlerImpl.java b/NMS/v1_20_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R2/NMSHandlerImpl.java index ebabeea8..88c660af 100644 --- a/NMS/v1_20_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R2/NMSHandlerImpl.java +++ b/NMS/v1_20_R2/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R2/NMSHandlerImpl.java @@ -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; @@ -496,6 +497,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, return new AsyncEntityDeathEventImpl(what, drops, droppedExp); } + @Override + public List 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 entityManager = (PersistentEntitySectionManager) field_ServerLevel_entityManager.get(world); diff --git a/NMS/v1_20_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R3/NMSHandlerImpl.java b/NMS/v1_20_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R3/NMSHandlerImpl.java index 44282aca..ef309232 100644 --- a/NMS/v1_20_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R3/NMSHandlerImpl.java +++ b/NMS/v1_20_R3/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R3/NMSHandlerImpl.java @@ -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; @@ -498,6 +499,22 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, return new AsyncEntityDeathEventImpl(what, drops, droppedExp); } + @Override + public List 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 entityManager = (PersistentEntitySectionManager) field_ServerLevel_entityManager.get(world); diff --git a/NMS/v1_20_R4/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R4/NMSHandlerImpl.java b/NMS/v1_20_R4/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R4/NMSHandlerImpl.java index 28e89739..46eda66b 100644 --- a/NMS/v1_20_R4/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R4/NMSHandlerImpl.java +++ b/NMS/v1_20_R4/src/main/java/dev/rosewood/rosestacker/nms/v1_20_R4/NMSHandlerImpl.java @@ -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; @@ -499,6 +501,21 @@ public EntityDeathEvent createAsyncEntityDeathEvent(@NotNull LivingEntity what, return new AsyncEntityDeathEventImpl(what, drops, droppedExp); } + @Override + public List 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 entityManager = (PersistentEntitySectionManager) field_ServerLevel_entityManager.get(world); diff --git a/Plugin/src/main/java/dev/rosewood/rosestacker/listener/EntityListener.java b/Plugin/src/main/java/dev/rosewood/rosestacker/listener/EntityListener.java index 0b1771e3..203659ee 100644 --- a/Plugin/src/main/java/dev/rosewood/rosestacker/listener/EntityListener.java +++ b/Plugin/src/main/java/dev/rosewood/rosestacker/listener/EntityListener.java @@ -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; @@ -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; @@ -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 { @@ -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; @@ -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 contents = getContents(item); + final List 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 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 getContents(Item item) { + List 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(); diff --git a/Plugin/src/main/java/dev/rosewood/rosestacker/manager/ConfigurationManager.java b/Plugin/src/main/java/dev/rosewood/rosestacker/manager/ConfigurationManager.java index 74022f8a..b51b922e 100644 --- a/Plugin/src/main/java/dev/rosewood/rosestacker/manager/ConfigurationManager.java +++ b/Plugin/src/main/java/dev/rosewood/rosestacker/manager/ConfigurationManager.java @@ -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?"),