-
Bug
-
Resolution: Duplicate
-
None
-
1.16.2 Pre-release 1, 1.16.2 Pre-release 2, 1.16.2 Pre-release 3, 1.16.3
-
None
-
Unconfirmed
-
Block states, Entities
I was suggested to remake and reword a similar ticket I made, MC-196897, as the wording of the ticket was incorrect for what the actual bug was.
The Bug:
Currently there is an inconsistency with how mobs enter the nether portal versus the end portal. A mob can enter the end portal after its movement as the end portal block itself calls the change dimensions. However this does not match the nether portal which requires two gameticks to go through due to the move method collision not calling the change dimensions directly but rather changing a boolean that is checked before the move method is called. This leads to the inconsistency in how the game handles the changing of dimensions and thus a bug.
How to reproduce:
- Create a flat world and teleport to 1000, 10 1000 via /tp 1000 10 1000
- Build a nether portal @ the cords 1009 4 1000 to 1009 4 997
- Light the portal and go thru to load and link the other side.
- Build a wall behind the portal on the east side.
- Wait 15 seconds so that the portal no longer loads the chunk as you came through it.
- Turn your render distance down to 2 and your entity distance up to max
- Go to the coords 989 5 1000 to make the portal chunk non-entity processing
- /summon pig 1007.00 5.67 999.45 {Motion:[4.0,0.0,0.0]}
- Observe that the pig is still there.
- Change the nether portal to end portals with a fill command.
- Re-summon the pig and watch it go through
- Video for the steps: https://youtu.be/2dg7jUIGeho
- Video excludes the end portal steps
Solution:
To fix the inconsistency as well as the bug, entities should change dimensions at the end of the tick by repositioning the tickNetherPortal() after the methods that call the move method (self movement/velocity) for most mobs.
Code analysis:
This code analysis is using Yarn mappings for 20w30a.
For this example, we will be using a pig and skip the beginning of the calling chain the starting the tick methods and go directly to the living entity part.
Here the Living entity calls the super first which will lead it down the nether portal check and changing of the dimensions. Later on, it calls the tickMovement(), which will call the move method and the impact check to say it hit a portal. Let's explore the super first
public abstract class LivingEntity extends Entity { //removed between public void tick() { super.tick(); //removed some lines between this.tickMovement();
From the super it goes to the entity class and calls the basetick almost right away to lets go down another level.
public abstract class Entity / /removed between public void tick() { //removed some lines between this.baseTick(); }
Here at the base tick it does a lot of stuff, but we are only interested in the ticknetherportal(), so let's look at that.
public void baseTick(){ //removed some lines between this.tickNetherPortal(); //removed some lines between }
In the tick nether portal, it gets the this.getMaxNetherPortalTime() which is used in a if check, but we can ignore it as it is zero for almost everything but players. The main thing we need to look at is that a if check looks at this.inNetherPortal, which can only be successful if the pig hits a portal in the previous tick, so as of now, this checks stops as the pig has not touched a portal yet.
protected void tickNetherPortal() { //removed some lines between int i = this.getMaxNetherPortalTime(); // =0 for non-players //removed some lines between if (this.inNetherPortal) { //removed some lines between if (lv3 != null && minecraftServer.isNetherAllowed() && !this.hasVehicle() && this.netherPortalTime++ >= i) { //removed some lines between this.changeDimension(lv3); } }
Now lets look around at the move method for the pig. It was called back from a chain of calls steaming from tickmovement(). Here in the move method, it will call this.checkBlockCollision(), which handles the effect of colliding with certain blocks.
public void move(MovementType arg2, Vec3d arg22) { //removed some lines between try{ this.checkBlockCollision(); } //removed some lines between }
Here it will call the the entity hitting the block. Let's first explore it hitting an end portal.
protected void checkBlockCollision() { //removed some lines between lv5.onEntityCollision(this.world, lv4, this); //removed some lines between }
If it hits an endportal, it will do some checks then make it change dimensions, thus allowing it to go the end after it's move method unlike the nether portal. Now let's look at the nether portal check.
public class EndPortalBlock @Override public void onEntityCollision(BlockState arg, World arg2, BlockPos arg3, Entity arg4) { //removed some lines between arg4.changeDimension(lv2); }
The nether portal check does a few checks then call the entity's setInNetherPortal method. So lets look at that for the final thing.
public class NetherPortalBlock public void onEntityCollision(BlockState arg, World arg2, BlockPos arg3, Entity arg4) { if (!arg4.hasVehicle() && !arg4.hasPassengers() && arg4.canUsePortals()) { arg4.setInNetherPortal(arg3); } }
Here is basically says the pig is in a nether portal so now when the portal tick method of the entity is called the pig can go in. This makes it unlike the end portal in which it will happen on the second tick instead of the first/same tick.
public abstract class Entity public void setInNetherPortal(BlockPos arg) { //removed some lines between this.inNetherPortal = true; }
- duplicates
-
MC-196897 Entities that move (Velocity) into a nether portal don't get sent to the nether in the same gametick
- Resolved