Jump to content

What is EntityTracker for? It drastically affects mob movement


MrChoke

Recommended Posts

In a large world the number of entities could get very large, so the server and the multiple clients only sync information about entities within a certain "tracking" distance. There is no need for server to be sending packets every tick to every client for entities that are not close to any of those players.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

Just now, jabelar said:

In a large world the number of entities could get very large, so the server and the multiple clients only sync information about entities within a certain "tracking" distance. There is no need for server to be sending packets every tick to every client for entities that are not close to any of those players.

Ok, what are good suggested values for:

updateFrequency,  sendVelocityUpdates

for a new type of Zombie? 

 

I was going to use 64 and 20...   I also saw examples of 80 and 3 but that was for animals like sheep.

 

Link to comment
Share on other sites

You should generally copy the vanilla values for similar sorts of entities unless you have a specific reason. For fast moving objects the update frequency is usually increased and the send velocity updates help smooth things -- basically use those for projectiles.

 

I have made bird entities before where I needed to greatly increase the tracking range (and render distance) because they were already flying like 80 blocks above the ground so if they would start not being tracked. But it is only in unusual cases where you need to do something different than the vanilla.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

On 9/27/2018 at 3:03 PM, jabelar said:

For fast moving objects the update frequency is usually increased

By increased I assume you mean that how often the entity updates is increased but the actual updateFrequency variable is decreased? Or have I been writing my code wrong this entire time?

 

I assume that the update handler is something like

if (time % frequency == 0) update();

Edited by Cadiboo
formatting

About Me

Spoiler

My Discord - Cadiboo#8887

My WebsiteCadiboo.github.io

My ModsCadiboo.github.io/projects

My TutorialsCadiboo.github.io/tutorials

Versions below 1.14.4 are no longer supported on this forum. Use the latest version to receive support.

When asking support remember to include all relevant log files (logs are found in .minecraft/logs/), code if applicable and screenshots if possible.

Only download mods from trusted sites like CurseForge (minecraft.curseforge.com). A list of bad sites can be found here, with more information available at stopmodreposts.org

Edit your own signature at www.minecraftforge.net/forum/settings/signature/ (Make sure to check its compatibility with the Dark Theme)

Link to comment
Share on other sites

I have a custom mob based off of a zombie.  The thing was moving much faster and more jerky (sudden stops and starts) then the vanilla zombie.  It took me quite a while to figure out why.  It is because of the EntityTracker.  This thing is like a black box to me.  I don't get it.  Why set speed values for the entity when the tracking values seem much more critical?

 

Also, you say use vanilla whenever possible.  Almost all vanilla mobs don't set anything!!!  See EntityTracker class, method:   

public void track(Entity entityIn)

 

Mobs do not get tracked.  Yet I believe Forge makes us set tracking values when we register a mob.  For mine cusotm Zombie I set 64 and 20.  The only way I got my mob to moving normally was by commenting out the first line in this method:

if (net.minecraftforge.fml.common.registry.EntityRegistry.instance().tryTrackingEntity(this, entityIn)) return;

 

I killed the forge hook.

 

Can someone explain why vanilla works fine with not having tracking set and Forge makes us set values?  What should I use to  make it act like a normal mob?

 

Thanks.

 

 

 

 

 

Link to comment
Share on other sites

40 minutes ago, MrChoke said:

64 and 20

Try a smaller value for the second parameter like 2. IE mimicing the EntityPlayerMP value.

VANILLA MINECRAFT CLASSES ARE THE BEST RESOURCES WHEN MODDING

I will be posting 1.15.2 modding tutorials on this channel. If you want to be notified of it do the normal YouTube stuff like subscribing, ect.

Forge and vanilla BlockState generator.

Link to comment
Share on other sites

12 minutes ago, Animefan8888 said:

Try a smaller value for the second parameter like 2. IE mimicing the EntityPlayerMP value.

Ok thanks.  So from that track method, sounds like you are saying use this:

this.track(entityIn, 512, 2);

 

Still wish I knew more about how vanilla mobs don't need it at all.  I probably need ot look at the code more I guess.

Link to comment
Share on other sites

Just now, MrChoke said:

Still wish I knew more about how vanilla mobs don't need it at all.  I probably need ot look at the code more I guess.

It probably has built in numbers deeper into the code specifically for all EntityMobs.

VANILLA MINECRAFT CLASSES ARE THE BEST RESOURCES WHEN MODDING

I will be posting 1.15.2 modding tutorials on this channel. If you want to be notified of it do the normal YouTube stuff like subscribing, ect.

Forge and vanilla BlockState generator.

Link to comment
Share on other sites

On 9/26/2018 at 11:31 PM, Cadiboo said:

By increased I assume you mean that how often the entity updates is increased but the actual updateFrequency variable is decreased? Or have I been writing my code wrong this entire time?

 

I assume that the update handler is something like

if (time % frequency == 0) update();

I think that is the opposite. "Frequency" means how often, so if you want more you would increase it. But I'm not entirely sure, let me check the code and I'll respond on the thread.

 

Some of the special vanilla trackers are set as follows:

    public void track(Entity entityIn)
    {
        if (net.minecraftforge.fml.common.registry.EntityRegistry.instance().tryTrackingEntity(this, entityIn)) return;

        if (entityIn instanceof EntityPlayerMP)
        {
            this.track(entityIn, 512, 2);
            EntityPlayerMP entityplayermp = (EntityPlayerMP)entityIn;

            for (EntityTrackerEntry entitytrackerentry : this.entries)
            {
                if (entitytrackerentry.getTrackedEntity() != entityplayermp)
                {
                    entitytrackerentry.updatePlayerEntity(entityplayermp);
                }
            }
        }
        else if (entityIn instanceof EntityFishHook)
        {
            this.track(entityIn, 64, 5, true);
        }
        else if (entityIn instanceof EntityArrow)
        {
            this.track(entityIn, 64, 20, false);
        }
        else if (entityIn instanceof EntitySmallFireball)
        {
            this.track(entityIn, 64, 10, false);
        }
        else if (entityIn instanceof EntityFireball)
        {
            this.track(entityIn, 64, 10, true);
        }
        else if (entityIn instanceof EntitySnowball)
        {
            this.track(entityIn, 64, 10, true);
        }
        else if (entityIn instanceof EntityLlamaSpit)
        {
            this.track(entityIn, 64, 10, false);
        }
        else if (entityIn instanceof EntityEnderPearl)
        {
            this.track(entityIn, 64, 10, true);
        }
        else if (entityIn instanceof EntityEnderEye)
        {
            this.track(entityIn, 64, 4, true);
        }
        else if (entityIn instanceof EntityEgg)
        {
            this.track(entityIn, 64, 10, true);
        }
        else if (entityIn instanceof EntityPotion)
        {
            this.track(entityIn, 64, 10, true);
        }
        else if (entityIn instanceof EntityExpBottle)
        {
            this.track(entityIn, 64, 10, true);
        }
        else if (entityIn instanceof EntityFireworkRocket)
        {
            this.track(entityIn, 64, 10, true);
        }
        else if (entityIn instanceof EntityItem)
        {
            this.track(entityIn, 64, 20, true);
        }
        else if (entityIn instanceof EntityMinecart)
        {
            this.track(entityIn, 80, 3, true);
        }
        else if (entityIn instanceof EntityBoat)
        {
            this.track(entityIn, 80, 3, true);
        }
        else if (entityIn instanceof EntitySquid)
        {
            this.track(entityIn, 64, 3, true);
        }
        else if (entityIn instanceof EntityWither)
        {
            this.track(entityIn, 80, 3, false);
        }
        else if (entityIn instanceof EntityShulkerBullet)
        {
            this.track(entityIn, 80, 3, true);
        }
        else if (entityIn instanceof EntityBat)
        {
            this.track(entityIn, 80, 3, false);
        }
        else if (entityIn instanceof EntityDragon)
        {
            this.track(entityIn, 160, 3, true);
        }
        else if (entityIn instanceof IAnimals)
        {
            this.track(entityIn, 80, 3, true);
        }
        else if (entityIn instanceof EntityTNTPrimed)
        {
            this.track(entityIn, 160, 10, true);
        }
        else if (entityIn instanceof EntityFallingBlock)
        {
            this.track(entityIn, 160, 20, true);
        }
        else if (entityIn instanceof EntityHanging)
        {
            this.track(entityIn, 160, Integer.MAX_VALUE, false);
        }
        else if (entityIn instanceof EntityArmorStand)
        {
            this.track(entityIn, 160, 3, true);
        }
        else if (entityIn instanceof EntityXPOrb)
        {
            this.track(entityIn, 160, 20, true);
        }
        else if (entityIn instanceof EntityAreaEffectCloud)
        {
            this.track(entityIn, 160, Integer.MAX_VALUE, true);
        }
        else if (entityIn instanceof EntityEnderCrystal)
        {
            this.track(entityIn, 256, Integer.MAX_VALUE, false);
        }
        else if (entityIn instanceof EntityEvokerFangs)
        {
            this.track(entityIn, 160, 2, false);
        }
    }

 

So you can see that fast moving things have higher numbers for higher frequency.

 

I think people are getting a bit confused. The actual movement of the entities on the server happens according to the movement (speed, pathfinding, etc.) but that information has to make it to all the clients attached to the game. The tracking range and frequency indicate how often that synchronization happen. There is a balance because if something is fast moving it should also have higher tracking frequency as it can get more out of sync between sync packets.

 

Anyway, again I would suggest using values similar to the vanilla.

 

 

Edited by jabelar
  • Thanks 1

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

29 minutes ago, jabelar said:

Anyway, again I would suggest using values similar to the vanilla.

The list there, doesn't contain any of the mobs and that was the authors problem. If you know where things such as EntityZombie or EntitySkeleton gain these values that is more of what they where looking for.

VANILLA MINECRAFT CLASSES ARE THE BEST RESOURCES WHEN MODDING

I will be posting 1.15.2 modding tutorials on this channel. If you want to be notified of it do the normal YouTube stuff like subscribing, ect.

Forge and vanilla BlockState generator.

Link to comment
Share on other sites

45 minutes ago, jabelar said:

I think that is the opposite. "Frequency" means how often, so if you want more you would increase it. But I'm not entirely sure, let me check the code and I'll respond on the thread.

The "most often" you can update an object is every frame and you have limited numbers slower than that if you consider that they have to be less than 1.

 

i.e. you can't update an object 0.8 (80%) of the time (what are you supposed to do any given frame?). So you end up with 0.5, 0.333, 0.25, 0.2 etc. as allowable values...

 

Or time%n == 0

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

On 9/26/2018 at 11:31 PM, Cadiboo said:

I assume that the update handler is something like

if (time % frequency == 0) update();

 

Confirmed you're correct. The update frequency is used in only one place (line 191 in EntityTrackerEntry) where the code to decide to send update has the condition:

        if (this.updateCounter % this.updateFrequency == 0 || this.trackedEntity.isAirBorne || this.trackedEntity.getDataManager().isDirty())

 

Note the isAirBorne check. So I guess the idea is that projectiles are covered with that?

 

To fully understand how things are updated, it seems most of the code is in the EntityTrackerEntry#updatePlayerList() method which has following code:

Spoiler

    public void updatePlayerList(List<EntityPlayer> players)
    {
        this.playerEntitiesUpdated = false;

        if (!this.updatedPlayerVisibility || this.trackedEntity.getDistanceSq(this.lastTrackedEntityPosX, this.lastTrackedEntityPosY, this.lastTrackedEntityPosZ) > 16.0D)
        {
            this.lastTrackedEntityPosX = this.trackedEntity.posX;
            this.lastTrackedEntityPosY = this.trackedEntity.posY;
            this.lastTrackedEntityPosZ = this.trackedEntity.posZ;
            this.updatedPlayerVisibility = true;
            this.playerEntitiesUpdated = true;
            this.updatePlayerEntities(players);
        }

        List<Entity> list = this.trackedEntity.getPassengers();

        if (!list.equals(this.passengers))
        {
            this.passengers = list;
            this.sendPacketToTrackedPlayers(new SPacketSetPassengers(this.trackedEntity));
        }

        if (this.trackedEntity instanceof EntityItemFrame && this.updateCounter % 10 == 0)
        {
            EntityItemFrame entityitemframe = (EntityItemFrame)this.trackedEntity;
            ItemStack itemstack = entityitemframe.getDisplayedItem();

            if (itemstack.getItem() instanceof ItemMap)
            {
                MapData mapdata = ((ItemMap) itemstack.getItem()).getMapData(itemstack, this.trackedEntity.world);

                for (EntityPlayer entityplayer : players)
                {
                    EntityPlayerMP entityplayermp = (EntityPlayerMP)entityplayer;
                    mapdata.updateVisiblePlayers(entityplayermp, itemstack);
                    Packet<?> packet = ((ItemMap) itemstack.getItem()).createMapDataPacket(itemstack, this.trackedEntity.world, entityplayermp);

                    if (packet != null)
                    {
                        entityplayermp.connection.sendPacket(packet);
                    }
                }
            }

            this.sendMetadata();
        }

        if (this.updateCounter % this.updateFrequency == 0 || this.trackedEntity.isAirBorne || this.trackedEntity.getDataManager().isDirty())
        {
            if (this.trackedEntity.isRiding())
            {
                int j1 = MathHelper.floor(this.trackedEntity.rotationYaw * 256.0F / 360.0F);
                int l1 = MathHelper.floor(this.trackedEntity.rotationPitch * 256.0F / 360.0F);
                boolean flag3 = Math.abs(j1 - this.encodedRotationYaw) >= 1 || Math.abs(l1 - this.encodedRotationPitch) >= 1;

                if (flag3)
                {
                    this.sendPacketToTrackedPlayers(new SPacketEntity.S16PacketEntityLook(this.trackedEntity.getEntityId(), (byte)j1, (byte)l1, this.trackedEntity.onGround));
                    this.encodedRotationYaw = j1;
                    this.encodedRotationPitch = l1;
                }

                this.encodedPosX = EntityTracker.getPositionLong(this.trackedEntity.posX);
                this.encodedPosY = EntityTracker.getPositionLong(this.trackedEntity.posY);
                this.encodedPosZ = EntityTracker.getPositionLong(this.trackedEntity.posZ);
                this.sendMetadata();
                this.ridingEntity = true;
            }
            else
            {
                ++this.ticksSinceLastForcedTeleport;
                long i1 = EntityTracker.getPositionLong(this.trackedEntity.posX);
                long i2 = EntityTracker.getPositionLong(this.trackedEntity.posY);
                long j2 = EntityTracker.getPositionLong(this.trackedEntity.posZ);
                int k2 = MathHelper.floor(this.trackedEntity.rotationYaw * 256.0F / 360.0F);
                int i = MathHelper.floor(this.trackedEntity.rotationPitch * 256.0F / 360.0F);
                long j = i1 - this.encodedPosX;
                long k = i2 - this.encodedPosY;
                long l = j2 - this.encodedPosZ;
                Packet<?> packet1 = null;
                boolean flag = j * j + k * k + l * l >= 128L || this.updateCounter % 60 == 0;
                boolean flag1 = Math.abs(k2 - this.encodedRotationYaw) >= 1 || Math.abs(i - this.encodedRotationPitch) >= 1;

                if (this.updateCounter > 0 || this.trackedEntity instanceof EntityArrow)
                {
                    if (j >= -32768L && j < 32768L && k >= -32768L && k < 32768L && l >= -32768L && l < 32768L && this.ticksSinceLastForcedTeleport <= 400 && !this.ridingEntity && this.onGround == this.trackedEntity.onGround)
                    {
                        if ((!flag || !flag1) && !(this.trackedEntity instanceof EntityArrow))
                        {
                            if (flag)
                            {
                                packet1 = new SPacketEntity.S15PacketEntityRelMove(this.trackedEntity.getEntityId(), j, k, l, this.trackedEntity.onGround);
                            }
                            else if (flag1)
                            {
                                packet1 = new SPacketEntity.S16PacketEntityLook(this.trackedEntity.getEntityId(), (byte)k2, (byte)i, this.trackedEntity.onGround);
                            }
                        }
                        else
                        {
                            packet1 = new SPacketEntity.S17PacketEntityLookMove(this.trackedEntity.getEntityId(), j, k, l, (byte)k2, (byte)i, this.trackedEntity.onGround);
                        }
                    }
                    else
                    {
                        this.onGround = this.trackedEntity.onGround;
                        this.ticksSinceLastForcedTeleport = 0;
                        this.resetPlayerVisibility();
                        packet1 = new SPacketEntityTeleport(this.trackedEntity);
                    }
                }

                boolean flag2 = this.sendVelocityUpdates;

                if (this.trackedEntity instanceof EntityLivingBase && ((EntityLivingBase)this.trackedEntity).isElytraFlying())
                {
                    flag2 = true;
                }

                if (flag2 && this.updateCounter > 0)
                {
                    double d0 = this.trackedEntity.motionX - this.lastTrackedEntityMotionX;
                    double d1 = this.trackedEntity.motionY - this.lastTrackedEntityMotionY;
                    double d2 = this.trackedEntity.motionZ - this.motionZ;
                    double d3 = 0.02D;
                    double d4 = d0 * d0 + d1 * d1 + d2 * d2;

                    if (d4 > 4.0E-4D || d4 > 0.0D && this.trackedEntity.motionX == 0.0D && this.trackedEntity.motionY == 0.0D && this.trackedEntity.motionZ == 0.0D)
                    {
                        this.lastTrackedEntityMotionX = this.trackedEntity.motionX;
                        this.lastTrackedEntityMotionY = this.trackedEntity.motionY;
                        this.motionZ = this.trackedEntity.motionZ;
                        this.sendPacketToTrackedPlayers(new SPacketEntityVelocity(this.trackedEntity.getEntityId(), this.lastTrackedEntityMotionX, this.lastTrackedEntityMotionY, this.motionZ));
                    }
                }

                if (packet1 != null)
                {
                    this.sendPacketToTrackedPlayers(packet1);
                }

                this.sendMetadata();

                if (flag)
                {
                    this.encodedPosX = i1;
                    this.encodedPosY = i2;
                    this.encodedPosZ = j2;
                }

                if (flag1)
                {
                    this.encodedRotationYaw = k2;
                    this.encodedRotationPitch = i;
                }

                this.ridingEntity = false;
            }

            int k1 = MathHelper.floor(this.trackedEntity.getRotationYawHead() * 256.0F / 360.0F);

            if (Math.abs(k1 - this.lastHeadMotion) >= 1)
            {
                this.sendPacketToTrackedPlayers(new SPacketEntityHeadLook(this.trackedEntity, (byte)k1));
                this.lastHeadMotion = k1;
            }

            this.trackedEntity.isAirBorne = false;
        }

        ++this.updateCounter;

        if (this.trackedEntity.velocityChanged)
        {
            this.sendToTrackingAndSelf(new SPacketEntityVelocity(this.trackedEntity));
            this.trackedEntity.velocityChanged = false;
        }
    }
 

 

One interesting thing is the EntityArrow is specially handled. Also Elytra flying is also specially handled.

 

  • Like 1

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

@jabelar  All that code in EntityTracker and EntityTrackerEntity is great but only if the entities make it into:

private final Set<EntityTrackerEntry> entries 

 

And unless track() is called on an entity, it never gets in there.  That includes all vanilla mobs.  There must be a completely different system for them somewhere.

 

I will say that setting 512, 2 on my custom mob fixed its issue.  The rubber-banding stopped.  So there is that.

Link to comment
Share on other sites

Starting and Stopping Tracking

 

A little more tracing of the code. In this case, this is the part that starts tracking or stops tracking.

 

So MinecraftServer#tick() method calls MinecraftServer#updateTimeLightAndEntities() method every tick. That method firstly actually updates the entities on the server then calls WorldServer#getEntityTracker().tick() method. The EntityTracker#tick() method first creates a list of all EntityPlayerMP then loops through that list and takes all EntityTrackerEntry instances in the EntityTracker and calls updatePlayerEntity() while passing the EntityPlayerMP instance. So that makes sense -- every tick every multiplayer player gets opportunity to get info on every tracked entity.

 

The updatePlayerEntity() method which is run on every entity for every multiplayer player firstly checks if the entity is visible (based on distance from the player) to the player and if not it removes the player from the tracking. If it is visible then and not already tracked it will start tracking.

 

Tracking For Entity State Change

 

It looks like a key method related to actually sending tracking updates is the WorldServer#setEntityState() method. That is called from a huge number of places in the code. In fact it looks like if you want to send a packet for your own custom entities, the proper way would be to call that method instead of "send to all" as it will only send to those players who are tracking (so should be less of a performance hit).

 

Note that vanilla is a bit inconsistent in this regard as in some places it calls EntityTracker#sendToTracking() or sendToTrackingAndSelf() directly.

 

 

 

 

 

 

 

 

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

23 minutes ago, MrChoke said:

@jabelar  All that code in EntityTracker and EntityTrackerEntity is great but only if the entities make it into:


private final Set<EntityTrackerEntry> entries 

 

And unless track() is called on an entity, it never gets in there.  That includes all vanilla mobs.  There must be a completely different system for them somewhere.

 

Yeah, I'm continuing to track it down the code but it seems hard to do. Basically when every entity including vanilla is added to the world there is a call to track(), but that method does a "try" for tracking which seems to rely on it being registered as a mod entity. In other words it checks to see if a mod entity is registered for it.

 

But it is weird because if you look at the code I posted in spoiler a few posts back, a fair number of vanilla things are given a tracker entry. But not all of them.

 

If you look at the update packets like S17PacketEntityLookMove, or SPacketEntityVelocity, they are only called the EntityTracker class. So does that mean they are only sent for mod entities and not vanilla? It is very convoluted (at least to me).

 

I'm traveling so don't have a computer (or time) to try to really trace through what's happening. I might later, but would be interesting if any of y'all could figure it out. In particular it would be interesting to print out the EntityTracker entries list to see if vanilla entities get in there somehow.

Edited by jabelar

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

15 minutes ago, jabelar said:

 

Yeah, I'm continuing to track it down the code but it seems hard to do. Basically when every entity including vanilla is added to the world there is a call to track(), but that method does a "try" for tracking which seems to rely on it being registered as a mod entity. In other words it checks to see if a mod entity is registered for it.

 

But it is weird because if you look at the code I posted in spoiler a few posts back, a fair number of vanilla things are given a tracker entry. But not all of them.

 

If you look at the update packets like S17PacketEntityLookMove, or SPacketEntityVelocity, they are only called the EntityTracker class. So does that mean they are only sent for mod entities and not vanilla? It is very convoluted (at least to me).

 

I'm traveling so don't have a computer (or time) to try to really trace through what's happening. I might later, but would be interesting if any of y'all could figure it out. 

I am going give up on it myself.  My best guess though I cannot confirm may lie in use of a class called SPacketEntity.  It has this code:

 

public void processPacket(INetHandlerPlayClient handler)
{
    handler.handleEntityMovement(this);
}

 

There is this class called:  PacketThreadUtil  that seems to run a bunch of scheduled tasks.  A packet for handling entity movement is one of them.  

  • Thanks 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.