--- a/net/minecraft/world/level/block/entity/TileEntityShulkerBox.java
+++ b/net/minecraft/world/level/block/entity/TileEntityShulkerBox.java
@@ -32,6 +32,11 @@
 import net.minecraft.world.phys.AxisAlignedBB;
 import net.minecraft.world.phys.Vec3D;
 
+// CraftBukkit start
+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
+import org.bukkit.entity.HumanEntity;
+// CraftBukkit end
+
 public class TileEntityShulkerBox extends TileEntityLootable implements IWorldInventory {
 
     public static final int COLUMNS = 9;
@@ -51,6 +56,37 @@
     @Nullable
     private final EnumColor color;
 
+    // CraftBukkit start - add fields and methods
+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
+    private int maxStack = MAX_STACK;
+    public boolean opened;
+
+    public List<ItemStack> getContents() {
+        return this.itemStacks;
+    }
+
+    public void onOpen(CraftHumanEntity who) {
+        transaction.add(who);
+    }
+
+    public void onClose(CraftHumanEntity who) {
+        transaction.remove(who);
+    }
+
+    public List<HumanEntity> getViewers() {
+        return transaction;
+    }
+
+    @Override
+    public int getMaxStackSize() {
+        return maxStack;
+    }
+
+    public void setMaxStackSize(int size) {
+        maxStack = size;
+    }
+    // CraftBukkit end
+
     public TileEntityShulkerBox(@Nullable EnumColor enumcolor, BlockPosition blockposition, IBlockData iblockdata) {
         super(TileEntityTypes.SHULKER_BOX, blockposition, iblockdata);
         this.itemStacks = NonNullList.a(27, ItemStack.EMPTY);
@@ -163,6 +199,7 @@
             }
 
             ++this.openCount;
+            if (opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call.
             this.level.playBlockAction(this.worldPosition, this.getBlock().getBlock(), 1, this.openCount);
             if (this.openCount == 1) {
                 this.level.a((Entity) entityhuman, GameEvent.CONTAINER_OPEN, this.worldPosition);
@@ -176,6 +213,7 @@
     public void closeContainer(EntityHuman entityhuman) {
         if (!entityhuman.isSpectator()) {
             --this.openCount;
+            if (opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call.
             this.level.playBlockAction(this.worldPosition, this.getBlock().getBlock(), 1, this.openCount);
             if (this.openCount <= 0) {
                 this.level.a((Entity) entityhuman, GameEvent.CONTAINER_CLOSE, this.worldPosition);
