Jump to content

Recommended Posts

Posted

Hello, I've created custom vehicle entity which is controllable. However I have quite big issue - as soon as I attempt to drive, the entity teleports back few times and then starts moving (while sometimes teleporting a bit). Also when I dismount the entity, it starts teleporting every second until it loses it's velocity. I have been reading few threads about this, but nothing worked. I made whole entity move on both sides, even started sending input packets to have most precise calculations, but nothing works. I'm out of ideas. I have been playing quite a lot with the updateFrequency on registration, but couldn't find anything what would work perfectly, it always had some kind of teleportation issues.

For registration I use the forge event and entry system, with tracking range of 256 blocks and update frequency of 20.

 

If it's helpful there's the entity code:

Spoiler

	private static final Predicate<Entity> TARGET = Predicates.and(EntitySelectors.NOT_SPECTATING, EntitySelectors.IS_ALIVE, Entity::canBeCollidedWith);
	private static final AxisAlignedBB BOX = new AxisAlignedBB(-0.5d, 0d, -0.5d, 1.5d, 1d, 1.5d);
	private static final float MAX_TURNING_MODIFIER = 3F;
	
	private boolean isWaterVehicle;
	
	/** The max damage vehicle can take before it explodes **/
	public float maxHealth = 100f;
	
	/** Current health state **/
	public float health = 100f;
	
	/** Acceleration speed **/
	public float acceleration = 0.08f;
	
	/** Speed at which will vehicle turn **/
	public float turnSpeed = 0.45f;
	
	/** The max speed vehicle will be able to do **/
	public float maxSpeed = 1.2f;
	
	public float currentSpeed = 0;
	
	/** How well will the vehicle drive on different surfaces **/
	public float turnModifier;
	
	//TODO implement
	public float fuel = 100f;
	
	/** If the vehicle is broken or not **/
	private boolean isBroken = false;
	
	/** Ticks underWater **/
	private short timeInInvalidState;
	
	/** Handles inputs from player who is driving the vehicle **/
	private boolean inputForward, inputBack, inputRight, inputLeft, inputBoost;
	
	public EntityVehicle(World world)
	{
		super(world);
		setSize(1f, 1f);
		stepHeight = 1f;
		preventEntitySpawning = true;
		maxSpeed = 1.0f;
	}
	
	public EntityVehicle(World world, int x, int y, int z)
	{
		this(world);
		setPosition(x, y, z);
	}
	
	@Override
	public void onUpdate()
	{
		super.onUpdate();

		if(!this.isBeingRidden() && (!noAccerationInput() || !noTurningInput()))
		{
			inputForward = false;
			inputBack = false;
			inputRight = false;
			inputLeft = false;
		}
		
		updateMotion();
		handleEntityCollisions();
		if(!world.isRemote) checkState();

		move(MoverType.SELF, motionX, motionY, motionZ);
	}
	
	public void handleEntityCollisions()
	{
		Vec3d vec1 = new Vec3d(posX, posY, posZ);
		Vec3d vec2 = new Vec3d(vec1.x + motionX, vec1.y + motionY, vec1.z + motionZ);
		Entity e = findEntityInPath(vec1, vec2);
		
		if(e != null)
		{
			e.motionX += motionX * currentSpeed;
			e.motionY += currentSpeed / 2;
			e.motionZ += motionZ * currentSpeed;
			e.attackEntityFrom(PMCDamageSources.VEHICLE, currentSpeed * 10f);
		}
	}
	
	public void updateMotion()
	{
		Vec3d lookVec = getLookVec();
		
		if(!isBroken && hasFuel())
		{
			if(inputForward && !inputBack)
			{
				burnFuel();
				currentSpeed = currentSpeed < maxSpeed ? currentSpeed + acceleration : maxSpeed;
			}
			
			if(inputBack && !inputForward)
			{
				burnFuel();
				currentSpeed = currentSpeed > 0 ? currentSpeed - acceleration : currentSpeed > (-maxSpeed * 0.3f) ? currentSpeed - 0.02f : -maxSpeed * 0.3f;
			}
		}
		
		if(inputRight && !inputLeft)
		{
			turnModifier = turnModifier < MAX_TURNING_MODIFIER ? turnModifier + turnSpeed : MAX_TURNING_MODIFIER;
		}
		
		if(inputLeft && !inputRight)
		{
			turnModifier = turnModifier > -MAX_TURNING_MODIFIER ? turnModifier - turnSpeed : -MAX_TURNING_MODIFIER;
		}
		
		if(noAccerationInput() || isBroken)
		{
			if(Math.abs(currentSpeed) < 0.009f) currentSpeed = 0f;
			
			if(currentSpeed != 0)
			{
				currentSpeed = currentSpeed > 0 ? currentSpeed - 0.008f : currentSpeed + 0.008f;
			}
		}
		
		if(noTurningInput())
		{
			if(Math.abs(turnModifier) < 0.1f) turnModifier = 0f;
			
			if(turnModifier != 0)
			{
				turnModifier = turnModifier > 0 ? turnModifier - 0.3f : turnModifier + 0.3f;
			}
		}
		
		motionX = lookVec.x * currentSpeed;
		motionZ = lookVec.z * currentSpeed;
		if(currentSpeed != 0)
		{
			rotationYaw += currentSpeed > 0 ? turnModifier : -turnModifier;
		}
		
		if(!onGround) motionY -= 0.1d;
	}
	
	@Nullable
	protected Entity findEntityInPath(Vec3d start, Vec3d end)
	{
		Entity e = null;
		List<Entity> entityList = world.getEntitiesInAABBexcluding(this, this.getEntityBoundingBox().expand(motionX, motionY, motionZ).grow(1d), TARGET);
		double d0 = 0;
		
		for(int i = 0; i < entityList.size(); i++)
		{
			Entity entity = entityList.get(i);
			
			if(entity != this)
			{
                AxisAlignedBB axisalignedbb = entity.getEntityBoundingBox().grow(0.30000001192092896D);
                RayTraceResult raytraceresult = axisalignedbb.calculateIntercept(start, end);
                
                if (raytraceresult != null)
                {
                    double d1 = start.squareDistanceTo(raytraceresult.hitVec);
                    
                    if (d1 < d0 || d0 == 0.0D)
                    {
                        e = entity;
                        d0 = d1;
                    }
                }
			}
		}
		
		return e;
	}
	
	@Override
	public boolean processInitialInteract(EntityPlayer player, EnumHand hand)
	{
		if(!world.isRemote)
		{
			if(this.canBeRidden(player))
			{
				player.startRiding(this);
			}
		}
		
		return true;
	}
	
	private void explode()
	{
		if(!world.isRemote)
		{
			this.world.createExplosion(this, posX, posY, posZ, 3f, false);
			this.setDead();
		}
	}
	
	// Should be running only on server side in case some client doesn't receive packet
	// containing new health value of this vehicle
	protected void checkState()
	{
		// if whole vehicle is under water -> can drive in shallow water
		if(this.isInWater() && world.getBlockState(getPosition().up()).getMaterial().isLiquid())
		{
			timeInInvalidState++;
			motionX *= 0.4d;
			motionZ *= 0.4d;
			motionY = -0.15d;
		}
		
		if(timeInInvalidState > 30)
		{
			isBroken = true;
		}
		
		if(isInLava() || health <= 0f) explode();
	}
	
	@Override
	public boolean attackEntityFrom(DamageSource source, float amount)
	{
		if(!getPassengers().contains(source.getTrueSource()))
		{
			this.health -= amount;
		}
		
		return true;
	}
	
	public void handleInputs(boolean forward, boolean back, boolean right, boolean left, EntityPlayer player)
	{
		if(isPlayerDriver(player))
		{
			this.inputForward = forward;
			this.inputBack = back;
			this.inputLeft = left;
			this.inputRight = right;
		}
	}
	
	@Override
	public boolean isInRangeToRenderDist(double distance) 
	{
		return true;
	}
	
	public boolean isPlayerDriver(EntityPlayer player)
	{
		return player.isRiding() && player.getRidingEntity() instanceof EntityVehicle && player.getRidingEntity().getPassengers().get(0) == player;
	}
	
	private boolean isVehicleMoving()
	{
		return currentSpeed != 0;
	}
	
	private boolean isVehicleMovingForward()
	{
		return currentSpeed > 0;
	}
	
	private boolean isVehicleMovingBackward()
	{
		return currentSpeed < 0;
	}
	
	@Override
	protected void entityInit() 
	{
	}
	
	@Override
	protected void readEntityFromNBT(NBTTagCompound compound)
	{
		posX = compound.getDouble("posX");
		posY = compound.getDouble("posY");
		posZ = compound.getDouble("posZ");
		motionX = compound.getDouble("motionX");
		motionY = compound.getDouble("motionY");
		motionZ = compound.getDouble("motionZ");
		health = compound.getFloat("health");
		fuel = compound.getFloat("fuel");
		currentSpeed = compound.getFloat("speed");
		acceleration = compound.getFloat("acceleration");
		turnSpeed = compound.getFloat("turnSpeed");
		isBroken = compound.getBoolean("isBroken");
	}
	
	@Override
	protected void writeEntityToNBT(NBTTagCompound compound) 
	{
		compound.setDouble("posX", this.posX);
		compound.setDouble("posY", this.posY);
		compound.setDouble("posZ", this.posZ);
		compound.setDouble("motionX", this.motionX);
		compound.setDouble("motionY", this.motionY);
		compound.setDouble("motionZ", this.motionZ);
		compound.setFloat("health", this.health);
		compound.setFloat("fuel", this.fuel);
		compound.setFloat("speed", this.currentSpeed);
		compound.setFloat("acceleration", this.acceleration);
		compound.setFloat("turnSpeed", this.turnSpeed);
		compound.setBoolean("isBroken", this.isBroken);
	}
	
	@Override
	public boolean canBeCollidedWith()
	{
		return true;
	}
	
	public boolean noAccerationInput()
	{
		return !inputForward && !inputBack;
	}
	
	public boolean noTurningInput()
	{
		return !inputRight && !inputLeft;
	}
	
	public void setAllRequiredValues(float maxHealth, float health, float maxSpeed, float acceleration, float turnSpeed)
	{
		this.maxHealth = maxHealth;
		this.health = health;
		this.maxSpeed = maxSpeed;
		this.acceleration = acceleration;
		this.turnSpeed = turnSpeed;
	}
	
	public boolean hasFuel()
	{
		return fuel > 0; 
	}
	
	/** Call with Fuel can **/
	public void refill()
	{
		fuel = fuel + 30f < 100f ? fuel + 30f : 100f;
	}
	
	/** Decrement each tick **/
	public void burnFuel()
	{
		fuel = hasFuel() ? fuel - 0.05f : 0f;
	}

 

 

Input is being taken from ClientTickEvent and is synced throught packet to the entity - not sure if it's good idea, but I gave it a try, since nothing seemed to work

 

Posted

You may already know this, but an update frequency of 20 means once per second.  The variable is not actually the frequency; it is actually the delay (opposite of frequency).  Ie. it's not 20 updates per tick, it's one update per 20 ticks.  If you haven't tried setting it to 1, go ahead and try it just to see if it resolves the issue or not.  It will give a better idea of where the issue may lie.

 

Iirc, either ridden entities or riding entities (or maybe both?  I can't remember) update every tick (update frequency of 1)...but I could be remembering that wrong.  I know animals use an update frequency of 3, and they're all over the place, so quite honestly having your vehicles update every tick may not be an issue.  This somewhat depends on how many vehicles you expect to be loaded at once I guess, but that falls more into the server admin category imo.

I was going to mention the possibility of rubberbanding if you don't use the built-in motion variables, but it looks like you're doing so.  Still might want to verify them though, just in case they're eg. being transferred *before* you update them each tick or something.

 

Oh, and if your tracker entry does not have "sendVelocityUpdates" set to true, you may want to try setting that to true as well.

Also, if you're doing any position/velocity computation client-side, it may actually be causing conflicts with the normal position/velocity tracking.  First I'd try removing all custom client-side position/velocity and use and update frequency of 1 with velocity updates enabled.  If it works nicely, you can try disabling velocity updates or increasing the delay between updates a bit and see what happens.  If it doesn't work...well then something more complicated is probably going wrong.

Posted

Okay, thanks for suggestion and information, I thought updateFrequency is how often is entity being updated... 

Anyway, I have tried pretty much all values ranging from 1 to 80 and every time I had issues. When I used 1 from what I remember the entity was rubberbanding quite a lot. I will try to move the motion logic on server, to see if it helps, although I'm pretty sure I've seen someone comment here about how it is important to handle motion logic on both sides, and I don't really think there would be different calculations on server and client (but I'm not expert at all). I was even debugging all motion related variables to see if they're the same and they were. I will try to move it on server, if it improves at least a bit I will be happy. Thanks for a lot of information

Posted
6 hours ago, Toma™ said:

I've seen someone comment here about how it is important to handle motion logic on both sides

They were absolutely right, but the entity tracker does this automatically, which is why if you also do it, and end up with different values, you will absolutely see rubberbanding, and if that happens then it will be worse at higher update frequencies (eg. when setting it to 1).

Basically you should either be using the built-in entity tracker synchronization, or you should use your own custom synchronization, but not both.  I'd suggest trying the built-in one first because if it works, it's a lot less programming.

  • Like 1
Posted

It appears that moving whole movement logic on server pretty much solved the issue. However the entity still sometimes rubberbands, but I guess thats because of it's speed. I changed the updateFrequency to 1. Thanks a lot for help

Posted

No problem.  And yeah, if it's moving faster than most vanilla stuff you might still notice some rubberbanding.  Hopefully it's not too bad though, because trying to fix it in an accurate way is a pain if you're already at max update frequency (1).

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.