Uploaded image for project: 'Minecraft: Java Edition'
  1. Minecraft: Java Edition
  2. MC-73302

Block collision box issues (mobs glitch through blocks and more)

    Details

    • Type: Bug
    • Status: Resolved
    • Resolution: Fixed
    • Affects Version/s: Minecraft 1.8, Minecraft 1.8.1-pre2, Minecraft 1.8.1-pre3, Minecraft 1.8.1, Minecraft 1.8.3, Minecraft 1.8.4, Minecraft 1.8.8, Minecraft 15w31a, Minecraft 15w31b, Minecraft 15w31c, Minecraft 15w33b, Minecraft 15w33c, Minecraft 15w35e, Minecraft 15w36b, Minecraft 15w36c, Minecraft 15w36d, Minecraft 15w37a
    • Fix Version/s: Minecraft 15w38a
    • Labels:
      None
    • Confirmation Status:
      Unconfirmed

      Description

      This post is about not correctly updated collision boxes of certain blocks.
      It describes more than one issue but they are caused by the same problematic part of code and should be seen as one in order to decide on a good solution for them.
      When it comes to explanation close to the code I will use MCP names. They should mostly speak for themselves.
      As Searge just recently tweeted about it: I will try to keep the posted code to a minimum and give just the needed context for my fix (It's pretty much just the added code). I obviously don't intent to make any of the (especially with MCP) decompiled code public.

      I will try to have a look on here in case there are any questions.

      1. Overview of bugs and exploits caused by this issue
      2. Description of the cause
      3. Attempt to fix it (not complete)
      4. Ways to reproduce
      5. Other posts on the bug tracker referring to it
      6. Item elevator bug reimplementation!
      7. Affected blocks
      8. TLDR

      1. Overview of bugs and exploits caused by this issue

      It causes the following wrong behaviors:

      • Entities glitching into certain blocks randomly (e.g. into slabs)
      • Pistons not pushing entities correctly (e.g. in elevators)
      • Rendering issues (e.g. normal slab renders as upside down for a split second)
      • Items/XPOrbs jumping up when on certain blocks (e.g. cauldron, stairs)

      Fixing it will probably also break the following by the community used glitches:

      • The most common wireless redstone
      • test137e29's simple item elevator (This thing: Item elevator)

      The item elevator is a default design used a lot by the technical community so it might be worth it to make it still work by design when the fix breaks it. I'll add a section about that at the very end of the post. Please consider it.

      2. Description of the cause

      I will try to explain it on the example of into blocks glitching entities.
      In Block.java we got 6 integer fields (minX, minY, minZ, maxX, maxY, maxZ).
      The following method sets these values. It is overridden by all blocks that can have changeable collision boxes (e.g. slabs).

      Block.java
      public void setBlockBoundsBasedOnState(IBlockAccess access, BlockPos pos)
      

      The usual way this is use is like that (let's imagine some method in Block.java):

      Block.java (random example, how a use could look)
      public class Block {
      	void doSomethingWithBlocks(World worldObj, BlockPos pos) {
      		setBlockBoundsBasedOnState(worldObj, pos);
      		// work with  minX, minY, minZ, maxX, maxY and/or maxZ
      		if (this.minY < 10) {
      			// do something
      		} else {
      			// do something else
      		}
      	}
      }
      

      Here we can already see the first risk this holds. It's not directly a bug but calls for creating bugs: What if you forget to call setBlockBoundsBasedOnState() before working with the values?
      This case commonly happed. For example for chest, anvils and moving blocks. (These are just from experience, I didn't look it up. So forgive me if I'm mistaken )
      See the ways to reproduce section for more detailed examples.
      This is also what's used to create wireless redstone.

      While this is not really a bug I guess it's still not what you want. Especially when it comes to the plugin API. The system works for blocks with simple AABBs. But by now Minecraft is not limited to that.

      The second issue is less visible but you might guess it when I bring up that by now in Minecraft SSP two threads run. One acting as a client and one as a local server.
      Both of these threads have access to Block.java. Both work with it.

      A common case is that the client thread uses setBlockBoundsBasedOnState() to set the block bounds for the renderer.
      An other common case is that the server thread uses it in order to calculate entity collisions.

      If both of these happen at the same time the values are changed by the other thread and therefor can cause entities to glitch into blocks or the renderer to show wrong things.

      3. Attempt to fix it (not completely)

      In the long run it should be completely gotten rid of the setBlockBoundsBasedOnState() and the 6 integer values in Block.java. This design just doesn't work well with multi threading and making all code that uses the on top described pattern synchronized is not really an option.
      This needs a lot of refactoring and therefor takes a lot of time though.
      I limited myself to explain my idea how to fix the collision issues with entities glitching into blocks.

      First up I added a few methods for convenience here and there:

      Block.java (added stuff)
      public class Block {
      	// BoundingBox for a full block
      	protected static final AxisAlignedBB AABB_FULL = new AxisAlignedBB(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
      
      	/**
      	 * Adds the bounding box (toAdd) to the list if it intersects with mask.
      	 * 
      	 * @param list	List it might get added to.
      	 * @param toAdd	BoundingBox that should get added. null won't be added.
      	 * @param mask	BoundingBox that should intersect with it
      	 * @return		true if added, false else (it's not needed atm but it's always usefull to get some feedback)
      	 */
      	protected static boolean addAABBToList(List list, AxisAlignedBB toAdd, AxisAlignedBB mask) {
      		if (toAdd != null && mask.intersectsWith(toAdd)) {
      			list.add(toAdd);
      			return true;
      		}
      		return false;
      	}
      
      AxisAlignedBB.java (added stuff)
      public class AxisAlignedBB {
      	/**
      	 * Returns a new AABB moved by offset
      	 * 
      	 * @param offset	The offset it should get moved by. 
      	 * @return		A new AABB moved by offset
      	 */
      	public AxisAlignedBB offset(BlockPos offset) {
      		return new AxisAlignedBB(this.minX + offset.getX(), this.minY + offset.getY(), this.minZ + offset.getZ(), this.maxX + offset.getX(), this.maxY + offset.getY(), this.maxZ + offset.getZ());
      	}
      }
      

      Now the following method should be overridden for all affected blocks that have a simple AABB (with that I mean that they don't need several AABBs).

      Block.java
      public AxisAlignedBB getCollisionBoundingBox(World worldIn, BlockPos pos, IBlockState state)
      

      For example that would be slabs, chests and anvils.
      It should be overridden to work independent from setBlockBoundsBasedOnState() and the 6 integer values in Block.java.

      For slabs you can nicely add the possible AABBs to the property enum.

      BlockSlab.java (added stuff)
      public abstract class BlockSlab extends Block {
      	@Override
      	public AxisAlignedBB getCollisionBoundingBox(World worldIn, BlockPos pos, IBlockState state) {
      		if(this.isDouble()) {
      			// The AABB we defined in Block.java
      			return AABB_FULL.offset(pos);
      		} else {
      			return ((BlockSlab.EnumBlockHalf)state.getValue(HALF_PROP)).getAABB().offset(pos);
      		}
      	}
      
      	public static enum EnumBlockHalf implements IStringSerializable {
      		TOP("top", new AxisAlignedBB(0.0F, 0.5F, 0.0F, 1.0F, 1.0F, 1.0F)),
      		BOTTOM("bottom", new AxisAlignedBB(0.0F, 0.0F, 0.0F, 1.0F, 0.5F, 1.0F));
      		private final String halfName;
      		private final AxisAlignedBB AABBForState;
      
      		private EnumBlockHalf(String name, AxisAlignedBB AABBForState) {
      			this.halfName = name;
      			this.AABBForState = AABBForState;
      		}
      
      		public AxisAlignedBB getAABB() {
      			return this.AABBForState;
      		}
      
      		public String toString() {
      			return this.halfName;
      		}
      
      		public String getName() {
      			return this.halfName;
      		}
      	}
      }
      

      For blocks which can need more than one AABB we want to override this method:
      Block.java

      Block.java
      public void addCollisionBoxesToList(World worldIn, BlockPos pos, IBlockState state, AxisAlignedBB mask, List list, Entity collidingEntity)
      

      It's for example called when the collision check of an entity is done to build a list of blocks which might collide with it.
      The implementation in Block.java just calls getCollisionBoundingBox() and adds this AABB to the list. That's why we can override getCollisionBoundingBox() for simple blocks.

      But I want to show the example of a fence too. For fences I added a few static fields which contain all possibly needed AABBs.

      BlockFence.java (added stuff)
      public class Block {
      	protected static final AxisAlignedBB AABB_POST = new AxisAlignedBB(0.375, 0.0, 0.375, 0.625, 1.5, 0.625);
      	protected static final AxisAlignedBB AABB_NORTH = new AxisAlignedBB(0.375, 0.0, 0.0, 0.625, 1.5, 0.625);
      	protected static final AxisAlignedBB AABB_EAST = new AxisAlignedBB(0.375, 0.0, 0.375, 1.0, 1.5, 0.625);
      	protected static final AxisAlignedBB AABB_SOUTH = new AxisAlignedBB(0.375, 0.0, 0.375, 0.625, 1.5, 1.0);
      	protected static final AxisAlignedBB AABB_WEST = new AxisAlignedBB(0.0, 0.0, 0.375, 0.625, 1.5, 0.625);
      	protected static final AxisAlignedBB AABB_NORTHSOUTH = AABB_NORTH.union(AABB_SOUTH);
      	protected static final AxisAlignedBB AABB_EASTWEST = AABB_EAST.union(AABB_WEST);
      
      	@Override
      	public void addCollisionBoxesToList(World worldIn, BlockPos pos, IBlockState state, AxisAlignedBB mask, List list, Entity collidingEntity) {
      		// func_176524_e() tells if it should connect to the other block
      		boolean connectedNorth = this.func_176524_e(worldIn, pos.offsetNorth());
      		boolean connectedSouth = this.func_176524_e(worldIn, pos.offsetSouth());
      		boolean connectedWest = this.func_176524_e(worldIn, pos.offsetWest());
      		boolean connectedEast = this.func_176524_e(worldIn, pos.offsetEast());
      
      		// Not connected at all? Add AABB_POST and return
      		if (!connectedNorth && !connectedSouth && !connectedEast && !connectedWest) {
      			Block.addAABBToList(list, AABB_POST.offset(pos), mask);
      			return;
      		}
      
      		if (connectedNorth && connectedSouth) {
      			Block.addAABBToList(list, AABB_NORTHSOUTH.offset(pos), mask);
      		} else {
      			if (connectedNorth) {
      				Block.addAABBToList(list, AABB_NORTH.offset(pos), mask);
      			} else if (connectedSouth) {
      				Block.addAABBToList(list, AABB_SOUTH.offset(pos), mask);
      			}
      		}
      	      
      		if (connectedEast && connectedWest) {
      			Block.addAABBToList(list, AABB_EASTWEST.offset(pos), mask);
      		} else {
      			if (connectedEast) {
      				Block.addAABBToList(list, AABB_EAST.offset(pos), mask);
      			} else if (connectedWest) {
      				Block.addAABBToList(list, AABB_WEST.offset(pos), mask);
      			}
      		}
      	}
      }
      

      For other blocks like stairs or moving blocks it might get more complicated but as the functionality is already in there, it just needs to be refactored a bit.

      In order to fix all issues with it (for example bouncing items would still happen in some cases) and to completely get rid of the setBlockBoundsBasedOnState() obviously more work is required.

      4. Ways to reproduce

      Video showing examples of the main issues

      Reproducing the first issue is quite easy:
      1. Place a double chest.
      2. Walk against it from the smaller side. Keep walking against it in the next steps.
      3. Look first at the chest closer to you and then at the one further away (by looking at it you update the values in Block.java).
      4. You should notice that you move forward and get set back all the time.

      Same works even better with anvils:
      1. Place two anvils in a row. One facing north-south, the other one east-west.
      2. Walk against one of them. And keep walking in the next steps.
      3. Look at one then at the other and then at the first anvil again.
      4. You should walk through it now.

      A good way to reproduce the entities glitching into blocks (threading issue) is the following:
      1. Create a new superflat world with the following string: “3;minecraft:bedrock,20*minecraft:stone_slab;1;” (You can use other affected blocks than slabs.)
      2. Place a upside down slab somewhere in the world.
      3. Place a armor stand (or an other entity) on top of it. (For fences you want to have a water stream pushing a mob against it.)
      4. Now set the render distance to a low and then to a high value again (This is to make it more likely by as it causes updates on the blocks).
      5. You should be able to see two things. First up if you look at the upside down slab it's hitbox will constantly change to a normal slab and back. Secondly the armor stand falls through the slab.

      Pistons not pushing entities:
      1. Build up whats shown in this screenshot.
      2. Stand where I stand in the screenshot and look at the sideways piston.
      3. Place a torch where I look at in the screenshot.
      4. You will find that you don't the pushed up. If you make the sideways facing piston look up you will get pushed up.

      The rendering issues rarely happen. You can force them by having a lot of the affected blocks around. But as they are a less important issue and somewhat hard to reproduce I will just leave it at that.

      Items jumping up is easy to reproduce again:
      1. Place a stair
      2. Throw an item into the cut out part.
      3. It jumps away.

      Same with cauldrons and other affected blocks. The item elevator linked earlier is also powered by this issue / a way to reproduce it.

      5. Other posts on the bug tracker referring to it

      MC-69007, MC-67127, MC-67896, MC-68578, MC-69797 and MC-57152 describe the same issue as far as I can tell.
      A part of MC-1836 describes this issue.

      It's likely that MC-2025 and/or some duplicates of it describe mobs glitching through fences. It's not unlikely that it is due to this bug.
      Also in the duplicates of MC-10 are a few posts were mobs really glitch through fences (which is likely this issue).

      There is probably more issues connected to it but I spent enough time browsing the bugtracker for now.
      If one of the bad-cop-mods feels like linking the issues to this one that would be great

      6. Item elevator bug reimplementation!

      If the partial fix I showed is used (or something similar) then the simple item elevator can be fixed by adding setBlockBoundsBasedOnState() to the method which makes items fly out of blocks in the right spot.

      Entity.java (change)
      public abstract class Entity implements ICommandSender {
      	protected boolean pushOutOfBlocks(double x, double y, double z) {
      		BlockPos var7 = new BlockPos(x, y, z);
      		double var8 = x - (double)var7.getX();
      		double var10 = y - (double)var7.getY();
      		double var12 = z - (double)var7.getZ();
      		List var14 = this.worldObj.func_147461_a(this.getEntityBoundingBox());
      
      		// added to keep the item elevator for now
      		this.worldObj.getBlockState(var7).getBlock().setBlockBoundsBasedOnState(this.worldObj, var7);
      
      		if(var14.isEmpty() && !this.worldObj.func_175665_u(var7)) {
      			return false;
      		} else {
      			...
      		}
      	}
      }
      

      In case you completely get rid of the old system now or some time in future please still consider to leave the fence item elevators working by adding a few lines in this method.
      I would be killed by the technical community if I didn't at least try to make you keep the item elevators

      7. Affected blocks

      This issue pretty much affects all blocks which have a changeable collision box or a collision box which consists out of more than one bounding box.
      This list is probably not complete but I'll give my best. Also the effect of this issue might not show the same for all of them.

      The following blocks are probably affected by this glitch in some way:
      Snow layers, cobble wall, mossy cobble wall, ladders, iron bars, glass panes, heads, stairs, slabs, hopper, wooden trapdoors, iron trapdoors, all doors, cactus, anvils, chests, trapped chests, brewing stand, all fences, all fence gates, extended pistons, extending pistons, end portal frames, cake and cauldrons.

      8. TLDR

      Wow, that got long.

      In short: For each block just one global Bounding Box is saved in 6 integer values. They have to be set dependent on the state of a block in a certain position before anything is done with them.
      This calls for coding bugs and causes threading issues.

      I will also make a video explaining the basics. It might take a week till I find time for it though. <Link will follow>

      Thanks for reading. Forgive me all the typos, it's late

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                grum [Mojang] Grum (Erik Broes)
                Reporter:
                panda4994 Panda
              • Votes:
                41 Vote for this issue
                Watchers:
                24 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: