--- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
@@ -1,10 +1,12 @@
 package net.minecraft.world.level.block.entity;
 
+import java.util.Iterator;
 import java.util.List;
 import java.util.stream.IntStream;
 import javax.annotation.Nullable;
 import net.minecraft.core.BlockPos;
 import net.minecraft.core.Direction;
+import net.minecraft.core.Holder;
 import net.minecraft.core.HolderLookup;
 import net.minecraft.core.NonNullList;
 import net.minecraft.nbt.CompoundTag;
@@ -31,8 +33,13 @@
 import net.minecraft.world.level.material.PushReaction;
 import net.minecraft.world.phys.AABB;
 import net.minecraft.world.phys.Vec3;
+// CraftBukkit start
+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
+import org.bukkit.entity.HumanEntity;
+// CraftBukkit end
 
 public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity implements WorldlyContainer {
+
     public static final int COLUMNS = 9;
     public static final int ROWS = 3;
     public static final int CONTAINER_SIZE = 27;
@@ -41,21 +48,56 @@
     public static final float MAX_LID_HEIGHT = 0.5F;
     public static final float MAX_LID_ROTATION = 270.0F;
     private static final int[] SLOTS = IntStream.range(0, 27).toArray();
-    private NonNullList<ItemStack> itemStacks = NonNullList.withSize(27, ItemStack.EMPTY);
+    private NonNullList<ItemStack> itemStacks;
     public int openCount;
-    private ShulkerBoxBlockEntity.AnimationStatus animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSED;
+    private ShulkerBoxBlockEntity.AnimationStatus animationStatus;
     private float progress;
     private float progressOld;
     @Nullable
     private final DyeColor 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) {
+        this.transaction.add(who);
+    }
+
+    public void onClose(CraftHumanEntity who) {
+        this.transaction.remove(who);
+    }
+
+    public List<HumanEntity> getViewers() {
+        return this.transaction;
+    }
+
+    @Override
+    public int getMaxStackSize() {
+        return this.maxStack;
+    }
+
+    public void setMaxStackSize(int size) {
+        this.maxStack = size;
+    }
+    // CraftBukkit end
+
     public ShulkerBoxBlockEntity(@Nullable DyeColor color, BlockPos pos, BlockState state) {
         super(BlockEntityType.SHULKER_BOX, pos, state);
+        this.itemStacks = NonNullList.withSize(27, ItemStack.EMPTY);
+        this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSED;
         this.color = color;
     }
 
     public ShulkerBoxBlockEntity(BlockPos pos, BlockState state) {
         super(BlockEntityType.SHULKER_BOX, pos, state);
+        this.itemStacks = NonNullList.withSize(27, ItemStack.EMPTY);
+        this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSED;
         this.color = ShulkerBoxBlock.getColorFromBlock(state.getBlock());
     }
 
@@ -65,39 +107,40 @@
 
     private void updateAnimation(Level world, BlockPos pos, BlockState state) {
         this.progressOld = this.progress;
-        switch (this.animationStatus) {
-            case CLOSED:
+        switch (this.animationStatus.ordinal()) {
+            case 0:
                 this.progress = 0.0F;
                 break;
-            case OPENING:
+            case 1:
                 this.progress += 0.1F;
                 if (this.progressOld == 0.0F) {
-                    doNeighborUpdates(world, pos, state);
+                    ShulkerBoxBlockEntity.doNeighborUpdates(world, pos, state);
                 }
 
                 if (this.progress >= 1.0F) {
                     this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.OPENED;
                     this.progress = 1.0F;
-                    doNeighborUpdates(world, pos, state);
+                    ShulkerBoxBlockEntity.doNeighborUpdates(world, pos, state);
                 }
 
                 this.moveCollidedEntities(world, pos, state);
                 break;
-            case OPENED:
+            case 2:
                 this.progress = 1.0F;
                 break;
-            case CLOSING:
+            case 3:
                 this.progress -= 0.1F;
                 if (this.progressOld == 1.0F) {
-                    doNeighborUpdates(world, pos, state);
+                    ShulkerBoxBlockEntity.doNeighborUpdates(world, pos, state);
                 }
 
                 if (this.progress <= 0.0F) {
                     this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSED;
                     this.progress = 0.0F;
-                    doNeighborUpdates(world, pos, state);
+                    ShulkerBoxBlockEntity.doNeighborUpdates(world, pos, state);
                 }
         }
+
     }
 
     public ShulkerBoxBlockEntity.AnimationStatus getAnimationStatus() {
@@ -105,27 +148,26 @@
     }
 
     public AABB getBoundingBox(BlockState state) {
-        return Shulker.getProgressAabb(1.0F, state.getValue(ShulkerBoxBlock.FACING), 0.5F * this.getProgress(1.0F));
+        return Shulker.getProgressAabb(1.0F, (Direction) state.getValue(ShulkerBoxBlock.FACING), 0.5F * this.getProgress(1.0F));
     }
 
     private void moveCollidedEntities(Level world, BlockPos pos, BlockState state) {
         if (state.getBlock() instanceof ShulkerBoxBlock) {
-            Direction direction = state.getValue(ShulkerBoxBlock.FACING);
-            AABB aABB = Shulker.getProgressDeltaAabb(1.0F, direction, this.progressOld, this.progress).move(pos);
-            List<Entity> list = world.getEntities(null, aABB);
+            Direction enumdirection = (Direction) state.getValue(ShulkerBoxBlock.FACING);
+            AABB axisalignedbb = Shulker.getProgressDeltaAabb(1.0F, enumdirection, this.progressOld, this.progress).move(pos);
+            List<Entity> list = world.getEntities((Entity) null, axisalignedbb);
+
             if (!list.isEmpty()) {
-                for (Entity entity : list) {
+                Iterator iterator = list.iterator();
+
+                while (iterator.hasNext()) {
+                    Entity entity = (Entity) iterator.next();
+
                     if (entity.getPistonPushReaction() != PushReaction.IGNORE) {
-                        entity.move(
-                            MoverType.SHULKER_BOX,
-                            new Vec3(
-                                (aABB.getXsize() + 0.01) * (double)direction.getStepX(),
-                                (aABB.getYsize() + 0.01) * (double)direction.getStepY(),
-                                (aABB.getZsize() + 0.01) * (double)direction.getStepZ()
-                            )
-                        );
+                        entity.move(MoverType.SHULKER_BOX, new Vec3((axisalignedbb.getXsize() + 0.01D) * (double) enumdirection.getStepX(), (axisalignedbb.getYsize() + 0.01D) * (double) enumdirection.getStepY(), (axisalignedbb.getZsize() + 0.01D) * (double) enumdirection.getStepZ()));
                     }
                 }
+
             }
         }
     }
@@ -165,27 +207,29 @@
                 this.openCount = 0;
             }
 
-            this.openCount++;
+            ++this.openCount;
+            if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call.
             this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount);
             if (this.openCount == 1) {
-                this.level.gameEvent(player, GameEvent.CONTAINER_OPEN, this.worldPosition);
-                this.level
-                    .playSound(null, this.worldPosition, SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F);
+                this.level.gameEvent((Entity) player, (Holder) GameEvent.CONTAINER_OPEN, this.worldPosition);
+                this.level.playSound((Player) null, this.worldPosition, SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F);
             }
         }
+
     }
 
     @Override
     public void stopOpen(Player player) {
         if (!this.remove && !player.isSpectator()) {
-            this.openCount--;
+            --this.openCount;
+            if (this.opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call.
             this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount);
             if (this.openCount <= 0) {
-                this.level.gameEvent(player, GameEvent.CONTAINER_CLOSE, this.worldPosition);
-                this.level
-                    .playSound(null, this.worldPosition, SoundEvents.SHULKER_BOX_CLOSE, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F);
+                this.level.gameEvent((Entity) player, (Holder) GameEvent.CONTAINER_CLOSE, this.worldPosition);
+                this.level.playSound((Player) null, this.worldPosition, SoundEvents.SHULKER_BOX_CLOSE, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F);
             }
         }
+
     }
 
     @Override
@@ -205,6 +249,7 @@
         if (!this.trySaveLootTable(nbt)) {
             ContainerHelper.saveAllItems(nbt, this.itemStacks, false, registryLookup);
         }
+
     }
 
     public void loadFromTag(CompoundTag nbt, HolderLookup.Provider registries) {
@@ -212,6 +257,7 @@
         if (!this.tryLoadLootTable(nbt) && nbt.contains("Items", 9)) {
             ContainerHelper.loadAllItems(nbt, this.itemStacks, registries);
         }
+
     }
 
     @Override
@@ -226,7 +272,7 @@
 
     @Override
     public int[] getSlotsForFace(Direction side) {
-        return SLOTS;
+        return ShulkerBoxBlockEntity.SLOTS;
     }
 
     @Override
@@ -258,9 +304,9 @@
     }
 
     public static enum AnimationStatus {
-        CLOSED,
-        OPENING,
-        OPENED,
-        CLOSING;
+
+        CLOSED, OPENING, OPENED, CLOSING;
+
+        private AnimationStatus() {}
     }
 }
